#!/usr/bin/env bash
#
# token2-linux-setup.sh
# ---------------------
# One-stop Linux setup for Token2 Companion Keyroost. Run it once and it makes
# everything the app needs work, across major distros:
#
#   1. installs the smart-card service (pcscd) + CCID driver if missing,
#   2. enables and starts pcscd,
#   3. registers recent Token2 keys with the CCID driver when the installed
#      libccid is older than 1.7.0 (so PIV / OpenPGP / on-device OTP work even on
#      Mint / Ubuntu LTS / Debian stable, which ship libccid 1.5.x),
#   4. installs a udev rule so your user can reach the key without sudo,
#   5. marks the AppImage executable.
#
# Each step checks whether it's actually needed, so re-running is safe and quick.
# It uses sudo only for the system steps. Supports apt, dnf/yum, pacman, zypper.
#
# Usage:
#   ./token2-linux-setup.sh [path-to-AppImage]
#
set -euo pipefail

VENDOR_ID="349e"                 # Token2 USB vendor id (lowercase, for udev/lsusb)
VENDOR_HEX="0x349E"              # for the CCID Info.plist
KEY_NAME="TOKEN2 FIDO2 Security Key"
RULE_FILE="/etc/udev/rules.d/70-token2.rules"
# Token2 PIN+ / Bio3 product ids that expose CCID (from the libccid 1.7.0 list).
TOKEN2_PIDS=(0013 0014 0015 0016 0017 0023 0024 0025 0026 0027 0203 0204 0205 0206 0207)
MIN_LIBCCID="1.7.0"

c_say()  { printf '\033[1;35m> %s\033[0m\n' "$*"; }
c_ok()   { printf '\033[1;32m  OK\033[0m %s\n' "$*"; }
c_warn() { printf '\033[1;33m  !\033[0m  %s\n' "$*"; }
c_info() { printf '     %s\n' "$*"; }

if [[ "${EUID}" -eq 0 ]]; then
  c_warn "Run this as your normal user (not root/sudo). It calls sudo itself when needed."
  exit 1
fi
if ! command -v sudo >/dev/null; then
  c_warn "sudo not found. Install sudo, or run the system steps manually (see README)."
  exit 1
fi

# ---- detect package manager + install command ------------------------------
PM=""; INSTALL=""
if   command -v apt-get >/dev/null; then PM="apt";    INSTALL="sudo apt-get install -y"
elif command -v dnf     >/dev/null; then PM="dnf";    INSTALL="sudo dnf install -y"
elif command -v yum     >/dev/null; then PM="yum";    INSTALL="sudo yum install -y"
elif command -v pacman  >/dev/null; then PM="pacman"; INSTALL="sudo pacman -S --needed --noconfirm"
elif command -v zypper  >/dev/null; then PM="zypper"; INSTALL="sudo zypper install -y"
else c_warn "No supported package manager found (apt/dnf/yum/pacman/zypper)."; fi

# package names differ per distro
pkgs_for() {
  case "$PM" in
    apt)        echo "pcscd libccid pcsc-tools" ;;
    dnf|yum)    echo "pcsc-lite ccid pcsc-tools" ;;
    pacman)     echo "pcsclite ccid pcsc-tools" ;;
    zypper)     echo "pcsc-lite pcsc-ccid pcsc-tools" ;;
    *)          echo "" ;;
  esac
}

# ===========================================================================
# 1 + 2. smart-card service and CCID driver
# ===========================================================================
c_say "Smart-card service (pcscd) and CCID driver"
need_install=0
command -v pcscd >/dev/null || need_install=1
# crude driver presence check
if ! find /usr/lib /usr/lib64 /usr/local/lib -path '*ifd-ccid*' -print -quit 2>/dev/null | grep -q .; then
  need_install=1
fi

if [[ "$need_install" == "1" ]]; then
  if [[ -n "$PM" ]]; then
    c_info "Installing: $(pkgs_for)"
    [[ "$PM" == "apt" ]] && sudo apt-get update -qq || true
    # shellcheck disable=SC2086
    $INSTALL $(pkgs_for) || c_warn "Some packages failed to install; continuing."
    c_ok "packages installed"
  else
    c_warn "Install 'pcscd'/'pcsc-lite' and the 'ccid' driver manually, then re-run."
  fi
