1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# ── Global settings (no section header) ─────────────────────────────────────
# HTTP listen port (default: 3000)
= 3000
# Control port for cache management endpoints (default: 17809)
= 17809
# Optional: Bearer token for /refresh-cache authentication
# If set, callers must include: Authorization: Bearer <token>
# control_auth = "your-secret-token-here"
# Values can reference environment variables using the $env:VAR syntax:
# control_auth = "$env:PF_CONTROL_AUTH"
# Optional: Load a .env file before resolving $env: references.
# false or absent → disabled (default)
# true → load .env from the current working directory (silently ignored if absent)
# "./path/.env" → load from the given path (error if file does not exist)
# dotenv = true
# dotenv = "./.env.local"
# Optional: HTTPS port — when set, cert_path and key_path are required.
# https_port = 443
# cert_path = "/etc/ssl/certs/fullchain.pem"
# key_path = "/etc/ssl/private/privkey.pem"
# ── Server blocks ─────────────────────────────────────────────────────────────
#
# Each [server.NAME] block configures one reverse-proxy entry.
# bind_to controls where it is mounted in the Axum router:
#
# bind_to = "*" → fallback (catch-all), registered last
# bind_to = "/api" → nested under /api, registered before shorter prefixes
#
# Note: Router::nest strips the prefix before forwarding. If your upstream
# expects the full path, encode the prefix in proxy_url instead.
[]
# Mount point in the Axum router (default: "*")
= "*"
# The backend URL to proxy requests to
= "http://localhost:8080"
# Optional: Paths to include in caching (empty means include all)
# Supports wildcards: * can appear anywhere in the pattern
# Supports method prefixes: "GET /api/*", "POST /*/users", etc.
# Examples: "/api/*", "/*/users", "/public/*/assets", "GET *"
= ["/api/*", "/public/*", "GET /admin/stats"]
# Optional: Paths to exclude from caching (empty means exclude none)
# Supports wildcards: * can appear anywhere in the pattern
# Supports method prefixes: "POST /api/*", "PUT *", etc.
# Exclude patterns override include patterns
= ["/api/admin/*", "/api/*/private", "POST *", "PUT *", "DELETE *"]
# Optional: Enable WebSocket / protocol-upgrade support (default: true)
# Upgrade requests bypass the cache and establish a direct TCP tunnel to the backend.
# Only active in Dynamic mode or PreGenerate mode with pre_generate_fallthrough = true.
# Pure SSG servers always return 501 for upgrade requests.
= true
# Optional: Only allow GET requests, reject all others (default: false)
# forward_get_only = false
# Optional: Control which response types are cached (default: "all")
# Available values: "all", "none", "only_html", "no_images", "only_images", "only_assets"
# cache_strategy = "none"
# Optional: Control how cached responses are stored in memory (default: "brotli")
# Available values: "none", "brotli", "gzip", "deflate"
# compress_strategy = "brotli"
# Optional: Control where cached response bodies are stored (default: "memory")
# Available values: "memory", "filesystem"
# cache_storage_mode = "filesystem"
# Optional: Override the directory used for filesystem-backed cache bodies
# cache_directory = "./.phantom-frame-cache"
# ── Webhooks ──────────────────────────────────────────────────────────────────
#
# Each [[server.NAME.webhooks]] entry defines one webhook for that server.
# Webhooks fire on every request, BEFORE cache reads are attempted, so access
# control is enforced even for cached responses.
#
# type = "blocking" — phantom-frame POSTs request metadata to the URL and waits.
# 2xx response → request is allowed to continue.
# Non-2xx response → the webhook's status code is returned to
# the client and the request is not forwarded.
# Timeout or connection error → 503 is returned to the client.
#
# type = "notify" — phantom-frame fires a background POST and does not wait for
# a response. The request always proceeds immediately.
#
# The POST body contains: method, path, query, headers (no request body).
#
# [[server.default.webhooks]]
# url = "http://auth-service.internal/check"
# type = "blocking"
# timeout_ms = 3000 # optional, default 5000 ms
#
# [[server.default.webhooks]]
# url = "http://logger.internal/log"
# type = "notify"
# ── Example: multi-server config (SSG frontend + dynamic API backend) ─────────
#
# [server.frontend]
# bind_to = "*"
# proxy_url = "http://localhost:5173"
# proxy_mode = "pre_generate"
# pre_generate_paths = ["/", "/about", "/blog"]
# enable_websocket = false # SSG — no backend at request time
#
# Optional: spawn a command and wait for proxy_url's port to accept connections.
# phantom-frame polls the port every 500 ms (360 s timeout) before serving.
# On Windows, commands are run via `cmd /C` so pnpm.cmd / npm.cmd etc. work as-is.
# On Unix, commands are run via `sh -c`.
#
# Simple command:
# execute = "pnpm run dev"
# execute_dir = "./apps/client" # working directory (relative to where phantom-frame runs)
#
# Commands support && / || chaining — intermediate segments run to completion,
# the final segment becomes the long-running server process:
# execute = "pnpm install && pnpm run build && pnpm run start"
# execute = "pnpm run build || echo 'build failed' && pnpm run start"
#
# cd changes the working directory for subsequent segments in the chain:
# execute = "cd ./apps/client && pnpm install && pnpm run dev"
#
# Linux-style KEY=VALUE inline env prefixes are supported on all platforms:
# execute = "PORT=5173 NODE_ENV=production pnpm run start"
# execute = "cd ./apps/client && PORT=5173 pnpm run dev"
#
# [server.api]
# bind_to = "/api"
# proxy_url = "http://localhost:8080"
# proxy_mode = "dynamic"
# enable_websocket = true
# execute = "cargo build --release && cargo run --release"
# execute_dir = "./apps/server"