Apt setup
Set up a code repository end-to-end for APT (Debian/Ubuntu) distribution — produce .deb packages, publish a GPG-signed APT repository on GitHub Pages, and a one-line installer — with full automated verification. Use when the user wants to distribute their app via apt, "apt install", a Debian/Ubuntu package, an APT repo, a .deb, or a `curl | bash` Linux installer.From its SKILL.md
npx -y skills add punkscience/agent-skills --skill apt-setupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
12.8 KB, ~3.4k tokens by cl100k_base, as published. Nobody here has run it
APT Distribution Setup
Take a repository from "builds a binary" to "users run sudo apt install <pkg>",
doing as much as possible automatically. The goal is zero manual steps for the
user where the tooling allows it (gh uploads the signing-key secret, enables
Pages, triggers the workflow; the skill generates the key and verifies the live
repo). Where a step genuinely requires a human (rare), make it one copy-paste.
Work on a dedicated branch and offer to merge at the end.
Operating principles
- Verify, don't assume. Never tell the user "it works" until
scripts/verify-apt-repo.shpasses against the live repo. We confirm what a realsudo apt updatesees. - Minimize intervention. Prefer
ghautomation over hand instructions. Only fall back to manual when a capability is missing (e.g. noghauth, nogpg). - Surgical. Add packaging/CI files; don't refactor the project.
- Run all shell/apt steps in a Linux environment. On Windows, use WSL
(
wsl.exe -- bash -lc '…'); for non-trivial scripts, base64-encode the script andecho <b64> | base64 -d | bashto avoid quoting corruption.
Phase 0 — Preflight: can this repo build a Linux binary?
This is the gate. APT ships Linux binaries; if the project can't produce one, nothing downstream matters.
- Detect language/build system:
go.mod(Go),Cargo.toml(Rust),package.json(Node),pyproject.toml/setup.py(Python),CMakeLists.txt/Makefile(C/C++), or a prebuilt-binary repo. - Determine whether a Linux amd64 (and ideally arm64) binary is
produced today:
- Go: try
GOOS=linux GOARCH=amd64 go build ./.... Note CGO — ifCGO_ENABLEDmust be 1, cross-compiling needs a C toolchain; flag it. - Rust:
cargo build --release(+rustup target addfor cross). - Others: look for an existing build target/CI that emits a Linux binary.
- Check
.github/workflows/*for an existing release/build pipeline.
- Go: try
- If it does NOT build on Linux: stop and offer to set that up first. Use
AskUserQuestionto confirm scope. For Go this is usually trivial (it already cross-compiles); for compiled-with-CGO or other languages, set up the minimal Linux build and confirmfile <binary>reports an ELF binary before moving on. Do not proceed to packaging until a Linux binary exists.
State what you found (language, arches, whether Linux build works) before continuing.
Phase 1 — Branch
- Infer the branch naming convention: inspect
git branch -a, recent merge commits, and anyCONTRIBUTING/docs. Common patterns:feature/<x>,feat/<x>,<user>/<x>. - Create and switch: default
feature/apt-setup(adapt to the detected pattern). - If the working tree is dirty, note it; branch from the current HEAD.
Phase 1.5 — License (UNLICENSE by default)
apt packaging records an SPDX license (the nfpms license: field, and the
package's copyright). Make it consistent with what the repo actually carries.
- If the repo already has a
LICENSE/LICENSE.txt/COPYING, use it; reuse its SPDX identifier in the nfpmslicense:field. - If no license file exists, add UNLICENSE (public domain) — copy
templates/UNLICENSE→ repo-rootLICENSE, and set the nfpmslicense:field toUnlicense(the SPDX id). Do not ask; this is the default. Mention it so the user can override.
Phase 2 — Produce .deb packages
The .deb filenames must match what the publisher downloads:
<pkg>_<version>_<arch>.deb (e.g. derpy_1.0.1_amd64.deb).
Pick the path that fits the project:
- Go + goreleaser already present (
.goreleaser.y*ml): add annfpms:block if missing. Minimal form:
Ensure the release workflow runs goreleaser on tag push (see derpy'snfpms: - id: <pkg> package_name: <pkg> file_name_template: "{{ .PackageName }}_{{ .Version }}_{{ .Arch }}" vendor: <vendor> maintainer: <name> <email> homepage: <url> description: <one-line> license: <spdx> # default Unlicense if the repo has no license file (Phase 1.5) formats: [deb] section: <e.g. sound/utils> priority: optional.github/workflows/release.ymlas a reference shape). - Go without goreleaser: add goreleaser (recommended) or use
templates/nfpm.yaml. - Rust:
cargo install cargo-deb;cargo deb. Rename output to the required filename pattern, or set it viacargo-debconfig. - Anything else:
templates/nfpm.yaml(build Linux binaries, thennfpm package), orfpm, or a hand-rolleddpkg-deb --build.
Whatever the route, the release CI must upload the .deb files as release
assets for each arch, because the publisher pulls them with gh release download.
Confirm: after a (test) release, gh release view <tag> --json assets lists
<pkg>_<version>_<arch>.deb for every arch.
Phase 3 — Signing key (automated)
apt requires a signed repo. Check first whether it's already set up:
gh secret list for APT_SIGNING_KEY, and whether a keyring is already served.
If not:
- Generate the key (no passphrase — CI is unattended):
Derive name/email frombash scripts/gen-gpg-key.sh "<pkg> Packaging" <packaging-email> ./_aptkeysgit config user.*or ask. This writes_aptkeys/private-key.ascand the binary_aptkeys/keyring.gpg. - Upload the private key as the CI secret (no manual GitHub UI step):
gh secret set APT_SIGNING_KEY < _aptkeys/private-key.asc - The public binary keyring will be committed to gh-pages as
apt/<pkg>-archive-keyring.gpgin Phase 4 / by the publisher workflow. - Delete the private key locally and never commit it:
rm -f _aptkeys/private-key.asc. Add_aptkeys/to.gitignore.
If gh lacks permission to set secrets, fall back to a single instruction:
"paste the contents of _aptkeys/private-key.asc into Settings → Secrets →
Actions → New secret named APT_SIGNING_KEY."
Phase 4 — gh-pages + GitHub Pages (automated)
- If no
gh-pagesbranch exists, create an orphan one with a placeholder, and seed the keyring:git switch --orphan gh-pages git rm -rf . 2>/dev/null || true mkdir -p apt cp _aptkeys/keyring.gpg apt/<pkg>-archive-keyring.gpg # binary keyring echo "<pkg> apt repo" > index.html git add apt index.html && git commit -m "chore: seed gh-pages apt repo" git push -u origin gh-pages git switch - # back to the feature branch - Enable Pages from the
gh-pagesbranch root (no UI):
(POST to create, PUT to update if already enabled.)gh api -X POST repos/{owner}/{repo}/pages \ -f 'source[branch]=gh-pages' -f 'source[path]=/' 2>/dev/null \ || gh api -X PUT repos/{owner}/{repo}/pages \ -f 'source[branch]=gh-pages' -f 'source[path]=/'
Phase 5 — Install the publisher workflow + installer
- Copy
templates/apt-repo.yml→.github/workflows/apt-repo.ymland replace placeholders:__OWNER__ __REPO__ __PKG__ __ORIGIN__ __LABEL__ __DESC__ __KEYRING__ __ARCHES__. - Copy
templates/install.sh→ repo rootinstall.sh; replace__PKG__ __OWNER__ __REPO__ __KEYRING__;chmod +x install.sh. - Commit these on the feature branch.
The publisher workflow generates a single-stanza Release file on purpose — see "Critical knowledge" below. Do not reintroduce blank lines before the checksum sections.
Phase 6 — Build the repo for the first time
The publisher needs a release whose assets include the .deb files.
- If a suitable release already exists (assets present), use its tag.
- Otherwise cut one: bump version,
git tag vX.Y.Z && git push origin vX.Y.Z, let the release workflow build and upload the.debassets. Confirm withgh release view <tag> --json assets.
workflow_dispatchruns the workflow definition from the DEFAULT branch. Ifapt-repo.ymlis only on the feature branch, either merge first (Phase 8) or, for an isolated test, push the workflow to the default branch. Plan the order with the user; usually: merge, then dispatch.
Trigger and wait:
gh workflow run "APT Repository" -f tag=<tag>
gh run watch <run-id> --exit-status
Phase 7 — Verify (hard gates — all must pass)
Run from a Linux env. CDN (GitHub Pages/Fastly) can lag a minute; cache-bust structural fetches and retry the apt check if needed.
- Release stanza is well-formed (catches the #1 failure offline):
bash scripts/validate-release-file.sh \ https://<owner>.github.io/<repo>/apt/dists/stable/InRelease - Signature verifies against the served keyring:
curl -fsSL https://<owner>.github.io/<repo>/apt/<pkg>-archive-keyring.gpg -o /tmp/k.gpg file /tmp/k.gpg # must say "OpenPGP Public Key", NOT armored block curl -fsSL https://<owner>.github.io/<repo>/apt/dists/stable/InRelease -o /tmp/ir gpgv --keyring /tmp/k.gpg /tmp/ir - End-to-end apt install path (no root, no system impact):
This must printbash scripts/verify-apt-repo.sh \ https://<owner>.github.io/<repo>/apt \ https://<owner>.github.io/<repo>/apt/<pkg>-archive-keyring.gpg \ <pkg> stable mainPASS:— meaningapt-get updatesucceeded with no weak/hash warning and<pkg>resolved to an install candidate.
Only after all three pass, report success and give the user the install one-liner.
Phase 8 — Merge
- Summarize what changed (files added, key/secret created, Pages enabled, verify results).
- Offer to merge
feature/apt-setup→ default branch, matching the user's workflow (open a PR withgh pr create, or fast-forward merge if they prefer). Remember: the publisher workflow becomes dispatchable from the default branch only after merge — re-dispatch + re-verify if the first publish ran from a branch/test push.
Critical knowledge (the gotchas that cost real debugging time)
- The Release file is ONE Debian control stanza. The
MD5Sum/SHA1/SHA256/ SHA512checksums are fields of that single paragraph. A blank line anywhere in the body (e.g. beforeMD5Sum:or between sections) ends the stanza, detaching the checksums. apt then binds no strong hash and fails with:W: No Hash entry in Release file …+E: … provides only weak security information.Generate headers with no leading\n.validate-release-file.shcatches this. - apt 3.x treats MD5Sum and SHA1 as weak. Always include SHA256 and SHA512. A repo with only MD5/SHA1 is rejected as "weak security information".
- Serve a BINARY keyring (
gpg --export > file), not ASCII-armored (gpg --armor --export), forsigned-by=.fileshould report "OpenPGP Public Key", not "PGP public key block". - Strip non-standard Packages fields.
dpkg-scanpackagescan emit fields likeArchitecture-Variantthat confuse some apt versions —sed -i '/^Architecture-Variant:/d'. - Verify without sudo by relocating apt's
Dirtree. PointDirat a temp root and setAPT::Sandbox::Userto the current user (the_aptdownload sandbox otherwise can't write into a user temp dir). This is exactly whatverify-apt-repo.shdoes — a trueapt-get updatewith zero system impact. - GitHub Pages CDN caches. Different edges can briefly serve stale files.
Cache-bust structural checks with
?cb=<timestamp>. apt itself can't cache-bust; if a user still sees the old error, clear their lists:sudo rm -f /var/lib/apt/lists/<host>_* && sudo apt update. workflow_dispatchuses the default-branch workflow definition. Fixes to the publisher must reach the default branch before a dispatch regenerates the repo correctly.Date:field should be RFC1123 UTC:date -u +"%a, %d %b %Y %H:%M:%S UTC".- install.sh under
curl | bash:$0is/dev/fd/Nwhichsudocan't read. Re-fetch the script from its canonical URL beforeexec sudo bash.
Files in this skill
scripts/verify-apt-repo.sh— no-root, no-system-impact end-to-end apt check.scripts/validate-release-file.sh— static linter for the Release stanza bug + weak hashes.scripts/gen-gpg-key.sh— batch-generate the signing key (private + binary keyring).templates/apt-repo.yml— the corrected publisher workflow.templates/install.sh— thecurl | bashinstaller.templates/nfpm.yaml— standalone.debpackaging for non-goreleaser projects.templates/UNLICENSE— the public-domain default license text (Phase 1.5).