The panel now fronts user domains with HAProxy 3.4 LTS (official performance packages) instead of Traefik: about 100 MB of RSS and 200 MB of disk saved for a feature set that never used more than Host routing on :80 and SNI passthrough on :443. - hpx: publish the proxy configuration as immutable GENERATIONS. Every domain mutation renders the whole set from the DB, validates it with the pinned `haproxy -c`, and only then repoints the `current` symlink, so a rejected candidate leaves the running proxy untouched and a stale domain can never survive into a later generation. Routing is carried by two map files (Host -> backend, SNI -> backend): per-domain frontend rules cannot work from a second file because HAProxy rejects duplicate section names since 3.3 and attaches bare directives to the section opened last. - mgr: republish and reload (SIGUSR2, i.e. `systemctl reload`) after every domain change, so a change takes effect without dropping established HTTP or TLS connections. Rollback semantics (DB first, then publish) are unchanged. - cfg,db: `net.traefik` is renamed to `net.haproxy` with identical values. A pre-rename config.yaml key and the legacy VPSMGR_TRAEFIK env var are still read (new name wins), and schema migration v18 moves the settings mirror from `traefik` to `haproxy`, keeping a newer value when both rows exist. - 30-haproxy.sh: install the pinned branch from the vendor repository and migrate a Traefik host in one run — install and validate HAProxy first, then remove Traefik's binary, unit, service account and dynamic rules (only its config file is kept, inert). No fallback keeps both proxies alive. - upgrade-haproxy.sh: follow the pinned branch to its newest patch, with a configuration check and a graceful reload; `install.sh --update` runs it. - tests: generation publish/rollback, map rendering, config-key and settings migrations, plus real-binary tests (skipped unless VPSMGR_TEST_HAPROXY_BIN is set) that run the generated configuration through `haproxy -c` and start it in a network namespace. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
4.9 KiB
AGENTS.md
Guide for AI coding agents working in this repo.
Project
vpsmgr — a lightweight LXC hosting panel. Debian 13/Ubuntu host + Incus containers, one Debian 13 container per user, managed via a web panel and a CLI (single Go binary). Hosting-panel concepts, not an ordinary web app: installers, containers, networking, storage.
Commands (always set CGO_ENABLED=0)
The project is pure Go — no cgo, never enable it. net (stdlib) pulls in
runtime/cgo whenever a C toolchain is present, which fails on machines with
broken gcc.
cd src
CGO_ENABLED=0 go build ./...
CGO_ENABLED=0 go test ./...
CGO_ENABLED=0 go vet ./...
Shell scripts: check syntax with bash -n scripts/*.sh. Note: install.sh
and 00-ip-ask.sh (the install-time network asks — IPv6 prefix + container
subnet octet) are sourced, so they must use return, not exit.
Incus (not LXD)
The container runtime is Incus 7 LTS, installed from the Zabbly package
repo (10-incus.sh) — never snap, never the lxc CLI.
- Daemon socket:
/var/lib/incus/unix.socket(groupincus-admin). The panel daemon (vps.service) runs as the unprivilegedvpsuser, which is a member ofincus-admin; all Incus access is via the REST API over that socket. internal/lxis the ONLY package that talks to Incus. Exec is done over the API websocket transport (Exec/ExecSH/RunInitScript) — there must be noincus/lxcCLI calls in the panel runtime path.- Storage pool
vpsmgr(ZFS by default, bridgeincusbr0).btrfsis supported as an alternative backend (on a btrfs host it becomes a native subvolume; elsewhere a loop file) —VPSMGR_STORAGE=btrfs. Adirbackend exists only as an explicitVPSMGR_STORAGE=diropt-in for test boxes — never a fallback; a failed pool (ZFS/btrfs) must abort, not downgrade. Container management is storage-driver-transparent in the Go panel; the only driver- aware code islx.EnsureZFSRemoveSnapshots, which is a no-op off ZFS. - A remote-qualified fallback image (
images:debian/13) must go throughlx.EnsureImagebeforelx.Launch— the API cannot auto-fetch it inside the create call (unlike the oldlxc launchCLI).
Privilege model
vps installruns as root (setup: user creation, units, kernel modules).vps serveruns as the unprivilegedvpsuser. Root commands are pinned in/etc/sudoers.d/vps(installed byensureSudoers, validated withvisudo -c); the panel escalates only those exact commands viainternal/su(sudo -n). Never add a newexec.Command("...")that needs root without also adding it to the whitelist.
Documentation
docs/README.md— indexdocs/architecture.md— system design (storage/network/security/bandwidth)docs/configuration.md—/etc/vpsmgr/config.yamlreferencedocs/ipv6.md— IPv6 pass-through design (large dev-branch feature)docs/development.md— build/test/release/conventions
Keep the top-level READMEs concise; technical detail belongs in docs/
(English).
Key invariants — do not break
uninstall.shwithout--purgemust keep/etc/vpsmgr(config/db) and/etc/haproxyso reinstall adopts them; only--purgedeletes them. A pre-HAProxy install's/etc/traefikis kept by the same rule (its config file is inert; the installer removes Traefik's binary/unit/account and the per-domain dynamic directory).- IPv6 bridge prefix is clamped to ≥ /64 (
bridgePrefixLen): Incus's dnsmasq only serves /64 networks. Container addresses always live in the first /64 of the configured prefix. - IPv6 prefix length is required in
ipv6_subnet— a bare address is rejected, never silently treated as /64. - Container hostnames are random (
vps-<8hex>) and never equal the username. install.sh --local-buildmust always rebuild (never reuse an installed binary) and warn about the branch.--updatere-downloads the latest prebuilt release over an existing binary and must keep it untouched on download failure (no fallback rebuild).- Image builds (
50-image.sh,60-rhel-image.sh,80-debian-dev-image.sh) must stay slim (apt/dnf/pacman clean) and delete the base image after publishing.60-rhel-image.sh,70-opensuse-image.sh,80-debian-dev-image.shand90-arch-image.share optional and must never be part ofinstall.sh(small boxes stay lean).90-arch-image.shis a rolling rebuild: it deletes the existing image and re-pulls upstream on every run. - Never add cgo or force C compilation.
- The panel service is
vps.service(restart viasystemctl restart vps). Do not reintroducevpsmgr-*.servicenames on the host. (The in-containervpsmgr-ipv6.servicehelper is a different thing — it lives inside container images.)
Conventions
- One commit per small bug/feature; short subject + bullet body.
- Run
git log --oneline -10for the current commit style before writing a message. - The test environment has ~2 GiB RAM; keep CI/local test workloads light.