Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m41s
Native packages the installer can actually fetch, before task 2014 wires up the fetching. Arch (task 2022, re-scoped): the source PKGBUILD is gone — asking every user to install rust+node and compile for minutes isn't distribution. Replaced by desktop/packaging/arch/package-prebuilt.sh, which wraps the binary the Linux job already built into a .pkg.tar.zst. No second Rust build, no Arch CI image: the binary bundles nothing and resolves webkit/gtk/soup by soname, identical on both distros, with SQLite compiled in and glibc used in the safe built-old/run-new direction. CI is Debian and has no pacman, so the step logs .PKGINFO plus the full file listing for audit instead of pretending to verify. Debian (task 2074): install.sh hands the .deb to every Debian/Ubuntu user and nothing had ever inspected it. tauri.conf.json now declares libwebkit2gtk-4.1-0 + libgtk-3-0 explicitly rather than trusting inference — and deliberately declares no appindicator or sqlite dep, since tauri is built with features=[] and rusqlite is "bundled". desktop/packaging/deb/verify.sh prints the generated control file, cross-checks it against what the ELF actually needs via dpkg-shlibdeps, confirms every declared dep exists in apt, and clean-container installs when a docker CLI is available. Both artifacts join the run artifact and the tagged release; install.sh grows a pacman branch so Arch/CachyOS gets a native install instead of the AppImage fallback. Still no release cut (rule 2). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SreJkbxB4gx8pPsu8QbLPi
146 lines
6.9 KiB
Bash
Executable File
146 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Verify the .deb that `cargo tauri build` produced is a correct, installable
|
|
# native package — BEFORE the one-command installer starts handing it to people.
|
|
#
|
|
# WHY: install.sh prefers the .deb on every Debian/Ubuntu machine, but until now
|
|
# nothing had ever inspected it, let alone installed it. Its Depends line was
|
|
# entirely whatever tauri inferred, unread. The failure mode we're guarding is a
|
|
# user running install.sh and getting either an apt resolution error or an app
|
|
# that installs and then won't launch because a library it needs was never
|
|
# declared.
|
|
#
|
|
# Four checks, cheapest first:
|
|
# 1. Print the control file + contents — the generated metadata becomes ground
|
|
# truth in the build log instead of an assumption.
|
|
# 2. dpkg-shlibdeps: the canonical Debian answer for "what does this ELF
|
|
# actually need". Compared against what the package declares.
|
|
# 3. Every declared dependency resolves to a real package in apt (catches a
|
|
# typo in the hand-written list, which would break install for everyone).
|
|
# 4. If a docker CLI is present, install into a clean debian container — the
|
|
# highest-fidelity check, since the build image already has the -dev
|
|
# packages installed and so can't prove resolution on its own.
|
|
#
|
|
# Check 4 is opportunistic on purpose: the build image is not guaranteed to carry
|
|
# a docker CLI, and adding one at job time would violate "the image is the
|
|
# toolchain" (rule 5). Checks 1-3 are self-contained and always run.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
DEB_DIR="$REPO_ROOT/desktop/src-tauri/target/release/bundle/deb"
|
|
|
|
shopt -s nullglob
|
|
DEBS=("$DEB_DIR"/*.deb)
|
|
[ ${#DEBS[@]} -gt 0 ] || { echo "ERROR: no .deb under $DEB_DIR — did the tauri build run?" >&2; exit 1; }
|
|
DEB="${DEBS[0]}"
|
|
echo "==> Verifying $(basename "$DEB")"
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT INT TERM
|
|
|
|
fail=0
|
|
note_fail() { echo "FAIL: $1" >&2; fail=1; }
|
|
|
|
# --- 1. control metadata + contents -----------------------------------------
|
|
echo
|
|
echo "--- control ---"
|
|
dpkg-deb -I "$DEB" | sed 's/^/ /'
|
|
echo "--- contents ---"
|
|
dpkg-deb -c "$DEB" | awk '{print $1, $6}' | sed 's/^/ /'
|
|
|
|
# Declared runtime deps, normalised: strip version constraints, split
|
|
# alternatives ("a | b" both count as declared), one package name per line.
|
|
declared="$(dpkg-deb -f "$DEB" Depends 2>/dev/null |
|
|
tr ',|' '\n\n' | sed -E 's/\(.*\)//; s/^[[:space:]]*//; s/[[:space:]]*$//' |
|
|
grep -v '^$' | sort -u || true)"
|
|
echo
|
|
echo "--- declared Depends ---"
|
|
printf '%s\n' "$declared" | sed 's/^/ /'
|
|
|
|
dpkg-deb -x "$DEB" "$WORK/root"
|
|
BIN="$(find "$WORK/root" -type f -path '*/bin/*' -print -quit)"
|
|
[ -n "$BIN" ] || { echo "ERROR: no binary found under */bin/ in the package" >&2; exit 1; }
|
|
echo " (binary: ${BIN#"$WORK/root"})"
|
|
|
|
# --- 2. what the ELF actually needs -----------------------------------------
|
|
if command -v dpkg-shlibdeps >/dev/null 2>&1; then
|
|
# dpkg-shlibdeps insists on a debian/control in the working directory even with
|
|
# -O (write to stdout); a stub is enough to let it do the ELF analysis.
|
|
mkdir -p "$WORK/deb-stub/debian"
|
|
printf 'Source: thoughtsync\n\nPackage: thoughtsync\nArchitecture: amd64\n' \
|
|
>"$WORK/deb-stub/debian/control"
|
|
# `|| true`: pipefail is on and dpkg-shlibdeps still exits non-zero on some
|
|
# symbol warnings even with --ignore-missing-info. An empty result degrades to
|
|
# "check skipped" below — it must not abort the whole verification.
|
|
required="$(cd "$WORK/deb-stub" &&
|
|
dpkg-shlibdeps -O --ignore-missing-info "$BIN" 2>/dev/null |
|
|
sed -E 's/^shlibs:Depends=//' | tr ',' '\n' |
|
|
sed -E 's/\(.*\)//; s/^[[:space:]]*//; s/[[:space:]]*$//' | grep -v '^$' | sort -u || true)"
|
|
|
|
echo
|
|
echo "--- required by the binary (dpkg-shlibdeps) ---"
|
|
printf '%s\n' "$required" | sed 's/^/ /'
|
|
|
|
# A package may be satisfied transitively — we deliberately declare only
|
|
# webkit2gtk + gtk3 and let glib/cairo/pango/gdk-pixbuf/libsoup3 arrive through
|
|
# them, so "required but not declared" is only a real problem if it's also
|
|
# unreachable from the declared set. Ask apt for that closure.
|
|
closure="$(apt-cache depends --recurse --no-recommends --no-suggests \
|
|
--no-conflicts --no-breaks --no-replaces --no-enhances -i $declared 2>/dev/null |
|
|
grep -E '^[a-zA-Z0-9]' | sed -E 's/[<>]//g' | sort -u || true)"
|
|
|
|
if [ -z "$required" ]; then
|
|
echo "WARN: dpkg-shlibdeps returned nothing — skipping the coverage check." >&2
|
|
elif [ -z "$closure" ]; then
|
|
echo "WARN: apt has no package index here — skipping the transitive-coverage check." >&2
|
|
else
|
|
for pkg in $required; do
|
|
# Essential / required-priority packages (libc6, libgcc-s1 …) are present on
|
|
# every Debian system by definition; declaring them is noise, not safety.
|
|
prio="$(dpkg-query -W -f='${Priority} ${Essential}' "$pkg" 2>/dev/null || true)"
|
|
case "$prio" in *required*|*important*|*yes*) continue ;; esac
|
|
printf '%s\n' "$closure" | grep -qx "$pkg" && continue
|
|
printf '%s\n' "$declared" | grep -qx "$pkg" && continue
|
|
note_fail "$pkg is needed by the binary but is neither declared nor reachable from the declared deps."
|
|
done
|
|
[ "$fail" -eq 0 ] && echo "OK: every non-essential library the binary needs is covered."
|
|
fi
|
|
else
|
|
echo "WARN: dpkg-shlibdeps unavailable — skipping the ELF dependency check." >&2
|
|
fi
|
|
|
|
# --- 3. declared deps are real packages -------------------------------------
|
|
if apt-cache policy dpkg >/dev/null 2>&1 && [ -n "$(apt-cache policy dpkg 2>/dev/null)" ]; then
|
|
for pkg in $declared; do
|
|
if [ -z "$(apt-cache policy "$pkg" 2>/dev/null)" ]; then
|
|
note_fail "declared dependency '$pkg' does not exist in apt — install would fail for every user."
|
|
fi
|
|
done
|
|
[ "$fail" -eq 0 ] && echo "OK: every declared dependency exists in apt."
|
|
fi
|
|
|
|
# --- 4. clean-container install (opportunistic) -----------------------------
|
|
# The build image already has libwebkit2gtk-4.1-dev etc. installed, so installing
|
|
# here would pass no matter what we declared. Only a pristine container proves
|
|
# apt can actually resolve the package for a real user.
|
|
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
|
echo
|
|
echo "==> Clean-container install test (debian:bookworm)"
|
|
if docker run --rm -v "$DEB:/tmp/$(basename "$DEB"):ro" debian:bookworm \
|
|
sh -c "apt-get update -qq && apt-get install -y --no-install-recommends '/tmp/$(basename "$DEB")'"; then
|
|
echo "OK: the .deb installs cleanly on a stock debian:bookworm."
|
|
else
|
|
note_fail "the .deb did NOT install on a stock debian:bookworm."
|
|
fi
|
|
else
|
|
echo
|
|
echo "NOTE: no usable docker CLI — skipping the clean-container install test."
|
|
echo " Checks 1-3 above still gate the package; a real-machine install is the operator's confirm."
|
|
fi
|
|
|
|
echo
|
|
[ "$fail" -eq 0 ] && { echo "==> .deb verification passed."; exit 0; }
|
|
echo "==> .deb verification FAILED." >&2
|
|
exit 1
|