|
| 1 | +# Kater runtime on bc-scan-arm |
| 2 | + |
| 3 | +Target: move the canonieke Kater runtime off laptop `joep` onto always-on |
| 4 | +`bc-scan-arm`, with ChefVault fail-closed secret bootstrap and no |
| 5 | +`/home/<person>` paths. |
| 6 | + |
| 7 | +This is packaging + runbook only. Do **not** cut over production traffic until |
| 8 | +shadow deploy + soaktest pass. |
| 9 | + |
| 10 | +## Layout |
| 11 | + |
| 12 | +```text |
| 13 | +/opt/kater/releases/<git-sha>/ |
| 14 | +/opt/kater/current -> /opt/kater/releases/<git-sha> |
| 15 | +/etc/kater/kater.conf # non-secret config (0640, root:kater) |
| 16 | +/etc/kater/broker-token # ChefVault consumer token (0600, kater:kater) |
| 17 | +/var/lib/kater # SQLite / settings state |
| 18 | +/var/cache/kater # fleet cache and other regenerable data |
| 19 | +/var/log/kater # optional file logs (journald remains primary) |
| 20 | +/run/kater # RuntimeDirectory (tmpfs) |
| 21 | +``` |
| 22 | + |
| 23 | +## Service identity |
| 24 | + |
| 25 | +```bash |
| 26 | +sudo useradd --system --home /var/lib/kater --shell /usr/sbin/nologin kater |
| 27 | +sudo install -d -o kater -g kater -m 0750 /var/lib/kater /var/cache/kater /var/log/kater |
| 28 | +sudo install -d -o root -g kater -m 0750 /etc/kater |
| 29 | +``` |
| 30 | + |
| 31 | +## Non-secret config |
| 32 | + |
| 33 | +```bash |
| 34 | +sudo install -o root -g kater -m 0640 \ |
| 35 | + /opt/kater/current/scripts/systemd/kater.conf.example \ |
| 36 | + /etc/kater/kater.conf |
| 37 | +``` |
| 38 | + |
| 39 | +Edit host-specific values: |
| 40 | + |
| 41 | +- `UTRECHT_FLEET_INVENTORY_PATH=/var/cache/kater/utrecht-fleet/inventory/fleet.json` |
| 42 | +- keep `KATER_HOST=127.0.0.1` while `KATER_AUTH_MODE=none` |
| 43 | +- keep CI SSH target pointing at `ubuntu@bc-scan-2` |
| 44 | + |
| 45 | +## ChefVault broker token |
| 46 | + |
| 47 | +```bash |
| 48 | +sudo install -o kater -g kater -m 0600 /dev/null /etc/kater/broker-token |
| 49 | +sudo -u kater tee /etc/kater/broker-token >/dev/null <<'EOF' |
| 50 | +<kater broker token> |
| 51 | +EOF |
| 52 | +sudo chmod 0600 /etc/kater/broker-token |
| 53 | +``` |
| 54 | + |
| 55 | +Token scope: |
| 56 | + |
| 57 | +- profile `kater-dev-tools/ops` |
| 58 | +- only `Kater/*` collections needed by that profile |
| 59 | +- no admin / unrelated collections |
| 60 | + |
| 61 | +Broker URL (private, not public): |
| 62 | + |
| 63 | +```bash |
| 64 | +# example in /etc/kater/kater.conf |
| 65 | +CHEF_VAULT_BROKER_URL=http://127.0.0.1:8322 |
| 66 | +``` |
| 67 | + |
| 68 | +## Install release |
| 69 | + |
| 70 | +```bash |
| 71 | +SHA="$(git -C /path/to/checkout rev-parse HEAD)" |
| 72 | +sudo mkdir -p "/opt/kater/releases/$SHA" |
| 73 | +sudo rsync -a --delete \ |
| 74 | + --exclude .git --exclude .venv --exclude .kater \ |
| 75 | + /path/to/checkout/ "/opt/kater/releases/$SHA/" |
| 76 | +sudo chown -R kater:kater "/opt/kater/releases/$SHA" |
| 77 | +# Runtime state dir the unit lists in ReadWritePaths; systemd needs it to exist |
| 78 | +# before ExecStart, and rsync excluded it from the release. |
| 79 | +sudo install -d -o kater -g kater -m 0700 "/opt/kater/releases/$SHA/.kater" |
| 80 | +sudo ln -sfn "/opt/kater/releases/$SHA" /opt/kater/current |
| 81 | +sudo -u kater bash -lc 'cd /opt/kater/current && uv sync --frozen' |
| 82 | +``` |
| 83 | + |
| 84 | +The release must be owned by `kater` before `uv sync`: the virtualenv is created |
| 85 | +project-local at `/opt/kater/current/.venv`, and the unit's `ExecStart` depends on |
| 86 | +`/opt/kater/current/.venv/bin/python` existing. |
| 87 | + |
| 88 | +## Fleet cache bootstrap |
| 89 | + |
| 90 | +```bash |
| 91 | +sudo -u kater bash -lc ' |
| 92 | + mkdir -p /var/cache/kater |
| 93 | + if [ ! -d /var/cache/kater/utrecht-fleet/.git ]; then |
| 94 | + git clone --depth 1 git@github.com:<org>/<fleet-inventory-repo>.git \ |
| 95 | + /var/cache/kater/utrecht-fleet |
| 96 | + else |
| 97 | + git -C /var/cache/kater/utrecht-fleet pull --ff-only |
| 98 | + fi |
| 99 | + test -f /var/cache/kater/utrecht-fleet/inventory/fleet.json |
| 100 | +' |
| 101 | +``` |
| 102 | + |
| 103 | +Prefer atomic refresh later (clone/pull into temp dir → validate → rename). |
| 104 | + |
| 105 | +## Systemd unit |
| 106 | + |
| 107 | +```bash |
| 108 | +sudo install -m 0644 \ |
| 109 | + /opt/kater/current/scripts/systemd/kater-system.service.example \ |
| 110 | + /etc/systemd/system/kater.service |
| 111 | +sudo systemctl daemon-reload |
| 112 | +sudo systemctl enable --now kater.service |
| 113 | +``` |
| 114 | + |
| 115 | +The unit: |
| 116 | + |
| 117 | +- runs as `User=kater` |
| 118 | +- uses `EnvironmentFile=/etc/kater/kater.conf` for non-secrets |
| 119 | +- starts via `scripts/kater-with-chefvault.py serve ...` (fail-closed) |
| 120 | +- binds loopback only |
| 121 | +- hardens filesystem with `ProtectHome=true` and `ProtectSystem=strict` |
| 122 | + |
| 123 | +## Acceptance checks (shadow) |
| 124 | + |
| 125 | +```bash |
| 126 | +systemctl is-active kater.service |
| 127 | +curl --fail --silent http://127.0.0.1:9091/health/live |
| 128 | +curl --silent http://127.0.0.1:9091/health/ready |
| 129 | +journalctl -u kater.service -n 100 --no-pager | rg -i 'token|secret|ghp_|cfut_|cfat_' || true |
| 130 | +``` |
| 131 | + |
| 132 | +Negative tests (must fail closed / degrade predictably): |
| 133 | + |
| 134 | +1. missing `/etc/kater/broker-token` |
| 135 | +2. broker unreachable |
| 136 | +3. missing fleet.json |
| 137 | +4. `bc-scan-2` SSH unreachable |
| 138 | +5. host reboot → service returns without laptop `joep` |
| 139 | + |
| 140 | +## Cutover gate |
| 141 | + |
| 142 | +Only after: |
| 143 | + |
| 144 | +- no runtime path under `/home/joep` |
| 145 | +- secrets materialize via ChefVault, not a durable plaintext env of provider keys |
| 146 | +- health/live always up when process is up |
| 147 | +- health/ready reports component degradation clearly |
| 148 | +- soaktest on `bc-scan-arm` (memory/CPU/FDs/orphans) passes |
| 149 | +- laptop runtime kept as rollback for the observation window |
0 commit comments