From 31586c305854ad1ae2aad9f064df87431d31472f Mon Sep 17 00:00:00 2001 From: Marcos Della Date: Mon, 7 Sep 2026 06:05:21 +0000 Subject: [PATCH 1/2] fix: tenancy.port is the intercept port (443); clear sdk dial error; 0.1.5 install.sh wrote tenancy.port 8081 (the in-pod port) but the monky.tenancy.deploy intercept is 443, so the SDK found no intercept and the check-in failed with 'service not available' followed by the SDK's bare TypeError. Also wrap non-OSError SDK failures in a TransportError that names the host:port and what to check. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1 --- CHANGELOG.md | 8 ++++++++ README.md | 4 ++-- ansible/roles/monky_deployd/README.md | 2 +- ansible/roles/monky_deployd/defaults/main.yml | 2 +- config.example.yaml | 4 ++-- monky_deployd/__init__.py | 2 +- monky_deployd/transport.py | 8 ++++++++ packaging/install.sh | 8 ++++---- pyproject.toml | 2 +- 9 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997400a..b70a5b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog +## 0.1.5 — 2026-09-07 + +- **config: `tenancy.port` is 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 bare `TypeError` from the SDK's + fallback). `install.sh` now writes 443; `config.example.yaml` updated. +- **transport sdk: clear error instead of a TypeError** when an address has no intercept or the identity + has no dial policy for the service (`TransportError` names the host:port and what to check). + ## 0.1.4 — 2026-09-07 - **install.sh: `ziti-edge-tunnel.service` failed to start after enrolment** (`203/EXEC`: the package's diff --git a/README.md b/README.md index d32746b..669e1eb 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ you the enrolment JWT, a one-time **bootstrap deploy grant** and the read-only * ```sh T= # 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 \ +curl -sSf -H "DEPLOY-TOKEN: $T" https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.5/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] +# [--transport sdk|proxy|system] [--version 0.1.5] [--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 diff --git a/ansible/roles/monky_deployd/README.md b/ansible/roles/monky_deployd/README.md index 5530d93..6615ff5 100644 --- a/ansible/roles/monky_deployd/README.md +++ b/ansible/roles/monky_deployd/README.md @@ -16,7 +16,7 @@ template → optional openbao-ca PEM → optional `monky-deployd-proxy.service` | var | note | |---|---| -| `monky_deployd_version` | pinned release, e.g. `0.1.4` | +| `monky_deployd_version` | pinned release, e.g. `0.1.5` | | `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 | diff --git a/ansible/roles/monky_deployd/defaults/main.yml b/ansible/roles/monky_deployd/defaults/main.yml index 62de746..951a47a 100644 --- a/ansible/roles/monky_deployd/defaults/main.yml +++ b/ansible/roles/monky_deployd/defaults/main.yml @@ -1,7 +1,7 @@ --- # 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.4" +monky_deployd_version: "0.1.5" monky_deployd_deb: "monky-deployd_{{ monky_deployd_version }}_amd64.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 diff --git a/config.example.yaml b/config.example.yaml index 851b07a..3aac077 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,4 +1,4 @@ -# /etc/monky-deployd/config.yaml — monky-deployd v0.1.4 (MONKY-ADR-0028 §D) +# /etc/monky-deployd/config.yaml — monky-deployd v0.1.5 (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. @@ -10,7 +10,7 @@ identity: /opt/openziti/etc/identities/monky-host.env-qa-02.json # the box's h tenancy: service: monky.tenancy.deploy # ziti service bound by the tenancy sidecar -> 127.0.0.1:8081 (agent entrypoint) host: monky.tenancy.deploy # intercept host (sdk/system); defaults to `service` - port: 8081 + port: 443 # intercept port of monky.tenancy.deploy (in-pod 8081) scheme: http # plain HTTP inside the mesh; the mesh is the transport security proxy_addr: 127.0.0.1:18443 # transport: proxy timeout_s: 30 diff --git a/monky_deployd/__init__.py b/monky_deployd/__init__.py index 2e0e37a..38981a2 100644 --- a/monky_deployd/__init__.py +++ b/monky_deployd/__init__.py @@ -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.4" +__version__ = "0.1.5" diff --git a/monky_deployd/transport.py b/monky_deployd/transport.py index bc08151..19ac946 100644 --- a/monky_deployd/transport.py +++ b/monky_deployd/transport.py @@ -104,6 +104,14 @@ class SdkTransport(Transport): return socket.create_connection((host, port), timeout=timeout) except OSError as exc: raise TransportError(f"transport sdk: dial {host}:{port} failed: {exc}") from exc + except Exception as exc: # noqa: BLE001 - the SDK raises bare Exception((code, msg)) and TypeError + # openziti-sdk-py: an address with NO matching intercept falls through to + # PySocket.connect(tuple) → TypeError; a matching intercept the identity may not dial + # raises Exception((-18, 'service not available')) — env-qa-02 pilot, 2026-09-07. + raise TransportError( + f"transport sdk: dial {host}:{port} failed: {exc} — no intercept for that host:port, or this " + "identity has no dial policy for the service (check the intercept port and the identity's attrs)" + ) from exc def describe(self) -> str: return f"sdk(identity={self.identity_path})" diff --git a/packaging/install.sh b/packaging/install.sh index bf2bdc4..565b064 100755 --- a/packaging/install.sh +++ b/packaging/install.sh @@ -3,9 +3,9 @@ # # T= # 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 \ +# https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.5/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] \ +# [--transport sdk|proxy|system] [--version 0.1.5] [--enrol-jwt /path/monky-host.env-qa-02.jwt] \ # [--laptop] [--source gitlab|gitea] [--docker-data-root /home/docker-data] # # --token / MONKY_DEPLOYD_TOKEN: the GitLab project is PRIVATE (its parent groups are private, so it @@ -29,7 +29,7 @@ set -euo pipefail umask 077 -DEFAULT_VERSION="0.1.4" +DEFAULT_VERSION="0.1.5" # 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 @@ -233,7 +233,7 @@ identity: $IDENTITY tenancy: service: monky.tenancy.deploy host: monky.tenancy.deploy - port: 8081 + port: 443 # the service's INTERCEPT port (host.v1 forwards to 8081 inside the pod); plain HTTP inside the mesh scheme: http proxy_addr: 127.0.0.1:18443 bao: diff --git a/pyproject.toml b/pyproject.toml index 8309ef7..f75f515 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "monky-deployd" -version = "0.1.4" +version = "0.1.5" 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" From fcf2dcb1eb0f397ca4601f003723b3702303eb09 Mon Sep 17 00:00:00 2001 From: Marcos Della Date: Mon, 7 Sep 2026 06:09:08 +0000 Subject: [PATCH 2/2] fix(bundle): ${VAR} inside comment lines is not a reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The renderer's .env.template header literally says "substitutes every ${VAR}", which the refusal check counted as an unresolved variable (ENV_INCOMPLETE: unresolved: VAR) — env-qa-02's first bundle was refused. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1 --- CHANGELOG.md | 4 ++++ monky_deployd/bundle.py | 10 ++++++++-- tests/test_bundle.py | 13 +++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b70a5b5..935aa66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## 0.1.5 — 2026-09-07 +- **`${VAR}` inside comment lines is not a reference.** The renderer's `.env.template` header 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.port` is 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 bare `TypeError` from the SDK's fallback). `install.sh` now writes 443; `config.example.yaml` updated. diff --git a/monky_deployd/bundle.py b/monky_deployd/bundle.py index 45000b5..ce8d08b 100644 --- a/monky_deployd/bundle.py +++ b/monky_deployd/bundle.py @@ -144,12 +144,18 @@ def parse(data: bytes, *, max_bytes: int = 4 * 1024 * 1024) -> Bundle: # --- refusal checks (pure; names only, never values) ----------------------------------------- +def _code_lines(text: str) -> str: + """Drop comment lines: a `# … ${VAR} …` remark in .env.template (the renderer writes one) + is not a reference. Compose/dotenv comments start with `#` after optional whitespace.""" + return "\n".join(ln for ln in text.splitlines() if not ln.lstrip().startswith("#")) + + def referenced_vars(text: str) -> set[str]: - return {m.group(1) for m in _VAR_RE.finditer(text)} + return {m.group(1) for m in _VAR_RE.finditer(_code_lines(text))} def defaulted_vars(text: str) -> set[str]: - return {m.group(1) for m in _VAR_DEFAULTED_RE.finditer(text)} + return {m.group(1) for m in _VAR_DEFAULTED_RE.finditer(_code_lines(text))} def unresolved_vars(bundle: Bundle, provided: set[str]) -> list[str]: diff --git a/tests/test_bundle.py b/tests/test_bundle.py index 6591c3c..e4871e8 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -79,3 +79,16 @@ def test_disk_need_bytes_spellings(): assert b.parse(tar_bytes(make_files(meta={"agent": {"disk_need_bytes": 5}}))).disk_need_bytes == 5 assert b.parse(tar_bytes(make_files(meta={"disk": {"need_bytes": 7}}))).disk_need_bytes == 7 assert b.parse(tar_bytes(make_files())).disk_need_bytes == 0 + + +def test_placeholders_in_comment_lines_are_not_references(): + """The renderer's .env.template header says '... substitutes every ${VAR} ...' — that must not + become an unresolved 'VAR' (env-qa-02 pilot: ENV_INCOMPLETE: unresolved: VAR).""" + from monky_deployd.bundle import defaulted_vars, referenced_vars + + text = ( + "# The on-box agent substitutes every ${VAR} from OpenBao per secrets.manifest.json.\n" + " # ${ALSO_COMMENT}\nGEMINI_API_KEY=${GEMINI_API_KEY}\nPG=${PGPASSWORD:-x}\n" + ) + assert referenced_vars(text) == {"GEMINI_API_KEY", "PGPASSWORD"} + assert defaulted_vars(text) == {"PGPASSWORD"}