Initial implementation: aptly container image, Compose stacks, Helm chart, and Gitea Actions pipelines
CI / lint (push) Failing after 24s
CI / smoke-test (push) Failing after 2m4s
Release image / release (push) Successful in 23m18s
Release chart / release (push) Successful in 7s

Provides a self-contained, containerized aptly (Debian repo manager)
stack with independently releasable image and Helm chart versions.

- images/: aptly-server (aptly built from source, cross-compiled) and
  aptly-deb-builder (nfpm + dpkg-buildpackage) container images
- rootfs/: shared aptly-init/aptly-reconcile/aptly-push/aptly-pack
  scripts consumed identically by Compose and the Helm chart, driven
  by one declarative state.yaml contract
- compose/: test (ephemeral, open) and production docker-compose
  stacks with an nginx read/auth sidecar
- charts/aptly/: aptly-native Helm chart covering every security
  posture from fully open to authenticated read+write, Ingress and
  Gateway API support (usable in parallel for migration scenarios),
  metrics, and declarative repo/mirror/publish reconciliation via a
  Helm hook
- .gitea/workflows/: CI (lint, template, kubeconform, E2E smoke test)
  plus separately tagged image (image/v*) and chart (chart/v*)
  releases, weekly rebuilds, and a preflight workflow validating the
  runner's Docker/Helm-OCI capabilities
- pubkeys/: RSA chart-signing key for Helm --sign / Artifact Hub's
  signKey annotation (Helm can't verify Ed25519 keys)
- docs/, README.md, charts/aptly/README.md: usage, security, and
  versioning documentation
This commit is contained in:
2026-08-12 12:21:08 +02:00
commit 103ad311b7
71 changed files with 4843 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
name: demo-package
arch: amd64
platform: linux
version: "1.0.0"
section: default
priority: optional
maintainer: "aptly-containerized CI <ci@example.com>"
description: Demo package used by tests/smoke-test.sh — proves the full
build -> push -> publish -> apt-get chain end to end.
contents:
- src: ./payload/hello.txt
dst: /usr/share/demo-package/hello.txt
+1
View File
@@ -0,0 +1 @@
hello from aptly-containerized
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# tests/smoke-test.sh — the actual merge gate for this repo. Proves the
# whole promise of the project end to end, not just that things compile:
#
# pack -> push -> publish -> `apt-get update && apt-get install` -> content
#
# against the real docker-compose.test.yaml stack (aptly-init -> aptly ->
# nginx sidecar), using the same rootfs scripts and images CI ships. Nothing
# here is mocked. Run from the repo root: ./tests/smoke-test.sh
set -euo pipefail
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
log() { printf '\033[1;34m[smoke]\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[smoke] ERROR:\033[0m %s\n' "$*" >&2; exit 1; }
COMPOSE_FILE="compose/docker-compose.test.yaml"
PROJECT="aptly-smoke-$$"
FIXTURE_DIR="tests/fixtures/demo-package"
WORK_DIR="$(mktemp -d)"
cleanup() {
log "cleaning up (project: ${PROJECT})"
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
log "building images and starting the test stack"
# Not --wait: it treats the `reconcile` one-shot service's expected exit(0)
# as a failure and aborts. Poll nginx's own healthcheck instead.
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --build
network="${PROJECT}_default"
log "waiting for nginx to report healthy"
deadline=$(( $(date +%s) + 120 ))
until docker compose -p "$PROJECT" -f "$COMPOSE_FILE" ps nginx --format '{{.Health}}' 2>/dev/null | grep -q healthy; do
(( $(date +%s) < deadline )) || die "nginx did not become healthy within 120s"
sleep 2
done
log "building the deb-builder image"
docker build -q -f images/aptly-deb-builder/Dockerfile -t "aptly-deb-builder:${PROJECT}" . >/dev/null
log "packaging the demo .deb with aptly-pack (nfpm path)"
mkdir -p "${WORK_DIR}/dist"
docker run --rm \
-v "${ROOT_DIR}/${FIXTURE_DIR}:/work:ro" \
-v "${WORK_DIR}/dist:/work-out" \
-w /tmp/pkg \
--entrypoint /bin/sh \
"aptly-deb-builder:${PROJECT}" \
-c "cp -r /work/. /tmp/pkg && aptly-pack --output-dir /work-out"
deb_file="$(find "${WORK_DIR}/dist" -name '*.deb' | head -1)"
[[ -n "$deb_file" ]] || die "aptly-pack produced no .deb"
log "built: $(basename "$deb_file")"
log "pushing the package into the (unsigned, open) test repo via aptly-push"
# Reuses the already-built `reconcile` service's image/network via `compose
# run` instead of guessing compose's image-naming convention.
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" run --rm --no-deps \
-e "APTLY_URL=http://aptly:8080" \
-v "${WORK_DIR}/dist:/dist:ro" \
--entrypoint /usr/local/bin/aptly-push \
reconcile \
--repo demo --distribution stable --no-sign "/dist/$(basename "$deb_file")"
log "verifying via a real apt-get against the published repo (through nginx)"
docker run --rm --network "$network" debian:trixie-slim bash -euxc "
echo 'deb [trusted=yes] http://nginx:8080/ stable main' > /etc/apt/sources.list.d/smoke.list
apt-get update
apt-get install -y --no-install-recommends demo-package
test \"\$(cat /usr/share/demo-package/hello.txt)\" = 'hello from aptly-containerized'
"
log "reconcile idempotency: re-running against unchanged state must not fail"
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" run --rm reconcile
log "PASS — build -> push -> publish -> apt-get chain verified end to end"