Commit Graph
41 Commits
Author SHA1 Message Date
bluebluesoda 61fdf68686 feat(images): optional Debian 13 dev image builder
Add scripts/80-debian-dev-image.sh, modeled on 50-image.sh: pulls
images:debian/13, installs the universal sshd/tooling set plus the dev
toolchain (python3 + pip + venv, git/sqlite3/ripgrep/jq/gh/sshpass, all
--no-install-recommends), Go 1.26.7 (arch-aware, sha256 from the dl JSON
API), nvm v0.40.7 + Node 24, cleans caches, and publishes
vpsmgr/debian-dev-sshd (base deleted).

- Symlink go/gofmt/node/npm/npx into /usr/local/bin: /etc/profile.d is
  only read by login shells, so non-interactive ssh commands would miss
  them; nvm's .bashrc hook already covered node
- Register the alias in imageLabels so the reinstall dialog shows
  "Debian 13 dev"; extend collectManagedImages tests
- Optional: not part of install.sh, keeping small hosts lean
- Document the new image in docs/architecture.md, development.md and the
  AGENTS.md image-build invariant
2026-08-24 05:09:32 +00:00
bluebluesoda c9d19090f8 Update 20-network.sh 2026-08-22 00:24:36 +08:00
bluebluesodaandCommandCodeBot dcc2f767ff feat(images): optional openSUSE Leap 16 image builder
Add scripts/70-opensuse-image.sh, modeled on 60-rhel-image.sh: pulls
images:opensuse/16.0, installs sshd + universal tooling via zypper,
cleans caches, and publishes vpsmgr/opensuse-sshd (base deleted).

- Register the alias in imageLabels so the reinstall dialog shows
  "openSUSE Leap 16"; extend collectManagedImages tests
- Leap 16 ships systemd-networkd, so panel IPv6 config takes the
  existing Debian networkd branch — no runtime code changes needed
- Optional: not part of install.sh, keeping small hosts lean
- Document the new image in README (zh/en) and docs/architecture.md

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-21 06:20:23 +00:00
bluebluesodaandCommandCodeBot 8188bd2dbf feat(install): remove unused snap lxd before installing Incus
Ubuntu images ship lxd as a snap even when it is never used or
initialized; its daemon/socket, /var/lib/lxd (or /var/snap/lxd) and
lxdbr0 collide with Incus. Before installing Incus, detect snap lxd and
remove it — but only when clearly unused: abort instead of deleting when
the daemon is running, containers exist, or an initialized lxd.db is
present (checked under both deb and snap data dirs). Idempotent; skipped
when the snap is already gone.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-20 16:48:24 +00:00
bluebluesodaandCommandCodeBot fd9f17cd09 fix(install): skip base image pull on reinstall
50-image.sh checked for the base image (vpsmgr-debian-13) BEFORE the
prebuilt sshd image, but the base is deleted after the first build as a
build intermediate. Every reinstall/upgrade therefore re-pulled
images:debian/13 over the network, then discovered vpsmgr/debian-sshd
already existed and exited. Check the sshd image first and exit before
touching the base image when it is present.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-20 16:21:51 +00:00
bluebluesodaandCommandCodeBot 7bc37567da fix(net): detect ext iface via IPv6 route / link fallback
The installer and IPv6 probe found the external interface only through
`ip route show default` (the IPv4 main table). On IPv6-only hosts and
policy-routed boxes (v4 default in a non-main table, e.g. WARP/wireguard
tunnels) that returns nothing, so check-ipv6-support.sh died at
"no default route / ext interface found" before probing anything and
the installer silently fell back to a wrong iface.

Fall back to the IPv6 default route, then the first non-virtual UP
interface (excluding lo/incusbr/virbr/docker/veth/warp/wg), in all five
places that detect the WAN iface: check-ipv6-support.sh, 00-check.sh,
00-ip-ask.sh, uninstall.sh and DetectExtIF.

Verified on a real IPv6-only host: probe now reports ext iface eth0 and
reaches Phase 2 instead of dying.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-20 08:12:43 +00:00
bluebluesoda 8ab4d396d9 feat(install): add --update to force prebuilt binary refresh
- install.sh accepts --update (alongside --local-build): it forces a
  fresh download of the latest prebuilt release over an existing binary.
  A plain re-run still skips an installed binary (idempotent adopt).
- Conservative by design: on download/checksum failure the installed
  binary is kept and a warning is printed — never a surprise local
  rebuild over an unknown checkout.
- install_prebuilt now stops vps.service before replacing the binary
  (ETXTBSY on a live panel), and only after the download has verified,
  so a failed download never takes the panel down.
