164 lines
7.6 KiB
Bash
Executable file
164 lines
7.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Install or update TVAnarchy from the forge.black release channel — no Xcode
|
|
# toolchain or source needed. First run installs the latest release; later runs
|
|
# no-op when already current. This is how every node EXCEPT the build box (plum)
|
|
# gets the app; plum cuts releases with tools/release.sh.
|
|
#
|
|
# Per-OS behavior (the fleet: iOS, macOS, Ubuntu, Bluefin, Windows):
|
|
# macOS → TVAnarchy-<tag>.zip → /Applications (admin) or ~/Applications
|
|
# Linux → TVAnarchy-<tag>-linux-<arch>.tar.gz → /opt/tv-anarchy (classic,
|
|
# writable) or ~/.local/opt/tv-anarchy (non-root; always on
|
|
# immutable/ostree systems like Bluefin, whose /usr is read-only)
|
|
# Windows → TVAnarchy-<tag>-windows-<arch>.zip → %LOCALAPPDATA%\Programs\TVAnarchy
|
|
# (per-user, no elevation; run under Git Bash/MSYS)
|
|
# iOS → not served here: no shell. Install via Xcode (TVAnarchyiOS scheme)
|
|
# or TestFlight/sideload — see docs/operations.md.
|
|
# A release that lacks the asset for this platform fails with the exact missing
|
|
# asset name (today only the macOS asset is published).
|
|
#
|
|
# Usage: tools/update.sh [--force]
|
|
# Needs: curl, python3 (JSON parsing). macOS also: ditto, PlistBuddy (built in).
|
|
# Config (env, all optional — defaults target the mesh Forgejo):
|
|
# FORGEJO_API=http://10.9.0.4:3000 FORGEJO_OWNER=lilith FORGEJO_REPO=tv-anarchy
|
|
# FORGEJO_TOKEN / ~/.config/tv-anarchy/forgejo-token (only if the repo is private)
|
|
# TVANARCHY_DEST=<path> explicit install destination override
|
|
set -euo pipefail
|
|
|
|
API="${FORGEJO_API:-http://10.9.0.4:3000}"
|
|
OWNER="${FORGEJO_OWNER:-lilith}"
|
|
REPO="${FORGEJO_REPO:-tv-anarchy}"
|
|
FORCE=""; [ "${1:-}" = "--force" ] && FORCE=1
|
|
|
|
# --- platform --------------------------------------------------------------
|
|
case "$(uname -s)" in
|
|
Darwin) OS=mac ;;
|
|
Linux) OS=linux ;;
|
|
MINGW*|MSYS*|CYGWIN*) OS=windows ;;
|
|
*) echo "✗ unsupported platform '$(uname -s)' — TVAnarchy ships for macOS, Linux, Windows (iOS via Xcode/TestFlight)." >&2; exit 1 ;;
|
|
esac
|
|
ARCH="$(uname -m)" # arm64 / x86_64 / aarch64
|
|
|
|
# True on image-based (ostree/bootc) Linux — Bluefin, Silverblue, etc. Their
|
|
# /usr is read-only and /opt is machine-local; user-scope installs are the norm.
|
|
is_immutable_linux() { [ -e /run/ostree-booted ] || [ -e /usr/lib/bootc ]; }
|
|
|
|
# Where TVAnarchy installs on THIS node. Mirrors build-install.sh's macOS logic
|
|
# (kept in sync) so this script stays curl-able standalone.
|
|
resolve_dest() {
|
|
if [ -n "${TVANARCHY_DEST:-}" ]; then printf '%s\n' "$TVANARCHY_DEST"; return; fi
|
|
case "$OS" in
|
|
mac)
|
|
if [ -w /Applications ]; then printf '/Applications/TVAnarchy.app\n'
|
|
else printf '%s/Applications/TVAnarchy.app\n' "$HOME"; fi ;;
|
|
linux)
|
|
if ! is_immutable_linux && [ -w /opt ]; then printf '/opt/tv-anarchy\n'
|
|
else printf '%s/.local/opt/tv-anarchy\n' "$HOME"; fi ;;
|
|
windows)
|
|
printf '%s/Programs/TVAnarchy\n' "${LOCALAPPDATA:-$HOME/AppData/Local}" ;;
|
|
esac
|
|
}
|
|
DEST="$(resolve_dest)"
|
|
|
|
# The release asset this platform consumes.
|
|
asset_name() {
|
|
case "$OS" in
|
|
mac) printf 'TVAnarchy-%s.zip\n' "$1" ;;
|
|
linux) printf 'TVAnarchy-%s-linux-%s.tar.gz\n' "$1" "$ARCH" ;;
|
|
windows) printf 'TVAnarchy-%s-windows-%s.zip\n' "$1" "$ARCH" ;;
|
|
esac
|
|
}
|
|
|
|
TOKEN="${FORGEJO_TOKEN:-}"
|
|
TOKEN_FILE="$HOME/.config/tv-anarchy/forgejo-token"
|
|
[ -z "$TOKEN" ] && [ -f "$TOKEN_FILE" ] && TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
|
|
auth=(); [ -n "$TOKEN" ] && auth=(-H "Authorization: token $TOKEN")
|
|
|
|
# --- resolve the latest release ---------------------------------------------
|
|
latest="$(curl -fsL "${auth[@]}" "$API/api/v1/repos/$OWNER/$REPO/releases/latest" 2>/dev/null)" || {
|
|
code="$(curl -s -o /dev/null -m 8 -w '%{http_code}' "${auth[@]}" "$API/api/v1/repos/$OWNER/$REPO/releases/latest" || true)"
|
|
if [ "$code" = "404" ]; then
|
|
echo "✗ $OWNER/$REPO has no releases yet — cut one on the build box with tools/release.sh." >&2
|
|
else
|
|
echo "✗ couldn't reach Forgejo at $API (HTTP ${code:-none} — mesh down, or repo private + no token?)." >&2
|
|
fi
|
|
exit 1; }
|
|
TAG="$(printf '%s' "$latest" | python3 -c 'import sys,json; print(json.load(sys.stdin)["tag_name"])')"
|
|
WANT="$(asset_name "$TAG")"
|
|
URL="$(printf '%s' "$latest" | python3 -c '
|
|
import sys, json
|
|
want = sys.argv[1]
|
|
r = json.load(sys.stdin)
|
|
a = next((a for a in r.get("assets", []) if a["name"] == want), None)
|
|
print(a["browser_download_url"] if a else "")
|
|
' "$WANT")"
|
|
if [ -z "$URL" ]; then
|
|
echo "✗ release $TAG has no asset '$WANT' for this platform ($OS/$ARCH)." >&2
|
|
echo " published assets:" >&2
|
|
printf '%s' "$latest" | python3 -c 'import sys,json; [print(" ", a["name"]) for a in json.load(sys.stdin).get("assets", [])]' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- compare to the installed copy (releases are tagged v<marketing version>)
|
|
# macOS reads the bundle plist; Linux/Windows read the .release-tag stamp this
|
|
# script writes on install.
|
|
installed_tag() {
|
|
if [ "$OS" = mac ]; then
|
|
[ -d "$1" ] && printf 'v%s\n' "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$1/Contents/Info.plist" 2>/dev/null || echo '?')"
|
|
else
|
|
[ -f "$1/.release-tag" ] && cat "$1/.release-tag"
|
|
fi
|
|
}
|
|
installed="$(installed_tag "$DEST" || true)"; installed="${installed:-none}"; stale=""
|
|
if [ "$installed" = "none" ] && [ "$OS" = mac ] && [ -z "${TVANARCHY_DEST:-}" ]; then
|
|
# An older mac install may sit at the other auto candidate (the layout moved
|
|
# from ~/Applications to /Applications). Count it for the version line and
|
|
# migrate it away after the install. Skipped under an explicit TVANARCHY_DEST
|
|
# (a test install must not touch the real one).
|
|
for other in "/Applications/TVAnarchy.app" "$HOME/Applications/TVAnarchy.app"; do
|
|
if [ "$other" != "$DEST" ] && [ -d "$other" ]; then
|
|
stale="$other"; installed="$(installed_tag "$other" || true) (at $other)"
|
|
fi
|
|
done
|
|
fi
|
|
if [ "$installed" = "$TAG" ] && [ -z "$FORCE" ]; then
|
|
echo "✓ already on $TAG — up to date (use --force to reinstall)."; exit 0
|
|
fi
|
|
echo "→ $installed → $TAG ($OS/$ARCH)"
|
|
|
|
# --- download, unpack, swap in ----------------------------------------------
|
|
TMP="$(mktemp -d "${TMPDIR:-/tmp}/tvanarchy-update.XXXXXX")"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
echo "→ downloading $WANT"
|
|
curl -fsSL "${auth[@]}" -o "$TMP/asset" "$URL"
|
|
|
|
mkdir -p "$TMP/unpacked"
|
|
case "$WANT" in
|
|
*.tar.gz) tar -xzf "$TMP/asset" -C "$TMP/unpacked" ;;
|
|
*.zip) if [ "$OS" = mac ]; then ditto -x -k "$TMP/asset" "$TMP/unpacked"
|
|
else unzip -q "$TMP/asset" -d "$TMP/unpacked"; fi ;;
|
|
esac
|
|
|
|
mkdir -p "$(dirname "$DEST")"
|
|
case "$OS" in
|
|
mac)
|
|
src="$(/usr/bin/find "$TMP/unpacked" -maxdepth 2 -name 'TVAnarchy.app' -print -quit)"
|
|
[ -n "$src" ] || { echo "✗ no TVAnarchy.app inside $WANT." >&2; exit 1; }
|
|
rm -rf "$DEST"; ditto "$src" "$DEST"
|
|
# Unsigned build copied across machines → clear Gatekeeper quarantine.
|
|
xattr -dr com.apple.quarantine "$DEST" 2>/dev/null || true
|
|
if [ -n "$stale" ]; then rm -rf "$stale" && echo " removed stale copy at $stale"; fi
|
|
relaunch="quit any running TVAnarchy and relaunch to pick this up."
|
|
;;
|
|
linux|windows)
|
|
# Convention: the archive holds a single top-level TVAnarchy/ directory.
|
|
src="$(find "$TMP/unpacked" -maxdepth 1 -mindepth 1 -type d -print -quit)"
|
|
[ -n "$src" ] || { echo "✗ no payload directory inside $WANT." >&2; exit 1; }
|
|
rm -rf "$DEST"; mv "$src" "$DEST"
|
|
printf '%s\n' "$TAG" > "$DEST/.release-tag"
|
|
[ "$OS" = linux ] && chmod +x "$DEST"/tvanarchy* 2>/dev/null || true
|
|
relaunch="restart TVAnarchy to pick this up."
|
|
;;
|
|
esac
|
|
|
|
echo "✓ installed $TAG → $DEST"
|
|
echo " $relaunch"
|