Commit Graph
48 Commits
Author SHA1 Message Date
bluebluesodaandCommandCodeBot 83f0f02d45 feat(panel,admin): fix knowledge preview, panel-native confirms, CPU editor, persistent admin sessions
User panel:
- the reinstall modal drops the system highlight as soon as a share code is
  typed, so "nothing selected" is an explicit state that says the container is
  rebuilt from a checkpoint rather than from an image; clearing the code
  restores the default image, and clicking a tile drops a stale code instead of
  letting it silently override the visible selection
- the time-machine actions (share/restore/delete/revoke, plus the sticky-notes
  reset) confirm in an in-page dialog styled like the panel instead of
  window.confirm(); publishing still asks twice when it would delete the older
  checkpoints

Admin panel:
- the knowledge-base preview renders again: it posts a FormData body
  (multipart/form-data) and ParseForm alone ignores multipart bodies, so the
  server rendered the empty string and the preview box stayed blank
- the dynamic CPU limit rule moves back to the panel: an editable card on the
  overview saves into the DB (the single source of truth) and applies on the
  spot, restoring capped containers when it is switched off. The cpu_limit.*
  config keys and their `vps config set` entries are gone, and `vps install`
  no longer touches the rule, so an upgrade cannot reset it
- admin sessions are persisted (new admin_sessions table, hashed tokens) so a
  panel restart no longer logs the operator out; a password change from the
  panel or the CLI still invalidates every other session

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-16 06:51:35 +00:00
bluebluesodaandCommandCodeBot 627543da07 feat(panel,audit): keep panel-password access on expired accounts; fill audit gaps
- an expired account may still change its own panel password, and the admin
  may still reset it — only the container stays locked. Both routes are
  deliberately exempt from the read-only gate and both stay audited
- snapshot sharing is now OFF by default (new installs and upgrades alike):
  `snapshots.share` defaults to false and the panel re-derives its DB mirror
  from the config file at startup, so even a binary-only upgrade picks the
  default up. No admin-panel UI for it; enable with `vps config set
  snapshots.share true`
- publishing a checkpoint now asks twice when it would delete older
  checkpoints (first the exposure, then the irreversible deletion)
- audit completeness: distinct domain.add/domain.update/domain.delete,
  notes.update, and subject suffixes for snapshot.share.<checkpoint>,
  reinstall.share.<owner> and quota.expire.<user>, with matching labels in
  the audit viewer (knowledge.* labels added too)

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 17:41:19 +00:00
bluebluesodaandCommandCodeBot 1078e99cfa ci(release): take release notes from the tag annotation
- the publish job reads the tag's own message and uses it as the release
  body: `git tag -a vX.Y.Z -m "..."` writes the notes shown on the release
  page
- a lightweight tag carries no message, so the body falls back to
  "fix some bugs"; GitHub still appends its auto-generated compare link
  below whatever body is passed
- a signed tag's PGP block is stripped from the notes
- document the annotated-tag flow in docs/development.md

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 17:03:14 +00:00
bluebluesodaandCommandCodeBot 76e6b0990c feat(quota): per-container validity period with expiry lockdown
Port of dev-quota-expiry onto 1.8. The branch's other three commits are
already in main, so only this feature is new here; its migration is
renumbered to v16 (1.8 already uses v14/v15).

- users.expires_at (RFC3339 UTC, '' = permanent): the admin sets days at
  creation and extends from the users table (72h/30d/90d/180d/custom/forever;
  extend = max(now, current) + duration)
- the 60s loop force-stops expired containers (stop force:true) and disables
  boot.autostart; the account is locked to read-only — the user panel refuses
  every mutation (including the new snapshot-share/unshare routes) and the
  admin panel allows only extend + delete
- the user panel shows a live countdown and a locked banner; the admin users
  table dims expired rows and tags the name "(-Nd)"
- CLI: vps add --days N, vps quota --days N | --clear-expiry
- audit: quota.expiry (admin change) and quota.expire.<user> (auto stop)
- the container and its checkpoints are never deleted, so a checkpoint shared
  before expiry stays installable by others (cloning a stopped container's
  snapshot works — verified end to end)

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 16:58:37 +00:00
bluebluesodaandCommandCodeBot 8cb5c7cb82 feat(knowledge): admin-authored knowledge base with a user-side reader
- new `knowledge` table (schema v15) and an admin page (/knowledge) to create,
  edit, preview and delete Markdown articles; the Markdown source is capped at
  256 KiB and both title and content are required
- `internal/markdown`: a small, dependency-free Markdown renderer for a basic
  subset (headings, fenced code, inline code, bold/italic, links, lists,
  blockquotes, rules). It escapes HTML first and only allows http/https/mailto/
  #/ links, so an article can never inject markup
- the admin editor previews through POST /knowledge-preview, which returns the
  rendered HTML as JSON
- the user panel's machine card gains a "知识库 / Knowledge base" button that
  opens a large overlay (nearly fullscreen on phones): every article is
  embedded rendered, the list opens an article with a back button, and a copy
  button is added to each code block

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 16:40:26 +00:00
bluebluesodaandCommandCodeBot 3b8cfdcd93 refactor(settings): move snapshot sharing and CPU limit to config.yaml
- `snapshots.share` and `cpu_limit.*` are now config.yaml fields managed with
  `vps config set` (validated, shown by `vps config list/help`) instead of being
  panel/DB-only settings; the admin panel now shows the CPU rule read-only and
  the snapshot-sharing card is gone
