#!/usr/bin/env bash
# app-commands — poll CSC for one-shot remote commands, run them, report back.
#
# Usage: app-commands [options]
#   (default)        tick: with REMOTE_COMMANDS=on, once REMOTE_COMMANDS_INTERVAL elapsed,
#                    claim/run/report pending commands (up to 10 per tick)
#   --once           claim, run and report ONE command now (ignores the interval)
#   --list           print the kinds this host accepts (catalog ∩ REMOTE_COMMANDS_KINDS)
#   --config <path>  use a specific config file
#   --show-config    print resolved configuration and exit
#   -h, --help       this help
#
# A host that allows no inbound connection still needs operating: it PULLS one-shot
# commands from CSC (signed, outbound HTTPS only), runs them and reports the result
# with evidence of what ran. A command names a KIND from a fixed catalog (ping,
# config, health, metrics, logs, sql, scripts-update) with typed parameters — there
# is no shell kind; new kinds arrive only as a new scripts bundle, which the
# scripts-update kind installs. A claimed command is never re-run (at most once).
#
# Inert with REMOTE_COMMANDS=off (default): the timer ticks every minute everywhere
# and this exits silently. Designed to run from app-commands.timer as ROOT
# (journal access, the bootstrap, future kinds); `sql` connects as the dedicated
# read-only role in REMOTE_COMMANDS_SQL_DSN. Needs jq (plus curl + openssl).
# Contract + design: csc.git/doc/instance-commands.md.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# help = the header comment block above, printed until the first non-# line. The
# generic command names (app-commands, …) are rewritten to THIS script's own name,
# so a renamed <product>-commands prints its own name with no install-time edit.
usage() {
    local cmd pfx; cmd="$(basename -- "$0")"; pfx="${cmd%-commands}"
    sed -n '2,${/^#/!q;s/^# \{0,1\}//;s/^#$//;p}' "$0" \
        | sed -E "s/app-(deploy|backup|health|update|commands)/$pfx-\1/g"
}

MODE=tick   # tick | once | list | showconfig
while [ $# -gt 0 ]; do
    case "$1" in
        --once)         MODE=once ;;
        --list)         MODE=list ;;
        --show-config)  MODE=showconfig ;;
        --config)       CONFIG_FILE="$2"; shift ;;
        --config=*)     CONFIG_FILE="${1#*=}" ;;
        -h|--help)      usage; exit 0 ;;
        *)              echo "Unknown option: $1" >&2; exit 2 ;;
    esac
    shift
done

# The config-permission warning fires at source time; from a per-minute timer that
# would be journal noise, so the tick is silent about it (the other CLIs still warn).
[ "$MODE" = tick ] && DE_QUIET_PERM_WARNING=1
. "$SCRIPT_DIR/common.sh"
. "$SCRIPT_DIR/lib/commands.sh"

case "$MODE" in
    showconfig) de_show_config; exit 0 ;;
    list)       cmd_list_kinds; exit 0 ;;
esac

# One run at a time per host: the timer never overlaps itself, but a manual --once
# must not race a running tick (both would claim and run commands). NB: no
# `2>/dev/null` on the exec line — that would redirect the script's stderr (all
# logging) for good; probe writability in a braced group instead.
if { : >> "$CMD_LOCK_FILE"; } 2>/dev/null; then
    exec 9>"$CMD_LOCK_FILE"
    if ! flock -n 9; then
        de_log "another $(basename -- "$0") run is active; skipping"
        exit 0
    fi
else
    de_warn "cannot write $CMD_LOCK_FILE; running without the single-run lock"
fi

case "$MODE" in
    tick) cmd_tick ;;
    once) cmd_once ;;
esac
exit 0
