From de757711c07999a203442a59900979b5bcebb403 Mon Sep 17 00:00:00 2001 From: Dakota Paasman <122491662+dpaasman00@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:45:24 -0500 Subject: [PATCH] github pages apt repository --- .github/workflows/apt-repo.yml | 159 +++++++++++++++++++++++++++++++ INSTALL.md | 65 +++++++++++++ README_APT_SETUP.md | 166 +++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 .github/workflows/apt-repo.yml create mode 100644 INSTALL.md create mode 100644 README_APT_SETUP.md diff --git a/.github/workflows/apt-repo.yml b/.github/workflows/apt-repo.yml new file mode 100644 index 0000000..3411608 --- /dev/null +++ b/.github/workflows/apt-repo.yml @@ -0,0 +1,159 @@ +name: Update APT Repository + +on: + release: + types: [published] + + # Allow manual trigger for re-processing a release + workflow_dispatch: + inputs: + release_tag: + description: "Release tag to process (e.g. v1.0.0)" + required: true + +permissions: + contents: write + +jobs: + update-apt-repo: + runs-on: ubuntu-latest + steps: + - name: Determine release tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "tag=${{ github.event.inputs.release_tag }}" >> "$GITHUB_OUTPUT" + else + echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" + fi + + - name: Install APT repository tools + run: | + sudo apt-get update + sudo apt-get install -y dpkg-dev gzip + + # GPG_SIGNING: Uncomment to import the GPG key for signing repository metadata. + # Requires the following GitHub secrets: + # GPG_PRIVATE_KEY - ASCII-armored private key (gpg --armor --export-secret-keys KEY_ID) + # GPG_PASSPHRASE - Passphrase for the private key + # GPG_KEY_ID - Key ID or email used to identify the signing key + # + # - name: Import GPG key + # run: | + # echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import + # echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf + # gpgconf --kill gpg-agent + # gpgconf --launch gpg-agent + + - name: Checkout gh-pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + path: gh-pages + + - name: Download .deb files from release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p debs + cd debs + gh release download "${{ steps.tag.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --pattern "*.deb" + echo "Downloaded .deb files:" + ls -la *.deb + + - name: Build APT repository metadata + run: | + # Set up the pool directory structure. + # All .deb files live under pool/main/ so APT can fetch them. + mkdir -p gh-pages/pool/main + cp debs/*.deb gh-pages/pool/main/ + + # Generate architecture-specific Packages indexes. + for arch in amd64 arm64; do + dir="gh-pages/dists/stable/main/binary-${arch}" + mkdir -p "${dir}" + + # Scan the pool for .deb files matching this architecture and + # produce the Packages index. dpkg-scanpackages paths are relative + # to the repository root (gh-pages/). + (cd gh-pages && dpkg-scanpackages --arch "${arch}" pool/main) \ + > "${dir}/Packages" + + gzip -9c "${dir}/Packages" > "${dir}/Packages.gz" + + echo "Packages index for ${arch}:" + cat "${dir}/Packages" + done + + - name: Generate Release file + run: | + release_file="gh-pages/dists/stable/Release" + { + echo "Origin: observIQ" + echo "Label: bindplane-supervisor" + echo "Suite: stable" + echo "Codename: stable" + echo "Architectures: amd64 arm64" + echo "Components: main" + echo "Date: $(date -Ru)" + echo "Description: APT repository for bindplane-supervisor" + + # Checksums cover every Packages / Packages.gz file under dists/. + # APT uses these to verify integrity of downloaded index files. + echo "MD5Sum:" + (cd gh-pages/dists/stable && find . -name 'Packages*' -type f | sort | while read -r file; do + size=$(stat -c%s "${file}") + md5=$(md5sum "${file}" | awk '{print $1}') + # Strip leading "./" to get the relative path APT expects. + printf " %s %16d %s\n" "${md5}" "${size}" "${file#./}" + done) + + echo "SHA256:" + (cd gh-pages/dists/stable && find . -name 'Packages*' -type f | sort | while read -r file; do + size=$(stat -c%s "${file}") + sha256=$(sha256sum "${file}" | awk '{print $1}') + printf " %s %16d %s\n" "${sha256}" "${size}" "${file#./}" + done) + } > "${release_file}" + + echo "Release file contents:" + cat "${release_file}" + + # GPG_SIGNING: Uncomment to create InRelease (inline-signed) and Release.gpg + # (detached signature). APT uses these to verify the repository is authentic. + # + # - name: Sign Release file + # run: | + # # InRelease: clearsigned version of Release (preferred by modern APT) + # gpg --batch --yes --pinentry-mode loopback \ + # --passphrase "${{ secrets.GPG_PASSPHRASE }}" \ + # --default-key "${{ secrets.GPG_KEY_ID }}" \ + # --clearsign \ + # -o gh-pages/dists/stable/InRelease \ + # gh-pages/dists/stable/Release + # + # # Release.gpg: detached signature (fallback for older APT versions) + # gpg --batch --yes --pinentry-mode loopback \ + # --passphrase "${{ secrets.GPG_PASSPHRASE }}" \ + # --default-key "${{ secrets.GPG_KEY_ID }}" \ + # --detach-sign --armor \ + # -o gh-pages/dists/stable/Release.gpg \ + # gh-pages/dists/stable/Release + # + # - name: Export public key for users + # run: | + # # Publish the public key so users can add it to their APT keyring. + # gpg --armor --export "${{ secrets.GPG_KEY_ID }}" \ + # > gh-pages/signing-key.gpg + + - name: Commit and push to gh-pages + working-directory: gh-pages + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet && echo "No changes to commit" && exit 0 + git commit -m "Update APT repository for ${{ steps.tag.outputs.tag }}" + git push diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..32018b7 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,65 @@ +# Installing bindplane-supervisor via APT + +## Prerequisites + +- A Debian-based Linux distribution (Debian, Ubuntu, etc.) +- `curl` and root/sudo access + +> **Note:** GPG signing is not currently enabled for this repository. The +> instructions below use `[trusted=yes]` to allow APT to fetch packages without +> signature verification. Once GPG signing is enabled this will be replaced with +> proper key verification. + +## Add the APT Repository + +```bash +# Add the repository source list. +# Replace OWNER with the GitHub org or user (e.g. observIQ). +echo "deb [trusted=yes] https://OWNER.github.io/bindplane-supervisor/dists/stable/ stable main" \ + | sudo tee /etc/apt/sources.list.d/bindplane-supervisor.list > /dev/null +``` + + + +## Install + +```bash +sudo apt-get update +sudo apt-get install bindplane-supervisor +``` + +## Update + +```bash +sudo apt-get update +sudo apt-get upgrade bindplane-supervisor +``` + +## Install a Specific Version + +```bash +# List available versions. +apt-cache madison bindplane-supervisor + +# Install a specific version. +sudo apt-get install bindplane-supervisor= +``` + +## Uninstall + +```bash +sudo apt-get remove bindplane-supervisor + +# To also remove configuration files: +sudo apt-get purge bindplane-supervisor +``` diff --git a/README_APT_SETUP.md b/README_APT_SETUP.md new file mode 100644 index 0000000..02873fb --- /dev/null +++ b/README_APT_SETUP.md @@ -0,0 +1,166 @@ +# APT Repository Setup + +This project uses **GitHub Pages** to host APT repository metadata and +**GitHub Releases** to store the actual `.deb` packages. GoReleaser builds +`.deb` files on every tagged release, and a GitHub Actions workflow +(`.github/workflows/apt-repo.yml`) regenerates the APT metadata automatically. + +## Architecture + +``` +GitHub Release (v1.2.3) + └─ bindplane-supervisor_v1.2.3_linux_amd64.deb + └─ bindplane-supervisor_v1.2.3_linux_arm64.deb + +gh-pages branch (served by GitHub Pages) + ├─ pool/main/ ← .deb files copied here + │ ├─ bindplane-supervisor_v1.2.3_linux_amd64.deb + │ └─ bindplane-supervisor_v1.2.3_linux_arm64.deb + ├─ dists/stable/ + │ ├─ Release ← repository-level metadata + │ ├─ main/ + │ │ ├─ binary-amd64/ + │ │ │ ├─ Packages + │ │ │ └─ Packages.gz + │ │ └─ binary-arm64/ + │ │ ├─ Packages + │ │ └─ Packages.gz + │ # After GPG signing is enabled: + │ # ├─ InRelease ← inline-signed Release + │ # └─ Release.gpg ← detached Release signature + └─ # signing-key.gpg ← public GPG key for users +``` + +**How it works:** + +1. A developer pushes a tag (`v1.2.3`). +2. The `release.yml` workflow runs GoReleaser, which builds and uploads `.deb` + files to the GitHub Release. +3. The `apt-repo.yml` workflow triggers on release publication, downloads the + `.deb` files, generates APT metadata, and pushes to `gh-pages`. +4. GitHub Pages serves the `gh-pages` branch, making the APT repo available at + `https://.github.io/bindplane-supervisor/`. + +## Initial Setup + +### 1. Create the gh-pages branch + +The `gh-pages` branch must exist before the workflow can push to it. Create it +as an empty orphan branch: + +```bash +git checkout --orphan gh-pages +git rm -rf . +echo "APT repository for bindplane-supervisor" > index.html +git add index.html +git commit -m "Initialize gh-pages branch" +git push origin gh-pages +git checkout main +``` + +### 2. Enable GitHub Pages + +1. Go to the repository **Settings > Pages**. +2. Under **Source**, select **Deploy from a branch**. +3. Set the branch to `gh-pages` and the folder to `/ (root)`. +4. Click **Save**. + +The repository will be available at +`https://.github.io/bindplane-supervisor/` once the first workflow run +populates it. + +### 3. Verify + +After the next release (or a manual workflow dispatch), confirm: + +```bash +curl -s https://.github.io/bindplane-supervisor/dists/stable/Release +``` + +You should see the `Release` file contents with `Origin`, `Label`, checksums, +etc. + +## Adding GPG Signing + +GPG signing lets users verify that the repository metadata has not been +tampered with. The workflow and install instructions already contain +commented-out GPG sections marked with `GPG_SIGNING:`. + +### Step 1 — Generate a GPG key (if you don't have one) + +```bash +gpg --full-generate-key +# Choose RSA (sign only), 4096 bits, no expiration (or set one). +# Note the key ID from the output. +``` + +### Step 2 — Add GitHub secrets + +Go to **Settings > Secrets and variables > Actions** and add: + +| Secret | Value | +|--------------------|--------------------------------------------------------------| +| `GPG_PRIVATE_KEY` | `gpg --armor --export-secret-keys ` | +| `GPG_PASSPHRASE` | Passphrase for the key | +| `GPG_KEY_ID` | Key ID or email (e.g. `ABCDEF1234567890` or `you@example.com`) | + +### Step 3 — Uncomment GPG sections + +In `.github/workflows/apt-repo.yml`, find every block prefixed with +`# GPG_SIGNING:` and uncomment the steps below it: + +- **Import GPG key** — imports the private key into the runner's keyring. +- **Sign Release file** — generates `InRelease` and `Release.gpg`. +- **Export public key for users** — writes `signing-key.gpg` for users. + +In `INSTALL.md`, swap the `[trusted=yes]` source list entry for the +`[signed-by=...]` entry inside the `` comment block. + +### Step 4 — Test + +Trigger a manual workflow run and verify: + +```bash +# The InRelease file should exist and contain a PGP signature. +curl -s https://.github.io/bindplane-supervisor/dists/stable/InRelease | head -5 + +# The public key should be downloadable. +curl -fsSL https://.github.io/bindplane-supervisor/signing-key.gpg | gpg --show-keys +``` + +## Troubleshooting + +### `apt-get update` returns 404 + +- Confirm GitHub Pages is enabled and serving from `gh-pages`. +- Confirm the `gh-pages` branch contains `dists/stable/Release`. +- The sources list URL must **not** include a trailing slash after the hostname + path. + +### `apt-get update` shows "The repository is not signed" + +This is expected while GPG signing is disabled. Using `[trusted=yes]` in the +source list suppresses this error. Follow the GPG signing steps above to +resolve it permanently. + +### Workflow fails with "no .deb files found" + +- Confirm GoReleaser is configured to produce `.deb` packages (check the + `nfpms` section in `.goreleaser.yaml`). +- Check the GitHub Release assets to verify `.deb` files were uploaded. +- If the release was created as a draft, `.deb` files may not be available + until the release is published. + +### Old package versions not available + +Every workflow run copies all `.deb` files to `pool/main/` and regenerates the +index. Older versions remain available as long as their `.deb` files stay in +`pool/main/` on the `gh-pages` branch. + +### Workflow needs to be re-run for an existing release + +Use the manual workflow dispatch: + +1. Go to **Actions > Update APT Repository**. +2. Click **Run workflow**. +3. Enter the release tag (e.g. `v1.2.3`).