Add Packaging

2026-08-12 09:47:07 +00:00
committed by Gitea
parent e4f8abadd7
commit 0c28d572af
+102
@@ -0,0 +1,102 @@
# Packaging & declarative state
## Build and push a package
```bash
docker run --rm -v "$PWD:/work" -w /work \
git.morlana.online/f.weber/aptly-deb-builder:<tag> \
aptly-release --config nfpm.yaml -- --repo stable --distribution stable
```
`aptly-release` = `aptly-pack` (builds a `.deb`) + `aptly-push` (uploads it, includes
it into the repo, refreshes the publish target) in one call. Everything before `--`
goes to `aptly-pack`, everything after goes to `aptly-push`; the built `.deb` files
are appended automatically.
**Two packaging paths**, auto-detected:
- no `debian/` directory present → **nfpm** (`nfpm.yaml`, no Debian toolchain
knowledge needed)
- `debian/` directory present → real `dpkg-buildpackage -us -uc -b`
(unsigned — signing is aptly's job at publish time, not the package's)
Auth: `APTLY_USER`/`APTLY_PASSWORD` (or none at all, for an open repo) — works
unchanged across all four presets from [security.md](security.md). Against a signed
repo: do **not** pass `--no-sign`, and make sure the server has a signing key (see
below); against an unsigned repo: pass `--no-sign`.
## Reusable Gitea Action
```yaml
- uses: f.weber/aptly-containerized@image/v1.6.3-1
with:
url: https://apt.example.com
repo: stable
distribution: stable
username: ${{ secrets.APTLY_USER }}
password: ${{ secrets.APTLY_PASSWORD }}
```
Lives at the repo root deliberately: `uses:` references to other Gitea repos **with
a subpath** (`owner/repo/path/action@ref`) are not reliable, only root and local
(`./.gitea/actions/x`) references are. A more robust fallback that needs no action
resolution at all: call the image directly (see above).
## Declarative state (`state.yaml`)
Consumed by `aptly-reconcile` — a single file in Compose (`compose/config/state.yaml`),
generated from `values.yaml`'s `aptly.{localRepos,mirrors,publish}` in the Helm
chart. Runs exclusively against the **REST API**, never against the aptly CLI:
`aptly api serve` holds the LevelDB lock, and a CLI call against the same `rootDir`
would either block or (with `-no-lock`) endanger the database.
```yaml
localRepos:
- { name: stable, comment: "", defaultDistribution: stable, defaultComponent: main }
mirrors:
- name: debian-security
archiveURL: http://security.debian.org/debian-security
distribution: trixie-security
components: [main]
architectures: [amd64, arm64]
publish:
- name: stable-root # only internal to the reconciler, not part of the aptly API
prefix: "" # "" = repo root
distribution: stable
sourceKind: local # local | snapshot
sources: [{ name: stable, component: main }]
architectures: [amd64, arm64]
acquireByHash: true
```
**Known, deliberate limitation:** `mirrors[].components` cannot be changed after a
mirror has been created — aptly's API has no endpoint for that. Changing it requires
deleting and recreating the mirror. Everything else in a mirror definition (filter,
architectures, download flags, …), and all of a local repo's metadata, is
synchronized on every run.
**Idempotency:** GET-before-write, never POST-and-swallow-40x (a swallowed 400 would
also skip real *changes*). `reconcile.failOnError` (Helm) resp. `APTLY_FAIL_ON_ERROR`
(Compose/env) defaults to `false` — an unreachable mirror must not make
`helm upgrade`/`docker compose up` fail.
**The prefix-escaping pitfall:** aptly's API expects the root prefix in the URL as
the literal `.` (not an empty path) — and `curl` normalizes away `/./` segments
client-side unless you set `--path-as-is` (every script here already does). Coming
back the other way, `GET /api/publish` also returns the root prefix as the literal
`"."` (empirically verified, not `""`) — so when checking "does this publish target
already exist", you need to compare against `"."`, not against the empty string from
`values.yaml`/`state.yaml`. Nested prefixes (`debian/stable`), on the other hand, are
returned with real slashes, not underscore-escaped — the escaped form (`/``_`) is
only a quirk of URL *path* construction, not of the stored/listed values. Both are
already handled correctly in `rootfs/usr/local/bin/aptly-reconcile` (`api_prefix()`
and the `prefix_for_compare` special case) — worth knowing if you write your own
scripts against the same API.
## GPG
Default: the `gpg` provider (a real gnupg binary, included in the image). Key source
in production is always a Secret (`aptly.gpg.signingKey.existingSecret` in the
chart, `compose/config/gpg/private.asc` in Compose) — never inline in `values.yaml`.
The key ID is auto-detected (`gpg --list-secret-keys --with-colons`); the public key
is served under `/signing-key.asc` so clients can reference it directly via
`signed-by=` (a copy-paste snippet appears in the Helm NOTES resp. `compose/README`).