#!/bin/sh
# Vigil self-hosted installer. Usage:
#   curl -fsSL https://puzaricloud.in/selfhosted/install.sh | sudo sh -s -- --domain vigil.example.com --email you@example.com --license 'VGL1.…'
# Options: --dir /opt/vigil   --bundle /path/vigil.tar.gz (offline; expects vigil.tar.gz.sig next to it)   --internal-tls (self-signed cert for private networks)
#          --no-phone-home (air-gapped)   --source https://puzaricloud.in (where to download the bundle from)   --http-port 80 --https-port 443 (if those ports are taken)
#          --skip-verify (do not check the bundle's Ed25519 signature; not recommended)
# Verify this installer before piping it to sh:  curl -fsSLO https://puzaricloud.in/selfhosted/install.sh -O https://puzaricloud.in/selfhosted/install.sh.sig
#   && curl -fsSL https://puzaricloud.in/selfhosted/pubkey | base64 -d > vigil-pub.der && base64 -d install.sh.sig > install.sh.sig.bin
#   && openssl pkeyutl -verify -pubin -inkey vigil-pub.der -keyform DER -rawin -in install.sh -sigfile install.sh.sig.bin
set -e
DIR=/opt/vigil; DOMAIN=""; EMAIL=""; LICENSE=""; BUNDLE=""; SOURCE="https://puzaricloud.in"; INTERNAL=0; PHONE=1; HTTP_PORT=80; HTTPS_PORT=443; VERIFY=1
PUBKEY_DER_B64="MCowBQYDK2VwAyEAjN3PWXceODEw/1bxqL6ejFt0pd3pj+C8ckPpOxgZLVI="
while [ $# -gt 0 ]; do case "$1" in
  --domain) DOMAIN="$2"; shift 2;; --email) EMAIL="$2"; shift 2;; --license) LICENSE="$2"; shift 2;; --dir) DIR="$2"; shift 2;;
  --bundle) BUNDLE="$2"; shift 2;; --source) SOURCE="$2"; shift 2;; --internal-tls) INTERNAL=1; shift;; --http-port) HTTP_PORT="$2"; shift 2;; --skip-verify) VERIFY=0; shift;; --https-port) HTTPS_PORT="$2"; shift 2;; --no-phone-home) PHONE=0; shift;;
  -h|--help) sed -n 2,6p "$0"; exit 0;; *) echo "unknown option $1"; exit 1;; esac; done
[ "$(id -u)" = 0 ] || { echo "Run as root (sudo)."; exit 1; }
[ -n "$DOMAIN" ] || { echo "--domain is required (e.g. vigil.example.com, or the server IP for --internal-tls)"; exit 1; }
[ -n "$EMAIL" ] || { echo "--email is required (Let's Encrypt notices + first admin hint)"; exit 1; }
say() { printf '\033[1;34m==>\033[0m %s\n' "$1"; }
# edit .env without replacing the inode (the containers bind-mount the file, so `sed -i` would leave them with a stale copy)
# verify_sig <file> <base64 sig file>: Ed25519 signature by PuzariCloud's release key (same key that signs licences)
verify_sig() {
  [ "$VERIFY" = 1 ] || { echo "   (signature check skipped)"; return 0; }
  command -v openssl >/dev/null 2>&1 || { echo "openssl is required to verify the bundle signature (or pass --skip-verify)"; return 1; }
  [ -s "$2" ] || { echo "signature file $2 missing; refusing to install an unsigned bundle (or pass --skip-verify)"; return 1; }
  T=$(mktemp -d); printf '%s' "$PUBKEY_DER_B64" | base64 -d > "$T/pub.der"; base64 -d < "$2" > "$T/sig.bin" 2>/dev/null || tr -d '\n' < "$2" | base64 -d > "$T/sig.bin"
  if openssl pkeyutl -verify -pubin -inkey "$T/pub.der" -keyform DER -rawin -in "$1" -sigfile "$T/sig.bin" >/dev/null 2>&1; then rm -rf "$T"; echo "   signature OK ($(basename "$1"))"; return 0; fi
  rm -rf "$T"; echo "SIGNATURE CHECK FAILED for $1 — the file is not what PuzariCloud published. Aborting."; return 1
}
setenv() { grep -v "^$1=" .env > .env.tmp || true; printf '%s=%s\n' "$1" "$2" >> .env.tmp; cat .env.tmp > .env; rm -f .env.tmp; }

say "Checking Docker"
if ! command -v docker >/dev/null 2>&1; then
  say "Installing Docker"
  if command -v apt-get >/dev/null; then apt-get update -qq && apt-get install -y -qq ca-certificates curl >/dev/null; curl -fsSL https://get.docker.com | sh
  elif command -v dnf >/dev/null; then dnf install -y -q docker docker-compose-plugin || curl -fsSL https://get.docker.com | sh
  elif command -v apk >/dev/null; then apk add --no-cache docker docker-cli-compose && rc-update add docker default && service docker start
  else curl -fsSL https://get.docker.com | sh; fi
fi
systemctl enable --now docker >/dev/null 2>&1 || true
docker compose version >/dev/null 2>&1 || { echo "Docker Compose plugin missing; install docker-compose-plugin and re-run."; exit 1; }