- both apply immediately: `vps config set` writes the config file, mirrors the
  value into the DB and applies it right away — no panel restart — the same
  pattern as net.v4_forward. `vps install` refreshes the mirror from the file
- the CPU rule is read from the mirror (falling back to the config file) so the
  enforcer and the read-only card pick up a change live; the share toggle too
- drop the now-unused admin routes/handlers, the DB-only rule setting and the
  ConfigSet CPU form

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 16:25:00 +00:00
bluebluesodaandCommandCodeBot 65b54d7970 feat(snapshot): admin toggle for snapshot sharing
- add a snapshot_share_enabled setting with an admin-overview toggle;
  absent means ENABLED, so an upgraded host gets the feature on by default
- turning it off hides the user-side share/import entry points and makes
  the manager refuse new shares and installs, while existing containers and
  checkpoints are left untouched
- the stored codes are deliberately kept in the database (not deleted), so
  re-enabling restores them for as long as their checkpoint still exists;
  a code whose checkpoint has vanished is reported as invalid instead of
  building a half-container, and deleting a checkpoint still removes its code

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 16:08:37 +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
bluebluesodaandCommandCodeBot 209383806b feat(bandwidth): configurable monthly reset day
- new panel.bandwidth_reset_day (1-28, default 1); the monthly bandwidth
  period key now starts on that day (mgr.BandwidthPeriod), so counters
  roll over on the configured day instead of the 1st
- user panel quota label shows "流量配额(至次月 N 号)"
- docs + BandwidthPeriod/label tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-12 08:09:44 +00:00
bluebluesodaandCommandCodeBot 44c3727ad3 docs: document multi-container user groups
- add a "User groups" section: name-derived grouping, name validation, the
  shared panel password and accent color, and the container switcher
- describe the switcher labels and the per-container sticky-notes tag
- note the accent color is group-wide in the color section
- list multi-container users in both READMEs

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-11 06:41:06 +00:00
bluebluesodaandCommandCodeBot a64df2ac96 feat(panel): show build version in user footer
- add configurable user footer visibility
- render the injected release or local-build version

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-08 13:15:58 +00:00
bluebluesodaandCommandCodeBot b46bd0a901 fix(panel): theme color only on admin impersonation; thin borders back to 1px
- Per-user accent color now tints the user panel only for operator
  impersonation sessions (handleOverview clears ThemeColor otherwise);
  users logging in with their own password see the default theme
- Restore 1px borders on user-panel cards, buttons, inputs, quick stats,
  modals and spinner, and on the login card/input (was 3px)
- Update docs and tests; add HTTP-level test covering both session kinds

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 04:52:37 +00:00
bluebluesoda e25e729de7 fix(ipv6): disable npd6 alongside ndppd to avoid competing NAs
The in-tree NDP responder relies on being the only NDP proxy on the host.
main.go already disabled ndppd.service at install; npd6 (an alternative
distro NDP proxy daemon) was left running, so a host that happened to have
npd6 installed would answer Neighbor Solicitations with a competing
link-local-source NA and break the global-address fix.

- main.go: also `systemctl disable --now npd6.service` at install
- uninstall.sh: also stop/disable npd6 in the IPv6 cleanup path
- docs: note both daemons are disabled (ipv6-ndp-responder.md, ipv6.md)
2026-09-04 08:47:36 +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 a3a81d943f docs: note how the NDP responder coexists with the isolation layer
Clarify that security.port_isolation + ipv4/ipv6_filtering gate east-west
traffic on the bridge while the responder handles north-south NA replies on
the upstream NIC, and document the gateway-neighbour pinning that plans
around the isolation filter's deliberate side effect on fe80::1 NDP.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-08-31 21:39:21 +08:00
bluebluesoda 9696752d25 docs: explain the in-tree NDP responder design and scale-test checklist
Add docs/ipv6-ndp-responder.md: why prefix mode replaced the distro ndppd
daemon with a project-owned responder (some providers drop link-local-source
NAs), the data-plane correctness guarantees, the A/B design hardening, what
changed on dev-ndp, and an 8-point checklist for wide-scale testing. Link it
from docs/README.md.
2026-08-31 17:51:14 +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 6fcc97b81b docs: document the snapshot "time machine" semantics
- architecture.md: new bullet under Users and resources explaining that
  restoring to an older checkpoint discards the later ones, done as a single
  Incus call via zfs.remove_snapshots on the ZFS root volume (recursive
  rollback), applied at add/reinstall and refreshed on every `vps install`
  (incl. --update), with the delete-and-retry fallback and the reinstall
  checkpoint-count warning.
- configuration.md: snapshots.limit row now notes that restoring to an older
  checkpoint auto-deletes the newer ones.
- README / README.en: list the time-machine (checkpoint) feature in the user
  panel feature lists.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-08-31 06:58:11 +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 1158eb96da feat(config): add independent Traefik toggle
- add runtime net.traefik switch with live DB state\n- stop and disable Traefik when IPv4 forwarding is off\n- block new domains while Traefik is disabled
2026-08-29 14:34:47 +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 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