- Add vivid blue and fuchsia to the accent palette (5 swatches per row)
- User panel: tint background, cards, borders and code blocks via CSS
variables plus a 3px colored bar under the title for a stronger cue
- DB v12: users.color column (hex from a fixed 8-color palette, allowlist)
- Admin: click the username to open a swatch picker + clear; colored text/dot
- Login button defaults to neutral black/white; themed when a color is set
- User panel: tint background and accents via CSS variables in both modes
- Document the hidden entry point in READMEs and docs/architecture.md
- 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>
The swap-reapply command is internal bookkeeping users should never
see: containers get the configured incus.swap_ratio automatically, so
remove it from the CLI surface.
- cmdInstall now calls ApplySwapToAll alongside the other upgrade
self-heal steps, so containers created before swap support get their
allowance after upgrading and running `vps install`
- vps config set incus.swap_ratio keeps applying immediately to all
containers (unchanged)
- drop the swap-reapply dispatch, cmdSwapReapply and help entry
- docs updated accordingly
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Incus 7 on cgroup v2 writes memory.swap.max=0 for every container
unless limits.memory.swap carries an explicit byte amount (true/false
both end up as 0), so containers previously got no host swap at all
and `free` inside showed 0. Add incus.swap_ratio (default 0.5) so each
container gets limits.memory.swap = limits.memory x ratio, set at
create/reinstall and on quota changes.
- config: incus.swap_ratio field + registry entry (non-negative float,
0 disables swap; ApplyImmediate)
- lx: Client carries swapRatio; SetMem keeps swap proportional,
SetSwap refreshes only the swap key
- mgr: ApplySwapToAll refreshes every container from the DB memory
quota (idempotent, no restart)
- cli: `vps config set incus.swap_ratio N` re-applies to all
containers automatically; `vps swap-reapply` is the manual/repair
path for containers created before swap support
- docs: configuration/architecture/README updated
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
display_ip is purely cosmetic (panel URL, SSH hints) and never used in
rules, so accept IPv6 addresses, domain names, or any string without
spaces instead of requiring a parseable IP.
- registry: validate only that the value contains no spaces
- tests: domain accepted, space-containing value rejected
- docs/examples: drop the "public IPv4" wording
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- user panel: create/list/restore/delete disk-only snapshots per container,
with confirm dialogs and a reinstall warning that snapshots are wiped
- snapshots.limit config key (default 1) enforces a per-container cap;
admin panel is untouched
- restore keeps the live container config (disk_only) so quotas/NIC limits
stay in sync with the DB, serialized via opMu against reinstall/delete
- audit log: snapshot.create / snapshot.delete / snapshot.restore
- fix: snapshot names carry a random suffix to avoid same-second collisions
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- continue host installation when one container rejects guest IPv6 setup\n- preserve fatal handling for host and database errors\n- identify the affected container in reapply failures
- collect container metrics in one background Incus sample\n- serve dashboard usage from seven-day SQLite history\n- replace per-container disk exec and live page sampling
- 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.
- Admin domain panel gains a blocked-domains textarea (one domain per
line); entries and all their subdomains are refused by mgr.AddDomain —
the single add path — using label-boundary matching (example.co.uk
blocks a.example.co.uk but not forexample.co.uk). Existing domains are
untouched; only the add path is guarded.
- Save is per-line validated: invalid lines are skipped and reported by
their 1-based line numbers in a flash banner, the rest are saved.
- Migration v5 seeds a default list (mozilla/github/aws/microsoft/apple/
google/etc.) only when the settings key was never written, so admin
edits or a full clear are never re-seeded.
- normalizeDomain is rewritten around explicit rules shared by the add
path and the blocked list: lowercase, strip all trailing dots, reject
any character outside [a-z0-9.-] (never silently strip — pasting a URL
is an error), must end with a letter (excludes dotted-quad IPs), must
contain a dot (no single-word names), each label non-empty/<=63 and
starting+ending with a letter or digit (consecutive hyphens mid-label
like xn--p1ai stay legal), total <= 253. No TLD validation.
- Tests: db round-trip + v5 seed (fresh/reopen/admin-edit/preexisting-key),
matching semantics, parse/save partial validation, validation rules.
- docs: architecture.md blocked-domain section.
- 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.
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>
- net.ipv6_pool is now operator-editable (was immutable): accept bare
addresses or explicit /128 (any other prefix length rejected), stored
normalized to the bare form.
- New admin "IPv6 Pool" page (visible in pool mode): multi-line
batch-add box, list with per-address state (free / used + owner), and
remove for free addresses (assigned ones are refused — a user keeps
its address for life).
- Adding addresses re-applies the host plumbing so any address the
provider bound on the external interface is detached immediately.
- mgr: AddPoolIPv6s / RemovePoolIPv6 / PoolList / PoolEntries with tests.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Empirically this is what makes discrete whitelist addresses work:
- The provider binds every address on the host's NIC at boot; while the
host holds an address the kernel treats it as LOCAL and drops the
container's packets using it as a source (source-address validation),
so outbound v6 silently fails. Pool mode now removes each pool address
from the external interface (vps ip6 addr-del), on add/reinstall and
every ipv6-reapply/boot — self-healing after a reboot re-binds them.
- Containers get TWO NICs: eth0 = nictype routed on the external
interface with ipv6.address=<pool /128> (Incus programs the /128 route
+ proxy_ndp + fe80::1 gateway), eth1 = bridged on incusbr0 for the
shared IPv4. systemd-networkd binds the /128 statically on eth0 and
DHCPv4s on eth1.
- The manual bridge /128-route + proxy_ndp plumbing (SetupIPv6BridgePool,
WireIPv6Pool route/neigh) is gone — Incus programs everything.
Verified end-to-end on the whitelist host: container reaches out over v6
(curl -6 returns its own /128) and v4 (eth1 NAT) simultaneously, external
Globalping hits the container /128 with 0% loss, delete releases the
address and a new container reuses it.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Pool mode adds a create-user dropdown (auto first-free / a specific free
address / no IPv6) and an IPv6 column in the user table. Docs cover the
two modes, the installer flow, and the empirically verified per-address
routing.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The UI already speaks "bandwidth"; the internals still said "traffic"
for the same concept (monthly quota, NIC counters, throttling). Align
the whole stack:
- db: table `traffic` -> `bandwidth`, column `traffic_quota_gb` ->
`bandwidth_quota_gb` (fresh schema, no migration needed)
- lx: `Traffic`/`TrafficMap` -> `Bandwidth`/`BandwidthMap`
- mgr: `SampleTraffic`/`TrafficFor`/`EnforceTrafficLimits`/
`ParseTrafficGB`/`UpdateQuotasAndTraffic` -> bandwidth equivalents
- panel/admin/main: template fields, form names, CLI flags and vars
- docs and AGENTS.md updated; ordinary English uses of "traffic"
(forwarded traffic, inter-container traffic) are kept
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>
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>