Skip to content
Open
174 changes: 128 additions & 46 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,66 +1,148 @@
# Public routing, discovery, and relay identity persistence
# Portal relay configuration.
#
# Copy to .env and edit. Every key here is read either by the relay binary or by
# Docker Compose; nothing else reads this file.
#
# Check a configured file before starting anything. The report gives the
# effective value and source of every key, names any key that nothing reads, and
# says which features are off and what is missing:
#
# docker compose run --rm -T portal config --env-file /dev/stdin < .env
#
# -T because the file arrives on stdin. Without it Compose asks for a TTY and
# older versions fail with "the input device is not a TTY".
#
# API_PORT and SNI_PORT are deliberately absent. The bundled topology fixes them
# at 4017 and 443 because the relay reaches its own API listener through its SNI
# router; overriding them breaks that wiring rather than moving it.


# ─────────────────────────────────────────────────────────────────────────────
# 1. Required
# ─────────────────────────────────────────────────────────────────────────────

# Public HTTPS origin browsers and tunnel clients use. Must be publicly
# resolvable when DISCOVERY=true; localhost and other local-only names are
# rejected by public discovery.
PORTAL_URL=https://localhost
# Optional directory inside the Portal container containing a custom SPA index.html.
# Leave empty to use the official frontend embedded in the Portal binary.
PORTAL_FRONTEND_DIR=
DISCOVERY=true

# Bearer token for the admin and policy APIs. Leaving this empty leaves those
# APIs unauthenticated. Generate one with: openssl rand -hex 32
ADMIN_TOKEN=


# ─────────────────────────────────────────────────────────────────────────────
# 2. Storage and ports
# ─────────────────────────────────────────────────────────────────────────────

# Directory for relay identity, policy state, and certificate material.
# Also the in-container mount point for ./.portal-certs.
IDENTITY_PATH=/portal-certs

# Public HTTPS/SNI
# Public and listen UDP port for the relay overlay. Required when DISCOVERY=true.
# Compose publishes this port.
WIREGUARD_PORT=51820
# Set when enabling public UDP or raw TCP lease ports.

# Inclusive lease port range shared by the UDP and raw TCP transports.
# 0 disables both. Enabling a transport without a range does nothing; the relay
# reports that at startup. Publish the same range in docker-compose.yml when set.
MIN_PORT=0
MAX_PORT=0
UDP_ENABLED=false
TCP_ENABLED=false

# Supported managed values: cloudflare, gcloud, hetzner, njalla, route53, vultr.
# Reused for ACME DNS-01, managed A records, ECH HTTPS records, and optional ENS DNS automation.
ACME_DNS_PROVIDER=

# Cloudflare API token (required when ACME_DNS_PROVIDER=cloudflare)
CLOUDFLARE_TOKEN=

# Google Cloud DNS settings. (required when ACME_DNS_PROVIDER=gcloud)
GCP_PROJECT_ID=
GCP_MANAGED_ZONE=
GOOGLE_APPLICATION_CREDENTIALS=
# ─────────────────────────────────────────────────────────────────────────────
# 3. Feature toggles - the defaults suit a single private relay
# ─────────────────────────────────────────────────────────────────────────────

# Hetzner DNS settings (required when ACME_DNS_PROVIDER=hetzner)
HETZNER_API_TOKEN=
# Serve relay discovery endpoints and poll discovery peers. Requires a publicly
# reachable PORTAL_URL and an open WIREGUARD_PORT/udp.
DISCOVERY=true

# Route53 settings (required when ACME_DNS_PROVIDER=route53)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_SESSION_TOKEN=
AWS_REGION=
AWS_DEFAULT_REGION=
AWS_HOSTED_ZONE_ID=
# Required only when ACME_DNS_PROVIDER=route53 and ENS_GASLESS_ENABLED=true and no ACTIVE KSK already exists.
AWS_DNSSEC_KMS_KEY_ARN=
# Comma-separated relay API URLs to seed discovery from.
BOOTSTRAPS=

# Vultr DNS settings (required when ACME_DNS_PROVIDER=vultr)
VULTR_API_KEY=
# Enable the UDP and raw TCP lease transports. Both need MIN_PORT/MAX_PORT.
UDP_ENABLED=false
TCP_ENABLED=false

# Njalla DNS settings (required when ACME_DNS_PROVIDER=njalla)
NJALLA_TOKEN=
# Initial landing-page state. Admin changes are persisted in policy.json.
LANDING_PAGE_ENABLED=false

# ENS gasless DNS import automation. When enabled, Portal uses ACME_DNS_PROVIDER
# for DNSSEC and ENS TXT automation, even when certificate files are managed manually.
ENS_GASLESS_ENABLED=false
# Directory inside the container holding a custom SPA index.html. Leave empty to
# serve the frontend embedded in the binary. Uncomment the matching mount in
# docker-compose.yml when setting this.
PORTAL_FRONTEND_DIR=

# Admin/auth configuration. Use a long random value for production relays.
ADMIN_TOKEN=
# Trust X-Forwarded-* and X-Real-IP. Portal owns its public port in the bundled
# topology, so client addresses already come from the socket; enable this only
# when a proxy you control genuinely sits in front. TRUSTED_PROXY_CIDRS empty
# means the default private and loopback ranges.
TRUST_PROXY_HEADERS=false
TRUSTED_PROXY_CIDRS=

# Optional embedded Sui x402 facilitator exposed under /api/x402.
# Relay-owned Sui x402 facilitator under /api/x402.
# X402_ENABLED=true without X402_PAY_TO cannot receive payments.
X402_ENABLED=false
X402_TESTNET=false
X402_PAY_TO=

