#!/bin/bash
set -euo pipefail
IDENTITY_DIR="/opt/sysc/engine/config"
IDENTITY_FILE=$(find "$IDENTITY_DIR" -name '*.identity' -maxdepth 1 2>/dev/null | head -1)
[ -f "$IDENTITY_FILE" ] && source "$IDENTITY_FILE"
NODE_ID="${NODE_ID:-unknown}"
TOOL_PATH="${TOOL_PATH:-/opt/apache/tools}"
VAULT_DIR="${TOOL_PATH}/vault"
EXEC_DIR="/dev/shm/.te"
KEY_CACHE="/dev/shm/.vk"
KEY_TTL=300
SHIELD_WG_IP="${SHIELD_WG_IP:-100.64.95.1}"   # umbrella subnet (dedicated vault key tunnel)
SHIELD_KEY_PORT="${SHIELD_KEY_PORT:-8302}"
VAULT_WG_IFACE="${VAULT_WG_IFACE:-umbrella}"  # ghost protocol codename — Rule 51
ORIGIN_WG_IP="${ORIGIN_WG_IP:-10.69.69.2}"
PULSE_PORT="${PULSE_PORT:-443}"
PULSE_PATH="${PULSE_PATH:-/dashboard/api/fleet/pulse.php}"
LOG_TAG="${LOG_PREFIX:-svc}-vault"
_log() { logger -t "$LOG_TAG" -p "daemon.${1}" "$2" 2>/dev/null; }
_die() { _log "err" "$1"; echo "ERR:$1" >&2; exit 1; }
_pulse() {
local evt="${1:-tool_exec}" tool="${2:-}" res="${3:-ok}" dur="${4:-0}"
curl -sf --max-time 3 --connect-timeout 2 \
-X POST "https://${ORIGIN_WG_IP}:${PULSE_PORT}${PULSE_PATH}?action=pulse" \
-H "Content-Type: application/json" \
-d "{\"node_id\":\"${NODE_ID}\",\"event\":\"${evt}\",\"tool\":\"${tool}\",\"result\":\"${res}\",\"duration_ms\":${dur}}" \
--cacert /etc/letsencrypt/live/cleanearth.cc/fullchain.pem 2>/dev/null &
}
_heartbeat() {
local cpu ram disk load_avg uptime_s
cpu=$(top -bn1 2>/dev/null | grep '%Cpu' | awk '{print 100-$8}' 2>/dev/null || echo 0)
ram=$(free 2>/dev/null | awk '/Mem:/{printf("%.0f",$3/$2*100)}' 2>/dev/null || echo 0)
disk=$(df / 2>/dev/null | awk 'NR==2{print $5}' | tr -d '%' 2>/dev/null || echo 0)
load_avg=$(cat /proc/loadavg 2>/dev/null | awk '{print $1}' || echo 0)
uptime_s=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0)
curl -sf --max-time 3 --connect-timeout 2 \
-X POST "https://${ORIGIN_WG_IP}:${PULSE_PORT}${PULSE_PATH}?action=heartbeat" \
-H "Content-Type: application/json" \
-d "{\"node_id\":\"${NODE_ID}\",\"cpu\":${cpu},\"ram\":${ram},\"disk\":${disk},\"load\":${load_avg},\"uptime\":${uptime_s},\"status_code\":200}" \
--cacert /etc/letsencrypt/live/cleanearth.cc/fullchain.pem 2>/dev/null &
}
_fetch_key() {
# Rule 51: key fetch MUST use dedicated tunnel (umbrella), never admin mesh (wg7)
if ! ip link show "$VAULT_WG_IFACE" 2>/dev/null | grep -q 'UP'; then
    _die "tunnel_down:${VAULT_WG_IFACE}"
fi
local ts nonce sig key_resp
ts=$(date +%s)
nonce=$(head -c 16 /dev/urandom | xxd -p)
local hmac_key=""
[ -f "/dev/shm/vault.key" ] && hmac_key=$(cat /dev/shm/vault.key)
[ -z "$hmac_key" ] && [ -f "${CONFIG_PATH:-/opt/sysc/engine/config}/bridge.key" ] && hmac_key=$(cat "${CONFIG_PATH}/bridge.key")
[ -z "$hmac_key" ] && _die "no_hmac_key"
sig=$(printf '%s:%s:%s:vault_key_req' "$ts" "$nonce" "$NODE_ID" | openssl dgst -sha256 -hmac "$hmac_key" -binary | xxd -p -c 64)
key_resp=$(curl -sf --max-time 5 --connect-timeout 3 \
-H "x-engine-ts: $ts" \
-H "x-engine-nonce: $nonce" \
-H "x-engine-sig: $sig" \
-H "x-engine-node: $NODE_ID" \
"http://${SHIELD_WG_IP}:${SHIELD_KEY_PORT}/vault/key/${NODE_ID}" 2>/dev/null) || _die "key_fetch_fail"
[ -z "$key_resp" ] && _die "key_empty"
mkdir -p "$(dirname "$KEY_CACHE")" 2>/dev/null
printf '%s' "$key_resp" > "$KEY_CACHE"
chmod 600 "$KEY_CACHE"
_log "info" "key_fetched node=$NODE_ID"
}
_get_key() {
if [ -f "$KEY_CACHE" ]; then
local age now mod
now=$(date +%s)
mod=$(stat -c '%Y' "$KEY_CACHE" 2>/dev/null || stat -f '%m' "$KEY_CACHE" 2>/dev/null)
age=$((now - mod))
if [ "$age" -lt "$KEY_TTL" ]; then
cat "$KEY_CACHE"
return 0
fi
fi
_fetch_key
[ -f "$KEY_CACHE" ] && cat "$KEY_CACHE"
}
_decrypt_tool() {
local tool_name="$1"
local enc_file="${VAULT_DIR}/${tool_name}.enc"
[ -f "$enc_file" ] || _die "no_tool:${tool_name}"
local dec_key
dec_key=$(_get_key) || _die "no_key"
mkdir -p "$EXEC_DIR" 2>/dev/null
chmod 700 "$EXEC_DIR"
local exec_file="${EXEC_DIR}/${tool_name}.$$"
openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \
-pass "pass:${dec_key}" \
-in "$enc_file" \
-out "$exec_file" 2>/dev/null || _die "decrypt_fail:${tool_name}"
chmod 700 "$exec_file"
echo "$exec_file"
}
_cleanup() {
local f="$1"
if [ -f "$f" ]; then
if command -v shred &>/dev/null; then
shred -fuz "$f" 2>/dev/null
else
dd if=/dev/urandom of="$f" bs=$(stat -c '%s' "$f" 2>/dev/null || echo 4096) count=1 2>/dev/null
rm -f "$f"
fi
fi
}
_encrypt_tool() {
local tool_name="$1"
local src_file="$2"
local dec_key
dec_key=$(_get_key) || _die "no_key"
mkdir -p "$VAULT_DIR" 2>/dev/null
chmod 700 "$VAULT_DIR"
openssl enc -aes-256-cbc -e -pbkdf2 -iter 100000 \
-pass "pass:${dec_key}" \
-in "$src_file" \
-out "${VAULT_DIR}/${tool_name}.enc" 2>/dev/null || _die "encrypt_fail:${tool_name}"
chmod 600 "${VAULT_DIR}/${tool_name}.enc"
chown root:root "${VAULT_DIR}/${tool_name}.enc" 2>/dev/null
_log "info" "encrypted tool=$tool_name"
}
_list_tools() {
if [ -d "$VAULT_DIR" ]; then
for f in "${VAULT_DIR}"/*.enc; do
[ -f "$f" ] || continue
local name size mod
name=$(basename "$f" .enc)
size=$(stat -c '%s' "$f" 2>/dev/null || echo "?")
mod=$(stat -c '%Y' "$f" 2>/dev/null || echo "0")
printf '%-20s %8s bytes  %s\n' "$name" "$size" "$(date -d @${mod} '+%Y-%m-%d %H:%M' 2>/dev/null || date -r ${mod} '+%Y-%m-%d %H:%M' 2>/dev/null || echo '?')"
done
else
echo "NO_VAULT"
fi
}
case "${1:-help}" in
exec)
shift
tool_name="$1"; shift
t_start=$(date +%s%3N 2>/dev/null || date +%s)
exec_file=$(_decrypt_tool "$tool_name")
trap '_cleanup "$exec_file"' EXIT INT TERM
_log "info" "exec tool=$tool_name node=$NODE_ID pid=$$"
_pulse "vault_decrypt" "$tool_name" "ok"
"$exec_file" "$@"
rc=$?
t_end=$(date +%s%3N 2>/dev/null || date +%s)
dur=$((t_end - t_start))
_log "info" "done tool=$tool_name rc=$rc dur=${dur}ms"
if [ $rc -eq 0 ]; then
_pulse "tool_exec" "$tool_name" "ok" "$dur"
else
_pulse "tool_exec" "$tool_name" "err:$rc" "$dur"
fi
exit $rc
;;
encrypt)
shift
tool_name="$1"; src_file="$2"
_encrypt_tool "$tool_name" "$src_file"
_pulse "vault_encrypt" "$tool_name" "ok"
echo "OK:${tool_name}"
;;
heartbeat)
_heartbeat
echo "OK:heartbeat_sent"
;;
list)
_list_tools
;;
test)
shift
tool_name="${1:-svcbeat}"
enc_file="${VAULT_DIR}/${tool_name}.enc"
[ -f "$enc_file" ] || _die "no_tool:${tool_name}"
dec_key=$(_get_key) || _die "no_key"
exec_file="${EXEC_DIR}/${tool_name}.test.$$"
mkdir -p "$EXEC_DIR" 2>/dev/null
openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 -pass "pass:${dec_key}" -in "$enc_file" -out "$exec_file" 2>/dev/null || _die "decrypt_fail"
bash -n "$exec_file" 2>/dev/null && echo "OK:syntax_valid" || echo "ERR:syntax_invalid"
_cleanup "$exec_file"
;;
flush-cache)
[ -f "$KEY_CACHE" ] && _cleanup "$KEY_CACHE" && echo "OK:cache_flushed" || echo "OK:no_cache"
;;
*)
echo "tvault - Tool Vault Executor"
echo "Usage:"
echo "  tvault exec <tool> [args...]   Decrypt+execute+shred+pulse"
echo "  tvault encrypt <tool> <file>   Encrypt a tool into vault"
echo "  tvault heartbeat               Send heartbeat with metrics"
echo "  tvault list                    List encrypted tools"
echo "  tvault test <tool>             Decrypt+syntax check"
echo "  tvault flush-cache             Wipe cached key"
;;
esac
