From 6f4814657311d3382a56ff705ec5f0128b2de5b7 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 8 Oct 2026 13:29:51 +0300 Subject: [PATCH] feat(packaging): publish signed APT and DNF repositories from the release workflow (#1059) --- .github/workflows/release.yml | 55 ++++++++++++++ README.md | 17 ++++- docs/packaging.md | 25 +++++++ scripts/linux/publish_package_repo.sh | 100 ++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100755 scripts/linux/publish_package_repo.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb706173..3f8aa528 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -546,3 +546,58 @@ jobs: TAG="${{ needs.publish.outputs.release_tag }}" chmod +x ./scripts/linux/aur_publish.sh ./scripts/linux/aur_publish.sh "$VER" "$TAG" + + publish-linux-repo: + name: Publish APT + DNF repository + needs: [publish] + runs-on: ubuntu-latest + steps: + - name: Check repository secrets + id: repo + env: + GPG_KEY: ${{ secrets.PACKAGE_REPO_GPG_PRIVATE_KEY }} + TOKEN: ${{ secrets.PACKAGE_REPO_TOKEN }} + run: | + if [ -n "${GPG_KEY}" ] && [ -n "${TOKEN}" ]; then + echo "enabled=true" >> "$GITHUB_OUTPUT" + else + echo "enabled=false" >> "$GITHUB_OUTPUT" + echo "PACKAGE_REPO_GPG_PRIVATE_KEY / PACKAGE_REPO_TOKEN not set; skipping." + fi + + - uses: actions/checkout@v4 + if: steps.repo.outputs.enabled == 'true' + + - uses: actions/download-artifact@v4 + if: steps.repo.outputs.enabled == 'true' + with: + name: bundle-linux + path: artifacts/linux + + - name: Install repository tooling + if: steps.repo.outputs.enabled == 'true' + run: | + sudo apt-get update + sudo apt-get install -y apt-utils createrepo-c rpm + + - name: Import signing key + if: steps.repo.outputs.enabled == 'true' + id: gpg + env: + GPG_KEY: ${{ secrets.PACKAGE_REPO_GPG_PRIVATE_KEY }} + run: | + set -euo pipefail + echo "$GPG_KEY" | gpg --batch --import + fpr="$(gpg --batch --list-secret-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')" + echo "fpr=$fpr" >> "$GITHUB_OUTPUT" + + - name: Publish + if: steps.repo.outputs.enabled == 'true' + env: + PACKAGE_REPO_URL: https://x-access-token:${{ secrets.PACKAGE_REPO_TOKEN }}@github.com/QueryaHub/repo.git + GPG_KEY_ID: ${{ steps.gpg.outputs.fpr }} + run: | + set -euo pipefail + deb="$(find artifacts/linux -name '*.deb' | head -n1)" + rpm="$(find artifacts/linux -name '*.rpm' | head -n1)" + ./scripts/linux/publish_package_repo.sh "$deb" "$rpm" diff --git a/README.md b/README.md index 0ca067f3..e768c3fb 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,22 @@ Whether you are navigating multi-million row datasets, authoring complex analyti ## Installation ### Linux -Download the latest `.deb`, `.AppImage`, or `.tar.gz` from [Releases](https://github.com/QueryaHub/Querya-Desktop/releases): +**Debian / Ubuntu (APT repository, updates with `apt upgrade`):** + +```bash +curl -fsSL https://repo.querya.app/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/querya.gpg +echo "deb [signed-by=/etc/apt/keyrings/querya.gpg] https://repo.querya.app/apt stable main" | sudo tee /etc/apt/sources.list.d/querya.list +sudo apt update && sudo apt install querya-desktop +``` + +**Fedora / RHEL (DNF repository):** + +```bash +sudo dnf config-manager --add-repo https://repo.querya.app/rpm/querya.repo +sudo dnf install querya-desktop +``` + +Or download the latest `.deb`, `.AppImage`, or `.tar.gz` from [Releases](https://github.com/QueryaHub/Querya-Desktop/releases): ```bash # Debian / Ubuntu (.deb) diff --git a/docs/packaging.md b/docs/packaging.md index 8eecbf83..0dcc0138 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -88,3 +88,28 @@ flatpak override --user com.queryahub.querya_desktop --filesystem=/var/run/postg ``` Flathub submission can reuse [`packaging/linux/flatpak/com.queryahub.querya_desktop.yml`](../packaging/linux/flatpak/com.queryahub.querya_desktop.yml). + +## APT and DNF repository (`repo.querya.app`) + +The Release workflow job `publish-linux-repo` runs +[`scripts/linux/publish_package_repo.sh`](../scripts/linux/publish_package_repo.sh) +after the GitHub Release is created. It adds the new `.deb` / `.rpm` to the +`gh-pages` branch of `QueryaHub/repo`, regenerates the APT indexes +(`apt-ftparchive`, signed `InRelease` / `Release.gpg`) and the DNF metadata +(`createrepo_c`, signed packages and `repomd.xml.asc`). Older versions stay in +the pool, so users can pin them. + +The job is skipped while its secrets are not set, like the AUR job. + +One-time setup: + +1. Create a signing key without a passphrase: `gpg --quick-generate-key "Querya Packages " rsa4096 sign never`. +2. Add repository secrets in `QueryaHub/Querya-Desktop`: + `PACKAGE_REPO_GPG_PRIVATE_KEY` (`gpg --armor --export-secret-keys `) and + `PACKAGE_REPO_TOKEN` (a token with write access to `QueryaHub/repo`). +3. In `QueryaHub/repo`, serve the `gh-pages` branch with GitHub Pages and set the + custom domain `repo.querya.app` (DNS `CNAME` to `queryahub.github.io`). +4. Run a release (or re-run `publish-linux-repo`) and check + `https://repo.querya.app/gpg.key` and `https://repo.querya.app/apt/dists/stable/InRelease`. + +Rotating the key means re-running the job and asking users to refresh `gpg.key`. diff --git a/scripts/linux/publish_package_repo.sh b/scripts/linux/publish_package_repo.sh new file mode 100755 index 00000000..f323f7a8 --- /dev/null +++ b/scripts/linux/publish_package_repo.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Publish the release .deb / .rpm to the signed APT + DNF repository. +# +# Usage: +# ./scripts/linux/publish_package_repo.sh +# +# Environment: +# PACKAGE_REPO_URL git URL of the repository served as repo.querya.app +# (token embedded for HTTPS), e.g. QueryaHub/repo +# PACKAGE_REPO_BRANCH branch that is served (default: gh-pages) +# GPG_KEY_ID fingerprint of the signing key (already imported) +# +# Layout written to the branch: +# gpg.key public key (ASCII armor) +# apt/pool/main/*.deb every published .deb +# apt/dists/stable/... Packages, Release, InRelease, Release.gpg +# rpm/*.rpm, rpm/repodata/ every published .rpm, signed repomd.xml +# rpm/querya.repo file for `dnf config-manager --add-repo` +# +# Requires: git, gpg, apt-utils (apt-ftparchive), createrepo-c, rpm. +set -euo pipefail + +DEB="${1:?path to .deb}" +RPM="${2:?path to .rpm}" +: "${PACKAGE_REPO_URL:?}" +: "${GPG_KEY_ID:?}" +BRANCH="${PACKAGE_REPO_BRANCH:-gh-pages}" +BASE_URL="${PACKAGE_REPO_BASE_URL:-https://repo.querya.app}" + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/querya-pkgrepo.XXXXXX")" +trap 'rm -rf "$WORK"' EXIT +SITE="$WORK/site" + +if git ls-remote --exit-code --heads "$PACKAGE_REPO_URL" "$BRANCH" >/dev/null 2>&1; then + git clone --quiet --depth 1 --branch "$BRANCH" "$PACKAGE_REPO_URL" "$SITE" +else + git init --quiet "$SITE" + git -C "$SITE" checkout --quiet -b "$BRANCH" + git -C "$SITE" remote add origin "$PACKAGE_REPO_URL" +fi + +gpg --batch --armor --export "$GPG_KEY_ID" > "$SITE/gpg.key" + +# ---- APT ------------------------------------------------------------------- +APT="$SITE/apt" +mkdir -p "$APT/pool/main" "$APT/dists/stable/main/binary-amd64" +cp -f "$DEB" "$APT/pool/main/" +( + cd "$APT" + apt-ftparchive packages pool > dists/stable/main/binary-amd64/Packages + gzip -9kf dists/stable/main/binary-amd64/Packages + apt-ftparchive \ + -o APT::FTPArchive::Release::Origin=Querya \ + -o APT::FTPArchive::Release::Label=Querya \ + -o APT::FTPArchive::Release::Suite=stable \ + -o APT::FTPArchive::Release::Codename=stable \ + -o APT::FTPArchive::Release::Architectures=amd64 \ + -o APT::FTPArchive::Release::Components=main \ + release dists/stable > dists/stable/Release + gpg --batch --yes --default-key "$GPG_KEY_ID" \ + --clearsign -o dists/stable/InRelease dists/stable/Release + gpg --batch --yes --default-key "$GPG_KEY_ID" \ + --armor --detach-sign -o dists/stable/Release.gpg dists/stable/Release +) + +# ---- DNF ------------------------------------------------------------------- +RPMDIR="$SITE/rpm" +mkdir -p "$RPMDIR" +cp -f "$RPM" "$RPMDIR/" +rpmsign_conf="$WORK/rpmmacros" +printf '%%_gpg_name %s\n%%__gpg %s\n' "$GPG_KEY_ID" "$(command -v gpg)" > "$rpmsign_conf" +HOME_RPM="$WORK/home" +mkdir -p "$HOME_RPM" +cp "$rpmsign_conf" "$HOME_RPM/.rpmmacros" +HOME="$HOME_RPM" GNUPGHOME="${GNUPGHOME:-$HOME/.gnupg}" rpmsign --addsign "$RPMDIR"/*.rpm +createrepo_c --update "$RPMDIR" +gpg --batch --yes --default-key "$GPG_KEY_ID" \ + --armor --detach-sign -o "$RPMDIR/repodata/repomd.xml.asc" "$RPMDIR/repodata/repomd.xml" +cat > "$RPMDIR/querya.repo" <