DD-0523 — !7 (31586c30, 0.1.5) moved install.sh and config.example.yaml to the
443 intercept after env-qa-02 hit "service not available". The ansible role
default (monky_deployd_tenancy_port) and TenancyCfg.port still said 8081, so
an ansible-installed box or a config that omits `port` still dialled the wrong
port; both now default to 443, the proxy-mapping and config tests follow, and
PROTOCOL.md §Where and how states the intercept port separately from the
in-pod 8081 and names openziti state/overlay/configs.json as the authority.
DD-0525 — PROTOCOL.md and README said "the broker adds the attr when the
identity is created at kit reveal". monky-ziti at b44c50a4 has no such code
(app/fabric.py host_identity_attrs carries the env template only) and openziti
docs/services.md says "Nothing carries the attr yet". Both now state the
dependency: an operator adds #monky-deploy-agent/#openbao-client on the
controller until the ADR-0028 addendum lands in monky-ziti.
DD-0527 — "the old kit's grant fails at login (unknown/used jti)". The
jwt-tenancy mount keeps no replay state (openbao terraform/jwt-tenancy.tf
see_env role: signature, aud, bound_claims, exp); a superseded grant logs in
until exp and the refusal is tenancy's 401 on the first bearer call. The
second-reveal paragraph, the grant-flow diagram and README §Security model say
so; FakeBao no longer pops a grant at login (the suite's superseded-token test
already goes through FakeTenancy.superseded_jtis, which is the real model).
DD-0528 — "Both locations keep being published": release:gitea has been a
never-run manual job on every tag pipeline (6999, 7044, 7066); README and
OPERATIONS.md now say when the Gitea mirror is published and that it has not
been yet.
Gates (local, py3.12): ruff format, ruff check, pytest 50 passed,
bash -n packaging/install.sh. `git grep 8081` afterwards hits only the in-pod
listener statements.
Doc-Drift: DD-0523 fixed
Doc-Drift: DD-0525 fixed
Doc-Drift: DD-0527 fixed
Doc-Drift: DD-0528 fixed
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AW3QqEpwLV69KHn24Re45Q
8.5 KiB
Operating monky-deployd
Units
| unit | what |
|---|---|
monky-deployd.timer |
fires monky-deployd.service 60 s after the previous tick finished (+ ≤10 s jitter); OnBootSec=90s |
monky-deployd.service |
Type=oneshot, monky-deployd run --once as user monky-deployd (+ docker group); SuccessExitStatus=75; hardened (NoNewPrivileges, ProtectSystem=strict, ReadWritePaths=/var/lib/monky-deployd /etc/monky-deployd /run/docker.sock, UMask=0077, no capabilities) |
monky-deployd-proxy.service |
only with transport: proxy: ziti tunnel proxy -i <identity> monky.tenancy.deploy:18443 openbao:18200 as user ziti; EnvironmentFile=/etc/monky-deployd/proxy.env |
ziti-edge-tunnel.service |
the host identity's tunneler in run-host mode (drop-in run-host.conf written by install.sh) |
systemctl status monky-deployd.timer monky-deployd.service
systemctl list-timers monky-deployd.timer
systemctl start monky-deployd.service # tick now (blocks until done)
journalctl -u monky-deployd -f # priorities are real (INFO/WARNING/ERROR)
monky-deployd status # exit 0 = token present and in sync
monky-deployd status --json | jq .
Files: /etc/monky-deployd/config.yaml (0640 root:monky-deployd), /etc/monky-deployd/openbao-ca.pem,
/etc/monky-deployd/bootstrap.jwt (only until the first login), /var/lib/monky-deployd/{bao.token,state.json,lock},
/var/lib/monky-deployd/<env>/releases/<sha>/ + current (the compose project dir, .env 0600),
/opt/openziti/etc/identities/monky-host.<env>.json (ziti:ziti 0600 + ACL u:monky-deployd:r).
Reading the journal
| line | meaning |
|---|---|
checkin: action=none desired=… applied=… then healthy; heartbeat reported |
converged |
checkin: action=apply … → read 3 secret(s): GEMINI_API_KEY, … → promoted release … → applied … |
a deploy |
refused: ENV_INCOMPLETE: unresolved: X |
the bundle needs a variable no manifest entry supplies — fix the descriptor / set the secret in the console; nothing was started |
refused: DISK_INSUFFICIENT: docker data-root has N MiB free, bundle needs M MiB |
free space (the env-dev-09 lesson): grow the data-root disk or prune |
refused: PRIVILEGED_REFUSED / ROLLBACK_REFUSED |
the bundle needs allow_privileged / allow_rollback in its agent profile |
temporary network failure (exit 75) |
mesh/tenancy unreachable — check ziti-edge-tunnel, the identity's terminators, monky.tenancy.deploy health |
AGENT_UNAUTHENTICATED: bearer refused (exit 1) |
the grant was superseded (kit re-revealed / retire) or the token revoked → re-run the install kit |
AGENT_ENV_MISMATCH (exit 78) |
the token belongs to another env than config.yaml — fix the config or re-issue the identity; the timer keeps firing but every tick exits 78 immediately (no storm) |
failed: docker compose pull failed (rc=1) |
registry/pull problem; compose output is in the report's tail and in the journal |
Values never appear in the journal ([REDACTED] for token shapes and every value the agent has read).
Secrets on the box
The only credential is /var/lib/monky-deployd/bao.token (0600, user monky-deployd). .env
files under releases/<sha>/ hold the rendered values (0600, same user, docker compose reads
them). Rotating a secret in the console changes the bundle sha → the next tick re-reads and
re-applies. To force a re-read now: systemctl start monky-deployd.service.
Lost or revoked token: monky-deployd status shows token ABSENT; reveal the kit again (admin,
GET /v1/backends/{id}/agent/install), paste its bootstrap grant to /etc/monky-deployd/bootstrap.jwt
(0600 monky-deployd) or re-run install.sh (enrolment is skipped when the identity exists), then
monky-deployd bootstrap or wait a tick.
Retire and volumes policy
Retire is driven by tenancy (POST /v1/backends/{id}/retire {confirm, force, purge_volumes}):
the next check-in returns action: down and the agent runs docker compose down --remove-orphans.
Data volumes:
-v(remove volumes) only when tenancy sentpurge_volumes: trueor the box'svolumes_on_absent: purge— and never on a prod env (the request is logged and ignored).- default
volumes_on_absent: keep: volumes stay for a manualdocker volume rmlater.
After down the agent reports down, clears applied_sha, removes current and keeps
releases/ (no secrets outside .env, which you can shred). Tenancy then destroys the Bao
paths, revokes the token accessor and deletes the host identity → the following ticks fail with
AGENT_UNAUTHENTICATED; systemctl disable --now monky-deployd.timer and apt remove monky-deployd
(apt purge also removes /var/lib/monky-deployd and /etc/monky-deployd).
Laptop mode
laptop_mode: true (install.sh --laptop): being offline is normal — a tick that cannot reach the
mesh logs one INFO line and exits 0. Run without the system timer:
monky-deployd run # loop, interval_s + jitter, SIGTERM stops; or a user timer with `run --once`
Liveness grace for laptops is 24 h on the tenancy side (P0); offline only hides the backend from
the picker, nothing is retired automatically.
Housekeeping
monky-deployd run --once --prune removes every releases/<sha> except current and runs
docker image prune -f after a successful apply. The state dir and the token survive apt remove;
apt purge deletes them.
Where the installer downloads from
The primary source is the scm.tikali.ai generic package registry of this project:
https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/<ver>/<file> for
monky-deployd_<ver>_amd64.deb, .sha256 and install.sh. The project is private (its parent
groups are private, so it cannot be made public): every fetch, the script itself included, sends the
DEPLOY-TOKEN header with a read-only GitLab deploy token — scope read_package_registry only,
nothing else (no repository, no API, no write); revocable at any time in the project's Settings →
Repository → Deploy tokens. It lives in OpenBao at monky/monky-tenancy/deployd-download (key
token); the monky-tenancy install kit carries it and passes --token, the ansible role sends it
from the vaulted monky_deployd_download_token. The one-liner:
curl -sSf -H "DEPLOY-TOKEN: $T" https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/<ver>/install.sh \
| sudo bash -s -- --env <id> --site <site> --token "$T" --bootstrap-file bootstrap.jwt
install.sh never prints the token (it goes through a 0600 curl -K file that is deleted after the
download; xtrace is switched off). A 401 on the download means the token is missing, revoked or
lacks the scope. Inside the estate gitea.cbs.tikali.net is split-horizon to jump1's RED EIP
(10.10.0.175), which has no HTTP ingress, so backend boxes cannot fetch from the Gitea mirror
(cbs/iac#102). Off-estate, install.sh --source gitea (ansible: monky_deployd_base_url/_deb_url,
no token) uses the Gitea release instead. The tag pipeline always publishes to GitLab (release); release:gitea is automatic only with
GITEA_TOKEN in CI and a manual job otherwise — it has not run on a tag pipeline yet.
Publishing a release to Gitea by hand
When release:gitea is manual (no GITEA_TOKEN in CI) — from jump1 with the Gitea API token:
T=$(tr -d '\n' < ~/.gitlab_tokens/gitea-token); G=http://172.16.8.1:3000/api/v1/repos/mdella/monky-deployd
curl -s -X POST -H "Authorization: token $T" $G/mirror-sync # pull the tag
RID=$(curl -s -X POST -H "Authorization: token $T" -H 'Content-Type: application/json' $G/releases \
-d '{"tag_name":"v0.1.0","name":"monky-deployd v0.1.0","body":"See CHANGELOG.md"}' | jq -r .id)
for f in monky-deployd_0.1.0_amd64.deb monky-deployd_0.1.0_amd64.deb.sha256 install.sh; do
curl -s -X POST -H "Authorization: token $T" -F "attachment=@$f" "$G/releases/$RID/assets?name=$f"; done
The assets are then at https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v0.1.0/<file>,
which is what install.sh --source gitea fetches (GitLab artifacts from the tag pipeline's package job).
Ansible (osg1-07)
ansible/roles/monky_deployd/ is the role skeleton to copy into osg1-07: .deb from the
scm.tikali.ai package registry (sha256-verified), config template, bootstrap grant from a vault var, ACL on the identity,
timer, one tick. Rolled to env-dev-06..09 after the env-qa-02 pilot; env-dev-01 last.