#!/usr/bin/env bash set -Eeuo pipefail INSTALL_ROOT="${TNW_INSTALL_ROOT:-}" COLLECTOR_URL="${TNW_COLLECTOR_URL:-https://tnw.catalinionescu.dev/tnw-metrics-collector.py}" COLLECTOR_USER="tnw-metrics" fail() { printf 'The Night Watch host installer: %s\n' "$1" >&2 exit 1 } prompt() { local variable="$1" message="$2" secret="${3:-false}" value="${!1:-}" if [[ -z "${value}" ]]; then [[ -r /dev/tty ]] || fail "Set ${variable} before running without an interactive terminal." if [[ "${secret}" == "true" ]]; then read -r -s -p "${message}: " value /dev/tty else read -r -p "${message}: " value /dev/null 2>&1 || fail "curl is required." command -v python3 >/dev/null 2>&1 || fail "Python 3 is required." command -v install >/dev/null 2>&1 || fail "install is required." python3 -c 'import sys; raise SystemExit(sys.version_info < (3, 8))' || fail "Python 3.8 or newer is required." prompt TNW_METRICS_ENDPOINT "Metrics endpoint (https://your-app.example/tnw/internal/metric_samples)" prompt TNW_METRICS_SERVER_ID "Server identifier" prompt TNW_METRICS_INGESTION_TOKEN "64-character ingestion secret" true [[ "${TNW_METRICS_ENDPOINT}" =~ ^https://[^[:space:]]+/[^[:space:]]+$ ]] || fail "The metrics endpoint must be an HTTPS URL." [[ "${TNW_METRICS_SERVER_ID}" =~ ^[A-Za-z0-9._-]{1,100}$ ]] || fail "The server identifier may contain letters, numbers, dots, underscores, and hyphens." [[ "${TNW_METRICS_INGESTION_TOKEN}" =~ ^[A-Fa-f0-9]{64}$ ]] || fail "The ingestion secret must be 64 hexadecimal characters." if [[ -z "${INSTALL_ROOT}" ]] && ! id -u "${COLLECTOR_USER}" >/dev/null 2>&1; then if command -v useradd >/dev/null 2>&1; then nologin_shell="$(command -v nologin || printf '/usr/sbin/nologin')" useradd --system --user-group --home-dir /nonexistent --shell "${nologin_shell}" "${COLLECTOR_USER}" elif command -v adduser >/dev/null 2>&1; then adduser --system --no-create-home --disabled-login --group "${COLLECTOR_USER}" else fail "useradd or adduser is required to create the collector account." fi fi tmp_dir="$(mktemp -d)" trap 'rm -rf "${tmp_dir}"' EXIT curl -fsSL "${COLLECTOR_URL}" -o "${tmp_dir}/tnw-metrics-collector" python3 -m py_compile "${tmp_dir}/tnw-metrics-collector" install -d -m 0755 "${INSTALL_ROOT}/usr/local/bin" "${INSTALL_ROOT}/etc/tnw" install -m 0755 "${tmp_dir}/tnw-metrics-collector" "${INSTALL_ROOT}/usr/local/bin/tnw-metrics-collector" cat >"${tmp_dir}/metrics.env" </dev/null 2>&1; then scheduler="systemd" else scheduler="cron" fi fi case "${scheduler}" in systemd) cat >"${tmp_dir}/tnw-metrics.service" <<'SERVICE' [Unit] Description=The Night Watch host metric collector After=network-online.target Wants=network-online.target [Service] Type=oneshot User=tnw-metrics Group=tnw-metrics EnvironmentFile=/etc/tnw/metrics.env ExecStart=/usr/local/bin/tnw-metrics-collector NoNewPrivileges=true PrivateTmp=true ProtectHome=true ProtectSystem=strict ProtectKernelTunables=true ProtectControlGroups=true RestrictSUIDSGID=true LockPersonality=true SERVICE cat >"${tmp_dir}/tnw-metrics.timer" <<'TIMER' [Unit] Description=Collect The Night Watch host metrics [Timer] OnBootSec=30s OnUnitActiveSec=5s AccuracySec=1s [Install] WantedBy=timers.target TIMER install -d -m 0755 "${INSTALL_ROOT}/etc/systemd/system" install -m 0644 "${tmp_dir}/tnw-metrics.service" "${INSTALL_ROOT}/etc/systemd/system/tnw-metrics.service" install -m 0644 "${tmp_dir}/tnw-metrics.timer" "${INSTALL_ROOT}/etc/systemd/system/tnw-metrics.timer" if [[ -z "${INSTALL_ROOT}" ]]; then systemctl daemon-reload systemctl enable --now tnw-metrics.timer fi ;; cron) command -v flock >/dev/null 2>&1 || fail "flock is required for the cron fallback." install -d -m 0755 "${INSTALL_ROOT}/etc/cron.d" cat >"${tmp_dir}/tnw-metrics.cron" <<'CRON' * * * * * tnw-metrics flock -n /run/tnw-metrics.lock /bin/sh -c 'set -a; . /etc/tnw/metrics.env; exec /usr/local/bin/tnw-metrics-collector' CRON install -m 0644 "${tmp_dir}/tnw-metrics.cron" "${INSTALL_ROOT}/etc/cron.d/tnw-metrics" ;; *) fail "TNW_SCHEDULER must be systemd or cron." ;; esac printf 'The Night Watch host collector is installed with the %s scheduler.\n' "${scheduler}" printf 'Configuration: /etc/tnw/metrics.env\n' printf 'Collector: /usr/local/bin/tnw-metrics-collector\n'