#!/usr/bin/env bash
# Configure one shared api.<base-domain> for the base storefront and all tenant
# subdomains. Idempotent. Run as root after the API DNS record resolves here.

set -euo pipefail

DOMAIN=""
EMAIL=""
UPSTREAM="https://127.0.0.1:445"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --domain)   DOMAIN="$2"; shift 2 ;;
    --email)    EMAIL="$2"; shift 2 ;;
    --upstream) UPSTREAM="$2"; shift 2 ;;
    *) echo "unknown argument: $1" >&2; exit 2 ;;
  esac
done

[[ $EUID -eq 0 ]] || { echo "must run as root" >&2; exit 1; }
[[ "$DOMAIN" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]] || {
  echo "--domain must be a valid lowercase hostname" >&2; exit 2;
}
[[ "$EMAIL" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]] || {
  echo "--email must be valid" >&2; exit 2;
}
[[ "$UPSTREAM" =~ ^https?://[a-zA-Z0-9.:-]+$ ]] || {
  echo "--upstream must be an http(s) origin without a path" >&2; exit 2;
}

API_DOMAIN="api.$DOMAIN"
CONF="/etc/nginx/sites-available/$API_DOMAIN"
DOMAIN_REGEX="${DOMAIN//./\\.}"

echo "==> checking DNS for $API_DOMAIN"
getent hosts "$API_DOMAIN" >/dev/null || {
  echo "ERROR: $API_DOMAIN does not resolve; create DNS before provisioning TLS" >&2
  exit 1
}

cat > "$CONF" <<NGINX
# Managed by marketplaces configure-api-domain.sh. Manual edits are overwritten.
# Storefront $DOMAIN and its tenant subdomains share https://$API_DOMAIN.
server {
    listen 80;
    listen [::]:80;
    server_name $API_DOMAIN;

    access_log /var/log/nginx/$API_DOMAIN.access.log;
    error_log  /var/log/nginx/$API_DOMAIN.error.log;

    set \$cors_origin "";
    set \$storefront_host "$DOMAIN";
    if (\$http_origin ~* "^https://(?<allowed_storefront>([a-z0-9-]+\\.)*$DOMAIN_REGEX)$") {
        set \$cors_origin \$http_origin;
        set \$storefront_host \$allowed_storefront;
    }

    add_header Access-Control-Allow-Origin      \$cors_origin always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Access-Control-Allow-Methods     "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers     "Authorization, Content-Type, AdminWebSessionID, WebSessionID, Currency, X-Language, X-Region, X-Requested-With" always;
    add_header Vary                             "Origin" always;

    if (\$request_method = OPTIONS) { return 204; }

    location / {
        proxy_pass $UPSTREAM;
        proxy_http_version 1.1;
        # Browser Origin selects the storefront tenant while every tenant under
        # this base domain shares one public API hostname.
        proxy_set_header Host              \$storefront_host;
        proxy_set_header X-Forwarded-Host  $API_DOMAIN;
        proxy_set_header X-Storefront-Host \$storefront_host;
        # nginx has already validated and answered CORS. The existing backend
        # rejects browser Origin on :445, so do not forward it a second time.
        proxy_set_header Origin            "";
        proxy_set_header X-Real-IP         \$remote_addr;
        proxy_set_header X-Forwarded-For   \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_read_timeout 60s;
        proxy_connect_timeout 10s;

        proxy_ssl_server_name on;
        proxy_ssl_name $DOMAIN;
    }
}
NGINX

ln -sfn "$CONF" "/etc/nginx/sites-enabled/$API_DOMAIN"
nginx -t

certbot --nginx -d "$API_DOMAIN" \
  --non-interactive --agree-tos --email "$EMAIL" \
  --redirect --keep-until-expiring

nginx -t
systemctl reload nginx

echo "==> verifying https://$API_DOMAIN/bootstrap"
bootstrap_tmp="$(mktemp)"
trap 'rm -f "$bootstrap_tmp"' EXIT
content_type="$(curl --resolve "$API_DOMAIN:443:127.0.0.1" -fsS \
  -o "$bootstrap_tmp" -w '%{content_type}' \
  "https://$API_DOMAIN/bootstrap")"
[[ "$content_type" == application/json* ]] || {
  echo "ERROR: $API_DOMAIN/bootstrap returned $content_type, expected application/json" >&2
  exit 1
}
jq -e 'type == "object"' "$bootstrap_tmp" >/dev/null
rm -f "$bootstrap_tmp"
trap - EXIT

echo "configured: $DOMAIN -> https://$API_DOMAIN -> $UPSTREAM"
