#!/bin/sh
# Termalin desktop app installer for Linux — no root needed.
#
#   curl -fsSL https://termal.in/install.sh | bash
#
# Installs the latest Termalin AppImage for your user:
#   ~/.local/share/termalin/Termalin.AppImage    the app (updates itself in place)
#   ~/.local/bin/termalin                        command on your PATH
#   ~/.local/share/applications/in.termal.Termalin.desktop   menu / KRunner entry
#   ~/.local/share/icons/hicolor/512x512/apps/in.termal.Termalin.png
#
# Run it again to update. Remove with:
#
#   curl -fsSL https://termal.in/install.sh | bash -s -- --uninstall
#
# The download is checked against the release's signature (made offline with
# the Termalin release key, the same one the app checks its updates with) and
# its SHA-256 before anything is installed.
#
# (This is the desktop app. To reach a server from the web terminal, install
# the Connector on that server instead: https://termal.in/tunnel-install.sh)

set -eu

SERVER="${TERMALIN_SERVER:-https://termal.in}"
DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
APP_DIR="$DATA_HOME/termalin"
APP="$APP_DIR/Termalin.AppImage"
BIN_DIR="$HOME/.local/bin"
BIN="$BIN_DIR/termalin"
DESKTOP_DIR="$DATA_HOME/applications"
DESKTOP="$DESKTOP_DIR/in.termal.Termalin.desktop"
ICON_DIR="$DATA_HOME/icons/hicolor/512x512/apps"
ICON="$ICON_DIR/in.termal.Termalin.png"

# Public half of the Termalin release key (Ed25519), as in the app
# (kUpdateSigningPubKeys in lib/core/security/trust.dart).
TERMALIN_RELEASE_PUBKEY='-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEARTEbmEbGjuZOPdJW8moky5D7IviDJ5vC/6Z3PRjOjnU=
-----END PUBLIC KEY-----'

say() { printf '%s\n' "$*"; }
die() { printf 'termalin install: %s\n' "$*" >&2; exit 1; }

refresh_menus() {
  command -v update-desktop-database >/dev/null 2>&1 &&
    update-desktop-database -q "$DESKTOP_DIR" 2>/dev/null || true
  command -v gtk-update-icon-cache >/dev/null 2>&1 &&
    gtk-update-icon-cache -q -t "$DATA_HOME/icons/hicolor" 2>/dev/null || true
  # KDE (menus and KRunner) rebuilds its cache on its own after a while;
  # this makes the entry show up right away.
  for k in kbuildsycoca6 kbuildsycoca5; do
    if command -v "$k" >/dev/null 2>&1; then
      "$k" --noincremental >/dev/null 2>&1 || true
      break
    fi
  done
}

uninstall() {
  # Only our own files; the link only if it still points at our AppImage.
  if [ -L "$BIN" ] && [ "$(readlink "$BIN")" = "$APP" ]; then rm -f "$BIN"; fi
  rm -f "$APP" "$DESKTOP" "$ICON"
  rmdir "$APP_DIR" 2>/dev/null || true
  refresh_menus
  say "Termalin removed. Your settings and saved data (~/.termalin and the app's data folder) were kept;"
  say "delete them yourself if you want a clean slate."
}

FORCE=0
for arg in "$@"; do
  case "$arg" in
    --uninstall|--remove) uninstall; exit 0 ;;
    --force) FORCE=1 ;;
    -h|--help)
      sed -n '2,22p' "$0" 2>/dev/null | sed 's/^# \{0,1\}//' || true
      exit 0
      ;;
    *) die "unknown option: $arg (use --uninstall or --force)" ;;
  esac
done

[ "$(uname -s)" = Linux ] || die "this installer is for Linux. Windows and macOS: https://termal.in/download"
case "$(uname -m)" in
  x86_64|amd64) ;;
  *) die "Termalin for Linux is built for x86_64 only (this machine is $(uname -m))." ;;
esac
[ "$(id -u)" -ne 0 ] || die "run it as your own user, not as root (it installs into your home folder; no sudo needed)."
[ -n "${HOME:-}" ] || die "HOME is not set."
command -v curl >/dev/null 2>&1 || die "curl is needed."

if command -v sha256sum >/dev/null 2>&1; then
  sha256_of() { sha256sum "$1" | cut -d' ' -f1; }
elif command -v shasum >/dev/null 2>&1; then
  sha256_of() { shasum -a 256 "$1" | cut -d' ' -f1; }
else
  die "sha256sum is needed (package coreutils)."
fi

TMP="$(mktemp -d "${TMPDIR:-/tmp}/termalin-install.XXXXXX")"
trap 'rm -rf "$TMP"' EXIT INT TERM

# --- the latest release, from the same feed the app updates from -------------
say "Looking up the latest Termalin release..."
curl -fsSL --proto '=https' "$SERVER/api/v1/updates/latest?platform=linux" -o "$TMP/manifest.json" ||
  die "could not reach $SERVER. Check your connection and try again."

field() {
  # Plain string fields only (version, url, sha256, sig, platform): none of
  # them can contain a quote.
  sed -n "s/.*\"$1\":\"\([^\"]*\)\".*/\1/p" "$TMP/manifest.json" | head -n 1
}
VERSION="$(field version)"
URL="$(field url)"
SHA256="$(field sha256 | tr 'A-F' 'a-f')"
SIG="$(field sig)"
PLATFORM="$(field platform)"

