#!/usr/bin/env bash
set -euo pipefail
TOOL_NAME="hello"
TOOL_VERSION="2.0.0"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
SESSION_ROOT="${SESSION_ROOT:-/opt/3x5/session}"
ACTIVE_DIR="${SESSION_ROOT}/active"
EXPIRED_DIR="${SESSION_ROOT}/expired"
LOCK_DIR="${SESSION_ROOT}/locks"
HISTORY_DIR="${SESSION_ROOT}/history"
DEFAULT_TTL="${HELLO_DEFAULT_TTL:-900}"
DEFAULT_SCOPE="${HELLO_DEFAULT_SCOPE:-maintenance}"
MAX_TTL="${HELLO_MAX_TTL:-7200}"
mkdir -p "$ACTIVE_DIR" "$EXPIRED_DIR" "$LOCK_DIR" "$HISTORY_DIR"
session_log_file() { date -u +%F; }
json_escape_local() {
local s="${1:-}"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\r'/\\r}"
s="${s//$'\t'/\\t}"
printf '%s' "$s"
}
now_epoch() { date -u +%s; }
now_iso()   { date -u +%Y-%m-%dT%H:%M:%SZ; }
append_history() {
local event="$1" sid="$2" actor="$3" purpose="$4" scope="$5" status="$6" detail="${7:-}"
local file="${HISTORY_DIR}/$(session_log_file).log"
umask 077
printf '{"ts":"%s","event":"%s","session_id":"%s","actor":"%s","node":"%s","purpose":"%s","scope":"%s","status":"%s","source_ip":"%s","detail":"%s"}\n' \
"$(now_iso)" \
"$(json_escape_local "$event")" \
"$(json_escape_local "$sid")" \
"$(json_escape_local "$actor")" \
"$(json_escape_local "${NODE_ID:-unknown}")" \
"$(json_escape_local "$purpose")" \
"$(json_escape_local "$scope")" \
"$(json_escape_local "$status")" \
"$(json_escape_local "${SSH_CLIENT%% *}")" \
"$(json_escape_local "$detail")" >> "$file"
chmod 600 "$file" 2>/dev/null || true
}
generate_session_id() {
printf 'h-%s-%s-%s\n' \
"$(date -u +%Y%m%d%H%M%S)" \
"$$" \
"$(head -c 6 /dev/urandom | xxd -p 2>/dev/null || echo $RANDOM)"
}
session_file_for() {
local sid="$1"
printf '%s/%s.json\n' "$ACTIVE_DIR" "$sid"
}
session_status_value() {
local sid="$1"
python3 - "$ACTIVE_DIR" "$sid" <<'PY'
import json, pathlib, sys, time
p = pathlib.Path(sys.argv[1]) / f"{sys.argv[2]}.json"
if not p.exists():
print("missing")
raise SystemExit(1)
data = json.loads(p.read_text())
if data.get("status") != "active":
print(data.get("status","unknown"))
raise SystemExit(0)
if int(data.get("expires_epoch",0)) < int(time.time()):
print("expired")
else:
print("active")
PY
}
expire_if_needed() {
local sid="$1"
local f
f="$(session_file_for "$sid")"
[[ -f "$f" ]] || return 1
local state
if ! state="$(session_status_value "$sid" 2>/dev/null)"; then
return 1
fi
[[ "$state" = "expired" ]] || return 0
local expired_to="${EXPIRED_DIR}/$(basename "$f")"
python3 - "$f" <<'PY'
import json, pathlib, sys, time
p = pathlib.Path(sys.argv[1])
data = json.loads(p.read_text())
if data.get("status") == "active":
data["status"] = "expired"
data["expired_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
p.write_text(json.dumps(data, indent=2))
PY
mv "$f" "$expired_to"
append_history "timeout" "$sid" "$(whoami)" "${HELLO_PURPOSE:-unknown}" "${HELLO_SCOPE:-unknown}" "expired" "watchdog_or_validate"
tool_audit "hello_timeout" "session_id=${sid}"
return 0
}
check_in() {
local purpose="${1:-maintenance}"
local scope="${2:-$DEFAULT_SCOPE}"
local ttl="${3:-$DEFAULT_TTL}"
[[ "$ttl" =~ ^[0-9]+$ ]] || die "TTL must be numeric seconds"
(( ttl > 0 )) || die "TTL must be positive"
(( ttl <= MAX_TTL )) || die "TTL exceeds max (${MAX_TTL})"
local sid actor source_ip started expires_epoch expires_at f lock
sid="$(generate_session_id)"
actor="$(whoami)"
source_ip="${SSH_CLIENT%% *}"
started="$(now_iso)"
expires_epoch="$(( $(now_epoch) + ttl ))"
expires_at="$(date -u -d "@${expires_epoch}" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || python3 - <<PY
import datetime
print(datetime.datetime.utcfromtimestamp(${expires_epoch}).strftime("%Y-%m-%dT%H:%M:%SZ"))
PY
)"
f="$(session_file_for "$sid")"
lock="${LOCK_DIR}/${sid}.lock"
umask 077
: > "$lock"
cat > "$f" <<EOF
{
"session_id": "$(json_escape_local "$sid")",
"actor": "$(json_escape_local "$actor")",
"node": "$(json_escape_local "${NODE_ID:-unknown}")",
"purpose": "$(json_escape_local "$purpose")",
"scope": "$(json_escape_local "$scope")",
"source_ip": "$(json_escape_local "${source_ip:-local}")",
"started_at": "$(json_escape_local "$started")",
"expires_at": "$(json_escape_local "$expires_at")",
"expires_epoch": ${expires_epoch},
"status": "active"
}
EOF
chmod 600 "$f" "$lock" 2>/dev/null || true
append_history "check_in" "$sid" "$actor" "$purpose" "$scope" "active" "ttl=${ttl}"
tool_audit "hello_check_in" "session_id=${sid} purpose=${purpose} scope=${scope} ttl=${ttl}"
printf 'export HELLO_SESSION_ID=%q\n' "$sid"
printf 'export HELLO_PURPOSE=%q\n' "$purpose"
printf 'export HELLO_SCOPE=%q\n' "$scope"
printf 'export HELLO_EXPIRES_AT=%q\n' "$expires_at"
printf '{"session_id":"%s","status":"active","expires_at":"%s","purpose":"%s","scope":"%s"}\n' "$sid" "$expires_at" "$purpose" "$scope"
}
check_out() {
local sid="${1:-${HELLO_SESSION_ID:-}}"
[[ -n "$sid" ]] || die "Session ID required"
expire_if_needed "$sid" || true
local f
f="$(session_file_for "$sid")"
[[ -f "$f" ]] || die "Active session not found: ${sid}"
python3 - "$f" <<'PY'
import json, pathlib, sys, time
p = pathlib.Path(sys.argv[1])
data = json.loads(p.read_text())
data["status"] = "closed"
data["checked_out_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
p.write_text(json.dumps(data, indent=2))
PY
mkdir -p "$EXPIRED_DIR"
mv "$f" "${EXPIRED_DIR}/$(basename "$f")"
rm -f "${LOCK_DIR}/${sid}.lock" 2>/dev/null || true
append_history "check_out" "$sid" "$(whoami)" "${HELLO_PURPOSE:-unknown}" "${HELLO_SCOPE:-unknown}" "closed" ""
tool_audit "hello_check_out" "session_id=${sid}"
printf '{"session_id":"%s","status":"closed"}\n' "$sid"
}
validate_session() {
local sid="${1:-${HELLO_SESSION_ID:-}}"
[[ -n "$sid" ]] || return 1
expire_if_needed "$sid" || true
local f
f="$(session_file_for "$sid")"
[[ -f "$f" ]] || return 1
python3 - "$f" <<'PY'
import json, pathlib, sys, time
p = pathlib.Path(sys.argv[1])
data = json.loads(p.read_text())
ok = data.get("status") == "active" and int(data.get("expires_epoch",0)) >= int(time.time())
if ok:
print(json.dumps({
"ok": True,
"session_id": data["session_id"],
"actor": data["actor"],
"scope": data["scope"],
"purpose": data["purpose"],
"expires_at": data["expires_at"]
}))
raise SystemExit(0)
print(json.dumps({"ok": False}))
raise SystemExit(1)
PY
}
status_session() {
local sid="${1:-${HELLO_SESSION_ID:-}}"
[[ -n "$sid" ]] || die "Session ID required"
expire_if_needed "$sid" || true
local f
f="$(session_file_for "$sid")"
if [[ -f "$f" ]]; then
cat "$f"
return 0
fi
if [[ -f "${EXPIRED_DIR}/${sid}.json" ]]; then
cat "${EXPIRED_DIR}/${sid}.json"
return 0
fi
die "Session not found: ${sid}"
}
list_sessions() {
local f first=1
echo '['
for f in "$ACTIVE_DIR"/*.json; do
[[ -f "$f" ]] || continue
expire_if_needed "$(basename "${f%.json}")" || true
done
for f in "$ACTIVE_DIR"/*.json; do
[[ -f "$f" ]] || continue
if [[ $first -eq 0 ]]; then echo ','; fi
cat "$f"
first=0
done
echo ']'
}
expire_all() {
local f sid
for f in "$ACTIVE_DIR"/*.json; do
[[ -f "$f" ]] || continue
sid="$(basename "${f%.json}")"
expire_if_needed "$sid" || true
done
}
engine_is_running() {
systemctl is-active --quiet s3rv1c3agent.service 2>/dev/null
}
engine_start() {
if engine_is_running; then
local pid
pid=$(systemctl show --property=MainPID --value s3rv1c3agent.service 2>/dev/null || echo "0")
printf '{"status":"already_running","service":"s3rv1c3agent","pid":%s}\n' "$pid"
return 0
fi
if [[ ! -f /var/www/private/bridge_hmac.key ]]; then
printf '{"status":"error","detail":"HMAC key not found"}\n' >&2
return 1
fi
systemctl daemon-reload 2>/dev/null || true
if ! systemctl start s3rv1c3agent.service 2>/dev/null; then
printf '{"status":"error","detail":"failed_to_start_service"}\n' >&2
return 1
fi
sleep 1
local pid
pid=$(systemctl show --property=MainPID --value s3rv1c3agent.service 2>/dev/null || echo "0")
printf '{"status":"started","service":"s3rv1c3agent","pid":%s}\n' "$pid"
}
engine_status() {
local active="false"
local pid="0"
local uptime="0"
if engine_is_running; then
active="true"
pid=$(systemctl show --property=MainPID --value s3rv1c3agent.service 2>/dev/null || echo "0")
local start_ts
start_ts=$(systemctl show --property=ExecMainStartTimestamp --value s3rv1c3agent.service 2>/dev/null || echo "")
if [[ -n "$start_ts" ]]; then
uptime="$(date -u +%s)"
fi
fi
printf '{"engine":{"active":%s,"pid":%s,"timestamp":%s},"node":"%s"}\n' "$active" "$pid" "$(date -u +%s)" "${NODE_ID:-unknown}"
}
security_status() {
local engine_active="false"
local agent_installed="false"
local hmac_ready="false"
if [[ -x /opt/apache/engine/s3rv1c3agent ]]; then
agent_installed="true"
fi
if [[ -f /var/www/private/bridge_hmac.key ]]; then
hmac_ready="true"
fi
if systemctl is-active --quiet s3rv1c3agent.service 2>/dev/null; then
engine_active="true"
fi
printf '{"security":{"agent_installed":%s,"hmac_ready":%s,"engine_active":%s},"node":"%s","timestamp":%s}\n' \
"$agent_installed" "$hmac_ready" "$engine_active" "${NODE_ID:-unknown}" "$(date -u +%s)"
}
tool_help() {
cat <<'EOF'
hello check-in [purpose] [scope] [ttl_seconds]
hello check-out [session_id]
hello validate [session_id]
hello status [session_id]
hello list
hello expire-all
hello --start-engine
hello --security
EOF
}
main() {
tool_init
case "${1:-}" in
check-in) shift; check_in "${1:-maintenance}" "${2:-$DEFAULT_SCOPE}" "${3:-$DEFAULT_TTL}" ;;
check-out) shift; check_out "${1:-${HELLO_SESSION_ID:-}}" ;;
validate) shift; validate_session "${1:-${HELLO_SESSION_ID:-}}" ;;
status|"") shift || true; status_session "${1:-${HELLO_SESSION_ID:-}}" ;;
list) list_sessions ;;
expire-all) expire_all ;;
--start-engine) engine_start ;;
--security) security_status ;;
help|--help|-h) tool_help ;;
*) die "Unknown command: ${1:-}" ;;
esac
}
main "$@"