Files
inkwell/desktop/packaging/arch/package-prebuilt.sh
T
bvandeusenandClaude Opus 5.5 01b53118b9
Android / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 12s
CI & Build / Web typecheck and unit tests (push) Successful in 8s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Android / Core and FFI clippy and tests (push) Successful in 1m3s
CI & Build / integration (push) Successful in 1m37s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m40s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m14s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m3s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m42s
Android / Build the server image (push) Successful in 1s
Core and desktop comments describe the code to a stranger (#6224)
Milestone 546 "Bring the repo to the comment standard (rule 32) and
compose defaults (rule 198)", step #6224 "Comment standard sweep: Rust
core and desktop client".

Doc comments and comments across inkwell-core, the Tauri app and
desktop/packaging now say what the code does and the non-obvious why in
the present tense. Gone: milestone tags (M10.x, M11.3, M13, M304, M315,
M314, milestone 325), issue and Scribe-note numbers, family-idea and
family-rule citations, the run numbers and commit hashes behind past
bugs, and the narration of what used to be (the stable channel before it
held its own bundles, CARGO_PKG_VERSION as the shown version, the
MANIFEST_TAG split, the store's missing tests, the old PKGBUILD). The
schema's migration notes keep what each step does and why, without
"until now". The forge is called the forge rather than Forgejo.

Hazards stay as facts: the dev-rolling tag that must not shadow the
branch, the manifest matched on this build's version, the APK's fixed
name surviving the prune, the LIKE guard in v9, -wal/-shm moving with
the renamed store, panic = abort ending the app.

34 files, comments only: a comment-stripping diff of every changed file
against HEAD is identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:30:37 -04:00

168 lines
7.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Build a PREBUILT Arch package (.pkg.tar.zst) from the binary the desktop CI job
# already compiled — no makepkg, no Arch container, no second Rust build.
#
# WHY prebuilt: a source PKGBUILD would make every user install rust + nodejs +
# npm and compile for minutes before they could open the app.
# Distribution means shipping a package people can just install, so the one-command
# installer (install.sh) can `pacman -U` a real native package on Arch/CachyOS
# instead of falling back to the AppImage.
#
# WHY it's safe to package a Debian-compiled binary for Arch: the binary bundles
# nothing. It resolves libwebkit2gtk-4.1.so.0 / libgtk-3.so.0 / libsoup-3.0.so.0 by
# SONAME at runtime, and those sonames are identical on both distros; SQLite is
# compiled in (rusqlite "bundled"). glibc symbol versioning is forward-compatible,
# and we build on Debian's older glibc and run on Arch's newer one — the safe
# direction. Using the host's graphics stack is also what avoids the bundled-lib
# EGL_BAD_PARAMETER black window.
#
# A .pkg.tar.* is just a tar whose FIRST entry is .PKGINFO, so we assemble it
# directly rather than nesting an Arch container over the docker socket.
#
# CI cannot verify this end-to-end: the runner is Debian and has no pacman. The
# step logs .PKGINFO and the full file listing so the package is auditable from the
# build log; `pacman -U` on a real Arch box is the final proof.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
SRC_TAURI="$REPO_ROOT/desktop/src-tauri"
BINARY="$REPO_ROOT/target/release/inkwell"
OUT_DIR="${1:-$REPO_ROOT/target/release/bundle/arch}"
PKGNAME="inkwell"
# Earlier names of this package: `thoughtsync` and `thoughtsync-desktop`. pacman
# needs both conflict and replace to retire them: without them a `pacman -U`
# installs ALONGSIDE the old one.
REPLACES=(thoughtsync thoughtsync-desktop)
PKGREL=1
PKGDESC="Inkwell desktop — local-first Keep-style thought capture"
URL="https://git.fabledsword.com/bvandeusen/inkwell"
LICENSE="MIT"
PACKAGER="Inkwell CI <noreply@fabledsword.com>"
# Runtime deps. Deliberately minimal and grounded in what the binary actually
# links: tauri is built with features=[] (no tray -> no libayatana-appindicator)
# and rusqlite is "bundled" (no sqlite package). Everything else — glib2, cairo,
# pango, gdk-pixbuf2, libsoup3 — arrives transitively via these two, so listing
# them would just be noise that can drift out of date.
DEPENDS=(webkit2gtk-4.1 gtk3)
[ -f "$BINARY" ] || {
echo "ERROR: no binary at $BINARY — run the tauri/cargo build first." >&2
exit 1
}
# Single source of truth for the version: the SAME helper the bundle build uses.
#
# Not tauri.conf.json: dev builds override the version on the command line and the
# file says 0.1.0, so reading it would stamp the package with a version its binary
# doesn't report.
# `|| true` so a miss falls through to the explicit error below rather than
# aborting on pipefail with no explanation.
# The ORDERING KEY: pacman compares this, and it must match the filename the
# bundle build produced (write-manifest.sh selects on it).
PKGVER="$(sh "$SCRIPT_DIR/../../../packaging/version.sh" key desktop || true)"
[ -n "$PKGVER" ] || { echo "ERROR: could not determine the build version" >&2; exit 1; }
# Reproducible-ish: prefer the commit date over "now" so rebuilding the same
# commit produces the same builddate.
BUILDDATE="$(git -C "$REPO_ROOT" log -1 --format=%ct 2>/dev/null || date +%s)"
echo "==> Packaging $PKGNAME $PKGVER-$PKGREL (x86_64) for pacman"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT INT TERM
# --- lay out the filesystem tree --------------------------------------------
# /usr/bin/inkwell — the same command name the .deb installs and the AppImage
# installer symlinks into ~/.local/bin, so it's identical whichever way the app
# arrived. The binary already carries this name (Cargo `[[bin]]`), which is also
# what the .desktop entry's StartupWMClass has to match.
install -Dm755 "$BINARY" "$STAGE/usr/bin/inkwell"
install -Dm644 "$SCRIPT_DIR/inkwell.desktop" \
"$STAGE/usr/share/applications/inkwell.desktop"
# Themed icons, so the menu entry's `Icon=inkwell` resolves at every size the
# desktop asks for; pixmaps is the legacy fallback for older launchers.
install -Dm644 "$SRC_TAURI/icons/32x32.png" \
"$STAGE/usr/share/icons/hicolor/32x32/apps/inkwell.png"
install -Dm644 "$SRC_TAURI/icons/128x128.png" \
"$STAGE/usr/share/icons/hicolor/128x128/apps/inkwell.png"
install -Dm644 "$SRC_TAURI/icons/128x128@2x.png" \
"$STAGE/usr/share/icons/hicolor/256x256/apps/inkwell.png"
install -Dm644 "$SRC_TAURI/icons/icon.png" \
"$STAGE/usr/share/icons/hicolor/512x512/apps/inkwell.png"
install -Dm644 "$SRC_TAURI/icons/icon.png" "$STAGE/usr/share/pixmaps/inkwell.png"
# Installed size in KiB, as pacman reports it.
INSTALLED_SIZE="$(du -sb "$STAGE" | cut -f1)"
# --- .PKGINFO ---------------------------------------------------------------
{
echo "# Generated by desktop/packaging/arch/package-prebuilt.sh"
echo "pkgname = $PKGNAME"
echo "pkgbase = $PKGNAME"
echo "pkgver = $PKGVER-$PKGREL"
echo "pkgdesc = $PKGDESC"
echo "url = $URL"
echo "builddate = $BUILDDATE"
echo "packager = $PACKAGER"
echo "size = $INSTALLED_SIZE"
echo "arch = x86_64"
echo "license = $LICENSE"
for d in "${DEPENDS[@]}"; do echo "depend = $d"; done
# conflict + replaces together: `conflict` is what makes pacman remove the old
# package rather than refuse the transaction, `replaces` is what makes an upgrade
# pick this one up under its new name.
for r in "${REPLACES[@]}"; do echo "conflict = $r"; echo "replaces = $r"; done
} >"$STAGE/.PKGINFO"
# --- .MTREE (optional) ------------------------------------------------------
# pacman uses .MTREE for `pacman -Qkk` file verification; it is NOT required to
# install, and generating it needs bsdtar (libarchive-tools), which the Debian CI
# image may not carry. Emit it when we can, skip loudly when we can't, rather
# than adding an apt install to the job (the image is the toolchain).
MTREE_ENTRY=()
if command -v bsdtar >/dev/null 2>&1; then
( cd "$STAGE" && LANG=C bsdtar -czf .MTREE --format=mtree \
--options='!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link' \
.PKGINFO usr )
MTREE_ENTRY=(.MTREE)
echo " .MTREE generated (bsdtar present)"
else
echo " NOTE: bsdtar absent — packaging without .MTREE."
echo " pacman installs this fine; only 'pacman -Qkk' file verification is degraded."
fi
# --- compress ---------------------------------------------------------------
# pacman reads whatever libarchive can decompress, so degrade gracefully instead
# of hard-requiring zstd in the build image. zstd is the modern default.
if command -v zstd >/dev/null 2>&1; then
COMP_EXT="zst"; COMP_CMD=(zstd -c -T0 -19 -)
elif command -v xz >/dev/null 2>&1; then
COMP_EXT="xz"; COMP_CMD=(xz -c -T0 -)
else
COMP_EXT="gz"; COMP_CMD=(gzip -c -)
fi
mkdir -p "$OUT_DIR"
PKGFILE="$OUT_DIR/$PKGNAME-$PKGVER-$PKGREL-x86_64.pkg.tar.$COMP_EXT"
# .PKGINFO MUST be the first entry in the archive — pacman reads it as a stream
# and gives up if the metadata isn't up front. Listing it first is what guarantees
# that ordering; `usr` is appended after.
TAR=tar
command -v bsdtar >/dev/null 2>&1 && TAR=bsdtar
( cd "$STAGE" && "$TAR" -cf - .PKGINFO "${MTREE_ENTRY[@]}" usr ) | "${COMP_CMD[@]}" >"$PKGFILE"
# --- report (the CI log IS the audit trail — see header) --------------------
echo "==> Built $(basename "$PKGFILE") ($(du -h "$PKGFILE" | cut -f1), compression: $COMP_EXT)"
echo "--- .PKGINFO ---"
sed 's/^/ /' "$STAGE/.PKGINFO"
echo "--- contents ---"
"$TAR" -tf "$PKGFILE" | sed 's/^/ /'
echo "==> Install on Arch with: sudo pacman -U $(basename "$PKGFILE")"