#!/usr/bin/env bash
# Install LLeMbas as a system service behind nginx with a self-signed cert.
#
# Creates a dedicated service user, a virtualenv, a systemd unit and an nginx
# vhost. Idempotent: safe to re-run. To deploy new code afterwards use
# update.sh, which is what a `git push` should be followed by.
#
# Everything is configurable from the environment:
#
#   SITE_HOST=chat.example   ./deploy/install.sh    # vhost name
#   APP_PORT=8080                                   # loopback port
#   PREFIX=/srv/lembas                              # install root
#   HOME_DIR=/home/lembas                           # service user's home
#   REPO_URL=...                                    # defaults to this checkout's origin
#
# PREFIX defaults to a bind mount of HOME_DIR rather than living directly under
# /srv, because on many machines the root filesystem is small and the venv plus
# database belong on the larger /home volume. Set PREFIX=HOME_DIR to skip that.
set -euo pipefail

HERE="$(dirname "$(readlink -f "$0")")"

SITE_HOST="${SITE_HOST:-lembas.local}"
APP_PORT="${APP_PORT:-8080}"
SERVICE_USER="${SERVICE_USER:-lembas}"
HOME_DIR="${HOME_DIR:-/home/lembas}"
PREFIX="${PREFIX:-/srv/lembas}"
BRANCH="${LEMBAS_BRANCH:-main}"
# Default to wherever this checkout came from, so a fork deploys itself.
REPO_URL="${REPO_URL:-$(git -C "$HERE" remote get-url origin 2>/dev/null || true)}"

APP="$PREFIX/app"
VENV="$PREFIX/venv"
ENV_FILE="$PREFIX/lembas.env"

if [[ -z "$REPO_URL" ]]; then
  echo "Could not determine REPO_URL. Set it explicitly." >&2
  exit 1
fi

echo "== plan =="
echo "   host    : https://$SITE_HOST  ->  127.0.0.1:$APP_PORT"
echo "   user    : $SERVICE_USER ($HOME_DIR)"
echo "   prefix  : $PREFIX"
echo "   repo    : $REPO_URL ($BRANCH)"

echo "== service user =="
# --system: no ageing, no mail spool. Home under /home, not /var/lib, so the
# venv and database sit on the larger volume.
if ! getent passwd "$SERVICE_USER" >/dev/null; then
  sudo useradd --system --create-home --home-dir "$HOME_DIR" \
       --shell /usr/bin/nologin --comment "LLeMbas" "$SERVICE_USER"
else
  echo "   user $SERVICE_USER already exists"
fi
sudo chmod 755 "$HOME_DIR"

if [[ "$PREFIX" != "$HOME_DIR" ]]; then
  echo "== $PREFIX bind-mount onto $HOME_DIR =="
  sudo mkdir -p "$PREFIX"
  grep -q "^$HOME_DIR[[:space:]]" /etc/fstab \
    || echo "$HOME_DIR  $PREFIX  none  bind  0 0" | sudo tee -a /etc/fstab >/dev/null
  sudo systemctl daemon-reload
  mountpoint -q "$PREFIX" || sudo mount "$PREFIX"
fi

echo "== checkout =="
if [[ ! -d "$APP/.git" ]]; then
  sudo -u "$SERVICE_USER" git clone --branch "$BRANCH" "$REPO_URL" "$APP"
else
  echo "   already cloned; use update.sh to pull"
fi

echo "== virtualenv =="
if [[ ! -x "$VENV/bin/python" ]]; then
  sudo -u "$SERVICE_USER" python -m venv "$VENV"
fi
sudo -u "$SERVICE_USER" "$VENV/bin/pip" install --quiet --upgrade pip
# The extras a deployment gets. `search` because DuckDuckGo is the default web
# search provider and is meant to need no setup; `ssh` because agent chats reach
# their machine over it and a deployment without it offers the feature with an
# install hint instead. Listed here AND in update.sh -- an extra added to only
# one of them means existing deployments silently miss it.
LEMBAS_EXTRAS="${LEMBAS_EXTRAS:-search,ssh}"
sudo -u "$SERVICE_USER" "$VENV/bin/pip" install --quiet -e "$APP[$LEMBAS_EXTRAS]"

