#!/bin/sh
# ZippChat for Linux: install, update or remove.
#
#   curl -fsSL https://www.zippchat.app/install.sh | sh
#   curl -fsSL https://www.zippchat.app/install.sh | sh -s -- --uninstall
#
# Installs for the current user only, into ~/.local, so the app can update
# itself without root. Root (sudo) is used for one thing: installing the
# system libraries it needs, through your distro's package manager.
#
# Supported: Debian and Ubuntu (and their derivatives), Arch and CachyOS,
# Fedora and Nobara, and SteamOS. SteamOS's system is read-only, so nothing
# is installed there; the libraries it needs already ship with it.
#
# Options:
#   --uninstall     remove the app, its launcher and icon (not your data)
#   --no-deps       skip the system libraries
#   --input-group   also add you to the "input" group, which push to talk
#                   needs to work while another window has focus (only if
#                   you turn on "Read input devices directly" in Keybinds)
#   --yes           do not ask before using sudo
set -eu

BASE="${ZIPPCHAT_DOWNLOAD:-https://www.zippchat.app/download}"
APP_ID="app.zippchat.client"
PREFIX="${XDG_DATA_HOME:-$HOME/.local/share}"
OPT="$HOME/.local/opt/zippchat"
BIN="$HOME/.local/bin"
DESKTOP="$PREFIX/applications/$APP_ID.desktop"
ICON="$PREFIX/icons/hicolor/512x512/apps/$APP_ID.png"

say()  { printf '\033[1m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[33mwarning:\033[0m %s\n' "$*" >&2; }
die()  { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; }

DEPS=1 INPUT=0 YES=0 UNINSTALL=0
for arg in "$@"; do
  case "$arg" in
    --uninstall)   UNINSTALL=1 ;;
    --no-deps)     DEPS=0 ;;
    --input-group) INPUT=1 ;;
    --yes|-y)      YES=1 ;;
    -h|--help)     sed -n '2,24p' "$0" 2>/dev/null || true; exit 0 ;;
    *) die "unknown option: $arg" ;;
  esac
done

[ "$(id -u)" -ne 0 ] || die "run this as yourself, not as root: it installs into your home directory"

if [ "$UNINSTALL" -eq 1 ]; then
  say "Removing ZippChat"
  rm -rf "$OPT"
  rm -f "$DESKTOP" "$ICON" "$BIN/zippchat"
  command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$PREFIX/applications" 2>/dev/null || true
  say "Removed. Your settings and keys are left in ~/.local/share/$APP_ID and your keyring."
  exit 0
fi

[ "$(uname -m)" = x86_64 ] || die "only x86-64 builds exist so far (this is $(uname -m))"

# --- which distro -----------------------------------------------------------
[ -r /etc/os-release ] || die "cannot tell which Linux this is (/etc/os-release is missing)"
. /etc/os-release
ids=" ${ID:-} ${ID_LIKE:-} "
case "$ids" in
  *" steamos "*) FAMILY=steamos ;;
  *" arch "*|*" archlinux "*|*" cachyos "*) FAMILY=arch ;;
  *" fedora "*|*" nobara "*|*" rhel "*) FAMILY=fedora ;;
  *" debian "*|*" ubuntu "*) FAMILY=debian ;;
  *) FAMILY=unknown ;;
esac
say "ZippChat for ${PRETTY_NAME:-${ID:-Linux}}"

SUDO=""
need_sudo() {
  [ -n "$SUDO" ] && return 0
  command -v sudo >/dev/null 2>&1 || die "sudo is needed to install system libraries (or re-run with --no-deps)"
  if [ "$YES" -ne 1 ]; then
    say "$1"
    printf 'Continue with sudo? [Y/n] '
    read -r answer </dev/tty || answer=y
    case "$answer" in [nN]*) return 1 ;; esac
  fi
  SUDO=sudo
}

# --- system libraries -------------------------------------------------------
# What the app links against beyond what it bundles: GTK 3 (and the X11,
# cairo, pango stack under it), libsecret for the keyring, Opus for voice,
# libgbm and libdrm for rendering, and the PulseAudio client library, which
# PipeWire systems provide through pipewire-pulse.
install_deps() {
  case "$FAMILY" in
    debian)
      need_sudo "Installing GTK 3, libsecret, Opus, libgbm and the PulseAudio client with apt" || return 0
      $SUDO apt-get update -qq
      # Ubuntu 24.04 and Debian 13 renamed some libraries with a t64 suffix.
      gtk=libgtk-3-0
      apt-cache show libgtk-3-0t64 >/dev/null 2>&1 && gtk=libgtk-3-0t64
      $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
        ca-certificates curl tar "$gtk" libsecret-1-0 libopus0 libgbm1 libdrm2 \
        libpulse0 libxcomposite1 libxdamage1 libxrandr2 >/dev/null
      ;;
    arch)
      need_sudo "Installing gtk3, libsecret, opus, mesa and libpulse with pacman" || return 0
      $SUDO pacman -S --needed --noconfirm \
        ca-certificates curl tar gtk3 libsecret opus mesa libdrm libpulse \
        libxcomposite libxdamage libxrandr >/dev/null
      ;;
    fedora)
      need_sudo "Installing gtk3, libsecret, opus, mesa-libgbm and pulseaudio-libs with dnf" || return 0
      $SUDO dnf install -y -q \
        ca-certificates curl tar gtk3 libsecret opus mesa-libgbm libdrm pulseaudio-libs \
        libXcomposite libXdamage libXrandr >/dev/null
      ;;
    steamos)
      say "SteamOS: the system is read-only, so no packages are installed; checking what is there"
      ;;
    *)
      warn "this distro (${ID:-unknown}) is not one the script knows; skipping system libraries"
      ;;
  esac
}
[ "$DEPS" -eq 1 ] && install_deps

