#!/usr/bin/env bash set -euo pipefail # load env (prefer absolute ENV_FILE injected by Python CLI; fallback to ./env.sh) if [[ -n "${ENV_FILE:-}" && -f "${ENV_FILE}" ]]; then set -a; . "${ENV_FILE}"; set +a elif [[ -f "./env.sh" ]]; then set -a; . ./env.sh; set +a fi : "${HOSTNAME:?HOSTNAME not set in env.sh}" : "${EMAIL:?EMAIL not set in env.sh}" export SYSTEMD_PAGER="" export SYSTEMD_COLORS="0" DEBIAN_FRONTEND=noninteractive # sanity check if [[ "${HOSTNAME}" == "localhost" || "${HOSTNAME}" == "127.0.0.1" || "${HOSTNAME}" == "ubuntu" ]]; then echo "ERROR: HOSTNAME cannot be 'localhost'. Use a public FQDN." >&2 exit 1 fi echo -e "\n* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *" # set paths & ports vars WEBROOT="/var/www/${HOSTNAME}" LE_ACME_DIR="/var/www/letsencrypt" SITES_AVAIL="/etc/nginx/sites-available" SITES_EN="/etc/nginx/sites-enabled" BASE_HTTP="${SITES_AVAIL}/${HOSTNAME}" # :80 vhost BASE_HTTPS="${SITES_AVAIL}/${HOSTNAME}-ssl" # :443 vhost (we’ll write it ourselves) WSS_AVAIL="${SITES_AVAIL}/wss-config-nym" BACKUP_DIR="/etc/nginx/sites-backups" NYM_PORT_HTTP="${NYM_PORT_HTTP:-8080}" NYM_PORT_WSS="${NYM_PORT_WSS:-9000}" WSS_LISTEN_PORT="${WSS_LISTEN_PORT:-9001}" mkdir -p "${WEBROOT}" "${LE_ACME_DIR}" "${BACKUP_DIR}" "${SITES_AVAIL}" "${SITES_EN}" # helpers neat_backup() { local file="$1"; [[ -f "$file" ]] || return 0 local sha_now; sha_now="$(sha256sum "$file" | awk '{print $1}')" || return 0 local tag; tag="$(basename "$file")" local latest="${BACKUP_DIR}/${tag}.latest" if [[ -f "$latest" ]]; then local sha_prev; sha_prev="$(awk '{print $1}' "$latest")" [[ "$sha_now" == "$sha_prev" ]] && return 0 fi cp -a "$file" "${BACKUP_DIR}/${tag}.bak.$(date +%s)" echo "$sha_now ${tag}" > "$latest" ls -1t "${BACKUP_DIR}/${tag}.bak."* 2>/dev/null | tail -n +6 | xargs -r rm -f } ensure_enabled() { local src="$1"; local name; name="$(basename "$src")" ln -sf "$src" "${SITES_EN}/${name}" } cert_ok() { [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" && -s "/etc/letsencrypt/live/${HOSTNAME}/privkey.pem" ]] } fetch_landing_html() { local url="https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html" mkdir -p "${WEBROOT}" if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" -o "${WEBROOT}/index.html" || true else wget -qO "${WEBROOT}/index.html" "$url" || true fi if [[ ! -s "${WEBROOT}/index.html" ]]; then cat > "${WEBROOT}/index.html" <<'HTML' Nym Exit Gateway

Nym Exit Gateway

This is a Nym Exit Gateway. The operator of this router has no access to any of the data routing through that due to encryption design.

