panel/README.md
agent 2d3b30d078 panel: redesign web UI and make syncing responsive
Web UI (panel/frontend/index.html) rewritten:
- Cards update in place from a single /status poll instead of being rebuilt
  on every action, so open tabs, unsaved compose/route edits, logs and the
  file browser position survive refreshes. Polling speeds up while an
  operation runs and pauses in background tabs; a header indicator shows
  when the panel last synced and detects an expired Authelia session.
- New-app dialog (starter / compose / git), suggested port and domain,
  proper confirm dialogs (the old "OK = keep volumes" remove prompt is gone),
  toasts, an activity drawer with operation output, overflow menu, search,
  status filters, keyboard shortcuts, deep links, dark mode and mobile layout.
- Tabs: overview (containers + routes), compose editor (dirty tracking,
  Ctrl+S), logs with follow, validated routes editor, file browser with
  drag-and-drop upload, backups, and a git source tab (deployed commit,
  check for updates, sync & deploy).

API (panel/panel-api.py):
- ThreadingHTTPServer so a long deploy no longer blocks every other request.
- Per-app operation lock; concurrent writes to a busy app return 409.
- GET /status: all apps, routes and container status in one request
  (statuses gathered in parallel); status reports running/partial/stopped.
- Git sync is fetch + hard reset instead of pull-or-reclone, keeps the stored
  token, reports before/after commits; GET /apps/<name>/repo[?fetch=1].
- Any http(s) git host (e.g. Forgejo), default branch detection, git
  timeouts, no credential prompts, tokens redacted from errors, and manifest
  values validated before being written into the bash-sourced manifest.

panelctl:
- flock around routes.caddy rewrites (util-linux added to the service path).
- deploy returns compose output so failures are visible in the UI.
- inspect-volumes no longer fails for apps without named podman volumes,
  which broke the file browser.

Docs: README/API.md updated; fixed outdated panelctl init examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbWSNkXxZhYf7eqHTyx3Bf
2026-09-26 22:17:07 +00:00

4.3 KiB

panelctl quickstart

Minimal container management panel for rootless Podman + Caddy.

Base directory

/var/lib/containers

Generated structure

/var/lib/containers/
├── stacks/<app>/compose.yaml    # Compose file per app
├── volumes/<app>/data           # Persistent volumes
├── routes/routes.caddy          # Single aggregate Caddy routes file
├── backups/<app>-<timestamp>.zip # Volume backups
└── state/apps/<app>.env         # App manifest

All app routes are written to a single routes/routes.caddy file that Caddy imports.

Quick workflow

# Routes are "domain|upstream[|path]" entries, comma-separated.

# Create a new app (single route, protected by Authelia)
panelctl init whoami "whoami.srazka.com|127.0.0.1:18080" true

# Create with multiple routes (different ports, optional path)
panelctl init myapp "app.srazka.com|127.0.0.1:18081,api.srazka.com|127.0.0.1:18082|/api/*" true

# Create with wildcard domain (requires DNS challenge in Caddy)
panelctl init wild "*.srazka.com|127.0.0.1:18083" false

# Change routes later (Caddy reloads automatically)
panelctl set-routes whoami "whoami.srazka.com|127.0.0.1:18080,who.srazka.com|127.0.0.1:18080"

# Deploy (compose up + caddy reload)
panelctl deploy whoami

# Check container status
panelctl status whoami

# View logs
panelctl logs whoami --tail 50

# Restart containers
panelctl restart whoami

# Stop containers
panelctl stop whoami

# Validate compose file
panelctl validate-compose whoami

# Backup volumes to zip
panelctl backup whoami

# List backups
panelctl list-backups whoami

# Restore from backup
panelctl restore whoami whoami-20260101-120000.zip

# List all apps
panelctl list

# Show app manifest
panelctl show whoami

# Remove app (keeps volumes)
panelctl remove whoami --keep-volumes

# Remove app and all data
panelctl remove whoami

Notes

  • The default compose file uses traefik/whoami for smoke testing — edit before production use.
  • App names must be lowercase slugs ([a-z0-9-]).
  • Wildcard domains (*.example.com) require DNS challenge in Caddy (provider-specific).
  • Backups stop containers for consistency, then restart if they were running.
  • If deploy reports XDG_RUNTIME_DIR missing, enable lingering:
    sudo loginctl enable-linger reudy
    

Web UI & API

  • Nix runs panel-api as a systemd service on 127.0.0.1:9911.
  • Caddy proxies https://panel.srazka.com → panel-api with Authelia forward_auth.
  • Open https://panel.srazka.com for the web UI.
  • API docs: API.md

Web UI features

  • Live status: one /status poll every few seconds (faster while something is running, paused when the tab is hidden) updates cards in place, so open tabs, unsaved edits and scroll positions are never lost. The header shows when the panel last synced and warns when the Authelia session has expired.
  • Per-app status (running / partial / stopped), container list, and a busy indicator that is shared between browsers while an operation runs.
  • New-app dialog: starter container, pasted compose file or git repository; suggests the next free port and a domain based on the app name.
  • Compose editor with unsaved-changes tracking, Ctrl+S, save & deploy, validate.
  • Logs with follow mode, routes editor with validation, file browser with drag-and-drop upload, backups with restore (and optional redeploy).
  • Git source tab: deployed commit, "check for updates", and sync & deploy.
  • Activity drawer with the output of every operation (e.g. why a deploy failed).
  • Keyboard: / search, N new app, Esc closes menus. Deep links like #/whoami/logs open an app on a specific tab.

Git-backed apps

Apps created from a repository (GitHub, Forgejo/Gitea or any https git host) are cloned to stacks/<app>/repo. Sync fetches the configured branch and hard-resets the checkout to it before redeploying, so the repository is the source of truth: compose edits made in the panel are discarded on the next sync (the UI warns about this). An access token for a private repository is stored in the clone's .git/config; use a read-only token.

Concurrency

panel-api handles requests concurrently, so a long deploy never blocks status or logs. Mutating operations are serialised per app — a second operation on a busy app gets HTTP 409 — and panelctl takes a flock on the shared routes file while rewriting it.