#!/bin/sh
set -eu

usage() {
  echo "usage: novij-protocol-edge render" >&2
  exit 2
}

die() {
  echo "novij-protocol-edge: $*" >&2
  exit 1
}

https_host() {
  value="$1"
  case "$value" in
    https://*) value=${value#https://} ;;
    *) die "$2 must be an https URL" ;;
  esac
  value=${value%/}
  case "$value" in
    ""|*/*|*:*|*[!A-Za-z0-9.-]*) die "$2 must contain only a DNS hostname" ;;
  esac
  case "$value" in
    .*|*..*|*.) die "$2 contains an invalid DNS hostname" ;;
  esac
  printf '%s' "$value"
}

valid_target() {
  case "$1" in
    ""|*[!A-Za-z0-9.:-]*) return 1 ;;
    *) return 0 ;;
  esac
}

valid_port() {
  case "$1" in
    ""|*[!0-9]*) return 1 ;;
  esac
  [ "$1" -ge 1 ] && [ "$1" -le 65535 ]
}

[ "${1:-}" = "render" ] || usage

relay_host=$(https_host "${RELAY_NODE_BASE_URL:-}" RELAY_NODE_BASE_URL)
storage_host=$(https_host "${STORAGE_NODE_BASE_URL:-}" STORAGE_NODE_BASE_URL)
target_host=${NOVIJ_PROTOCOL_EDGE_TARGET_HOST:-172.19.0.1}
relay_port=${NOVIJ_PROTOCOL_EDGE_RELAY_PORT:-9081}
relay_active_slot_file=${NOVIJ_PROTOCOL_EDGE_RELAY_ACTIVE_SLOT_FILE:-/var/lib/novij-relay/active-slot}
relay_blue_port=${NOVIJ_PROTOCOL_EDGE_RELAY_BLUE_PORT:-9101}
relay_green_port=${NOVIJ_PROTOCOL_EDGE_RELAY_GREEN_PORT:-9102}
storage_ws_port=${NOVIJ_PROTOCOL_EDGE_STORAGE_WS_PORT:-38991}
storage_http_port=${NOVIJ_PROTOCOL_EDGE_STORAGE_HTTP_PORT:-38993}
output=${NOVIJ_PROTOCOL_EDGE_OUTPUT:-/opt/novij-mail/traefik/dynamic/novij-protocol.yml}
resolver=${NOVIJ_PROTOCOL_EDGE_CERT_RESOLVER:-letsencrypt}
storage_service_mode=${NOVIJ_PROTOCOL_EDGE_STORAGE_SERVICE_MODE:-static}
http2_enabled=${NOVIJ_PROTOCOL_EDGE_HTTP2_ENABLED:-true}

valid_target "$target_host" || die "NOVIJ_PROTOCOL_EDGE_TARGET_HOST is invalid"
valid_port "$relay_port" || die "NOVIJ_PROTOCOL_EDGE_RELAY_PORT is invalid"
valid_port "$relay_blue_port" || die "NOVIJ_PROTOCOL_EDGE_RELAY_BLUE_PORT is invalid"
valid_port "$relay_green_port" || die "NOVIJ_PROTOCOL_EDGE_RELAY_GREEN_PORT is invalid"
valid_port "$storage_ws_port" || die "NOVIJ_PROTOCOL_EDGE_STORAGE_WS_PORT is invalid"
valid_port "$storage_http_port" || die "NOVIJ_PROTOCOL_EDGE_STORAGE_HTTP_PORT is invalid"
case "$resolver" in ""|*[!A-Za-z0-9_-]*) die "NOVIJ_PROTOCOL_EDGE_CERT_RESOLVER is invalid" ;; esac
case "$storage_service_mode" in static|external) ;; *) die "NOVIJ_PROTOCOL_EDGE_STORAGE_SERVICE_MODE must be static or external" ;; esac
case "$http2_enabled" in
  true) alpn_protocols='h2, http/1.1' ;;
  false) alpn_protocols='http/1.1' ;;
  *) die "NOVIJ_PROTOCOL_EDGE_HTTP2_ENABLED must be true or false" ;;
esac
case "$output" in /*) ;; *) die "NOVIJ_PROTOCOL_EDGE_OUTPUT must be an absolute path" ;; esac

if [ -r "$relay_active_slot_file" ]; then
  relay_active_slot=$(tr -d '[:space:]' <"$relay_active_slot_file")
  case "$relay_active_slot" in
    blue) relay_port=$relay_blue_port ;;
    green) relay_port=$relay_green_port ;;
    *) die "relay active slot file contains an invalid slot" ;;
  esac
fi

directory=${output%/*}
[ "$directory" != "$output" ] || die "NOVIJ_PROTOCOL_EDGE_OUTPUT has no directory"
mkdir -p "$directory"
temporary=$(mktemp "$directory/.novij-protocol-edge.XXXXXX")
trap 'rm -f "$temporary"' EXIT HUP INT TERM

cat >"$temporary" <<EOF
http:
  routers:
    novij-relay-http:
      entryPoints: [web]
      rule: "Host(\`${relay_host}\`)"
      middlewares: [novij-protocol-redirect]
      service: novij-relay
    novij-relay-https:
      entryPoints: [websecure]
      rule: "Host(\`${relay_host}\`)"
      middlewares: [novij-protocol-security, novij-relay-route, novij-relay-rate, novij-relay-inflight]
      service: novij-relay
      tls:
        certResolver: ${resolver}
        options: novij-protocol-transport@file
    novij-storage-http:
      entryPoints: [web]
      rule: "Host(\`${storage_host}\`)"
      middlewares: [novij-protocol-redirect]
      service: novij-storage-http
    novij-storage-ws:
      entryPoints: [websecure]
      rule: "Host(\`${storage_host}\`) && PathPrefix(\`/ws/\`)"
      priority: 100
      middlewares: [novij-protocol-security, novij-storage-ws-rate, novij-storage-inflight]
      service: novij-storage-ws
      tls:
        certResolver: ${resolver}
        options: novij-protocol-transport@file
    novij-storage-https:
      entryPoints: [websecure]
      rule: "Host(\`${storage_host}\`)"
      middlewares: [novij-protocol-security, novij-storage-http-rate, novij-storage-inflight]
      service: novij-storage-http
      tls:
        certResolver: ${resolver}
        options: novij-protocol-transport@file
  middlewares:
    novij-protocol-redirect:
      redirectScheme:
        scheme: https
        permanent: true
    novij-protocol-security:
      headers:
        contentTypeNosniff: true
        frameDeny: true
        referrerPolicy: no-referrer
        stsSeconds: 31536000
        stsIncludeSubdomains: true
    novij-relay-route:
      headers:
        customRequestHeaders:
          X-Novij-Route-Mode: direct-go
        customResponseHeaders:
          X-Novij-Route-Mode: direct-go
    novij-relay-rate:
      rateLimit:
        average: 300
        burst: 600
        period: 1s
    novij-relay-inflight:
      inFlightReq:
        amount: 512
    novij-storage-http-rate:
      rateLimit:
        average: 100
        burst: 200
        period: 1s
    novij-storage-ws-rate:
      rateLimit:
        average: 40
        burst: 80
        period: 1s
    novij-storage-inflight:
      inFlightReq:
        amount: 256
  services:
    novij-relay:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: "http://${target_host}:${relay_port}"
EOF

if [ "$storage_service_mode" = "static" ]; then
  cat >>"$temporary" <<EOF
    novij-storage-ws:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: "http://${target_host}:${storage_ws_port}"
    novij-storage-http:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: "http://${target_host}:${storage_http_port}"
EOF
fi

cat >>"$temporary" <<EOF
tls:
  options:
    novij-protocol-transport:
      alpnProtocols: [${alpn_protocols}]
EOF

chmod 0640 "$temporary"
mv -f "$temporary" "$output"
trap - EXIT HUP INT TERM
echo "novij-protocol-edge: rendered $output"