[ -n "$VERSION" ] && [ -n "$URL" ] && [ -n "$SHA256" ] && [ -n "$SIG" ] ||
  die "the release information from $SERVER is incomplete."
[ "$PLATFORM" = linux ] || die "the release offered is not for Linux ($PLATFORM)."
case "$URL" in
  https://termal.in/download/*) ;;
  *) [ "$SERVER" != https://termal.in ] || die "unexpected download address: $URL" ;;
esac

# --- the signature: the release key signed version, platform, hash and URL ----
command -v openssl >/dev/null 2>&1 ||
  die "openssl is needed to check the download's signature (Fedora: sudo dnf install openssl; Debian/Ubuntu: sudo apt install openssl)."
printf '%s\n' "$TERMALIN_RELEASE_PUBKEY" >"$TMP/release.pem"
printf 'termalin-release-v1\n%s\n%s\n%s\n%s' "$VERSION" "$PLATFORM" "$SHA256" "$URL" >"$TMP/payload"
printf '%s' "$SIG" | openssl base64 -d -A -out "$TMP/sig.bin" 2>/dev/null ||
  die "the release signature is not readable."
if ! openssl pkeyutl -verify -pubin -inkey "$TMP/release.pem" -rawin \
  -in "$TMP/payload" -sigfile "$TMP/sig.bin" >/dev/null 2>&1; then
  if ! openssl version 2>/dev/null | grep -q '^OpenSSL [3-9]'; then
    die "your openssl ($(openssl version 2>/dev/null)) can't check Ed25519 signatures; OpenSSL 3 or newer is needed."
  fi
  die "the release signature does NOT match the Termalin release key. Nothing was installed. If this keeps happening, write to hello@termal.in."
fi
say "Release $VERSION: signature OK."

# --- already up to date? ------------------------------------------------------
if [ "$FORCE" -eq 0 ] && [ -f "$APP" ] && [ "$(sha256_of "$APP")" = "$SHA256" ]; then
  say "Termalin $VERSION is already installed (the app also updates itself)."
  NEED_DOWNLOAD=0
else
  NEED_DOWNLOAD=1
fi

if [ "$NEED_DOWNLOAD" -eq 1 ]; then
  say "Downloading Termalin $VERSION..."
  mkdir -p "$APP_DIR"
  # Download next to the target: the final step is a rename within one
  # folder, which also works while an older Termalin is running from it.
  PART="$APP_DIR/.Termalin.AppImage.download"
  rm -f "$PART"
  if [ -t 2 ]; then PROGRESS=--progress-bar; else PROGRESS=-sS; fi
  curl -fL --proto '=https' $PROGRESS "$URL" -o "$PART" || { rm -f "$PART"; die "download failed."; }
  GOT="$(sha256_of "$PART")"
  if [ "$GOT" != "$SHA256" ]; then
    rm -f "$PART"
    die "the download is damaged or not the signed release (SHA-256 $GOT, expected $SHA256). Nothing was installed."
  fi
  chmod 755 "$PART"
  mv -f "$PART" "$APP"
fi

# --- command, menu entry, icon -------------------------------------------------
mkdir -p "$BIN_DIR" "$DESKTOP_DIR" "$ICON_DIR"
if [ -e "$BIN" ] && [ ! -L "$BIN" ]; then
  say "Note: $BIN exists and is not ours; leaving it alone. Start the app with $APP"
else
  ln -sfn "$APP" "$BIN"
fi

if [ ! -s "$ICON" ] || [ "$NEED_DOWNLOAD" -eq 1 ]; then
  if curl -fsSL --proto '=https' "$SERVER/icon-512.png" -o "$TMP/icon.png" 2>/dev/null; then
    mv -f "$TMP/icon.png" "$ICON"
  else
    say "(could not fetch the icon; the menu entry will use a generic one)"
  fi
fi

cat >"$DESKTOP" <<EOF
[Desktop Entry]
Type=Application
Name=Termalin
GenericName=SSH Terminal Client
Comment=SSH client with SFTP, tunnels and end-to-end encrypted sync
Exec="$APP" %U
TryExec=$APP
Icon=in.termal.Termalin
Terminal=false
Categories=Network;RemoteAccess;TerminalEmulator;
Keywords=SSH;SFTP;FTP;Terminal;Console;Remote;Server;Telnet;Serial;
StartupWMClass=in.termal.Termalin
StartupNotify=true
MimeType=x-scheme-handler/ssh;x-scheme-handler/sftp;
EOF
refresh_menus

# --- hints ---------------------------------------------------------------------
# AppImages are mounted with FUSE; without it the app won't start.
if ! command -v fusermount >/dev/null 2>&1 && ! command -v fusermount3 >/dev/null 2>&1; then
  say "Note: FUSE was not found. If Termalin doesn't start, install it (Fedora: sudo dnf install fuse fuse-libs; Debian/Ubuntu: sudo apt install libfuse2)."
fi
case ":$PATH:" in
  *":$BIN_DIR:"*) ;;
  *) say "Note: $BIN_DIR is not on your PATH yet; open a new terminal, or add it: export PATH=\"\$HOME/.local/bin:\$PATH\"" ;;
esac

say ""
say "Termalin $VERSION is installed. Start it from your app menu, or run: termalin"
say "It updates itself; run this command again any time to reinstall the latest version."