# Enable only when an explicitly configured upstream proxy supplies forwarded client IP headers.
# Optionally restrict which proxy source ranges may supply those headers; leave empty for default private/loopback proxy ranges.
TRUST_PROXY_HEADERS=false
TRUSTED_PROXY_CIDRS=
# pprof diagnostics. Keep the address on loopback unless the port is otherwise
# protected. PPROF_PORT only matters when you also uncomment the pprof port
# mapping in docker-compose.yml, which exposes it to the host.
PPROF_ENABLED=false
PPROF_ADDR=127.0.0.1:6060
# PPROF_PORT=6060

# Initial landing-page state. Admin changes are persisted in policy.json.
LANDING_PAGE_ENABLED=false

# ─────────────────────────────────────────────────────────────────────────────
# 4. DNS provider - pick one, fill only that block
#
# Used for ACME DNS-01, managed A records, ECH HTTPS records, and optional ENS
# DNS automation. Leave ACME_DNS_PROVIDER empty to place fullchain.pem and
# privatekey.pem under IDENTITY_PATH yourself.
#
# Only one provider's credentials are ever read. The unused blocks below stay
# commented out on purpose.
# ─────────────────────────────────────────────────────────────────────────────

# cloudflare | gcloud | hetzner | njalla | route53 | vultr
ACME_DNS_PROVIDER=

# -> ACME_DNS_PROVIDER=cloudflare
# The token needs Zone:Read as well as DNS:Edit. DNS:Edit alone cannot
# locate the zone, and issuance fails with "no cloudflare zone found".
# CLOUDFLARE_TOKEN=

# -> ACME_DNS_PROVIDER=gcloud
# Project is auto-detected from ADC or GCE metadata when omitted.
# GOOGLE_APPLICATION_CREDENTIALS is read by the Google Cloud SDK itself,
# not by a relay flag: mount the service account file and point this at
# the in-container path.
# GCP_PROJECT_ID=
# GCP_MANAGED_ZONE=
# GOOGLE_APPLICATION_CREDENTIALS=

# -> ACME_DNS_PROVIDER=hetzner
# HETZNER_API_TOKEN=

# -> ACME_DNS_PROVIDER=route53
# Omit the static keys to use the default AWS credential chain.
# AWS_DNSSEC_KMS_KEY_ARN is needed only with ENS_GASLESS_ENABLED=true when
# no ACTIVE KSK exists yet.
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# AWS_SESSION_TOKEN=
# AWS_REGION=
# AWS_HOSTED_ZONE_ID=
# AWS_DNSSEC_KMS_KEY_ARN=

# -> ACME_DNS_PROVIDER=vultr
# VULTR_API_KEY=

# -> ACME_DNS_PROVIDER=njalla
# NJALLA_TOKEN=

# ENS gasless DNS import automation. Requires ACME_DNS_PROVIDER to be set, even
# when certificate files are managed manually. Not needed for normal relay
# operation; leave false unless you specifically want it.
ENS_GASLESS_ENABLED=false
43 changes: 42 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install fmt vet lint lint-auto test tidy all run build build-frontend build-docs build-tunnel build-server build-server-bin clean load-test
.PHONY: help install fmt vet lint lint-auto test tidy all run build build-frontend build-docs build-tunnel build-server build-server-bin clean load-test check-env-example env-reference

.DEFAULT_GOAL := help

Expand All @@ -16,6 +16,8 @@ help:
@echo " make fmt - Apply gofmt/goimports"
@echo " make lint-auto - Run autofix lint/format pipeline"
@echo " make test - Run Go and frontend tests"
@echo " make check-env-example - Fail if .env.example is missing a configuration key"
@echo " make env-reference - Print every configuration key, generated from the flags"
@echo " make build - Build Go tunnel and relay server artifacts"
@echo " make build-frontend - Build React frontend (Tailwind CSS 4)"
@echo " make build-docs - Build documentation site (SvelteKit)"
Expand Down Expand Up @@ -52,6 +54,45 @@ tidy:
go mod tidy
go mod verify

# The keys themselves are owned by the flag definitions in
# cmd/relay-server/main.go and by the catalog of keys other components read.
# .env.example and the configuration reference are documentation of that set.
# Adding a flag without documenting it is how configuration drifts away from the
# code, so fail loudly here rather than let an operator find the gap in
# production. Keys the bundled topology pins are excluded on purpose; see
# cmd/relay-server/envcatalog.go.
CONFIG_DOC := docs/src/routes/configuration/+page.md

check-env-example:
@go run ./cmd/relay-server config --format names > /tmp/portal-env-names.txt
@status=0; \
missing=""; \
while read -r name; do \
grep -qE "^#? *$$name=" .env.example || missing="$$missing $$name"; \
done < /tmp/portal-env-names.txt; \
if [ -n "$$missing" ]; then \
echo "[env] .env.example does not document:"; \
for name in $$missing; do echo " - $$name"; done; \
status=1; \
fi; \
missing=""; \
while read -r name; do \
grep -qF "\`$$name\`" $(CONFIG_DOC) || missing="$$missing $$name"; \
done < /tmp/portal-env-names.txt; \
if [ -n "$$missing" ]; then \
echo "[env] $(CONFIG_DOC) does not document:"; \
for name in $$missing; do echo " - $$name"; done; \
status=1; \
fi; \
if [ "$$status" -ne 0 ]; then \
echo "[env] run 'make env-reference' to see each key with its usage text"; \
exit 1; \
fi; \
echo "[env] .env.example and $(CONFIG_DOC) document every configuration key"

env-reference:
@go run ./cmd/relay-server config --format env

all: fmt vet lint test build

run:
Expand Down
Loading
Loading