HTML fi } inject_email() { local file="${WEBROOT}/index.html" [[ -n "${EMAIL:-}" && -s "$file" ]] || return 0 # Escape characters that would break sed replacement local esc_email esc_email="$(printf '%s' "$EMAIL" | sed -e 's/[&/\]/\\&/g')" # try to update existing meta (case-insensitive on the name attr) if grep -qiE ']+name=["'"'"']contact:email["'"'"']' "$file"; then sed -i -E \ "s|(]+name=[\"']contact:email[\"'][^>]*content=\")[^\"]*(\"[^>]*>)|\1${esc_email}\2|I" \ "$file" || true return 0 fi # insert before if present (case-insensitive) if grep -qi '' "$file"; then awk -v email="$EMAIL" ' BEGIN{IGNORECASE=1} /<\/head>/ && !done { print " " done=1 } { print } ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" return 0 fi # fallback: append at end printf '\n\n' "$EMAIL" >> "$file" || true } fetch_logo() { local logo_url="https://raw.githubusercontent.com/nymtech/websites/refs/heads/main/www/nym.com/public/images/Nym_meta_Image.png?token=GHSAT0AAAAAACEERII7URYRTFACZ4F2OWZ42GMCPBQ" mkdir -p "${WEBROOT}/images" if [[ ! -s "${WEBROOT}/images/nym_logo.png" ]]; then if command -v curl >/dev/null 2>&1; then curl -fsSL "$logo_url" -o "${WEBROOT}/images/nym_logo.png" || true else wget -qO "${WEBROOT}/images/nym_logo.png" "$logo_url" || true fi fi } reload_nginx() { nginx -t && systemctl reload nginx; } # landing page (idempotent) fetch_landing_html inject_email fetch_logo echo "Landing page at ${WEBROOT}/index.html" # disable default and stale SSL configs [[ -L "${SITES_EN}/default" ]] && unlink "${SITES_EN}/default" || true for f in "${SITES_EN}"/*; do [[ -L "$f" ]] || continue if grep -q "/etc/letsencrypt/live/localhost" "$f"; then echo "Disabling vhost referencing localhost cert: $f"; unlink "$f" fi done for f in "${SITES_EN}"/*; do [[ -L "$f" ]] || continue if grep -qE 'listen\s+.*443' "$f"; then cert=$(awk '/ssl_certificate[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1) key=$(awk '/ssl_certificate_key[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1) if [[ -n "${cert:-}" && ! -s "$cert" ]] || [[ -n "${key:-}" && ! -s "$key" ]]; then echo "Disabling SSL vhost with missing cert/key: $f"; unlink "$f" fi fi done # HTTP :80 vhost (ACME-safe, proxy to :8080) neat_backup "${BASE_HTTP}" cat > "${BASE_HTTP}" </dev/null; then echo "WARNING: Can't reach Let's Encrypt directory. We'll still keep HTTP up." >&2 fi THIS_IP="$(curl -fsS -4 https://ifconfig.me || true)" DNS_IP="$(getent ahostsv4 "${HOSTNAME}" 2>/dev/null | awk '{print $1; exit}')" echo "Public IPv4: ${THIS_IP:-unknown} DNS A(${HOSTNAME}): ${DNS_IP:-unresolved}" if [[ -n "${THIS_IP:-}" && -n "${DNS_IP:-}" && "${THIS_IP}" != "${DNS_IP}" ]]; then echo "WARNING: DNS for ${HOSTNAME} does not match this server's public IPv4." fi timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -qi yes || timedatectl set-ntp true || true # install certbot if missing if ! command -v certbot >/dev/null 2>&1; then if command -v snap >/dev/null 2>&1; then snap install core || true; snap refresh core || true snap install --classic certbot; ln -sf /snap/bin/certbot /usr/bin/certbot else apt-get update -y >/dev/null 2>&1 || true apt-get install -y certbot >/dev/null 2>&1 || true fi fi # issue/renew via WEBROOT (no nginx auto-edit), non-fatal if it fails STAGING_FLAG=""; [[ "${CERTBOT_STAGING:-0}" == "1" ]] && STAGING_FLAG="--staging" && echo "Using Let's Encrypt STAGING." if ! cert_ok; then certbot certonly --non-interactive --agree-tos -m "${EMAIL}" -d "${HOSTNAME}" \ --webroot -w "${LE_ACME_DIR}" ${STAGING_FLAG} || true fi # create own 443 vhost (only if certs exist) if cert_ok; then neat_backup "${BASE_HTTPS}" cat > "${BASE_HTTPS}" <HTTPS (keeps ACME path in HTTP too via separate small server) neat_backup "${BASE_HTTP}" cat > "${BASE_HTTP}" < "${WSS_AVAIL}" <