Commit Graph
153 Commits
Author SHA1 Message Date
bluebluesodaandCommandCodeBot c2009ddc69 chore(copy): tighten 1.8 wording, drop the "was X (not Y)" asides
- admin CPU card: the "what saving does depends on the checkbox" footnote is
  gone; the state line says it instead, and the state/description text is
  shorter
- user panel: the reinstall share field is just "或从分享的检查点安装" and its
  note "将从该检查点恢复,不安装系统。"; the share warning no longer parenthesises
  the contents ("拿到分享码的人可以读取该检查点内的全部数据,包括容器里的凭据。"),
  the second confirmation is "确认删除 / 更早的 N 个检查点会被删除,无法恢复。",
  the revoke prompt drops the repeated question, and the restore/reset prompts
  lose their "…,确认?"/"(重置后无法恢复)" tails
- the share toast only mentions removed checkpoints when there were some
  (previously "share code created (0 older checkpoints removed)")
- expiry: "账户已到期,机器已关机并锁定。" and "该账户已到期,仅可延期。";the
  "new deadline = max(now, current) + duration" footnote and "(0 = permanent)"
  parentheticals are gone
- knowledge editor: label is a plain "Markdown: headings, lists, …" legend and
  the empty preview is a dash instead of an instruction
- docs: the 1.8 sections (expiry, snapshot sharing, refquota, sessions, CPU
  rule, knowledge base) are cut roughly in half

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-16 07:12:06 +00:00
bluebluesodaandCommandCodeBot 3c48c27cc3 fix(admin): the CPU limit card no longer claims to be in effect while it is off
- with the rule disabled the parameter inputs are hidden entirely (they were
  shown with values that had no effect), and a state line says which of the two
  states the rule is in; ticking the box reveals the parameters again
- the parameter fields lost `required`: they are prefilled from the server, the
  server validates every range, and a hidden-but-required input makes the
  browser refuse to submit with a "not focusable" error
- the save message now matches what happened: "saved and applied now" only when
  the rule is enabled, otherwise "saved — it is disabled, so no container is
  capped" (nothing is applied, and any capped container was just restored)
- the button is a plain "Save"; the effect depends on the checkbox above it

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-16 07:00:27 +00:00
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 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 0e4528ad85 feat(cpu): global dynamic CPU limit rule
- admin panel gains a global dynamic CPU limit card (default off, prefilled
  10/60/0.5/2/30): a container that keeps using over y% of its own quota for
  x consecutive minutes is capped to z cores for n hours m minutes, then its
  normal quota is restored automatically
