Files
vpsmgr/docs/configuration.md
T
bluebluesodaandCommandCodeBot 1663563020 docs: reflect the Incus runtime, vps.* services and privilege drop
- architecture.md: Incus components, unprivileged panel section
  (incus-admin group + sudoers whitelist), vps.* unit names, atomic
  user creation transaction.
- configuration.md: incus.* config keys, /var/lib/incus socket, vps
  service restart.
- development.md: Incus dev-loop instructions, updated layout.
- README(.zh-CN).md: Debian 13/Ubuntu requirements, Incus mention.
- AGENTS.md: Incus invariants (socket, websocket exec, EnsureImage
  before Launch, sudoers whitelist, vps.service naming).

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 03:33:37 +00:00

9.8 KiB
Raw Blame History

Configuration

The config file is at /etc/vpsmgr/config.yaml (auto-generated at install, root-only read/write). The VPSMGR_CONFIG env var points the vps binary at another path; the shell install/uninstall scripts always use /etc/vpsmgr/config.yaml.

Editing config.yaml by hand is discouraged. The sanctioned interface is vps config list / vps config set / vps config help, which validate every change and refuse immutable fields. A raw YAML edit still works but is not checked — get the rules from vps config help.

Field matrix (kind + how a change applies)

Every field is classified exactly one way — there is no ambiguity about who may change it or when the change takes effect. vps config help prints the same table; vps config list shows the live values with this annotation.

Key Kind Change applies Notes
panel.listen operator systemctl restart vps a FRESH install picks a random free port in 2000-9999
panel.cert operator restart panel TLS certificate path
panel.key operator restart panel TLS private key path
panel.db operator restart panel SQLite database path
panel.public_ip operator re-run vps install NIC IPv4 used by firewall/routing; cert is regenerated
panel.display_ip operator restart panel public IPv4 shown to users (panel URL / SSH hints); empty = fall back to public_ip
panel.session_days operator restart panel login session lifetime (days)
panel.url_path fixed at install — secret prefix of the user panel; settable only while empty (re-enable)
panel.admin_url_path operator restart panel secret prefix of the admin panel; an empty value disables the admin panel (shown as disabled)
panel.admin_pass_hash managed elsewhere — bcrypt hash of the admin password; stored in the DB, set via vps admin-passwd / web UI
net.subnet fixed at install — container subnet 10.<n>.0.0/24; changing breaks existing containers
net.gateway fixed at install — bridge gateway (derived from subnet)
net.v4_forward runtime toggle applied immediately false = IPv6-only containers (no SSH/port DNAT, traefik disabled, NAT4 outbound kept)
net.ext_if operator re-run vps install external NIC (auto-detected from default route)
net.ipv6_subnet operator re-run vps install global IPv6 prefix for pass-through, e.g. 2602:fada:6::/64; empty = disabled (does not remove IPv6 state already applied, see note below)
incus.image operator next vps add / reinstall container image alias
incus.image_fallback operator next vps add / reinstall fallback remote image
incus.pool fixed at install — storage pool
incus.bridge fixed at install — managed bridge
incus.socket operator restart panel Incus daemon Unix socket
installed_version auto (panel-managed) — binary version that installed/adopted the config
uninstalled_version auto (panel-managed) — binary version a non-purging uninstall removed

How "fixed at install" is enforced

Fields marked fixed at install (net.subnet, net.gateway, incus.pool, incus.bridge, panel.url_path) are snapshotted into the DB settings table on the first vps install. Every later vps install and vps serve compares the live config against that snapshot and refuses to run if any of them drifted — vps config set also refuses them up front. The user panel path (panel.url_path) is immutable on purpose: moving it strands every user who bookmarked it. The admin path is NOT immutable — it can be rotated and emptied to disable the admin panel.

Auto-written metadata

installed_version / uninstalled_version are written by vps install and a non-purging uninstall.sh (vps note-version). Both survive a reinstall (/etc/vpsmgr is kept) so a future release that makes breaking changes can detect which version a config/db came from and migrate or warn.

Managed files

Files generated by the panel carry a Managed by vpsmgr — generated, do not edit by hand banner and are overwritten on the next write:

File Written by
/etc/vpsmgr/nftables.conf vps install
/etc/vpsmgr/nftables.d/user-<name>.nft vps add / quota / firewall updates
/etc/traefik/traefik.yaml install (from configs/traefik.yaml); only written when absent — existing installs keep theirs
/etc/traefik/dynamic/<domain>.yaml domain add/update/delete
/etc/ndppd.conf IPv6 pass-through updates
/etc/sysctl.d/99-vpsmgr.conf vps install
systemd units (vps/vps-nft/vps-ipv6, traefik) vps install

Example

