#!/usr/bin/env bash
set -euo pipefail
# Idempotent install-node v2
ENV_FILE="${1:-}"
[[ -f "$ENV_FILE" ]] || { echo "Usage: sudo $0 /path/to/node.env"; exit 1; }
set -a; source "$ENV_FILE"; set +a

install_packages(){
  export DEBIAN_FRONTEND=noninteractive
  apt-get update
  apt-get install -y ca-certificates curl gnupg lsb-release ufw wireguard wireguard-tools gettext-base python3
  if ! command -v docker >/dev/null 2>&1; then
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    chmod a+r /etc/apt/keyrings/docker.gpg
    . /etc/os-release
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  fi
  systemctl enable docker
  systemctl start docker
}
configure_hosts(){
  for line in "${DB1_WG_IP} db1" "${DB2_WG_IP} db2" "${DB3_WG_IP} db3" "${PROXY1_WG_IP} proxy1"; do
    grep -qF "$line" /etc/hosts || echo "$line" >> /etc/hosts
  done
}
configure_wg(){
  mkdir -p /etc/wireguard
  chmod 700 /etc/wireguard
  if [[ ! -f /etc/wireguard/privatekey ]]; then
    wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey >/dev/null
  fi
  priv="$(cat /etc/wireguard/privatekey)"
  cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
Address = ${WG_ADDRESS}
ListenPort = ${WG_PORT}
PrivateKey = ${priv}
SaveConfig = false

[Peer]
PublicKey = ${DB1_PUBKEY}
AllowedIPs = ${DB1_WG_IP}/32
Endpoint = ${DB1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25

[Peer]
PublicKey = ${DB2_PUBKEY}
AllowedIPs = ${DB2_WG_IP}/32
Endpoint = ${DB2_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25

[Peer]
PublicKey = ${DB3_PUBKEY}
AllowedIPs = ${DB3_WG_IP}/32
Endpoint = ${DB3_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25

[Peer]
PublicKey = ${PROXY1_PUBKEY}
AllowedIPs = ${PROXY1_WG_IP}/32
Endpoint = ${PROXY1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  chmod 600 /etc/wireguard/wg0.conf
  systemctl enable wg-quick@wg0 >/dev/null 2>&1 || true
  systemctl restart wg-quick@wg0
}
deploy_db(){
  mkdir -p /opt/db-stack/{postgres-data,etcd-data}
  cd /opt/db-stack
  cat > Dockerfile <<'EOF'
FROM postgres:16-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip python3-psycopg2 curl ca-certificates postgresql-16-cron && pip3 install --no-cache-dir patroni[etcd3] && apt-get clean && rm -rf /var/lib/apt/lists/*
COPY patroni.yml /etc/patroni/patroni.yml
CMD ["patroni", "/etc/patroni/patroni.yml"]
EOF
  cat > compose.yaml <<EOF
services:
  etcd:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: ${NODE_NAME}-etcd
    restart: unless-stopped
    network_mode: host
    environment:
      ETCD_NAME: ${NODE_NAME}
      ETCD_DATA_DIR: /var/lib/etcd
      ETCD_INITIAL_ADVERTISE_PEER_URLS: http://${WG_ADDRESS%/*}:2380
      ETCD_ADVERTISE_CLIENT_URLS: http://${WG_ADDRESS%/*}:2379
      ETCD_LISTEN_PEER_URLS: http://${WG_ADDRESS%/*}:2380
      ETCD_LISTEN_CLIENT_URLS: http://${WG_ADDRESS%/*}:2379,http://127.0.0.1:2379
      ETCD_INITIAL_CLUSTER: db1=http://db1:2380,db2=http://db2:2380,db3=http://db3:2380
      ETCD_INITIAL_CLUSTER_TOKEN: pg-ha-cluster
      ETCD_INITIAL_CLUSTER_STATE: new
    volumes:
      - ./etcd-data:/var/lib/etcd
  patroni:
    build: .
    container_name: ${NODE_NAME}-patroni
    restart: unless-stopped
    network_mode: host
    depends_on:
      - etcd
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
      - ./patroni.yml:/etc/patroni/patroni.yml:ro
EOF
  cat > patroni.yml <<EOF
scope: ${PATRONI_SCOPE}
namespace: ${PATRONI_NAMESPACE}
name: ${NODE_NAME}
restapi:
  listen: ${WG_ADDRESS%/*}:8008
  connect_address: ${WG_ADDRESS%/*}:8008
etcd3:
  hosts:
    - db1:2379
    - db2:2379
    - db3:2379
bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_keep_size: 256MB
        shared_preload_libraries: "pg_cron"
        cron.database_name: "${PATRONI_DB}"
        max_connections: 300
        shared_buffers: 256MB
  initdb:
    - encoding: UTF8
    - data-checksums
postgresql:
  listen: ${WG_ADDRESS%/*}:5432
  connect_address: ${WG_ADDRESS%/*}:5432
  data_dir: /var/lib/postgresql/data
  bin_dir: /usr/lib/postgresql/16/bin
  authentication:
    superuser:
      username: ${PATRONI_SUPERUSER_USERNAME}
      password: ${POSTGRES_PASSWORD}
    replication:
      username: ${PATRONI_REPLICATION_USERNAME}
      password: ${REPLICATION_PASSWORD}
  pg_hba:
    - host replication ${PATRONI_REPLICATION_USERNAME} ${DB1_WG_IP}/32 scram-sha-256
    - host replication ${PATRONI_REPLICATION_USERNAME} ${DB2_WG_IP}/32 scram-sha-256
    - host replication ${PATRONI_REPLICATION_USERNAME} ${DB3_WG_IP}/32 scram-sha-256
    - host all all ${DB1_WG_IP}/32 scram-sha-256
    - host all all ${DB2_WG_IP}/32 scram-sha-256
    - host all all ${DB3_WG_IP}/32 scram-sha-256
    - host all all ${PROXY1_WG_IP}/32 scram-sha-256
    - host all all 127.0.0.1/32 scram-sha-256
  parameters:
    unix_socket_directories: /var/run/postgresql
tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nosync: false
EOF
  docker compose up -d --build
}
deploy_proxy(){
  mkdir -p /opt/proxy-stack/pgadmin-data
  cd /opt/proxy-stack
  cat > haproxy.cfg <<'EOF'
global
    log stdout format raw local0
defaults
    log global
    mode tcp
    timeout connect 5s
    timeout client  1m
    timeout server  1m
frontend postgres_write
    bind *:5432
    default_backend patroni_primary
backend patroni_primary
    mode tcp
    option httpchk GET /primary
    http-check expect status 200
    server db1 db1:5432 check port 8008
    server db2 db2:5432 check port 8008
    server db3 db3:5432 check port 8008
EOF
  cat > Caddyfile <<EOF
{
  email ${CADDY_EMAIL}
}
${PGADMIN_DOMAIN} {
  basic_auth {
    ${PGADMIN_BASIC_AUTH_USER} ${PGADMIN_BASIC_AUTH_HASH}
  }
  reverse_proxy 127.0.0.1:8080
}
${API_DOMAIN} {
  basic_auth {
    ${API_BASIC_AUTH_USER} ${API_BASIC_AUTH_HASH}
  }
  reverse_proxy 127.0.0.1:3000
}
EOF
  cat > compose.yaml <<EOF
services:
  haproxy:
    image: haproxy:3.0
    container_name: pg-haproxy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
  pgadmin:
    image: dpage/pgadmin4:9.13
    container_name: pgadmin
    restart: unless-stopped
    environment:
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./pgadmin-data:/var/lib/pgadmin
  postgrest:
    image: postgrest/postgrest:v12.2.12
    container_name: postgrest
    restart: unless-stopped
    environment:
      PGRST_DB_URI: ${POSTGREST_DB_URI}
      PGRST_DB_SCHEMAS: ${POSTGREST_DB_SCHEMAS}
      PGRST_DB_ANON_ROLE: ${POSTGREST_DB_ANON_ROLE}
      PGRST_SERVER_HOST: 0.0.0.0
      PGRST_SERVER_PORT: 3000
    ports:
      - "127.0.0.1:3000:3000"
  caddy:
    image: caddy:2
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
volumes:
  caddy_data:
  caddy_config:
EOF
  docker compose up -d
}
install_packages
configure_hosts
configure_wg
if [[ "$ROLE" == "db" ]]; then deploy_db; else deploy_proxy; fi
