6 Commits

Author SHA1 Message Date
monky-deployd-merger 41541629ec Merge branch 'fix/tunnel-execstartpre' into 'main'
fix(install.sh): clear ExecStartPre in the run-host drop-in; config/grant before aborting; 0.1.4

See merge request tikali/applications/monky/monky-deployd!6
2026-09-07 06:00:16 +00:00
mdella 744041ca1e fix(install.sh): clear ExecStartPre in the run-host drop-in; don't abort before config/grant land; 0.1.4
On env-qa-02 (ziti-edge-tunnel 1.18.7) the package's ExecStartPre script is
not executable by user ziti (203/EXEC), so the unit never started, and under
set -e `systemctl enable --now` aborted install.sh before config.yaml, ACLs,
the bootstrap grant and the timer were written — a second kit was needed.

- run-host drop-in clears ExecStartPre= (run-host does not auto-enrol JWTs)
- a tunneler start failure is a WARNING until everything else is installed,
  then a hard error at the end (re-run needs no new kit)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
2026-09-07 05:58:57 +00:00
monky-deployd-merger b08e0adf82 Merge branch 'fix/install-version-clobber-docker' into 'main'
fix(install.sh): --version clobbered by os-release; Docker Engine install; 0.1.3

See merge request tikali/applications/monky/monky-deployd!5
2026-09-06 16:20:12 +00:00
mdella 837a7e5bb1 fix(install.sh): --version clobbered by /etc/os-release; install Docker Engine when absent; 0.1.3
The first real kit run (env-qa-02) downloaded
monky-deployd_26.04 LTS (Resolute Raccoon)_amd64.deb: sourcing /etc/os-release
inline overwrote VERSION. Script vars are now DEPLOYD_VERSION and os-release is
read in a subshell.

Per the operator's rule (end users run ONE script), install.sh now installs
Docker Engine when absent — Docker's apt suite for the host codename, falling
back to Ubuntu's docker.io — with --docker-data-root <dir> to place the
data-root before first start. Existing Docker is left untouched.

