Apt setup
Agent Skills for software distribution — apt-setup (Debian/Ubuntu) and choco-setup (Windows), SKILL.md standard
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.
What its author says it does
Copied from the file, not written here
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.
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).