Commit Graph
25 Commits
Author SHA1 Message Date
bluebluesoda de95681073 feat(admin): expand palette to 10 colors; strengthen user-panel theming
- 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
2026-08-26 18:35:09 +00:00
bluebluesoda 705b5d5b64 feat(admin): per-user accent color for login button and user panel
- 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
2026-08-26 18:15:18 +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 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
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 7344b8ef3c feat(swap): auto-apply swap ratio on install, drop swap-reapply CLI
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>
2026-08-21 05:03:41 +00:00
bluebluesodaandCommandCodeBot 7b31f2d4ec feat(swap): configurable per-container swap allowance
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>
2026-08-20 21:01:44 +00:00
bluebluesodaandCommandCodeBot 76e99e0fcf feat(config): allow any space-free string for panel.display_ip
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>
2026-08-20 16:05:19 +00:00
bluebluesodaandCommandCodeBot 484b53e811 feat(panel): user-managed container snapshots
- 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>
2026-08-20 06:46:56 +00:00
bluebluesoda 86b7038e2f fix(install): tolerate guest ipv6 setup failures
- 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
2026-08-19 16:39:51 +00:00
bluebluesoda 99f990b910 Merge main into dev 2026-08-19 16:11:42 +00:00
bluebluesoda 7866badddc feat(metrics): persist minute resource samples
- 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
2026-08-19 15:53:04 +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 adad8584d4 feat(domain): admin blocked-domains list + strict validation
- 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.
2026-08-16 05:19:15 +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 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 c1db56312d feat(panel): editable IPv6 pool with batch-add management page
- 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>
2026-08-15 14:47:27 +00:00
bluebluesodaandCommandCodeBot 790ed11369 fix(pool): routed NIC for per-address IPv6, detach pool addrs from host
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>
2026-08-15 14:47:27 +00:00
bluebluesodaandCommandCodeBot 904e60ef7e feat(panel): IPv6 pool dropdown on create + docs
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>
2026-08-15 14:47:27 +00:00
bluebluesodaandCommandCodeBot d93b93709f refactor: rename traffic to bandwidth across code, schema and docs
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>
2026-08-14 05:58:50 +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 1663563020 docs: reflect the Incus runtime, vps.* services and privilege drop
- architecture.md: Incus components, unprivileged panel section
  (incus-admin group + sudoers whitelist), vps.* unit names, atomic
  user creation transaction.
- configuration.md: incus.* config keys, /var/lib/incus socket, vps
  service restart.
- development.md: Incus dev-loop instructions, updated layout.
- README(.zh-CN).md: Debian 13/Ubuntu requirements, Incus mention.
- AGENTS.md: Incus invariants (socket, websocket exec, EnsureImage
  before Launch, sudoers whitelist, vps.service naming).

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-14 03:33:37 +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