#!/usr/bin/env bash
# GroupChat server installer for a fresh Ubuntu 22.04 / 24.04 VM (x86 or ARM).
# Safe to re-run: it updates the code and keeps all data.
#
#   sudo bash install.sh [DOMAIN] [DUCKDNS_TOKEN] [--import-data]
#
# DOMAIN        e.g. mygroup.duckdns.org. If empty, <public-ip>.sslip.io is used.
# DUCKDNS_TOKEN optional; keeps a duckdns.org name pointed at this server.
# --import-data copy db.sqlite + uploads from the bundle (only if the server has no data yet)
set -euo pipefail

if [ "$(id -u)" -ne 0 ]; then echo "Run with sudo."; exit 1; fi
export DEBIAN_FRONTEND=noninteractive

SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
APP_DIR=/opt/groupchat
DATA_DIR=/var/lib/groupchat
BACKUP_DIR=/var/backups/groupchat
DOMAIN="${1:-}"
DUCKDNS_TOKEN="${2:-}"
IMPORT_DATA=0
for a in "$@"; do [ "$a" = "--import-data" ] && IMPORT_DATA=1; done
[ "$DOMAIN" = "--import-data" ] && DOMAIN=""
[ "$DUCKDNS_TOKEN" = "--import-data" ] && DUCKDNS_TOKEN=""

step() { echo; echo "==> $*"; }

step "Adding swap (small VMs need it to install packages)"
if [ "$(swapon --show | wc -l)" -eq 0 ] && [ "$(awk '/MemTotal/{print $2}' /proc/meminfo)" -lt 3000000 ]; then
  fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048
  chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
  grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi

step "Installing system packages"
apt-get update -y || echo "Warning: some package lists failed to update; continuing."
apt-get install -y ca-certificates curl gnupg build-essential python3 sqlite3 rsync \
  debian-keyring debian-archive-keyring apt-transport-https

step "Installing Node.js 22 LTS"
if ! command -v node >/dev/null || ! node -v | grep -qE '^v(2[2-9]|[3-9][0-9])'; then
  curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
  apt-get install -y nodejs
fi
node -v

step "Installing Caddy (automatic HTTPS)"
if ! command -v caddy >/dev/null; then
  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list
  apt-get update -y
  apt-get install -y caddy
fi

step "Creating app user and folders"
id groupchat >/dev/null 2>&1 || useradd --system --home "$APP_DIR" --shell /usr/sbin/nologin groupchat
mkdir -p "$APP_DIR" "$DATA_DIR" "$BACKUP_DIR"

step "Copying application code"
for f in server.js package.json package-lock.json create-admin.js reset-admin.js; do
  [ -f "$SRC_DIR/$f" ] && install -m 644 "$SRC_DIR/$f" "$APP_DIR/$f"
done
cd "$APP_DIR"
if [ -f package-lock.json ]; then npm ci --omit=dev --no-audit --no-fund || npm install --omit=dev --no-audit --no-fund
else npm install --omit=dev --no-audit --no-fund; fi

if [ "$IMPORT_DATA" = "1" ]; then
  if [ -f "$DATA_DIR/db.sqlite" ]; then
    echo "Server already has data - NOT importing (existing data kept)."
  elif [ -f "$SRC_DIR/db.sqlite" ]; then
    step "Importing your existing groups, messages and uploads"
    cp "$SRC_DIR"/db.sqlite* "$DATA_DIR/" 2>/dev/null || true
    [ -d "$SRC_DIR/uploads" ] && rsync -a "$SRC_DIR/uploads/" "$DATA_DIR/uploads/"
  fi
fi
chown -R groupchat:groupchat "$DATA_DIR"
chown -R root:root "$APP_DIR"

step "Creating the background service"
cat > /etc/systemd/system/groupchat.service <<UNIT
[Unit]
Description=GroupChat
After=network.target

[Service]
User=groupchat
Group=groupchat
WorkingDirectory=$APP_DIR
Environment=NODE_ENV=production
Environment=DATA_DIR=$DATA_DIR
Environment=PORT=3000
Environment=HOST=127.0.0.1
ExecStart=/usr/bin/node $APP_DIR/server.js
Restart=always
RestartSec=3
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=$DATA_DIR