for tool in curl tar sha256sum; do
  command -v "$tool" >/dev/null 2>&1 || die "$tool is needed"
done

# --- download and verify ----------------------------------------------------
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT INT TERM
say "Downloading the latest release"
curl -fL --progress-bar -o "$TMP/zippchat-linux-x64.tar.gz" "$BASE/zippchat-linux-x64.tar.gz" \
  || die "download failed from $BASE"
curl -fsSL -o "$TMP/SHA256SUMS" "$BASE/SHA256SUMS" || die "could not fetch SHA256SUMS"
( cd "$TMP" && grep ' zippchat-linux-x64.tar.gz$' SHA256SUMS | sha256sum -c --status ) \
  || die "checksum mismatch: the download is not the published file; nothing was installed"
say "Checksum verified"

# --- install ----------------------------------------------------------------
mkdir -p "$TMP/x" "$HOME/.local/opt" "$BIN" "$(dirname "$DESKTOP")" "$(dirname "$ICON")"
tar -xzf "$TMP/zippchat-linux-x64.tar.gz" -C "$TMP/x"
[ -x "$TMP/x/zippchat/zippchat" ] || die "the archive does not contain zippchat/zippchat"
rm -rf "$OPT.new" && mv "$TMP/x/zippchat" "$OPT.new"
rm -rf "$OPT" && mv "$OPT.new" "$OPT"
ln -sf "$OPT/zippchat" "$BIN/zippchat"

if [ -f "$OPT/$APP_ID.png" ]; then
  cp "$OPT/$APP_ID.png" "$ICON"
fi
cat >"$DESKTOP" <<EOF
[Desktop Entry]
Type=Application
Name=ZippChat
GenericName=Chat
Comment=End-to-end encrypted chat and voice
Exec=$OPT/zippchat
Path=$OPT
Icon=$APP_ID
Terminal=false
Categories=Network;Chat;InstantMessaging;
Keywords=chat;voice;messaging;e2ee;
StartupNotify=true
StartupWMClass=$APP_ID
EOF
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$PREFIX/applications" 2>/dev/null || true
command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q "$PREFIX/icons/hicolor" 2>/dev/null || true

# --- check what is still missing ---------------------------------------------
missing="$( (ldd "$OPT/zippchat"; for f in "$OPT"/lib/*.so; do ldd "$f"; done) 2>/dev/null \
  | awk '/not found/ {print $1}' | sort -u | grep -v -e '^libflutter' -e '^libapp' -e '^libvorbis' -e '^libogg' -e '^libFLAC' || true)"
if [ -n "$missing" ]; then
  warn "these libraries are still missing, so ZippChat may not start:"
  printf '    %s\n' $missing >&2
fi

# The keyring stores your session and encryption keys. Without a Secret
# Service (GNOME Keyring, KWallet, KeePassXC...) the app cannot keep you
# signed in.
if command -v busctl >/dev/null 2>&1 && ! busctl --user list 2>/dev/null | grep -q org.freedesktop.secrets; then
  warn "no keyring service is running (GNOME Keyring, KWallet or KeePassXC); ZippChat needs one to store your keys"
fi

# --- optional: input group -----------------------------------------------------
if [ "$INPUT" -eq 1 ]; then
  if ! getent group input >/dev/null 2>&1; then
    warn "this system has no input group, so it cannot be used to read input devices"
  elif id -nG | tr ' ' '\n' | grep -qx input; then
    say "You are already in the input group"
  elif need_sudo "Adding you to the input group, so ZippChat can read push-to-talk keys while another window has focus"; then
    $SUDO usermod -aG input "$(id -un)"
    say "Added. Log out and back in for it to take effect."
  fi
fi

case ":$PATH:" in *":$BIN:"*) ;; *) warn "$BIN is not on your PATH; start ZippChat from your app menu, or run $OPT/zippchat" ;; esac
say "Installed to $OPT. Open ZippChat from your app menu, or run: zippchat"
say "It updates itself. To remove it: curl -fsSL https://www.zippchat.app/install.sh | sh -s -- --uninstall"
