desktop: prebuilt pacman package + verified .deb (tasks 2022, 2074)
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m41s
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
This commit is contained in:
@@ -78,6 +78,24 @@ jobs:
|
|||||||
- name: De-bundle AppImage graphics libraries
|
- name: De-bundle AppImage graphics libraries
|
||||||
run: bash desktop/packaging/appimage/debundle-graphics.sh
|
run: bash desktop/packaging/appimage/debundle-graphics.sh
|
||||||
|
|
||||||
|
# install.sh hands the .deb to every Debian/Ubuntu user, so the package's
|
||||||
|
# Depends must be right BEFORE a release exists. Prints the generated
|
||||||
|
# control file and cross-checks it against what the ELF actually needs
|
||||||
|
# (dpkg-shlibdeps). tauri.conf.json declares libwebkit2gtk-4.1-0 + libgtk-3-0
|
||||||
|
# explicitly rather than trusting inference alone; there's deliberately no
|
||||||
|
# appindicator or sqlite dep (tauri features = [], rusqlite is "bundled").
|
||||||
|
- name: Verify the .deb
|
||||||
|
run: bash desktop/packaging/deb/verify.sh
|
||||||
|
|
||||||
|
# Repackage the binary just built into a native pacman package, so Arch /
|
||||||
|
# CachyOS gets a real native install from install.sh instead of the AppImage
|
||||||
|
# fallback — without a second Rust build or an Arch CI image. Safe because
|
||||||
|
# nothing is bundled: the binary resolves webkit/gtk by soname, which is
|
||||||
|
# identical across the two distros. Can't be pacman-tested here (Debian
|
||||||
|
# runner), so the step logs .PKGINFO + the full file listing for audit.
|
||||||
|
- name: Package for Arch (pacman)
|
||||||
|
run: bash desktop/packaging/arch/package-prebuilt.sh
|
||||||
|
|
||||||
# Make the built .deb + .AppImage downloadable from the run (for hand-testing).
|
# Make the built .deb + .AppImage downloadable from the run (for hand-testing).
|
||||||
# continue-on-error: the Forgejo artifact backend may not be configured yet; a
|
# continue-on-error: the Forgejo artifact backend may not be configured yet; a
|
||||||
# failed upload must not fail the build itself.
|
# failed upload must not fail the build itself.
|
||||||
@@ -91,6 +109,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
desktop/src-tauri/target/release/bundle/appimage/*.AppImage
|
desktop/src-tauri/target/release/bundle/appimage/*.AppImage
|
||||||
desktop/src-tauri/target/release/bundle/deb/*.deb
|
desktop/src-tauri/target/release/bundle/deb/*.deb
|
||||||
|
desktop/src-tauri/target/release/bundle/arch/*.pkg.tar.*
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
# Tag builds only: publish a real, versioned Fabled-Git Release with the
|
# Tag builds only: publish a real, versioned Fabled-Git Release with the
|
||||||
|
|||||||
+16
-1
@@ -59,8 +59,23 @@ backend/frontend push.
|
|||||||
- **Steps:** build the shared frontend (embedded by `generate_context!`) →
|
- **Steps:** build the shared frontend (embedded by `generate_context!`) →
|
||||||
`cargo tauri icon app-icon.png` (platform icon set from the committed 1024px
|
`cargo tauri icon app-icon.png` (platform icon set from the committed 1024px
|
||||||
source) → `cargo fmt --check` → `cargo clippy -D warnings` → `cargo test` →
|
source) → `cargo fmt --check` → `cargo clippy -D warnings` → `cargo test` →
|
||||||
`cargo tauri build` (produces `.deb` + `.AppImage`).
|
`cargo tauri build` (produces `.deb` + `.AppImage`) → de-bundle the AppImage's
|
||||||
|
graphics libs → verify the `.deb` → repackage for pacman.
|
||||||
- **`APPIMAGE_EXTRACT_AND_RUN=1`** is set: AppImage tooling FUSE-mounts by default
|
- **`APPIMAGE_EXTRACT_AND_RUN=1`** is set: AppImage tooling FUSE-mounts by default
|
||||||
and CI containers have no `/dev/fuse`.
|
and CI containers have no `/dev/fuse`.
|
||||||
|
- **Packaging tools used from the image** (none installed at job time, rule 5):
|
||||||
|
`dpkg-deb` / `dpkg-query` / `apt-cache` and `dpkg-shlibdeps` (from `dpkg-dev`,
|
||||||
|
pulled in by `build-essential`) for `desktop/packaging/deb/verify.sh`; `tar` +
|
||||||
|
a compressor for `desktop/packaging/arch/package-prebuilt.sh`. Both scripts
|
||||||
|
degrade gracefully rather than hard-failing on an absent optional tool:
|
||||||
|
`bsdtar` (`libarchive-tools`) is used for the pacman package's `.MTREE` when
|
||||||
|
present and skipped when not, compression falls back zstd → xz → gzip, and the
|
||||||
|
`.deb` clean-container install test runs only if a docker CLI is available.
|
||||||
|
Adding `libarchive-tools` + `zstd` + a docker CLI to `ci-tauri` would upgrade
|
||||||
|
those degraded paths, but none of them block a green build.
|
||||||
|
- **Not verifiable in CI:** the runner is Debian, so the pacman package cannot be
|
||||||
|
`pacman -U`-tested here. That step logs `.PKGINFO` + the full file listing so
|
||||||
|
the package is auditable from the run log; a real Arch install is the operator's
|
||||||
|
confirm.
|
||||||
- No Postgres lane (unchanged): the desktop app's local store + sync behavior is
|
- No Postgres lane (unchanged): the desktop app's local store + sync behavior is
|
||||||
verified on the operator's machine, not in CI.
|
verified on the operator's machine, not in CI.
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
# Maintainer: bvandeusen
|
|
||||||
#
|
|
||||||
# ThoughtSync desktop (Tauri v2) — native Arch package.
|
|
||||||
#
|
|
||||||
# Built from THIS repo checkout, from SOURCE, linked against your SYSTEM libraries.
|
|
||||||
# That is the whole point: it uses the host's graphics stack (webkit2gtk-4.1 + your
|
|
||||||
# GPU driver), which avoids the bundled-AppImage "EGL_BAD_PARAMETER" black window.
|
|
||||||
#
|
|
||||||
# Usage (from a clone of the thoughtsync repo):
|
|
||||||
# cd desktop/packaging/arch
|
|
||||||
# makepkg -si
|
|
||||||
#
|
|
||||||
pkgname=thoughtsync-desktop
|
|
||||||
pkgver=0.1.0
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc="ThoughtSync desktop — local-first Keep-style thought capture"
|
|
||||||
arch=('x86_64')
|
|
||||||
url="https://git.fabledsword.com/bvandeusen/thoughtsync"
|
|
||||||
license=('MIT')
|
|
||||||
depends=(
|
|
||||||
'webkit2gtk-4.1'
|
|
||||||
'gtk3'
|
|
||||||
'cairo'
|
|
||||||
'gdk-pixbuf2'
|
|
||||||
'glib2'
|
|
||||||
'libsoup3'
|
|
||||||
'pango'
|
|
||||||
'hicolor-icon-theme'
|
|
||||||
'desktop-file-utils'
|
|
||||||
'librsvg'
|
|
||||||
)
|
|
||||||
makedepends=('rust' 'nodejs' 'npm' 'git' 'pkgconf')
|
|
||||||
# Cargo's release profile already does its own LTO; don't let makepkg inject C-level
|
|
||||||
# LTO flags into the crates that build native code.
|
|
||||||
options=('!lto')
|
|
||||||
|
|
||||||
# Locate the repo root this PKGBUILD lives inside (desktop/packaging/arch/).
|
|
||||||
_repo() { git -C "$startdir" rev-parse --show-toplevel; }
|
|
||||||
|
|
||||||
pkgver() {
|
|
||||||
cd "$startdir"
|
|
||||||
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
local repo; repo="$(_repo)"
|
|
||||||
|
|
||||||
# 1) Build the shared Vue frontend — it's embedded into the binary at compile time
|
|
||||||
# (tauri generate_context!), so it must exist before the cargo build.
|
|
||||||
cd "$repo/frontend"
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# 2) Compile the native binary against SYSTEM libraries (no bundling, no tauri-cli).
|
|
||||||
cd "$repo/desktop/src-tauri"
|
|
||||||
cargo build --release
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
local repo; repo="$(_repo)"
|
|
||||||
|
|
||||||
install -Dm755 "$repo/desktop/src-tauri/target/release/thoughtsync-desktop" \
|
|
||||||
"$pkgdir/usr/bin/thoughtsync"
|
|
||||||
install -Dm644 "$repo/desktop/src-tauri/app-icon.png" \
|
|
||||||
"$pkgdir/usr/share/pixmaps/thoughtsync.png"
|
|
||||||
install -Dm644 "$startdir/thoughtsync.desktop" \
|
|
||||||
"$pkgdir/usr/share/applications/thoughtsync.desktop"
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,60 @@
|
|||||||
# ThoughtSync desktop — Arch package
|
# ThoughtSync desktop — Arch package
|
||||||
|
|
||||||
A native Arch/pacman package, built **from source** so it links against your
|
A **prebuilt** native pacman package (`.pkg.tar.zst`), published as an asset on
|
||||||
**system** libraries (webkit2gtk-4.1 + your GPU driver). This is the robust install
|
every ThoughtSync release. Nothing to compile, no toolchain to install.
|
||||||
on Arch/CachyOS: it avoids the bundled-AppImage `EGL_BAD_PARAMETER` black-window
|
|
||||||
problem entirely, and it installs a proper application-menu entry.
|
Installing natively on Arch matters for more than tidiness: pacman pulls
|
||||||
|
`webkit2gtk-4.1` itself and the app runs against your system's graphics stack,
|
||||||
|
which is what keeps the bundled-library `EGL_BAD_PARAMETER` black window
|
||||||
|
(issue 2021) from coming back. It also means the app is package-manager tracked
|
||||||
|
and uninstalls cleanly.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
From a clone of the `thoughtsync` repo:
|
Easiest — the one-command installer picks this package automatically on any
|
||||||
|
pacman system:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd desktop/packaging/arch
|
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/main/desktop/packaging/install.sh | sh
|
||||||
makepkg -si
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`makepkg` will pull the build tools (`rust`, `nodejs`, `npm`) and runtime deps
|
Or grab the `.pkg.tar.zst` from the
|
||||||
(`webkit2gtk-4.1`, `gtk3`, …) via pacman, compile the frontend + the Rust binary,
|
[latest release](https://git.fabledsword.com/bvandeusen/thoughtsync/releases/latest)
|
||||||
and install:
|
and install it directly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -U thoughtsync-desktop-*-x86_64.pkg.tar.zst
|
||||||
|
```
|
||||||
|
|
||||||
|
Either way you get:
|
||||||
|
|
||||||
- `/usr/bin/thoughtsync` — the app
|
- `/usr/bin/thoughtsync` — the app
|
||||||
- `/usr/share/applications/thoughtsync.desktop` — the menu entry
|
- `/usr/share/applications/thoughtsync.desktop` — the menu entry
|
||||||
- `/usr/share/pixmaps/thoughtsync.png` — the icon
|
- `/usr/share/icons/hicolor/*/apps/thoughtsync.png` — themed icons
|
||||||
|
|
||||||
Then launch **ThoughtSync** from your app menu (or run `thoughtsync`).
|
Launch **ThoughtSync** from your app menu, or run `thoughtsync`.
|
||||||
|
|
||||||
## Notes
|
Uninstall: `sudo pacman -R thoughtsync-desktop`.
|
||||||
|
|
||||||
- Builds in-place from the checkout (`makepkg` reads the committed state via git).
|
## How the package is built
|
||||||
- The compile takes a few minutes the first time (Rust release build).
|
|
||||||
- Uninstall: `sudo pacman -R thoughtsync-desktop`.
|
`package-prebuilt.sh` runs in CI (`.forgejo/workflows/desktop.yml`) and wraps the
|
||||||
|
binary the Linux build already produced into a pacman package — it does not
|
||||||
|
compile anything a second time.
|
||||||
|
|
||||||
|
Packaging a Debian-compiled binary for Arch is safe here because the binary
|
||||||
|
bundles nothing: it resolves `libwebkit2gtk-4.1.so.0`, `libgtk-3.so.0` and
|
||||||
|
`libsoup-3.0.so.0` by SONAME at runtime and those are identical on both distros,
|
||||||
|
SQLite is compiled in (`rusqlite` "bundled"), and glibc's forward compatibility
|
||||||
|
means building on Debian's older glibc and running on Arch's newer one is the
|
||||||
|
safe direction.
|
||||||
|
|
||||||
|
CI is Debian and has no `pacman`, so it cannot install-test the result. The build
|
||||||
|
step instead logs the package's `.PKGINFO` and complete file listing, so the
|
||||||
|
package is auditable from the run log; a real `pacman -U` is the final proof.
|
||||||
|
|
||||||
|
## Previously
|
||||||
|
|
||||||
|
This directory used to hold a source-build `PKGBUILD` requiring `makepkg -si`
|
||||||
|
with `rust` + `nodejs` + `npm` installed. It was removed once prebuilt packages
|
||||||
|
shipped — asking every user to install a compiler toolchain isn't distribution.
|
||||||
|
|||||||
Executable
+155
@@ -0,0 +1,155 @@
|
|||||||
|
#!/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: the source PKGBUILD this replaces required every user to 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 exactly what keeps issue 2021
|
||||||
|
# (bundled-lib EGL_BAD_PARAMETER black window) fixed.
|
||||||
|
#
|
||||||
|
# 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="$SRC_TAURI/target/release/thoughtsync-desktop"
|
||||||
|
OUT_DIR="${1:-$SRC_TAURI/target/release/bundle/arch}"
|
||||||
|
|
||||||
|
PKGNAME="thoughtsync-desktop"
|
||||||
|
PKGREL=1
|
||||||
|
PKGDESC="ThoughtSync desktop — local-first Keep-style thought capture"
|
||||||
|
URL="https://git.fabledsword.com/bvandeusen/thoughtsync"
|
||||||
|
LICENSE="MIT"
|
||||||
|
PACKAGER="ThoughtSync 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 tauri.conf.json value the
|
||||||
|
# .deb and the AppImage are stamped with, so all three artifacts on a release
|
||||||
|
# always agree. Plain grep — jq is not guaranteed in the CI image.
|
||||||
|
# `|| true` so a miss falls through to the explicit error below rather than
|
||||||
|
# aborting on pipefail with no explanation.
|
||||||
|
PKGVER="$(grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' "$SRC_TAURI/tauri.conf.json" |
|
||||||
|
head -1 | sed -E 's/.*"([^"]+)"$/\1/' || true)"
|
||||||
|
[ -n "$PKGVER" ] || { echo "ERROR: could not read version from tauri.conf.json" >&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/thoughtsync (not thoughtsync-desktop): matches the CLI name the
|
||||||
|
# AppImage installer symlinks into ~/.local/bin, so the command is the same
|
||||||
|
# whichever way the app was installed.
|
||||||
|
install -Dm755 "$BINARY" "$STAGE/usr/bin/thoughtsync"
|
||||||
|
install -Dm644 "$SCRIPT_DIR/thoughtsync.desktop" \
|
||||||
|
"$STAGE/usr/share/applications/thoughtsync.desktop"
|
||||||
|
|
||||||
|
# Themed icons, so the menu entry's `Icon=thoughtsync` 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/thoughtsync.png"
|
||||||
|
install -Dm644 "$SRC_TAURI/icons/128x128.png" \
|
||||||
|
"$STAGE/usr/share/icons/hicolor/128x128/apps/thoughtsync.png"
|
||||||
|
install -Dm644 "$SRC_TAURI/icons/128x128@2x.png" \
|
||||||
|
"$STAGE/usr/share/icons/hicolor/256x256/apps/thoughtsync.png"
|
||||||
|
install -Dm644 "$SRC_TAURI/icons/icon.png" \
|
||||||
|
"$STAGE/usr/share/icons/hicolor/512x512/apps/thoughtsync.png"
|
||||||
|
install -Dm644 "$SRC_TAURI/icons/icon.png" "$STAGE/usr/share/pixmaps/thoughtsync.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
|
||||||
|
} >"$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 (rule 5: 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")"
|
||||||
Executable
+145
@@ -0,0 +1,145 @@
|
|||||||
|
#!/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
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
#
|
#
|
||||||
# Fetches the LATEST published release for this machine's architecture and
|
# Fetches the LATEST published release for this machine's architecture and
|
||||||
# installs it, ending with a working app + menu entry. Native-first:
|
# installs it, ending with a working app + menu entry. Native-first:
|
||||||
# * Debian/Ubuntu (dpkg+apt) -> the native .deb (system libs; needs sudo).
|
# * Arch/CachyOS (pacman) -> the native .pkg.tar.zst (system libs; needs sudo).
|
||||||
# * everything else (Arch/CachyOS/Fedora/…) -> the de-bundled AppImage,
|
# * Debian/Ubuntu (dpkg+apt) -> the native .deb (system libs; needs sudo).
|
||||||
|
# * everything else (Fedora/openSUSE/…) -> the de-bundled AppImage,
|
||||||
# installed user-locally (no sudo). The AppImage's graphics libs are
|
# installed user-locally (no sudo). The AppImage's graphics libs are
|
||||||
# stripped in CI (see debundle-graphics.sh), so it uses the host GPU stack
|
# stripped in CI (see debundle-graphics.sh), so it uses the host GPU stack
|
||||||
# and renders where a stock Tauri AppImage would black-window (issue 2021).
|
# and renders where a stock Tauri AppImage would black-window (issue 2021).
|
||||||
@@ -45,25 +46,52 @@ json="$(curl -fsSL "$API/releases/latest" 2>/dev/null)" || die \
|
|||||||
# .AppImage/.deb URLs present are the asset download links.
|
# .AppImage/.deb URLs present are the asset download links.
|
||||||
appimage_url="$(printf '%s' "$json" | grep -oE 'https?://[^"]+\.AppImage' | head -1 || true)"
|
appimage_url="$(printf '%s' "$json" | grep -oE 'https?://[^"]+\.AppImage' | head -1 || true)"
|
||||||
deb_url="$(printf '%s' "$json" | grep -oE 'https?://[^"]+\.deb' | head -1 || true)"
|
deb_url="$(printf '%s' "$json" | grep -oE 'https?://[^"]+\.deb' | head -1 || true)"
|
||||||
|
pkg_url="$(printf '%s' "$json" | grep -oE 'https?://[^"]+\.pkg\.tar\.[a-z]+' | head -1 || true)"
|
||||||
version="$(printf '%s' "$json" | grep -oE '"tag_name":"[^"]+"' | head -1 | sed -E 's/.*:"([^"]+)".*/\1/')"
|
version="$(printf '%s' "$json" | grep -oE '"tag_name":"[^"]+"' | head -1 | sed -E 's/.*:"([^"]+)".*/\1/')"
|
||||||
[ -n "$appimage_url" ] || [ -n "$deb_url" ] || die "the latest release has no installable Linux asset."
|
[ -n "$appimage_url" ] || [ -n "$deb_url" ] || [ -n "$pkg_url" ] ||
|
||||||
|
die "the latest release has no installable Linux asset."
|
||||||
say "Latest release: ${version:-unknown}"
|
say "Latest release: ${version:-unknown}"
|
||||||
|
|
||||||
tmp="$(mktemp -d)"
|
tmp="$(mktemp -d)"
|
||||||
trap 'rm -rf "$tmp"' EXIT INT TERM
|
trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||||
|
|
||||||
|
# Both native paths install system-wide, so they need root. Resolved once here
|
||||||
|
# rather than duplicated per branch; the AppImage path below never calls this.
|
||||||
|
need_root() {
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then sudo=""; else
|
||||||
|
have sudo || die "a native install needs root; re-run as root or install sudo."
|
||||||
|
sudo="sudo"
|
||||||
|
fi
|
||||||
|
say "Installing (you may be prompted for your password)…"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- native pacman path (Arch/CachyOS/Manjaro) ------------------------------
|
||||||
|
# Preferred over the AppImage on Arch: pacman pulls webkit2gtk-4.1 itself and the
|
||||||
|
# app then runs against the host graphics stack, which is what keeps the
|
||||||
|
# EGL_BAD_PARAMETER black window (issue 2021) from coming back. It also means the
|
||||||
|
# app is tracked by the package manager and uninstalls cleanly.
|
||||||
|
if have pacman && [ -n "$pkg_url" ]; then
|
||||||
|
say "Arch-family system detected — installing the native pacman package"
|
||||||
|
# Keep the published filename: pacman -U expects a *.pkg.tar.* name and refuses
|
||||||
|
# a file that doesn't look like a package, whatever its actual contents.
|
||||||
|
pkg_file="$tmp/$(basename "$pkg_url")"
|
||||||
|
curl -fSL -o "$pkg_file" "$pkg_url"
|
||||||
|
need_root
|
||||||
|
$sudo pacman -U --noconfirm "$pkg_file"
|
||||||
|
say "Done. Launch ThoughtSync from your application menu, or run thoughtsync."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# --- native .deb path (Debian/Ubuntu) ---------------------------------------
|
# --- native .deb path (Debian/Ubuntu) ---------------------------------------
|
||||||
if have dpkg && have apt-get && [ -n "$deb_url" ]; then
|
if have dpkg && have apt-get && [ -n "$deb_url" ]; then
|
||||||
say "Debian-family system detected — installing the native .deb"
|
say "Debian-family system detected — installing the native .deb"
|
||||||
curl -fSL -o "$tmp/thoughtsync.deb" "$deb_url"
|
curl -fSL -o "$tmp/thoughtsync.deb" "$deb_url"
|
||||||
if [ "$(id -u)" -eq 0 ]; then sudo=""; else
|
need_root
|
||||||
have sudo || die "installing the .deb needs root; re-run as root or install sudo."
|
# apt-get resolves the .deb's dependencies (webkit2gtk etc.). dpkg is the
|
||||||
sudo="sudo"
|
# fallback if this apt is too old for local-file installs — it leaves the deps
|
||||||
fi
|
# unconfigured, so `apt-get -f install` is what actually completes that path.
|
||||||
say "Installing (you may be prompted for your password)…"
|
$sudo apt-get install -y "$tmp/thoughtsync.deb" ||
|
||||||
# apt-get resolves the .deb's dependencies (webkit2gtk etc.); dpkg is the
|
{ $sudo dpkg -i "$tmp/thoughtsync.deb" || true; $sudo apt-get -f install -y; }
|
||||||
# fallback if this apt is too old for local-file installs.
|
|
||||||
$sudo apt-get install -y "$tmp/thoughtsync.deb" || $sudo dpkg -i "$tmp/thoughtsync.deb"
|
|
||||||
say "Done. Launch ThoughtSync from your application menu."
|
say "Done. Launch ThoughtSync from your application menu."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# The build + de-bundle steps run first; this consumes their output:
|
# The build + de-bundle steps run first; this consumes their output:
|
||||||
# desktop/src-tauri/target/release/bundle/appimage/*.AppImage (de-bundled)
|
# desktop/src-tauri/target/release/bundle/appimage/*.AppImage (de-bundled)
|
||||||
# desktop/src-tauri/target/release/bundle/deb/*.deb
|
# desktop/src-tauri/target/release/bundle/deb/*.deb
|
||||||
|
# desktop/src-tauri/target/release/bundle/arch/*.pkg.tar.* (prebuilt pacman)
|
||||||
#
|
#
|
||||||
# Instance-agnostic: server + repo come from the runner's github.* context
|
# Instance-agnostic: server + repo come from the runner's github.* context
|
||||||
# (Forgejo populates them for compatibility), so nothing is hardcoded to one host.
|
# (Forgejo populates them for compatibility), so nothing is hardcoded to one host.
|
||||||
@@ -42,6 +43,7 @@ shopt -s nullglob
|
|||||||
ASSETS=(
|
ASSETS=(
|
||||||
"$BUNDLE_ROOT"/appimage/*.AppImage
|
"$BUNDLE_ROOT"/appimage/*.AppImage
|
||||||
"$BUNDLE_ROOT"/deb/*.deb
|
"$BUNDLE_ROOT"/deb/*.deb
|
||||||
|
"$BUNDLE_ROOT"/arch/*.pkg.tar.*
|
||||||
)
|
)
|
||||||
if [ ${#ASSETS[@]} -eq 0 ]; then
|
if [ ${#ASSETS[@]} -eq 0 ]; then
|
||||||
echo "ERROR: no bundles under $BUNDLE_ROOT — did the tauri build run?" >&2
|
echo "ERROR: no bundles under $BUNDLE_ROOT — did the tauri build run?" >&2
|
||||||
@@ -74,7 +76,7 @@ first_id() { grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE
|
|||||||
echo "==> Creating release for $TAG"
|
echo "==> Creating release for $TAG"
|
||||||
BODY=$(cat <<JSON
|
BODY=$(cat <<JSON
|
||||||
{"tag_name":"$TAG","name":"ThoughtSync $TAG","draft":false,"prerelease":false,
|
{"tag_name":"$TAG","name":"ThoughtSync $TAG","draft":false,"prerelease":false,
|
||||||
"body":"ThoughtSync desktop $TAG. Linux AppImage (de-bundled graphics — renders on any GPU/Wayland setup) + .deb.\n\nInstall / update:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/main/desktop/packaging/install.sh | sh\n\`\`\`"}
|
"body":"ThoughtSync desktop $TAG.\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.zst\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/main/desktop/packaging/install.sh | sh\n\`\`\`"}
|
||||||
JSON
|
JSON
|
||||||
)
|
)
|
||||||
# 409 = a release for this tag already exists (re-run) — fall through to lookup.
|
# 409 = a release for this tag already exists (re-run) — fall through to lookup.
|
||||||
|
|||||||
@@ -38,6 +38,11 @@
|
|||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.png"
|
"icons/icon.png"
|
||||||
]
|
],
|
||||||
|
"linux": {
|
||||||
|
"deb": {
|
||||||
|
"depends": ["libwebkit2gtk-4.1-0", "libgtk-3-0"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user