scripts/sepolia/systemd/install.sh131 lines · 7491 bytes
| 1 | #!/usr/bin/env bash |
| 2 | # Install the Upgrade Airlock services for the CURRENT USER ONLY (systemd --user; no sudo, nothing system-wide): |
| 3 | # airlock-listener.service airlock.sh in the foreground, restarted 5 s after any exit |
| 4 | # airlock-sweep.service one sweep.sh run |
| 5 | # airlock-sweep.timer starts the sweep every 60 s (Persistent) |
| 6 | # |
| 7 | # scripts/sepolia/systemd/install.sh # write the env file if missing, install, enable and start |
| 8 | # scripts/sepolia/systemd/install.sh --no-start # install and enable only |
| 9 | # scripts/sepolia/systemd/install.sh --env-only # only (re)write the env file and show it |
| 10 | # DRY_RUN=1 scripts/sepolia/systemd/install.sh # print the rendered units and the env file; change nothing |
| 11 | # |
| 12 | # The units read ~/.config/upgrade-airlock/airlock.env (ENV_FILE overrides). It holds settings, never keys: |
| 13 | # KEY_DIR (where owner/attester/sender/approver/emergency.key live), CRE_SIM, SEPOLIA_RPC, AIRLOCK_STATE (logs, PID |
| 14 | # files and the compiled workflow; default ~/.local/state/upgrade-airlock) and the PATH the services run with. |
| 15 | # On the first install it is written from the current shell's values (KEY_DIR, CRE_SIM, SEPOLIA_RPC, AIRLOCK_STATE, |
| 16 | # PATH); edit it and run install.sh again to apply. FORCE_ENV=1 rewrites it. |
| 17 | # Load the same file in the operator shell so propose.sh / status.sh see the services' state dir: |
| 18 | # set -a; . ~/.config/upgrade-airlock/airlock.env; set +a |
| 19 | # The services keep running after logout only with lingering: LINGER=1 runs `loginctl enable-linger` for you. |
| 20 | # Pre-flight (skip with SKIP_CHECKS=1): Linux with a user systemd, not root, the attester and sender keys, a |
| 21 | # deployment with airlockAdmin, the workflow config, the CRE CLI, and cast/jq/bc/node on the services' PATH. |
| 22 | set -euo pipefail |
| 23 | HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) |
| 24 | KIT=$(cd "$HERE/.." && pwd) |
| 25 | ROOT=$(cd "$KIT/../.." && pwd) |
| 26 | UNITS=(airlock-listener.service airlock-sweep.service airlock-sweep.timer) |
| 27 | UNIT_DIR=${SYSTEMD_USER_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user} |
| 28 | ENV_FILE=${ENV_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/upgrade-airlock/airlock.env} |
| 29 | DRY=${DRY_RUN:-} |
| 30 | START=1 ENV_ONLY= |
| 31 | for a in "$@"; do |
| 32 | case "$a" in |
| 33 | --no-start) START= ;; |
| 34 | --env-only) ENV_ONLY=1 ;; |
| 35 | -h | --help) sed -n '2,24p' "$0"; exit 0 ;; |
| 36 | *) echo "error: unknown option $a" >&2; exit 2 ;; |
| 37 | esac |
| 38 | done |
| 39 | die() { echo "error: $*" >&2; exit 1; } |
| 40 | say() { printf '%s\n' "$*"; } |
| 41 | |
| 42 | [ "$(id -u)" != 0 ] || die "run this as the user that runs the demo, not as root (the units are per-user)" |
| 43 | case "$ENV_FILE$UNIT_DIR$KIT" in *[[:space:]]*) die "paths with spaces are not supported in unit files" ;; esac |
| 44 | |
| 45 | # ---------------------------------------------------------------- the env file (settings only, no keys) |
| 46 | env_text() { |
| 47 | local sim |
| 48 | sim=${CRE_SIM:-$(command -v cre 2>/dev/null || echo cre)} |
| 49 | cat <<EOF |
| 50 | # Upgrade Airlock services (systemd --user): settings only. Never put a key in here; KEY_DIR points at the key files. |
| 51 | # Written by scripts/sepolia/systemd/install.sh. Edit, then run install.sh again (or systemctl --user restart ...). |
| 52 | KEY_DIR=${KEY_DIR:-$HOME/.config/ethsz-2026} |
| 53 | CRE_SIM=$sim |
| 54 | SEPOLIA_RPC=${SEPOLIA_RPC:-https://ethereum-sepolia-rpc.publicnode.com} |
| 55 | AIRLOCK_STATE=${AIRLOCK_STATE:-${XDG_STATE_HOME:-$HOME/.local/state}/upgrade-airlock} |
| 56 | PATH=$PATH |
| 57 | # Optional: |
| 58 | # DEPLOY_JSON=<deployment json> default: deployments/sepolia.json in the repo |
| 59 | # WORKFLOW_CONFIG=<workflow config> default: cre/veto/config.sepolia.json in the repo |
| 60 | # SWEEP_MIN_AGE=3 blocks a proposal is left to the listener before the sweeper takes it |
| 61 | # WASM_CACHE=0 compile the workflow inside every simulator run |
| 62 | EOF |
| 63 | } |
| 64 | if [ -n "$DRY" ]; then |
| 65 | say "# ---- $ENV_FILE$([ -f "$ENV_FILE" ] && echo ' (exists; kept unless FORCE_ENV=1)')" |
| 66 | if [ -f "$ENV_FILE" ] && [ -z "${FORCE_ENV:-}" ]; then cat "$ENV_FILE"; else env_text; fi |
| 67 | elif [ ! -f "$ENV_FILE" ] || [ -n "${FORCE_ENV:-}" ] || [ -n "$ENV_ONLY" ]; then |
| 68 | if [ -f "$ENV_FILE" ] && [ -z "${FORCE_ENV:-}" ]; then |
| 69 | say "env file $ENV_FILE exists; FORCE_ENV=1 rewrites it from this shell" |
| 70 | else |
| 71 | mkdir -p "$(dirname "$ENV_FILE")" |
| 72 | (umask 077 && env_text >"$ENV_FILE") |
| 73 | say "wrote $ENV_FILE" |
| 74 | fi |
| 75 | fi |
| 76 | [ -z "$ENV_ONLY" ] || { cat "$ENV_FILE"; exit 0; } |
| 77 | |
| 78 | # ---------------------------------------------------------------- pre-flight, with the services' own settings |
| 79 | if [ -z "${SKIP_CHECKS:-}" ] && [ -z "$DRY" ]; then |
| 80 | [ "$(uname -s)" = Linux ] || die "systemd user units need Linux (SKIP_CHECKS=1 to install anyway)" |
| 81 | command -v systemctl >/dev/null || die "no systemctl" |
| 82 | systemctl --user show-environment >/dev/null 2>&1 || die "no user systemd instance for $(id -un) (log in through a real session, or enable lingering)" |
| 83 | ( |
| 84 | set -a |
| 85 | # shellcheck disable=SC1090 |
| 86 | . "$ENV_FILE" |
| 87 | set +a |
| 88 | for t in cast jq bc node; do command -v "$t" >/dev/null || die "$t is not on the PATH in $ENV_FILE"; done |
| 89 | command -v "$CRE_SIM" >/dev/null 2>&1 || die "CRE_SIM=$CRE_SIM in $ENV_FILE is not an executable" |
| 90 | for r in attester sender; do [ -r "$KEY_DIR/$r.key" ] || die "no readable $r.key in KEY_DIR=$KEY_DIR"; done |
| 91 | D=${DEPLOY_JSON:-$ROOT/deployments/sepolia.json} |
| 92 | [ -f "$D" ] && jq -e '.airlockAdmin' "$D" >/dev/null || die "$D has no airlockAdmin (deploy with scripts/sepolia/deploy.sh)" |
| 93 | C=${WORKFLOW_CONFIG:-$ROOT/cre/veto/config.sepolia.json} |
| 94 | [ -f "$C" ] || die "no workflow config $C (node scripts/sepolia/make-config.mjs)" |
| 95 | [ "$(jq -r .airlockAdmin "$C" | tr 'A-F' 'a-f')" = "$(jq -r .airlockAdmin "$D" | tr 'A-F' 'a-f')" ] \ |
| 96 | || die "$C is for another deployment (re-run make-config.mjs)" |
| 97 | ) |
| 98 | if [ "${LINGER:-}" = 1 ]; then |
| 99 | loginctl enable-linger "$(id -un)" && say "lingering enabled: the services keep running after logout" |
| 100 | elif [ "$(loginctl show-user "$(id -un)" -p Linger --value 2>/dev/null || echo no)" != yes ]; then |
| 101 | say "note: lingering is off, so the services stop when your last session ends (LINGER=1 install.sh, or: loginctl enable-linger)" |
| 102 | fi |
| 103 | fi |
| 104 | |
| 105 | # ---------------------------------------------------------------- units |
| 106 | render() { sed -e "s|@KIT@|$KIT|g" -e "s|@ENV_FILE@|$ENV_FILE|g" "$HERE/$1"; } |
| 107 | if [ -n "$DRY" ]; then |
| 108 | for u in "${UNITS[@]}"; do say "# ---- $UNIT_DIR/$u"; render "$u"; done |
| 109 | say "# ---- then: systemctl --user daemon-reload; systemctl --user enable${START:+ --now} airlock-listener.service airlock-sweep.timer" |
| 110 | exit 0 |
| 111 | fi |
| 112 | mkdir -p "$UNIT_DIR" |
| 113 | for u in "${UNITS[@]}"; do |
| 114 | render "$u" >"$UNIT_DIR/$u.tmp" && chmod 644 "$UNIT_DIR/$u.tmp" && mv "$UNIT_DIR/$u.tmp" "$UNIT_DIR/$u" |
| 115 | say "installed $UNIT_DIR/$u" |
| 116 | done |
| 117 | systemctl --user daemon-reload |
| 118 | if [ -n "$START" ]; then |
| 119 | systemctl --user enable airlock-listener.service airlock-sweep.timer |
| 120 | systemctl --user restart airlock-listener.service |
| 121 | systemctl --user restart airlock-sweep.timer |
| 122 | say "started: airlock-listener.service, airlock-sweep.timer" |
| 123 | else |
| 124 | systemctl --user enable airlock-listener.service airlock-sweep.timer |
| 125 | say "enabled (not started): systemctl --user start airlock-listener.service airlock-sweep.timer" |
| 126 | fi |
| 127 | say "" |
| 128 | say " status systemctl --user status airlock-listener.service; systemctl --user list-timers airlock-sweep.timer" |
| 129 | say " logs journalctl --user -u airlock-listener -f; journalctl --user -u airlock-sweep -n 50" |
| 130 | say " shell set -a; . $ENV_FILE; set +a (then propose.sh / status.sh share the services' state dir)" |
| 131 | say " remove $HERE/uninstall.sh" |