echo "== environment =="
# Generated once and never regenerated: rotating LEMBAS_SECRET_KEY signs every
# user out AND makes the stored upstream API keys unreadable.
if [[ ! -f "$ENV_FILE" ]]; then
  KEY=$("$VENV/bin/python" -c "import secrets; print(secrets.token_urlsafe(48))")
  sudo tee "$ENV_FILE" >/dev/null <<EOF
# LLeMbas service environment. Generated by deploy/install.sh.
# LEMBAS_SECRET_KEY signs sessions and encrypts stored API keys.
# Changing it signs everyone out and makes stored API keys unreadable.
LEMBAS_SECRET_KEY=$KEY
LEMBAS_DATA_DIR=$PREFIX/data
# Loopback only: reachable through the nginx vhost, never directly.
LEMBAS_HOST=127.0.0.1
LEMBAS_PORT=$APP_PORT
LEMBAS_LOG_LEVEL=info
LEMBAS_ALLOW_SIGNUP=true
LEMBAS_DEFAULT_THEME=moria
EOF
  sudo chown "$SERVICE_USER:$SERVICE_USER" "$ENV_FILE"
  sudo chmod 600 "$ENV_FILE"
  echo "   generated $ENV_FILE"
else
  echo "   $ENV_FILE exists, keeping it (and its secret key)"
fi

sudo install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 750 "$PREFIX/data"

echo "== systemd unit =="
sed -e "s|__PREFIX__|$PREFIX|g" -e "s|__SERVICE_USER__|$SERVICE_USER|g" \
    "$HERE/lembas.service" | sudo tee /etc/systemd/system/lembas.service >/dev/null
# Which version of the template this host is running. update.sh compares
# against it and says so when the template moves on, because the installed
# unit usually grows host-specific lines and cannot simply be overwritten.
sha256sum "$HERE/lembas.service" | cut -d' ' -f1 | sudo tee "$PREFIX/.unit-applied" >/dev/null
sudo systemctl daemon-reload

echo "== self-signed cert for $SITE_HOST =="
sudo mkdir -p /etc/nginx/ssl
if [[ ! -f "/etc/nginx/ssl/$SITE_HOST.crt" ]]; then
  sudo openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "/etc/nginx/ssl/$SITE_HOST.key" -out "/etc/nginx/ssl/$SITE_HOST.crt" \
    -days 3650 -subj "/CN=$SITE_HOST" -addext "subjectAltName=DNS:$SITE_HOST"
  sudo chmod 600 "/etc/nginx/ssl/$SITE_HOST.key"
  sudo chmod 644 "/etc/nginx/ssl/$SITE_HOST.crt"
fi

echo "== nginx vhost =="
sed -e "s|__SITE_HOST__|$SITE_HOST|g" -e "s|__APP_PORT__|$APP_PORT|g" \
    "$HERE/nginx-vhost.conf" | sudo tee "/etc/nginx/conf.d/$SITE_HOST.conf" >/dev/null
sudo nginx -t
sudo systemctl reload nginx

# What this host was installed with, so update.sh can name the vhost it should
# be comparing against and print a command that actually runs. Without it the
# drift check below could only say "something changed somewhere".
printf 'SITE_HOST=%s\nAPP_PORT=%s\n' "$SITE_HOST" "$APP_PORT" \
  | sudo tee "$PREFIX/.deploy-env" >/dev/null
sha256sum "$HERE/nginx-vhost.conf" | cut -d' ' -f1 \
  | sudo tee "$PREFIX/.vhost-applied" >/dev/null

echo "== local name resolution =="
# Only useful when the LAN's DNS does not already answer for this name.
if ! getent hosts "$SITE_HOST" >/dev/null; then
  printf '127.0.0.1\t%s\n::1\t\t%s\n' "$SITE_HOST" "$SITE_HOST" | sudo tee -a /etc/hosts >/dev/null
  echo "   added $SITE_HOST to /etc/hosts"
else
  echo "   $SITE_HOST already resolves"
fi

echo "== enable service =="
sudo systemctl enable --now lembas
sleep 2
sudo systemctl --no-pager --lines=0 status lembas || true

echo
echo "LLeMbas is up at https://$SITE_HOST  (self-signed cert; accept the warning)"
echo "Create the first account -- it becomes the administrator."
