name: ci # CI for souveraine — Rust crate. Tests + clippy on push/PR to primary (dev) # and public (published). On push to public, mirror public:public to Codeberg # on green. primary stays the private dev branch; public is what publishes. # # Required repo secrets: # CODEBERG_TOKEN — push to codeberg.org/Fimeg/Souveraine (public branch) on: push: branches: [primary, public] pull_request: branches: [primary, public] jobs: rust-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Install system deps run: sudo apt-get update -qq && sudo apt-get install -y -qq libasound2-dev libchafa-dev # Pinned fork on our Gitea (rgb patch committed there) — upstream HEAD # must never decide whether our build passes. - name: Clone tuie (pinned fork) run: | git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ clone "${GITHUB_SERVER_URL}/Fimeg/tuie.git" /workspace/Fimeg/tuie git -C /workspace/Fimeg/tuie checkout aa43e335c8f5a3cd2c78e397a1a8dfe534a6bc35 - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with: toolchain: "1.94" components: clippy - name: cargo test run: cargo test - name: cargo clippy run: cargo clippy -- -D warnings no-ai-attribution: runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - name: Check commit messages for AI attribution run: | if [ "${{ github.event_name }}" = "pull_request" ]; then RANGE="${{ github.event.pull_request.base.sha }}..${{ github.sha }}" else RANGE="${{ github.event.before }}..${{ github.sha }}" if [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then RANGE="HEAD~10..HEAD" fi fi # CO assembled from fragments so this guard file does not itself trip # the public-branch pre-commit scanner (which bans the literal token). CO="Co-Authored""-By:" PATTERNS="${CO}.*[Cc]laude|${CO}.*OpenAI|${CO}.*ChatGPT|${CO}.*Copilot|${CO}.*Letta|${CO}.*Cursor|Generated by|Generated with|AI-assisted|Auto-generated by" FAIL=0 while IFS= read -r msg; do if echo "$msg" | grep -qiE "$PATTERNS"; then echo "::error::AI attribution found in commit: $msg" FAIL=1 fi done < <(git log --format='%s%n%b' $RANGE 2>/dev/null) if [ "$FAIL" -eq 1 ]; then echo "::error::Commits contain AI attribution lines. Remove them before merging." exit 1 fi echo "No AI attribution found in commits." action-pins: runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Check for floating action refs run: | if grep -rE 'uses:.*@(v[0-9]+|stable|main|master)(\s|$)' .gitea/workflows/; then echo "::error::Floating action refs found — pin every action to a commit SHA" exit 1 fi echo "All action refs are SHA-pinned." # Phone artifact on green. Runs on the ArchDev build node (host mode, label # archdev) where the aarch64 sysroot lives — no docker, no node, plain shell. # Publishes the cross-built binary to a rolling `edge` prerelease; the phone # updates itself from there instead of waiting on a laptop scp. aarch64-artifact: runs-on: archdev needs: [rust-test, no-ai-attribution, action-pins] if: github.event_name == 'push' && github.ref == 'refs/heads/public' steps: - name: Clone repo + pinned tuie run: | set -euo pipefail rm -rf "$GITHUB_WORKSPACE/src" git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ clone "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE/src/Souveraine" git -C "$GITHUB_WORKSPACE/src/Souveraine" checkout "$GITHUB_SHA" git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ clone "${GITHUB_SERVER_URL}/Fimeg/tuie.git" "$GITHUB_WORKSPACE/src/tuie" git -C "$GITHUB_WORKSPACE/src/tuie" checkout aa43e335c8f5a3cd2c78e397a1a8dfe534a6bc35 - name: Build release binaries run: | set -euo pipefail cd "$GITHUB_WORKSPACE/src/Souveraine" # Persistent target dirs: fresh clones per run, warm compile cache. export CARGO_TARGET_DIR="$HOME/.cache/souveraine-ci-target" export SOUVERAINE_AARCH64_SYSROOT="$HOME/aarch64-sysroot" ./scripts/build-cross.sh BIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine" file "$BIN" | grep -q aarch64 || { echo "built binary is not aarch64" >&2; exit 1; } cp "$BIN" souveraine-aarch64 export CARGO_TARGET_DIR="$HOME/.cache/souveraine-ci-target-x86_64" cargo build --release BIN="$CARGO_TARGET_DIR/release/souveraine" file "$BIN" | grep -q 'x86-64' || { echo "built binary is not x86_64" >&2; exit 1; } cp "$BIN" souveraine-x86_64 sha256sum souveraine-aarch64 souveraine-x86_64 > souveraine-binaries.sha256 - name: Build UPower fork (per-arch install trees) run: | set -euo pipefail cd "$GITHUB_WORKSPACE/src/Souveraine" # The fork is a git submodule of Souveraine. The runner checkout # above is not recursive; clone it explicitly with the same LAN # token used for tuie, pinned to the submodule's recorded commit. UPOWER_URL=$(git config --file .gitmodules submodule."packaging/upower-souveraine".url) UPOWER_SHA=$(git ls-tree HEAD packaging/upower-souveraine | awk '{print $3}') git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ clone "$UPOWER_URL" "$GITHUB_WORKSPACE/src/upower" git -C "$GITHUB_WORKSPACE/src/upower" checkout "$UPOWER_SHA" SYSROOT="$HOME/aarch64-sysroot" # --- x86_64: native build on the archdev host --- ( cd "$GITHUB_WORKSPACE/src/upower" meson setup build-x86_64 \ --prefix=/usr --sysconfdir=/etc --localstatedir=/var \ -Dsystemdsystemunitdir=/usr/lib/systemd/system meson compile -C build-x86_64 DESTDIR="$GITHUB_WORKSPACE/src/upower/dest-x86_64" meson install -C build-x86_64 ) tar -C "$GITHUB_WORKSPACE/src/upower/dest-x86_64" \ -cf "$GITHUB_WORKSPACE/src/Souveraine/upower-tree-x86_64.tar" . # --- aarch64: cross build against the same sysroot souveraine uses --- CROSS="$GITHUB_WORKSPACE/src/upower/aarch64-cross.ini" cat > "$CROSS" <&2; exit 1; } tar -C "$GITHUB_WORKSPACE/src/upower/dest-aarch64" \ -cf "$GITHUB_WORKSPACE/src/Souveraine/upower-tree-aarch64.tar" . - name: Package and sign pacman repository run: | set -euo pipefail cd "$GITHUB_WORKSPACE/src/Souveraine" PKGVER="0.1.r$(git rev-list --count HEAD).g${GITHUB_SHA:0:12}" WORK="$GITHUB_WORKSPACE/pacman-package" REPO="$GITHUB_WORKSPACE/pacman-repo" ARCHIVE_KEY="3CD9E99E222C2A174986FC9AFF4949AA20C8E911" rm -rf "$WORK" "$REPO" mkdir -p "$WORK" "$REPO" for ARCH in aarch64 x86_64; do PKG_WORK="$WORK/$ARCH" ARCH_REPO="$REPO/$ARCH" mkdir -p "$PKG_WORK" "$ARCH_REPO" cp "souveraine-$ARCH" "$PKG_WORK/souveraine-binary" cp packaging/souveraine.service LICENSE "$PKG_WORK/" cp packaging/arch/PKGBUILD.prebuilt "$PKG_WORK/PKGBUILD" ( cd "$PKG_WORK" export SOUVERAINE_PKGVER="$PKGVER" export SOUVERAINE_PKGARCH="$ARCH" CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild ) PKG=$(find "$PKG_WORK" -maxdepth 1 -name 'souveraine-*.pkg.tar.zst' -print -quit) test -n "$PKG" bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine' cp "$PKG" "$ARCH_REPO/" gpg --batch --yes --local-user "$ARCHIVE_KEY" \ --detach-sign "$ARCH_REPO/$(basename "$PKG")" # UPower fork package — built from packaging/upower-souveraine # (git submodule of the Souveraine fork of upstream upower). Built # natively per-arch by the "Build UPower fork" step above into # upower-tree-$ARCH.tar, then folded into the SAME per-arch pacman # database so the phone installs upower-souveraine from the same # edge archive it already uses for souveraine. UPOWER_WORK="$WORK/upower-$ARCH" mkdir -p "$UPOWER_WORK" cp "upower-tree-$ARCH.tar" "$UPOWER_WORK/upower-tree.tar" cp packaging/upower-souveraine/dbus/org.freedesktop.UPower.Device.xml \ "$UPOWER_WORK/" 2>/dev/null \ || cp "$GITHUB_WORKSPACE/src/upower/dbus/org.freedesktop.UPower.Device.xml" \ "$UPOWER_WORK/" cp packaging/arch/PKGBUILD.upower.prebuilt "$UPOWER_WORK/PKGBUILD" ( cd "$UPOWER_WORK" export SOUVERAINE_PKGVER="$PKGVER" export SOUVERAINE_PKGARCH="$ARCH" CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild ) UPKG=$(find "$UPOWER_WORK" -maxdepth 1 -name 'upower-souveraine-*.pkg.tar.zst' -print -quit) test -n "$UPKG" bsdtar -tf "$UPKG" | grep -qx 'usr/lib/upowerd' cp "$UPKG" "$ARCH_REPO/" gpg --batch --yes --local-user "$ARCHIVE_KEY" \ --detach-sign "$ARCH_REPO/$(basename "$UPKG")" # Pacman databases are architecture-specific: one database cannot # retain two same-named, same-version packages for different # architectures. Keep each database and its assets distinct. DB="souveraine-${ARCH}" repo-add --include-sigs --sign --key "$ARCHIVE_KEY" \ "$ARCH_REPO/$DB.db.tar.zst" "$ARCH_REPO"/*.pkg.tar.zst # repo-add makes .db and .db.sig symlinks. Release assets cannot # preserve those, so replace both links with real files. rm -f "$ARCH_REPO/$DB.db" "$ARCH_REPO/$DB.db.sig" cp "$ARCH_REPO/$DB.db.tar.zst" "$ARCH_REPO/$DB.db" cp "$ARCH_REPO/$DB.db.tar.zst.sig" "$ARCH_REPO/$DB.db.sig" sha256sum "$ARCH_REPO"/*.pkg.tar.zst "$ARCH_REPO/$DB.db" \ > "$REPO/$DB-repo.sha256" done cp packaging/arch/souveraine-archive-key.asc "$REPO/" - name: Publish rolling edge prerelease run: | set -euo pipefail cd "$GITHUB_WORKSPACE/src/Souveraine" API="${GITHUB_SERVER_URL}/api/v1" AUTH="Authorization: token ${{ secrets.GITHUB_TOKEN }}" DESC=$(git describe --tags --always) OLD_ID=$(curl -s -H "$AUTH" "$API/repos/${GITHUB_REPOSITORY}/releases/tags/edge" \ | python3 -c "import json,sys try: print(json.load(sys.stdin).get('id','')) except Exception: print('')") if [ -n "$OLD_ID" ]; then curl -s -X DELETE -H "$AUTH" "$API/repos/${GITHUB_REPOSITORY}/releases/$OLD_ID" curl -s -X DELETE -H "$AUTH" "$API/repos/${GITHUB_REPOSITORY}/tags/edge" fi REL_ID=$(curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \ "$API/repos/${GITHUB_REPOSITORY}/releases" \ -d "{\"tag_name\":\"edge\",\"target_commitish\":\"${GITHUB_SHA}\",\"name\":\"edge (${DESC})\",\"body\":\"Rolling aarch64 build of public HEAD (${GITHUB_SHA}). Built on the archdev node, published only when CI is green.\",\"prerelease\":true}" \ | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])") for f in souveraine-aarch64 souveraine-x86_64 souveraine-binaries.sha256 \ $(find "$GITHUB_WORKSPACE/pacman-repo" -type f -print); do curl -sf -X POST -H "$AUTH" \ "$API/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets?name=$(basename "$f")" \ -F "attachment=@${f}" -o /dev/null done echo "published edge (${DESC}) with aarch64 package + pacman database" # Mirror the published branch only. primary (dev) never reaches Codeberg. sync-codeberg: runs-on: ubuntu-latest needs: [rust-test, no-ai-attribution, action-pins] if: github.event_name == 'push' && github.ref == 'refs/heads/public' steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - name: Push public to Codeberg run: | if [ -z "${{ secrets.CODEBERG_TOKEN }}" ]; then echo "[INFO] [sync] CODEBERG_TOKEN not set — skipping codeberg push" exit 0 fi git remote add codeberg-ci "https://${{ secrets.CODEBERG_TOKEN }}@codeberg.org/Fimeg/Souveraine.git" git push codeberg-ci public:public echo "Pushed public to codeberg.org/Fimeg/Souveraine"