#!/bin/bash
# hal0-systemctl — privileged seam for hal0-api's slot/systemd ops (P3-perms).
#
# Background: hal0-api now runs as the unprivileged `hal0` system user
# (installer/install.sh's hal0-api.service ships User=hal0 — the OwnershipStore
# flip in src/hal0/install/perms.py). It still needs a handful of genuinely-root
# operations for slot lifecycle: writing the per-slot systemd unit under
# /etc/systemd/system, `daemon-reload`, and start/stop/restart/enable/disable/
# reset-failed of a slot unit — plus restarting itself on self-update. This
# script is the ENTIRE privileged surface for those ops, modeled exactly on
# hal0-agentenv / hal0-benchctl: every argument is validated (a slot id must
# match a strict identifier regex), no shell is ever evaluated, and no
# wildcards are accepted.
#
# NOTE: the iptables FORWARD-chain repair the original P3-perms design doc
# sketched for this seam is NOT needed — that repair already runs as its own
# independent root oneshot unit (packaging/systemd/hal0-podman-forward.service,
# no User= — always root), entirely unaffected by hal0-api's User=hal0 flip.

set -euo pipefail

UNIT_PREFIX="hal0-slot@"
UNIT_DIR="/etc/systemd/system"
SELF_UNIT="hal0-api.service"

# P3-quadlet: per-slot Podman Quadlet source files live here, root-owned by
# design; podman's systemd generator turns hal0-slot@<id>.container into
# hal0-slot@<id>.service on daemon-reload.
QUADLET_DIR="/etc/containers/systemd"

# Fixed, literal drop-in path for the hermes gateway secrets fragment. Written
# by the hermes provisioner when it runs unprivileged (as the hal0 user) and so
# cannot write /etc/systemd/system itself. LITERAL — no variable, no traversal:
# the caller supplies only the body on stdin, never the path.
GATEWAY_DROPIN_DIR="/etc/systemd/system/hermes-gateway.service.d"
GATEWAY_DROPIN_FILE="${GATEWAY_DROPIN_DIR}/10-hal0-secrets.conf"

die() { echo "hal0-systemctl: $1" >&2; exit 64; }

validate_slot_id() {   # arg: slot id (the "<id>" in hal0-slot@<id>.service)
  local id="$1"
  [[ -n "$id" ]] || die "missing slot id"
  [[ "$id" =~ ^[A-Za-z0-9_-]{1,64}$ ]] || die "bad slot id: $id"
}

cmd="${1:-help}"; shift || true

case "$cmd" in
  write-unit)                 # write-unit <slot-id>   (unit body on stdin)
    id="${1:-}"; validate_slot_id "$id"
    path="${UNIT_DIR}/${UNIT_PREFIX}${id}.service"
    install -m 0644 -o root -g root /dev/stdin "$path"
    ;;

  remove-unit)                # remove-unit <slot-id>   (rm -f semantics: ok if absent)
    id="${1:-}"; validate_slot_id "$id"
    rm -f "${UNIT_DIR}/${UNIT_PREFIX}${id}.service"
    ;;

  write-quadlet)              # write-quadlet <slot-id>   (.container body on stdin)
    id="${1:-}"; validate_slot_id "$id"
    install -d -m 0755 -o root -g root "$QUADLET_DIR"
    install -m 0644 -o root -g root /dev/stdin "${QUADLET_DIR}/${UNIT_PREFIX}${id}.container"
    ;;

  remove-quadlet)             # remove-quadlet <slot-id>   (rm -f semantics: ok if absent)
    id="${1:-}"; validate_slot_id "$id"
    rm -f "${QUADLET_DIR}/${UNIT_PREFIX}${id}.container"
    ;;

  write-gateway-dropin)       # write the hermes-gateway secrets drop-in (body on stdin)
    # Literal fixed path — the caller never supplies it. 0644 root:root so
    # systemd can read the unit fragment; the referenced secrets live in the
    # 0600 vault, not here. Atomic (tmp + mv), mirroring hal0-agentenv's
    # write-driver-env — `install /dev/stdin` misbehaves overwriting an
    # existing target when the source is a pipe.
    install -d -m 0755 -o root -g root "$GATEWAY_DROPIN_DIR"
    umask 022
    tmp="$(mktemp "${GATEWAY_DROPIN_FILE}.XXXXXX")"
    trap 'rm -f "$tmp"' EXIT
    cat > "$tmp"
    chown root:root "$tmp"
    chmod 0644 "$tmp"
    mv -f "$tmp" "$GATEWAY_DROPIN_FILE"
    trap - EXIT
    ;;

  daemon-reload)
    exec systemctl daemon-reload
    ;;

  start|stop|restart|enable|disable|reset-failed)
    id="${1:-}"; validate_slot_id "$id"
    exec systemctl "$cmd" "${UNIT_PREFIX}${id}.service"
    ;;

  restart-self)                # literal-only: restart hal0-api.service, never a variable name
    exec systemctl restart "$SELF_UNIT"
    ;;

  help|"")
    cat <<EOF
usage: hal0-systemctl <command> [slot-id]
  write-unit <slot-id>             write ${UNIT_DIR}/${UNIT_PREFIX}<id>.service (body on stdin)
  remove-unit <slot-id>            rm -f ${UNIT_DIR}/${UNIT_PREFIX}<id>.service
  write-quadlet <slot-id>          write ${QUADLET_DIR}/${UNIT_PREFIX}<id>.container (body on stdin)
  remove-quadlet <slot-id>         rm -f ${QUADLET_DIR}/${UNIT_PREFIX}<id>.container
  write-gateway-dropin             write ${GATEWAY_DROPIN_FILE} (body on stdin)
  daemon-reload                    systemctl daemon-reload
  start|stop|restart <slot-id>     systemctl <verb> ${UNIT_PREFIX}<id>.service
  enable|disable <slot-id>         systemctl <verb> ${UNIT_PREFIX}<id>.service
  reset-failed <slot-id>           clear a crash-looped unit's failed sub-state
  restart-self                     systemctl restart ${SELF_UNIT}
EOF
    ;;

  *) die "bad cmd: $cmd" ;;
esac
