clawdie-ai/scripts/hostd-call.sh
Operator & Claude Code 0340674432 Eliminate runtime sudo: hostd migration, module stripping, hostd shell client
- Remove sudo fallbacks from telegram-commands.ts and channels/telegram.ts
- Refactor startup-report.ts to accept pre-fetched hostd data (async path)
- Add bastille-destroy hostd op to privileged-commands.ts
- Strip TTS/STT/vision/chat-policy/model-catalog from index.ts
- Add scripts/hostd-call.sh for shell script -> hostd communication
- Remaining runtime sudo: startup-report sync fallback (lines 847-850)

---
Build: pass | Tests: n/a (Linux agent, 3 pre-existing controlplane-db errors)

---
Build: FAIL | Tests: FAIL — 16 failed
2026-05-10 14:57:28 +02:00

119 lines
4.1 KiB
Bash
Executable file

#!/bin/sh
#
# hostd-call.sh — Call the hostd Unix socket API from POSIX sh.
#
# Lets shell scripts invoke privileged operations (bastille, zfs, etc.)
# through the hostd daemon without needing sudo or Node.js.
#
# Usage: hostd-call.sh <op> [json-params]
# op Operation name (e.g. "bastille-list", "zfs-snapshot")
# json-params Optional JSON object for params (e.g. '{"jail":"db"}')
#
# Environment:
# HOSTD_SOCKET Socket path (default: /var/run/clawdie-hostd.sock)
# HOSTD_TOKEN Auth token (overrides env/file lookup)
# CONTROLPLANE_SHARED_SECRET Shared secret (same as Node.js runtime)
# HOSTD_TOKEN_FILE Token file (default: ~/.config/clawdie/hostd-token)
#
# Examples:
# hostd-call.sh bastille-list
# hostd-call.sh bastille-start '{"jail":"db"}'
# hostd-call.sh zfs-snapshot '{"dataset":"zroot/data","name":"pre-deploy"}'
set -e
op="${1:?Usage: hostd-call.sh <op> [json-params]}"
params="$2"
: "${params:={}}"
socket="${HOSTD_SOCKET:-/var/run/clawdie-hostd.sock}"
token_file="${HOSTD_TOKEN_FILE:-$HOME/.config/clawdie/hostd-token}"
if [ -n "${HOSTD_TOKEN:-}" ]; then
token="$HOSTD_TOKEN"
elif [ -n "${CONTROLPLANE_SHARED_SECRET:-}" ]; then
token="$CONTROLPLANE_SHARED_SECRET"
elif [ -r "$token_file" ]; then
token=$(cat "$token_file")
else
echo "hostd-call: no auth token — set HOSTD_TOKEN, CONTROLPLANE_SHARED_SECRET, or create $token_file" >&2
exit 1
fi
token=$(printf '%s' "$token" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if command -v uuidgen >/dev/null 2>&1; then
id=$(uuidgen)
elif [ -r /proc/sys/kernel/random/uuid ]; then
id=$(cat /proc/sys/kernel/random/uuid)
else
id="hostd-$(date +%s)-$$"
fi
token_esc=$(printf '%s' "$token" | sed 's/\\/\\\\/g; s/"/\\"/g')
op_esc=$(printf '%s' "$op" | sed 's/\\/\\\\/g; s/"/\\"/g')
request=$(printf '{"id":"%s","op":"%s","params":%s,"auth":{"token":"%s","caller":"operator","tenantId":""}}' \
"$id" "$op_esc" "$params" "$token_esc")
if command -v nc >/dev/null 2>&1; then
response=$(printf '%s\n' "$request" | nc -U -w 10 "$socket" 2>/dev/null) || true
elif command -v node >/dev/null 2>&1; then
response=$(printf '%s\n' "$request" | HOSTD_SOCKET_PATH="$socket" node -e '
var net=require("net"),fs=require("fs"),b="";
var c=net.connect(process.env.HOSTD_SOCKET_PATH);
c.on("connect",function(){c.write(fs.readFileSync("/dev/stdin","utf8"));});
c.on("data",function(d){b+=d.toString();if(b.indexOf("\n")>=0){process.stdout.write(b);c.destroy();process.exit(0);}});
setTimeout(function(){if(b)process.stdout.write(b);process.exit(1);},10000);
' 2>/dev/null) || true
else
echo "hostd-call: need nc or node to connect to Unix socket" >&2
exit 1
fi
if [ -z "$response" ]; then
echo "hostd-call: no response from $socket (is hostd running?)" >&2
exit 1
fi
printf '%s\n' "$response" | awk '
function jstr(s, key, idx,rest,val,c,nc,i) {
idx = index(s, "\"" key "\"")
if (!idx) return ""
rest = substr(s, idx + length(key) + 2)
sub(/^[[:space:]]*:[[:space:]]*/, "", rest)
if (substr(rest,1,1) != "\"") return ""
rest = substr(rest, 2)
val = ""
for (i = 1; i <= length(rest); i++) {
c = substr(rest, i, 1)
if (c == "\\" && i < length(rest)) {
nc = substr(rest, i+1, 1)
if (nc == "n") { val = val "\n"; i++; continue }
if (nc == "t") { val = val "\t"; i++; continue }
if (nc == "r") { val = val "\r"; i++; continue }
if (nc == "\\") { val = val "\\"; i++; continue }
if (nc == "\"") { val = val "\""; i++; continue }
if (nc == "/") { val = val "/"; i++; continue }
val = val nc; i++; continue
}
if (c == "\"") return val
val = val c
}
return val
}
{
ok = (match($0, /"ok"[[:space:]]*:[[:space:]]*true/) > 0)
out = jstr($0, "output")
err = jstr($0, "error")
if (ok) {
if (out != "") printf "%s\n", out
exit 0
} else {
if (err != "") printf "%s\n", err > "/dev/stderr"
else if (out != "") printf "%s\n", out > "/dev/stderr"
else printf "hostd-call: unknown error\n" > "/dev/stderr"
exit 1
}
}'