4 Commits

Author SHA1 Message Date
monky-deployd-merger f8a35947db Merge branch 'feat/deploy-token-download' into 'main'
feat(install): read-only deploy token for the private package registry (v0.1.2)

See merge request tikali/applications/monky/monky-deployd!4
2026-09-05 19:10:24 +00:00
mdella 6336012b74 feat(install): read-only deploy token for the private package registry (v0.1.2)
The GitLab project is private (its parent groups are private, so it cannot be
made public): the v0.1.1 one-liner answered 401 anonymously. install.sh gains
--token / MONKY_DEPLOYD_TOKEN and sends `DEPLOY-TOKEN: <token>` (a GitLab deploy
token, scope read_package_registry only, revocable) on every registry download,
the script itself included; the token goes through a 0600 curl -K file (never the
command line, the log or an xtrace). The grant is taken via --bootstrap-file when
the script is piped (stdin IS the script). Ansible: monky_deployd_download_token
(vaulted) -> DEPLOY-TOKEN header, no_log. Docs explain why, the token's scope and
the --source gitea alternative (split-horizon Gitea, cbs/iac#102).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
2026-09-05 19:03:25 +00:00
monky-deployd-merger bd3f8c1b6e Merge branch 'fix/install-urls-scm' into 'main'
fix(install): fetch from scm.tikali.ai (public project) — Gitea name is split-horizon inside the estate

See merge request tikali/applications/monky/monky-deployd!3
2026-09-05 17:59:27 +00:00
mdella c966450d8e fix(install): fetch from scm.tikali.ai (public project) — Gitea name is split-horizon inside the estate
Inside the estate gitea.cbs.tikali.net resolves to jump1's RED EIP (10.10.0.175),
which has no HTTP ingress, so backend boxes could not download the install
artefacts from the Gitea mirror (cbs/iac#102). scm.tikali.ai is reachable from
those boxes and the project is now public, so the GitLab generic package
registry becomes the PRIMARY source:

- packaging/install.sh: default source = scm.tikali.ai generic package registry
  (projects/69/packages/generic/monky-deployd/<ver>/...); `--source gitea` /
  MONKY_DEPLOYD_SOURCE=gitea keeps the Gitea release as the off-estate
  alternative; --base-url / MONKY_DEPLOYD_BASE_URL still override the base.
- ansible role defaults: monky_deployd_base_url/_deb_url point at the registry,
  Gitea layout kept as a commented alternative.
- README / docs/OPERATIONS.md / CLAUDE.md / CI comments + release description:
  both locations keep being published (release + release:gitea).
- Version 0.1.1 (the tag gate refuses v* tags whose version != __version__);
  tests compare against __version__ instead of a literal. No agent change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
2026-09-05 17:57:27 +00:00
14 changed files with 190 additions and 49 deletions
+12 -6
View File
@@ -8,6 +8,11 @@
# #
# CI/CD variables (project or group level): # CI/CD variables (project or group level):
# GITEA_TOKEN — Gitea API token (write:repository) for `release:gitea`; without it the job is manual # GITEA_TOKEN — Gitea API token (write:repository) for `release:gitea`; without it the job is manual
#
# Release assets are published to BOTH the GitLab generic package registry (the installer's primary
# source — the project is PRIVATE (parent groups are private), so install.sh sends a read-only deploy
# token (read_package_registry) as DEPLOY-TOKEN; gitea.cbs.tikali.net is split-horizon inside the
# estate, cbs/iac#102) and the Gitea mirror release (`install.sh --source gitea`, off-estate).
# GITLAB_TRANSLATE_TOKEN — used by the translate-docs component (group level) # GITLAB_TRANSLATE_TOKEN — used by the translate-docs component (group level)
include: include:
@@ -139,7 +144,8 @@ package:
dotenv: build.env dotenv: build.env
expire_in: 90 days expire_in: 90 days
# --- GitLab release (v* tags): generic package registry + release with asset links ------------- # --- GitLab release (v* tags): generic package registry (the PRIMARY download) + release ---------
# install.sh / the ansible role fetch ${CI_API_V4_URL}/projects/69/packages/generic/monky-deployd/<ver>/...
release: release:
stage: release stage: release
image: image:
@@ -155,15 +161,15 @@ release:
- 'DEB=$(basename dist/*.deb)' - 'DEB=$(basename dist/*.deb)'
- >- - >-
release-cli create --name "monky-deployd $CI_COMMIT_TAG" --tag-name "$CI_COMMIT_TAG" release-cli create --name "monky-deployd $CI_COMMIT_TAG" --tag-name "$CI_COMMIT_TAG"
--description "See CHANGELOG.md. Public install assets are on the Gitea mirror: https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/tag/$CI_COMMIT_TAG" --description "See CHANGELOG.md. Install assets: this release's package registry links (primary; the install.sh default) and the Gitea mirror for off-estate use: https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/tag/$CI_COMMIT_TAG"
--assets-link "{\"name\":\"$DEB\",\"url\":\"$PKG/$DEB\",\"link_type\":\"package\"}" --assets-link "{\"name\":\"$DEB\",\"url\":\"$PKG/$DEB\",\"link_type\":\"package\"}"
--assets-link "{\"name\":\"$DEB.sha256\",\"url\":\"$PKG/$DEB.sha256\",\"link_type\":\"other\"}" --assets-link "{\"name\":\"$DEB.sha256\",\"url\":\"$PKG/$DEB.sha256\",\"link_type\":\"other\"}"
--assets-link "{\"name\":\"install.sh\",\"url\":\"$PKG/install.sh\",\"link_type\":\"other\"}" --assets-link "{\"name\":\"install.sh\",\"url\":\"$PKG/install.sh\",\"link_type\":\"other\"}"
# --- Gitea release (the PUBLIC download the installer uses) ------------------------------------------ # --- Gitea release (the off-estate alternative: install.sh --source gitea) -----------------------
# The GitLab project is private, so install.sh fetches from the Gitea mirror # Same assets at https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v<ver>/... .
# https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v<ver>/... . This job # Not the default: inside the estate that name is split-horizon to jump1's RED EIP (cbs/iac#102).
# waits for the pull-mirror to carry the tag, creates the release and uploads the assets. # This job waits for the pull-mirror to carry the tag, creates the release and uploads the assets.
# Automatic when GITEA_TOKEN is set; otherwise a manual, non-blocking job (docs/OPERATIONS.md # Automatic when GITEA_TOKEN is set; otherwise a manual, non-blocking job (docs/OPERATIONS.md
# has the by-hand recipe). # has the by-hand recipe).
release:gitea: release:gitea:
+27
View File
@@ -1,6 +1,33 @@
<!-- xlate:verbatim-fences --> <!-- xlate:verbatim-fences -->
# Changelog # Changelog
## 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.sh` gains `--token <deploy-token>`
/ `MONKY_DEPLOYD_TOKEN`: every download from the generic package registry — the script itself
included — sends `DEPLOY-TOKEN: <token>`, a read-only GitLab **deploy token** (scope
`read_package_registry` only, revocable), seeded in OpenBao at `monky/monky-tenancy/deployd-download`
(key `token`) and handed to the box by the monky-tenancy install kit. The token never reaches the
command line, the log or an xtrace (curl `-K` config file, 0600, deleted after the download;
`set +x` forced). The one-liner now fetches `install.sh` from the registry
(`…/monky-deployd/<ver>/install.sh`) instead of `-/raw/main`, and takes the bootstrap grant via
`--bootstrap-file` — with `curl … | 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-TOKEN` header 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 from `scm.tikali.ai/…/-/raw/main/packaging/install.sh`. Inside the
estate `gitea.cbs.tikali.net` is 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 ## v0.1.0 — 2026-09-05
First release (MONKY-ADR-0028 §D, Reconciliation v2, Gate 1 v2). First release (MONKY-ADR-0028 §D, Reconciliation v2, Gate 1 v2).
+4 -2
View File
@@ -75,8 +75,10 @@ systemd-analyze verify packaging/systemd/*.service # when available
Bump `monky_deployd.__version__` + `pyproject.toml` + `packaging/install.sh` `DEFAULT_VERSION` + Bump `monky_deployd.__version__` + `pyproject.toml` + `packaging/install.sh` `DEFAULT_VERSION` +
`CHANGELOG.md`, merge to `main`, tag `vX.Y.Z` (protected `v*`). The tag pipeline builds the `.deb`, `CHANGELOG.md`, merge to `main`, tag `vX.Y.Z` (protected `v*`). The tag pipeline builds the `.deb`,
publishes the GitLab release and — with `GITEA_TOKEN` — the **Gitea release** the installer publishes the **GitLab generic package registry + release** the installer downloads from (PRIVATE
downloads from (`docs/OPERATIONS.md` has the manual recipe). `lint` refuses a tag whose version project — the installer sends a read-only deploy token as `DEPLOY-TOKEN`; the Gitea name is
split-horizon inside the estate, cbs/iac#102) and — with `GITEA_TOKEN` —
the **Gitea release** (`install.sh --source gitea`, off-estate; `docs/OPERATIONS.md` has the manual recipe). `lint` refuses a tag whose version
differs from `__version__`. differs from `__version__`.
## What NOT to do ## What NOT to do
+27 -9
View File
@@ -18,20 +18,37 @@ Verified on **Ubuntu 26.04**.
## Install (one-liner, from the enrolment kit) ## Install (one-liner, from the enrolment kit)
An admin reveals the kit once in the console (`GET /v1/backends/{id}/agent/install`); it hands An admin reveals the kit once in the console (`GET /v1/backends/{id}/agent/install`); it hands
you the enrolment JWT and a one-time **bootstrap deploy grant**. On the box: you the enrolment JWT, a one-time **bootstrap deploy grant** and the read-only **download token**
(the kit runs the line below for you). By hand, on the box:
```sh ```sh
curl -fsSL https://gitea.cbs.tikali.net/mdella/monky-deployd/raw/branch/main/packaging/install.sh \ T=<deploy token> # read-only GitLab deploy token (read_package_registry); the kit carries it
| sudo bash -s -- --env env-qa-02 --site cbs --enrol-jwt ./monky-host.env-qa-02.jwt < bootstrap.jwt curl -sSf -H "DEPLOY-TOKEN: $T" https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.2/install.sh \
# [--transport sdk|proxy|system] [--version 0.1.0] [--laptop] [--bao-ca openbao-ca.pem] | sudo bash -s -- --env env-qa-02 --site cbs --token "$T" --bootstrap-file bootstrap.jwt --enrol-jwt ./monky-host.env-qa-02.jwt
# [--transport sdk|proxy|system] [--version 0.1.2] [--laptop] [--bao-ca openbao-ca.pem] [--source gitlab|gitea]
``` ```
`install.sh` installs `ziti-edge-tunnel` (OpenZiti `jammy` suite) and `docker-compose-plugin` if `install.sh` installs `ziti-edge-tunnel` (OpenZiti `jammy` suite) and `docker-compose-plugin` if
absent, downloads the pinned `.deb` + `.sha256` from the [Gitea release](https://gitea.cbs.tikali.net/mdella/monky-deployd/releases), absent, downloads the pinned `.deb` + `.sha256` from the [scm.tikali.ai generic package registry](https://scm.tikali.ai/tikali/applications/monky/monky-deployd/-/packages)
(`https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/<ver>/monky-deployd_<ver>_amd64.deb`),
enrols `monky-host.<env>` if the identity is missing, switches the tunneler to `run-host`, enrols `monky-host.<env>` if the identity is missing, switches the tunneler to `run-host`,
writes `/etc/monky-deployd/config.yaml`, grants the agent read access to the identity (ACL), writes `/etc/monky-deployd/config.yaml`, grants the agent read access to the identity (ACL),
stages the bootstrap grant (0600), enables `monky-deployd.timer`, runs one tick and deletes the stages the bootstrap grant (0600), enables `monky-deployd.timer`, runs one tick and deletes the
JWT. The GitLab project is private, so **the public download is the Gitea mirror**. JWT.
**Why a token.** The GitLab project is **private** and cannot be made public (its parent groups
are private), so anonymous downloads answer 401. Every registry fetch — the script itself
included — therefore sends `DEPLOY-TOKEN: <token>`: a GitLab **deploy token** with the single
scope `read_package_registry` (it can download packages and nothing else: no code, no API, no
write; revoke it in the project's *Settings → Repository → Deploy tokens* and issue a new one).
The operator seeds it in OpenBao at `monky/monky-tenancy/deployd-download` (key `token`); the
monky-tenancy install kit reads it from there and passes `--token` (`MONKY_DEPLOYD_TOKEN` also
works). The script never prints it (curl `-K` config file, 0600, deleted after the download;
`set -x` is switched off). Why not the Gitea mirror: 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 reach it (cbs/iac#102); `--source gitea` (or `MONKY_DEPLOYD_SOURCE=gitea`, no token) keeps
the [Gitea release](https://gitea.cbs.tikali.net/mdella/monky-deployd/releases) as the
off-estate alternative.
## Transports ## Transports
@@ -140,9 +157,10 @@ systemd-analyze verify packaging/systemd/*.service # where systemd is availabl
egress to github.com and pypi.org) and `package` builds the `.deb` with `nfpm` (binary from GitHub egress to github.com and pypi.org) and `package` builds the `.deb` with `nfpm` (binary from GitHub
releases, goreleaser apt repo as fallback). Both were proven on the v0.1.0 tag pipeline and are releases, goreleaser apt repo as fallback). Both were proven on the v0.1.0 tag pipeline and are
blocking on `main`/tags (manual on MRs); the `.deb` always ships the SDK wheel. On a `v*` tag blocking on `main`/tags (manual on MRs); the `.deb` always ships the SDK wheel. On a `v*` tag
`release` uploads to the GitLab generic package registry + release, and `release:gitea` publishes `release` uploads to the GitLab generic package registry + release (**the primary download**; the
the same assets on the public Gitea mirror (automatic when `GITEA_TOKEN` is set, manual otherwise project is private, so the installer sends the read-only deploy token), and `release:gitea` publishes the same assets on the Gitea mirror (the `--source gitea`
— see `docs/OPERATIONS.md` for the by-hand recipe). alternative; automatic when `GITEA_TOKEN` is set, manual otherwise — see `docs/OPERATIONS.md` for
the by-hand recipe). Both locations keep being published.
## See also ## See also
+6 -2
View File
@@ -4,7 +4,9 @@ Installs and configures [monky-deployd](https://scm.tikali.ai/tikali/application
(the Monky backend pull agent, MONKY-ADR-0028) on a docker host that already carries an enrolled (the Monky backend pull agent, MONKY-ADR-0028) on a docker host that already carries an enrolled
host identity (`roles/ziti_tunneler`, run-host mode). Skeleton for **osg1-07**; copy it there. host identity (`roles/ziti_tunneler`, run-host mode). Skeleton for **osg1-07**; copy it there.
What it does: pin + download the `.deb` from the Gitea release (sha256 verified) → ACL What it does: pin + download the `.deb` from the scm.tikali.ai package registry (sha256 verified; the
project is **private**, so the fetches send the read-only deploy token `monky_deployd_download_token`
as `DEPLOY-TOKEN`, `no_log`; `monky_deployd_base_url`/`_deb_url` switch to the Gitea release off-estate) → ACL
`u:monky-deployd:r` on the identity (`rx` on the dir) → `/etc/monky-deployd/config.yaml` from the `u:monky-deployd:r` on the identity (`rx` on the dir) → `/etc/monky-deployd/config.yaml` from the
template → optional openbao-ca PEM → optional `monky-deployd-proxy.service` (`transport: proxy`) template → optional openbao-ca PEM → optional `monky-deployd-proxy.service` (`transport: proxy`)
→ the **one-time bootstrap deploy grant** from a vault var (0600, `no_log`) → `monky-deployd.timer` → the **one-time bootstrap deploy grant** from a vault var (0600, `no_log`) → `monky-deployd.timer`
@@ -14,7 +16,8 @@ template → optional openbao-ca PEM → optional `monky-deployd-proxy.service`
| var | note | | var | note |
|---|---| |---|---|
| `monky_deployd_version` | pinned release, e.g. `0.1.0` | | `monky_deployd_version` | pinned release, e.g. `0.1.2` |
| `monky_deployd_download_token` | **vaulted**: GitLab deploy token, scope `read_package_registry` only (revocable) — the registry is private; seeded in OpenBao at `monky/monky-tenancy/deployd-download` key `token` (path/key are the operator's choice). Empty = no header (only works with the Gitea `base_url`) |
| `monky_deployd_env_id` / `_site` / `_transport` | per host (`env-dev-06`, `cbs`, `sdk`) | | `monky_deployd_env_id` / `_site` / `_transport` | per host (`env-dev-06`, `cbs`, `sdk`) |
| `monky_deployd_bootstrap_grant` | tenancy-minted deploy grant (1 h) — `ansible-vault` or a lookup at play time; empty keeps the existing token | | `monky_deployd_bootstrap_grant` | tenancy-minted deploy grant (1 h) — `ansible-vault` or a lookup at play time; empty keeps the existing token |
| `monky_deployd_bao_ca_pem` | the `openbao-ca` certificate (PEM) | | `monky_deployd_bao_ca_pem` | the `openbao-ca` certificate (PEM) |
@@ -30,6 +33,7 @@ template → optional openbao-ca PEM → optional `monky-deployd-proxy.service`
- role: monky_deployd - role: monky_deployd
vars: vars:
monky_deployd_bootstrap_grant: "{{ lookup('pipe', 'tenancy-mint-grant ' ~ inventory_hostname) }}" monky_deployd_bootstrap_grant: "{{ lookup('pipe', 'tenancy-mint-grant ' ~ inventory_hostname) }}"
monky_deployd_download_token: "{{ vault_monky_deployd_download_token }}" # ansible-vault / OpenBao lookup
``` ```
Rollout order (plan §E): pilot env-qa-02 → env-dev-06..09 → env-dev-01 last (after its live key moves into Bao). Rollout order (plan §E): pilot env-qa-02 → env-dev-06..09 → env-dev-01 last (after its live key moves into Bao).
+16 -3
View File
@@ -1,11 +1,24 @@
--- ---
# monky_deployd — install and configure the Monky backend pull agent (MONKY-ADR-0028 §D). # monky_deployd — install and configure the Monky backend pull agent (MONKY-ADR-0028 §D).
# Copy this role into osg1-07 (roles/monky_deployd) and roll to env-dev-06..09 after the pilot. # Copy this role into osg1-07 (roles/monky_deployd) and roll to env-dev-06..09 after the pilot.
monky_deployd_version: "0.1.0" monky_deployd_version: "0.1.2"
monky_deployd_base_url: "https://gitea.cbs.tikali.net/mdella/monky-deployd"
monky_deployd_deb: "monky-deployd_{{ monky_deployd_version }}_amd64.deb" monky_deployd_deb: "monky-deployd_{{ monky_deployd_version }}_amd64.deb"
monky_deployd_deb_url: "{{ monky_deployd_base_url }}/releases/download/v{{ monky_deployd_version }}/{{ monky_deployd_deb }}" # PRIMARY download = the GitLab project's generic package registry on scm.tikali.ai. Inside the estate
# gitea.cbs.tikali.net is split-horizon to jump1's RED EIP (10.10.0.175, no HTTP ingress), so backend
# boxes cannot fetch from the Gitea mirror (cbs/iac#102). The project is PRIVATE (its parent groups are
# private), so the registry needs a read-only GitLab deploy token (scope read_package_registry only,
# revocable) sent as the `DEPLOY-TOKEN` header — `monky_deployd_download_token`, a VAULTED var
# (ansible-vault or an OpenBao lookup: KV `monky/monky-tenancy/deployd-download`, key `token`).
# Off-estate alternative (Gitea release, no token):
# monky_deployd_base_url: "https://gitea.cbs.tikali.net/mdella/monky-deployd"
# monky_deployd_deb_url: "{{ monky_deployd_base_url }}/releases/download/v{{ monky_deployd_version }}/{{ monky_deployd_deb }}"
# monky_deployd_download_token: ""
monky_deployd_base_url: "https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd"
monky_deployd_deb_url: "{{ monky_deployd_base_url }}/{{ monky_deployd_version }}/{{ monky_deployd_deb }}"
monky_deployd_deb_sha256_url: "{{ monky_deployd_deb_url }}.sha256" monky_deployd_deb_sha256_url: "{{ monky_deployd_deb_url }}.sha256"
monky_deployd_download_token: ""
# the header the download tasks send (empty map when no token)
monky_deployd_download_headers: "{{ {'DEPLOY-TOKEN': monky_deployd_download_token} if monky_deployd_download_token | length > 0 else {} }}"
# per host (inventory / host_vars) # per host (inventory / host_vars)
monky_deployd_env_id: "{{ inventory_hostname }}" # env-dev-06 ... monky_deployd_env_id: "{{ inventory_hostname }}" # env-dev-06 ...
+7 -1
View File
@@ -22,20 +22,26 @@
changed_when: false changed_when: false
failed_when: false failed_when: false
- name: monky_deployd | download .deb + sha256 from the Gitea release # The registry is PRIVATE: both fetches send `DEPLOY-TOKEN: {{ monky_deployd_download_token }}`
# (read_package_registry only) and run no_log so the header never reaches the play output.
- name: monky_deployd | download .deb + sha256 from the scm.tikali.ai package registry
when: monky_deployd_installed.stdout != monky_deployd_version when: monky_deployd_installed.stdout != monky_deployd_version
block: block:
- name: monky_deployd | fetch sha256 - name: monky_deployd | fetch sha256
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ monky_deployd_deb_sha256_url }}" url: "{{ monky_deployd_deb_sha256_url }}"
headers: "{{ monky_deployd_download_headers }}"
return_content: true return_content: true
register: monky_deployd_sha register: monky_deployd_sha
no_log: true
- name: monky_deployd | fetch .deb (checksum verified) - name: monky_deployd | fetch .deb (checksum verified)
ansible.builtin.get_url: ansible.builtin.get_url:
url: "{{ monky_deployd_deb_url }}" url: "{{ monky_deployd_deb_url }}"
headers: "{{ monky_deployd_download_headers }}"
dest: "/var/cache/apt/archives/{{ monky_deployd_deb }}" dest: "/var/cache/apt/archives/{{ monky_deployd_deb }}"
checksum: "sha256:{{ monky_deployd_sha.content.split()[0] }}" checksum: "sha256:{{ monky_deployd_sha.content.split()[0] }}"
mode: "0644" mode: "0644"
no_log: true
- name: monky_deployd | install .deb - name: monky_deployd | install .deb
ansible.builtin.apt: ansible.builtin.apt:
deb: "/var/cache/apt/archives/{{ monky_deployd_deb }}" deb: "/var/cache/apt/archives/{{ monky_deployd_deb }}"
+1 -1
View File
@@ -1,4 +1,4 @@
# /etc/monky-deployd/config.yaml — monky-deployd v0.1.0 (MONKY-ADR-0028 §D) # /etc/monky-deployd/config.yaml — monky-deployd v0.1.2 (MONKY-ADR-0028 §D)
# Written by packaging/install.sh (or the ansible role monky_deployd). YAML *subset*: maps, scalars, # Written by packaging/install.sh (or the ansible role monky_deployd). YAML *subset*: maps, scalars,
# simple lists, comments. Keys not listed here are a config error. # simple lists, comments. Keys not listed here are a config error.
+27 -3
View File
@@ -86,6 +86,30 @@ the picker, nothing is retired automatically.
`docker image prune -f` after a successful apply. The state dir and the token survive `apt remove`; `docker image prune -f` after a successful apply. The state dir and the token survive `apt remove`;
`apt purge` deletes them. `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:
```sh
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 publishes to both (`release`, `release:gitea`).
## Publishing a release to Gitea by hand ## Publishing a release to Gitea by hand
When `release:gitea` is manual (no `GITEA_TOKEN` in CI) — from jump1 with the Gitea API token: When `release:gitea` is manual (no `GITEA_TOKEN` in CI) — from jump1 with the Gitea API token:
@@ -100,10 +124,10 @@ for f in monky-deployd_0.1.0_amd64.deb monky-deployd_0.1.0_amd64.deb.sha256 inst
``` ```
The assets are then at `https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v0.1.0/<file>`, The assets are then at `https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v0.1.0/<file>`,
which is what `install.sh` fetches (GitLab artifacts from the tag pipeline's `package` job). which is what `install.sh --source gitea` fetches (GitLab artifacts from the tag pipeline's `package` job).
## Ansible (osg1-07) ## Ansible (osg1-07)
`ansible/roles/monky_deployd/` is the role skeleton to copy into osg1-07: `.deb` from the Gitea `ansible/roles/monky_deployd/` is the role skeleton to copy into osg1-07: `.deb` from the
release (sha256-verified), config template, bootstrap grant from a vault var, ACL on the identity, 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. timer, one tick. Rolled to env-dev-06..09 after the env-qa-02 pilot; env-dev-01 last.
+1 -1
View File
@@ -4,4 +4,4 @@ Dials monky-tenancy over the mesh with the box's host identity, fetches the rend
leases a deploy grant, logs in to OpenBao, reads its own secrets, runs `docker compose`, leases a deploy grant, logs in to OpenBao, reads its own secrets, runs `docker compose`,
reports. Stdlib only; the optional `openziti` SDK is the `sdk` transport.""" reports. Stdlib only; the optional `openziti` SDK is the `sdk` transport."""
__version__ = "0.1.0" __version__ = "0.1.2"
+57 -17
View File
@@ -1,17 +1,26 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# monky-deployd installer — Ubuntu 26.04 (verified target). # monky-deployd installer — Ubuntu 26.04 (verified target).
# #
# curl -fsSL https://gitea.cbs.tikali.net/mdella/monky-deployd/raw/branch/main/packaging/install.sh \ # T=<read-only GitLab deploy token, scope read_package_registry> # from the install kit / OpenBao
# | sudo bash -s -- --env env-qa-02 --site cbs [--transport sdk|proxy|system] [--version 0.1.0] \ # curl -sSf -H "DEPLOY-TOKEN: $T" \
# [--enrol-jwt /path/monky-host.env-qa-02.jwt] [--laptop] < bootstrap.jwt # https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.2/install.sh \
# | sudo bash -s -- --env env-qa-02 --site cbs --token "$T" --bootstrap-file bootstrap.jwt \
# [--transport sdk|proxy|system] [--version 0.1.2] [--enrol-jwt /path/monky-host.env-qa-02.jwt] \
# [--laptop] [--source gitlab|gitea]
# #
# stdin (or --bootstrap-file): the ONE-TIME bootstrap deploy grant from the install kit # --token / MONKY_DEPLOYD_TOKEN: the GitLab project is PRIVATE (its parent groups are private, so it
# (GET /v1/backends/{id}/agent/install). The enrolment JWT is read from --enrol-jwt or # cannot be made public); every download from the generic package registry — this script included —
# /etc/monky-deployd/enrol.jwt and is only needed when the host identity is not enrolled yet. # sends `DEPLOY-TOKEN: <token>`. The token is read-only (read_package_registry), revocable, and is
# never printed by this script. Not needed with --source gitea.
# --bootstrap-file (or stdin when the script runs from a FILE): the ONE-TIME bootstrap deploy grant
# from the install kit (GET /v1/backends/{id}/agent/install). The enrolment JWT is read from
# --enrol-jwt or /etc/monky-deployd/enrol.jwt and is only needed when the host identity is not
# enrolled yet.
# #
# What it does (idempotent): # What it does (idempotent):
# 1. apt: ziti-edge-tunnel (OpenZiti `jammy` suite) if absent, docker-compose-plugin, acl # 1. apt: ziti-edge-tunnel (OpenZiti `jammy` suite) if absent, docker-compose-plugin, acl
# 2. downloads the pinned monky-deployd_<ver>_amd64.deb + .sha256 from the Gitea release, verifies, installs # 2. downloads the pinned monky-deployd_<ver>_amd64.deb + .sha256 from the scm.tikali.ai package registry
# (DEPLOY-TOKEN header; --source gitea: the Gitea release, off-estate), verifies, installs
# 3. enrols /opt/openziti/etc/identities/monky-host.<env>.json if absent (ziti-edge-tunnel enroll), # 3. enrols /opt/openziti/etc/identities/monky-host.<env>.json if absent (ziti-edge-tunnel enroll),
# chown ziti:ziti 0600, switches ziti-edge-tunnel.service to `run-host` via a drop-in # chown ziti:ziti 0600, switches ziti-edge-tunnel.service to `run-host` via a drop-in
# 4. writes /etc/monky-deployd/config.yaml, ACLs so user monky-deployd can read the identity, # 4. writes /etc/monky-deployd/config.yaml, ACLs so user monky-deployd can read the identity,
@@ -20,14 +29,25 @@
set -euo pipefail set -euo pipefail
umask 077 umask 077
DEFAULT_VERSION="0.1.0" DEFAULT_VERSION="0.1.2"
BASE_URL="${MONKY_DEPLOYD_BASE_URL:-https://gitea.cbs.tikali.net/mdella/monky-deployd}" # Download source. PRIMARY is the GitLab project's generic package registry on scm.tikali.ai: 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 reach the Gitea mirror (cbs/iac#102); scm.tikali.ai they can. The
# project is PRIVATE (parent groups are private), so the registry needs the read-only deploy token
# (--token / MONKY_DEPLOYD_TOKEN, sent as the DEPLOY-TOKEN header). `--source gitea` keeps the Gitea
# release as the off-estate alternative (no token). --base-url / MONKY_DEPLOYD_BASE_URL override the
# base for whichever layout is selected.
GITLAB_BASE_URL="https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd"
GITEA_BASE_URL="https://gitea.cbs.tikali.net/mdella/monky-deployd"
SOURCE="${MONKY_DEPLOYD_SOURCE:-gitlab}"
BASE_URL="${MONKY_DEPLOYD_BASE_URL:-}"
TOKEN="${MONKY_DEPLOYD_TOKEN:-}"
OPENZITI_SUITE="${OPENZITI_SUITE:-jammy}" OPENZITI_SUITE="${OPENZITI_SUITE:-jammy}"
IDENTITY_DIR="/opt/openziti/etc/identities" IDENTITY_DIR="/opt/openziti/etc/identities"
ETC="/etc/monky-deployd" ETC="/etc/monky-deployd"
ENV_ID="" SITE="" TRANSPORT="sdk" VERSION="$DEFAULT_VERSION" ENROL_JWT="" BOOTSTRAP_FILE="" NO_RUN="" FORCE_CONFIG="" BAO_CA="" LAPTOP="false" ENV_ID="" SITE="" TRANSPORT="sdk" VERSION="$DEFAULT_VERSION" ENROL_JWT="" BOOTSTRAP_FILE="" NO_RUN="" FORCE_CONFIG="" BAO_CA="" LAPTOP="false"
usage() { sed -n '2,20p' "$0"; exit "${1:-0}"; } usage() { if [ -f "$0" ]; then sed -n '2,28p' "$0"; else echo "monky-deployd install.sh — see README.md (Install)"; fi; exit "${1:-0}"; }
die() { echo "install.sh: $*" >&2; exit 1; } die() { echo "install.sh: $*" >&2; exit 1; }
log() { echo "==> $*"; } log() { echo "==> $*"; }
@@ -41,6 +61,8 @@ while [ $# -gt 0 ]; do
--bootstrap-file) BOOTSTRAP_FILE="$2"; shift 2 ;; --bootstrap-file) BOOTSTRAP_FILE="$2"; shift 2 ;;
--bao-ca) BAO_CA="$2"; shift 2 ;; --bao-ca) BAO_CA="$2"; shift 2 ;;
--base-url) BASE_URL="$2"; shift 2 ;; --base-url) BASE_URL="$2"; shift 2 ;;
--source) SOURCE="$2"; shift 2 ;;
--token) TOKEN="$2"; shift 2 ;;
--laptop) LAPTOP="true"; shift ;; --laptop) LAPTOP="true"; shift ;;
--no-run) NO_RUN=1; shift ;; --no-run) NO_RUN=1; shift ;;
--force-config) FORCE_CONFIG=1; shift ;; --force-config) FORCE_CONFIG=1; shift ;;
@@ -56,6 +78,10 @@ done
SITE="${SITE,,}" SITE="${SITE,,}"
[[ "$SITE" =~ ^(cbs|pdx)$ ]] || die "site must be cbs|pdx" [[ "$SITE" =~ ^(cbs|pdx)$ ]] || die "site must be cbs|pdx"
[[ "$TRANSPORT" =~ ^(sdk|proxy|system)$ ]] || die "transport must be sdk|proxy|system" [[ "$TRANSPORT" =~ ^(sdk|proxy|system)$ ]] || die "transport must be sdk|proxy|system"
[[ "$SOURCE" =~ ^(gitlab|gitea)$ ]] || die "source must be gitlab|gitea"
# never let the token leak through xtrace / the environment of children
{ set +x; } 2>/dev/null
export -n MONKY_DEPLOYD_TOKEN 2>/dev/null || true
IDENTITY="$IDENTITY_DIR/monky-host.$ENV_ID.json" IDENTITY="$IDENTITY_DIR/monky-host.$ENV_ID.json"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
@@ -98,10 +124,23 @@ if [ "$installed" = "$VERSION" ]; then
else else
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
deb="monky-deployd_${VERSION}_amd64.deb" deb="monky-deployd_${VERSION}_amd64.deb"
url="$BASE_URL/releases/download/v${VERSION}" # the deploy token travels in a 0600 curl config file (-K), never on the command line, so neither
log "downloading $deb from $url" # `ps` nor an xtrace shows it; the file dies with $tmp
curl -fsSL -o "$tmp/$deb" "$url/$deb" curlrc="$tmp/curlrc"; : > "$curlrc"; chmod 0600 "$curlrc"
curl -fsSL -o "$tmp/$deb.sha256" "$url/$deb.sha256" if [ "$SOURCE" = gitlab ]; then
url="${BASE_URL:-$GITLAB_BASE_URL}/${VERSION}"
if [ -n "$TOKEN" ]; then
printf 'header = "DEPLOY-TOKEN: %s"\n' "$TOKEN" > "$curlrc"
else
echo "WARNING: no --token/MONKY_DEPLOYD_TOKEN: the scm.tikali.ai project is private, the download will 401 (use --source gitea off-estate)" >&2
fi
else
url="${BASE_URL:-$GITEA_BASE_URL}/releases/download/v${VERSION}"
fi
log "downloading $deb from $url${TOKEN:+ (DEPLOY-TOKEN)}"
curl -fsSL -K "$curlrc" -o "$tmp/$deb" "$url/$deb" || die "download of $deb failed (private registry: is the deploy token set and valid?)"
curl -fsSL -K "$curlrc" -o "$tmp/$deb.sha256" "$url/$deb.sha256" || die "download of $deb.sha256 failed"
rm -f "$curlrc"
(cd "$tmp" && sha256sum -c "$deb.sha256") || die "sha256 mismatch on $deb" (cd "$tmp" && sha256sum -c "$deb.sha256") || die "sha256 mismatch on $deb"
apt_update_once || true apt_update_once || true
apt-get install -y -qq "$tmp/$deb" apt-get install -y -qq "$tmp/$deb"
@@ -179,10 +218,11 @@ if [ "$TRANSPORT" = "proxy" ]; then
printf 'ZITI_IDENTITY=%s\n' "$IDENTITY" > "$ETC/proxy.env"; chmod 0644 "$ETC/proxy.env" printf 'ZITI_IDENTITY=%s\n' "$IDENTITY" > "$ETC/proxy.env"; chmod 0644 "$ETC/proxy.env"
systemctl enable --now monky-deployd-proxy.service systemctl enable --now monky-deployd-proxy.service
fi fi
# bootstrap grant: stdin (the kit pipes it) or --bootstrap-file; 0600, owned by the agent so it can consume it # bootstrap grant: --bootstrap-file, or stdin — but ONLY when this script runs from a file: with
# `curl … | bash -s --` stdin IS the script, so reading it here would swallow the rest of it
grant="" grant=""
if [ -n "$BOOTSTRAP_FILE" ]; then grant="$(tr -d '[:space:]' < "$BOOTSTRAP_FILE")" if [ -n "$BOOTSTRAP_FILE" ]; then grant="$(tr -d '[:space:]' < "$BOOTSTRAP_FILE")"
elif [ ! -t 0 ]; then grant="$(tr -d '[:space:]' </dev/stdin || true)"; fi elif [ ! -t 0 ] && [ -f "$0" ]; then grant="$(tr -d '[:space:]' </dev/stdin || true)"; fi
if [ -n "$grant" ]; then if [ -n "$grant" ]; then
printf '%s\n' "$grant" > "$ETC/bootstrap.jwt" printf '%s\n' "$grant" > "$ETC/bootstrap.jwt"
chown monky-deployd:monky-deployd "$ETC/bootstrap.jwt"; chmod 0600 "$ETC/bootstrap.jwt" chown monky-deployd:monky-deployd "$ETC/bootstrap.jwt"; chmod 0600 "$ETC/bootstrap.jwt"
@@ -192,7 +232,7 @@ elif [ -s /var/lib/monky-deployd/bao.token ]; then
else else
echo "WARNING: no bootstrap grant on stdin and no bao.token: the agent cannot check in until you provide one" >&2 echo "WARNING: no bootstrap grant on stdin and no bao.token: the agent cannot check in until you provide one" >&2
fi fi
unset grant unset grant TOKEN
# --- 5. timer + first tick ---------------------------------------------------------------------------------- # --- 5. timer + first tick ----------------------------------------------------------------------------------
systemctl daemon-reload systemctl daemon-reload
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "monky-deployd" name = "monky-deployd"
version = "0.1.0" version = "0.1.2"
description = "Monky backend pull agent: checkin -> bundle -> lease -> OpenBao -> docker compose -> report, over the ziti mesh (MONKY-ADR-0028)" description = "Monky backend pull agent: checkin -> bundle -> lease -> OpenBao -> docker compose -> report, over the ziti mesh (MONKY-ADR-0028)"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+2 -1
View File
@@ -7,6 +7,7 @@ import os
import time import time
from pathlib import Path from pathlib import Path
from monky_deployd import __version__
from monky_deployd import state as statemod from monky_deployd import state as statemod
from monky_deployd.agent import EX_ENV_MISMATCH, EX_FAIL, EX_OK, EX_TEMPFAIL, Agent from monky_deployd.agent import EX_ENV_MISMATCH, EX_FAIL, EX_OK, EX_TEMPFAIL, Agent
from monky_deployd.bundle import bundle_sha from monky_deployd.bundle import bundle_sha
@@ -24,7 +25,7 @@ def test_first_tick_bootstraps_applies_and_reports(bootstrapped, tenancy, bao, f
assert not Path(cfg.bootstrap_path).exists() assert not Path(cfg.bootstrap_path).exists()
assert cfg.token_path.exists() and (cfg.token_path.stat().st_mode & 0o777) == 0o600 assert cfg.token_path.exists() and (cfg.token_path.stat().st_mode & 0o777) == 0o600
# protocol: checkin -> bundle -> lease -> report applied # protocol: checkin -> bundle -> lease -> report applied
assert tenancy.checkins[0]["env_id"] == ENV and tenancy.checkins[0]["agent_version"] == "0.1.0" assert tenancy.checkins[0]["env_id"] == ENV and tenancy.checkins[0]["agent_version"] == __version__
assert tenancy.checkins[0]["host"]["docker"] == "28.3.0" assert tenancy.checkins[0]["host"]["docker"] == "28.3.0"
assert len(tenancy.leases) == 1 and tenancy.leases[0]["reason"] == "apply" assert len(tenancy.leases) == 1 and tenancy.leases[0]["reason"] == "apply"
assert [r["result"] for r in tenancy.reports] == ["applied"] assert [r["result"] for r in tenancy.reports] == ["applied"]
+2 -2
View File
@@ -1,7 +1,7 @@
import json import json
from pathlib import Path from pathlib import Path
from monky_deployd import cli from monky_deployd import __version__, cli
from monky_deployd.agent import EX_OK from monky_deployd.agent import EX_OK
@@ -49,7 +49,7 @@ def test_bootstrap_command(bootstrapped, bao, capsys):
def test_version_and_bad_config(capsys, tmp_path): def test_version_and_bad_config(capsys, tmp_path):
assert cli.main(["version"]) == 0 assert cli.main(["version"]) == 0
assert capsys.readouterr().out.strip() == "0.1.0" assert capsys.readouterr().out.strip() == __version__
bad = tmp_path / "c.yaml" bad = tmp_path / "c.yaml"
bad.write_text("env_id: nope\nsite: cbs\n") bad.write_text("env_id: nope\nsite: cbs\n")
assert cli.main(["-c", str(bad), "status"]) == 78 assert cli.main(["-c", str(bad), "status"]) == 78