#!/bin/sh # Termic installer for macOS and Linux. # # curl -fsSL https://termic.dev/install.sh | sh # # macOS: puts Termic.app in /Applications (or ~/Applications if that is not # writable). Linux: puts the AppImage at ~/Applications/Termic.AppImage, a # file you own, so the in-app updater can replace it in place. No root # needed on Linux. Re-running it updates an existing install. On Linux, an # app launcher entry that opens a copy elsewhere is pointed at the new one, # and that old copy is removed. # # Environment: # TERMIC_VERSION=1.20.0 install that release instead of the latest # TERMIC_DIR=/some/dir install there instead of the default # TERMIC_NO_LAUNCH=1 do not start Termic when done # # Source: https://github.com/simion/termic.dev/blob/main/public/install.sh set -eu REPO="simion/termic" say() { printf '%s\n' "$*"; } fail() { printf 'termic install: %s\n' "$*" >&2; exit 1; } command -v curl >/dev/null 2>&1 || fail "curl is required." # Everything below is inside main so a download cut off halfway by # `curl | sh` runs nothing at all. main() { if [ -n "${TERMIC_VERSION:-}" ]; then version="${TERMIC_VERSION#v}" else # /releases/latest redirects to /releases/tag/vX.Y.Z; no API rate limit. url=$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest") \ || fail "could not reach GitHub to find the latest release." version="${url##*/v}" fi case "$version" in [0-9]*.[0-9]*.[0-9]*) ;; *) fail "could not work out the latest version (got '$version')." ;; esac base="https://github.com/$REPO/releases/download/v$version" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT INT TERM case "$(uname -s)" in Darwin) install_macos ;; Linux) install_linux ;; *) fail "unsupported OS $(uname -s). Windows: download the installer from https://github.com/$REPO/releases/latest" ;; esac } # The AppImage path in a desktop entry's Exec= line, quoting undone. entry_exec() { v=$(sed -n 's/^Exec=//p' "$1" | head -n 1) v="${v% %u}" case "$v" in \"*\") v="${v#\"}"; v="${v%\"}"; v=$(printf '%s' "$v" | sed 's/\\\(.\)/\1/g') ;; esac printf '%s' "$v" } fetch() { # url dest curl -fL --progress-bar -o "$2" "$1" || fail "download failed: $1" } install_macos() { # The universal build runs natively on Apple Silicon and Intel. dir="${TERMIC_DIR:-/Applications}" if [ -z "${TERMIC_DIR:-}" ] && [ ! -w "$dir" ]; then dir="$HOME/Applications"; fi mkdir -p "$dir" say "Downloading Termic $version for macOS..." fetch "$base/Termic.app.tar.gz" "$tmp/Termic.app.tar.gz" tar -xzf "$tmp/Termic.app.tar.gz" -C "$tmp" [ -d "$tmp/Termic.app" ] || fail "the download did not contain Termic.app." # Only the copy being replaced: a Termic run from anywhere else (a beta, a # dev build, another folder) is left alone. Ask it to quit so its tasks # save, and wait for it rather than guessing a delay. if pgrep -qf "^$dir/Termic.app/Contents/MacOS/" 2>/dev/null; then say "Termic is running from $dir. Quitting it to replace it..." osascript -e "tell application \"$dir/Termic.app\" to quit" >/dev/null 2>&1 || true i=0 while pgrep -qf "^$dir/Termic.app/Contents/MacOS/" 2>/dev/null; do i=$((i + 1)); [ "$i" -gt 30 ] && fail "Termic did not quit. Quit it and run this again." sleep 1 done fi rm -rf "$dir/Termic.app" mv "$tmp/Termic.app" "$dir/Termic.app" say "Installed $dir/Termic.app" if [ -z "${TERMIC_NO_LAUNCH:-}" ]; then open "$dir/Termic.app"; fi } install_linux() { case "$(uname -m)" in x86_64|amd64) ;; *) fail "only x86_64 Linux builds exist so far (this machine is $(uname -m))." ;; esac # The launcher entry Termic writes from Settings (src-tauri/src/linux_desktop.rs). entry="${XDG_DATA_HOME:-$HOME/.local/share}/applications/termic.desktop" linked="" [ -f "$entry" ] && linked=$(entry_exec "$entry") dest="${TERMIC_DIR:-$HOME/Applications}/Termic.AppImage" mkdir -p "$(dirname "$dest")" say "Downloading Termic $version for Linux..." fetch "$base/Termic_${version}_amd64.AppImage" "$tmp/Termic.AppImage" chmod +x "$tmp/Termic.AppImage" mv -f "$tmp/Termic.AppImage" "$dest" say "Installed $dest" # A launcher entry for a copy that is gone (or elsewhere, with TERMIC_DIR) # would open nothing: point it here instead. if [ -f "$entry" ] && [ "$linked" != "$dest" ]; then q="$dest" case "$q" in *[!A-Za-z0-9/._+-]*) q="\"$(printf '%s' "$q" | sed 's/[\\"]/\\&/g')\"" ;; esac EXEC_LINE="Exec=$q %u" awk '/^Exec=/ { print ENVIRON["EXEC_LINE"]; next } { print }' "$entry" > "$tmp/termic.desktop" \ && cat "$tmp/termic.desktop" > "$entry" command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$(dirname "$entry")" >/dev/null 2>&1 || true say "Pointed the app launcher entry at it." # The copy the launcher used to open is the old install: moved, not kept, # so there is one Termic. case "$linked" in *.AppImage) [ -f "$linked" ] && rm -f "$linked" && say "Removed the old copy at $linked." ;; esac fi # Copies downloaded by hand earlier. Not deleted (they are yours), just named. for f in "$HOME"/Termic*.AppImage "$HOME"/Downloads/Termic*.AppImage "$HOME"/Desktop/Termic*.AppImage "$HOME"/Applications/Termic*.AppImage; do [ -f "$f" ] && [ "$f" != "$dest" ] && say "An older copy is still at $f. You can delete it." done # The AppImage mounts itself through FUSE. Its runtime is static, so no # libfuse2 is needed, only the fusermount helper, which most desktops ship. if ! command -v fusermount3 >/dev/null 2>&1 && ! command -v fusermount >/dev/null 2>&1; then say "" say "This system has no FUSE (fusermount), which the AppImage mounts itself with." say "Install your distro's fuse3 package (Ubuntu/Debian: sudo apt install fuse3)," say "or run it without FUSE: APPIMAGE_EXTRACT_AND_RUN=1 $dest" return fi if [ -z "${TERMIC_NO_LAUNCH:-}" ] && { [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; }; then # Detached, so closing this terminal does not take Termic with it. On # first run it offers to add itself to your app launcher. nohup "$dest" >/dev/null 2>&1 & if [ -f "$entry" ]; then say "Starting Termic." else say "Starting Termic. It will offer to add itself to your app launcher."; fi else say "Run it with: $dest" fi } main "$@"