89 Commits
Author SHA1 Message Date
bluebluesodaandCommandCodeBot 26015c1fe9 fix(install): identify the Debian archive by keyring, not by hostname
- The first cut matched the literal host deb.debian.org, which a Debian 13
  netinst never contains: it describes the archive as
  "URIs: mirror+file:///etc/apt/mirrors/debian.list" with
  "Components: main". So contrib was never appended and
  "apt-get install zfsutils-linux" still reported "no installation
  candidate".
- A stanza/line now counts as Debian when its Signed-By names the Debian
  archive keyring, or when it has none (the default trusted keys). A
  third-party repo signs with its own keyring and is left alone. The
  deb822 case is parsed per stanza (awk, mawk-compatible) so a mixed file
  is handled correctly.
- Verified on a Debian 13 netinst: after the edit, apt-get update succeeds
  and zfsutils-linux / zfs-dkms resolve from trixie/contrib.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-10-07 05:05:48 +00:00
bluebluesodaandCommandCodeBot d835d77c35 fix(install): enable Debian contrib so the ZFS packages resolve
- zfsutils-linux (and the zfs-dkms it Recommends) live in Debian's
  'contrib' component — ZFS's CDDL license is not DFSG-free — but a
  minimal Debian install enables only 'main' (+ 'non-free-firmware').
  Without contrib, apt reports "Package 'zfsutils-linux' has no
  installation candidate", zfs-dkms is never installed and the DKMS
  rebuild then dies with "no ZFS DKMS version found". Enable contrib
  idempotently before installing, in both the deb822 (*.sources) and the
  classic one-line formats, touching only the deb.debian.org archive so a
  third-party repo is never given a contrib it lacks. Mirrors the
  existing Ubuntu 'universe' handling.
- Strip the trailing colon from 'dkms status' version fields: a module
  whose postinst ran before the headers existed registers as
  "zfs/<ver>: added" (no kernel), and 'dkms build zfs/<ver>:' is invalid,
  so the fallback rebuild failed even once contrib was enabled.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-10-07 04:48:55 +00:00
bluebluesodaandCommandCodeBot 5e87c9bed7 fix(ipv6): plumb none mode + extra prefix through host, boot and creates
- RewireAllIPv6() began with `if !IPv6Enabled() { return nil }`, so in none
  mode — the mode that carries an extra prefix — it did nothing at all: the
  boot unit and `vps ipv6-reapply` never set net.ipv6.conf.all.forwarding,
  all.proxy_ndp or <ext_if>.proxy_ndp, and without them Incus could not install
  the routed NIC's proxy_ndp entry, so every create failed
- give that branch its plumbing: forwarding, kernel proxy_ndp, the NDP rules
  and the whole-/64 routes
- ensure forwarding + proxy_ndp at create time, before the routed NIC exists,
  so a create no longer depends on a prior install / ipv6-reapply
- a clone or launch failure now calls cleanup() and removes the partial
  instance; the leftover used to hold the name, so every retry was refused with
  "already exists (orphan?)"
- the installer persists the name-free sysctls (all.proxy_ndp always,
  all.forwarding when vpsmgr relays container v6) and uninstall resets
  all.proxy_ndp again

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-27 14:47:16 +00:00
bluebluesoda b7ebdb5374 fix(install): bound stalled downloads and tighten prebuild checksums
- dl_content no longer runs with no timeout at all: every attempt carries
  --connect-timeout, and a content transfer that connects then crawls below
  1 KiB/s for 30s is aborted so the next try can take over (a slow but moving
  download is still never cut off by a total cap)
- verify the release binary by looking up its SHA256SUMS entry explicitly
  instead of `sha256sum -c --ignore-missing`, which quietly succeeded when the
  manifest held no line for the arch
- copy the local vps-prebuild instead of moving it, so a later failure cannot
  destroy the only snapshot along with the temp dir
- fail loudly when lib-download.sh is missing instead of degrading silently
- drop the unused dl_handshake helper, accept binary-mode `*asset` lines, and
  lower-case v4_forward in 30-haproxy so True/TRUE match like 00-ip-ask