- reuse lx.SetCPU (live instance-config PATCH) and the per-minute
  resource_samples.cpu_pct_x10 (already relative to the container's quota);
  the new EnforceCPULimits pass runs in sampleResourceLoop, and the rule plus
  the active-limit state live in DB settings so a vps.service restart keeps
  every countdown and still restores on expiry
- user panel shows the capped core count and a frontend countdown while a
  limit is active; containers whose own quota is <= z are skipped
- cover the rule validation, streak detection, active-state cleanup and the
  user-panel badge in unit tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-15 14:44:54 +00:00
bluebluesodaandCommandCodeBot 3df47aece0 fix(panel): show whole GiB for large switcher memory
- MachineSpecs keeps raw MiB up to 9600, then switches to a rounded whole
  GiB (e.g. 10000 -> "2c 10g")
- cover the boundary and rounding in the group tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-12 08:31:33 +00:00
bluebluesodaandCommandCodeBot 8a73d1bd0f fix(panel): use MiB for the switcher memory unit
- MachineSpecs now renders "<cpu>c <mem>m <disk>g" (e.g. "2c 2048m 15g")
  instead of converting memory to GiB
- update the pinned group/panel tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-12 08:19:02 +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 0c159ae504 fix(admin): attribute impersonated container switches to the operator
- handleSwitchUser logged the raw session username, so a container switch
  made under an operator ("log in as user") session was recorded as the
  user's own action; use auditActor to record "000+<user>"
- localize the session.switch.<target> and color.update audit actions,
  which rendered as raw action strings on the audit page
- add a regression test for the impersonated switch actor

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-12 07:15:25 +00:00
bluebluesodaandCommandCodeBot 2e2022fd1f fix(panel): space out group switcher spec labels
- render each switcher option as "<label> - <cpu>c <mem>g <disk>g"
  (e.g. "A - 2c 8g 60g") instead of the cramped "A-2c8g60g"
- update MachineSpecs and the pinned tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-12 07:15:22 +00:00
bluebluesodaandCommandCodeBot 770b763d2a fix(cli): keep panel/ssh output when the group password is inherited
- vps add of a sibling inherited the group hash and blanked Result.Password,
  which also suppressed the ssh and panel lines in printAdded
- print an explicit inherited-password note and always emit ssh/panel

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-11 06:41:01 +00:00
bluebluesodaandCommandCodeBot 77b3fa927a feat(panel): compact machine labels in the group switcher
- label each switcher option "<label>-<cpu>c<mem>g<disk>g" (e.g. 2-4c8g40g)
  and drop the shared group-name prefix
- order members base-first then by numeric suffix (A, 1, 2, ... 10)
- tag the sticky-notes card title with the current machine on multi-container
  groups; single-container users keep a bare title
- add mgr.MachineSpecs and cover labels, order and the tag with tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-11 06:40:56 +00:00
bluebluesodaandCommandCodeBot 69d771d17d fix(admin): sort users by name by default
- make the initial user list order deterministic
- keep manual column sorting behavior intact

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-09 08:33:25 +00:00
bluebluesodaandCommandCodeBot bb4f9160d1 fix(admin): preserve create-form helper text
- keep the full username validation hint visible
- reserve a stable IPv6 pool usage row
- prevent helper text from shifting form controls

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-09 08:11:11 +00:00
bluebluesodaandCommandCodeBot 77a98d3824 fix(admin): improve create-user form layout
- keep username hints from shifting resource fields
- give IPv6 pool selection room to breathe
- align the create action and tighten responsive layout

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-09 08:04:02 +00:00
bluebluesodaandCommandCodeBot f5e26a376a fix(panel): show group-specific machine labels
- show A for the primary machine
- preserve numeric child suffixes including leading zeroes
- cover group label parsing with tests

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-09 07:54:32 +00:00
bluebluesodaandCommandCodeBot b327000001 fix(panel): clarify machine terminology and switching
- call user-facing containers machines in Chinese text
- keep impersonation state when switching machines
- align and narrow admin create-user controls

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-09 07:41:14 +00:00
bluebluesodaandCommandCodeBot e6bf390da0 feat(panel): add grouped multi-container users
- group sibling users by the username-number convention
- synchronize group credentials, colors, and session rotation
- add group switching and destructive-action warnings
- keep group updates transactional and validate names server-side

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-09 07:21:02 +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 9b68d7b7b6 fix(panel+admin): checkbox color clash and over-wide IPv6 column
- user/admin password rule checklists no longer reuse the global .ok
  button class (whose background turned whole rows green); ticks now use
  a dedicated .done state that only recolors the item text and mark
- admin user table in IPv6-pool mode shows 有/无 (Yes/No) instead of the
  full address, keeping the column narrow like the other two modes

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 12:52:54 +00:00
bluebluesodaandCommandCodeBot 602544c7bb feat(passwd+admin): real-time rule checks; usernames fold to lowercase
- pw.Validate now requires >=10 chars with an uppercase, a lowercase and a
  digit; messages and templates updated to match
- Change-password forms (user panel + admin) show a live checklist that
  ticks each rule off as the user types
- mgr.Add and `vps add` lowercase the username before validating and
  checking duplicates, so uppercase input is accepted and stored lowercase
- Create-user form folds the username to lowercase live and reports rule
  and duplicate problems before submit
- drop the wordy password-rule hints added in the last passwd commit

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 12:18:27 +00:00
bluebluesodaandCommandCodeBot e012352b31 feat(passwd): require >=10 chars with letters and digits on password change
Shared pw.Validate rule replaces the per-panel 14-char checks; user and
admin change-password handlers, templates and i18n messages all updated.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 10:03:31 +00:00
bluebluesodaandCommandCodeBot 7e87cfd373 fix(admin): reset-password modal login-as posts a blank name; panel borders 2px
- The modal "log in as this user" form carries class=inline, and the CSS
  rule form.inline{display:inline} overrides the hidden attribute, so the
  button was visible in every modal. It posted the stale/empty name input
  and failed with "user not found". Add form.inline[hidden]{display:none}
- Reset-panel-pass now attaches the username to the flash (kind
  user_created), so the reset modal's login-as button is shown only for
  that user and logs in correctly instead of submitting a blank name
- User panel: component borders (cards, buttons, inputs, quick-stat
  tiles, modal, spinner; login card and input) go from 1px back to 2px
- Tests: login-as form must render hidden + CSS guard present; reset flash
  must carry the username

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 06:14:03 +00:00
bluebluesodaandCommandCodeBot ba3d23af82 fix(panel): restore gray-blue light bg; bold port block; v4-off notice
- Restore the light-mode page background to the gray-blue #e6ebf1 (with
  white cards) that predates the all-white change, in the user overview,
  login page and the themed override, so light mode keeps its contrast
- Port-forwarding card: bold the three-digit block number and render the
  00/99 tails at the same size, un-bolded and faded (opacity only)
- When IPv4 forwarding is off the card now says "IPv4 转发已关闭 / IPv4
  forwarding is off" instead of leaving the slot empty
