#!/usr/bin/env bash # Configure one shared api. 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" <([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"