Public Access
Add Packaging
+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`).
|
||||
Reference in New Issue
Block a user