bash -n + shellcheck -S warning clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
2026-09-06 16:18:47 +00:00
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
12 changed files with 221 additions and 67 deletions
+3 -2
View File
@@ -10,8 +10,9 @@
# 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 public; gitea.cbs.tikali.net is split-horizon inside the estate, cbs/iac#102)
# and the Gitea mirror release (`install.sh --source gitea`, off-estate).
# 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)
include:
+34
View File
@@ -1,6 +1,40 @@
<!-- xlate:verbatim-fences -->
# Changelog
## 0.1.4 — 2026-09-07
- **install.sh: `ziti-edge-tunnel.service` failed to start after enrolment** (`203/EXEC`: the package's
`ExecStartPre` script is not executable by user `ziti` on ziti-edge-tunnel 1.18.x) and, because the
unit was enabled with `--now` under `set -e`, the script aborted BEFORE writing config/ACLs/grant/timer —
a second kit was needed. The run-host drop-in now clears `ExecStartPre=`; 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: `--version` was clobbered by `/etc/os-release`** (it defines `VERSION`), so the kit tried to download
`monky-deployd_26.04 LTS (Resolute Raccoon)_amd64.deb` (env-qa-02 pilot, first real kit run). Script variables are now
`DEPLOYD_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.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
+3 -2
View File
@@ -75,8 +75,9 @@ systemd-analyze verify packaging/systemd/*.service # when available
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`,
publishes the **GitLab generic package registry + release** the installer downloads from (public
project; the Gitea name is split-horizon inside the estate, cbs/iac#102) and — with `GITEA_TOKEN` —
publishes the **GitLab generic package registry + release** the installer downloads from (PRIVATE
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__`.
+30 -11
View File
@@ -18,12 +18,14 @@ Verified on **Ubuntu 26.04**.
## Install (one-liner, from the enrolment kit)
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
curl -fsSL https://scm.tikali.ai/tikali/applications/monky/monky-deployd/-/raw/main/packaging/install.sh \
| sudo bash -s -- --env env-qa-02 --site cbs --enrol-jwt ./monky-host.env-qa-02.jwt < bootstrap.jwt
# [--transport sdk|proxy|system] [--version 0.1.1] [--laptop] [--bao-ca openbao-ca.pem] [--source gitlab|gitea]
T=<deploy token> # read-only GitLab deploy token (read_package_registry); the kit carries it
curl -sSf -H "DEPLOY-TOKEN: $T" https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.4/install.sh \
| 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.4] [--docker-data-root /home/docker-data] [--laptop] [--bao-ca openbao-ca.pem] [--source gitlab|gitea]
```
`install.sh` installs `ziti-edge-tunnel` (OpenZiti `jammy` suite) and `docker-compose-plugin` if
@@ -32,11 +34,21 @@ absent, downloads the pinned `.deb` + `.sha256` from the [scm.tikali.ai generic
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),
stages the bootstrap grant (0600), enables `monky-deployd.timer`, runs one tick and deletes the
JWT. The GitLab project is **public**, so anonymous downloads work from scm.tikali.ai. 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`) keeps the
[Gitea release](https://gitea.cbs.tikali.net/mdella/monky-deployd/releases) as the off-estate alternative.
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
@@ -145,8 +157,8 @@ 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
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
`release` uploads to the GitLab generic package registry + release (**the primary download**, public
project), and `release:gitea` publishes the same assets on the Gitea mirror (the `--source gitea`
`release` uploads to the GitLab generic package registry + release (**the primary download**; the
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`
alternative; automatic when `GITEA_TOKEN` is set, manual otherwise — see `docs/OPERATIONS.md` for
the by-hand recipe). Both locations keep being published.
@@ -155,3 +167,10 @@ the by-hand recipe). Both locations keep being published.
- `docs/PROTOCOL.md`, `docs/OPERATIONS.md`, `CHANGELOG.md`
- monky-tenancy `docs/usage.md` (agent protocol), `app/api/agent.py`, `app/schemas_backends.py`
- monky-deploy (the renderer whose `render_files` produces the bundle)
### Docker Engine (since 0.1.3)
`install.sh` installs Docker Engine when it is absent (Docker's apt suite for the host's Ubuntu codename, falling back to
Ubuntu's `docker.io`), so an end user runs exactly one script. `--docker-data-root <dir>` writes `/etc/docker/daemon.json`
before the daemon first starts (pilot VMs keep the data-root on a dedicated disk, e.g. `/home/docker-data`). A pre-existing
Docker is left untouched. macOS/Windows laptops still need Docker Desktop from the user.
+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
host identity (`roles/ziti_tunneler`, run-host mode). Skeleton for **osg1-07**; copy it there.
What it does: pin + download the `.deb` from the scm.tikali.ai package registry (sha256 verified; `monky_deployd_base_url`/`_deb_url` switch to the Gitea release off-estate) → 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
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`
@@ -14,7 +16,8 @@ template → optional openbao-ca PEM → optional `monky-deployd-proxy.service`
| var | note |
|---|---|
| `monky_deployd_version` | pinned release, e.g. `0.1.1` |
| `monky_deployd_version` | pinned release, e.g. `0.1.4` |
| `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_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) |
@@ -30,6 +33,7 @@ template → optional openbao-ca PEM → optional `monky-deployd-proxy.service`
- role: monky_deployd
vars:
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).
+12 -4
View File
@@ -1,16 +1,24 @@
---
# 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.
monky_deployd_version: "0.1.1"
monky_deployd_version: "0.1.4"
monky_deployd_deb: "monky-deployd_{{ monky_deployd_version }}_amd64.deb"
# PRIMARY download = the public 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). Off-estate alternative (Gitea release):
# 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_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)
monky_deployd_env_id: "{{ inventory_hostname }}" # env-dev-06 ...
@@ -22,20 +22,26 @@
changed_when: false
failed_when: false
# 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
block:
- name: monky_deployd | fetch sha256
ansible.builtin.uri:
url: "{{ monky_deployd_deb_sha256_url }}"
headers: "{{ monky_deployd_download_headers }}"
return_content: true
register: monky_deployd_sha
no_log: true
- name: monky_deployd | fetch .deb (checksum verified)
ansible.builtin.get_url:
url: "{{ monky_deployd_deb_url }}"
headers: "{{ monky_deployd_download_headers }}"
dest: "/var/cache/apt/archives/{{ monky_deployd_deb }}"
checksum: "sha256:{{ monky_deployd_sha.content.split()[0] }}"
mode: "0644"
no_log: true
- name: monky_deployd | install .deb
ansible.builtin.apt:
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.1 (MONKY-ADR-0028 §D)
# /etc/monky-deployd/config.yaml — monky-deployd v0.1.4 (MONKY-ADR-0028 §D)
# 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.
+20 -7
View File
@@ -88,14 +88,27 @@ the picker, nothing is retired automatically.
## Where the installer downloads from
The primary source is the **scm.tikali.ai generic package registry** of this (public) project:
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 one-liner fetches the script from
`https://scm.tikali.ai/tikali/applications/monky/monky-deployd/-/raw/main/packaging/install.sh`.
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`) uses the Gitea release
instead. The tag pipeline publishes to both (`release`, `release:gitea`).
`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
+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`,
reports. Stdlib only; the optional `openziti` SDK is the `sdk` transport."""
__version__ = "0.1.1"
__version__ = "0.1.4"
+104 -36
View File
@@ -1,18 +1,26 @@
#!/usr/bin/env bash
# monky-deployd installer — Ubuntu 26.04 (verified target).
#
# curl -fsSL https://scm.tikali.ai/tikali/applications/monky/monky-deployd/-/raw/main/packaging/install.sh \
# | sudo bash -s -- --env env-qa-02 --site cbs [--transport sdk|proxy|system] [--version 0.1.1] \
# [--enrol-jwt /path/monky-host.env-qa-02.jwt] [--laptop] [--source gitlab|gitea] < bootstrap.jwt
# T=<read-only GitLab deploy token, scope read_package_registry> # from the install kit / OpenBao
# curl -sSf -H "DEPLOY-TOKEN: $T" \
# https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.4/install.sh \
# | sudo bash -s -- --env env-qa-02 --site cbs --token "$T" --bootstrap-file bootstrap.jwt \
# [--transport sdk|proxy|system] [--version 0.1.4] [--enrol-jwt /path/monky-host.env-qa-02.jwt] \
# [--laptop] [--source gitlab|gitea] [--docker-data-root /home/docker-data]
#
# stdin (or --bootstrap-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.
# --token / MONKY_DEPLOYD_TOKEN: the GitLab project is PRIVATE (its parent groups are private, so it
# cannot be made public); every download from the generic package registry — this script included —
# 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):
# 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 scm.tikali.ai package registry
# (--source gitea: the Gitea release, off-estate), verifies, installs
# (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),
# 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,
@@ -21,22 +29,25 @@
set -euo pipefail
umask 077
DEFAULT_VERSION="0.1.1"
# Download source. PRIMARY is the public 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.
# `--source gitea` keeps the Gitea release as the off-estate alternative. --base-url / MONKY_DEPLOYD_BASE_URL
# override the base for whichever layout is selected.
DEFAULT_VERSION="0.1.4"
# 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}"
IDENTITY_DIR="/opt/openziti/etc/identities"
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" DOCKER_DATA_ROOT="" TUNNEL_FAILED="" DEPLOYD_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; }
log() { echo "==> $*"; }
@@ -45,12 +56,14 @@ while [ $# -gt 0 ]; do
--env) ENV_ID="$2"; shift 2 ;;
--site) SITE="$2"; shift 2 ;;
--transport) TRANSPORT="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
--version) DEPLOYD_VERSION="$2"; shift 2 ;;
--docker-data-root) DOCKER_DATA_ROOT="$2"; shift 2 ;;
--enrol-jwt) ENROL_JWT="$2"; shift 2 ;;
--bootstrap-file) BOOTSTRAP_FILE="$2"; shift 2 ;;
--bao-ca) BAO_CA="$2"; shift 2 ;;
--base-url) BASE_URL="$2"; shift 2 ;;
--source) SOURCE="$2"; shift 2 ;;
--token) TOKEN="$2"; shift 2 ;;
--laptop) LAPTOP="true"; shift ;;
--no-run) NO_RUN=1; shift ;;
--force-config) FORCE_CONFIG=1; shift ;;
@@ -67,13 +80,20 @@ SITE="${SITE,,}"
[[ "$SITE" =~ ^(cbs|pdx)$ ]] || die "site must be cbs|pdx"
[[ "$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"
export DEBIAN_FRONTEND=noninteractive
if [ -r /etc/os-release ]; then
. /etc/os-release
if [ "${ID:-}" != "ubuntu" ] || [ "${VERSION_ID:-}" != "26.04" ]; then
echo "WARNING: verified on Ubuntu 26.04; this is ${PRETTY_NAME:-unknown}. Continuing." >&2
# read os-release in a SUBSHELL: it defines VERSION/NAME/ID/... and sourcing it inline clobbered
# this script's own variables (0.1.2 → "26.04 LTS (Resolute Raccoon)" on the env-qa-02 pilot)
OS_ID=$(. /etc/os-release; printf '%s' "${ID:-}")
OS_VERSION_ID=$(. /etc/os-release; printf '%s' "${VERSION_ID:-}")
OS_CODENAME=$(. /etc/os-release; printf '%s' "${VERSION_CODENAME:-${UBUNTU_CODENAME:-}}")
if [ "$OS_ID" != "ubuntu" ] || [ "$OS_VERSION_ID" != "26.04" ]; then
echo "WARNING: verified on Ubuntu 26.04 only (this is ${OS_ID:-?} ${OS_VERSION_ID:-?}); continuing" >&2
fi
fi
@@ -93,7 +113,34 @@ fi
if [ "$TRANSPORT" = "proxy" ] && ! command -v ziti >/dev/null 2>&1; then
need_pkgs+=(openziti) # the `ziti` CLI (ziti tunnel proxy) from the same repo
fi
command -v docker >/dev/null 2>&1 || die "docker is not installed; install Docker Engine first (https://docs.docker.com/engine/install/ubuntu/)"
if ! command -v docker >/dev/null 2>&1; then
# Docker Engine from Docker's apt repo (the suite for this Ubuntu codename); falls back to Ubuntu's
# docker.io if Docker has no suite for the codename yet. --docker-data-root puts the data-root on
# a dedicated disk BEFORE the daemon first starts (pilot VMs mount one at /home/docker-data).
log "docker is not installed — installing Docker Engine"
export DEBIAN_FRONTEND=noninteractive
install -d -m 0755 /etc/apt/keyrings
if curl -fsSL --max-time 20 "https://download.docker.com/linux/ubuntu/dists/${OS_CODENAME:-noble}/Release" -o /dev/null 2>/dev/null; then
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${OS_CODENAME:-noble} stable" > /etc/apt/sources.list.d/docker.list
apt-get update -qq
docker_pkgs="docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin"
else
log "Docker has no apt suite for '${OS_CODENAME:-?}' yet — using Ubuntu's docker.io"
apt-get update -qq
docker_pkgs="docker.io docker-compose-v2"
fi
if [ -n "$DOCKER_DATA_ROOT" ]; then
install -d -m 0710 "$DOCKER_DATA_ROOT"
install -d -m 0755 /etc/docker
[ -f /etc/docker/daemon.json ] || printf '{ "data-root": "%s" }\n' "$DOCKER_DATA_ROOT" > /etc/docker/daemon.json
fi
# shellcheck disable=SC2086
apt-get install -y -qq --no-install-recommends $docker_pkgs
systemctl enable --now docker
log "installed $(docker --version) (data-root $(docker info -f '{{.DockerRootDir}}' 2>/dev/null || echo default))"
fi
docker compose version >/dev/null 2>&1 || need_pkgs+=(docker-compose-plugin)
python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 12) else 1)' 2>/dev/null || die "python3 >= 3.12 is required"
if [ "${#need_pkgs[@]}" -gt 0 ]; then
@@ -104,19 +151,28 @@ fi
# --- 2. the pinned .deb -----------------------------------------------------------------------------
installed="$(dpkg-query -W -f='${Version}' monky-deployd 2>/dev/null || true)"
if [ "$installed" = "$VERSION" ]; then
log "monky-deployd $VERSION already installed"
if [ "$installed" = "$DEPLOYD_VERSION" ]; then
log "monky-deployd $DEPLOYD_VERSION already installed"
else
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
deb="monky-deployd_${VERSION}_amd64.deb"
deb="monky-deployd_${DEPLOYD_VERSION}_amd64.deb"
# the deploy token travels in a 0600 curl config file (-K), never on the command line, so neither
# `ps` nor an xtrace shows it; the file dies with $tmp
curlrc="$tmp/curlrc"; : > "$curlrc"; chmod 0600 "$curlrc"
if [ "$SOURCE" = gitlab ]; then
url="${BASE_URL:-$GITLAB_BASE_URL}/${VERSION}"
url="${BASE_URL:-$GITLAB_BASE_URL}/${DEPLOYD_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}"
url="${BASE_URL:-$GITEA_BASE_URL}/releases/download/v${DEPLOYD_VERSION}"
fi
log "downloading $deb from $url"
curl -fsSL -o "$tmp/$deb" "$url/$deb"
curl -fsSL -o "$tmp/$deb.sha256" "$url/$deb.sha256"
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"
apt_update_once || true
apt-get install -y -qq "$tmp/$deb"
@@ -138,14 +194,21 @@ chmod 0600 "$IDENTITY"
# ziti-edge-tunnel as a HOST (bind side, no tun/DNS) — the agent dials with the SDK or the proxy
install -d /etc/systemd/system/ziti-edge-tunnel.service.d
cat > /etc/systemd/system/ziti-edge-tunnel.service.d/run-host.conf <<'DROPIN'
# monky-deployd: run-host mode (no tun, no DNS); identities from the standard directory
# monky-deployd: run-host mode (no tun, no DNS); identities from the standard directory.
# ExecStartPre is cleared: the package's ziti-edge-tunnel.sh (auto-enrol *.jwt in the identity dir)
# is not executable by user ziti on 1.18.x (203/EXEC, env-qa-02 pilot) and run-host does not need it.
[Service]
ExecStartPre=
ExecStart=
ExecStart=/opt/openziti/bin/ziti-edge-tunnel run-host --identity-dir=/opt/openziti/etc/identities
DROPIN
systemctl daemon-reload
systemctl enable --now ziti-edge-tunnel.service
systemctl restart ziti-edge-tunnel.service || true
systemctl enable ziti-edge-tunnel.service >/dev/null 2>&1 || true
if ! systemctl restart ziti-edge-tunnel.service; then
# do NOT abort here: config, ACLs, grant and timer below must land so a re-run needs no new kit
echo "WARNING: ziti-edge-tunnel.service failed to start — see: journalctl -u ziti-edge-tunnel; continuing" >&2
TUNNEL_FAILED=1
fi
# --- 4. config, ACLs, bootstrap grant -----------------------------------------------------------------
install -d -m 0750 -o root -g monky-deployd "$ETC"
@@ -162,7 +225,7 @@ if [ -s "$ETC/config.yaml" ] && [ -z "$FORCE_CONFIG" ]; then
log "keeping existing $ETC/config.yaml (use --force-config to rewrite)"
else
cat > "$ETC/config.yaml" <<CFG
# written by install.sh $(date -u +%FT%TZ) — monky-deployd $VERSION
# written by install.sh $(date -u +%FT%TZ) — monky-deployd $DEPLOYD_VERSION
env_id: $ENV_ID
site: $SITE
transport: $TRANSPORT
@@ -194,10 +257,11 @@ if [ "$TRANSPORT" = "proxy" ]; then
printf 'ZITI_IDENTITY=%s\n' "$IDENTITY" > "$ETC/proxy.env"; chmod 0644 "$ETC/proxy.env"
systemctl enable --now monky-deployd-proxy.service
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=""
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
printf '%s\n' "$grant" > "$ETC/bootstrap.jwt"
chown monky-deployd:monky-deployd "$ETC/bootstrap.jwt"; chmod 0600 "$ETC/bootstrap.jwt"
@@ -207,7 +271,7 @@ elif [ -s /var/lib/monky-deployd/bao.token ]; then
else
echo "WARNING: no bootstrap grant on stdin and no bao.token: the agent cannot check in until you provide one" >&2
fi
unset grant
unset grant TOKEN
# --- 5. timer + first tick ----------------------------------------------------------------------------------
systemctl daemon-reload
@@ -221,7 +285,7 @@ rm -f "$ETC/enrol.jwt"
cat <<CHECK
monky-deployd $VERSION installed for $ENV_ID ($SITE, transport $TRANSPORT). Verification checklist (Ubuntu 26.04):
monky-deployd $DEPLOYD_VERSION installed for $ENV_ID ($SITE, transport $TRANSPORT). Verification checklist (Ubuntu 26.04):
python3 --version # >= 3.12
docker compose version # compose plugin present
systemctl is-active ziti-edge-tunnel # run-host mode (drop-in run-host.conf)
@@ -235,3 +299,7 @@ monky-deployd $VERSION installed for $ENV_ID ($SITE, transport $TRANSPORT). Veri
CHECK
CHECK_STATUS="$(monky-deployd status 2>&1 || true)"
echo "$CHECK_STATUS" | sed 's/^/ | /'
if [ -n "$TUNNEL_FAILED" ] && ! systemctl is-active --quiet ziti-edge-tunnel.service; then
echo "ERROR: ziti-edge-tunnel.service is not running; the agent cannot reach the mesh until it is. Everything else is installed — fix the unit and 'systemctl restart ziti-edge-tunnel monky-deployd'." >&2
exit 1
fi
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "monky-deployd"
version = "0.1.1"
version = "0.1.4"
description = "Monky backend pull agent: checkin -> bundle -> lease -> OpenBao -> docker compose -> report, over the ziti mesh (MONKY-ADR-0028)"
readme = "README.md"
requires-python = ">=3.12"