else
  c_ok "pcscd and CCID driver already present"
fi

# enable + start the service (systemd or fallback)
if command -v systemctl >/dev/null; then
  if ! systemctl is-enabled --quiet pcscd 2>/dev/null; then
    sudo systemctl enable pcscd 2>/dev/null || true
  fi
  # pcscd often runs socket-activated; start it so it's live now.
  sudo systemctl start pcscd 2>/dev/null || sudo systemctl start pcscd.socket 2>/dev/null || true
  if systemctl is-active --quiet pcscd || systemctl is-active --quiet pcscd.socket; then
    c_ok "pcscd is enabled and running"
  else
    c_warn "Could not confirm pcscd is running; it may start on demand when a key is plugged in."
  fi
else
  # non-systemd: try to start the daemon directly
  sudo pcscd 2>/dev/null || true
  c_ok "started pcscd (non-systemd system)"
fi

# ===========================================================================
# 3. register recent Token2 keys with an older libccid
# ===========================================================================
c_say "Checking the CCID driver knows recent Token2 keys"

ver_lt() { # ver_lt A B  -> true if A < B
  [[ "$1" == "$2" ]] && return 1
  local lo
  lo="$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)"
  [[ "$lo" == "$1" ]]
}

LIBCCID_VER=""
if command -v dpkg >/dev/null; then
  LIBCCID_VER="$(dpkg-query -W -f='${Version}' libccid 2>/dev/null | sed 's/[-+].*//' || true)"
fi
[[ -z "$LIBCCID_VER" ]] && command -v pkg-config >/dev/null && \
  LIBCCID_VER="$(pkg-config --modversion libccid 2>/dev/null || true)"

PLIST="$(find /usr/lib /usr/lib64 /usr/local/lib -path '*ifd-ccid.bundle/Contents/Info.plist' -print -quit 2>/dev/null || true)"
[[ -z "$PLIST" ]] && PLIST="$(sudo find / -path '*ifd-ccid.bundle/Contents/Info.plist' -print -quit 2>/dev/null || true)"

register_needed=1
if [[ -n "$LIBCCID_VER" ]]; then
  if ver_lt "$LIBCCID_VER" "$MIN_LIBCCID"; then
    c_info "installed libccid $LIBCCID_VER is older than $MIN_LIBCCID - will register keys"
  else
    c_ok "libccid $LIBCCID_VER already knows recent Token2 keys"
    register_needed=0
  fi
else
  c_info "couldn't read libccid version; will register keys to be safe"
fi

if [[ "$register_needed" == "1" ]]; then
  if [[ -z "$PLIST" ]]; then
    c_warn "CCID driver Info.plist not found; skipping registration."
  else
    BACKUP="${PLIST}.token2-backup"
    [[ -f "$BACKUP" ]] || { sudo cp "$PLIST" "$BACKUP"; c_info "backed up Info.plist -> $BACKUP"; }
    PIDS_JOINED="$(IFS=,; echo "${TOKEN2_PIDS[*]}")"
    sudo VENDOR="$VENDOR_HEX" NAME="$KEY_NAME" PIDS="$PIDS_JOINED" python3 - "$PLIST" <<'PY'
import sys, os, re
plist = sys.argv[1]
vendor = os.environ["VENDOR"]; name = os.environ["NAME"]
pids = [p.strip() for p in os.environ["PIDS"].split(",") if p.strip()]
text = open(plist, encoding="utf-8").read()

def get_list(key):
    m = re.search(r'(<key>%s</key>\s*<array>)(.*?)(</array>)' % re.escape(key), text, re.DOTALL)
    if not m: sys.exit("could not find <%s> in Info.plist" % key)
    return re.findall(r'<string>(.*?)</string>', m.group(2), re.DOTALL)

