Some hosts resolve go.dev/dl through an IPv6 path that serves the site and
manifest fine but returns 404 for the tarball CDN (dl.google.com). curl
prefers IPv6 by default, so ensure_go's download loop failed on every
candidate and aborted the local build.
- add curl_retry: fetch with the default address family, fall back to
'curl -4' on failure; use it for both the manifest and the tarball
- the IPv4 fallback is a pure retry — hosts with working IPv6 are
unaffected, and pure-IPv4 hosts were never broken
- drop an unused 'latest' variable in the download loop
net.slot_range required operators to reason about IPv4 last-octet slots to
control the host's reserved port span. Replace it with net.user_ports, a
direct comma-separated set of user-port ranges (default 10000-29999) that a
new container's whole-hundred block is drawn from, auto-aligned to whole
hundreds and merged. Only-new-users effect; existing containers keep their
ports.
- cfg: ParseUserPorts/CanonicalUserPorts/UserPortRanges/UserPortCount/
UserPortBlockStarts; legacy slot_range is migrated once on Load and the
old key stays in the file, ignored, no longer editable.
- db: migration v13 adds a UNIQUE index on users.start_port (port blocks are
no longer derived from idx, so uniqueness must be enforced directly);
UsedStartPorts() feeds the allocator.
- mgr: port blocks are allocated independently of the IPv4 idx via
allocUserPortBlock; admin capacity readout follows UserPortCount.
- installer: 00-ip-ask.sh asks for port ranges (default = no confirm, a
changed value echoes capacity and asks Y/n) and converts legacy slot_range;
00-check.sh scans only the effective user-port ranges.
- ssh: allocSSHPort now probes the host (bind test) before picking a port,
reassigning on conflict; the install no longer hard-fails on an occupied
30000-31999 port.
On the dev-ndp branch the prefix-mode boot unit vps-ipv6.service runs a
long-lived `vps ipv6-proxy` process that maps /usr/local/bin/vps, so
stopping only vps.service left the binary held and 40-panel.sh failed to
overwrite it with ETXTBSY ("Text file busy"). Stop every vpsmgr unit that
keeps the binary mapped (vps.service and vps-ipv6.service) before copying,
in both the local-build and prebuilt paths. Verified live: cp fails while
the panel runs, succeeds once it is stopped.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
On the dev-ndp branch the prefix-mode boot unit vps-ipv6.service runs a
long-lived `vps ipv6-proxy` process that maps /usr/local/bin/vps, so
stopping only vps.service left the binary held and 40-panel.sh failed to
overwrite it with ETXTBSY ("Text file busy"). Stop every vpsmgr unit that
keeps the binary mapped (vps.service and vps-ipv6.service) before copying,
in both the local-build and prebuilt paths. Verified live: cp fails while
the panel runs, succeeds once it is stopped.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Bring in the two v6-unrelated features committed on main: the snapshot
size column in the time-machine modal, and tab-completion for `vps config
set`.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Typing a config key by hand was tedious, so `vps config set <TAB>` now
completes just the keys `vps config set` accepts. The completion script
(dropped into /usr/share/bash-completion/completions by 40-panel.sh) pulls
candidates live from a hidden `vps config completions` subcommand, which
returns EditableKeys() from the registry — so offered keys never drift from
what set actually enforces. Also completes top-level commands, config
subcommands and (for del/power/quota/passwd) container names.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Some upstream IPv6 providers reject NDP replies whose source is a
link-local address (what ndppd and Linux proxy_ndp emit) and require the
advertised global address. Replace the distro ndppd prefix-mode data
plane with an in-tree raw AF_PACKET responder whose NA source is the
target's global address, re-reading a panel-writable rules file while it
runs so vps add/del never restarts it.
- add internal/ndp: minimal NDP responder (NS->NA, DAD handling, correct
ICMPv6 checksum/hop-limit/flags); only answers for target blocks inside
the operator's routed prefix (confines the root responder's trust surface)
- main: ipv6-proxy unit (prefix mode) + ipv6-proxy command + ethernetMAC
pre-check at install so a broken ext_if never hot-loops under Restart=always
- ipv6.go: write ndppd.conf atomically (temp+rename), drop all ndppd daemon
management and its sudoers grants
- mgr/routed_ipv6.go: configure guest v6 before publishing the NDP rule;
pin fe80::1 gateway neighbor, UseGateway=false, local /112 route +
vpsmgr-ipv6.service helper in the guest
- 50-image.sh/80-debian-dev-image.sh: bake UseGateway=false into [IPv6AcceptRA]
- VPSMGR_STORAGE now accepts btrfs alongside zfs and dir. On a btrfs root the
pool is created as a native subvolume at /var/lib/incus/storage-pools/vpsmgr
(Incus only allows that source path and requires it NOT to pre-exist); on any
other host it becomes a sparse loop-file pool sized exactly like ZFS.
- install.sh asks for an explicit beta confirmation up front when the btrfs
backend is selected (default yes — on a btrfs host there is no ZFS fallback).
Non-interactive installs proceed; non-btrfs/default-zfs installs are
unchanged.
- btrfs swap: on a btrfs root the swapfile is created with
`btrfs filesystem mkswapfile` (btrfs-progs >= 6.1), falling back to
truncate + chattr +C + dd for older tools — the generic fallocate+mkswap path
fails on btrfs (swap must be NODATACOW and preallocated). Non-btrfs hosts
keep the existing fallocate path.
- 00-check.sh installs btrfs-progs and skips the ZFS DKMS/kernel-header
toolchain for btrfs and dir alike; 10-incus.sh verifies the final driver.
- Panel Go code is untouched: container management is storage-driver-
transparent, and lx.EnsureZFSRemoveSnapshots is a no-op off ZFS.
- Docs (architecture, configuration, READMEs, AGENTS) updated for the new
backend, including snapshot-restore semantics (btrfs keeps later snapshots).
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
30-traefik.sh enabled traefik whenever the adopted net.traefik was true, even
on an IPv6-only box (net.v4_forward false). The domain proxy is only served
while BOTH v4 ingress and net.traefik are on, so traefik must stay installed
but DISABLED (no start, no boot autostart) when v4 forwarding is off, regardless
of the kept net.traefik value; re-enabling v4 later restores it. The start
branch is now gated on VPSMGR_V4_FORWARD first, matching the panel's own
ApplyTraefikState (V4Forward && Traefik).
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
- ask_range now prints one line right after the slot range is finalized
(env / adoption / non-interactive / interactive alike) with the computed
container capacity and the usable user-port span it reserves, so the
operator sees the effect immediately.
- An existing config WITHOUT a slot_range key (an upgrade from a
pre-slot-range version) no longer prompts: it keeps the default 2-201
(which the panel applies anyway) and returns, so a one-click upgrade of
an already-deployed panel is never interrupted by the new question.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Add net.slot_range, a shrinkable container slot range (read as the IPv4
last octet a new container may take; idx = octet-1) that bounds how many
containers can be created and, consequently, how much of the user-port
block 10000-29999 a host reserves. Default 2-201 = 200 slots; must always
be a sub-range of 2-201 (only-shrink rule). Affects ONLY new containers —
existing ones keep their slot/ports.
- cfg: add net.slot_range with strict ParseSlotRange validation
(exactly one '-', both ends integers in 2..201, lo<=hi), SlotCount/
SlotIdxBounds helpers, and VPSMGR_SLOT_RANGE env handling in FillAuto.
Register net.slot_range as operator / next-add in the config registry.
- db/mgr: NextFreeIdx now picks from a caller-supplied [idxMin,idxMax]
window instead of the fixed 1..cfg.MaxUsers; mgr.Add feeds it from the
configured range. Existing users outside a narrowed range keep their
slots.
- admin panel: capacity readout follows SlotCount() instead of a fixed
200 (honestly shows over-capacity when pre-existing users fall outside
a shrunken range).
- installer: 00-checklist asks the slot range right after the subnet
octet (env/adoption/non-interactive branches, validated). The
port-occupancy check now scans only the range's user-port span rather
than the whole 10000-29999.
- 80/443 handling: if either is already bound by a non-vpsmgr process,
the install no longer fails — it proceeds with Traefik installed but
DISABLED (net.traefik false, no autostart). 00-check writes a marker
that install.sh turns into VPSMGR_TRAEFIK=0 for 30-traefik and the
panel. A belt-and-suspenders guard skips the force-off when our own
traefik.service is already active (update/adoption). SSH 30000-31999
and the user-port span remain hard failures.
- traefik: bump pinned version to 3.7.12 and rewrite 30-traefik's
enable/disable decision to respect the effective net.traefik (forced
env > adopted config > default), so a disabled traefik is never
briefly started.
- docs: README(s), config example, architecture and configuration docs
updated; tests added for slot-range parsing and the FillAuto env vars.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
- new installs pull incus-base (Zabbly lts-7.0) which ships the same
incusd daemon without the QEMU/SPICE/Ceph VM stack (~240 MiB less
download, 500 MiB+ less disk)
- leave hosts that already carry the full `incus` package untouched
- uninstall --purge now also removes the full-incus VM dependencies,
guarded by an installed reverse-dependency check (never blanket autoremove)
- update the dev docs install snippet to incus-base
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Move the swap decision to the start of install.sh (before 00-ip-ask).
Swap is now mandatory: if absent, ask the user for a size in GiB
(default decline aborts with a bilingual note pointing at swap files
or systemd-zram-generator). The old optional swap block in
00-check.sh is removed.
All image build scripts now append the TCP tuning sysctls to
/etc/sysctl.conf and run sysctl -p (best-effort) during the build.
Covers 50/eol-debian11/80 (Debian) and 60/70/90 (RHEL/openSUSE/Arch).
scripts/eol-debian11-image.sh builds vpsmgr/debian-11-sshd from
images:debian/11 (fallback bullseye). Reuses the 50-image.sh slim
tooling and IPv6 on-link fix — systemd 247 on bullseye supports the
full [IPv6AcceptRA] section. Optional, never part of install.sh.
The minimal Arch base ships a single-default mirror list, so a slow or flaky
mirror aborted the whole pacman transaction. Write a mirrorlist with a few
reliable servers (official geo redirect + kernel.org + constant.com) so pacman
falls back automatically, and pass --disable-download-timeout so a slow-but-
alive download finishes instead of tripping the 'too slow' check.
- Arch script records the build date as 8 digits (YYYYMMDD) in the image
description
- lx.ImageAliasesWithDesc exposes image descriptions; Images() folds the
date into the Arch intro ('…滚动发行版,我们提供镜像打包于 <date>')
- Arch icon is now the 'A without crossbar' (Λ) shape instead of a solid
triangle
- openssh on Arch ships both sshd and the ssh client; openssh-client does not
exist and broke the whole pacman transaction (tested on a real Incus host)
- pacman -Syu --needed avoids reinstalling base packages; remove orphaned
packages after the upgrade (apt-autoremove equivalent)
- record the Archlinux<YYMM> version code via the publish key=value property
(incus publish has no --description flag); plain rm for cache cleanup
(pacman -Scc prompts in a non-tty exec)
- verified end-to-end: built vpsmgr/arch-sshd, launched a container and
confirmed IPv4 + IPv6 connectivity and SSH over both stacks
- scripts/90-arch-image.sh: builds vpsmgr/arch-sshd from images:archlinux/
current; unlike other image scripts it always deletes and rebuilds (rolling),
records Archlinux<YYMM> in the image description, keeps the image slim and
deletes the base afterwards
- Register Arch Linux label + rolling-release blurb and a gray triangle icon
in the user panel reinstall dialog
- Docs (README, architecture, development, AGENTS) and image tests updated
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
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>
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>
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>
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>
- 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.
- 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.
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>
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>
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>
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>
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>
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>
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>
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>