- Update tests for the new markup, v4-off notice and restored background

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 05:59:52 +00:00
bluebluesodaandCommandCodeBot c0ce7a422d fix(panel): style port block like IPs; hide it when v4 forwarding is off
- Render the port block in the same yellow code style as IP addresses,
  dropping the bold; de-emphasize the trailing 00/99 with a smaller,
  faded span
- Only advertise the port block while v4 forwarding is on — with it off
  (IPv6-only host) there is nothing to forward to, so the card hides it
- Update tests: v4-off renders no port-block line even when a start_port
  exists; zh/en assert the de-emphasized tails

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 05:43:48 +00:00
bluebluesodaandCommandCodeBot 9dc133f99a feat(panel): rework user overview connectivity UI
- Drop the "available ports" cell from the overview table; the IPV4 row
  now carries just the public IP across the row
- Rename the SSH row label to "SSH 端口" in zh (SSH port unchanged)
- Rename the Domains card to "端口转发 / Port forwarding": it now shows
  the user's port block (Available: <b>103</b>00-103<b>99</b>, block
  number bolded) followed by a "域名转发 / Domain forwarding" section
  with the existing domain table and add form unchanged
- pageData: replace the unused PortsShort with PortsPrefix (whole-hundred
  block number); update tests for the new layout and zh labels

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-06 05:21:53 +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
bluebluesodaandCommandCodeBot 01869464cb fix(panel): responsive layout for sticky notes on narrow screens
Stack the notes list above the editor below 640px instead of squeezing
the fixed 28% sidebar alongside the textarea; let the toolbar buttons
wrap so save/lock/delete no longer overflow on mobile.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-05 15:44:40 +00:00
bluebluesodaandCommandCodeBot b1d40a1107 chore(notes): halve sticky-notes plaintext cap to 256 KiB
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-05 08:27:23 +00:00
bluebluesodaandCommandCodeBot a8bc34a325 feat(admin): drop the plan-preset hint line
The explanation line above the S/M/L preset cards is unnecessary
chrome; remove it and its CSS/JS/labels.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-04 12:30:47 +00:00
bluebluesodaandCommandCodeBot 817b23c922 feat(admin): add S/M/L plan presets to the create-user form
Three fixed preset cards sit above the create form; each stores a
customizable name plus cpu/mem/disk/bandwidth specs in localStorage
(pure front-end, no backend change). "Edit" opens a modal, "Apply"
overwrites the current form values.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-04 12:22:07 +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 fd6448bd27 Update handlers.go 2026-09-02 19:04:21 +08:00
bluebluesoda 2f43b95b0f Update overview.html 2026-09-02 13:23:03 +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 4bc161c379 feat(panel): show snapshot disk usage in a wider time-machine modal
Incus's snapshot API already returns each snapshot's size, so the overview
now renders a Size column (via a newly exported HumanBytes) and the time
machine modal is bumped from 420px to the wide layout. The per-row value is
greeted only when snapshots exist; the header ships in the template.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-08-31 21:54:12 +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 e0dd123dc1 feat(name): allow hyphens in usernames, require letter start+end
- Usernames may now contain hyphens (lowercase letters/digits/hyphens), but a
  new name must both start and end with a lowercase letter, max 31 unchanged.
  This drops digit-terminated names like "a1"/"vps12345678" at creation.
- The strict rule is enforced only when CREATING a user (ValidateName in
  Add/userAdd and the admin add-user form's pattern). An existing-user
  operation (del / quota / passwd) now uses ValidateExistingName, which accepts
  every historically-valid shape, so a legacy digit-ending username stays
  operable rather than being locked out by the new creation rule.
- Tests cover the strict rule, its new rejects, and the legacy-compatible
  existing-name check.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-08-31 07:06:59 +00:00
bluebluesoda 2ff2144e1b feat(snapshot): one-call "time machine" restore via zfs.remove_snapshots
- A restore to an older checkpoint now relies on zfs.remove_snapshots set on
  the container root volume, so a single Incus restore auto-discards the later
  snapshots instead of failing with "subsequent snapshot(s)" and leaving a
  partially-executed rollback.
- The volume option is set at Add/Reinstall (on the fresh root volume) and by
  a new ApplyZFSRemoveSnapshotsToAll pass run during `vps install`, so a host
  updated via install.sh --update gets the behaviour immediately without
  reinstalling containers. ZFS-only: non-ZFS (dir test) pools are skipped, as
  they have no snapshots.
- SnapshotRestore keeps a fallback that deletes the newer snapshots by
  creation time and retries once, for a volume that somehow lacks the option.
- User panel adopts the time-machine metaphor: the button is now
  "时光机"/Time machine, individual snapshots are "检查点"/checkpoints, the
  reinstall modal warns with the exact checkpoint count, and the admin audit
  labels + success toasts use the same terms. Adds unit tests for the
  newer-snapshot selection and timestamp parsing.
2026-08-31 06:56:29 +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 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 c156e13322 docs(panel): shorten notes change-password copy to 改密 2026-08-27 08:30:00 +00:00