From 3058b5c229d267fb7298df0ee5ddce3582e50639 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 29 Oct 2026 15:11:47 -0600 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp --- .github/workflows/release.yml | 77 ++++++++++ README.md | 9 ++ deploy/README.md | 6 +- deploy/packaging/aur/PKGBUILD | 25 ++++ deploy/packaging/build-linux.sh | 51 +++++++ deploy/packaging/icons/512x512.png | Bin 0 -> 3463 bytes deploy/packaging/install-method-aur | 1 + deploy/packaging/install-method-deb | 1 + deploy/packaging/install-method-rpm | 1 + deploy/packaging/local-release.sh | 32 +++++ deploy/packaging/packager.toml | 36 +++++ deploy/packaging/repo-indexes.sh | 37 +++++ deploy/packaging/test-linux-packages.sh | 92 ++++++++++++ deploy/packaging/wanderbooks-static.desktop | 7 + deploy/packaging/wanderbooks.desktop | 10 ++ docs/architecture.md | 42 +++++- docs/packaging.md | 148 ++++++++++++++++++-- docs/testing.md | 76 ++++++++++ justfile | 32 +++++ mise.toml | 4 + 20 files changed, 669 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 deploy/packaging/aur/PKGBUILD create mode 100755 deploy/packaging/build-linux.sh create mode 100644 deploy/packaging/icons/512x512.png create mode 100644 deploy/packaging/install-method-aur create mode 100644 deploy/packaging/install-method-deb create mode 100644 deploy/packaging/install-method-rpm create mode 100755 deploy/packaging/local-release.sh create mode 100644 deploy/packaging/packager.toml create mode 100755 deploy/packaging/repo-indexes.sh create mode 100755 deploy/packaging/test-linux-packages.sh create mode 100644 deploy/packaging/wanderbooks-static.desktop create mode 100644 deploy/packaging/wanderbooks.desktop diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..231444f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,77 @@ +# Releases (plan P11a, D-24, D-26): on a version tag, build the Linux +# packages on the oldest system they claim (ubuntu-24.04, glibc 2.39; a +# program linked on a newer one does not start on an older one), attach +# them to the GitHub release, and add them to the apt and dnf repositories +# on the gh-pages branch (GitHub Pages serves them). Unsigned: step 5 of +# the unit (the release key, Q-4) signs the manifest and the indexes, and +# step 6 adds the macOS and Windows jobs. The release holds no key today. +name: release + +on: + push: + tags: ["v*"] + workflow_dispatch: + +permissions: + contents: write + +jobs: + linux: + runs-on: ubuntu-24.04 + # Same disk discipline as check.yml: no incremental caches. + env: + CARGO_INCREMENTAL: "0" + steps: + - uses: actions/checkout@v4 + - name: Room for the build (the image ships toolchains we never use) + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /opt/hostedtoolcache/CodeQL /usr/local/.ghcup + df -h / | tail -1 + - uses: jdx/mise-action@v2 + with: + # Only what packaging needs, not the proof and fuzz tools. + install_args: "rust just cargo:cargo-packager cargo:cargo-generate-rpm" + - name: GUI build dependencies + run: sudo apt-get update && sudo apt-get install -y libxkbcommon-dev libgtk-3-dev apt-utils createrepo-c + - name: The tag is the program's version + id: version + run: | + own=$(sed -n 's/^version = "\(.*\)"/\1/p' crates/ledger-cli/Cargo.toml | head -n1) + tag="${GITHUB_REF_NAME#v}" + if [ "${GITHUB_REF_TYPE}" = tag ] && [ "$tag" != "$own" ]; then + echo "tag $GITHUB_REF_NAME is not version $own" >&2 + exit 1 + fi + echo "version=$own" >> "$GITHUB_OUTPUT" + - name: Build the packages + run: just package-linux ${{ steps.version.outputs.version }} + - name: Attach them to the release + if: github.ref_type == 'tag' + env: + GH_TOKEN: ${{ github.token }} + run: | + v=${{ steps.version.outputs.version }} + gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 || gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes "wanderbooks $v" + gh release upload "$GITHUB_REF_NAME" --clobber target/packages/$v/*.AppImage target/packages/$v/*.deb target/packages/$v/*.rpm target/packages/$v/*.tar.gz + - name: Add them to the apt and dnf repositories (gh-pages) + if: github.ref_type == 'tag' + run: | + v=${{ steps.version.outputs.version }} + git fetch origin gh-pages || true + if git rev-parse --verify origin/gh-pages >/dev/null 2>&1; then + git worktree add repo origin/gh-pages + else + git worktree add --detach repo && git -C repo checkout --orphan gh-pages && git -C repo rm -rfq . || true + fi + mkdir -p repo/apt/pool repo/dnf + cp target/packages/$v/*.deb repo/apt/pool/ + cp target/packages/$v/*.rpm repo/dnf/ + deploy/packaging/repo-indexes.sh apt repo/apt + deploy/packaging/repo-indexes.sh dnf repo/dnf + cd repo + git config user.name "wanderbooks release" + git config user.email "release@wanderbooks.ai" + git add -A + git commit -m "wanderbooks $v" + git push origin HEAD:gh-pages diff --git a/README.md b/README.md index 748343f..55c3315 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,15 @@ create acme`, `wanderbooks connect claude --book acme` (or `connect codex`). Then talk to the agent; `wanderbooks gui acme` shows the books, and its Books menu backs them up, restores, starts new books and closes them. +Updating: an AppImage, or a `wanderbooks` you put somewhere yourself, brings +itself to the newest release with `wanderbooks update` (it checks the +download against our release key, keeps the version it replaces, and the +open books restart onto the new one when idle); `wanderbooks rollback` goes +back, unless a book has since moved to a newer journal format. A `.deb`, +`.rpm` or AUR install is updated by its package manager and `update` says +so. The Linux packages are built here with `just package-linux` +(docs/packaging.md); none is published yet. + ## Developing Every tool the recipes use (Rust, just, Kani, Lean, cargo-mutants, diff --git a/deploy/README.md b/deploy/README.md index 3a72587..fe454b3 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -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 diff --git a/deploy/packaging/aur/PKGBUILD b/deploy/packaging/aur/PKGBUILD new file mode 100644 index 0000000..5a4ae40 --- /dev/null +++ b/deploy/packaging/aur/PKGBUILD @@ -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" +} diff --git a/deploy/packaging/build-linux.sh b/deploy/packaging/build-linux.sh new file mode 100755 index 0000000..26cce1b --- /dev/null +++ b/deploy/packaging/build-linux.sh @@ -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" diff --git a/deploy/packaging/icons/512x512.png b/deploy/packaging/icons/512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..3505023a07843977234c232b950677b4be57fe8c GIT binary patch literal 3463 zcmbVPU2GIp6#nkrX?JW3odRi#pgT1fqY_xfhsK|FC|jU`RHB%asObs{JZTdQ7GulJ z7NMq18WV#&Kxj1ZqS1vH6n_|gHMW7&_y9;{1ERzqgi7c_x5abrOuMvM+O5eXbMLw5 zeD^!wIrr}Ddn;;}Ov$@955Sa~rIF_VNka)UmtNN!f4W8X+M3A1m5pcrKDV#vNZ7){ zA3Ck7l4UQ@5j^v#EKGeIHc_-LDeF8zd5h~_YaX!uuH|%APm3;)FtU2h7*>v z^I$@UP&UciXs}_mW1HYYQKTX)FM>Bodot+jB4KM#aW|K7qWz^&h!eZpfX`zrH}}tt zy<7W2CVv!&wXC@E%7nMhjFCKEz&ifpiHh5gw>k&OX#h-3^HqXO6fQ=NG_ z^j3NIft(BNH=a5F&EU_Q&Isw~XhO6#7;RuNHiw(M6{9gd!Qc{xw-<5MTPwJ+X*_19 z;tz%c;+;F+*X{(ze){F3Y8(z=&cb{B{wNIgw6XK^;W)iFc_exw9>U~!F*GT3M|$y2 zgUM5>D(6AyF^eZX4YdL+sZNb)>G+J(M3Kjz*^xiMvlv z1l59#p|yFqy>H-hg^7YviGQX`SIrC?_~ju(i3QOsEpP|6ARNu@ZnDt0+d^))`m%7u zv40^FO;soj2Yw@jub(v$k}@QZY*RidT@zNxubDU0^=F>hO-~ESuB;J9`c=qgN^z{b z)>OX~8Wv|wrM&>_{;KGnfuS}1J(PcGS{N_cz*_fQP^kWUMAR+VF<|3rMHvcqhz+4} z6s$0e3SDDx5-%MXGr`l#sBnbkBNFch6v)`%lXqim<-=m{zP7afpxUn+=+sFcpc z$xes1acnjlEq0 znc5}efxB8}(?e|}xEcJqr}=qQoh&aPzXjBNlfHob+seQCeJcDivCb=A7I&fhJsM72 zIl5dVDmR9D>B12s!fc)F{hU@XvB;r^+DLMJ{JLwYJbqbU0-VgS^HY<)fV_4YZdIR( zcdAZ$#Y>~>T6$C3k%|Z6P2~Aohe!~J{Ci};N^7n%p%Q&1/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" diff --git a/deploy/packaging/packager.toml b/deploy/packaging/packager.toml new file mode 100644 index 0000000..be918ba --- /dev/null +++ b/deploy/packaging/packager.toml @@ -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" diff --git a/deploy/packaging/repo-indexes.sh b/deploy/packaging/repo-indexes.sh new file mode 100755 index 0000000..671d60d --- /dev/null +++ b/deploy/packaging/repo-indexes.sh @@ -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 diff --git a/deploy/packaging/test-linux-packages.sh b/deploy/packaging/test-linux-packages.sh new file mode 100755 index 0000000..431b24d --- /dev/null +++ b/deploy/packaging/test-linux-packages.sh @@ -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" diff --git a/deploy/packaging/wanderbooks-static.desktop b/deploy/packaging/wanderbooks-static.desktop new file mode 100644 index 0000000..ffa4bbe --- /dev/null +++ b/deploy/packaging/wanderbooks-static.desktop @@ -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 diff --git a/deploy/packaging/wanderbooks.desktop b/deploy/packaging/wanderbooks.desktop new file mode 100644 index 0000000..574cd59 --- /dev/null +++ b/deploy/packaging/wanderbooks.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Categories={{categories}} +{{#if comment}} +Comment={{comment}} +{{/if}} +Exec={{exec}} +Icon={{icon}} +Name={{name}} +Terminal=false +Type=Application diff --git a/docs/architecture.md b/docs/architecture.md index a00682d..771f10e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -36,7 +36,7 @@ the specification in `bookspec/` (which never depends on the engine). | `ledger-import` | formats: QuickBooks CSV in; IIF, XLSX, Typst PDFs (statements, the invoice, labels) out | `ledger-core`, `ledger-reader` | | `ledger-package` | the shared read-only view over `Store` or `Snapshot` (`PackageBooks`): the CPA package, the 1099 worksheet, the invoice document, Needs attention | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-vault` | | `ledger-mcp` | the books server: the MCP tools (122), resources, the handbook, scopes, the door and the access log, backups' scheduling | all of the above | -| `ledger-host` | the host and its leases, the agent bridge (`wanderbooks mcp`), tokens in 0600 files under the data root (D-63), the owner's commands, uploads and downloads | `ledger-store`, `ledger-mcp`, `ledger-config` | +| `ledger-host` | the host and its leases, the agent bridge (`wanderbooks mcp`), tokens in 0600 files under the data root (D-63), the owner's commands, uploads and downloads, `update` and `rollback` (P11a) | `ledger-store`, `ledger-mcp`, `ledger-config` | | `ledger-gui` | the viewer: read-only, live, egui; the Books menu hands work to the server | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-package`, `ledger-vault`, `ledger-host` | | `ledger-cli` | `wanderbooks`, the one executable: every mode (`serve`, `server`, `host`, `mcp`, `gui`, `setup`, `books`, `token`, `connect`, `doctor`, …) and the over-the-wire test suites | everything | | `bookspec/checker`, `bookspec/runner` | the example schema and parser, the rule catalogue, the `Engine` trait; the per-rule runner | nothing of ours (the adapter in `ledger-core`'s tests depends on them) | @@ -313,3 +313,43 @@ still run on the server). The recipe, the head check for the move and the two-account CI job are in `deploy/README.md`, `deploy/wanderbooks-server.service` and `.github/workflows/two-user.yml`; the checks are `crates/ledger-cli/tests/two_user.rs`. + +## Packaging and updates (`ledger-host`'s `update.rs`, `deploy/packaging/`, P11a) + +One program, installed one of two ways, updated one of two ways +(plan D-24). The install says which: a package ships +`share/wanderbooks/install-method` beside `bin/`, and a file there makes +the program package-managed; `wanderbooks update` and `rollback` refuse it +and name the manager (apt, dnf and the AUR keep every release, so +downgrading is theirs). Anything else (an AppImage, whose file is named by +`$APPIMAGE`; an executable put somewhere by hand) updates itself: + +- the feed is a `manifest.json` (a version, and per platform a file's + address, sha256 and minisign signature; the shape `cargo-packager`'s + updater reads, with the sha256 added) at GitHub Releases' fixed address + (D-26); `--feed` reads another, and only with a `file://` feed may + `--trusted-key` name the key; +- the key a release is checked against is compiled in + (`WANDERBOOKS_RELEASE_PUBKEY`, set by the release workflow once the + owner's key exists; step 5): a build without one updates from nothing; +- a download is accepted only if its sha256 matches AND its signature + verifies; then it is staged, the running version kept under + `/updates/previous/` with the journal format it understood, and the + file replaced by a rename of a complete copy made beside it; +- nothing tells the books to stop. The host notices its executable's file + was replaced and restarts as it does for a package upgrade (host.rs + INVARIANT 7), finishing calls in progress; bridges reconnect upstream + without the agent noticing. The format gate stays the server's (3.9): + `rollback` refuses while a book declares a format or feature the kept + version does not know; +- an AppImage is the exception to the restart: the host runs from the old + image's mount, whose path does not change, so it keeps running the old + version until the books are next closed and opened (docs/packaging.md). + +`deploy/packaging/` holds the Linux packaging: `packager.toml` +(cargo-packager: `.deb`, AppImage, the pacman tarball with its PKGBUILD), +the rpm table in `crates/ledger-cli/Cargo.toml`, `aur/PKGBUILD` (the AUR +package, which repacks the release's tarball), `build-linux.sh`, +`repo-indexes.sh` (the apt and dnf indexes), `local-release.sh` (a +throwaway-keyed feed on this machine) and `test-linux-packages.sh`. +`.github/workflows/release.yml` builds them on a version tag. diff --git a/docs/packaging.md b/docs/packaging.md index dba150e..3ec5b02 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -1,23 +1,141 @@ # Packaging and updates: the trial (P11a step 1) -Status 2026-10-29: **the trial has not been run.** It needs clean Linux, -macOS and Windows machines for the six formats (`.deb`, `.rpm`, AUR, -AppImage, `.dmg`, NSIS `.exe`), and the one machine that built step 0 is a -Linux desktop with neither a macOS nor a Windows target, no container -runtime and none of the packager tools installed. The tools are pinned in -`mise.toml` now (`cargo-packager` 0.11.8, `cargo-packager-updater` 0.2.3, -`cargo-generate-rpm` 0.21.1), so the trial is `mise install` and the VMs. -Nothing below is a result; it is what is known before the trial, and the -table is filled by the person who runs it. +Status 2026-10-29: **the Linux half of the trial is run** (on the Arch +desktop that builds the project, with Debian, Fedora and Arch containers +for the installs); **the macOS and Windows halves are not**: they need +those machines (and for the `.dmg` and the NSIS `.exe`, the signing +identities of step 5), and this machine has neither a macOS nor a Windows +target. `cargo-packager` 0.11.8 and `cargo-generate-rpm` 0.21.1 are pinned in +`mise.toml`; `cargo-packager-updater` is **not** pinned: it is a library +(the program checks releases itself, `crates/ledger-host/src/update.rs`), +not a tool, so there is nothing for a recipe to call. | format | built by | installs as | updated by | signed by | |---|---|---|---|---| -| `.deb` | `cargo packager` | (trial) | apt repository (step 3) | (trial) | -| `.rpm` | `cargo-generate-rpm` | (trial) | dnf repository (step 3) | (trial) | -| AUR `PKGBUILD` | hand-written | (trial) | the package manager | n/a | -| AppImage | `cargo packager` | (trial) | `cargo-packager-updater` | (trial) | -| `.dmg` | `cargo packager` | (trial) | `cargo-packager-updater` | Developer ID (step 5) | -| NSIS `.exe` | `cargo packager` | (trial) | `cargo-packager-updater` | Windows identity (step 5) | +| `.deb` | `cargo packager` (`deb`) | `/usr/bin/wanderbooks`, a menu entry, `/usr/share/wanderbooks/install-method` = `deb`; 47 MB | apt repository (`repo-indexes.sh apt`, GitHub Pages) | unsigned until step 5 (the repository's index is `trusted=yes` in the test) | +| `.rpm` | `cargo-generate-rpm` (table in `crates/ledger-cli/Cargo.toml`) | same paths, marker `rpm`; 36 MB | dnf repository (`repo-indexes.sh dnf`) | unsigned until step 5 (`--signing-key` takes the PGP key then) | +| AUR `PKGBUILD` | `cargo packager` makes the tarball (`pacman` format) and a PKGBUILD for it; ours is `deploy/packaging/aur/PKGBUILD`, which repacks the release's tarball (package `wanderbooks-bin`) | same paths, marker `aur`; built with `makepkg` | pacman (the AUR keeps old PKGBUILDs) | n/a (pacman trusts the AUR's checksums) | +| AppImage | `cargo packager` (`appimage`); 44 MB | one file the owner puts anywhere and runs; needs no admin password | `wanderbooks update` (`$APPIMAGE` is the file replaced) against the manifest; minisign | minisign by the release key (step 5) | +| `.dmg` | `cargo packager` (`dmg`) | (trial) needs a Mac | `wanderbooks update` | Developer ID and notarization (step 5) | +| NSIS `.exe` | `cargo packager` (`nsis`) | (trial) needs Windows, and native Windows is unit P11c (D-62) | `wanderbooks update` | the Windows identity (step 5) | + +`just package-linux` builds the first four (`deploy/packaging/build-linux.sh`, +about a minute after the release build, itself 8 to 12 minutes on a +loaded 16-core machine); `just test-linux-packages` is the install-and-upgrade test. + +## Linux results (2026-10-29) + +What the trial found, each of which is built around now: + +1. **A program linked here does not start on Debian.** This machine's + glibc is 2.44; the binary asks for `GLIBC_2.43` and Debian 13 (trixie) + has 2.41. A package must be built on the oldest system it claims. + `.github/workflows/release.yml` builds on `ubuntu-24.04` (glibc 2.39); + `just release-trixie` builds in `rust:1.99-trixie` for the local test. The + claim is therefore "Debian 13, Ubuntu 24.04 and newer, current Fedora and + Arch"; Debian 12 and Ubuntu 22.04 are not claimed (they would need a + build container of their own). +2. **The books refuse to run under a seccomp filter** (`host.rs` + INVARIANT 2), and Docker's default profile is one: in a container the + host fails to start ("refusing to run under a seccomp filter"). The + tests run containers with `--security-opt seccomp=unconfined`. Anyone + running the books in Docker meets this; it is not a bug of the packages. +3. **An AppImage's launcher runs the desktop file's `Exec` line and appends + the arguments it was given.** With `Exec=wanderbooks gui`, + `Wanderbooks.AppImage update` ran `wanderbooks gui update`. The desktop + files say plain `Exec=wanderbooks`, and a launch with nothing to say and + no terminal is the viewer (`main.rs::is_bare_launch`); at a terminal + it is still the usage. So the AppImage is also the command line: + `Wanderbooks.AppImage update`, `... mcp --book acme`. +4. **cargo-packager's configuration** (0.11.8): paths in a config are + relative to the *config file*, not the working directory (the build script + writes an absolute-path copy); `files` maps *source to destination*; + `category` is a fixed list (`Finance`); the AppImage needs a square icon + (`deploy/packaging/icons/512x512.png`, **a placeholder the owner should + replace**); the `pacman` format yields the tarball and PKGBUILD but not a + package (that is `makepkg`'s); its `.deb` has `Depends` from the config. +5. **The AppImage tools are downloaded when it is built** (`AppRun`, + `linuxdeploy` and its AppImage plugin from tauri-apps' and linuxdeploy's + GitHub releases, unverified by cargo-packager): a supply-chain edge of + the release build, not of the installed app. They run with + `APPIMAGE_EXTRACT_AND_RUN=1`, so the build needs no FUSE. +6. **`cargo packager signer` signs in a format `update` verifies.** Its + key and its `.sig` are the base64 of minisign's two-line text; the + manifest's `signature` is that `.sig` as written, and `update` reads the + public key in either form. Checked end to end by hand (below), and by + `a_public_key_is_read_in_every_form_minisign_and_the_packager_write_it`. +7. **The AppImage updates itself but the books it holds do not restart + onto the new image.** `update` replaces `$APPIMAGE`, and an AppImage run + from a shell (`Wanderbooks.AppImage update`, or the one that was running) + proves the swap and the rollback: after `update`, the AppImage on disk + is the new file, the old one is kept, and `rollback` restores it. But the + host that is open runs from the old image's mount + (`/tmp/.mount_*/usr/bin/wanderbooks`), a path that does not change, so + `host.rs` never sees a replaced file and the open books keep the old + version until they are closed and opened again (`wanderbooks close`). + Fixing it means the host learning the image's path (its environment is + scrubbed on purpose; `APPIMAGE` would join the allow-list of `start.rs`, + or the path would come from the mount table) and starting its successor + from it: a decision about what the host may trust, left to the EM (the + unit's report names it). Package installs and a hand-placed executable + restart as designed (`bridge_survives_update`). +8. **Onboarding with no terminal does not exist yet.** The unit's Done-when + (1) says an AppImage "opens, onboards and posts an entry with no + terminal". The AppImage opens (a bare launch is the viewer), but the viewer + opens only an existing book (`ledger_gui::book_in_root`), and a data root + and a first book come from `wanderbooks setup` and `books create`, which + need a terminal. A first-run path in the viewer ("New books…" is in its + menu, but the menu is not reachable until a book is open) is not in P11a's + steps. It is the gap between this wave's AppImage and Done-when (1). + +By hand, on this machine, against a throwaway release folder made by `just +local-release` (a key from `cargo packager signer generate`; the feed +`file://…/manifest.json`; a scratch home made by `wanderbooks setup --home`, +nothing of the owner's): + +```text +Wanderbooks.AppImage --help -> the usage (FUSE mount, and with APPIMAGE_EXTRACT_AND_RUN=1) +Wanderbooks.AppImage setup --home H -> the private folder; books create acme -> books list: "acme open" +Wanderbooks.AppImage update --check --feed F --trusted-key K + -> "wanderbooks 0.2.0 is available (this is 0.1.0)" +Wanderbooks.AppImage update --feed F --trusted-key K + -> "Updated wanderbooks from 0.1.0 to 0.2.0."; the file's sha256 + is the release's; updates/previous/ holds the 0.1.0 image +Wanderbooks.AppImage rollback -> "Went back to wanderbooks 0.1.0."; the file is the original again +``` + +`linux_packages_install_and_upgrade` (`deploy/packaging/test-linux-packages.sh`), +2026-10-29, 0.1.0 then 0.1.1, all three passing: in `debian:trixie-slim` +and `fedora:latest` the package installs from a repository made by +`repo-indexes.sh` (the release workflow runs the same script), in +`archlinux:latest` from `makepkg` over `deploy/packaging/aur/PKGBUILD`; then +books are made as an ordinary user and held open, 0.1.1 is added and +upgraded to by the package manager (`apt-get install --only-upgrade`, `dnf +upgrade`, `pacman -U`), the host restarts onto the new file (a new pid whose +`/proc/PID/exe` is not deleted), the journal's last line (the head) is +byte-for-byte unchanged, and `wanderbooks update` exits non-zero naming the +package manager. Not tested: Ubuntu, openSUSE, a package-manager +*downgrade* (the repositories keep every release by construction), and the +`.rpm` and `.deb` under their real signatures (step 5). + +## What `update` and `rollback` need from a release + +`manifest.json` at `releases/latest/download/` (D-26): + +```json +{ "version": "0.2.0", "notes": "…", + "platforms": { "linux-x86_64": { "url": "wanderbooks-0.2.0-linux-x86_64", + "sha256": "…hex…", "signature": "…base64 of the .sig file…" } } } +``` + +`url` is absolute or relative to the manifest; the platform key is +`std::env::consts::OS-ARCH`; the file is the whole program (the AppImage +on Linux). The program checks the version is higher, the sha256, and the +minisign signature against `WANDERBOOKS_RELEASE_PUBKEY` (compiled in by the +release workflow once the owner has a key: **step 5, Q-4**; today no build +carries one, so a build updates only from a `file://` feed with +`--trusted-key`). The two-key release manifest of D-25 would wrap this +(rotation, expiry, a monotone version) and is step 5's. ## What step 0 found about the platforms (read before the macOS and Windows rows) diff --git a/docs/testing.md b/docs/testing.md index bd944d8..d75e105 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -3669,3 +3669,79 @@ it); the digest's `since_seq` comparison (`>`), its `as_of` bound, the `zero_net_accounts` branch and the `never_reconciled` split; `report_status`'s three open conditions; the open-drafts refusal's `<= as_of` and its decision; the attention `awaiting_evidence` branch. + +## P11a steps 2 and 3: update, rollback, the Linux packages (2026-10-29) + +`wanderbooks update` and `rollback` (`ledger-host/src/update.rs`) are +tested over a local release folder (a `file://` feed) and a minisign key +the test makes, with real programs: each test makes a home, a stripped +copy of the program in its `bin/`, and a manifest naming a newer copy of +it (the same program with a tail, so its hash differs). All in +`crates/ledger-cli/tests/update.rs` (Linux only): + +- `an_update_replaces_the_program_keeps_the_old_one_and_rollback_brings_it_back`: + no key and no feed is refused ("no release key"); the same version is "the + newest"; an older offer is refused and nothing is kept; an update puts + the new bytes in place, keeps the old under `updates/previous/` with the + format it understood, leaves no temporary or staged file; rollback puts + the old bytes back and consumes what was kept; a second rollback says + there is nothing to go back to. +- `update_is_atomic`: a reader polling the program file all through the + update sees, on every read, the old file whole or the new whole. +- `failed_update_rolls_back`: a validly signed file that does not start + (`exit 3`) is swapped in, fails `--help`, and the old program is put + back; nothing is staged or left beside the program. +- `unsigned_update_refused`: five bad manifests (a good signature by + another key; a signature of other bytes; a sha256 that differs; an empty + signature; a signature that is not one), each refused with the program + unchanged and nothing kept or staged; `--trusted-key` with an `https` + feed is refused. +- `package_managed_install_refuses_update`: a + `share/wanderbooks/install-method` marker beside `bin/` makes both + commands refuse, naming the manager, with nothing made in the data root. +- `rollback_refuses_after_a_book_consented_to_a_newer_format`: with a + book that declares this version's format and a kept version that knows + one feature less, rollback refuses and the program stays; with the kept + version knowing everything the book declares, it goes back. +- `bridge_survives_update` and `update_waits_for_a_running_call`: an + agent's bridge session writes without pause while `update` runs and the + host restarts onto the new file; every write is answered and made once + (the account is in the chart exactly once), the session needs no + reconnect, its tool list is unchanged, and the host and server run the + file now on disk (not a deleted one). The host's own restart laws stay + `open_while_held.rs`'s (`a_restart_is_not_a_close`). + +Unit tests in `update.rs`: `versions_are_three_numbers_and_order_by_number`, +`a_package_marker_beside_the_program_makes_it_package_managed`, +`a_manifest_address_is_resolved_beside_the_manifest`, +`a_swap_leaves_the_whole_new_file_and_no_temporary`, +`a_public_key_is_read_in_every_form_minisign_and_the_packager_write_it`. +`main.rs`: `only_nothing_at_all_with_no_terminal_is_the_viewer` (the bare +launch an AppImage's launcher makes; it cannot be run as a test, because the +viewer finds the account's real data root from the password database). + +Not tests, run by hand on this machine and recorded in `docs/packaging.md`: +the AppImage updating and rolling back itself against a feed signed by +`cargo packager signer` (`just local-release`), which is the check that the +packager's signature format is the one `update` verifies. + +**`linux_packages_install_and_upgrade`** is `deploy/packaging/test-linux-packages.sh` +(`just test-linux-packages`, needs docker; not part of `just check`): in a +clean Debian, Fedora and Arch container it installs release 0.1.0 (Debian +and Fedora from an apt and a dnf repository made by `repo-indexes.sh`, the +same script the release workflow runs; Arch by `makepkg` from the +packager's PKGBUILD), makes books as an ordinary user, holds them open, +adds 0.1.1 to the repository and upgrades; then checks the host restarted +onto the new file, `wanderbooks books list` is unchanged, and +`wanderbooks update` refuses and names the package manager. The programs +are built in a `rust:1.99-trixie` container (`just release-trixie`): +see `docs/packaging.md` for why the build machine's own binary is not used. + +**For P12 (mutation targets, none run, D-33):** `update.rs`'s `verify` +(a mutant that drops the sha check or the signature check must die on +`unsigned_update_refused`'s sha and key cases), `refuse_if_consented`'s +two comparisons (`rollback_refuses_after_a_book_consented_to_a_newer_format` +covers the feature one; a case with a higher format number is a gap to +add), `swap_in`'s temporary-then-rename (`update_is_atomic`), `healthy` +(`failed_update_rolls_back`), `install_kind` (the marker test), and +`parse_version`'s rejections. diff --git a/justfile b/justfile index 1fa699d..a6d248e 100644 --- a/justfile +++ b/justfile @@ -263,3 +263,35 @@ deploy: site # Deploy a preview of the website and print its URL deploy-preview: site {{netlify}} deploy --dir site/dist --no-build + +# The Linux packages (P11a step 3), in target/packages/VERSION. Builds the +# release program first; needs the tools `mise install` pins. A VERSION other +# than the program's repackages the same program under it (the upgrade +# test's second release). +# Build the AppImage, .deb, pacman tarball and .rpm +package-linux version="" formats="deb,appimage,pacman": + cargo build --release -p ledger-cli + deploy/packaging/build-linux.sh {{version}} {{formats}} + +# A release folder on this machine signed with a throwaway key; then +# `wanderbooks update --feed file://DIR/manifest.json --trusted-key DIR/release.pub` +# Make a local release feed to try `wanderbooks update` against +local-release file version dir: + deploy/packaging/local-release.sh {{file}} {{version}} {{dir}} + +# A program linked here does not start on Debian (docs/packaging.md); this +# builds it in rust:1.99-trixie, into target/container. Needs docker. +# Build the release program in a Debian trixie container +release-trixie: + mkdir -p target/container + docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -e CARGO_HOME=/tmp/cargo-home -e CARGO_TARGET_DIR=/target -v "$PWD:/src:ro" -v "$PWD/target/container:/target" -w /src rust:1.99-trixie cargo build --release --locked -p ledger-cli + +# Install each Linux package in a clean Arch, Debian and Fedora container, +# open the books, upgrade to the next release, and check the books' head is +# unchanged and the open books restarted onto the new program. Builds the +# program in a trixie container, then releases 0.1.0 and 0.1.1 of it. +# Install and upgrade the Linux packages in Arch, Debian and Fedora containers +test-linux-packages: release-trixie + WB_PROGRAM=target/container/release/wanderbooks deploy/packaging/build-linux.sh 0.1.0 deb,pacman + WB_PROGRAM=target/container/release/wanderbooks deploy/packaging/build-linux.sh 0.1.1 deb,pacman + deploy/packaging/test-linux-packages.sh diff --git a/mise.toml b/mise.toml index 0cd80e8..88b925e 100644 --- a/mise.toml +++ b/mise.toml @@ -23,3 +23,7 @@ node = "26" # A compile cache shared by every jj workspace's target dir: point # cargo's build.rustc-wrapper at it (~/.cargo/config.toml). sccache = "0.18.0" +# `just package-linux` (docs/packaging.md): the AppImage and .deb by +# cargo-packager, the .rpm by cargo-generate-rpm (D-25). +"cargo:cargo-packager" = "0.11.8" +"cargo:cargo-generate-rpm" = "0.21.1"