- docs: AGENTS.md invariant, architecture.md install lifecycle,
  README.zh-CN usage line.
2026-08-16 05:29:46 +00:00
bluebluesoda fb3f8b7cfc feat(install): add VPSMGR_STORAGE=dir opt-in for test boxes
- New installs use a dir storage pool when VPSMGR_STORAGE=dir (default
  zfs). A failed ZFS pool still aborts — the switch is explicit, never
  an automatic fallback.
- 00-check.sh skips zfsutils-linux, the DKMS module build and the zfs
  boot units entirely in dir mode (no kernel module needed).
- 10-incus.sh creates/adopts a dir pool without requiring zpool; zfs
  mode still refuses any non-ZFS pool, fresh or adopted.
- uninstall.sh is unchanged: incus storage delete handles both drivers
  and the zpool destroy is already guarded by 'zpool list'.
- docs: architecture.md and AGENTS.md document the switch.
2026-08-15 17:33:53 +00:00
bluebluesodaandCommandCodeBot 555401ea5b feat(ui): trim Globalping credit; warn against adding host's own v6
Trim the Globalping attribution in the installer to a short credit.
In the admin IPv6 pool page, show a prominent bilingual alert telling
operators not to add the host's own currently-used public IPv6 address
to the pool (it would break host IPv6 connectivity).

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 16:27:09 +00:00
bluebluesodaandCommandCodeBot 902e9a9063 feat(install): default pool mode + Globalping attribution
Make the IPv6 pool option the default choice in the installer prompt
(press enter for pool) and show a small thank-you attribution to
Globalping for powering the external reachability probe.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 16:20:02 +00:00
bluebluesodaandCommandCodeBot 90b7193127 fix(install): resolve Go tarballs from stable download manifest
The VERSION endpoint can temporarily name a release before its archive is
available through every download edge, causing a hard 404 during local
builds. Read the official stable JSON manifest instead, select the archive
for the current architecture, retry downloads, and fall back through the
previous stable release when the newest archive is unavailable.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 15:30:07 +00:00
bluebluesodaandCommandCodeBot 64349de0c0 refactor(install): always use root loop-file storage
Remove secondary-disk discovery and whole-disk ZFS creation from the
installer. New installations now always create a sparse ZFS loop-file
pool from free space on /, while existing vpsmgr pools are still adopted.
The installer no longer scans, selects, formats, or modifies devices such
as /dev/vdb, including virtual ISO/CD devices.

Update storage documentation and keep the existing reclaim, pool sizing,
and ZFS quota behavior.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 15:24:18 +00:00
bluebluesodaandCommandCodeBot 3d524eef05 fix(install): remove duplicate legacy IPv6 prompt
The script accidentally contained two ask_ipv6 definitions. Bash used the
second legacy definition, so the new detector/globalping flow was silently
overridden and installations jumped straight to manual prefix input.
Keep only the detector-based implementation.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 15:03:12 +00:00
bluebluesodaandCommandCodeBot da198973de fix(install): allow empty pool in env-override pool mode
The interactive flow already starts pool mode with an EMPTY pool (the
admin adds addresses later in the panel), but the VPSMGR_IPV6_MODE=pool
env override still demanded a non-empty VPSMGR_IPV6_POOL and died. New
installations selecting pool mode through the env path would fail; the
pool list is now optional in both paths.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 14:47:27 +00:00
bluebluesodaandCommandCodeBot 2238b927c3 feat(install): simplify IPv6 mode choice, drop NIC-address collection
Whitelist providers typically bind only ONE address on the NIC at boot;
the rest are added by the user in the provider panel when needed. So
auto-collecting "all global addresses on the NIC" at install finds at
most one address and never reaches the pool branch. New flow:

