feat(packaging): the trial's Linux column and the Linux packages (P11a steps 1 and 3)
The Linux half of the packaging trial, run on this machine and in Debian, Fedora and Arch containers, and the recipes it produced (docs/packaging.md holds the table and what it found): - mise.toml pins cargo-packager 0.11.8 and cargo-generate-rpm 0.21.1, now that `just package-linux` calls them. cargo-packager-updater is a library, not a tool, so it is not pinned (update.rs does the checking with minisign-verify, which is what the updater uses). - deploy/packaging/: packager.toml (.deb, AppImage, the pacman tarball and its PKGBUILD), the rpm table in crates/ledger-cli/Cargo.toml, the AUR's own PKGBUILD (repacks the release tarball), build-linux.sh, repo-indexes.sh (the apt and dnf indexes, unsigned until the signing step), local-release.sh (a throwaway-keyed feed to try `update`), test-linux-packages.sh (linux_packages_install_and_upgrade: install 0.1.0, open books as an ordinary user, upgrade to 0.1.1, check the host restarted onto the new file, the books' last journal line unchanged and `update` refuses; passes on Debian trixie, Fedora and Arch). A package marks itself package-managed with share/wanderbooks/install-method. - The icon is a placeholder square (the AppImage cannot be built without one). - .github/workflows/release.yml: on a version tag, build on ubuntu-24.04 (the oldest glibc the packages claim: a program linked on this machine needs GLIBC_2.43 and does not start on Debian 13), attach the packages to the release, and add them to the apt and dnf repositories on gh-pages. No signing and no macOS or Windows job yet. - The release profile strips symbols. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
4b5f44689d
commit
3058b5c229
20 files changed
+669
-18
No files matched your search
+4
-2
@@ -32,8 +32,10 @@ sudo install -D -m 0644 deploy/wanderbooks-server.service \
|
||||
```
|
||||
|
||||
The executable is the server-only build: no viewer, no window libraries.
|
||||
On a Linux package install (P11) this is the same binary and the package
|
||||
manager writes the file; until then, copy it as above.
|
||||
The Linux packages (`.deb`, `.rpm`, AUR; `just package-linux`, docs/packaging.md)
|
||||
carry the viewer build, which also serves: on a package install the package
|
||||
manager writes the file and `wanderbooks update` refuses. A server with no
|
||||
window libraries is still this server-only build, copied as above.
|
||||
|
||||
## 2. Make the data root, once
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Maintainer: Stephen Waits
|
||||
# The AUR package (P11a step 3, D-24, D-26): it repacks the tarball of the
|
||||
# release's files that GitHub Releases serves; nothing is built from source.
|
||||
# `updpkgsums` fills the checksum when a release is published.
|
||||
pkgname=wanderbooks-bin
|
||||
pkgver=0.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="Double-entry bookkeeping your agent keeps and you can read"
|
||||
arch=('x86_64')
|
||||
url="https://wanderbooks.ai"
|
||||
license=('MIT' 'Apache-2.0')
|
||||
depends=('gcc-libs' 'glibc')
|
||||
provides=('wanderbooks')
|
||||
conflicts=('wanderbooks')
|
||||
options=('!lto' '!strip')
|
||||
# The tarball holds usr/bin/wanderbooks, usr/share/applications/ and
|
||||
# usr/share/wanderbooks/install-method (which makes `wanderbooks update`
|
||||
# refuse and send the owner to pacman).
|
||||
source=("wanderbooks_${pkgver}_x86_64.tar.gz::https://github.com/swaits/wanderbooks/releases/download/v${pkgver}/wanderbooks_${pkgver}_x86_64.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
noextract=()
|
||||
|
||||
package() {
|
||||
cp -r "${srcdir}/usr" "${pkgdir}/usr"
|
||||
}
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
# The Linux packages (P11a step 3), from the release program already built
|
||||
# at target/release/wanderbooks (`cargo build --release -p ledger-cli`).
|
||||
#
|
||||
# build-linux.sh [VERSION] [FORMATS]
|
||||
#
|
||||
# Puts into target/packages/VERSION/: the .deb, the AppImage and the
|
||||
# pacman tarball with its PKGBUILD (cargo-packager), and the .rpm
|
||||
# (cargo-generate-rpm). VERSION defaults to the program's own; a different
|
||||
# one repackages the same program under that version, which is how the
|
||||
# install-and-upgrade test gets its second release. FORMATS is
|
||||
# cargo-packager's list (default deb,appimage,pacman); the .rpm is always
|
||||
# built. Run from the repository root.
|
||||
#
|
||||
# The program is target/release/wanderbooks, or the file WB_PROGRAM names:
|
||||
# a package must run on the oldest system it claims, and a program linked
|
||||
# on a newer one (this machine's glibc) fails there with "GLIBC_2.43 not
|
||||
# found". CI builds on ubuntu-24.04; `just test-linux-packages` builds in a
|
||||
# Debian trixie container.
|
||||
set -euo pipefail
|
||||
own=$(sed -n 's/^version = "\(.*\)"/\1/p' crates/ledger-cli/Cargo.toml | head -n1)
|
||||
version=${1:-$own}
|
||||
formats=${2:-deb,appimage,pacman}
|
||||
out="target/packages/$version"
|
||||
program=${WB_PROGRAM:-target/release/wanderbooks}
|
||||
test -x "$program" || { echo "build the release program first: cargo build --release -p ledger-cli" >&2; exit 1; }
|
||||
rm -rf "$out"; mkdir -p "$out"
|
||||
stage="target/packages/.stage-$version"
|
||||
rm -rf "$stage"; mkdir -p "$stage"
|
||||
cp "$program" "$stage/wanderbooks"
|
||||
config=$(mktemp --suffix=.toml)
|
||||
trap 'rm -f "$config"' EXIT
|
||||
# cargo-packager reads paths relative to the config file, which is in /tmp
|
||||
# here, so they are made absolute.
|
||||
sed "s/^version = \".*\"/version = \"$version\"/
|
||||
s#^out-dir = .*#out-dir = \"$PWD/$out\"#
|
||||
s#\"target/release\"#\"$PWD/$stage\"#
|
||||
s#\"deploy/packaging/#\"$PWD/deploy/packaging/#
|
||||
s#\"LICENSE-MIT\"#\"$PWD/LICENSE-MIT\"#" \
|
||||
deploy/packaging/packager.toml >"$config"
|
||||
# The AppImage tools are themselves AppImages; run them without FUSE.
|
||||
export APPIMAGE_EXTRACT_AND_RUN=1
|
||||
cargo packager --config "$config" --formats "$formats" --release
|
||||
assets='assets = [
|
||||
{ source = "'$PWD/$stage'/wanderbooks", dest = "/usr/bin/wanderbooks", mode = "0755" },
|
||||
{ source = "deploy/packaging/install-method-rpm", dest = "/usr/share/wanderbooks/install-method", mode = "0644" },
|
||||
{ source = "deploy/packaging/wanderbooks-static.desktop", dest = "/usr/share/applications/wanderbooks.desktop", mode = "0644" },
|
||||
]'
|
||||
cargo generate-rpm -p crates/ledger-cli --set-metadata "version=\"$version\"" --set-metadata "$assets" -o "$out/"
|
||||
rm -rf "$stage"
|
||||
ls -l "$out"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1 @@
|
||||
aur
|
||||
@@ -0,0 +1 @@
|
||||
deb
|
||||
@@ -0,0 +1 @@
|
||||
rpm
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# A release folder on this machine, for trying `wanderbooks update` without
|
||||
# a network or the owner's keys (P11a step 2; docs/packaging.md).
|
||||
#
|
||||
# local-release.sh FILE VERSION OUTDIR
|
||||
#
|
||||
# Makes a throwaway signing key in OUTDIR/key (never a release key), signs
|
||||
# FILE with it through cargo-packager, copies it beside a manifest.json in
|
||||
# the shape `wanderbooks update` reads, and leaves the key's public half in
|
||||
# OUTDIR/release.pub. Then:
|
||||
#
|
||||
# wanderbooks update --feed file://OUTDIR/manifest.json \
|
||||
# --trusted-key OUTDIR/release.pub
|
||||
set -euo pipefail
|
||||
file=${1:?usage: local-release.sh FILE VERSION OUTDIR}
|
||||
version=${2:?usage: local-release.sh FILE VERSION OUTDIR}
|
||||
out=${3:?usage: local-release.sh FILE VERSION OUTDIR}
|
||||
mkdir -p "$out"
|
||||
if [ ! -f "$out/key" ]; then
|
||||
cargo packager signer generate --path "$out/key" --password "" --ci >/dev/null
|
||||
cp "$out/key.pub" "$out/release.pub"
|
||||
fi
|
||||
case "$(uname -m)" in arm64) arch=aarch64 ;; *) arch=$(uname -m) ;; esac
|
||||
platform="$(uname -s | tr '[:upper:]' '[:lower:]')-$arch"
|
||||
name="wanderbooks-$version-$platform"
|
||||
cp "$file" "$out/$name"
|
||||
cargo packager signer sign "$out/$name" --private-key "$out/key" --password "" >/dev/null
|
||||
sha=$(sha256sum "$out/$name" | cut -d' ' -f1)
|
||||
sig=$(tr -d '\n' <"$out/$name.sig")
|
||||
printf '{"version":"%s","notes":"a local release","platforms":{"%s":{"url":"%s","sha256":"%s","signature":"%s"}}}\n' \
|
||||
"$version" "$platform" "$name" "$sha" "$sig" >"$out/manifest.json"
|
||||
echo "feed: file://$(cd "$out" && pwd)/manifest.json key: $out/release.pub"
|
||||
@@ -0,0 +1,36 @@
|
||||
# cargo-packager's description of the Linux bundles (P11a step 3; D-25).
|
||||
# `just package-linux` builds the release program and runs
|
||||
# cargo packager --config deploy/packaging/packager.toml
|
||||
# from the repository root. The .rpm is cargo-generate-rpm's, from the
|
||||
# [package.metadata.generate-rpm] table of crates/ledger-cli/Cargo.toml.
|
||||
name = "wanderbooks"
|
||||
product-name = "Wanderbooks"
|
||||
version = "0.1.0"
|
||||
identifier = "ai.wanderbooks.app"
|
||||
description = "Double-entry bookkeeping your agent keeps and you can read."
|
||||
long-description = "One program: the viewer, the books server, and the bridge that lets Claude Code or Codex keep the books."
|
||||
homepage = "https://wanderbooks.ai"
|
||||
authors = ["Stephen Waits"]
|
||||
license-file = "LICENSE-MIT"
|
||||
category = "Finance"
|
||||
binaries-dir = "target/release"
|
||||
out-dir = "target/packages"
|
||||
icons = ["deploy/packaging/icons/512x512.png"]
|
||||
formats = ["deb", "appimage", "pacman"]
|
||||
binaries = [{ path = "wanderbooks", main = true }]
|
||||
|
||||
[deb]
|
||||
section = "utils"
|
||||
# A deb marks itself package-managed: `wanderbooks update` refuses on it
|
||||
# and says to use apt (plan 3.9).
|
||||
desktop-template = "deploy/packaging/wanderbooks.desktop"
|
||||
depends = ["libc6", "libgcc-s1"]
|
||||
|
||||
[deb.files]
|
||||
"deploy/packaging/install-method-deb" = "usr/share/wanderbooks/install-method"
|
||||
|
||||
[pacman]
|
||||
depends = ["gcc-libs", "glibc"]
|
||||
|
||||
[pacman.files]
|
||||
"deploy/packaging/install-method-aur" = "usr/share/wanderbooks/install-method"
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# The apt and dnf repository indexes (P11a step 3), served from GitHub
|
||||
# Pages (D-26). Unsigned until step 5 gives them the release key.
|
||||
#
|
||||
# repo-indexes.sh apt DIR DIR/pool/*.deb -> DIR/dists/stable/...
|
||||
# repo-indexes.sh dnf DIR DIR/*.rpm -> DIR/repodata/
|
||||
#
|
||||
# Every release stays in the pool: a downgrade is the package manager's
|
||||
# (plan 3.9). apt needs apt-utils (`apt-ftparchive`), dnf needs
|
||||
# createrepo_c.
|
||||
set -euo pipefail
|
||||
kind=${1:?usage: repo-indexes.sh apt|dnf DIR}
|
||||
dir=${2:?usage: repo-indexes.sh apt|dnf DIR}
|
||||
case "$kind" in
|
||||
apt)
|
||||
cd "$dir"
|
||||
arch=binary-amd64
|
||||
mkdir -p "dists/stable/main/$arch"
|
||||
apt-ftparchive packages pool >"dists/stable/main/$arch/Packages"
|
||||
gzip -9 -kf "dists/stable/main/$arch/Packages"
|
||||
apt-ftparchive \
|
||||
-o APT::FTPArchive::Release::Origin=wanderbooks \
|
||||
-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.tmp
|
||||
mv dists/stable/Release.tmp dists/stable/Release
|
||||
;;
|
||||
dnf)
|
||||
createrepo_c --update "$dir"
|
||||
;;
|
||||
*)
|
||||
echo "usage: repo-indexes.sh apt|dnf DIR" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# linux_packages_install_and_upgrade (P11a step 3): in a clean Debian,
|
||||
# Fedora and Arch container, install release 0.1.0 from its package,
|
||||
# open the books as an ordinary user, upgrade to 0.1.1 as root, and check
|
||||
# that the books' head is unchanged, that the host the books were open
|
||||
# under restarted onto the new file, and that `wanderbooks update` refuses
|
||||
# (a package manager owns the program). The containers run with
|
||||
# seccomp=unconfined: the host refuses to run under a filter (host.rs
|
||||
# INVARIANT 2), and Docker's default profile is one. Run from the repository root after
|
||||
# `just test-linux-packages` built both releases (target/packages/).
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
old=0.1.0 new=0.1.1
|
||||
pk=$PWD/target/packages
|
||||
mount=(-v "$pk:/packages:ro")
|
||||
|
||||
# The steps every distro shares, run inside the container: `install` and
|
||||
# `upgrade` are shell snippets for that distro.
|
||||
common() { cat <<'INNER'
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
useradd -m tester
|
||||
as_tester() { su tester -c "$*"; }
|
||||
install_old
|
||||
test -x /usr/bin/wanderbooks
|
||||
grep -q . /usr/share/wanderbooks/install-method
|
||||
as_tester 'wanderbooks --help' >/dev/null 2>&1 || true
|
||||
as_tester 'wanderbooks setup' >/dev/null
|
||||
as_tester 'wanderbooks books create acme' >/dev/null
|
||||
head_of() { as_tester 'tail -n1 ~/.local/share/wanderbooks/books/acme/journal.jsonl | sha256sum'; }
|
||||
before=$(as_tester 'wanderbooks books list')
|
||||
head_before=$(head_of)
|
||||
echo "before: $before head $head_before"
|
||||
# The books open: a holder keeps the host and server running.
|
||||
as_tester 'nohup wanderbooks serve >/tmp/serve.log 2>&1 &'
|
||||
for _ in $(seq 50); do pgrep -u tester -x wanderbooks -a | grep -q ' host' && break; sleep 0.2; done
|
||||
host_before=$(pgrep -u tester -f 'wanderbooks host' | head -n1)
|
||||
test -n "$host_before"
|
||||
upgrade_new
|
||||
# The host notices the replaced file and restarts onto it.
|
||||
for _ in $(seq 150); do
|
||||
host_after=$(pgrep -u tester -f 'wanderbooks host' | head -n1 || true)
|
||||
[ -n "$host_after" ] && [ "$host_after" != "$host_before" ] && break
|
||||
sleep 0.2
|
||||
done
|
||||
test -n "$host_after" && test "$host_after" != "$host_before"
|
||||
! readlink "/proc/$host_after/exe" | grep -q deleted
|
||||
after=$(as_tester 'wanderbooks books list')
|
||||
echo "after: $after head $(head_of)"
|
||||
test "$before" = "$after"
|
||||
test "$head_before" = "$(head_of)"
|
||||
if as_tester 'wanderbooks update' >/tmp/update.out 2>&1; then echo "update did not refuse" >&2; exit 1; fi
|
||||
grep -q 'package manager' /tmp/update.out
|
||||
as_tester 'wanderbooks close' >/dev/null 2>&1 || true
|
||||
echo "OK"
|
||||
INNER
|
||||
}
|
||||
|
||||
run() { # name image install_old upgrade_new
|
||||
local name=$1 image=$2
|
||||
echo "== $name ($image)"
|
||||
docker run --rm --security-opt seccomp=unconfined -e DEBIAN_FRONTEND=noninteractive "${mount[@]}" "$image" bash -c "
|
||||
$3
|
||||
$4
|
||||
$(common)"
|
||||
}
|
||||
|
||||
# Debian and Fedora install from a repository built by repo-indexes.sh
|
||||
# (the one GitHub Pages serves), the new release added to it afterwards.
|
||||
mount+=(-v "$PWD/deploy/packaging:/tools:ro")
|
||||
run debian debian:trixie-slim \
|
||||
"apt-get update -qq && apt-get install -y -qq apt-utils procps >/dev/null
|
||||
mkdir -p /repo/apt/pool && cp /packages/$old/*.deb /repo/apt/pool/ && /tools/repo-indexes.sh apt /repo/apt
|
||||
echo 'deb [trusted=yes] file:///repo/apt stable main' >/etc/apt/sources.list.d/wanderbooks.list
|
||||
install_old() { apt-get update -qq && apt-get install -y -qq wanderbooks >/dev/null; }" \
|
||||
"upgrade_new() { cp /packages/$new/*.deb /repo/apt/pool/ && /tools/repo-indexes.sh apt /repo/apt && apt-get update -qq && apt-get install -y -qq --only-upgrade wanderbooks >/dev/null; }"
|
||||
|
||||
run fedora fedora:latest \
|
||||
"dnf install -y -q procps-ng util-linux createrepo_c >/dev/null
|
||||
mkdir -p /repo/dnf && cp /packages/$old/*.rpm /repo/dnf/ && /tools/repo-indexes.sh dnf /repo/dnf
|
||||
printf '[wanderbooks]\nname=wanderbooks\nbaseurl=file:///repo/dnf\ngpgcheck=0\n' >/etc/yum.repos.d/wanderbooks.repo
|
||||
install_old() { dnf install -y -q wanderbooks >/dev/null; }" \
|
||||
"upgrade_new() { cp /packages/$new/*.rpm /repo/dnf/ && /tools/repo-indexes.sh dnf /repo/dnf && dnf upgrade -y -q --refresh wanderbooks >/dev/null; }"
|
||||
|
||||
# Arch: the AUR's own PKGBUILD (deploy/packaging/aur), its source pointed at
|
||||
# the tarball just built instead of the release URL, makes the package as
|
||||
# an ordinary user.
|
||||
arch_build='build() { rm -rf /build/$1 && mkdir -p /build/$1 && cp /packages/$1/*.tar.gz /build/$1/ && cp /tools/aur/PKGBUILD /build/$1/PKGBUILD && sed -i "s/^pkgver=.*/pkgver=$1/; s|^source=.*|source=(wanderbooks_$1_x86_64.tar.gz)|" /build/$1/PKGBUILD && chown -R builder /build/$1 && su builder -c "cd /build/$1 && makepkg -f --nodeps >/dev/null 2>&1"; }'
|
||||
run arch archlinux:latest \
|
||||
"useradd -m builder; pacman -Sy --noconfirm --needed base-devel procps-ng >/dev/null 2>&1; $arch_build; install_old() { build $old; pacman -U --noconfirm /build/$old/*.pkg.tar.zst >/dev/null 2>&1; }" \
|
||||
"upgrade_new() { build $new; pacman -U --noconfirm /build/$new/*.pkg.tar.zst >/dev/null 2>&1; }"
|
||||
echo "all packages install and upgrade"
|
||||
@@ -0,0 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Categories=Office;Finance;
|
||||
Comment=Double-entry bookkeeping your agent keeps and you can read.
|
||||
Exec=wanderbooks
|
||||
Name=Wanderbooks
|
||||
Terminal=false
|
||||
Type=Application
|
||||
@@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Categories={{categories}}
|
||||
{{#if comment}}
|
||||
Comment={{comment}}
|
||||
{{/if}}
|
||||
Exec={{exec}}
|
||||
Icon={{icon}}
|
||||
Name={{name}}
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Reference in new issue
Block a user