Source mirror · commit 369512f · 2026-10-26 · MIT · canonical repo: gitlab.com/jiapunk/upgrade-airlock
git clone https://upgrade-airlock.pages.dev/source/upgrade-airlock.git
upgrade-airlock / scripts / sepolia / systemd / install.sh
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.
22set -euo pipefail
23HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
24KIT=$(cd "$HERE/.." && pwd)
25ROOT=$(cd "$KIT/../.." && pwd)
26UNITS=(airlock-listener.service airlock-sweep.service airlock-sweep.timer)
27UNIT_DIR=${SYSTEMD_USER_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user}
28ENV_FILE=${ENV_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/upgrade-airlock/airlock.env}
29DRY=${DRY_RUN:-}
30START=1 ENV_ONLY=
31for 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
38done
39die() { echo "error: $*" >&2; exit 1; }
40say() { 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)"
43case "$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)
46env_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 ...).
52KEY_DIR=${KEY_DIR:-$HOME/.config/ethsz-2026}
53CRE_SIM=$sim
54SEPOLIA_RPC=${SEPOLIA_RPC:-https://ethereum-sepolia-rpc.publicnode.com}
55AIRLOCK_STATE=${AIRLOCK_STATE:-${XDG_STATE_HOME:-$HOME/.local/state}/upgrade-airlock}
56PATH=$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
62EOF
63}
64if [ -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
67elif [ ! -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
75fi
76[ -z "$ENV_ONLY" ] || { cat "$ENV_FILE"; exit 0; }
77
78# ---------------------------------------------------------------- pre-flight, with the services' own settings
79if [ -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
103fi
104
105# ---------------------------------------------------------------- units
106render() { sed -e "s|@KIT@|$KIT|g" -e "s|@ENV_FILE@|$ENV_FILE|g" "$HERE/$1"; }
107if [ -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
111fi
112mkdir -p "$UNIT_DIR"
113for 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"
116done
117systemctl --user daemon-reload
118if [ -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"
123else
124 systemctl --user enable airlock-listener.service airlock-sweep.timer
125 say "enabled (not started): systemctl --user start airlock-listener.service airlock-sweep.timer"
126fi
127say ""
128say " status systemctl --user status airlock-listener.service; systemctl --user list-timers airlock-sweep.timer"
129say " logs journalctl --user -u airlock-listener -f; journalctl --user -u airlock-sweep -n 50"
130say " shell set -a; . $ENV_FILE; set +a (then propose.sh / status.sh share the services' state dir)"
131say " remove $HERE/uninstall.sh"