mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 05:36:15 +00:00
1c42e913a8
Stdlib-only Python 3.12 agent for docker VMs and laptops: flock → checkin
(bearer = the agent's OpenBao token, bootstrapped from the install kit's
jwt-tenancy deploy grant) → action apply|none|down → bundle (sha256
verified) → refusal checks (unresolved ${VAR} names only, manifest paths
pinned to monky/data/<env>/see/, privileged/host-network, rollback, disk
need×1.5+headroom) → lease → POST /v1/auth/jwt-tenancy/login → KV reads →
.env 0600 → promote → compose pull/up → wait healthy → report; finally
renew-self / re-lease before max TTL, scrub. Exit 0/75/78/1. Redactor log
filter. Transports sdk (openziti) / proxy (ziti tunnel proxy 18443/18200) /
system. Laptop mode.
Packaging: hardened oneshot + 60 s timer + proxy unit, nfpm .deb with
/opt/monky-deployd/venv, install.sh for Ubuntu 26.04 (Gitea release
download, enrol, ACLs, bootstrap from stdin), ansible role skeleton for
osg1-07. CI: lint/test on every change; wheel (openziti on ubuntu:26.04) and
package (nfpm) allow_failure until runner egress is proven; GitLab release +
release:gitea on v* tags. Docs: README, PROTOCOL, OPERATIONS, CHANGELOG,
CLAUDE/AGENTS.
Divergence noted: monky-tenancy main (MR !15) still ships the AppRole lease
and kit; this agent implements the plan's Gate 1 RESULT (login_jwt, no
unwrap) and refuses an AppRole lease loudly (LEASE_SHAPE).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
195 lines
9.5 KiB
YAML
195 lines
9.5 KiB
YAML
# monky-deployd CI/CD (mirrors monky-tenancy's conventions)
|
|
#
|
|
# lint -> test -> build (openziti wheel) -> package (.deb via nfpm) -> release (v* tags) -> docs
|
|
#
|
|
# Every script line is single-quoted (a bare ": " turns the line into a YAML map and silently
|
|
# yields a 0-job pipeline). Jobs that need egress the runner may not have (GitHub for the
|
|
# openziti sdist's ziti-sdk-c fetch, GitHub for the nfpm binary) are allow_failure: true until
|
|
# proven; see README "CI notes".
|
|
#
|
|
# CI/CD variables (project or group level):
|
|
# GITEA_TOKEN — Gitea API token (write:repository) for `release:gitea`; without it the job is manual
|
|
# GITLAB_TRANSLATE_TOKEN — used by the translate-docs component (group level)
|
|
|
|
include:
|
|
# doc-translator via the CI/CD component; PARTIAL PIN @11.3 tracks 11.3.x (no `v`).
|
|
- component: $CI_SERVER_FQDN/tikali/platform/doc-translator/translate-docs@11.3
|
|
inputs:
|
|
docs_stage: docs
|
|
|
|
stages: [lint, test, build, package, release, docs]
|
|
|
|
# One pipeline per change: MR pipeline for merge requests, branch pipeline otherwise, never both.
|
|
workflow:
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG'
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"'
|
|
when: never
|
|
- if: '$CI_COMMIT_BRANCH'
|
|
|
|
variables:
|
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
|
OPENZITI_VERSION: "1.7.1"
|
|
NFPM_VERSION: "2.43.0"
|
|
|
|
cache:
|
|
key: "$CI_JOB_NAME"
|
|
paths: [.cache/pip]
|
|
|
|
.run_rules:
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG'
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH'
|
|
|
|
lint:
|
|
extends: [.run_rules]
|
|
stage: lint
|
|
image: python:3.12-slim
|
|
before_script:
|
|
- 'pip install -q "ruff>=0.16,<0.17"'
|
|
script:
|
|
- 'ruff check .'
|
|
- 'ruff format --check .'
|
|
- 'bash -n packaging/install.sh'
|
|
- 'bash -n packaging/scripts/postinstall.sh packaging/scripts/preremove.sh packaging/scripts/postremove.sh'
|
|
- 'python -c "import ast,sys; [ast.parse(open(f).read()) for f in sys.argv[1:]]" tests/fakebin/docker'
|
|
# the release version must match the tag when there is one
|
|
- 'if [ -n "$CI_COMMIT_TAG" ]; then v=$(python -c "import monky_deployd;print(monky_deployd.__version__)"); [ "v$v" = "$CI_COMMIT_TAG" ] || { echo "tag $CI_COMMIT_TAG != __version__ $v"; exit 1; }; fi'
|
|
|
|
test:
|
|
extends: [.run_rules]
|
|
stage: test
|
|
image: python:3.12-slim
|
|
before_script:
|
|
- 'pip install -q pytest'
|
|
script:
|
|
- 'chmod +x tests/fakebin/docker'
|
|
- 'pytest --junitxml=report.xml'
|
|
artifacts:
|
|
when: always
|
|
reports:
|
|
junit: report.xml
|
|
|
|
# --- the openziti wheel ---------------------------------------------------------------------
|
|
# PyPI ships `openziti` as an sdist whose build fetches ziti-sdk-c (+ prebuilt tlsuv/uv-mbed
|
|
# via cmake FetchContent) from github.com at install time. Building it here on ubuntu:26.04
|
|
# (the target OS; python3 = the target's python3) gives us a wheel to vendor into the venv.
|
|
# NEEDS runner egress to github.com + pypi.org; allow_failure until proven on this runner —
|
|
# without the wheel the .deb still builds (transports proxy/system work; sdk logs a clear error).
|
|
wheel:
|
|
stage: build
|
|
image: ubuntu:26.04
|
|
needs: ["test"]
|
|
allow_failure: true
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG'
|
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
when: manual
|
|
allow_failure: true
|
|
variables:
|
|
DEBIAN_FRONTEND: noninteractive
|
|
script:
|
|
- 'apt-get update -qq && apt-get install -y -qq --no-install-recommends python3 python3-venv python3-dev build-essential cmake ninja-build git pkg-config libssl-dev zlib1g-dev ca-certificates curl >/dev/null'
|
|
- 'python3 -m venv /tmp/wb && /tmp/wb/bin/pip install -q --upgrade pip wheel setuptools'
|
|
- 'mkdir -p vendor'
|
|
- '/tmp/wb/bin/pip wheel --no-deps --no-binary openziti "openziti==${OPENZITI_VERSION}" -w vendor/'
|
|
- 'ls -l vendor/'
|
|
artifacts:
|
|
paths: [vendor/*.whl]
|
|
expire_in: 30 days
|
|
|
|
# --- the .deb --------------------------------------------------------------------------------------
|
|
# venv at its final path (/opt/monky-deployd/venv is where the .deb puts it; venvs are not
|
|
# relocatable) + nfpm. nfpm comes from GitHub releases (egress) with the goreleaser apt repo as
|
|
# fallback; allow_failure until proven.
|
|
package:
|
|
stage: package
|
|
image: ubuntu:26.04
|
|
needs:
|
|
- job: test
|
|
- job: wheel
|
|
optional: true
|
|
allow_failure: true
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG'
|
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
when: manual
|
|
allow_failure: true
|
|
variables:
|
|
DEBIAN_FRONTEND: noninteractive
|
|
script:
|
|
- 'apt-get update -qq && apt-get install -y -qq --no-install-recommends python3 python3-venv python3-pip ca-certificates curl gnupg >/dev/null'
|
|
- 'VERSION=$(python3 -c "import monky_deployd;print(monky_deployd.__version__)"); echo "VERSION=$VERSION" | tee build.env'
|
|
- 'python3 -m venv /opt/monky-deployd/venv'
|
|
- '/opt/monky-deployd/venv/bin/pip install -q --upgrade pip'
|
|
- '/opt/monky-deployd/venv/bin/pip install -q .'
|
|
- 'if ls vendor/*.whl >/dev/null 2>&1; then /opt/monky-deployd/venv/bin/pip install -q vendor/*.whl && /opt/monky-deployd/venv/bin/python -c "import openziti; print(\"openziti\", openziti.__version__ if hasattr(openziti, \"__version__\") else \"ok\")"; else echo "WARNING: no vendored openziti wheel — transport sdk will not work from this build"; fi'
|
|
- '/opt/monky-deployd/venv/bin/python -m monky_deployd version'
|
|
- 'mkdir -p build dist && cp -a /opt/monky-deployd/venv build/venv'
|
|
# nfpm: GitHub release .deb, else the goreleaser apt repo
|
|
- 'curl -fsSL -o /tmp/nfpm.deb "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_amd64.deb" && apt-get install -y -qq /tmp/nfpm.deb >/dev/null || { echo "deb [trusted=yes] https://repo.goreleaser.com/apt/ /" > /etc/apt/sources.list.d/goreleaser.list; apt-get update -qq; apt-get install -y -qq nfpm; }'
|
|
- 'VERSION=$VERSION nfpm package --config packaging/nfpm.yaml --packager deb --target dist/'
|
|
- 'cd dist && for f in *.deb; do sha256sum "$f" > "$f.sha256"; done && ls -l && cd ..'
|
|
- 'cp packaging/install.sh dist/install.sh'
|
|
artifacts:
|
|
paths: [dist/]
|
|
reports:
|
|
dotenv: build.env
|
|
expire_in: 90 days
|
|
|
|
# --- GitLab release (v* tags): generic package registry + release with asset links -------------
|
|
release:
|
|
stage: release
|
|
image:
|
|
name: registry.gitlab.com/gitlab-org/release-cli:latest
|
|
entrypoint: [""]
|
|
needs: ["package"]
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG =~ /^v/'
|
|
script:
|
|
- 'apk add --no-cache curl >/dev/null 2>&1 || true'
|
|
- 'VERSION=${CI_COMMIT_TAG#v}; PKG="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/monky-deployd/${VERSION}"'
|
|
- 'for f in dist/*.deb dist/*.sha256 dist/install.sh; do curl -fsS --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "$f" "$PKG/$(basename "$f")"; echo; done'
|
|
- 'DEB=$(basename dist/*.deb)'
|
|
- >-
|
|
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"
|
|
--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\":\"install.sh\",\"url\":\"$PKG/install.sh\",\"link_type\":\"other\"}"
|
|
|
|
# --- Gitea release (the PUBLIC download the installer uses) ------------------------------------------
|
|
# The GitLab project is private, so install.sh fetches from the Gitea mirror
|
|
# https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v<ver>/... . 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
|
|
# has the by-hand recipe).
|
|
release:gitea:
|
|
stage: release
|
|
image:
|
|
name: alpine:3.20
|
|
entrypoint: [""]
|
|
needs: ["package"]
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG =~ /^v/ && $GITEA_TOKEN'
|
|
- if: '$CI_COMMIT_TAG =~ /^v/'
|
|
when: manual
|
|
allow_failure: true
|
|
variables:
|
|
GITEA_API: "https://gitea.cbs.tikali.net/api/v1/repos/mdella/monky-deployd"
|
|
script:
|
|
- 'apk add --no-cache curl jq >/dev/null'
|
|
- '[ -n "$GITEA_TOKEN" ] || { echo "GITEA_TOKEN is not set"; exit 1; }'
|
|
- 'H="Authorization: token $GITEA_TOKEN"'
|
|
- 'curl -fsS -X POST -H "$H" "$GITEA_API/mirror-sync" >/dev/null || echo "mirror-sync trigger failed; polling anyway"'
|
|
- 'for i in $(seq 1 30); do curl -fsS -H "$H" "$GITEA_API/tags/$CI_COMMIT_TAG" >/dev/null 2>&1 && break; echo "waiting for the mirror to carry $CI_COMMIT_TAG ($i)"; sleep 10; done'
|
|
- 'curl -fsS -H "$H" "$GITEA_API/tags/$CI_COMMIT_TAG" >/dev/null || { echo "tag not on the mirror yet"; exit 1; }'
|
|
- 'RID=$(curl -fsS -H "$H" "$GITEA_API/releases/tags/$CI_COMMIT_TAG" 2>/dev/null | jq -r .id || true)'
|
|
- 'if [ -z "$RID" ] || [ "$RID" = "null" ]; then RID=$(curl -fsS -X POST -H "$H" -H "Content-Type: application/json" "$GITEA_API/releases" -d "{\"tag_name\":\"$CI_COMMIT_TAG\",\"name\":\"monky-deployd $CI_COMMIT_TAG\",\"body\":\"See CHANGELOG.md\",\"draft\":false,\"prerelease\":false}" | jq -r .id); fi'
|
|
- 'echo "release id $RID"'
|
|
- 'for f in dist/*.deb dist/*.sha256 dist/install.sh; do n=$(basename "$f"); curl -fsS -X POST -H "$H" -F "attachment=@$f" "$GITEA_API/releases/$RID/assets?name=$n" >/dev/null && echo "uploaded $n"; done'
|