Trigger the pre-zpool cache/kernel cleanup earlier (free < 25 GiB). On
small VPS hosts the kernel comes from the provider template and stale
kernel rollback is not a concern, so the extra headroom is worth it.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The pre-zpool cleanup now triggers earlier (free < 20 GiB), leaving more
headroom for the pool loop file, Incus image store and the root
filesystem before a sparse pool is carved out.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The pre-zpool space reclaim kept the two newest kernels (running + one
fallback). Kernels are large (~200 MiB each), so drop to keeping ONLY
the running kernel — with two guards:
- If a NEWER kernel than the running one is already installed (upgrade
pending a reboot), keep that too: purging it would make the next
reboot boot an OLDER kernel than the one currently running.
- The linux-image-<arch> meta package (no version, holds no payload) is
excluded from the purge; deleting it would silently stop apt from
pulling future kernel upgrades.
Verified on this host: running 6.12.101 kept, 6.12.95 purged, meta
package untouched.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- Admin user table now shows the persistent lifecycle status (creating /
reinstalling / failed) next to the container state, so a crashed
Add/Reinstall is visible to the operator instead of looking like a
healthy user (follow-up to the P1-2/P1-3 lifecycle work).
- 40-panel.sh's prebuilt-download scratch dir only had explicit rm on the
success/known-failure paths; an exit between cp and chmod would leak
it. An EXIT trap now guarantees cleanup (rm -rf of a missing dir is a
harmless no-op on the existing explicit paths).
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Security review finding P2-9 + the extra requirement in the review doc:
- P2-9: fixed /tmp paths were used by install steps — a local attacker
could pre-create the file (symlink follow on cat/tee/curl -o) and
/tmp/vpsmgr-install.out carried the one-time admin password in a world
guessable location. 10-incus.sh preseed now uses mktemp; 30-traefik.sh
downloads into a private mktemp dir with an EXIT trap; 40-panel.sh
writes the install transcript to /etc/vpsmgr/.last-install.out (0600,
root-only) instead of /tmp, and install.sh reads it from there.
- Extra: before sizing a loop-file zpool, 10-incus.sh now reclaims space
when / has less than 15 GiB free: apt clean, apt list purge, autoremove,
journal vacuum (3d), /tmp + /var/tmp, stale kernels (keep 2 newest,
linux-image-* only), pip/go build caches — then re-checks free space and
refuses to build a pool that would leave < 3 GiB free worst-case.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Security review findings P2-6 + P2-7:
- The sudoers whitelist listed `systemctl enable traefik.service`, but
the code calls `enable --now traefik.service` (and `disable --now`),
and `is-active vps.service` vs the real `is-active --quiet`. sudo
matches argv exactly, so every one of those calls was rejected at
runtime: traefik could never be started by the panel and `vps config
set` could never restart the panel. The whitelist now mirrors the
actual invocations.
- Removed dead whitelist entries with no code call (start/stop/is-active
traefik.service, route replace, neigh add proxy, addr replace/del) —
narrower surface, nothing functional lost.
- ensureVPSUser/ensureSudoers swallowed every error and even skipped
installing the whitelist on failure, leaving a panel that looked
healthy but failed all privileged ops. Both now return errors and
abort `vps install`.
- scripts/00-check.sh now installs sudo (with visudo) as a hard
dependency.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- Ubuntu ships the ZFS kernel module PREBUILT inside the kernel package
(linux-modules, since 22.04), so installing zfsutils-linux alone is
enough — no DKMS compile, no linux-headers/build-essential. The
00-check package list is now distro-conditional: Debian gets the DKMS
toolchain, Ubuntu skips it entirely.
- Ubuntu minimal/cloud images often only have the 'main' component;
enable 'universe' (needed for ndppd on IPv6 pass-through and other
packages) idempotently on Ubuntu.
No code changes on the Debian path (still verified). On Ubuntu the
install loses the one-time ~2 min ZFS compile and kernel upgrades keep
working with zero rebuilds.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
zfsutils-linux normally enables zfs-import-cache/zfs-mount/zfs-zed/
zfs.target in its postinst, but a broken or interrupted install (or a
minimal image) can leave them disabled. Without them a reboot leaves
the ZFS module unloaded and the pool unimported, so Incus's storage
pool is gone and every container fails to start.
00-check.sh now explicitly enables the boot units after verifying the
module loads. Verified with a real host reboot: module auto-loads, the
vpsmgr pool imports ONLINE, and Incus + the panel come up on top.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Debian has no prebuilt ZFS kmod (unlike Ubuntu's linux-modules or
RHEL's zfsonlinux kmod), so zfs-dkms compiles against the running
kernel. Using linux-headers-$(uname -r) pinned the compile to ONE
kernel: after a kernel upgrade the module was missing and needed a
manual dkms rebuild.
Install the version-less meta package linux-headers-amd64 instead: it
tracks the installed kernel, so /etc/kernel/postinst.d/dkms has the
matching headers at upgrade time and rebuilds the module automatically
before the next boot. The modprobe check stays: if the module is ever
absent for the running kernel (e.g. headers were missing during the
upgrade), 00-check rebuilds it on the spot instead of failing later at
pool creation.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- Drop the dir backend entirely: quotas, snapshots and clone-on-create
need ZFS, and dir silently breaks disk limits. 10-incus.sh now
refuses a non-ZFS pool (fresh or adopted) instead of silently
degrading, and pool creation failures are fatal.
- Incus does NOT bundle the ZFS userspace tools, so 00-check.sh installs
zfsutils-linux plus what Debian needs to build it: DKMS compiles the
ZFS kernel modules against the running kernel, so linux-headers-$(uname
-r) and build-essential (gcc/make) are required on Debian (Ubuntu
ships prebuilt modules). If the module is missing for the current
kernel, 00-check builds it with dkms and verifies `modprobe zfs`.
- Harden other Debian-minimal gaps: ca-certificates (every curl HTTPS
call fails without it), python3 (prefix/subnet validation), tar +
xz-utils (traefik tarball). 30-traefik.sh re-ensures curl/tar/
ca-certificates when run standalone.
Verified end-to-end on Debian 13: dir pool rejected, ZFS loop-file pool
created, container boot, ZFS disk quota applied (5G), full test suite
passes.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The previous RHEL approach poked kernel sysctls and `ip -6` commands
with wait/retry loops, which was guesswork: convergence depended on RA
timing, DAD completion and the kernel picking the right source address,
and it kept failing intermittently in e2e testing.
RHEL's network stack is NetworkManager, so the deterministic /128 and
the bridge gateway are now declared as a STATIC connection profile:
nmcli con mod <eth0> ipv6.method manual \
ipv6.addresses <det-addr>/128 ipv6.gateway fe80::1
ipv6.method=manual makes NM own the whole IPv6 stack: it sets
accept_ra=0 itself (no kernel RA interference, no SLAAC/dynamic
addresses outside the routed /112), applies the address and default
route atomically, and re-applies the profile on every boot. No daemons,
no waiting, no retries — the profile IS the final state. The
vpsmgr-ipv6 helper unit and the baked sysctl/helper/service are removed
from the image entirely.
Verified: reinstall to Alma 9 and fresh Debian->RHEL reinstall both
converge immediately (ipv6.method manual, accept_ra 0, single /128, no
dynamic addresses, egress + interop OK, stable over time).
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Strict end-to-end testing against a real Incus 7 host with IPv6
pass-through enabled surfaced several issues, all fixed here:
- setup: default profile must carry root+eth0 devices even when Incus
was already initialized (preseed may leave it empty); profile devices
are now ensured on every install (scripts/10-incus.sh).
- IPv6 bridge route: Incus 7.0 does not auto-create the bridge's prefix
route when the external interface already has one, which broke the
per-container /112 routes ("no route to host"). SetupIPv6Bridge now
adds it explicitly, plus a fixed fe80::1 link-local gateway that the
dnsmasq RA advertises to containers (ipv6_filtering only permits NDP
to that address).
- RHEL IPv6: the helper now fully staticizes IPv6 — learn the gateway
from RA, disable RA processing (accept_ra=0), drop SLAAC/dynamic
addresses (they fall outside the routed /112 and get filtered), pin
the deterministic /128 + a static default route with a preferred
source, and wait for DAD so the route add cannot fail on a tentative
source. The unit retries (Restart=on-failure) until converged.
- provisioning race: wait for systemd to be ready inside the container
before hostnamectl/systemctl (4/10 adds failed with "Failed to
connect to system scope bus"); same wait in the IPv6 script.
- traefik permissions: the unprivileged panel could not write
/etc/traefik/dynamic (parent dir is root:traefik 750). vps and
traefik users are now cross-linked into each other's groups and the
dynamic dir is owned by vps (scripts/30-traefik.sh, ensureVPSUser).
- ndppd.conf: pre-created and chowned to vps so the panel can render it
(reinstall failed with "permission denied").
- sudoers: allow `ip -6 addr add/del` for the bridge gateway setup.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The Incus line is a new project, not an upgrade path — remove every
piece of cross-version compatibility:
- version gate: ver.Blocked/MinAdoptable/SemVerLT, blockUnadoptable,
cmdNoteVersion, the installed_version/uninstalled_version config
fields and registry entries, install.sh old-binary detection and the
uninstall note-version call.
- DB migrations for pre-0.3 schemas: migrateAuditTargetDrop,
migrateUserColumns, migrateDomainColumns, migrateCPU and their tests.
- legacy admin-pass migration from config.yaml into the DB.
- duplicate static sudoers file (configs/sudoers.d/vps) — the Go
ensureSudoers embedded copy is the only source.
- docs swept of version-gate and 0.2.x/0.3.x language.
Also rename the bridge lxdbr0 -> incusbr0 across code, scripts, config
and docs, and adapt NIC isolation to Incus 7.0 validation: ipv6_filtering
is only applied when the eth0 device actually carries an IPv6 address or
route (Incus rejects it on a v6-less device, unlike LXD).
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- 10-incus.sh replaces 10-lxd.sh: adds the Zabbly incus lts-7.0 apt
repo (key fingerprint verified), installs the incus package, creates
the vpsmgr pool (ZFS/spare/loop-file, dir fallback) and lxdbr0 via
`incus admin init --preseed`, and keeps dns.mode=none.
- 00-check.sh targets Debian 12/13 + Ubuntu 22.04/24.04/26.04, drops
snapd, checks incus instead of the lxd snap.
- 50/60-image scripts use `incus` (was `lxc`).
- Service naming unified to vps.*: vps.service (panel),
vps-nft.service, vps-ipv6.service. `systemctl restart vps` is now the
one command operators need. unit After= incus.service.
- uninstall.sh stops vps.* units and removes the Incus package on
--purge.
- sudoers whitelist for the unprivileged panel (configs/sudoers.d/vps).
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The panel now talks to Incus over its Unix socket REST API
(/var/lib/incus/unix.socket, group incus-admin) exclusively:
- internal/lx: exec moved from the `lxc` CLI to the API websocket
transport (Exec/ExecSH/RunInitScript); EnsureImage pulls a
remote-qualified fallback (images:debian/13) before Launch since the
API cannot auto-fetch it inside the create call; per-container df
probe in mgr/batch.go uses the same API path.
- cfg: config key renamed lxd.* -> incus.* (image, image_fallback,
pool, bridge, socket); DefaultSocket is the Debian-package path.
- mgr: container disk probe and image handling updated; comment sweep
from LXD to Incus.
- deps: add gorilla/websocket for the exec transport.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>