split the public gates from the internal packaging half
The three portable jobs stay; the archdev packaging and archive publish move to packages.yml, which the public branch does not carry. The handoff document is operator archaeology and does not travel.
This commit is contained in:
parent
b8e76c4e9e
commit
76846feb61
4 changed files with 457 additions and 579 deletions
|
|
@ -136,448 +136,3 @@ jobs:
|
|||
# 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/primary'
|
||||
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
|
||||
|
||||
# sessiond is a separate bin behind the sessiond feature. It was
|
||||
# hand-copied to the phone's /usr/local/bin and owned by no package,
|
||||
# so it never received updates; build and ship it with the rest.
|
||||
./scripts/build-cross.sh --features sessiond --bin souveraine-sessiond
|
||||
SBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-sessiond"
|
||||
file "$SBIN" | grep -q aarch64 || { echo "sessiond is not aarch64" >&2; exit 1; }
|
||||
cp "$SBIN" souveraine-sessiond-aarch64
|
||||
|
||||
# The sensor reporter. Same story as sessiond: it replaces shell
|
||||
# scripts that shipped in rootfs-overlay/ and drifted per-device.
|
||||
./scripts/build-cross.sh --bin souveraine-sensord
|
||||
NBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-sensord"
|
||||
file "$NBIN" | grep -q aarch64 || { echo "sensord is not aarch64" >&2; exit 1; }
|
||||
cp "$NBIN" souveraine-sensord-aarch64
|
||||
|
||||
# Full USB KVM receiver: GUD display-in and the HID return surface.
|
||||
./scripts/build-cross.sh --features usb-kvm --bin souveraine-usb-kvm
|
||||
UBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-usb-kvm"
|
||||
file "$UBIN" | grep -q aarch64 || { echo "usb-kvm is not aarch64" >&2; exit 1; }
|
||||
cp "$UBIN" souveraine-usb-kvm-aarch64
|
||||
|
||||
# The wry host: self-hosted sites as apps, and the rig she wears.
|
||||
# Needs webkit2gtk on the build sysroot; it is already installed on
|
||||
# the phone (TASK-59 phase 1 measured against it).
|
||||
# Best effort: it links against the system webview, so it needs
|
||||
# webkit2gtk in the aarch64 sysroot. Until that is there this is the
|
||||
# one binary that may legitimately not build, and it must not stop the
|
||||
# ones that decide whether the phone locks.
|
||||
if ./scripts/build-cross.sh --features web --bin souveraine-web; then
|
||||
WBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-web"
|
||||
if file "$WBIN" | grep -q aarch64; then
|
||||
cp "$WBIN" souveraine-web-aarch64
|
||||
else
|
||||
echo "::warning::web host built but is not aarch64; skipping it"
|
||||
fi
|
||||
else
|
||||
echo "::warning::the wry host did not cross-build (webkit2gtk sysroot?); skipping it"
|
||||
fi
|
||||
|
||||
# secrets rail + machined: also hand-copied on the phone until now.
|
||||
./scripts/build-cross.sh --features secrets --bin souveraine-secrets
|
||||
./scripts/build-cross.sh --bin souveraine-machined
|
||||
./scripts/build-cross.sh --bin souveraine-admit
|
||||
for b in souveraine-secrets souveraine-machined souveraine-admit; do
|
||||
B="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/$b"
|
||||
file "$B" | grep -q aarch64 || { echo "$b is not aarch64" >&2; exit 1; }
|
||||
cp "$B" "$b-aarch64"
|
||||
done
|
||||
|
||||
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
|
||||
|
||||
cargo build --release --features secrets --bin souveraine-secrets
|
||||
cargo build --release --bin souveraine-machined
|
||||
cargo build --release --bin souveraine-admit
|
||||
for b in souveraine-secrets souveraine-machined souveraine-admit; do
|
||||
B="$CARGO_TARGET_DIR/release/$b"
|
||||
file "$B" | grep -q 'x86-64' || { echo "$b is not x86_64" >&2; exit 1; }
|
||||
cp "$B" "$b-x86_64"
|
||||
done
|
||||
|
||||
# sessiond for x86_64 as well. It was skipped here with a comment
|
||||
# calling it deliberate; it never was (Casey, 2026-08-02) — the
|
||||
# laptop hit lock-screen errors with it once and it stayed
|
||||
# phone-only by inertia. That inertia is now load-bearing in the
|
||||
# wrong direction: viewtop targets the laptop screen too
|
||||
# (VIEWTOP-AND-DENIAL.md), and panel power, the LockedHint report and
|
||||
# button reporting all assume the daemon is there — so no session
|
||||
# authority on x86 means no viewtop on the laptop.
|
||||
#
|
||||
# Building and packaging are not enabling. The package-owned user
|
||||
# unit ships on both architectures, but package installation neither
|
||||
# enables nor starts it; target activation remains separately tested.
|
||||
cargo build --release --features sessiond --bin souveraine-sessiond
|
||||
B="$CARGO_TARGET_DIR/release/souveraine-sessiond"
|
||||
file "$B" | grep -q 'x86-64' || { echo "sessiond is not x86_64" >&2; exit 1; }
|
||||
cp "$B" souveraine-sessiond-x86_64
|
||||
|
||||
# sensord for x86_64 too. It stopped being phone-only when charge
|
||||
# became a source: sessiond no longer probes the supplies itself, so
|
||||
# a machine without the reporter has no charge evidence at all — and
|
||||
# the laptop is a machine with a battery.
|
||||
cargo build --release --bin souveraine-sensord
|
||||
B="$CARGO_TARGET_DIR/release/souveraine-sensord"
|
||||
file "$B" | grep -q 'x86-64' || { echo "sensord is not x86_64" >&2; exit 1; }
|
||||
cp "$B" souveraine-sensord-x86_64
|
||||
|
||||
sha256sum souveraine-aarch64 souveraine-x86_64 \
|
||||
souveraine-sessiond-aarch64 souveraine-sensord-aarch64 \
|
||||
souveraine-usb-kvm-aarch64 \
|
||||
souveraine-secrets-aarch64 souveraine-secrets-x86_64 \
|
||||
souveraine-machined-aarch64 souveraine-machined-x86_64 \
|
||||
souveraine-admit-aarch64 souveraine-admit-x86_64 \
|
||||
souveraine-sessiond-x86_64 souveraine-sensord-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"
|
||||
# .cargo/aarch64-pkg-config reads SOUVERAINE_AARCH64_SYSROOT and falls
|
||||
# back to /usr/aarch64-linux-gnu, which has no glib. Steps do not share
|
||||
# environment, so exporting it in the binaries step does not reach here.
|
||||
export SOUVERAINE_AARCH64_SYSROOT="$SYSROOT"
|
||||
|
||||
# --- x86_64: native build on the archdev host ---
|
||||
(
|
||||
cd "$GITHUB_WORKSPACE/src/upower"
|
||||
# gtkdoc-scan is not installed on the runner, so gtk-doc=true fails
|
||||
# here too; man needs the same toolchain. Introspection is left ON
|
||||
# for the native build — it works, and costs nothing.
|
||||
meson setup build-x86_64 \
|
||||
--prefix=/usr --sysconfdir=/etc --localstatedir=/var \
|
||||
-Dsystemdsystemunitdir=/usr/lib/systemd/system \
|
||||
-Dgtk-doc=false -Dman=false
|
||||
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" <<EOF
|
||||
[binaries]
|
||||
c = 'aarch64-linux-gnu-gcc'
|
||||
cpp = 'aarch64-linux-gnu-g++'
|
||||
ar = 'aarch64-linux-gnu-ar'
|
||||
strip = 'aarch64-linux-gnu-strip'
|
||||
pkgconfig = '$PWD/.cargo/aarch64-pkg-config'
|
||||
# gobject-introspection is on by default and RUNS the aarch64 binary
|
||||
# it just built (g-ir-scanner). qemu needs -L to find the aarch64
|
||||
# loader; binfmt is registered on the Proxmox host so this works
|
||||
# inside the unprivileged archdev container.
|
||||
exe_wrapper = ['qemu-aarch64-static', '-L', '$SYSROOT']
|
||||
[built-in options]
|
||||
c_args = ['--sysroot=$SYSROOT']
|
||||
cpp_args = ['--sysroot=$SYSROOT']
|
||||
# -L as well as --sysroot: libc.so/libbsd.so in the sysroot are linker
|
||||
# SCRIPTS naming bare paths (libm, libmvec, -lmd), which ld resolves
|
||||
# against its own prefix and misses without an explicit search dir.
|
||||
c_link_args = ['--sysroot=$SYSROOT', '-L$SYSROOT/usr/lib']
|
||||
cpp_link_args = ['--sysroot=$SYSROOT', '-L$SYSROOT/usr/lib']
|
||||
[host_machine]
|
||||
system = 'linux'
|
||||
cpu_family = 'aarch64'
|
||||
cpu = 'aarch64'
|
||||
endian = 'little'
|
||||
EOF
|
||||
(
|
||||
cd "$GITHUB_WORKSPACE/src/upower"
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
PKG_CONFIG_LIBDIR="$SYSROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_SYSROOT_DIR="$SYSROOT" \
|
||||
# gtk-doc/man need host doc tooling, and introspection needs a
|
||||
# g-ir-scanner that can scan an aarch64 build (the host copy reads
|
||||
# its data files out of the sysroot and fails). None of the three
|
||||
# ship anything the phone uses — quickshell's UPower module is
|
||||
# native Qt, and the device carries no UPowerGlib typelib.
|
||||
meson setup build-aarch64 \
|
||||
--prefix=/usr --sysconfdir=/etc --localstatedir=/var \
|
||||
-Dsystemdsystemunitdir=/usr/lib/systemd/system \
|
||||
-Dgtk-doc=false -Dman=false -Dintrospection=disabled \
|
||||
--cross-file "$CROSS"
|
||||
meson compile -C build-aarch64
|
||||
DESTDIR="$GITHUB_WORKSPACE/src/upower/dest-aarch64" meson install -C build-aarch64
|
||||
)
|
||||
# Sanity: the aarch64 upowerd must actually be aarch64.
|
||||
# It installs to libexec/, not lib/ — the old path made this check
|
||||
# fail even on a good build.
|
||||
file "$GITHUB_WORKSPACE/src/upower/dest-aarch64/usr/libexec/upowerd" \
|
||||
| grep -q aarch64 || { echo "aarch64 upowerd is not aarch64" >&2; exit 1; }
|
||||
# Cross-install leak. upower asks pkg-config for the udev dir, and
|
||||
# with a sysroot in play that answer comes back sysroot-prefixed, so
|
||||
# the rules and hwdb install under $DESTDIR/home/casey/aarch64-sysroot/
|
||||
# instead of /usr/lib/udev. systemdsystemunitdir is passed explicitly
|
||||
# above for exactly this reason; the udev dir was missed. Measured on
|
||||
# the phone 2026-07-27: all four of 60-upower-battery.{rules,hwdb},
|
||||
# 95-upower-wup.rules and 95-upower-hid.hwdb were absent from
|
||||
# /usr/lib/udev and present only under the junk path — so the fork's
|
||||
# udev quirks had never once been applied on the device.
|
||||
# Fold any leaked tree back before the tar, then refuse to ship a
|
||||
# tree containing anything but the three prefixes upower legitimately
|
||||
# installs into: usr/, etc/ (UPower.conf) and var/ (var/lib/upower).
|
||||
# Leaving var/ out of that list is what failed run 1234.
|
||||
DEST="$GITHUB_WORKSPACE/src/upower/dest-aarch64"
|
||||
LEAK="$DEST$HOME/aarch64-sysroot"
|
||||
if [ -d "$LEAK" ]; then
|
||||
echo "cross install leaked into $HOME/aarch64-sysroot — relocating"
|
||||
cp -a "$LEAK/." "$DEST/"
|
||||
rm -rf "$LEAK"
|
||||
# Removing the sysroot subtree leaves its empty parents behind
|
||||
# ($DEST/home/casey), and an empty directory still ships AND still
|
||||
# trips the stray check below — which is how runs 1234 and 1248
|
||||
# failed. The PKGBUILD copy of this had the rmdir; this one did not.
|
||||
rmdir -p --ignore-fail-on-non-empty "$(dirname "$LEAK")" 2>/dev/null || true
|
||||
fi
|
||||
stray=$(find "$DEST" -mindepth 1 -maxdepth 1 \
|
||||
! -name usr ! -name etc ! -name var -printf '%f\n')
|
||||
if [ -n "$stray" ]; then
|
||||
echo "upower tree would ship outside usr/, etc/ and var/: $stray" >&2
|
||||
exit 1
|
||||
fi
|
||||
# And the rules must have landed where udev actually reads them.
|
||||
for f in usr/lib/udev/rules.d/60-upower-battery.rules \
|
||||
usr/lib/udev/hwdb.d/60-upower-battery.hwdb; do
|
||||
[ -e "$DEST/$f" ] || { echo "upower tree is missing $f" >&2; exit 1; }
|
||||
done
|
||||
tar -C "$GITHUB_WORKSPACE/src/upower/dest-aarch64" \
|
||||
-cf "$GITHUB_WORKSPACE/src/Souveraine/upower-tree-aarch64.tar" .
|
||||
|
||||
- name: Stage the shell surface for packaging
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "$GITHUB_WORKSPACE/src/Souveraine"
|
||||
# deploy.sh composes ~/.config/quickshell/souveraine from repo files
|
||||
# (symlinks) plus ii dirs borrowed from the ii-base pin. Run it in a
|
||||
# scratch HOME and tar the result dereferenced, so the package is
|
||||
# self-contained real files and never ships dangling links.
|
||||
SHELL_STAGE="$GITHUB_WORKSPACE/shell-stage"
|
||||
SHELL_TAR="$GITHUB_WORKSPACE/shell-package-input"
|
||||
rm -rf "$SHELL_STAGE" "$SHELL_TAR"
|
||||
mkdir -p "$SHELL_STAGE" "$SHELL_TAR"
|
||||
HOME="$SHELL_STAGE" ./surfaces/quickshell/deploy.sh
|
||||
test -L "$SHELL_STAGE/.config/quickshell/souveraine/shell.qml"
|
||||
tar -h --zstd -C "$SHELL_STAGE/.config/quickshell" -cf "$SHELL_TAR/souveraine-shell-config.tar.zst" souveraine
|
||||
tar -tf "$SHELL_TAR/souveraine-shell-config.tar.zst" | grep -qx 'souveraine/shell.qml'
|
||||
|
||||
- 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 "souveraine-secrets-$ARCH" "$PKG_WORK/souveraine-secrets-binary"
|
||||
cp "souveraine-machined-$ARCH" "$PKG_WORK/souveraine-machined-binary"
|
||||
cp "souveraine-admit-$ARCH" "$PKG_WORK/souveraine-admit-binary"
|
||||
cp "souveraine-sessiond-$ARCH" "$PKG_WORK/souveraine-sessiond-binary"
|
||||
cp "souveraine-sensord-$ARCH" "$PKG_WORK/souveraine-sensord-binary"
|
||||
cp packaging/souveraine.service packaging/souveraine-secrets.service \
|
||||
packaging/org.freedesktop.secrets.service \
|
||||
packaging/souveraine-machined.service \
|
||||
packaging/souveraine-sessiond.service \
|
||||
packaging/souveraine-sensord.service LICENSE "$PKG_WORK/"
|
||||
cp packaging/arch/souveraine.sysusers "$PKG_WORK/"
|
||||
# Phone-only reporters and surfaces remain aarch64-only.
|
||||
if [ "$ARCH" = aarch64 ]; then
|
||||
cp "souveraine-usb-kvm-$ARCH" "$PKG_WORK/souveraine-usb-kvm-binary"
|
||||
# The wry host. Guarded, and the PKGBUILD source line is removed
|
||||
# when it is absent: it needs webkit2gtk in the aarch64 sysroot,
|
||||
# and a missing avatar must not stop sessiond reaching the phone.
|
||||
# makepkg validates every entry in `source`, so leaving a name
|
||||
# there with no file fails the whole package.
|
||||
if [ -f "souveraine-web-$ARCH" ]; then
|
||||
cp "souveraine-web-$ARCH" "$PKG_WORK/souveraine-web-binary"
|
||||
fi
|
||||
fi
|
||||
cp packaging/souveraine-verify-trail "$PKG_WORK/"
|
||||
cp packaging/souveraine-button "$PKG_WORK/"
|
||||
cp packaging/souveraine-stepup.pam packaging/souveraine-sessiond.pam \
|
||||
packaging/org.souveraine.stepup.policy "$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'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-sessiond'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/lib/systemd/user/souveraine-sessiond.service'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-sensord'
|
||||
bsdtar -tf "$PKG" | grep -qx 'etc/pam.d/souveraine-sessiond'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/lib/sysusers.d/souveraine.conf'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-admit'
|
||||
if [ "$ARCH" = aarch64 ]; then
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-usb-kvm'
|
||||
fi
|
||||
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.
|
||||
# Version from UPSTREAM upower + the fork commit, NOT souveraine's
|
||||
# r-count. The hand-built package on the phone was 1.91.3+<sha>;
|
||||
# reusing souveraine's 0.1.rN here sorts LOWER, so pacman treats
|
||||
# the repo copy as a downgrade and -Syu silently skips it.
|
||||
UPOWER_VER=$(sed -nE "s/^ *version *: *'([^']+)'.*/\1/p" \
|
||||
"$GITHUB_WORKSPACE/src/upower/meson.build" | head -1)
|
||||
# '.r' not '+': a literal + in a release asset filename cannot be
|
||||
# fetched from Gitea at all — it is stored decoded as a space, and
|
||||
# both the raw + and %2B forms 404. Still sorts above the old
|
||||
# hand-built 1.91.3+<sha> because vercmp reads + as a separator too.
|
||||
UPOWER_PKGVER="${UPOWER_VER}.r$(git -C "$GITHUB_WORKSPACE/src/upower" rev-parse --short HEAD)"
|
||||
|
||||
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="$UPOWER_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"
|
||||
# libexec/, not lib/ — same path correction as the sanity check above.
|
||||
bsdtar -tf "$UPKG" | grep -qx 'usr/libexec/upowerd'
|
||||
cp "$UPKG" "$ARCH_REPO/"
|
||||
gpg --batch --yes --local-user "$ARCHIVE_KEY" \
|
||||
--detach-sign "$ARCH_REPO/$(basename "$UPKG")"
|
||||
|
||||
# Shell package — the composed quickshell config, arch=any, from
|
||||
# the staged tar. Same per-arch database as the binaries above.
|
||||
SHELL_WORK="$WORK/shell-$ARCH"
|
||||
mkdir -p "$SHELL_WORK"
|
||||
cp "$GITHUB_WORKSPACE/shell-package-input/souveraine-shell-config.tar.zst" "$SHELL_WORK/"
|
||||
cp packaging/arch/PKGBUILD.shell.prebuilt "$SHELL_WORK/PKGBUILD"
|
||||
cp LICENSE "$SHELL_WORK/"
|
||||
(
|
||||
cd "$SHELL_WORK"
|
||||
export SOUVERAINE_PKGVER="$PKGVER"
|
||||
CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild
|
||||
)
|
||||
SPKG=$(find "$SHELL_WORK" -maxdepth 1 -name 'souveraine-shell-*.pkg.tar.zst' -print -quit)
|
||||
test -n "$SPKG"
|
||||
bsdtar -tf "$SPKG" | grep -qx 'etc/xdg/quickshell/souveraine/shell.qml'
|
||||
bsdtar -tf "$SPKG" | grep -qx 'usr/share/licenses/souveraine-shell/LICENSE'
|
||||
cp "$SPKG" "$ARCH_REPO/"
|
||||
gpg --batch --yes --local-user "$ARCHIVE_KEY" \
|
||||
--detach-sign "$ARCH_REPO/$(basename "$SPKG")"
|
||||
|
||||
# The per-arch database is NOT built here. `edge` is a shared archive
|
||||
# with more than one producer in it, so the database has to be
|
||||
# read-modify-written against the live copy rather than generated
|
||||
# fresh — packaging/arch/publish-edge.sh owns that, under a lock.
|
||||
done
|
||||
cp packaging/arch/souveraine-archive-key.asc "$REPO/"
|
||||
|
||||
- name: Publish rolling edge prerelease
|
||||
env:
|
||||
# A PAT rather than the job's own GITHUB_TOKEN, so the identical
|
||||
# invocation works from a producer repo other than this one.
|
||||
EDGE_TOKEN: ${{ secrets.LOCAL_GITEA_TOKEN }}
|
||||
ARCHIVE_KEY: 3CD9E99E222C2A174986FC9AFF4949AA20C8E911
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "$GITHUB_WORKSPACE/src/Souveraine"
|
||||
# Additive: the release and tag are never deleted, the live databases
|
||||
# are merged into, and only this producer's own superseded packages
|
||||
# are removed. Before 2026-07-25 this step deleted and recreated the
|
||||
# release, which erased every other producer's packages on each push.
|
||||
export EDGE_TARGET_SHA="$GITHUB_SHA"
|
||||
export PRODUCER_VERSION="0.1.r$(git rev-list --count HEAD).g${GITHUB_SHA:0:12}"
|
||||
./packaging/arch/publish-edge.sh souveraine "$GITHUB_WORKSPACE/pacman-repo" \
|
||||
souveraine-aarch64 souveraine-x86_64 souveraine-binaries.sha256 \
|
||||
"$GITHUB_WORKSPACE/pacman-repo/souveraine-archive-key.asc"
|
||||
|
||||
- name: Mirror into the Gitea Arch registry (alongside edge)
|
||||
# Best-effort, never fails the publish: the registry sits alongside
|
||||
# release-assets until proven as the phone's source. A 409 means
|
||||
# (name, version) is already present immutably -- bump to republish.
|
||||
# Device-side auth is handled by souveraine-pacman-fetch; this only
|
||||
# uploads. Covers souveraine + upower-souveraine (both arches).
|
||||
env:
|
||||
EDGE_TOKEN: ${{ secrets.LOCAL_GITEA_TOKEN }}
|
||||
run: |
|
||||
set +e
|
||||
REG="$GITHUB_SERVER_URL/api/packages/${GITHUB_REPOSITORY%/*}/arch/edge"
|
||||
ok=0; skip=0; bad=0
|
||||
shopt -s nullglob
|
||||
for pkg in "$GITHUB_WORKSPACE"/pacman-repo/*/*.pkg.tar.zst; do
|
||||
code=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
|
||||
-H "Authorization: token $EDGE_TOKEN" --upload-file "$pkg" "$REG")
|
||||
case "$code" in
|
||||
200|201) echo "registry: published $(basename "$pkg")"; ok=$((ok+1));;
|
||||
409) echo "registry: present $(basename "$pkg") (bump to republish)"; skip=$((skip+1));;
|
||||
*) echo "::warning::registry $(basename "$pkg") -> HTTP $code"; bad=$((bad+1));;
|
||||
esac
|
||||
done
|
||||
echo "registry mirror: $ok new, $skip present, $bad failed"
|
||||
exit 0
|
||||
|
|
|
|||
456
.gitea/workflows/packages.yml
Normal file
456
.gitea/workflows/packages.yml
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
name: packages
|
||||
|
||||
# Internal only. Packaging, signing and archive publication run on the
|
||||
# build host where the aarch64 sysroot and the signing key live. This file
|
||||
# is not carried on the public branch; the gates in ci.yml are.
|
||||
on:
|
||||
push:
|
||||
branches: [primary, main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
aarch64-artifact:
|
||||
runs-on: archdev
|
||||
needs: [rust-test, no-ai-attribution, action-pins]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/primary'
|
||||
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
|
||||
|
||||
# sessiond is a separate bin behind the sessiond feature. It was
|
||||
# hand-copied to the phone's /usr/local/bin and owned by no package,
|
||||
# so it never received updates; build and ship it with the rest.
|
||||
./scripts/build-cross.sh --features sessiond --bin souveraine-sessiond
|
||||
SBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-sessiond"
|
||||
file "$SBIN" | grep -q aarch64 || { echo "sessiond is not aarch64" >&2; exit 1; }
|
||||
cp "$SBIN" souveraine-sessiond-aarch64
|
||||
|
||||
# The sensor reporter. Same story as sessiond: it replaces shell
|
||||
# scripts that shipped in rootfs-overlay/ and drifted per-device.
|
||||
./scripts/build-cross.sh --bin souveraine-sensord
|
||||
NBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-sensord"
|
||||
file "$NBIN" | grep -q aarch64 || { echo "sensord is not aarch64" >&2; exit 1; }
|
||||
cp "$NBIN" souveraine-sensord-aarch64
|
||||
|
||||
# Full USB KVM receiver: GUD display-in and the HID return surface.
|
||||
./scripts/build-cross.sh --features usb-kvm --bin souveraine-usb-kvm
|
||||
UBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-usb-kvm"
|
||||
file "$UBIN" | grep -q aarch64 || { echo "usb-kvm is not aarch64" >&2; exit 1; }
|
||||
cp "$UBIN" souveraine-usb-kvm-aarch64
|
||||
|
||||
# The wry host: self-hosted sites as apps, and the rig she wears.
|
||||
# Needs webkit2gtk on the build sysroot; it is already installed on
|
||||
# the phone (TASK-59 phase 1 measured against it).
|
||||
# Best effort: it links against the system webview, so it needs
|
||||
# webkit2gtk in the aarch64 sysroot. Until that is there this is the
|
||||
# one binary that may legitimately not build, and it must not stop the
|
||||
# ones that decide whether the phone locks.
|
||||
if ./scripts/build-cross.sh --features web --bin souveraine-web; then
|
||||
WBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-web"
|
||||
if file "$WBIN" | grep -q aarch64; then
|
||||
cp "$WBIN" souveraine-web-aarch64
|
||||
else
|
||||
echo "::warning::web host built but is not aarch64; skipping it"
|
||||
fi
|
||||
else
|
||||
echo "::warning::the wry host did not cross-build (webkit2gtk sysroot?); skipping it"
|
||||
fi
|
||||
|
||||
# secrets rail + machined: also hand-copied on the phone until now.
|
||||
./scripts/build-cross.sh --features secrets --bin souveraine-secrets
|
||||
./scripts/build-cross.sh --bin souveraine-machined
|
||||
./scripts/build-cross.sh --bin souveraine-admit
|
||||
for b in souveraine-secrets souveraine-machined souveraine-admit; do
|
||||
B="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/$b"
|
||||
file "$B" | grep -q aarch64 || { echo "$b is not aarch64" >&2; exit 1; }
|
||||
cp "$B" "$b-aarch64"
|
||||
done
|
||||
|
||||
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
|
||||
|
||||
cargo build --release --features secrets --bin souveraine-secrets
|
||||
cargo build --release --bin souveraine-machined
|
||||
cargo build --release --bin souveraine-admit
|
||||
for b in souveraine-secrets souveraine-machined souveraine-admit; do
|
||||
B="$CARGO_TARGET_DIR/release/$b"
|
||||
file "$B" | grep -q 'x86-64' || { echo "$b is not x86_64" >&2; exit 1; }
|
||||
cp "$B" "$b-x86_64"
|
||||
done
|
||||
|
||||
# sessiond for x86_64 as well. It was skipped here with a comment
|
||||
# calling it deliberate; it never was (Casey, 2026-08-02) — the
|
||||
# laptop hit lock-screen errors with it once and it stayed
|
||||
# phone-only by inertia. That inertia is now load-bearing in the
|
||||
# wrong direction: viewtop targets the laptop screen too
|
||||
# (VIEWTOP-AND-DENIAL.md), and panel power, the LockedHint report and
|
||||
# button reporting all assume the daemon is there — so no session
|
||||
# authority on x86 means no viewtop on the laptop.
|
||||
#
|
||||
# Building and packaging are not enabling. The package-owned user
|
||||
# unit ships on both architectures, but package installation neither
|
||||
# enables nor starts it; target activation remains separately tested.
|
||||
cargo build --release --features sessiond --bin souveraine-sessiond
|
||||
B="$CARGO_TARGET_DIR/release/souveraine-sessiond"
|
||||
file "$B" | grep -q 'x86-64' || { echo "sessiond is not x86_64" >&2; exit 1; }
|
||||
cp "$B" souveraine-sessiond-x86_64
|
||||
|
||||
# sensord for x86_64 too. It stopped being phone-only when charge
|
||||
# became a source: sessiond no longer probes the supplies itself, so
|
||||
# a machine without the reporter has no charge evidence at all — and
|
||||
# the laptop is a machine with a battery.
|
||||
cargo build --release --bin souveraine-sensord
|
||||
B="$CARGO_TARGET_DIR/release/souveraine-sensord"
|
||||
file "$B" | grep -q 'x86-64' || { echo "sensord is not x86_64" >&2; exit 1; }
|
||||
cp "$B" souveraine-sensord-x86_64
|
||||
|
||||
sha256sum souveraine-aarch64 souveraine-x86_64 \
|
||||
souveraine-sessiond-aarch64 souveraine-sensord-aarch64 \
|
||||
souveraine-usb-kvm-aarch64 \
|
||||
souveraine-secrets-aarch64 souveraine-secrets-x86_64 \
|
||||
souveraine-machined-aarch64 souveraine-machined-x86_64 \
|
||||
souveraine-admit-aarch64 souveraine-admit-x86_64 \
|
||||
souveraine-sessiond-x86_64 souveraine-sensord-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"
|
||||
# .cargo/aarch64-pkg-config reads SOUVERAINE_AARCH64_SYSROOT and falls
|
||||
# back to /usr/aarch64-linux-gnu, which has no glib. Steps do not share
|
||||
# environment, so exporting it in the binaries step does not reach here.
|
||||
export SOUVERAINE_AARCH64_SYSROOT="$SYSROOT"
|
||||
|
||||
# --- x86_64: native build on the archdev host ---
|
||||
(
|
||||
cd "$GITHUB_WORKSPACE/src/upower"
|
||||
# gtkdoc-scan is not installed on the runner, so gtk-doc=true fails
|
||||
# here too; man needs the same toolchain. Introspection is left ON
|
||||
# for the native build — it works, and costs nothing.
|
||||
meson setup build-x86_64 \
|
||||
--prefix=/usr --sysconfdir=/etc --localstatedir=/var \
|
||||
-Dsystemdsystemunitdir=/usr/lib/systemd/system \
|
||||
-Dgtk-doc=false -Dman=false
|
||||
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" <<EOF
|
||||
[binaries]
|
||||
c = 'aarch64-linux-gnu-gcc'
|
||||
cpp = 'aarch64-linux-gnu-g++'
|
||||
ar = 'aarch64-linux-gnu-ar'
|
||||
strip = 'aarch64-linux-gnu-strip'
|
||||
pkgconfig = '$PWD/.cargo/aarch64-pkg-config'
|
||||
# gobject-introspection is on by default and RUNS the aarch64 binary
|
||||
# it just built (g-ir-scanner). qemu needs -L to find the aarch64
|
||||
# loader; binfmt is registered on the Proxmox host so this works
|
||||
# inside the unprivileged archdev container.
|
||||
exe_wrapper = ['qemu-aarch64-static', '-L', '$SYSROOT']
|
||||
[built-in options]
|
||||
c_args = ['--sysroot=$SYSROOT']
|
||||
cpp_args = ['--sysroot=$SYSROOT']
|
||||
# -L as well as --sysroot: libc.so/libbsd.so in the sysroot are linker
|
||||
# SCRIPTS naming bare paths (libm, libmvec, -lmd), which ld resolves
|
||||
# against its own prefix and misses without an explicit search dir.
|
||||
c_link_args = ['--sysroot=$SYSROOT', '-L$SYSROOT/usr/lib']
|
||||
cpp_link_args = ['--sysroot=$SYSROOT', '-L$SYSROOT/usr/lib']
|
||||
[host_machine]
|
||||
system = 'linux'
|
||||
cpu_family = 'aarch64'
|
||||
cpu = 'aarch64'
|
||||
endian = 'little'
|
||||
EOF
|
||||
(
|
||||
cd "$GITHUB_WORKSPACE/src/upower"
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
PKG_CONFIG_LIBDIR="$SYSROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_SYSROOT_DIR="$SYSROOT" \
|
||||
# gtk-doc/man need host doc tooling, and introspection needs a
|
||||
# g-ir-scanner that can scan an aarch64 build (the host copy reads
|
||||
# its data files out of the sysroot and fails). None of the three
|
||||
# ship anything the phone uses — quickshell's UPower module is
|
||||
# native Qt, and the device carries no UPowerGlib typelib.
|
||||
meson setup build-aarch64 \
|
||||
--prefix=/usr --sysconfdir=/etc --localstatedir=/var \
|
||||
-Dsystemdsystemunitdir=/usr/lib/systemd/system \
|
||||
-Dgtk-doc=false -Dman=false -Dintrospection=disabled \
|
||||
--cross-file "$CROSS"
|
||||
meson compile -C build-aarch64
|
||||
DESTDIR="$GITHUB_WORKSPACE/src/upower/dest-aarch64" meson install -C build-aarch64
|
||||
)
|
||||
# Sanity: the aarch64 upowerd must actually be aarch64.
|
||||
# It installs to libexec/, not lib/ — the old path made this check
|
||||
# fail even on a good build.
|
||||
file "$GITHUB_WORKSPACE/src/upower/dest-aarch64/usr/libexec/upowerd" \
|
||||
| grep -q aarch64 || { echo "aarch64 upowerd is not aarch64" >&2; exit 1; }
|
||||
# Cross-install leak. upower asks pkg-config for the udev dir, and
|
||||
# with a sysroot in play that answer comes back sysroot-prefixed, so
|
||||
# the rules and hwdb install under $DESTDIR/home/casey/aarch64-sysroot/
|
||||
# instead of /usr/lib/udev. systemdsystemunitdir is passed explicitly
|
||||
# above for exactly this reason; the udev dir was missed. Measured on
|
||||
# the phone 2026-07-27: all four of 60-upower-battery.{rules,hwdb},
|
||||
# 95-upower-wup.rules and 95-upower-hid.hwdb were absent from
|
||||
# /usr/lib/udev and present only under the junk path — so the fork's
|
||||
# udev quirks had never once been applied on the device.
|
||||
# Fold any leaked tree back before the tar, then refuse to ship a
|
||||
# tree containing anything but the three prefixes upower legitimately
|
||||
# installs into: usr/, etc/ (UPower.conf) and var/ (var/lib/upower).
|
||||
# Leaving var/ out of that list is what failed run 1234.
|
||||
DEST="$GITHUB_WORKSPACE/src/upower/dest-aarch64"
|
||||
LEAK="$DEST$HOME/aarch64-sysroot"
|
||||
if [ -d "$LEAK" ]; then
|
||||
echo "cross install leaked into $HOME/aarch64-sysroot — relocating"
|
||||
cp -a "$LEAK/." "$DEST/"
|
||||
rm -rf "$LEAK"
|
||||
# Removing the sysroot subtree leaves its empty parents behind
|
||||
# ($DEST/home/casey), and an empty directory still ships AND still
|
||||
# trips the stray check below — which is how runs 1234 and 1248
|
||||
# failed. The PKGBUILD copy of this had the rmdir; this one did not.
|
||||
rmdir -p --ignore-fail-on-non-empty "$(dirname "$LEAK")" 2>/dev/null || true
|
||||
fi
|
||||
stray=$(find "$DEST" -mindepth 1 -maxdepth 1 \
|
||||
! -name usr ! -name etc ! -name var -printf '%f\n')
|
||||
if [ -n "$stray" ]; then
|
||||
echo "upower tree would ship outside usr/, etc/ and var/: $stray" >&2
|
||||
exit 1
|
||||
fi
|
||||
# And the rules must have landed where udev actually reads them.
|
||||
for f in usr/lib/udev/rules.d/60-upower-battery.rules \
|
||||
usr/lib/udev/hwdb.d/60-upower-battery.hwdb; do
|
||||
[ -e "$DEST/$f" ] || { echo "upower tree is missing $f" >&2; exit 1; }
|
||||
done
|
||||
tar -C "$GITHUB_WORKSPACE/src/upower/dest-aarch64" \
|
||||
-cf "$GITHUB_WORKSPACE/src/Souveraine/upower-tree-aarch64.tar" .
|
||||
|
||||
- name: Stage the shell surface for packaging
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "$GITHUB_WORKSPACE/src/Souveraine"
|
||||
# deploy.sh composes ~/.config/quickshell/souveraine from repo files
|
||||
# (symlinks) plus ii dirs borrowed from the ii-base pin. Run it in a
|
||||
# scratch HOME and tar the result dereferenced, so the package is
|
||||
# self-contained real files and never ships dangling links.
|
||||
SHELL_STAGE="$GITHUB_WORKSPACE/shell-stage"
|
||||
SHELL_TAR="$GITHUB_WORKSPACE/shell-package-input"
|
||||
rm -rf "$SHELL_STAGE" "$SHELL_TAR"
|
||||
mkdir -p "$SHELL_STAGE" "$SHELL_TAR"
|
||||
HOME="$SHELL_STAGE" ./surfaces/quickshell/deploy.sh
|
||||
test -L "$SHELL_STAGE/.config/quickshell/souveraine/shell.qml"
|
||||
tar -h --zstd -C "$SHELL_STAGE/.config/quickshell" -cf "$SHELL_TAR/souveraine-shell-config.tar.zst" souveraine
|
||||
tar -tf "$SHELL_TAR/souveraine-shell-config.tar.zst" | grep -qx 'souveraine/shell.qml'
|
||||
|
||||
- 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 "souveraine-secrets-$ARCH" "$PKG_WORK/souveraine-secrets-binary"
|
||||
cp "souveraine-machined-$ARCH" "$PKG_WORK/souveraine-machined-binary"
|
||||
cp "souveraine-admit-$ARCH" "$PKG_WORK/souveraine-admit-binary"
|
||||
cp "souveraine-sessiond-$ARCH" "$PKG_WORK/souveraine-sessiond-binary"
|
||||
cp "souveraine-sensord-$ARCH" "$PKG_WORK/souveraine-sensord-binary"
|
||||
cp packaging/souveraine.service packaging/souveraine-secrets.service \
|
||||
packaging/org.freedesktop.secrets.service \
|
||||
packaging/souveraine-machined.service \
|
||||
packaging/souveraine-sessiond.service \
|
||||
packaging/souveraine-sensord.service LICENSE "$PKG_WORK/"
|
||||
cp packaging/arch/souveraine.sysusers "$PKG_WORK/"
|
||||
# Phone-only reporters and surfaces remain aarch64-only.
|
||||
if [ "$ARCH" = aarch64 ]; then
|
||||
cp "souveraine-usb-kvm-$ARCH" "$PKG_WORK/souveraine-usb-kvm-binary"
|
||||
# The wry host. Guarded, and the PKGBUILD source line is removed
|
||||
# when it is absent: it needs webkit2gtk in the aarch64 sysroot,
|
||||
# and a missing avatar must not stop sessiond reaching the phone.
|
||||
# makepkg validates every entry in `source`, so leaving a name
|
||||
# there with no file fails the whole package.
|
||||
if [ -f "souveraine-web-$ARCH" ]; then
|
||||
cp "souveraine-web-$ARCH" "$PKG_WORK/souveraine-web-binary"
|
||||
fi
|
||||
fi
|
||||
cp packaging/souveraine-verify-trail "$PKG_WORK/"
|
||||
cp packaging/souveraine-button "$PKG_WORK/"
|
||||
cp packaging/souveraine-stepup.pam packaging/souveraine-sessiond.pam \
|
||||
packaging/org.souveraine.stepup.policy "$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'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-sessiond'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/lib/systemd/user/souveraine-sessiond.service'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-sensord'
|
||||
bsdtar -tf "$PKG" | grep -qx 'etc/pam.d/souveraine-sessiond'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/lib/sysusers.d/souveraine.conf'
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-admit'
|
||||
if [ "$ARCH" = aarch64 ]; then
|
||||
bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-usb-kvm'
|
||||
fi
|
||||
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.
|
||||
# Version from UPSTREAM upower + the fork commit, NOT souveraine's
|
||||
# r-count. The hand-built package on the phone was 1.91.3+<sha>;
|
||||
# reusing souveraine's 0.1.rN here sorts LOWER, so pacman treats
|
||||
# the repo copy as a downgrade and -Syu silently skips it.
|
||||
UPOWER_VER=$(sed -nE "s/^ *version *: *'([^']+)'.*/\1/p" \
|
||||
"$GITHUB_WORKSPACE/src/upower/meson.build" | head -1)
|
||||
# '.r' not '+': a literal + in a release asset filename cannot be
|
||||
# fetched from Gitea at all — it is stored decoded as a space, and
|
||||
# both the raw + and %2B forms 404. Still sorts above the old
|
||||
# hand-built 1.91.3+<sha> because vercmp reads + as a separator too.
|
||||
UPOWER_PKGVER="${UPOWER_VER}.r$(git -C "$GITHUB_WORKSPACE/src/upower" rev-parse --short HEAD)"
|
||||
|
||||
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="$UPOWER_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"
|
||||
# libexec/, not lib/ — same path correction as the sanity check above.
|
||||
bsdtar -tf "$UPKG" | grep -qx 'usr/libexec/upowerd'
|
||||
cp "$UPKG" "$ARCH_REPO/"
|
||||
gpg --batch --yes --local-user "$ARCHIVE_KEY" \
|
||||
--detach-sign "$ARCH_REPO/$(basename "$UPKG")"
|
||||
|
||||
# Shell package — the composed quickshell config, arch=any, from
|
||||
# the staged tar. Same per-arch database as the binaries above.
|
||||
SHELL_WORK="$WORK/shell-$ARCH"
|
||||
mkdir -p "$SHELL_WORK"
|
||||
cp "$GITHUB_WORKSPACE/shell-package-input/souveraine-shell-config.tar.zst" "$SHELL_WORK/"
|
||||
cp packaging/arch/PKGBUILD.shell.prebuilt "$SHELL_WORK/PKGBUILD"
|
||||
cp LICENSE "$SHELL_WORK/"
|
||||
(
|
||||
cd "$SHELL_WORK"
|
||||
export SOUVERAINE_PKGVER="$PKGVER"
|
||||
CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild
|
||||
)
|
||||
SPKG=$(find "$SHELL_WORK" -maxdepth 1 -name 'souveraine-shell-*.pkg.tar.zst' -print -quit)
|
||||
test -n "$SPKG"
|
||||
bsdtar -tf "$SPKG" | grep -qx 'etc/xdg/quickshell/souveraine/shell.qml'
|
||||
bsdtar -tf "$SPKG" | grep -qx 'usr/share/licenses/souveraine-shell/LICENSE'
|
||||
cp "$SPKG" "$ARCH_REPO/"
|
||||
gpg --batch --yes --local-user "$ARCHIVE_KEY" \
|
||||
--detach-sign "$ARCH_REPO/$(basename "$SPKG")"
|
||||
|
||||
# The per-arch database is NOT built here. `edge` is a shared archive
|
||||
# with more than one producer in it, so the database has to be
|
||||
# read-modify-written against the live copy rather than generated
|
||||
# fresh — packaging/arch/publish-edge.sh owns that, under a lock.
|
||||
done
|
||||
cp packaging/arch/souveraine-archive-key.asc "$REPO/"
|
||||
|
||||
- name: Publish rolling edge prerelease
|
||||
env:
|
||||
# A PAT rather than the job's own GITHUB_TOKEN, so the identical
|
||||
# invocation works from a producer repo other than this one.
|
||||
EDGE_TOKEN: ${{ secrets.LOCAL_GITEA_TOKEN }}
|
||||
ARCHIVE_KEY: 3CD9E99E222C2A174986FC9AFF4949AA20C8E911
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "$GITHUB_WORKSPACE/src/Souveraine"
|
||||
# Additive: the release and tag are never deleted, the live databases
|
||||
# are merged into, and only this producer's own superseded packages
|
||||
# are removed. Before 2026-07-25 this step deleted and recreated the
|
||||
# release, which erased every other producer's packages on each push.
|
||||
export EDGE_TARGET_SHA="$GITHUB_SHA"
|
||||
export PRODUCER_VERSION="0.1.r$(git rev-list --count HEAD).g${GITHUB_SHA:0:12}"
|
||||
./packaging/arch/publish-edge.sh souveraine "$GITHUB_WORKSPACE/pacman-repo" \
|
||||
souveraine-aarch64 souveraine-x86_64 souveraine-binaries.sha256 \
|
||||
"$GITHUB_WORKSPACE/pacman-repo/souveraine-archive-key.asc"
|
||||
|
||||
- name: Mirror into the Gitea Arch registry (alongside edge)
|
||||
# Best-effort, never fails the publish: the registry sits alongside
|
||||
# release-assets until proven as the phone's source. A 409 means
|
||||
# (name, version) is already present immutably -- bump to republish.
|
||||
# Device-side auth is handled by souveraine-pacman-fetch; this only
|
||||
# uploads. Covers souveraine + upower-souveraine (both arches).
|
||||
env:
|
||||
EDGE_TOKEN: ${{ secrets.LOCAL_GITEA_TOKEN }}
|
||||
run: |
|
||||
set +e
|
||||
REG="$GITHUB_SERVER_URL/api/packages/${GITHUB_REPOSITORY%/*}/arch/edge"
|
||||
ok=0; skip=0; bad=0
|
||||
shopt -s nullglob
|
||||
for pkg in "$GITHUB_WORKSPACE"/pacman-repo/*/*.pkg.tar.zst; do
|
||||
code=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
|
||||
-H "Authorization: token $EDGE_TOKEN" --upload-file "$pkg" "$REG")
|
||||
case "$code" in
|
||||
200|201) echo "registry: published $(basename "$pkg")"; ok=$((ok+1));;
|
||||
409) echo "registry: present $(basename "$pkg") (bump to republish)"; skip=$((skip+1));;
|
||||
*) echo "::warning::registry $(basename "$pkg") -> HTTP $code"; bad=$((bad+1));;
|
||||
esac
|
||||
done
|
||||
echo "registry mirror: $ok new, $skip present, $bad failed"
|
||||
exit 0
|
||||
|
|
@ -21,7 +21,7 @@ The personal agreement is `~/.codex/AGENTS.md`. The cross-repo map is
|
|||
- Gitea Actions is the reproducible build/test/package path. Push the scoped
|
||||
change and let CI build it.
|
||||
- Keep local checks small and targeted. Do not replace the pipeline with a
|
||||
direct ArchDev build, an offline build, or a hand-copied artifact.
|
||||
a direct build on the build host, an offline build, or a hand-copied artifact.
|
||||
- Runner access is diagnostic. A green result matters only if the relevant job
|
||||
ran and, when required, published the expected package.
|
||||
- Phone delivery is through package ownership and pacman. A symlink or copied
|
||||
|
|
|
|||
133
HANDOFF.md
133
HANDOFF.md
|
|
@ -1,133 +0,0 @@
|
|||
# Handoff — 2026-08-07, the day her face went on the glass
|
||||
|
||||
Everything below is pushed. Nothing is half-applied. The one thing that is
|
||||
**not** durable is the phone's binaries — see "The phone is running test
|
||||
installs" at the end, and fix that first if a `-Syu` is coming.
|
||||
|
||||
## Read these before touching any of it
|
||||
|
||||
`docs/tasks/59-her-face-on-the-glass.md` (rewritten today — the five bugs in
|
||||
the way, and why each presented as something else), `docs/tasks/60-the-
|
||||
multitasking-view.md`, `docs/DUMP-bugs-2026-08-06.md` (items 3/4/5 closed, 7
|
||||
retracted, 8/9/10 new).
|
||||
|
||||
## What is on the phone and working
|
||||
|
||||
She draws. She is **furniture on home**, not an app: no zone, no dock entry, no
|
||||
overview card, no frame, her own size, hung from the top of the zone with the
|
||||
room below deliberately left free for what she shares. She takes touch only on
|
||||
her own silhouette, so the home screen underneath still works. She follows a
|
||||
finger **anywhere on the panel**. Holding her records and sends through the
|
||||
existing STT into the sidebar. Double-tapping her dismisses her; locking the
|
||||
phone sends her away and she stays away. The clock and the quote fade out when
|
||||
she is up.
|
||||
|
||||
Also landed: multitasking cards draw; zones mint correctly; the dial records
|
||||
the screen on a hold; layer-surface popups exist at all; move mode can be left;
|
||||
the dock is bigger and its scale is a setting.
|
||||
|
||||
## The five that cost the most, kept short
|
||||
|
||||
1. **The rig drew nothing because of one missing DOM element.** `live2d.js` is
|
||||
the reference's whole bundle, not just Cubism, and it reads `#live_talk`
|
||||
every frame. Missing, it throws on the first update — after the model and
|
||||
all four textures have loaded. Everything else reads healthy.
|
||||
2. **`--app-id` was a no-op.** `set_prgname` after GTK made the surface. The
|
||||
compositor saw `souveraine-web`; window rules could only match the title.
|
||||
3. **Claim the GL context *after* sizing the canvas.** Before it, the context is
|
||||
born 300x150, `gl.viewport` stays there, and she renders as a thumbnail in
|
||||
the bottom-left corner of a full-size buffer.
|
||||
4. **Multitasking cards were `width: 0`** — a Column↔loader binding cycle with
|
||||
no search widget to break it. Every other signal read healthy.
|
||||
5. **`Feeds::publish` dropped subscribers whose channel was `Full`**, so a fast
|
||||
stream unsubscribed its own reader inside the first burst. An external probe
|
||||
that read faster kept working, which is what made it look like a shell bug.
|
||||
|
||||
## Traps this session re-learned the hard way
|
||||
|
||||
- **`pgrep -f` / `pkill -f` match your own command string.** Killed the SSH
|
||||
session and a kitty more than once. Bracket the pattern *and* make sure the
|
||||
unbracketed form does not also appear later in the same command.
|
||||
- **A duplicate signal handler fails the whole QML module** and takes the shell
|
||||
into a restart loop. `onCanceled` already existed on the rail; adding a second
|
||||
is `Property value set multiple times`.
|
||||
- **Quickshell's `Socket` emits `onConnectionStateChanged`,** not
|
||||
`onConnectedChanged`. A handler for the wrong one is silent.
|
||||
- **Check `locked` in viewtop's `state` before diagnosing a capture failure.**
|
||||
A session left locked by a test refuses every capture, correctly, and it
|
||||
looks exactly like a broken capture path.
|
||||
- **`GlobalStates.screenLocked` drifts.** `session lock` answered
|
||||
`already-locked` on a phone in use while logind said `LockedHint=no`. Gate on
|
||||
`screenLockSecure`, the compositor's ack.
|
||||
- **A greetd restart leaves the phone locked** and only the PIN opens it —
|
||||
`unlock` is deliberately not a verb anything but a human can call. Do not
|
||||
leave it locked and walk away.
|
||||
- **`scene` intent `drop`** clears a stuck window grab in one call. It is the
|
||||
unstick for a phone frozen in move mode. The intent is a bare string
|
||||
(`{"op":"scene","intent":"drop"}`), not a map.
|
||||
|
||||
## What is open, honestly
|
||||
|
||||
- **The pill strands a pose** (DUMP 8). My cancel-path theory was **wrong** —
|
||||
`onCanceled` already handles it. Cause unfound. Casey wants no backing-off
|
||||
state at all: gate the partial slide on speed and length, then commit.
|
||||
- **The swipe into multitasking is awkward** (TASK-60). Two named suspects, both
|
||||
needing to be watched in motion.
|
||||
- **Side panels glitch on the way out** (DUMP 9) — keyboard half fixed, visual
|
||||
glitch uncharacterised. **The terminal cannot keep its keyboard** (DUMP 10);
|
||||
the two are mirror images and probably one focus-handoff bug.
|
||||
- **Dock appstacks** — the popup fix is committed and **unverified on hardware**.
|
||||
Layer-surface popups were never composited *or* hit-tested, which is why
|
||||
tapping a stack did nothing. Needs a finger on it.
|
||||
- **Holding her to talk is the wrong affordance.** Casey: *"holding onto her has
|
||||
a different connotation."* It works; it should become explicit controls.
|
||||
- **Her states are wired but thin.** Nine postures collapse onto two levers —
|
||||
`#live_talk` and the bundle's head/body hit test. That is all the rig has.
|
||||
- **VPN through the state machine** — Casey's call: its own session, and it
|
||||
probably wants a settings surface for SSIDs. Do not start it as a side quest,
|
||||
and remember §12: an `Action`, never a daemon (`wiuf-vpn-gate` was 652
|
||||
recycles in 90 minutes).
|
||||
|
||||
## The rig is the next big thing, and it is a pipeline
|
||||
|
||||
Not a repaint. Strip the parts out by name with their exact UV footprints,
|
||||
rebuild by compositing them back at those footprints, and let agents swap
|
||||
components the way they swap outfits — the rebuild half is what makes swapping
|
||||
real. V1 of the *look* is the face only: shrink the eyes, slim the face with
|
||||
transparency, because Histoire reads as a little girl and not a woman. Wings go
|
||||
holographic. Outfit is fine for now.
|
||||
|
||||
**Ceiling:** parts are paint, silhouette is mesh. Twin-tails and the pose need a
|
||||
new rig, and that is a Cubism 2 → 4/5 change — a different loader, which also
|
||||
replaces the character layer that reads `#live_talk`.
|
||||
|
||||
Assets: `~/Projects/live2d-editing` (4 × 1024², palette-indexed, convert to RGBA
|
||||
first). Style reference: `~/Projects/AniAvatar`.
|
||||
|
||||
## The phone is running test installs
|
||||
|
||||
`/usr/bin/viewtop` and `/usr/bin/souveraine-web` are **hand-installed**, and the
|
||||
QML lives in `/home/casey/souveraine-surfaces`. Originals parked at
|
||||
`/usr/local/bin/viewtop.prev-r88-package` and `souveraine-web.prev-r363`. A
|
||||
`pacman -Syu` reverts the binaries. This is `souveraine/CLAUDE.md` failure mode
|
||||
#4 and it should be closed by building the packages, not by copying again.
|
||||
|
||||
Build loop that works: edit on the laptop → `scp` the changed file to archdev
|
||||
(`/home/casey/souveraine-viewtop`, `/home/casey/souveraine`) → **checksum it**,
|
||||
because another session may be in the same tree → `VIEWTOP_AARCH64_SYSROOT=
|
||||
/home/casey/aarch64-sysroot ./scripts/build-cross.sh --release` → base64 down,
|
||||
scp up, `install -m755`. `~/souveraine` needs `~/tuie` beside it (symlinked to
|
||||
`~/Projects/tuie`).
|
||||
|
||||
## Cross-repo state
|
||||
|
||||
- `souveraine` → `primary` @ `630d691`
|
||||
- `souveraine-viewtop` → `main` @ `62ec16b`
|
||||
- `SouveraineOS` → `main` @ (this commit)
|
||||
- smithay fork → `souveraine` @ `645961c7`, pinned; the `colour-transform`
|
||||
branch was folded in and **deleted**, and the local `[patch]` that pointed at
|
||||
`/home/casey/smithay-colour` is gone — the tree builds from the fork now.
|
||||
- Dead branches removed: `public`, `main`, `detached-reinit-backup`,
|
||||
`experimental/session-trust-phase1` (souveraine), `public-main`,
|
||||
`codeberg-publish-tmp` (Pixel3Arch). Nothing of value was stranded on any of
|
||||
them — all merges and dead codeberg snapshots. `archive/*` refs kept.
|
||||
Loading…
Reference in a new issue