Deployments: deploy, restart and git sync now run in the background, one at
a time per app (a newer request replaces a queued one). Each run is recorded
in SQLite with its log, streamed to the UI while it runs, and can be
cancelled. Any earlier deployment can be deployed again, which rolls back to
its commit, or to its saved compose file for compose apps.
Auto deploy: POST /hooks/<app>, verified with the app's secret (Forgejo,
Gitea and GitHub HMAC signatures, or the secret as a token for CI). With a
Forgejo token stored, the panel adds the webhook to the repository itself.
The NixOS module routes /hooks/* past Authelia. Caddy matches the cleaned
path but forwards the original, so the panel refuses dot segments and only
accepts webhook deliveries from that route (tagged with X-Panel-Hook).
Domains: a route can point at a compose service's container port
("web:8080"). The panel picks a free 127.0.0.1 port and panelctl publishes
it through a generated .panel-ports.yaml override, so compose files need no
ports: section. Existing host:port upstreams keep working.
Logs stream live over server-sent events, with service and text filters.
A sampler keeps an hour of CPU and memory per container for the new
Monitoring tab. The Terminal tab opens `podman exec` in a container over a
WebSocket, using xterm.js bundled by the Nix package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbWSNkXxZhYf7eqHTyx3Bf
225 lines
8.5 KiB
Markdown
225 lines
8.5 KiB
Markdown
# panelctl quickstart
|
|
|
|
Minimal container management panel for rootless Podman + Caddy.
|
|
|
|
- **Deployments** run in the background with a live log, a history per app
|
|
(the last 50, with their output), cancel, redeploy and one-click rollback to
|
|
an earlier commit or compose file.
|
|
- **Auto deploy**: a push to the app's branch deploys it. The panel adds the
|
|
webhook to Forgejo itself; GitHub and CI use the shown URL and secret.
|
|
- **Domains** point at a service's container port (`web:8080`); the panel
|
|
picks a free local port and publishes it, so compose files need no `ports:`.
|
|
- **Live logs**, **CPU / memory graphs** per container and a **web terminal**
|
|
(`podman exec`) in the browser.
|
|
|
|
## Installing on NixOS
|
|
|
|
This repository is a flake that provides the panel as a package
|
|
(`packages.<system>.default`) and a NixOS module (`nixosModules.default`).
|
|
Add it to your system flake:
|
|
|
|
```nix
|
|
{
|
|
inputs.panel = {
|
|
url = "git+https://git.reudy.net/reudy-net/panel";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
|
|
outputs = { nixpkgs, panel, ... }: {
|
|
nixosConfigurations.vps = nixpkgs.lib.nixosSystem {
|
|
modules = [
|
|
panel.nixosModules.default
|
|
{
|
|
services.reudy-panel = {
|
|
enable = true;
|
|
domain = "panel.example.com"; # Caddy virtual host
|
|
autheliaAddress = "127.0.0.1:9091"; # forward_auth in front of it
|
|
};
|
|
}
|
|
];
|
|
};
|
|
};
|
|
}
|
|
```
|
|
|
|
The module sets up the `panel-api` service, the directories below, the
|
|
`panelroutes` group shared with Caddy, the Caddy import of the generated routes
|
|
and a path unit that reloads Caddy when they change. With Forgejo enabled on
|
|
the same host, its URLs are passed to the panel automatically
|
|
(`services.reudy-panel.forgejo.enable`). See `nix/module.nix` for all options.
|
|
|
|
To deploy a new panel version, bump the input and rebuild:
|
|
|
|
```bash
|
|
nix flake update panel
|
|
sudo nixos-rebuild switch --flake .#vps
|
|
```
|
|
|
|
Checks (package build and a module evaluation) run with `nix flake check`.
|
|
|
|
## Base directory
|
|
|
|
`/var/lib/containers`
|
|
|
|
## Generated structure
|
|
|
|
```
|
|
/var/lib/containers/
|
|
├── stacks/<app>/compose.yaml # Compose file per app
|
|
├── stacks/<app>/.panel-*.yaml # Generated overrides: published route ports, env vars
|
|
├── 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
|
|
└── state/panel/ # Panel state: panel.db (deployment history),
|
|
# deployments/<app>/<id>.log, webhook secrets, keys
|
|
```
|
|
|
|
All app routes are written to a single `routes/routes.caddy` file that Caddy imports.
|
|
|
|
## Quick workflow
|
|
|
|
```bash
|
|
# Routes are "domain|upstream[|path]" entries, comma-separated.
|
|
|
|
# Create a new app (single route, protected by Authelia)
|
|
panelctl init whoami "whoami.reudy.net|127.0.0.1:18080" true
|
|
|
|
# Create with multiple routes (different ports, optional path)
|
|
panelctl init myapp "app.reudy.net|127.0.0.1:18081,api.reudy.net|127.0.0.1:18082|/api/*" true
|
|
|
|
# Create with wildcard domain (requires DNS challenge in Caddy)
|
|
panelctl init wild "*.reudy.net|127.0.0.1:18083" false
|
|
|
|
# Change routes later (Caddy reloads automatically)
|
|
panelctl set-routes whoami "whoami.reudy.net|127.0.0.1:18080,who.reudy.net|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.reudy.net` → panel-api with Authelia forward_auth.
|
|
- Open `https://panel.reudy.net` for the web UI.
|
|
- API docs: [API.md](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 (Forgejo, GitHub or any git host, over https or
|
|
ssh) are cloned to `stacks/<app>/repo`.
|
|
|
|
**Forgejo.** `panel.nix` points the panel at the local Forgejo
|
|
(`PANEL_FORGEJO_URL`, `PANEL_FORGEJO_API_URL`, `PANEL_FORGEJO_SSH_URL`, taken
|
|
from `forgejo.nix`). In the panel's **Settings** you can connect a Forgejo
|
|
access token (read access to repositories and user). With it, the new-app
|
|
dialog lists your repositories and branches, and private ones are cloned over
|
|
https with the token. Without it, public repositories are listed and private
|
|
ones are cloned over ssh with the deploy key. Commit and compare links point at
|
|
Forgejo. The token is stored in `state/panel/forgejo-token` (mode 0600).
|
|
|
|
**SSH / deploy key.** The panel generates an ed25519 key pair in
|
|
`state/panel/ssh/` the first time it is needed. Its public half is shown in
|
|
Settings (and next to ssh URLs); add it as a read-only deploy key to a
|
|
repository — or to your Forgejo account for access to all repositories — to
|
|
clone `ssh://git@git.reudy.net:14922/owner/repo.git` style URLs.
|
|
|
|
**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.
|
|
|
|
### Environment variables
|
|
|
|
Each app can have environment variables (the **Environment** tab, or when
|
|
creating the app; `.env` text can be pasted in). They are stored in
|
|
`state/env/<app>.env` as `KEY=VALUE` lines (mode 0600) — outside the repository
|
|
and stack directory, so git syncs never touch them — and `panelctl` passes them
|
|
to every compose command:
|
|
|
|
- They are always available for `${VAR}` interpolation in the compose file.
|
|
The UI points out variables the compose file uses without a default that
|
|
aren't set.
|
|
- With **Pass to every container** (the default), `deploy`/`restart` also
|
|
generate `stacks/<app>/.panel-env.yaml`, a compose override that lists the
|
|
keys under every service's `environment:`. Compose reads the values from its
|
|
own environment, so they are never quoted into YAML, and they take
|
|
precedence over values set in the compose file.
|
|
|
|
Values must be single-line. Names that would change how podman/compose run
|
|
(`PATH`, `HOME`, `XDG_*`, `DOCKER_*`, `COMPOSE_*`, `PODMAN_*`, …) are rejected.
|
|
Changes apply on the next deploy. Backups do not include variables.
|
|
|
|
### 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.
|