No description
  • Go 63.6%
  • JavaScript 16%
  • Shell 9.7%
  • CSS 8.4%
  • HTML 2.1%
  • Other 0.2%
Find a file
Geert Rademakers 2ab50aa7d2 docs: warn that upgrading from pre-v0.3 needs a config edit first
v0.3.0 dropped `reload:` and unknown keys are fatal, so updating the binary on a
v0.2.x install restarts a service that will not come back up. The docs said
"just run ctconf -update", which is now a trap.
2026-09-06 00:28:38 +02:00
.forgejo/workflows docs: stop documenting v0.1.0 as the release to cut 2026-09-06 00:27:08 +02:00
cmd/ctconf feat: configurable action buttons replace the reload key 2026-09-06 00:21:39 +02:00
deploy ctconf v0.1: config editor & reloader (single static Go binary) 2026-08-25 16:41:37 +02:00
docs docs: warn that upgrading from pre-v0.3 needs a config edit first 2026-09-06 00:28:38 +02:00
internal feat: configurable action buttons replace the reload key 2026-09-06 00:21:39 +02:00
scripts feat(scripts): ship default actions, smoke-test /api/run 2026-09-06 00:22:23 +02:00
web feat(web): render action buttons from config 2026-09-06 00:22:23 +02:00
.gitignore ctconf v0.1: config editor & reloader (single static Go binary) 2026-08-25 16:41:37 +02:00
AGENTS.md docs: stop documenting v0.1.0 as the release to cut 2026-09-06 00:27:08 +02:00
go.mod ctconf v0.1: config editor & reloader (single static Go binary) 2026-08-25 16:41:37 +02:00
go.sum ctconf v0.1: config editor & reloader (single static Go binary) 2026-08-25 16:41:37 +02:00
Makefile ctconf v0.1: config editor & reloader (single static Go binary) 2026-08-25 16:41:37 +02:00
README.md docs: warn that upgrading from pre-v0.3 needs a config edit first 2026-09-06 00:28:38 +02:00
SPEC.md docs: document the actions model 2026-09-06 00:23:11 +02:00

ctconf

Edit config files inside your LXC from a phone and apply them with a button — without SSH, nano, and prayer.

ctconf is a single-binary web tool you install inside an existing Proxmox LXC (or any systemd Linux). You register the text files it manages (a Caddyfile, a filebrowser config, anything), edit them in a real code editor in your browser, and press buttons you defined in YAMLcaddy reload, a reset, a reboot-safe restart, ctconf -update — with the output streamed straight back to you.

Why

Editing /etc/caddy/Caddyfile over SSH on a phone is counter-intuitive and one typo away from a broken web server. oliveTin runs commands but has no first-class "edit this file, then apply it" workflow. ctconf is that workflow: file-centric, with lean safety (atomic writes, version history, restore) and explicit buttons instead of magic.

⚠️ Security — read this first

ctconf has no authentication. Anyone who can reach its port can edit the registered files and run their configured commands as root. That is remote code execution by design, and it is only acceptable on a trusted LAN. If your LAN is not trusted, put ctconf behind something that authenticates (reverse proxy with auth, Tailscale, VPN) before exposing it.

Install

Setting up from scratch? docs/SETUP.md walks through testing on a scratch LXC (creation, manual systemd setup), the full config reference, day-2 operations and troubleshooting.

SSH into the target container and run:

wget -qO- https://git.geertrademakers.nl/master/ctconf/raw/branch/main/scripts/install.sh | bash

The installer (run as root inside the container):

  • drops the static binary at /usr/local/bin/ctconf,
  • writes a ctconf.service systemd unit and starts it,
  • creates /etc/ctconf/config.yaml if absent — registering ctconf's own config first (so you can add the rest of your files from the browser instead of over SSH), plus /etc/caddy/Caddyfile when it exists, and two instance-wide buttons: ⬆️ Update ctconf and ♻️ Restart. It never overwrites an existing config.

Then open http://<container-ip>:8090 from your phone or laptop.

Updating

The binary updates itself — from the UI as ⬆️ Update ctconf in the header, or by hand:

ctconf -update          # fetch the newest release, swap the binary, restart
ctconf -version         # what am I running?