- detector VERIFIED -> prefix mode (as before)
- not verified -> ask: pool mode (starts EMPTY; addresses are added
  later in the admin panel) / manual prefix (trust the user's input) /
  disable
- no IPv6 on the NIC at all -> manual prefix or disable

The pool is no longer seeded from the NIC at install.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 14:47:27 +00:00
bluebluesodaandCommandCodeBot 5a0494df3c feat(install): probe-then-choose IPv6 mode (prefix / pool / v4)
00-ip-ask.sh now runs the unchanged check-ipv6-support.sh. Whole prefix
verified -> classic prefix mode. Not verified but >1 global address on
the NIC -> list them, keep the first for the host, ask the user to
confirm the rest as the pool. Declined or no addresses -> pure IPv4.
ndppd is only installed in prefix mode; pool mode uses kernel proxy_ndp.
vps ip6 gains neigh-add-proxy for the per-address wiring.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 14:47:27 +00:00
bluebluesodaandCommandCodeBot c72139ee7d fix(install): complete purge and normalize forwarding flags
- remove vpsmgr Incus resources, state, packages, and firewall tables
- avoid chowning the Traefik directory before the vps user exists
- accept boolean v4 forwarding values consistently

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 07:20:28 +00:00
bluebluesodaandCommandCodeBot 7d455d32bb fix(traefik): stop reverting dynamic dir to 0750 on every vps install
The 0755 world-readable scheme from 2980e3a only touched the installer
scripts; ensureVPSUser still ran the old cross-link block on every
`vps install` (40-panel.sh), chmodding /etc/traefik and the dynamic dir
back to 0750 after 30-traefik.sh had set 0755 — so the fix never stuck
until a manual chmod 755.

Drop the vps<->traefik group cross-link and 0750 from ensureVPSUser:
with a world-readable dynamic dir, traefik reads the YAMLs as its own
user and vps writes as owner; no group link is needed. 30-traefik.sh now
chowns the dynamic dir to vps:vps (matching ensureVPSUser) instead of
root:traefik, so the service unit keeps running as unprivileged traefik.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-15 06:04:27 +00:00
bluebluesodaandCommandCodeBot 2980e3a9ce fix(traefik): v1.0.0 binary with world-readable dynamic dir fallback
Keep the v1.0.0 codebase unchanged (main.go untouched). Two installer
script fixes on top:

- scripts/30-traefik.sh: make /etc/traefik and the dynamic dir 0755
  (world-readable) instead of relying on the vps<->traefik group
  cross-link, so traefik can always read the dynamic dir regardless of
  install order or group membership. vps still owns the dir and writes
  as the owner.
- scripts/40-panel.sh: fix the prebuilt-download EXIT trap to not
  reference a function-local variable under `set -u` (unbound variable
  skipped cleanup).

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 19:11:20 +00:00
bluebluesodaandCommandCodeBot ac2c7eecb0 fix(install): drop the 3 GiB pool reserve check
The loop-file pool is sparse — creating it costs no disk, only actual
container data grows it. The "leave 3 GiB free" guard was a worst-case
worry that blocked real installs (a 23 GiB-free host shrank its pool
instead of just working), and the percentage ceiling already keeps the
host from being fully starved. Remove the check; pool = 90% (or 80%)
of free space, no reserve.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 12:37:12 +00:00
bluebluesodaandCommandCodeBot 8683afeefc fix(install): shrink pool instead of aborting when 90% ceiling starves host
A 30 GiB disk (23 GiB free) failed install: pool = 90% of free = 20.7
GiB, leaving 2.3 GiB — under the 3 GiB reserve, so 10-incus.sh died
with "too little free space". Any disk with < 30 GiB free was
uninstallable.

Now the pool is shrunk in 512 MiB steps (down to a 1 GiB floor) until
the 3 GiB host reserve holds, instead of aborting. A small pool beats
no pool. Verified on the 30 GiB Ubuntu 24.04 test host: 19.5 GiB pool
created, install completes, full test suite passes.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 12:21:31 +00:00
bluebluesodaandCommandCodeBot 9c0e58c4a2 fix(install): skip apt golang-go; clean up the toolchain after local build
The distro package was pointless: Debian 12's golang-go (1.19) can't
parse go.mod's `toolchain` directive anyway, so the official go.dev
tarball was always the fallback. Now we go straight to go.dev when no
usable go (>= 1.21) exists — one less apt install, one less failure
path.

And once the panel binary is produced, the go toolchain is dead weight
(a couple hundred MB on a small host) along with the module/build
caches including the auto-downloaded go1.26.5 toolchain. Track whether
the installer brought the toolchain itself (GO_FROM_OFFICIAL) and, if
so, remove /usr/local/go + GOMODCACHE + GOCACHE right after bin/vps is
in place. A pre-existing host go is left untouched.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 11:42:17 +00:00
bluebluesodaandCommandCodeBot 361bc7285d fix(install): swap = RAM size (1:1), floored at 1 GiB instead of 2
2 GiB floor was more than a small VPS needs. Create a swap file the
size of RAM (1:1) with a 1 GiB floor: 1G box gets 1G, 2G box gets 2G,
still plenty for --local-build's go build, less disk eaten.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 10:48:17 +00:00
bluebluesodaandCommandCodeBot 340ecea577 fix(install): ensure a modern Go for --local-build on Debian 12
Debian 12 ships golang-go 1.19, which cannot parse go.mod's `toolchain
go1.26.5` directive (unknown directive) — so `install.sh --local-build`
failed on Debian 12 before the panel was ever built. ensure_go only
checked `command -v go` and accepted the broken 1.19.

ensure_go now:
- accepts an existing go only if it is >= 1.21 (the minimum for
  toolchain auto-switch that go.mod relies on)
- otherwise installs the distro package, and if that is still too old,
  downloads the official Go 1.26.x tarball from go.dev into
  /usr/local/go and puts it first on PATH for the installer

Verified on the Debian 12 test host (previously failed with go1.19).

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 10:38:38 +00:00
bluebluesodaandCommandCodeBot 3c50de05e0 fix(install): floor recommended swap at 2 GiB — 1G boxes OOM on go build
The swap recommendation was half of RAM, so a 1 GiB host got a 512M
swap file. `--local-build` compiles the panel (go build) and runs Incus
+ ZFS in the same memory; on the Debian 13 1G test host the build OOMs
and only an explicitly-added 2G swap got it through. Floor the swap
file at 2 GiB so the interactive default (and the non-interactive
auto-create) is enough for small boxes.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 10:37:55 +00:00
bluebluesodaandCommandCodeBot 96f1db04a8 fix(install): script robustness on fresh/adopted installs (found on Ubuntu 24)
Three install-path bugs surfaced by the Ubuntu 24.04 test host:

- 40-panel.sh: replacing the running vps binary failed with ETXTBSY on
  adopted installs (vps.service active) and the failure was ignored —
  the install "succeeded" with the OLD binary. Stop the service before
  cp and die if the copy fails.
- 40-panel.sh: `tee /etc/vpsmgr/.last-install.out` failed on a fresh
  host because /etc/vpsmgr does not exist yet (it is created by
  `vps install`). mkdir it first.
- 30-traefik.sh: chown vps:vps on /etc/traefik/dynamic ran before the
  vps user existed (40-panel creates it), erroring with "invalid user"
  and aborting the step under set -e. Skip when the user is absent;
  ensureVPSUser re-chowns the dir on every install.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 10:19:02 +00:00
bluebluesodaandCommandCodeBot 9e9533d2b3 fix(scripts): reclaim space below 25 GiB instead of 20 GiB
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>
2026-08-14 09:06:22 +00:00
bluebluesodaandCommandCodeBot c9185f0bae fix(scripts): reclaim space below 20 GiB instead of 15 GiB
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>
2026-08-14 09:03:55 +00:00
bluebluesodaandCommandCodeBot 32a44ad83e fix(scripts): keep zero stale kernels during space reclaim
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>
2026-08-14 09:01:37 +00:00
bluebluesodaandCommandCodeBot 0bc999fe88 fix(admin,scripts): show lifecycle status; plug scratch-dir leak
- 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>
2026-08-14 08:25:02 +00:00
bluebluesodaandCommandCodeBot c9b7e20c85 fix(scripts): private scratch paths; reclaim space before zpool sizing
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>
2026-08-14 08:21:21 +00:00
bluebluesodaandCommandCodeBot 358b69bd9f fix(sudo): whitelist must match real calls; sudo is a hard dependency
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>
2026-08-14 08:04:39 +00:00
bluebluesodaandCommandCodeBot b0b8591c94 fix(scripts): Ubuntu needs no ZFS compile; enable universe repo
- 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>
2026-08-14 06:40:48 +00:00
bluebluesodaandCommandCodeBot 58aee23cb7 fix(scripts): enable ZFS boot units so pools survive reboots
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>
2026-08-14 06:38:06 +00:00
bluebluesodaandCommandCodeBot 7c26121932 fix(scripts): use headers meta-package so DKMS survives kernel upgrades
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>
2026-08-14 06:32:07 +00:00
bluebluesodaandCommandCodeBot ef67357d33 feat(storage): ZFS-only pool; harden Debian host dependencies
- 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>
2026-08-14 06:29:04 +00:00
bluebluesodaandCommandCodeBot be6509779d fix(rhel): declare static IPv6 via NetworkManager instead of sysctl/ip hacks
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>
2026-08-14 05:37:54 +00:00
bluebluesodaandCommandCodeBot c0351ff8d8 fix: harden IPv6 pass-through and unprivileged panel found by e2e testing
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>
2026-08-14 05:29:13 +00:00
bluebluesodaandCommandCodeBot 57f4ccb94e chore: drop upgrade/compat scaffolding for the fresh start
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>
2026-08-14 04:05:53 +00:00
bluebluesodaandCommandCodeBot c0faea6c8c feat(scripts): install Incus via Zabbly LTS repo; unify services as vps.*
- 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>
2026-08-14 03:33:30 +00:00
bluebluesodaandCommandCodeBot ebc2b1478f feat(incus): port the container runtime from LXD to Incus 7 LTS
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>
2026-08-14 03:33:15 +00:00