diff --git a/.gitea/scripts/packages.sh b/.gitea/scripts/packages.sh new file mode 100755 index 0000000..c26eeed --- /dev/null +++ b/.gitea/scripts/packages.sh @@ -0,0 +1,99 @@ +#!/bin/sh +set -eu +version=$1 +releases=$(pwd)/releases/$version +root=${PACKAGES_ROOT:-/var/www/packages.arasaka.software} +keep=${PACKAGES_KEEP:-5} +url=https://packages.arasaka.software +fingerprint=F92C166E9B1C13EDD1629E76888DCF969D133FB2 +deb=$releases/MeWeb-$version-linux-x64.deb +rpm=$releases/MeWeb-$version-linux-x64.rpm +test -f "$deb" +test -f "$rpm" + +exec 9>>"$root/.lock" +flock -w 2700 9 + +keys=$(mktemp -d) +trap 'rm -rf "$keys"' EXIT +export GNUPGHOME="$keys" +printf '%s\n' "$PACKAGES_SIGNING_KEY" | gpg --batch --quiet --import +gpg --batch --with-colons --list-secret-keys | grep -q "^fpr:::::::::$fingerprint:$" +gpg --batch --armor --export "$fingerprint" > "$keys/public.asc" + +rpmsign --define "__gpg /usr/bin/gpg" --define "_gpg_name $fingerprint" --define "_gpg_path $keys" --addsign "$rpm" >/dev/null +rpmkeys --dbpath "$keys/rpmdb" --import "$keys/public.asc" +rpmkeys --dbpath "$keys/rpmdb" -K "$rpm" | grep -q 'digests signatures OK' + +full=$(dpkg-deb -f "$deb" Version) +test "$(dpkg-deb -f "$deb" Package)" = meweb +test "${full%-*}" = "$version" +debname=meweb_${full}_$(dpkg-deb -f "$deb" Architecture).deb +rpmname=$(rpm -qp --nosignature --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' "$rpm") +test "$(rpm -qp --nosignature --qf '%{NAME} %{VERSION}' "$rpm")" = "meweb $version" + +snap=$root/.snap/$(date -u +%Y%m%d%H%M%S)-$version +mkdir -p "$snap/apt/pool/main" "$snap/rpm" +if [ -d "$root/apt/pool/main" ]; then cp -al "$root/apt/pool/main/." "$snap/apt/pool/main/"; fi +if [ -d "$root/rpm" ]; then + for folder in "$root"/rpm/*/; do + [ "$(basename "$folder")" = repodata ] || cp -al "$folder" "$snap/rpm/" + done +fi +mkdir -p "$snap/apt/pool/main/m/meweb" "$snap/rpm/meweb" +rm -f "$snap/apt/pool/main/m/meweb/$debname" "$snap/rpm/meweb/$rpmname" +cp "$deb" "$snap/apt/pool/main/m/meweb/$debname" +cp "$rpm" "$snap/rpm/meweb/$rpmname" +for folder in "$snap/apt/pool/main/m/meweb" "$snap/rpm/meweb"; do + ls "$folder" | sed -E 's/^meweb[_-]([0-9.]+)-.*/\1/' | sort -uV | head -n "-$keep" | while read -r old; do + rm -f "$folder/meweb_$old-"* "$folder/meweb-$old-"* + done +done + +binary=$snap/apt/dists/stable/main/binary-amd64 +mkdir -p "$binary" +(cd "$snap/apt" && apt-ftparchive packages pool) > "$binary/Packages" +gzip -9nc "$binary/Packages" > "$binary/Packages.gz" +(cd "$snap/apt/dists/stable" && apt-ftparchive \ + -o APT::FTPArchive::Release::Origin=Arasaka -o APT::FTPArchive::Release::Label=Arasaka \ + -o APT::FTPArchive::Release::Suite=stable -o APT::FTPArchive::Release::Codename=stable \ + -o APT::FTPArchive::Release::Architectures=amd64 -o APT::FTPArchive::Release::Components=main \ + -o APT::FTPArchive::Release::NotAutomatic=yes -o APT::FTPArchive::Release::ButAutomaticUpgrades=yes \ + -o "APT::FTPArchive::Release::Description=Arasaka packages" release .) > "$keys/Release" +mv "$keys/Release" "$snap/apt/dists/stable/Release" +gpg --batch --yes --local-user "$fingerprint" --clearsign -o "$snap/apt/dists/stable/InRelease" "$snap/apt/dists/stable/Release" +gpg --batch --yes --local-user "$fingerprint" --armor --detach-sign -o "$snap/apt/dists/stable/Release.gpg" "$snap/apt/dists/stable/Release" + +createrepo_c --quiet "$snap/rpm" +repodata=$snap/rpm/repodata +gpg --batch --yes --local-user "$fingerprint" --armor --detach-sign -o "$repodata/repomd.xml.asc" "$repodata/repomd.xml" +cp "$keys/public.asc" "$repodata/repomd.xml.key" +if [ -f "$root/rpm/repodata/repomd.xml" ]; then + grep -o 'repodata/[^"]*' "$root/rpm/repodata/repomd.xml" | while read -r href; do + [ -e "$snap/rpm/$href" ] || cp "$root/rpm/$href" "$snap/rpm/$href" + done +fi +chmod -R u=rwX,go=rX "$snap" + +for part in apt rpm; do + ln -sfn ".snap/$(basename "$snap")/$part" "$root/$part.new" + mv -T -f "$root/$part.new" "$root/$part" +done +ls -1d "$root"/.snap/* | sort | head -n -3 | while read -r old; do + find "$old" -maxdepth 0 -mmin +1440 -exec rm -rf {} + +done +echo "published $debname and $rpmname in $root" + +(cd "$releases" && for file in $(ls | LC_ALL=C sort); do [ "$file" = SHA256SUMS.txt ] || sha256sum "$file"; done) > "$keys/sums" +mv "$keys/sums" "$releases/SHA256SUMS.txt" + +if [ "$root" = /var/www/packages.arasaka.software ]; then + curl -fsS "$url/apt/dists/stable/main/binary-amd64/Packages" | grep -qx "Version: $full" + curl -fsS "$url/apt/dists/stable/InRelease" > "$keys/InRelease" + gpg --batch --quiet --verify "$keys/InRelease" + curl -fsS "$url/rpm/repodata/repomd.xml" > "$keys/repomd.xml" + curl -fsS "$url/rpm/repodata/repomd.xml.asc" > "$keys/repomd.xml.asc" + gpg --batch --quiet --verify "$keys/repomd.xml.asc" "$keys/repomd.xml" + curl -fsSI "$url/rpm/meweb/$rpmname" > /dev/null + echo "live at $url: meweb $version" +fi diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 36e855f..7a36422 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -46,12 +46,8 @@ jobs: - name: The deb and rpm on packages.arasaka.software, the rpm signed if: ${{ github.ref_type == 'tag' }} env: - YOLO_TOKEN: ${{ secrets.YOLO_TOKEN }} - YOLO_GITEA: http://localhost:3000 - YOLO_MIN: "0.1.0" - run: | - yolo repo submit --project meweb --version "$VERSION" --replace-signed "releases/$VERSION/MeWeb-$VERSION-linux-x64.deb" "releases/$VERSION/MeWeb-$VERSION-linux-x64.rpm" - yolo sums "releases/$VERSION" + PACKAGES_SIGNING_KEY: ${{ secrets.PACKAGES_SIGNING_KEY }} + run: .gitea/scripts/packages.sh "$VERSION" - name: Publish the release if: ${{ github.ref_type == 'tag' || inputs.publish == 'true' }} diff --git a/CLAUDE.md b/CLAUDE.md index 3849a32..d9658fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -143,13 +143,20 @@ The Android release key: alias `meweb`, certificate SHA-256 the network and the block without the publisher's server. A change to any knowledge file's format bumps `KnowledgeFeeder.Format` and the `"format":N` that `wait-knowledge.sh` waits for: the feeder rebuilds only when a source changes or the format differs, and a release must not embed the previous build. -- **The deb and rpm reach packages.arasaka.software through YoloCICD.** `release.yml` runs `yolo repo submit - --replace-signed` on tags (org secret `YOLO_TOKEN`, `yolo` installed on nuvola by YoloCICD): the files go to the - bot's private inbox, YoloCICD's `publish.yml` checks this run against the tag, signs the rpm, gates on an upgrade - check in containers and publishes; the signed rpm replaces the local one, then `yolo sums` rewrites SHA256SUMS. - `packaging/arasaka-repository.sh` is generated (`yolo repo kit --out`, never edited by hand) and is written into - both maintainer scripts by `tools/release.cs`; the package owns `/usr/share/arasaka-repository/meweb`, the marker - that keeps the repository configured while any Arasaka package is installed. +- **The deb and rpm reach packages.arasaka.software from this repository's own release job.** On tags, + `.gitea/scripts/packages.sh` runs on the `nuvola` runner (host mode, user `gitea-runner`, which owns + `/var/www/packages.arasaka.software`). It imports the org secret `PACKAGES_SIGNING_KEY` into a throwaway + `GNUPGHOME`, refuses any key but `F92C166E…3FB2`, signs the rpm in place, hard-links the current pool into a new + `.snap/` folder, adds this version, keeps the newest five, rebuilds and signs the apt (`InRelease`, `Release.gpg`) + and rpm (`repomd.xml.asc`) metadata, carries over the files the previous `repomd.xml` named so a client mid-refresh + still finds them, swaps the `apt` and `rpm` links, rewrites `SHA256SUMS.txt` for the signed rpm, and checks the live + site. Never copy a package over a pool file: the pools are hard links into older snapshots, so it is `rm` then `cp`. + `PACKAGES_ROOT` and `PACKAGES_KEEP` exist for a test against a copy. YoloCICD, which used to do this, was erased on + 23 Oct 2026. `packaging/arasaka-repository.sh` is written into both maintainer scripts by `tools/release.cs`; it is + now maintained here by hand, and any change to a file it writes needs the old content's SHA-256 kept in + `arasaka_repository_known`, or installed copies are treated as edited by hand and left alone. The package owns + `/usr/share/arasaka-repository/meweb`, the marker that keeps the repository configured while any Arasaka package + is installed. The signing key's only copies are that secret and the owner's Nextcloud backup. - **The server cannot ping**: `NoNewPrivileges` drops `ping`'s file capability and `ping_group_range` is closed, so the trace back to a caller uses `tracepath` (UDP, unprivileged) and keeps only public hops outside `Probe:Refuse`, so the box's own network never reaches an app. diff --git a/README.md b/README.md index d9970c5..4f8f001 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Linux (deb, rpm), Windows, macOS and Android. Builds are published as releases o On Linux the deb and the rpm add the Arasaka package repository, packages.arasaka.software, when they are installed, so later versions arrive through the package manager. The rpm is signed with the repository's key; on openSUSE, import it first with `sudo rpm --import https://packages.arasaka.software/arasaka.asc`. The script the packages run -is `packaging/arasaka-repository.sh`, generated by YoloCICD (`yolo repo kit`); it never calls the package manager, +is `packaging/arasaka-repository.sh`; it never calls the package manager, leaves files you edited alone, and removes the repository only when no Arasaka package is left (purge or erase). ## Privacy