PORTSUFFIX=""; [ "$HTTPS_PORT" != 443 ] && PORTSUFFIX=":$HTTPS_PORT"
say "Preparing $DIR"
mkdir -p "$DIR/src" "$DIR/backups"
cd "$DIR"
if [ -n "$BUNDLE" ]; then cp "$BUNDLE" ./vigil.tar.gz; [ -f "$BUNDLE.sig" ] && cp "$BUNDLE.sig" ./vigil.tar.gz.sig || rm -f ./vigil.tar.gz.sig
else say "Downloading Vigil from $SOURCE"; curl -fsSL "$SOURCE/selfhosted/vigil-latest.tar.gz" -o vigil.tar.gz; curl -fsSL "$SOURCE/selfhosted/vigil-latest.tar.gz.sig" -o vigil.tar.gz.sig || rm -f vigil.tar.gz.sig; fi
say "Verifying bundle signature"; verify_sig ./vigil.tar.gz ./vigil.tar.gz.sig || exit 1
rm -rf src/app src/requirements.txt src/Dockerfile; tar -xzf vigil.tar.gz -C src
[ -f src/app/main.py ] || { echo "Bundle looks wrong (no app/main.py)"; exit 1; }
cp src/selfhosted/docker-compose.yml ./docker-compose.yml
VERSION=$(cat src/VERSION 2>/dev/null || echo unknown)

if [ ! -f .env ]; then
  say "Writing .env"
  SECRET=$(head -c 48 /dev/urandom | base64 | tr -d '/+=\n' | head -c 48); DBPW=$(head -c 24 /dev/urandom | base64 | tr -d '/+=\n' | head -c 24)
  cat > .env <<ENV
VIGIL_EDITION=selfhosted
VIGIL_VERSION=$VERSION
HTTP_PORT=$HTTP_PORT
HTTPS_PORT=$HTTPS_PORT
APP_NAME=Vigil
PUBLIC_BASE_URL=https://$DOMAIN$PORTSUFFIX
SECRET_KEY=$SECRET
DB_PASSWORD=$DBPW
DATABASE_URL=postgresql+psycopg://vigil:$DBPW@db:5432/vigil
ADMIN_EMAILS=$EMAIL
LICENSE_KEY=$LICENSE
LICENSE_PHONE_HOME=$PHONE
LICENSE_SERVER=$SOURCE
LICENSE_ENV_FILE=/srv/env/.env
EMAIL_FROM=Vigil <no-reply@$DOMAIN>
# Optional integrations (see https://puzaricloud.in/selfhosted): BREVO_API_KEY=, SMTP_HOST=, GOOGLE_CLIENT_ID=, GEMINI_API_KEY=
WORKER_CONCURRENCY=6
ENV
  chmod 600 .env
else
  say ".env exists, keeping it (updating VIGIL_VERSION)"; setenv VIGIL_VERSION "$VERSION"
  [ -n "$LICENSE" ] && setenv LICENSE_KEY "$LICENSE"
fi

say "Writing Caddyfile"
TLSLINE=""; [ "$INTERNAL" = 1 ] && TLSLINE="tls internal"
sed -e "s/__DOMAIN__/$DOMAIN/" -e "s/__EMAIL__/$EMAIL/" -e "s/__TLS__/$TLSLINE/" src/selfhosted/Caddyfile.template > Caddyfile
# status pages on customers' own domains need a catch-all with on-demand TLS; skip it when Vigil itself is reached by IP (TLS has no SNI for IPs)
case "$DOMAIN" in *[a-zA-Z]*) cat src/selfhosted/Caddyfile.ondemand >> Caddyfile;; esac

say "Building and starting (first build takes 2-4 minutes)"
docker compose build -q web
docker compose up -d
n=0; until docker compose exec -T web python -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8100/healthz',timeout=3)" >/dev/null 2>&1; do n=$((n+1)); [ $n -ge 90 ] && { echo "web did not become healthy; see: docker compose logs web"; exit 1; }; sleep 2; done

say "Installing helper commands"
cat > /usr/local/bin/vigil <<CMD
#!/bin/sh
# vigil update | backup | logs | status | restart | license <key>
export VIGIL_DIR="$DIR"; cd "$DIR" || exit 1
case "\$1" in
  update) sh src/selfhosted/update.sh "\${2:-}";;
  backup) sh src/selfhosted/backup.sh;;
  logs) docker compose logs -f --tail 200 web web2 worker;;
  status) docker compose ps;;
  restart) docker compose restart web web2 worker;;
  license) grep -v "^LICENSE_KEY=" .env > .env.tmp; printf 'LICENSE_KEY=%s\n' "\$2" >> .env.tmp; cat .env.tmp > .env; rm -f .env.tmp; echo "licence updated (picked up within a minute, no restart needed)";;
  *) echo "usage: vigil update|backup|logs|status|restart|license <key>";;
esac
CMD
chmod +x /usr/local/bin/vigil
cat > /etc/cron.d/vigil-selfhosted <<CRON
15 */6 * * * root VIGIL_DIR=$DIR sh $DIR/src/selfhosted/backup.sh >> /var/log/vigil-backup.log 2>&1
CRON

say "Done. Vigil $VERSION is running."
echo "   Open:  https://$DOMAIN$PORTSUFFIX   (create the first account with $EMAIL; it becomes the admin)"
echo "   Later: vigil update · vigil backup · vigil logs · vigil status · vigil license <key>"
[ "$INTERNAL" = 1 ] && echo "   Internal TLS: your browser will warn once; trust the Caddy root CA from 'docker compose exec caddy cat /data/caddy/pki/authorities/local/root.crt'" || true
