From fac00f941c286296a3d07783ef931cbd3d123472 Mon Sep 17 00:00:00 2001 From: Mike Langmayr <1809691+mikelangmayr@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:27:50 -0700 Subject: [PATCH 1/2] Hand host setup to hispec install --- docs/operations/systemd.md | 32 +++--- src/hispec/cli/instrument.toml | 1 + systemd/README.md | 23 +++- systemd/bin/hispec | 17 --- systemd/bin/hispec-enable | 60 ----------- systemd/hispec@.service | 63 ----------- systemd/install.sh | 188 +++------------------------------ systemd/polkit/49-hispec.rules | 19 ---- 8 files changed, 50 insertions(+), 353 deletions(-) delete mode 100755 systemd/bin/hispec delete mode 100755 systemd/bin/hispec-enable delete mode 100644 systemd/hispec@.service delete mode 100644 systemd/polkit/49-hispec.rules diff --git a/docs/operations/systemd.md b/docs/operations/systemd.md index 293cc3e..07526cd 100644 --- a/docs/operations/systemd.md +++ b/docs/operations/systemd.md @@ -505,12 +505,17 @@ sudo ./systemd/install.sh alice bob # operator usernames `install.sh` is idempotent. Re-run it: - to enrol more operators (`sudo ./systemd/install.sh carol`); -- after a pull that changed dependencies in `pyproject.toml`, or - `hispec@.service`, the polkit rule or `hispec-enable` under `systemd/`; +- after a pull that changed dependencies in `pyproject.toml`, or the unit or + polkit rule in `instrumentctl`; - to repair a host `hispec doctor` says is broken. You do **not** need it to add a daemon or to pick up code changes; -[`hispec deploy`](#deploying-an-instance) and a `git pull` cover those. It: +[`hispec deploy`](#deploying-an-instance) and a `git pull` cover those. + +The script itself only bootstraps a pinned `uv` and builds the venv at +`/opt/hispec/venv`, which is also where the `hispec` command comes from. It +then hands over to `hispec install`, which lives in `instrumentctl` and does +the rest: - creates the `hispec-ops` group and the unprivileged `hispec` system user the daemons run as; @@ -518,17 +523,17 @@ You do **not** need it to add a daemon or to pick up code changes; - creates `/etc/hispec/{,instances/}` and `/var/log/hispec/`, group-owned by `hispec-ops` and setgid so files created by one operator stay readable by the rest; -- creates `/etc/hispec/secrets.env`, writable by `hispec-ops`; -- creates the venv at `/opt/hispec/venv` with the repo `pip install -e`'d into - it, which also provides the `hispec` CLI; -- installs `hispec@.service` (then runs `daemon-reload`), the polkit rule, the - sudoers drop-in, `/usr/local/bin/hispec` and `/usr/local/sbin/hispec-enable`; -- removes the retired `hispec-fei-start`, `hispec-fei-stop` and `hispec-doctor` - scripts (now `hispec start hsfei`, `hispec stop hsfei` and `hispec doctor`); -- migrates any instance still running under the old `hispec-daemon@` name, - preserving whether it was enabled and whether it was up; +- creates `/etc/hispec/secrets.env`, writable by `hispec-ops`, and records this + host's role in `/etc/hispec/host`; +- renders `hispec@.service` (then runs `daemon-reload`), the polkit rule, the + sudoers drop-in, `/usr/local/bin/hispec` and `/usr/local/sbin/hispec-enable` + from `src/hispec/cli/instrument.toml`, reporting only what it changes; - lists instances that are deployed but not set to start at boot. +Because those five are rendered rather than committed, `hispec doctor` +compares what is installed against what `install` would write now, which is +what catches a host whose unit predates a template change. + `HISPEC_REPO_DIR` and `HISPEC_VENV_DIR` override the paths. `/usr/local/bin/hispec` is a tiny wrapper that runs the CLI from the venv. The @@ -552,7 +557,8 @@ and `/var/log/hispec`; device nodes under `/dev` are unaffected by both. Starting and stopping a unit is the polkit action `org.freedesktop.systemd1.manage-units`, and systemd tells polkit which unit is -involved, so `systemd/polkit/49-hispec.rules` can allow it for `hispec@*` and +involved, so `/etc/polkit-1/rules.d/49-hispec.rules` can allow it for +`hispec@*` and nothing else. Enabling and disabling is `org.freedesktop.systemd1.manage-unit-files`, and systemd does **not** pass a unit name with it. A polkit rule for it would grant `hispec-ops` enable/disable diff --git a/src/hispec/cli/instrument.toml b/src/hispec/cli/instrument.toml index d656d31..e04af02 100644 --- a/src/hispec/cli/instrument.toml +++ b/src/hispec/cli/instrument.toml @@ -10,6 +10,7 @@ [instrument] name = "hispec" +display_name = "HISPEC" user = "hispec" ops_group = "hispec-ops" # Instance files set HISPEC_DAEMON, HISPEC_CONFIG and HISPEC_HOST diff --git a/systemd/README.md b/systemd/README.md index 73d1e3d..a89602c 100644 --- a/systemd/README.md +++ b/systemd/README.md @@ -11,12 +11,25 @@ directory. | Path | What it is | | --- | --- | -| `hispec@.service` | The template unit. One file runs every daemon; `hispec@` is an instance of it. Installed to `/etc/systemd/system/`. | | `instances/.env` | Per-instance settings: `HISPEC_DAEMON` (script, relative to `daemons/`) and `HISPEC_CONFIG` (deployed config path). Deployed to `/etc/hispec/instances/`. | -| `polkit/49-hispec.rules` | Lets `hispec-ops` members start/stop/restart `hispec@*` without sudo. Installed to `/etc/polkit-1/rules.d/`. | -| `bin/hispec` | Wrapper that runs the `hispec` CLI from the venv. Installed to `/usr/local/bin/`. | -| `bin/hispec-enable` | Root helper behind `hispec enable` / `hispec deploy`: enables/disables instances at boot, which `systemctl enable` cannot be granted per-unit. Installed to `/usr/local/sbin/`, invoked via `sudo -n` by a NOPASSWD drop-in. | -| `install.sh` | Host setup for admins / IT: users, groups, directories, venv, and all of the above. Idempotent, run as root. Not needed to add a daemon. | +| `install.sh` | Host setup for admins / IT. Bootstraps uv, builds the venv, then hands over to `hispec install`. Idempotent, run as root. Not needed to add a daemon. | + +`hispec install` writes the rest, rendered from +`src/hispec/cli/instrument.toml`: + +| Installed | What it is | +| --- | --- | +| `/etc/systemd/system/hispec@.service` | The template unit. One file runs every daemon; `hispec@` is an instance of it. | +| `/etc/polkit-1/rules.d/49-hispec.rules` | Lets `hispec-ops` members start/stop/restart `hispec@*` without sudo. | +| `/usr/local/bin/hispec` | Wrapper that runs the CLI from the venv. | +| `/usr/local/sbin/hispec-enable` | Root helper behind `hispec enable` / `hispec deploy`: enables/disables instances at boot, which `systemctl enable` cannot be granted per-unit. Invoked via `sudo -n` by a NOPASSWD drop-in. | +| `/etc/sudoers.d/hispec-ops` | That drop-in. | + +The templates live in +[instrumentctl](https://github.com/CaltechOpticalObservatories/instrumentctl), +so a change to the unit or the polkit rule is made there and reaches a host +through `install.sh`. `hispec doctor` compares what is installed against what +`install` would write now. ## Quick reference diff --git a/systemd/bin/hispec b/systemd/bin/hispec deleted file mode 100755 index 60e8383..0000000 --- a/systemd/bin/hispec +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# Front door for the hispec CLI, installed to /usr/local/bin. -# -# The CLI itself lives in the venv, installed editable from the repo, so a -# `git pull` updates it with no reinstall. This wrapper only exists so that a -# missing or broken venv produces an instruction rather than "command not -# found", since `hispec doctor` is exactly what people run when things break. - -VENV_DIR=${HISPEC_VENV_DIR:-/opt/hispec/venv} -REPO_DIR=${HISPEC_REPO_DIR:-/opt/hispec/app} - -if [[ ! -x $VENV_DIR/bin/hispec ]]; then - echo "hispec: $VENV_DIR/bin/hispec is missing; the venv needs (re)installing." >&2 - echo " admin: sudo $REPO_DIR/systemd/install.sh" >&2 - exit 1 -fi -exec "$VENV_DIR/bin/hispec" "$@" diff --git a/systemd/bin/hispec-enable b/systemd/bin/hispec-enable deleted file mode 100755 index fc08789..0000000 --- a/systemd/bin/hispec-enable +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -# Enable (or disable) hispec@ units at boot. -# -# `systemctl enable` needs the org.freedesktop.systemd1.manage-unit-files -# polkit action, which polkit cannot scope to a single unit by name. Granting -# it to hispec-ops would grant enable/disable on every unit on the host. This -# helper is the narrow alternative: it runs as root via a sudoers drop-in that -# is NOPASSWD for hispec-ops (see install.sh), and it only ever touches -# hispec@ for a name that already has an instance file deployed. -# -# Operators normally reach it through `hispec enable` / `hispec deploy`, which -# call it as `sudo -n hispec-enable ...`. It stays a standalone root-owned -# script rather than part of the CLI because the CLI runs from the venv, which -# the hispec user can write to, and root must not run code from there. -# -# hispec-enable hsfei_adc hsfei_ms # enable at boot (does not start) -# hispec-enable --now hsfei_adc # enable and start -# hispec-enable --disable hsfei_adc # disable at boot (does not stop) - -set -euo pipefail -export PATH=/usr/sbin:/usr/bin:/sbin:/bin - -INSTANCE_DIR=/etc/hispec/instances - -verb=enable -now=() - -usage() { - awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0" - exit "${1:-0}" -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --disable) verb=disable; shift ;; - --now) now=(--now); shift ;; - -h|--help) usage 0 ;; - --*) echo "hispec-enable: unknown option $1" >&2; usage 1 ;; - *) break ;; - esac -done - -[[ $# -gt 0 ]] || usage 1 - -# Validate before doing anything, so a typo in the third name does not leave -# the first two half-applied. -for name in "$@"; do - if [[ ! $name =~ ^[a-z][a-z0-9_]*$ ]]; then - echo "hispec-enable: refusing '$name': not a valid instance name" >&2 - exit 1 - fi - if [[ $verb == enable && ! -f "$INSTANCE_DIR/$name.env" ]]; then - echo "hispec-enable: no $INSTANCE_DIR/$name.env; deploy the instance file first" >&2 - exit 1 - fi -done - -for name in "$@"; do - systemctl "$verb" ${now[@]+"${now[@]}"} "hispec@$name.service" -done diff --git a/systemd/hispec@.service b/systemd/hispec@.service deleted file mode 100644 index c1a96f6..0000000 --- a/systemd/hispec@.service +++ /dev/null @@ -1,63 +0,0 @@ -# HISPEC hardware daemon: systemd template unit. -# -# This is a template: one file manages any daemons//