# 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). Runner egress to github.com + pypi.org was PROVEN on the v0.1.0 tag # pipeline (6999: openziti 1.7.1 wheel built, nfpm .deb packaged), so wheel/package are blocking. # # CI/CD variables (project or group level): # 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). # 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 (proven 2026-09-05, pipeline 6999). wheel: stage: build image: ubuntu:26.04 needs: ["test"] 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 (proven 2026-09-05, pipeline 6999). The wheel is required: a .deb without it would # silently ship a broken `transport: sdk`. package: stage: package image: ubuntu:26.04 needs: - job: test - job: wheel 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 .' - 'ls vendor/*.whl >/dev/null 2>&1 || { echo "no vendored openziti wheel (wheel job artifact missing)"; exit 1; }' - '/opt/monky-deployd/venv/bin/pip install -q vendor/*.whl' - '/opt/monky-deployd/venv/bin/python -c "import openziti; print(\"openziti import ok\")"' - '/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 (the PRIMARY download) + release --------- # install.sh / the ansible role fetch ${CI_API_V4_URL}/projects/69/packages/generic/monky-deployd//... 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. 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.sha256\",\"url\":\"$PKG/$DEB.sha256\",\"link_type\":\"other\"}" --assets-link "{\"name\":\"install.sh\",\"url\":\"$PKG/install.sh\",\"link_type\":\"other\"}" # --- Gitea release (the off-estate alternative: install.sh --source gitea) ----------------------- # Same assets at https://gitea.cbs.tikali.net/mdella/monky-deployd/releases/download/v/... . # Not the default: inside the estate that name is split-horizon to jump1's RED EIP (cbs/iac#102). # 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'