Files
inkwell/desktop/packaging/arch/package-prebuilt.sh
T
bvandeusenandClaude Opus 5.5 06ed76814f
Android / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Android / Core and FFI clippy and tests (push) Successful in 55s
CI & Build / Build now, or wait for Android? (push) Successful in 4s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m25s
CI & Build / Web typecheck and unit tests (push) Successful in 12s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 2m20s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m29s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m27s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m17s
Android / Build the server image (push) Successful in 1s
Inkwell is licensed AGPL-3.0-only (#6227)
#6227 "License Inkwell under AGPL-3.0-only: LICENSE file and SPDX
identifier". The operator chose AGPL-3.0-only, as for FabledStash (#6212).

- LICENSE is the canonical GNU AGPL v3 text from gnu.org: 661 lines, md5
  eb1e647870add0502f8f010b19de32af, the same file as FabledStash's.
- AGPL-3.0-only is declared in pyproject.toml, frontend/package.json (and
  its lockfile root entry), the four crates, and tauri.conf.json's bundle.
- The pacman package declared MIT; it now declares AGPL-3.0-only.
- pyproject's build floor moves to setuptools>=77, the first release that
  reads `license` as an SPDX expression.
- The README's Licence section names the licence, links LICENSE, and
  states the network clause in one sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:12: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="AGPL-3.0-only"
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")"