- Python 60.3%
- JavaScript 21.9%
- CSS 9.9%
- HTML 7.9%
Caddy's CLI warnings carry no line/file field, only a message. Since warnings are emitted in file order and each names its directive, correlate every warning to its occurrence in the validated config with a forward cursor and prefix it with [site-address:line] — so a directive repeated across many sites (e.g. header_up X-Forwarded-For) is attributed to the correct block. - _line_sections(): map each config line to its enclosing top-level block - _warning_token(): extract a searchable directive from "Unnecessary <x>: ..." - _locate_messages(): forward-cursor correlation, handles duplicates - _format_caddy_log() now takes the validated text and annotates warn/error lines Line numbers reference the formatted output (visible via Preview). Verified against caddy v2.8.4 and the fixture's exact 7-warning stream; all suites pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| static | ||
| templates | ||
| tests | ||
| .gitignore | ||
| app.py | ||
| caddyfile.py | ||
| directives.py | ||
| README.md | ||
| requirements.txt | ||
CaddyGUI
A local Flask web GUI for editing a Caddyfile.
Load an existing Caddyfile, edit its site entries with friendly helpers for the
common directives (reverse_proxy, transport / tls_insecure_skip_verify,
header, …), browse the full documented directive catalog, back up, save, and
reload the Caddy service.
Features
- Set the Caddyfile path and load it into the GUI (remembered between runs).
- Entry management — create new site entries, copy an existing one, delete.
- Quick-apply common settings — a reverse-proxy builder (upstream + toggles for
transport http { tls_insecure_skip_verify }, commonheader_up, streamingflush_interval -1, load balancing, health checks), a header builder with security/CORS/HSTS presets, and the fulltransport httpoption set. - Every documented directive — a searchable catalog (name, description, docs link, insertable snippet) built from https://caddyserver.com/docs/caddyfile/directives, plus request matchers and global options.
caddy fmtformatting on save — output is normalized to Caddy's canonical style (tab indentation, no trailing whitespace) so saved files never trigger the "Caddyfile input is not formatted" warning. Uses thecaddybinary when available (byte-for-byte identical tocaddy fmt), with a tab-based fallback otherwise. Toggle it off in Settings (format_on_save) to keep your file's original formatting byte-for-byte.- Faithful parsing — the parser round-trips any Caddyfile byte-exactly (placeholders, nested blocks, matchers, comments); with formatting off, an unedited save reproduces the file exactly and only edited entries change.
- Safe save path — format → validate → back up → write to a temp file in the same directory → atomic replace. A config that fails validation never lands on disk (unless you explicitly force it).
- Backup the current Caddyfile on demand (timestamped copies).
- Reload Caddy via a configurable command.
Install & run
python3 -m venv .venv
source .venv/bin/activate # fish: source .venv/bin/activate.fish
pip install -r requirements.txt
python app.py
Then open http://127.0.0.1:5001. It serves through waitress, a production WSGI server (no Flask "development server" warning).
Environment overrides: CADDYGUI_HOST (default 127.0.0.1), CADDYGUI_PORT
(default 5001), CADDYGUI_DEBUG (set to run Flask's dev server with the
reloader/debugger instead of waitress).
Run it as a single process. The parsed Caddyfile is held in module-level in-memory state, so CaddyGUI must not run under a multi-process server (e.g.
gunicorn -w N) — each worker would get its own unshared copy. waitress is single-process/multi-threaded, which is exactly what this needs.
Configuration (gear icon)
| Setting | Default | Notes |
|---|---|---|
| Caddyfile path | /etc/caddy/Caddyfile |
The file to edit. |
| Backup directory | (blank) | Blank = alongside the Caddyfile. |
| Reload command | caddy reload --config {path} |
{path} = the edited file. |
| Validate command | caddy adapt --adapter caddyfile --config {path} |
Used on save. |
| Format on save | on |
Normalize output with caddy fmt style (tab indentation). Turn off to preserve your file's exact formatting. |
Validate vs. adapt (and secrets)
CaddyGUI validates with caddy adapt, which checks Caddyfile
syntax/structure by adapting it to JSON. It deliberately does not use
caddy validate, because validate additionally provisions every module —
including TLS/ACME/DNS providers — which reads runtime secrets such as
{env.CLOUDFLARE_API_TOKEN}. Those env vars are normally injected into Caddy by
your service manager (e.g. a systemd EnvironmentFile) and are not present
in CaddyGUI's process, so caddy validate reports false failures like
API token '' appears invalid on a perfectly good config. adapt needs no
secrets and no network.
You can switch the command back to caddy validate ... in Settings if you want
full provisioning checks — but then you must ensure the required secrets are
present in CaddyGUI's environment.
The same secret consideration applies to reload.
caddy reload --config {path}resolves{env.*}placeholders in this process's environment. If your Caddy runs under systemd with anEnvironmentFile, prefer setting the reload command tosystemctl reload caddy— it reloads the service's own/etc/caddy/Caddyfileusing the service's environment (so the secrets are present). Save to/etc/caddy/Caddyfileand the edited file and the reloaded file are the same.
If the caddy binary is not on PATH, validation falls back to a
comment/quote-aware brace-balance check.
Notes on privileges & security
- This is a localhost admin tool. It binds to
127.0.0.1by default. Do not expose it to untrusted networks. /etc/caddy/Caddyfileis typically root-owned and reloading the service needs privileges. Run CaddyGUI with an account that has write access to the target file and rights to reload Caddy, or it will hit permission errors (which it reports rather than silently succeeding).- The reload/validate commands are configurable and are executed on the host —
that is arbitrary command execution by design, appropriate for a trusted local
admin tool. Commands run via
subprocesswith argument lists (never a shell). caddy reload --config {path}reloads the file you edited.systemctl reload caddyreloads the service's own configured Caddyfile — if that path differs from the one you edited, nothing you changed will take effect. The reload result surfaces which file it applied to.
Round-trip / parser tests
python tests/test_roundtrip.py
The gate test parses a real ~40-site Caddyfile and asserts serialization is
byte-identical, then exercises edits, add/copy/delete, and edge cases
(placeholders, comment/quoted braces, nested transport blocks, snippets).