panel:
  listen: ":5231"              # panel listen address — a FRESH install picks a random free port in 2000-9999
  cert: /etc/vpsmgr/panel.crt  # HTTPS certificate
  key: /etc/vpsmgr/panel.key   # private key
  db: /etc/vpsmgr/vpsmgr.db    # SQLite database
  public_ip: AUTO              # NIC IPv4 used by the firewall / routing; on NAT-ing clouds (AWS/Alibaba) this is a private address
  display_ip: AUTO             # public IPv4 for DISPLAY ONLY (panel URL, SSH hints); auto-fetched from ipv4.ip.sb when public_ip is private; empty = fall back to public_ip
  session_days: 3              # login session lifetime (days)
  url_path: AUTO               # random secret path, the only panel entrance; do not change after first install
  admin_url_path: AUTO         # random secret path of the admin panel; do not change after first install
  admin_pass_hash: AUTO        # bcrypt hash of the admin password — stored in the DB since v0.3.x, not in this file

net:
  subnet: "10.115.0.0/24"      # container subnet 10.<n>.0.0/24 — only the second octet is settable, at install
  gateway: "10.115.0.1"
  v4_forward: true             # IPv4 inbound policy (false = IPv6-only containers)
  ext_if: AUTO                 # external NIC, auto-detected from the default route
  ipv6_subnet: ""              # optional: global prefix for IPv6 pass-through, e.g. "2602:fada:6::/64"
                               # (/64 or shorter, incl. provider /80 slices like
                               # "2406:da14:1dd2:a807:753a::/80"); empty = disabled (default),
                               # but does not remove IPv6 state already applied

incus:
  image: "vpsmgr/debian-sshd"
  image_fallback: "images:debian/13"
  pool: vpsmgr
  bridge: lxdbr0
  socket: "/var/lib/incus/unix.socket"   # Incus daemon Unix socket (REST API)

Port scheme (fixed)

The port layout is fixed at install and immutable — there is no config knob:

  • Panel port: random free port in 2000-9999, chosen on a fresh install and stored in panel.listen. Change it in the config only if you know why.
  • SSH port: each container gets one random port in 30000-31999 (TCP, DNAT to container :22). Shown as ssh -p <port>.
  • User ports: each container owns a whole-hundred block of 100 ports in 10000-29999, assigned deterministically (10000+(idx-1)*100 .. +99), DNAT to the container (TCP and UDP). Displayed compactly as e.g. 107xx.
  • Capacity: at most 200 containers (200 blocks × 100 ports = 20000).

IPv4 inbound policy (v4_forward)

net.v4_forward controls whether containers receive shared IPv4 inbound. Always enabled by default — the installer does not ask (with IPv6 off, IPv4 forwarding is mandatory, as containers would otherwise be unreachable).

  • true (default): containers get the random SSH port + user port block (DNAT), and the domain proxy (traefik) is available.
  • false: containers are IPv6-only. No SSH DNAT, no port-block DNAT, and traefik is stopped (domains are kept but not served; adding a domain is rejected until re-enabled). Containers still reach IPv4 outbound via the NAT4 masquerade.

Toggle at runtime with vps config set net.v4_forward true|false — the rules are refreshed and traefik started/stopped immediately (its boot autostart is disabled along with it, so it cannot come back on reboot). The SSH/user ports stay recorded in the DB, so turning it back on restores everything. The user panel hides IPv4 inbound info and shows "v4 SSH unavailable" while off, and domain-add is blocked while off — the add form is hidden and the handler rejects it (the panel reads the toggle live from the DB, no restart needed).

Port 25 (SMTP) is always blocked

The host nftables ruleset drops port 25 for all forwarded traffic, both directions, TCP and UDP (containers ⇄ internet, container ⇄ container). This is a permanent anti-spam measure: there is no user toggle, and the rule only goes away when the panel is uninstalled.

Field notes

  • panel.listen, panel.public_ip, net.ext_if only affect display/on how the panel listens.
  • url_path, net.subnet, incus.pool etc. are bound to existing data — do not change them after first install.
  • net.ipv6_subnet must be a global (non-ULA) IPv6 CIDR with an explicit prefix length — a bare address is rejected, never silently assumed /64. Valid range is /48../80 (see ipv6.md).
  • Clearing net.ipv6_subnet (empty) only stops vpsmgr from applying IPv6 on the next vps install; it does not remove IPv6 already in place (bridge address, ndppd, routes). Full IPv6 cleanup happens in uninstall.sh.
  • v0.3 upgrade gate: v0.3 makes breaking changes, so it refuses to adopt a config/db recorded as originating from an older release. vps install and vps serve both abort (and install.sh aborts early) when installed_version / uninstalled_version is missing or older than 0.3.0. A config with no recorded version is treated as too old — it predates the metadata fields. Existing 0.2.x installs must stay on v0.2.x until a migration path exists.