No description
  • Python 60.3%
  • JavaScript 21.9%
  • CSS 9.9%
  • HTML 7.9%
Find a file
Brian a3d8d53bfb Annotate validation warnings with their site block and line
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>
2026-07-07 21:26:59 -06:00
static Keep the validation status banner up until dismissed 2026-07-07 21:19:58 -06:00
templates Keep the validation status banner up until dismissed 2026-07-07 21:19:58 -06:00
tests Initial commit: CaddyGUI — Flask GUI for editing a Caddyfile 2026-07-07 20:53:20 -06:00
.gitignore Initial commit: CaddyGUI — Flask GUI for editing a Caddyfile 2026-07-07 20:53:20 -06:00
app.py Annotate validation warnings with their site block and line 2026-07-07 21:26:59 -06:00
caddyfile.py Initial commit: CaddyGUI — Flask GUI for editing a Caddyfile 2026-07-07 20:53:20 -06:00
directives.py Initial commit: CaddyGUI — Flask GUI for editing a Caddyfile 2026-07-07 20:53:20 -06:00
README.md Format output like caddy fmt on save (fixes "not formatted" warning) 2026-07-07 21:14:39 -06:00
requirements.txt Serve via waitress instead of Flask's dev server 2026-07-07 20:57:06 -06:00

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 }, common header_up, streaming flush_interval -1, load balancing, health checks), a header builder with security/CORS/HSTS presets, and the full transport http option 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 fmt formatting 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 the caddy binary when available (byte-for-byte identical to caddy 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 an EnvironmentFile, prefer setting the reload command to systemctl reload caddy — it reloads the service's own /etc/caddy/Caddyfile using the service's environment (so the secrets are present). Save to /etc/caddy/Caddyfile and 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.1 by default. Do not expose it to untrusted networks.
  • /etc/caddy/Caddyfile is 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 subprocess with argument lists (never a shell).
  • caddy reload --config {path} reloads the file you edited. systemctl reload caddy reloads 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).