The tab now shows only the subscription token and the shared
credential set. The Reality public key moved to the node edit form
(item 17) and the socks username to the node itself (item 21), so
nothing the bindings list showed is lost. Display-only change: the
credentials endpoint payload is unchanged.
Item 20: drop the zero-client deploy gate in ValidateNodeDeploy for
all protocols, stop RebuildNode from auto-removing a node whose
bindings reach zero, and let server/heartbeat reconciliation rebuild
zero-client nodes like any other. Nodes are built and deployed first;
bindings can be added and removed freely.
Item 21: socks5-auth nodes carry one node-level fixed account
(node.socksUser/socksPassword, minted at creation or by the store-load
migration MigrateSocksNodeCredentials, idempotent). The sing-box
inbound's only user is that pair regardless of bindings; subscription
links for every bound customer carry the same pair. The generated
username avoids the ng: prefix so the agent never attributes the
account's traffic to a customer — it stays on the node's inbound
counters. Edit form shows both fields (copyable) for socks nodes;
NormalizeNode validates operator-supplied usernames.
Item 10: remove the disabled decorative topbar search input (each page
has its own working search); CSS rule and media-query reference removed.
Item 11: menu/title renames — 服务器 -> 节点管理, 客户与订阅 -> 用户订阅,
转发与节点 -> 入站&出站, 设置与运维 -> 设置 (nav, TITLES/breadcrumb and
cross-references synced); the chains page add button is now 添加入站 and
its count reads N 个入站; the 安装与命令 page is merged into 设置
(commandsHTML block between the settings grid and the jobs table) and
the page itself is removed from nav, PAGES, TITLES, load() and the view
map — an old #commands hash now lands on the SPA's standard overview
fallback.
Item 12: remove the system page's 最近活动 panel (rendering and its
audit label map plus orphaned CSS); backend audit records untouched.
Item 13: the settings jobs table gets the w-jobs treatment — ID, status
and time cells never wrap, compact row padding, and the error column is
capped at 22% width so an empty error column no longer balloons.
Also fixes a class collision from the item-8 batch: the exit-mode
segmented control is renamed .seg -> .segctl so it no longer draws a
border box around the system page info-bar segments.
Golden re-captured: the only diff is the served index HTML inside
errors.badSubscription, audited character by character as exactly these
renames and removals.
A forward node's exit target is now a choice: a managed panel server
(existing behavior) or a manual landing endpoint — address + port +
protocol (VLESS / SS2022 / SOCKS5) + operator-supplied credentials,
including third-party nodes the panel does not run. The panel only
builds the entry-side sing-box outbound and route rule; there is no
exit resource, no exit job, and the node is exit-ready by definition.
- model: Node.ExitMode/ExitAddress; manual exits force fixed port mode
and skip the exit-server port table entirely
- validation: address/port required; per-protocol credential presence;
SS2022 passwords must be base64 key material of the method's key
length (one PSK segment or serverKey:userKey) — the exact form
sing-box accepts, rejected up front instead of failing at deploy
- orchestration: resource building, deploy validation and rebuilds all
branch on manualExit; switching managed->manual queues removal of the
old exit resource, manual->managed mints a fresh exit resource id
(deploy validation previously dereferenced the nil exit server —
regression test locks the fix)
- UI: exit target segmented control, link paste box that parses
vless:// / ss:// / socks5:// links into the form, per-protocol
credential fields (blank keeps the stored value on edit)
- golden: new manualCreate/manualAssign/manualDeploy/manualJobs/
manualNodes sections — exactly one entry-side job whose outbound
targets the manual address; all prior sections unchanged
- the generated combined sing-box config passes the real sing-box
1.13.21 check
One credential set per customer (UUID + 32-byte root relay key + shortId)
lives on the customer record; node bindings no longer store credentials.
SS2022 user keys derive deterministically from the root key by method
length (16/24/32-byte prefix), so a single password serves every protocol,
matching 3x-ui's per-client credential model. Reality public keys stay a
node property and are shown per binding in the credentials pane.
- store: load-time convergence — first binding's set wins; a converged
password that is not a 32-byte root key is regenerated (uuid/shortId
preserved); bindings are stripped; idempotent, covered by tests
- deploy: the orchestrator stamps the customer's set onto a build copy;
subscriptions resolve the same set; the derivation applies in both the
inbound builder and the URI builder (verified live: Alice's N1 link is
byte-identical to the pre-change seed expectation, her N2 user key is
exactly the root key's 16-byte prefix)
- API: GET /api/customers/:id/credentials returns {credentials, bindings};
regenerate is now POST /api/customers/:id/credentials/regenerate and
rotates the whole set, rebuilding every deployed node of the customer
- UI: credentials pane shows the single shared set plus per-binding
node/protocol/Reality-public-key rows
- golden subscriptions fixture regenerated under one shared set (RF and
SS entries byte-identical to before; five entries audited by hand)
- golden baseline re-captured for the new shapes; difftest normalizer now
treats a base64 root key's prefix-truncation encodings and the Surge
'='-mangled variants as the same secret
agent-go: engineReport gains singBoxVersion. The probe runs
`sing-box version` once, parses the version token from its first line
('unknown' from unstamped builds reports empty), and caches it keyed by
the binary's mtime+size — re-probed only when an engine install/update
replaces the file. Steady-state heartbeats pay one stat, no subprocess.
Agent version stays 0.6.8.
panel-go: EngineState.singBoxVersion persisted from heartbeats (old
agents without the field keep showing '-'), servers table core-version
column now renders it. Golden gains heartbeatWithEngine +
serversAfterEngineHeartbeat sections locking the full chain, plus a
listen-address (<hostip>) normalization for the sandbox bind address.
singboxversion_test.go covers parsing, cache hits, and re-probe on
binary replacement.
GET /api/customers gains a summary object (total/online/depleted/
depletingSoon/disabled/enabled) computed server-side; the customers page
renders it as a six-cell stats strip above the toolbar.
Predicates aligned verbatim with 3x-ui client_paging.go:
- depleted = (limit>0 && used>=limit) || (expiry set && expiry<=now),
counted regardless of status;
- depletingSoon = active && !depleted && (expiry within expireDiff days
|| remaining traffic < trafficDiff GB), thresholds from new settings
expireDiff/trafficDiff with 3x-ui's factory default 0 (nobody counts
until configured);
- enabled = status active; disabled = manual suspensions only.
Online reuses the existing 3-minute presence projection.
Golden diff confined to the additive summary keys; summary_test.go
covers every bucket with thresholds off (soon=0) and on (soon=2).
- trafficResetDay/Period/At on the shared customer record (omitempty,
inert for the Node panel, which preserves unknown fields)
- housekeeping applies due resets before quota evaluation: period-key
idempotency, short-month clamping (31st fires Feb 28), missed-period
catch-up after panel downtime, traffic suspensions auto-resume
- first sight of a schedule only arms the period marker, never zeroes;
API create/PATCH accept trafficResetDay (0=off) and re-arm on change
- UI: reset-day input + last-reset display in the customer form
- tests: calendar math, apply pass, API lifecycle, persistence;
full Go/Node suites green, difftest formal 50/50
GitHub accounts suspended; all first-party distribution now served from
git.881218.xyz/rogue/NexusGate (Gitea):
- raw: /rogue/NexusGate/raw/branch/<branch>/<path>
- release assets and source archives under the same repo path
- scripts gain a GIT_BASE variable built from NG_REPO (default
rogue/NexusGate); NG_REPO/NG_BRANCH/--repo/--branch semantics unchanged
- panel enrollment/uninstall commands in public/app.js point at Gitea raw
- install.sh/nexusgate.sh/agent-singbox.sh stats source previously
defaulted to the upstream repo (a2899882); defaults now this repo
Upstream dependency references (sagernet/sing-box Go module) untouched.