mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
The last platform from #306: a Linux install running as an AppImage can now download, verify, and apply a release from inside the app, through the same tty7-updater helper the macOS (#309) and Windows (#330) paths use. Tarball and distro installs are deliberately untouched — they keep the named-package hint and the release page, because replacing a file a package manager may own is not this code's call to make. The installed artifact is one file, the path $APPIMAGE names, so the install is the simplest of the three platforms: stage the download beside the image (two renames only stay atomic on one filesystem), verify, swap, relaunch, and restore the preserved previous image if the new one does not survive its launch grace. What is Linux-shaped about it is the mount: the image the GUI runs from is FUSE-mounted by the AppImage runtime and torn down when the app exits, which is the moment the installer starts working — so the GUI copies the helper out of the mount into staging and runs the copy, the way the Windows path runs a private copy because Setup replaces the installed one. The daemon is left running throughout, as on macOS: nothing on Linux locks a running executable's file, and the panes it serves are the reason the update restarts only the GUI. The swap also carries the installed image's own mode onto its replacement, so a 0700 image stays private and the download's missing execute bit never reaches the installation. Verification holds the issue's requirements with what an unsigned ELF can offer: the bytes must match the release's checksums.txt, the file must actually be a type-2 AppImage — a mis-published asset fails with a name instead of at launch — and the image must state the version it claims. That statement is new: bundle-appimage.sh stamps X-AppImage-Version into the desktop entry, and the updater reads it back with one --appimage-extract, answered by the runtime before any application code and without FUSE. The same pass requires the new image to bundle its own tty7-updater, because an image without one would install fine and then be the last version that ever could. release.yml and nightly.yml now build the updater on the Linux leg and bundle it into the AppImage, and both check the packaged image for the same facts the updater checks on a user's machine — helper present, version stamped — so a packaging mistake fails the workflow instead of the update. The first release carrying this can only bootstrap: images already installed predate the helper and keep the manual hint, so the first complete in-app update is the release after it.
113 lines
4.9 KiB
Bash
Executable File
113 lines
4.9 KiB
Bash
Executable File
#!/bin/bash
|
||
# Usage: bundle-appimage.sh <target-triple> <arch-label>
|
||
# Package the release binary into a self-contained AppImage:
|
||
# dist/tty7-<version>-linux-<arch>.AppImage
|
||
#
|
||
# Unlike the bare tarball (bundle-linux.sh), this bundles the x11/wayland/xkb/
|
||
# fontconfig/freetype runtime libraries alongside the binary, so it launches on
|
||
# distros that don't ship the exact same set Ubuntu does — Fedora, Arch, etc.
|
||
# (glibc itself is NOT bundled — an AppImage still needs the host glibc to be
|
||
# >= the build machine's, so the release runner's Ubuntu sets the floor.)
|
||
#
|
||
# Completion signatures are loaded at runtime relative to the executable
|
||
# (<exe-dir>/completions — see terminal::signature), so they go beside the
|
||
# binary at usr/bin/completions inside the AppDir.
|
||
set -euo pipefail
|
||
|
||
TARGET="$1"
|
||
ARCH="$2"
|
||
# Anchored on `= "` — see the note in bundle-macos.sh: the root manifest leads
|
||
# with `version.workspace = true`, which a bare `^version` match would return
|
||
# verbatim as the "version" and bake into every asset filename.
|
||
VERSION="$(grep -m1 '^version = "' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')"
|
||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||
echo "bundle-appimage: could not read a version from Cargo.toml (got '$VERSION')" >&2
|
||
exit 1
|
||
fi
|
||
NAME="tty7-${VERSION}-linux-${ARCH}"
|
||
|
||
# AppImage tools need FUSE to self-mount; CI runners usually lack it, so extract
|
||
# and run instead. Harmless on machines that do have FUSE.
|
||
export APPIMAGE_EXTRACT_AND_RUN=1
|
||
|
||
# linuxdeploy is published per-arch; map Rust's arch label to its naming.
|
||
case "$ARCH" in
|
||
x86_64) LD_ARCH=x86_64 ;;
|
||
arm64 | aarch64) LD_ARCH=aarch64 ;;
|
||
*) echo "unsupported arch for AppImage: $ARCH" >&2; exit 1 ;;
|
||
esac
|
||
|
||
TOOLS="$(mktemp -d)"
|
||
LINUXDEPLOY="$TOOLS/linuxdeploy-${LD_ARCH}.AppImage"
|
||
APPIMAGETOOL="$TOOLS/appimagetool-${LD_ARCH}.AppImage"
|
||
curl -fsSL -o "$LINUXDEPLOY" \
|
||
"https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${LD_ARCH}.AppImage"
|
||
curl -fsSL -o "$APPIMAGETOOL" \
|
||
"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${LD_ARCH}.AppImage"
|
||
chmod +x "$LINUXDEPLOY" "$APPIMAGETOOL"
|
||
|
||
# NB: don't wipe dist/ — the tarball step (bundle-linux.sh) runs first and its
|
||
# artifact must survive. Only clean our own AppDir.
|
||
APPDIR="dist/AppDir"
|
||
rm -rf "$APPDIR"
|
||
mkdir -p "$APPDIR/usr/bin"
|
||
|
||
cp "target/${TARGET}/release/tty7-app" "$APPDIR/usr/bin/tty7-app"
|
||
chmod +x "$APPDIR/usr/bin/tty7-app"
|
||
|
||
# The CLI, beside the GUI as everywhere else. Unlike the tarball, an AppImage is
|
||
# mounted at a fresh /tmp/.mount_XXXX per run, so `core::cli_install` must copy
|
||
# this onto PATH rather than symlink it — a link into the mount dies the moment
|
||
# the app exits. That branch keys off $APPIMAGE, which the runtime sets.
|
||
cp "target/${TARGET}/release/tty7" "$APPDIR/usr/bin/tty7"
|
||
chmod +x "$APPDIR/usr/bin/tty7"
|
||
|
||
# The in-app updater, beside the GUI the way every platform ships it. The GUI
|
||
# copies it out of the mount into its staging directory before use — the mount
|
||
# is gone by the time an install runs (see src/bin/tty7-updater.rs).
|
||
cp "target/${TARGET}/release/tty7-updater" "$APPDIR/usr/bin/tty7-updater"
|
||
chmod +x "$APPDIR/usr/bin/tty7-updater"
|
||
|
||
# A desktop entry + icon are mandatory AppImage metadata; linuxdeploy places
|
||
# them and generates AppRun. Icon basename must match the desktop's Icon= key.
|
||
cat > "$TOOLS/tty7.desktop" <<'DESKTOP'
|
||
[Desktop Entry]
|
||
Type=Application
|
||
Name=tty7
|
||
Comment=A fast, native terminal
|
||
Exec=tty7-app
|
||
Icon=tty7
|
||
Categories=System;TerminalEmulator;
|
||
Terminal=false
|
||
StartupWMClass=tty7
|
||
DESKTOP
|
||
# The release version, stamped where the in-app updater can read it back with
|
||
# one `--appimage-extract` and no mount: a downloaded image must state the
|
||
# version it claims before it may replace the installed one (`verify_update`
|
||
# in src/bin/tty7-updater.rs). X-AppImage-Version is the AppImage convention
|
||
# for exactly this. Appended outside the heredoc, which is quoted on purpose.
|
||
echo "X-AppImage-Version=${VERSION}" >> "$TOOLS/tty7.desktop"
|
||
# linuxdeploy only accepts fixed icon resolutions (…256, 384, 512 — NOT the
|
||
# source's 1024), so downscale to 256×256.
|
||
convert assets/app-icon.png -resize 256x256 "$TOOLS/tty7.png"
|
||
|
||
# Phase 1 — populate the AppDir: copy in dependent libs (ldd + patchelf) and
|
||
# install the desktop/icon into their standard locations.
|
||
"$LINUXDEPLOY" \
|
||
--appdir "$APPDIR" \
|
||
--executable "$APPDIR/usr/bin/tty7-app" \
|
||
--desktop-file "$TOOLS/tty7.desktop" \
|
||
--icon-file "$TOOLS/tty7.png"
|
||
|
||
# Runtime-loaded completion specs live beside the binary (not bundled by
|
||
# linuxdeploy, which only tracks ELF deps), so drop them in after populate.
|
||
mkdir -p "$APPDIR/usr/bin/completions"
|
||
cp assets/completions/*.json "$APPDIR/usr/bin/completions/"
|
||
|
||
# Phase 2 — pack the finished AppDir. Done separately from linuxdeploy so the
|
||
# completions added above are included.
|
||
"$APPIMAGETOOL" "$APPDIR" "dist/${NAME}.AppImage"
|
||
chmod +x "dist/${NAME}.AppImage"
|
||
rm -rf "$APPDIR" "$TOOLS"
|
||
echo "✅ dist/${NAME}.AppImage"
|