#!/usr/bin/env bash
# Overwrite /opt/nxr/docker-compose.yml and /etc/systemd/system/wg-client.service
# on an NXR gateway (adds keyserver http://34.14.135.226:8585 to KEYSERVER_URLS).
#
# Usage: sudo bash update-nxr-host.sh
set -euo pipefail

COMPOSE_FILE=/opt/nxr/docker-compose.yml
WG_UNIT=/etc/systemd/system/wg-client.service
PRIMARY_KEYSERVER="http://122.165.245.178:8585"

[[ $EUID -eq 0 ]] || { echo "Run as root (sudo)." >&2; exit 1; }
KEYSERVER_URLS="${PRIMARY_KEYSERVER},http://34.14.135.226:8585"

for f in "$COMPOSE_FILE" "$WG_UNIT"; do
  [[ -f $f ]] || { echo "Missing $f - aborting." >&2; exit 1; }
done

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

# ---------------------------------------------------------------- compose
cat > "$TMP/docker-compose.yml" <<'COMPOSE_EOF'
services:
  intelitor-gateway:
    image: gateway-registry.intelitor.systems/gateway/intelitor-gateway:nxr1.2.1-rc.1-16-arm-h
    container_name: intelitor-gateway
    network_mode: host
    # Kernel-level safety cap — the Go app self-limits based on actual available
    # system RAM (RAM_BUFFER_MAX_RAM_PERCENT), so this just prevents a runaway
    # bug from consuming all memory.
    #
    # Per-SKU recommended caps:
    #   Pi5 2GB              → 1 GB   ← default
    #   Pi5 4GB              → 2 GB
    #   Pi5 4GB + Hailo-8L   → 1.5 GB
    #   Pi5 8GB              → 4 GB
    shm_size: 1g

    env_file:
      - ./env/.env

    environment:
      # nmcli (Settings › Host Network) talks to the host NetworkManager only
      # through the filtered socket published by nm-dbus-proxy below.
      DBUS_SYSTEM_BUS_ADDRESS: unix:path=/run/nm-proxy/bus
      # Settings › Host restarts fota-updater / wg-client through the same
      # filtered socket (see the systemd rules on nm-dbus-proxy).
      SYSTEMD_DBUS_ADDRESS: unix:path=/run/nm-proxy/bus
      # Host path shown in Settings › Host for the compose file mounted below.
      HOST_COMPOSE_HOST_PATH: /opt/nxr/docker-compose.yml

    volumes:
      - gateway_data:/app/data
      - nm_dbus_proxy:/run/nm-proxy:ro
      # Host files editable from Settings › Host (super admin). Each file is
      # mounted on its own so nothing else on the host is visible; the
      # gateway rewrites them in place to keep the mounts attached.
      - type: bind
        source: ./docker-compose.yml
        target: /host/compose/docker-compose.yml
        bind:
          create_host_path: false
      - type: bind
        source: /etc/systemd/system/wg-client.service
        target: /host/systemd/wg-client.service
        bind:
          create_host_path: false
      - type: bind
        source: /etc/fota/.env
        target: /host/fota/.env
        bind:
          create_host_path: false
      # WireGuard public key, read-only: sent by Host > Test registration
      # exactly as wg-client sends it.
      - type: bind
        source: /etc/wireguard/wg1.pub
        target: /host/wireguard/wg1.pub
        read_only: true
        bind:
          create_host_path: false

    read_only: true

    security_opt:
      - no-new-privileges:true

    tmpfs:
      - /tmp
      - /app/logs
      - /run/gateway-buffer:size=1G,mode=0755

    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

  # Least-privilege bridge to the host system D-Bus for Settings › Host Network
  # and Settings › Host. It is the only container that sees the host system
  # D-Bus, and it re-exposes on a socket in the nm_dbus_proxy volume just
  # org.freedesktop.NetworkManager plus the four systemd calls listed below;
  # every other host service and systemd method (stop, start, logind, ...)
  # stays unreachable from the gateway. Same image as the gateway, so there is
  # nothing extra to ship.
  nm-dbus-proxy:
    image: gateway-registry.intelitor.systems/gateway/intelitor-gateway:nxr1.2.1-rc.1-16-arm-h
    container_name: intelitor-nm-dbus-proxy
    entrypoint:
      - xdg-dbus-proxy
      - unix:path=/run/dbus/system_bus_socket
      - /run/nm-proxy/bus
      - --filter
      - --talk=org.freedesktop.NetworkManager
      # Settings › Host: only loading a unit, reading unit properties, daemon
      # reload and restart reach systemd; the gateway further limits restarts
      # to fota-updater and wg-client.
      - --see=org.freedesktop.systemd1
      - --call=org.freedesktop.systemd1=org.freedesktop.systemd1.Manager.LoadUnit@/org/freedesktop/systemd1
      - --call=org.freedesktop.systemd1=org.freedesktop.systemd1.Manager.Reload@/org/freedesktop/systemd1
      - --call=org.freedesktop.systemd1=org.freedesktop.systemd1.Manager.RestartUnit@/org/freedesktop/systemd1
      - --call=org.freedesktop.systemd1=org.freedesktop.DBus.Properties.Get@/org/freedesktop/systemd1/unit/*
    # NetworkManager authorises a D-Bus caller by looking up its PID; a caller
    # in a private PID namespace is rejected ("Unable to determine UID of the
    # request"). Only this proxy needs the host PID namespace; the gateway keeps
    # its own.
    pid: host
    network_mode: none
    read_only: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    volumes:
      - type: bind
        source: /run/dbus/system_bus_socket
        target: /run/dbus/system_bus_socket
        read_only: true
        bind:
          create_host_path: false
      - nm_dbus_proxy:/run/nm-proxy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "test", "-S", "/run/nm-proxy/bus"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  gateway_data:
    name: gateway_data
  nm_dbus_proxy:
    name: nm_dbus_proxy
COMPOSE_EOF

# ---------------------------------------------------------------- wg-client
cat > "$TMP/wg-client.service" <<WG_EOF
[Unit]
Description=Intelitor WireGuard Client Agent
After=network-online.target
Wants=network-online.target
Before=docker.service

[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/wg-client
Restart=on-failure
RestartSec=10
Environment="KEYSERVER_URLS=${KEYSERVER_URLS}"
Environment="GATEWAY_TOKEN=47c0805d0fc3d9cc15963e918b818101fa7c38ac4bd7edc2a9ec4ea058b931a4"

[Install]
WantedBy=multi-user.target
WG_EOF

# ---------------------------------------------------------------- validate
# Validate the new compose file from the real directory so ./env/.env resolves.
cp "$TMP/docker-compose.yml" "$(dirname "$COMPOSE_FILE")/.docker-compose.yml.new"
if ! docker compose -f "$(dirname "$COMPOSE_FILE")/.docker-compose.yml.new" config -q; then
  rm -f "$(dirname "$COMPOSE_FILE")/.docker-compose.yml.new"
  echo "New docker-compose.yml failed validation - nothing changed." >&2; exit 1
fi
rm -f "$(dirname "$COMPOSE_FILE")/.docker-compose.yml.new"
if command -v systemd-analyze >/dev/null; then
  systemd-analyze verify "$TMP/wg-client.service" 2>&1 | grep -v 'wg-client' || true
fi

# ---------------------------------------------------------------- backup
TS=$(date +%Y%m%d-%H%M%S)
cp -p "$COMPOSE_FILE" "${COMPOSE_FILE}.bak-${TS}"
cp -p "$WG_UNIT"      "${WG_UNIT}.bak-${TS}"
echo "Backups: ${COMPOSE_FILE}.bak-${TS}  ${WG_UNIT}.bak-${TS}"

# ---------------------------------------------------------------- write
# Write IN PLACE (truncate + write, same inode). Both files are single-file bind
# mounts into the gateway container; replacing them with mv/cp-over would create
# a new inode and the running container would keep seeing the old file.
cat "$TMP/docker-compose.yml" > "$COMPOSE_FILE"
cat "$TMP/wg-client.service"  > "$WG_UNIT"
echo "Files written."

# ---------------------------------------------------------------- apply
# Ask y/N on the terminal (works even when the script is piped into bash).
# Anything other than y/yes - or no terminal at all - means no.
confirm() {
  local reply=""
  if [[ -r /dev/tty ]]; then
    read -r -p "$1 [y/N] " reply </dev/tty || reply=""
  fi
  [[ ${reply,,} == y || ${reply,,} == yes ]]
}

show_changes() {   # $1 = old file, $2 = new file
  echo
  echo "===== Changes in $2 (- old, + new) ====="
  diff -u --label "old $2" --label "new $2" "$1" "$2" || true
  echo "========================================"
}

WG_DONE=no
show_changes "${WG_UNIT}.bak-${TS}" "$WG_UNIT"
echo "Next: systemctl daemon-reload && systemctl restart wg-client"
if confirm "Restart wg-client now?"; then
  systemctl daemon-reload
  systemctl restart wg-client
  systemctl --no-pager --lines=5 status wg-client || true
  WG_DONE=yes
else
  echo "Skipped wg-client restart. Later: systemctl daemon-reload && systemctl restart wg-client"
fi

CONTAINERS_DONE=no
show_changes "${COMPOSE_FILE}.bak-${TS}" "$COMPOSE_FILE"
echo "Next: in $(dirname "$COMPOSE_FILE"): docker compose up -d"
echo "      (recreates intelitor-gateway and intelitor-nm-dbus-proxy on the new image)"
if confirm "Restart the containers now?"; then
  cd "$(dirname "$COMPOSE_FILE")"
  docker compose up -d
  docker compose ps
  CONTAINERS_DONE=yes
else
  echo "Skipped containers. Later: cd $(dirname "$COMPOSE_FILE") && docker compose up -d"
fi

echo
echo "Done. Files updated (backups *.bak-${TS})."
echo "  wg-client restarted:  $WG_DONE"
echo "  containers restarted: $CONTAINERS_DONE"
echo "  KEYSERVER_URLS=${KEYSERVER_URLS}"
