diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index e77a8bb..f370b4f 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -217,7 +217,7 @@ flowchart LR end subgraph etc["/etc/hispec"] - ENV["instances/<name>.env
HISPEC_DAEMON, HISPEC_CONFIG"] + ENV["instances/<name>.env
HISPEC_DAEMON, HISPEC_HOST"] YML["<name>.yaml
deployed config"] end @@ -231,8 +231,9 @@ flowchart LR One systemd template unit, `hispec@.service`, runs every daemon. An instance `hispec@` reads `/etc/hispec/instances/.env`, which -names the daemon script (`HISPEC_DAEMON`, relative to `daemons/`) and its config -(`HISPEC_CONFIG`). Running a second unit of the same hardware model — two +names the daemon script (`HISPEC_DAEMON`, relative to `daemons/`). Its config +is `/etc/hispec/.yaml`, derived from the instance name rather than +declared. Running a second unit of the same hardware model, two Lakeshores, five filter wheels — is two env files pointing at the same script with different configs. diff --git a/docs/operations/systemd.md b/docs/operations/systemd.md index 293cc3e..32efa28 100644 --- a/docs/operations/systemd.md +++ b/docs/operations/systemd.md @@ -36,7 +36,7 @@ An instance gets its identity from two files: | File | What it says | |---|---| -| `/etc/hispec/instances/.env` | which daemon script to run (`HISPEC_DAEMON`) and which config to hand it (`HISPEC_CONFIG`) | +| `/etc/hispec/instances/.env` | which daemon script to run (`HISPEC_DAEMON`) and which host should run it (`HISPEC_HOST`) | | `/etc/hispec/.yaml` | the config itself: serial ports, addresses, limits, keyword names | So running a second Lakeshore is not new code and not a new unit. It is a @@ -239,7 +239,6 @@ matching `systemd/instances/.env`: ```sh HISPEC_DAEMON= -HISPEC_CONFIG=/etc/hispec/.yaml HISPEC_HOST= ``` @@ -368,10 +367,11 @@ Common causes, in the order they bite: - **`Failed to load environment files`**: `/etc/hispec/instances/.env` is missing or unreadable. The instance name in the command must match the file name exactly. -- **`No such file or directory`** on the config: `HISPEC_CONFIG` points - somewhere that does not exist. The config has to be deployed to `/etc/hispec/` - as well as the `.env`. `hispec deploy ` copies both; copying one by - hand and forgetting the other is the usual mistake. +- **`No such file or directory`** on the config: the unit passes + `/etc/hispec/.yaml`, derived from the instance name, and nothing is + there. The config has to be deployed as well as the `.env`. `hispec deploy + ` copies both; copying one by hand and forgetting the other is the + usual mistake. - **A serial port or USB error**: the device is unplugged, powered off, or claimed by another process. Two instances pointing at the same port will do this to each other, and so will a daemon left running from a manual test. @@ -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/pyproject.toml b/pyproject.toml index 025e753..36e29d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ # influxdb extra: needed by daemons/generic/keygrabber "libby[influxdb]@git+https://github.com/CaltechOpticalObservatories/libby.git", "hardware_device_base@git+https://github.com/COO-Utilities/hardware_device_base", - "instrumentctl@git+https://github.com/CaltechOpticalObservatories/instrumentctl.git", + "instrumentctl@git+https://github.com/CaltechOpticalObservatories/instrumentctl.git@v0.2.0", "camera_daemon@git+https://github.com/COO-Utilities/camera-daemon.git" ] diff --git a/src/hispec/cli/instrument.toml b/src/hispec/cli/instrument.toml index d656d31..2729b67 100644 --- a/src/hispec/cli/instrument.toml +++ b/src/hispec/cli/instrument.toml @@ -10,9 +10,10 @@ [instrument] name = "hispec" +display_name = "HISPEC" user = "hispec" ops_group = "hispec-ops" -# Instance files set HISPEC_DAEMON, HISPEC_CONFIG and HISPEC_HOST +# Instance files set HISPEC_DAEMON and HISPEC_HOST env_prefix = "HISPEC" docs_url = "https://caltechopticalobservatories.github.io/hispec/operations/systemd.html" diff --git a/systemd/README.md b/systemd/README.md index 73d1e3d..79d2778 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. | +| `instances/.env` | Per-instance settings: `HISPEC_DAEMON` (script, relative to `daemons/`) and `HISPEC_HOST` (which host runs it). Deployed to `/etc/hispec/instances/`; the config path is derived as `/etc/hispec/.yaml`. | +| `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//