2026-09-27 12:16:30 +00:00
bluebluesodaandCommandCodeBot d8d70e2bb6 fix(install): rework vps-prebuild logic for first/install/update
- Fresh install (no /usr/local/bin/vps): prefer local vps-prebuild
  when its SHA-256 matches the release manifest, otherwise download.
  Checksum verification is mandatory whenever the manifest is fetched.
  If the local snapshot is used, mv it into the temp dir and chmod 755.
- Update (--update): always re-download the latest release; local
  vps-prebuild is never reused for updates (existing behavior).
- Already installed: skip entirely, ignoring any local vps-prebuild.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-24 14:04:40 +00:00
bluebluesodaandCommandCodeBot cddf5a8d95 fix(install): vps-prebuild can also update an existing binary
Previously the local vps-prebuild check only ran inside
install_prebuilt (fresh install path). An already-installed vps was
never replaced, so placing vps-prebuild next to the installer had
no effect on upgrades. Now the "already installed" branch also
checks the snapshot against the release manifest and swaps the
binary when it matches.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-24 13:47:12 +00:00
bluebluesodaandCommandCodeBot 9e35bbbdb7 fix(install): try local vps-prebuild before contacting GitHub
The checksum-manifest fetch previously had to succeed first; on any
transient GitHub failure the local prebuild was skipped entirely and
the installer fell through to dl_content. Now the local file is
attempted immediately when present, only hitting GitHub for the
manifest if needed.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-24 13:46:34 +00:00
bluebluesodaandCommandCodeBot 02e590c974 fix(install): make oneclick.sh work via bash <(curl ...)
When piped through /dev/fd the script's $0 is not a filesystem path, so
sourcing scripts/lib-download.sh failed ("... /dev/fd/scripts/lib-download.sh:
No such file or directory"). Inline dl_content() instead so the gitless
bootstrap can still retry the repo tarball download.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-24 13:21:26 +00:00
bluebluesoda a4fc822822 feat(net): allow incusbr0 in DOCKER-USER when Docker is on the host
Docker sets iptables FORWARD policy to DROP, which blocks forwarded traffic
from incusbr0 (both IPv4, and IPv6 when Docker's experimental/ipv6 is enabled).
This caused container apt and outbound traffic to time out during image build
and container runtime on hosts where Docker is installed.

- allow incusbr0 traffic in DOCKER-USER for iptables and ip6tables
- install docker.service.d/vpsmgr.conf drop-in so rules persist across Docker restarts
- clean up the drop-in and rules on uninstall
- document Docker compatibility in docs/architecture.md
2026-09-22 13:55:24 +00:00
bluebluesoda 1fdcf3f37b fix(ports): align user port upper bound strictly inward to block end <= b
The previous normalization logic aligned the upper bound using (b // 100) * 100 + 99,
which rounded down to a block start and then extended to +99. For any input where
the upper bound did not already end in 99 (such as 10000-15000 or 10000-20000),
this spilled over by (99 - (b % 100)) ports past the user's requested range
(e.g. 10000-15000 became 10000-15099).

Ports outside the user's input may be in use or unavailable. Align the upper
bound strictly inward so each allocated block is a subset of the input:

- calculate hi as ((b + 1) / PortsPerUser) * PortsPerUser - 1 (ending in ...99 <= b)
- mirror the calculation in scripts/00-ip-ask.sh normalize_user_ports
- update unit tests in internal/cfg to verify strict inward alignment
- update docs/configuration.md examples and container capacity readouts
2026-09-22 13:31:49 +00:00
bluebluesoda 8d9156b333 fix(apt): pin zabbly repository to priority 600 against ubuntu pro esm
On Ubuntu systems with Ubuntu Pro / ESM Apps enabled, Ubuntu's ESM repository
pins its own packages to Pin-Priority: 510. Ubuntu 24.04 ships Incus 6.0 in
universe/ESM, so APT prefers the older 6.0 incus-client over Zabbly's 7.0
incus-client (default priority 500). When installing incus-base, this causes
unmet dependencies and breaks the installer with held broken packages.

- write /etc/apt/preferences.d/zabbly-incus with Pin-Priority: 600 in 10-incus.sh
- purge the preference file in uninstall.sh --purge
2026-09-22 13:31:31 +00:00
bluebluesodaandCommandCodeBot 471b0653a6 chore(net): raise the TCP socket buffer max to 8 MiB
- host sysctl (20-network.sh): tcp_rmem/tcp_wmem max, rmem_max and
  wmem_max all go from 4194304 to 8388608
- image builds (50/60/70/80/90 + eol-debian11): tcp_rmem/tcp_wmem max
  bumped the same way
- autotune defaults (262144) and the min values are untouched

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-20 07:19:13 +00:00
bluebluesodaandCommandCodeBot 5c76576ffb fix(haproxy): load the published generation in the unit, not just the entry config
The installed unit started HAProxy with the static entry config alone:

  -Ws -f /etc/haproxy/haproxy.cfg

That file declares the frontends, the reject backends and the timeouts; the
per-domain backends and the routing maps live in the generation the panel
publishes, and the maps' paths point at it. So HAProxy came up healthy, bound
80/443 and answered 404 on :80 and closed every :443 connection — no managed
domain was reachable. `haproxy -c` on the entry config alone passes, which is
why the install-time check did not catch it.

- the unit now passes both files, the generation through "current" so a reload
  picks up whatever the panel published last;
- 30-haproxy.sh validates exactly that command line (entry + generation) and
  requires domains.cfg AND both maps before it considers the bootstrap
  generation in place, so the pre-start check can no longer pass a setup that
  serves nothing;
- tests: TestUnitLoadsBothConfigFiles parses the unit's own ExecStart and fails
  if it does not pass two config files, and TestUnitCommandLineRoutesTraffic
  runs that command line against a real HAProxy and asserts a registered host
  reaches its backend. Both fail against the old unit.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-16 14:17:43 +00:00
bluebluesodaandCommandCodeBot 4c93ae4be3 feat(haproxy): replace Traefik with a pinned HAProxy domain proxy
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>
2026-09-16 12:59:13 +00:00
bluebluesodaandCommandCodeBot 2363b89743 feat(snapshot): share a checkpoint to install from it
- a user can publish one checkpoint behind a random v4-UUID code; another
  user enters that code in the reinstall dialog and gets a container cloned
  copy-on-write from the snapshot (no image is built, so no extra disk until
  it diverges), re-provisioned as their own: own IP/ports/disk quota and
  network, a fresh hostname/root password/machine-id, the source's
  authorized_keys cleared, and no init script run
- publishing deletes the older checkpoints (a clone pins its origin, so they
  could never be restored to again); the panel warns with the exact count.
  One share per container; deleting the checkpoint/container or reinstalling
  revokes it, and a code for the container itself is handled as a restore
- clone via POST /1.0/instances with source.type=copy, then delete the old
  container and rename the clone into place; new lx.CloneFromSnapshot /
  lx.RenameInstance and a shared instanceSpec() so a clone is configured
  exactly like a fresh container
- new snapshot_shares table (schema v14) and /snapshot-share /
  /snapshot-unshare panel routes plus the time-machine and reinstall UI
- also fix a refused restore (a checkpoint pinned by a clone) so the
  container is always left running, and recognise the "subsequent internal
  snapshot(s)" refusal message alongside the existing one
- also give new containers a HARD disk limit: the pool carries
  volume.zfs.use_refquota so a volume's usage counts the blocks it
  references (including inherited image/snapshot blocks); existing
  containers are untouched

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 15:57:30 +00:00
bluebluesoda f2d3eee4d8 fix(install): fall back to IPv4 when go.dev download fails over IPv6
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
2026-09-04 08:53:34 +00:00
bluebluesoda c9b18ffee5 Merge remote-tracking branch 'origin/dev-ndp'
# Conflicts:
#	src/internal/admin/handlers.go
2026-09-04 08:33:56 +00:00
bluebluesoda 69f79187c8 feat(ports): replace net.slot_range with net.user_ports; probe SSH ports at add time
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.
2026-09-04 08:32:13 +00:00
bluebluesoda 3fdb946baa Update 80-debian-dev-image.sh 2026-09-02 23:30:25 +08:00
bluebluesoda 83f9214b72 fix(install): stop vps-ipv6.service too before replacing the binary
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>
2026-08-31 22:04:42 +08:00
bluebluesoda da3f8b2105 fix(install): stop vps-ipv6.service too before replacing the binary
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>
2026-08-31 22:04:31 +08:00
bluebluesoda 0548b50eb2 Merge main into dev-ndp
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>
2026-08-31 21:55:07 +08:00
bluebluesoda 31143f2cf8 feat(cli): tab-complete config keys for vps config set
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>
2026-08-31 21:54:19 +08:00
bluebluesoda 062cf9c4f5 feat(ipv6): in-tree NDP responder for global-address NA source
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]
2026-08-31 17:45:24 +08:00
bluebluesoda f79ccb09f0 feat(install): add btrfs storage backend (native subvolume or loop file)
- 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>
2026-08-31 17:00:03 +08:00
bluebluesoda 6eafac3274 fix(install): don't start traefik when IPv4 forwarding is off
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>
2026-08-31 07:26:32 +00:00
bluebluesoda 366ceff41e Update 00-ip-ask.sh 2026-08-31 14:13:46 +08:00
bluebluesoda 6e8dd6c4f8 feat(install): echo slot-range capacity + ports hint, don't prompt on upgrade
- 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>
2026-08-30 09:43:44 +00:00
bluebluesoda fa151eaee4 feat(install): configurable slot range + auto-off Traefik on 80/443 conflict
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>
2026-08-30 09:36:29 +00:00
bluebluesoda 1b564a9900 feat(install): use container-only incus-base instead of full incus
- 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>
2026-08-30 08:19:19 +00:00
bluebluesoda 07dedf9e9f Update 00-ip-ask.sh 2026-08-30 08:46:58 +08:00
bluebluesoda 4c0d1750a3 Update 00-ip-ask.sh 2026-08-30 08:44:36 +08:00
bluebluesoda 2e3dc7f3d2 Update 50-image.sh 2026-08-29 03:30:51 +08:00
bluebluesoda ebd6e9372e Revert "fix(install): preserve temporary worktrees"
This reverts commit 46df0e5fd2.
2026-08-28 18:09:38 +00:00
bluebluesoda 46df0e5fd2 fix(install): preserve temporary worktrees
- stop deleting /tmp wholesale during ZFS pool sizing\n- clean only stale vpsmgr scratch paths and preserve oneclick installs
2026-08-28 18:07:01 +00:00
bluebluesoda f755823bc1 fix(install): limit header check to Debian
- keep Ubuntu's prebuilt ZFS module path unchanged
2026-08-28 15:50:49 +00:00
bluebluesoda 01db14ef0b fix(install): build ZFS for cloud kernels
- install matching Debian cloud kernel headers\n- detect missing running-kernel headers before DKMS\n- show actionable DKMS build and install errors
2026-08-28 15:50:13 +00:00
bluebluesoda 97396589f5 feat(install): support disabling IPv4 forwarding
- add --disable-v4forward with explicit confirmation\n- skip reserved-port checks and keep Traefik stopped for IPv6-only installs\n- document dedicated-host requirements and sync config references
2026-08-28 15:07:24 +00:00
bluebluesoda 6d897cc1bb feat(install): enforce swap up front, before IPv6 prompts
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.
2026-08-28 12:36:53 +00:00
bluebluesoda 539dae613e feat(images): bake vpsmgr TCP tuning (BBR + buffers) into every image
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).
2026-08-28 09:58:18 +00:00
bluebluesoda b8eda138d7 feat(images): add optional EOL Debian 11 sshd image builder
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.
2026-08-28 09:09:57 +00:00
bluebluesoda d221de8754 fix(install): prompt before continuing with <5 GiB free on /
Replace the hard die with a y/N confirmation so users can force
an install on small disks when they understand the risk.
2026-08-28 08:36:29 +00:00
bluebluesoda c246249a0b Revert "fix(images): Arch build — multi-mirror fallback + no download timeout"
This reverts commit e010552683.
2026-08-24 19:28:40 +00:00
bluebluesoda e010552683 fix(images): Arch build — multi-mirror fallback + no download timeout
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.
2026-08-24 19:21:54 +00:00
bluebluesoda e3e49b12e7 feat(panel): show Arch build date in the reinstall intro; fix Arch icon
- 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
2026-08-24 18:50:42 +00:00
bluebluesoda 5bb4a5776c fix(images): Arch build — drop openssh-client, record version, clean orphans
- 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
2026-08-24 18:10:26 +00:00
bluebluesoda 49d34f3748 feat(images): optional Arch Linux rolling image builder
- 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
2026-08-24 17:48:12 +00:00
bluebluesoda 3ffa594a34 Update 80-debian-dev-image.sh 2026-08-25 00:44:05 +08:00
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