[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable groupchat >/dev/null 2>&1
systemctl restart groupchat

step "Working out the web address"
PUBLIC_IP="$(curl -fsS4 https://api.ipify.org || curl -fsS4 https://ifconfig.me || true)"
if [ -z "$DOMAIN" ]; then
  if [ -f /etc/groupchat-domain ]; then DOMAIN="$(cat /etc/groupchat-domain)"
  elif [ -n "$PUBLIC_IP" ]; then DOMAIN="${PUBLIC_IP//./-}.sslip.io"
  else echo "Could not detect the public IP. Re-run with a domain."; exit 1; fi
fi
echo "$DOMAIN" > /etc/groupchat-domain

if [ -n "$DUCKDNS_TOKEN" ] && [[ "$DOMAIN" == *.duckdns.org ]]; then
  SUB="${DOMAIN%.duckdns.org}"
  echo "url=\"https://www.duckdns.org/update?domains=$SUB&token=$DUCKDNS_TOKEN&ip=\"" > /etc/groupchat-duckdns.conf
  chmod 600 /etc/groupchat-duckdns.conf
  echo "*/5 * * * * root curl -fsS -K /etc/groupchat-duckdns.conf -o /dev/null" > /etc/cron.d/groupchat-duckdns
  curl -fsS -K /etc/groupchat-duckdns.conf; echo
fi

cat > /etc/caddy/Caddyfile <<CADDY
$DOMAIN {
	encode gzip
	request_body {
		max_size 110MB
	}
	reverse_proxy 127.0.0.1:3000
}
CADDY
systemctl enable caddy >/dev/null 2>&1
systemctl reload caddy 2>/dev/null || systemctl restart caddy

step "Opening ports 80/443 in the VM firewall"
if iptables -S INPUT 2>/dev/null | grep -q REJECT; then
  iptables -C INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || \
    iptables -I INPUT 1 -p tcp -m multiport --dports 80,443 -j ACCEPT
  if command -v netfilter-persistent >/dev/null; then netfilter-persistent save >/dev/null 2>&1 || true; fi
fi
if command -v ufw >/dev/null && ufw status | grep -q active; then ufw allow 80/tcp; ufw allow 443/tcp; fi

step "Setting up daily database backups (kept 14 days)"
cat > /etc/cron.daily/groupchat-backup <<BK
#!/bin/sh
sqlite3 $DATA_DIR/db.sqlite ".backup $BACKUP_DIR/db-\$(date +%F).sqlite" && find $BACKUP_DIR -name 'db-*.sqlite' -mtime +14 -delete
BK
chmod 755 /etc/cron.daily/groupchat-backup

cat > /usr/local/bin/groupchat-create-admin <<'ADM'
#!/usr/bin/env bash
set -e
read -rp "Admin username: " U
read -rsp "Admin password (6+ characters): " P; echo
cd /opt/groupchat && sudo -u groupchat env DATA_DIR=/var/lib/groupchat ADMIN_USER="$U" ADMIN_PASS="$P" node create-admin.js "$@"
ADM
chmod 755 /usr/local/bin/groupchat-create-admin

for _ in $(seq 1 15); do [ -s "$DATA_DIR/db.sqlite" ] && break; sleep 1; done
if ! systemctl is-active --quiet groupchat; then
  echo "The app failed to start. Last log lines:"; journalctl -u groupchat -n 30 --no-pager; exit 1
fi

HAS_ADMIN=0
if [ -s "$DATA_DIR/db.sqlite" ]; then
  HAS_ADMIN="$(sqlite3 -readonly "$DATA_DIR/db.sqlite" 'SELECT COUNT(*) FROM admin' 2>/dev/null || echo 0)"
fi
if [ "$HAS_ADMIN" = "0" ] && [ -t 0 ]; then
  step "Create your admin account"
  /usr/local/bin/groupchat-create-admin
fi

step "Checking HTTPS (may take up to a minute the first time)"
OK=0
for _ in $(seq 1 12); do
  if curl -fsS -o /dev/null "https://$DOMAIN/"; then OK=1; break; fi
  sleep 5
done

echo
echo "=============================================================="
if [ "$OK" = "1" ]; then
  echo " DONE!  Your app is live at:  https://$DOMAIN"
  echo " Admin panel:                 https://$DOMAIN/admin"
else
  echo " The app is running, but https://$DOMAIN isn't reachable yet."
  echo " Most likely ports 80 and 443 are not open in your cloud"
  echo " provider's firewall (Oracle: VCN > Security List > Ingress rules)."
  echo " Open them, then run:  sudo systemctl restart caddy"
fi
echo " Invite links copied from the admin panel will use this address."
echo " Logs:  sudo journalctl -u groupchat -f"
echo "=============================================================="
