#!/bin/sh
# Managed by ansible 095-tropa (tag client). Установщик клиента Тропы Жоры для Linux
# (m:клиент-tropa). Запуск один раз:  sudo sh install.sh '<ссылка подписки>'
# Ссылку подписки учётки даёт Инженер; без довода она читается строкой с терминала.
# Повторный запуск ставит новый выпуск, если он есть, и меняет ссылку, только когда довод
# либо терминал её дали. Регресс — files/install.test.sh.
set -eu

# Каталоги и адрес — переменными только ради регресса.
BASE=${TROPA_BASE:-https://git.rolder.cloud/api/packages/tropa-public/generic/tropa-client}
ROOT=${TROPA_ROOT:-/opt/tropa}
STATE_DIR=${TROPA_STATE_DIR:-/var/lib/tropa}
SYSTEMD_DIR=${TROPA_SYSTEMD_DIR:-/run/systemd/system}
NIX_DIR=${TROPA_NIX_DIR:-/run/current-system}
LINK_DIR=${TROPA_LINK_DIR:-/usr/local/bin}
UNIT_DIR=${TROPA_UNIT_DIR:-/etc/systemd/system}
TTY=${TROPA_TTY:-/dev/tty}

die() { echo "install: $*" >&2; exit 1; }

[ "$(id -u)" = 0 ] || die "запусти от root: sudo sh install.sh '<ссылка подписки>'"
[ "$(uname -m)" = x86_64 ] || die "клиент есть только для x86_64, а тут $(uname -m)"
[ -d "$SYSTEMD_DIR" ] || die "systemd не управляет системой (нет $SYSTEMD_DIR): клиент ставится только на Linux с systemd"
command -v curl >/dev/null 2>&1 || die "нужен curl"
command -v sha256sum >/dev/null 2>&1 || die "нужен sha256sum"

USER_NAME=${SUDO_USER:-}
[ "$USER_NAME" != root ] || USER_NAME=
USER_HOME=
if [ -n "$USER_NAME" ]; then
  USER_HOME=${TROPA_USER_HOME:-$(getent passwd "$USER_NAME" | cut -d: -f6)}
fi

# Команда tropa: без /run/current-system — /usr/local/bin/tropa, на NixOS (где
# /usr/local/bin не в PATH) — ~/.local/bin/tropa пользователя из SUDO_USER.
if [ -e "$NIX_DIR" ]; then
  [ -n "$USER_HOME" ] || die "на NixOS команду tropa ставят пользователю: запусти через sudo из его сессии"
  LINK=$USER_HOME/.local/bin/tropa
else
  LINK=$LINK_DIR/tropa
fi
TARGET=$ROOT/current/tropa

# Что лежит на месте команды: наша ссылка или обёртка прежнего тега (вторая строка
# «# Managed by ansible 095-tropa») заменяются, чужой файл — отказ до любой правки.
if [ -e "$LINK" ] || [ -L "$LINK" ]; then
  if [ -L "$LINK" ] && [ "$(readlink "$LINK")" = "$TARGET" ]; then
    :
  elif [ -f "$LINK" ] && [ ! -L "$LINK" ] \
      && sed -n 2p "$LINK" | grep -q '^# Managed by ansible 095-tropa'; then
    :
  else
    die "$LINK уже занят чужим файлом: убери его и запусти установщик снова"
  fi
fi

# Ссылка подписки: довод, а без довода — терминал; ссылка уже стоит, а довода нет — прежняя
# остаётся. Ссылка проверяется до любой правки компьютера: она начинается с https://, и по
# ней приходит строка подключения tropa://. Не пришла — отказ, который называет, что пришло.
KEY=
if [ "$#" -ge 1 ]; then
  KEY=$(printf '%s' "$1" | tr -d '[:space:]')
elif [ ! -s "$STATE_DIR/key" ]; then
  [ -r "$TTY" ] || die "ссылка подписки не дана: sudo sh install.sh '<ссылка подписки>'"
  printf 'Ссылка подписки (https://…): ' > "$TTY"
  IFS= read -r KEY < "$TTY" || true
  KEY=$(printf '%s' "$KEY" | tr -d '[:space:]')
fi
SUB=
case "$KEY" in
  '' ) [ -s "$STATE_DIR/key" ] || die "ссылка подписки пуста" ;;
  https://?*)
    # -f: ответ 4xx и 5xx — отказ curl, и его вывод называет код ответа.
    SUB=$(curl -fsS --max-time 10 "$KEY" 2>&1) || die "по ссылке подписки строка не пришла: $SUB"
    SUB=$(printf '%s' "$SUB" | tr -d '[:space:]')
    case "$SUB" in
      tropa://?*) ;;
      *) die "по ссылке подписки пришла не строка подключения tropa://, а: $(printf '%s' "$SUB" | cut -c1-80)" ;;
    esac
    ;;
  *) die "ссылка подписки должна начинаться с https://" ;;