It downloads the release asset for this architecture, refuses anything that isn't a Linux binary, swaps it in atomically and restarts the service. If the running version is already the newest release it does nothing.

Re-running the installer does the same job and also refreshes the systemd unit; your config and version history survive either way.

Coming from before v0.3.0: that release removed the reload: config key, and unknown keys are fatal, so update the config (→ actions:) before updating the binary or the service will refuse to start. See docs/SETUP.md §B6.

Configuration

Buttons are config

Every button in ctconf comes from actions: in your YAML — ctconf has no idea what a "reload" is. Top-level actions are instance-wide (they render in the header); files[].actions render next to Save.

listen: 0.0.0.0:8090
data_dir: /var/lib/ctconf
versions_kept: 10

actions:
  - id: update                 # optional; defaults to a slug of the title
    title: Update ctconf       # required
    icon: "⬆️"            # optional emoji
    cmds: [ctconf -update]     # required, run in order via sh -c
    confirm: true              # false | true | "your own question"
    restarts_ctconf: true      # UI waits for the server and reloads itself
  - id: restart
    title: Restart
    icon: "♻️"
    cmds:
      - ctconf -check-config
      - systemctl --no-block restart ctconf   # --no-block: stream first, die after
    style: danger
    confirm: true
    restarts_ctconf: true

files:
  - name: Caddyfile
    path: /etc/caddy/Caddyfile
    language: caddyfile        # optional; inferred from extension
    actions:
      - id: reload
        title: Reload
        icon: "🔄"
        cmds:
          - caddy validate --config /etc/caddy/Caddyfile
          - caddy reload --config /etc/caddy/Caddyfile
        timeout: 120s          # optional, per command
      - id: reset
        title: Reset
        icon: "🧹"
        cmds: [systemctl restart caddy]
        confirm: true
        style: danger
  • name is the unique id used in the UI and API; id is unique per scope and is what run history is recorded under (give your actions one).
  • Commands run in order as root via sh -c; a non-zero exit stops the sequence and is reported as a failed run.
  • A file with no actions list is edit-only.
  • Anything invalid — duplicate ids, blank cmds, the old reload: key — is a startup error naming the file and action, never a silent skip. Check before applying: ctconf -check-config.

ctconf's own config registers an Apply config button that validates before restarting:

  - name: ctconf
    path: /etc/ctconf/config.yaml
    language: yaml
    actions:
      - id: apply
        title: Apply config
        icon: "♻️"
        cmds:
          - ctconf -check-config -config /etc/ctconf/config.yaml
          - systemctl --no-block restart ctconf
        style: danger
        restarts_ctconf: true
        confirm: "Apply this config to ctconf and restart it?"

Edit, Save, Apply config — a broken config fails the action with the error in the console and leaves the running server on its old config, rather than bricking the UI you would need to fix it. The config is never hot-reloaded, so the restart is what applies it.

How it behaves

  • Save writes atomically (temp + rename, preserving mode/owner) and snapshots the previous content into version history. It never runs anything.
  • Actions stream output live; each command has a timeout (default 120 s) and is killed with its whole process group. One run at a time per file — a second button on a busy file is refused — while different files can run at once.
  • Status badge per file: green up to date (disk matches what ctconf last wrote), red modified externally (someone edited over SSH; the editor shows current disk content so saving is an explicit overwrite, and Load from disk accepts it as the new baseline), grey missing, amber unsaved changes while your buffer differs.
  • Action dots answer the question the file status cannot: did this command run against these bytes? Green = yes, amber = the file changed since, red = last run failed, no dot = never run.
  • History drawer per file: restore any snapshot (with confirm). Restoring counts as a save, so the actions that ran against the old bytes go amber.

Development

See AGENTS.md for architecture and testing guidance; design decisions live in SPEC.md.

make build    # dist/ctconf
make test     # go test ./...
make smoke    # end-to-end: real server on 127.0.0.1:18090, curl assertions

Roadmap (not in v1)

  • Pre-save validation commands + auto-rollback on a failed action.
  • Save groups with ordered multi-file action sequences.
  • Optional auth / token gate for non-LAN use.