v = get_list("ifdVendorID"); p = get_list("ifdProductID"); n = get_list("ifdFriendlyName")
existing = set(zip((x.strip() for x in v), (x.strip() for x in p)))
added = []
for pid in pids:
    ph = pid if pid.lower().startswith("0x") else "0x" + pid.upper()
    if (vendor, ph) in existing: continue
    v.append(vendor); p.append(ph); n.append(name); added.append(ph)

if not added:
    print("  CCID: Token2 keys already registered"); sys.exit(0)

def rebuild(key, items):
    inner = "\n" + "\n".join("        <string>%s</string>" % i for i in items) + "\n    "
    return "<key>%s</key>\n    <array>%s</array>" % (key, inner)

for key, items in (("ifdVendorID", v), ("ifdProductID", p), ("ifdFriendlyName", n)):
    block = re.search(r'<key>%s</key>\s*<array>.*?</array>' % re.escape(key), text, re.DOTALL).group(0)
    text = text.replace(block, rebuild(key, items), 1)
open(plist, "w", encoding="utf-8").write(text)
print("  CCID: registered", ", ".join(added))
PY
    sudo systemctl restart pcscd 2>/dev/null || { sudo pkill pcscd 2>/dev/null || true; sudo pcscd 2>/dev/null || true; }
    c_ok "CCID driver updated and pcscd restarted"
  fi
fi

# ===========================================================================
# 4. udev rule (user access without sudo)
# ===========================================================================
c_say "Installing the udev rule for Token2 keys (vendor $VENDOR_ID)"
read -r -d '' RULE <<EOF || true
# Token2 FIDO2 / PIN+ security keys (USB vendor $VENDOR_ID).
# Grants the logged-in user access over CCID (PC/SC) and raw HID, no sudo needed.
# Installed by token2-linux-setup.sh.
SUBSYSTEM=="usb", ATTRS{idVendor}=="$VENDOR_ID", TAG+="uaccess", MODE="0660"
KERNEL=="hidraw*", ATTRS{idVendor}=="$VENDOR_ID", TAG+="uaccess", MODE="0660"
EOF
if [[ -f "$RULE_FILE" ]] && diff -q <(printf '%s\n' "$RULE") "$RULE_FILE" >/dev/null 2>&1; then
  c_ok "udev rule already present and up to date"
else
  printf '%s\n' "$RULE" | sudo tee "$RULE_FILE" >/dev/null
  sudo udevadm control --reload-rules 2>/dev/null || true
  sudo udevadm trigger 2>/dev/null || true
  c_ok "udev rule installed and reloaded"
fi

# ===========================================================================
# 5. make the AppImage executable
# ===========================================================================
c_say "Making the AppImage executable"
APPIMAGE="${1:-}"
if [[ -z "$APPIMAGE" ]]; then
  SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
  APPIMAGE="$(ls -1 "$SELF_DIR"/*Token2*.AppImage ./*Token2*.AppImage 2>/dev/null | head -n1 || true)"
  [[ -z "$APPIMAGE" ]] && APPIMAGE="$(ls -1 "$SELF_DIR"/*.AppImage ./*.AppImage 2>/dev/null | grep -iv linuxdeploy | head -n1 || true)"
fi
if [[ -n "$APPIMAGE" && -f "$APPIMAGE" ]]; then
  chmod +x "$APPIMAGE"
  c_ok "marked executable: $APPIMAGE"
else
  c_warn "No AppImage found. Pass its path:  ./token2-linux-setup.sh /path/to/App.AppImage"
fi

# ---- done ------------------------------------------------------------------
echo
c_say "Setup complete."
c_info "UNPLUG and REPLUG your Token2 key so the new permissions and driver apply."
c_info "Then verify the smart-card side with:   pcsc_scan"
c_info "(you should see a TOKEN2 reader appear), and launch the app."
[[ -n "$APPIMAGE" && -f "$APPIMAGE" ]] && c_info "App: $APPIMAGE"
if [[ -n "${PLIST:-}" && -f "${PLIST}.token2-backup" ]]; then
  echo
  c_info "To undo the CCID change later:"
  c_info "  sudo cp \"${PLIST}.token2-backup\" \"$PLIST\" && sudo systemctl restart pcscd"
fi