esac

WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT

# Манифест выпуска; каждый файл в нём на своей строке.
curl -fsS --max-time 60 -o "$WORK/manifest.json" "$BASE/latest/manifest.json" \
  || die "манифест не скачан: $BASE/latest/manifest.json"
RELEASE=$(sed -n 's/.*"release": *"\([^"]*\)".*/\1/p' "$WORK/manifest.json" | head -n 1)
LINE=$(grep '"tropa-update-linux-amd64"' "$WORK/manifest.json" | head -n 1 || true)
UPD_NAME=$(printf '%s' "$LINE" | sed -n 's/.*"name": *"\([^"]*\)".*/\1/p')
UPD_SUM=$(printf '%s' "$LINE" | sed -n 's/.*"sha256": *"\([^"]*\)".*/\1/p')
[ -n "$RELEASE" ] && [ -n "$UPD_NAME" ] && [ -n "$UPD_SUM" ] || die "манифест не называет выпуск и tropa-update"

curl -fsS --max-time 120 -o "$WORK/tropa-update" "$BASE/$RELEASE/$UPD_NAME" \
  || die "tropa-update не скачан: $BASE/$RELEASE/$UPD_NAME"
GOT=$(sha256sum "$WORK/tropa-update" | cut -d' ' -f1)
[ "$GOT" = "$UPD_SUM" ] || die "сумма tropa-update $GOT, а манифест называет $UPD_SUM"
chmod 0755 "$WORK/tropa-update"

# apply сверяет подпись манифеста и ставит выпуск. Код 10 — встал, 0 — уже стоит.
mkdir -p "$STATE_DIR"
RC=0
OUT=$(TROPA_UPDATE_BASE="$BASE" "$WORK/tropa-update" apply --root "$ROOT" --state "$STATE_DIR" 2>&1) || RC=$?
case "$RC" in
  0|10) ;;
  *) printf '%s\n' "$OUT" >&2; die "выпуск не встал (код $RC); компьютер не тронут" ;;
esac
[ -e "$ROOT/current/RELEASE" ] || die "после apply нет $ROOT/current/RELEASE"

# Ссылка, строка подписки и пользователь — только после удачного apply. Файл зовётся key:
# путь /var/lib/tropa/key читает переключатель VPN на NixOS (строка KEY_FILE обёртки), а
# содержимое он отдаёт клиенту как есть. Строка в subscription — первая сессия ноги и без запроса.
chmod 0755 "$STATE_DIR"
if [ -n "$KEY" ]; then
  ( umask 077; printf '%s\n' "$KEY" > "$STATE_DIR/key.new"; printf '%s\n' "$SUB" > "$STATE_DIR/subscription.new" )
  chmod 0600 "$STATE_DIR/key.new" "$STATE_DIR/subscription.new"
  mv -f "$STATE_DIR/subscription.new" "$STATE_DIR/subscription"
  mv -f "$STATE_DIR/key.new" "$STATE_DIR/key"
fi
if [ -n "$USER_NAME" ]; then
  printf '%s\n' "$USER_NAME" > "$STATE_DIR/user"
fi

# Команда tropa.
mkdir -p "$(dirname "$LINK")"
ln -sfn "$TARGET" "$LINK"
if [ -e "$NIX_DIR" ] && [ -n "$USER_NAME" ]; then
  chown -h "$USER_NAME" "$LINK" 2>/dev/null || true
  chown "$USER_NAME" "$(dirname "$LINK")" 2>/dev/null || true
fi

# Сборки клиента по коммитам прежнего тега: ~/.local/share/tropa/<коммит из 40 знаков>/.
if [ -n "$USER_HOME" ] && [ -d "$USER_HOME/.local/share/tropa" ]; then
  for d in "$USER_HOME"/.local/share/tropa/*; do
    [ -d "$d" ] || continue
    case "$(basename "$d")" in
      *[!0-9a-f]*) continue ;;
    esac
    [ "$(basename "$d" | wc -c)" -eq 41 ] || continue
    rm -rf "$d"
  done
fi

# Служба диагностики: где /etc/systemd/system изменяем — постоянный unit; на NixOS её
# поднимает tropa session командой systemd-run.
if [ ! -e "$NIX_DIR" ]; then
  cp "$ROOT/current/tropa-diag.service" "$UNIT_DIR/tropa-diag.service"
  systemctl daemon-reload
  systemctl enable --now tropa-diag
fi

echo "install: выпуск $(tr -d '[:space:]' < "$ROOT/current/RELEASE") стоит; подключение — tropa up, проверка — tropa check"
