Design merged first: monky-design-docs !225 (doc 24 §4a). Pairs with monky-tenancy!40, which copies the estate-wide read-only Harbor robot into each environment's own prefix and marks the manifest entry `use: registry-auth`. - That entry is not an env var (it would otherwise land in .env and therefore in every container's environment). The agent parses it — JSON, or `username:password` with the new `registry_host` — and writes `<state_dir>/docker/config.json` 0600 in a directory it owns, with an explicit DOCKER_CONFIG pointing the docker CLI at it. The unit runs as monky-deployd, so a `docker login` by a human or by root is invisible to the agent: that is what made env-dev-01 look like it had no credential at all after the operator had just logged in. - `compose pull` failures now carry the registry's own message ("no basic auth credentials", "manifest unknown", DNS) into the journal and the report instead of `rc=1`. - Tests: both credential shapes, the refusals, 0600/0700 modes, idempotent rewrite, and that the runner never silently falls back to a human's $HOME. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
7.1 KiB
Changelog
0.1.7 — registry credential + a pull error you can read (2026-09-08)
- No hand
docker loginon a new box (monky-design-docs !225, doc 24 §4a). A manifest entry markeduse: registry-authis the Harbor pull credential, not an env var: it is written to<state_dir>/docker/config.json(0600, in a directory the agent owns) and the docker CLI is pointed at it with an explicitDOCKER_CONFIG. That last part is the trap — the unit runs asmonky-deployd, so adocker loginby a human or by root is invisible to the agent and looks exactly like no credential at all (env-dev-01, 2026-09-08). Both seeded shapes are accepted: a JSON object, orusername:passwordwith the registry from the newregistry_hostconfig. compose pullfailures carry the registry's own message instead ofrc=1. "no basic auth credentials", "manifest unknown" and DNS failures now reach the journal and the report to tenancy.
0.1.6 — 2026-09-07
- A valid lease token is reused across applies. Every apply requested a new lease, so a failing deploy
retried by the 60 s timer burned tenancy's 5-leases-per-hour budget and then failed on
LEASE_RATE_LIMITEDforever (env-qa-02 pilot). Now: reuse a lease-derived token whilelookup-selfsays it is valid; swap the bootstrap token for a lease once; and if tenancy rate-limits the lease while a working token exists, apply with it and defer the swap instead of failing the deploy.
0.1.5 — 2026-09-07
-
tenancy.portdefaults to 443 everywhere — the ansible role default and theTenancyCfgdefault now matchinstall.sh/config.example.yaml(0.1.5 only changed those two); PROTOCOL.md states the intercept port separately from the in-pod 8081, that the broker does not add#monky-deploy-agentyet, that the jwt mount keeps nojtistate, and thatrelease:giteahas not run (doc-drift DD-0523/0525/0527/0528). -
${VAR}inside comment lines is not a reference. The renderer's.env.templateheader literally says "substitutes every ${VAR}", which the refusal check counted as an unresolved variable (ENV_INCOMPLETE: unresolved: VAR) — the first bundle on env-qa-02 was refused for it. -
config:
tenancy.portis the service's intercept port (443), not the in-pod 8081. With 8081 the SDK found no intercept and the dial failed (service not available, then a bareTypeErrorfrom the SDK's fallback).install.shnow writes 443;config.example.yamlupdated. -
transport sdk: clear error instead of a TypeError when an address has no intercept or the identity has no dial policy for the service (
TransportErrornames the host:port and what to check).
0.1.4 — 2026-09-07
- install.sh:
ziti-edge-tunnel.servicefailed to start after enrolment (203/EXEC: the package'sExecStartPrescript is not executable by userzition ziti-edge-tunnel 1.18.x) and, because the unit was enabled with--nowunderset -e, the script aborted BEFORE writing config/ACLs/grant/timer — a second kit was needed. The run-host drop-in now clearsExecStartPre=; a tunneler start failure is a WARNING until everything else is in place, then a hard error at the end.
0.1.3 — 2026-09-06
- install.sh:
--versionwas clobbered by/etc/os-release(it definesVERSION), so the kit tried to downloadmonky-deployd_26.04 LTS (Resolute Raccoon)_amd64.deb(env-qa-02 pilot, first real kit run). Script variables are nowDEPLOYD_VERSION; os-release is read in a subshell. - install.sh installs Docker Engine when absent (Docker's apt suite for the codename → fallback
docker.io), with--docker-data-root <dir>to place the data-root before first start. One script for the end user, per the operator's rule.
v0.1.2 — 2026-09-05
- The GitLab project is private (its parent groups are private, so it cannot be made public;
found when the v0.1.1 one-liner returned 401 anonymously).
install.shgains--token <deploy-token>/MONKY_DEPLOYD_TOKEN: every download from the generic package registry — the script itself included — sendsDEPLOY-TOKEN: <token>, a read-only GitLab deploy token (scoperead_package_registryonly, revocable), seeded in OpenBao atmonky/monky-tenancy/deployd-download(keytoken) and handed to the box by the monky-tenancy install kit. The token never reaches the command line, the log or an xtrace (curl-Kconfig file, 0600, deleted after the download;set +xforced). The one-liner now fetchesinstall.shfrom the registry (…/monky-deployd/<ver>/install.sh) instead of-/raw/main, and takes the bootstrap grant via--bootstrap-file— withcurl … | bash -s --stdin IS the script, so it is no longer read for the grant in that mode.--source gitea(no token) stays the off-estate alternative. - Ansible role:
monky_deployd_download_token(vaulted) →DEPLOY-TOKENheader on both fetches,no_log: true. - README / OPERATIONS: why (private project; split-horizon Gitea, cbs/iac#102), token scope, revocation. No agent behaviour change.
v0.1.1 — 2026-09-05
install.sh/ ansible role / README: the primary download is the public GitLab project's generic package registry on scm.tikali.ai (/api/v4/projects/69/packages/generic/monky-deployd/<ver>/…); the one-liner fetches the script fromscm.tikali.ai/…/-/raw/main/packaging/install.sh. Inside the estategitea.cbs.tikali.netis split-horizon to jump1's RED EIP (no HTTP ingress), so backend boxes could not fetch the artefacts (cbs/iac#102). The Gitea release stays the off-estate alternative (--source gitea/MONKY_DEPLOYD_SOURCE=gitea). No agent behaviour change.
v0.1.0 — 2026-09-05
First release (MONKY-ADR-0028 §D, Reconciliation v2, Gate 1 v2).
- Stdlib-only Python 3.12 agent:
run --once|loop,status,bootstrap,--prune. - Protocol:
POST /v1/agent/checkin(action apply|none|down),GET /v1/agent/bundle/{env}/{sha}(sha256 verified),POST /v1/agent/lease→ deploy grant (login_jwt),POST /v1/agent/report(result applied|failed|down, redactedlog_tail). An AppRole-shaped lease is refused (LEASE_SHAPE). - OpenBao:
POST /v1/auth/jwt-tenancy/login {"role":"see-env","jwt":…}; KV-v2 reads pinned to the manifest's versions, paths pinned tomonky/data/<env>/see/; renew-self / re-lease before max TTL. - Refusals:
ENV_INCOMPLETE(names only),PRIVILEGED_REFUSED,ROLLBACK_REFUSED,DISK_INSUFFICIENT(need × 1.5 + 2 GiBvs docker data-root),BUNDLE_SHA_MISMATCH,BUNDLE_ENV_MISMATCH. - Transports
sdk(openziti SDK),proxy(ziti tunnel proxy18443/18200),system. - Exit codes 0 / 75 / 78 (
AGENT_ENV_MISMATCH, no retry storm) / 1; laptop mode (offline exits 0). - Packaging: hardened
monky-deployd.serviceoneshot + 60 s timer,monky-deployd-proxy.service,nfpm.debwith/opt/monky-deployd/venv,packaging/install.sh(Ubuntu 26.04), ansible role skeleton. - Known divergence: monky-tenancy
main(MR !15) still ships the AppRole lease/kit; the JWT-grant follow-up is the tenancy side of this release (docs/PROTOCOL.md "Divergences").