agent_nodes gains probe_only; getConfig always serves the flag and withholds the sing-box payload (null config, empty hash) for flagged nodes while probe tasks, fast interval, stat iface and update intents ride as usual. The agent gates both core branches when the local agent.json preset or the master flag is on, retires a running core once (best-effort tail flush, switchover bookkeeping untouched so turning the flag off self-heals through the existing hash-current-but-not-running path), and makes the traffic cycle an explicit no-op. Panel: node create/update accept probeOnly, the node view overlays it from the binding, the node form gains a switch (carried into the generated install command), the list shows a probe-only tag in the core-version cell, and install-agent.sh --probe-only writes the preset, skips the core download/self-check and inherits the flag on no-arg reruns.
- 0: sing-box gen overrides fragment listen/port from the inbound row
(empty listen -> "::"), Update/Add recompute the stored fragment;
non-loopback E2E against the real core binary.
- 1: agent fetches the sing-box core from the Gitea release when
missing, self-checks it, and only then latches the switch.
- 2: node list shows the sing-box core version (heartbeat field,
persisted on the node row), xray version as fallback.
- 3: public-page fallback returns 404 to anonymous visitors when the
page is disabled.
- 4: settings restart hint bar removed, dead i18n keys cleaned.
- 5: two PUT calls missing JSON headers fixed (audit: only those two),
batch probe-task creation endpoint + UI.
- 6: cf-probe retirement card and coverage pipeline removed.
- 7: generated vless links no longer carry spx (panel export + sub);
parser still accepts spx; golden fixtures updated.
- probe batch: dedicated probe settings tab (public-page settings,
tasks, per-node meta), retention_days editable, per-node traffic
calibration + stat-interface pinning, runtime metrics split into a
lightweight fast report (default 3s, 1-60s adjustable).
- 8: sing-box core processes start with GOMEMLIMIT=32MiB, GOGC=50.
- 9: dead /getTwoFactorEnable docs entries removed (frontend call and
contract entry already gone at base; verified by grep).
The B5 full-stack rehearsal caught a silent accounting killer:
with the production switch set (inbound on, outbound off) the
generated v2ray_api stats block omitted the outbounds list, and
sing-box then registers NO counters at all — QueryStats answered
zero entries while real traffic flowed (A/B against the real core:
adding the list produced full user/inbound/outbound counters).
- builder: whenever the stats block is generated, emit inbounds,
users and outbounds together; the gates now only decide whether
the block exists
- master: enforce the inbound category gate at recording time in
the agent traffic report (the whole-network accounting switch
semantics: off period is never counted); onlines stay ungated
- golden gate test updated to the corrected contract
- master getConfig serves singbox_config only; xray payload retired
(build failure is a hard 500, agent keeps its current state)
- agent: singbox_config arrival is the switch signal — flush xray
tail traffic, stop xray, activate sing-box; activation failure
restarts xray from the last payload and retries next poll; a
legacy master (no sing-box keys) keeps the xray path untouched
- sing-box log rotation: 8 MiB copy-truncate with one .1 kept,
checked on the traffic cycle
- install-agent.sh: xray/geo downloads and checks removed, agent
binary now from the V2.0.0 release, self-check is sing-box
binary + check on the live config
- display version default 2.0.0 (master V2.0.0 / agent A2.0.0)
- probe package: probe_ping_task/probe_ping_node tables; task CRUD
with hub save semantics (id high-water, base-diff assignment,
64-per-agent crowding refusal, auto_join cap); filing with
assignment-gated insert; PingHistory weighted-median chart read
over minute+hour tiers; auto-join on first Record (ensureRows).
- controller: /panel/api/agent/probe-tasks* management endpoints
(node id space, hub validation chain verbatim); series=ping now
serves real rows from probe.PingHistory; heartbeat ingests
probeResults (missing latency skipped, batch capped); config
response carries probe_tasks (always present, empty = clear).
- agent: per-task probe loops (immediate first probe, ticker,
900ms resolve/dial budgets, <=3 addresses, resolve overrun = no
sample), result buffer drained on heartbeat and requeued on
failure; task set applied before the config-hash early return.
- SingboxService builds the full sing-box config from the new model:
stored fragments re-projected against the identity layer (converter
projections factored into internal/singbox/project.go, shared by
converter and generator), agent singbox_outbounds/routing_rules
override, forwarding compiled to sing-box outbounds+route rules,
default private-CIDR reject rule (geoip:private replacement),
tun dns landing in the config dns block (closes the B1 'B2' pending).
- trafficStatsSwitches wiring: experimental.v2ray_api.stats lists are
generated per category only when both direction switches are on;
all-off yields no experimental block (no backfill, per the switches'
documented semantics).
- Agent /config endpoint additionally serves singbox_config +
singbox_config_hash; xray payload untouched, sing-box build failures
log-and-omit.
- SubService.GetLinkFromModel regenerates share links from identity +
singbox_config/singbox_preserved only (legacy generators reused over
rebuilt settings/stream views). Golden fixtures 26/26 byte-equal;
production backup DB: 107/107 links equal (55 byte-equal, 52 equal
modulo the legacy generator's own random Reality sni/sid pick),
generated config passes sing-box check.
- Converter preservation amendments (panel-side, for blocked/discarded
material link generation still needs): blockedTls for xhttp/kcp,
verbatim streamNetwork/streamSecurity, vlessEncryption presence,
ssPassword for removed ciphers, wireguardDns, socksAuth/socksAccounts
provenance for exact mixed/socks user refresh.
Co-Authored-By: Muse Spark <noreply@muse.dev>
New internal/web/service/probe package keeps probe history in its own
probe.db beside the panel DB (single connection, WAL), so history
writes never contend with the panel write lock.
- Minute rows per hub semantics: a held-reading rhythm books traffic
against each reading own arrival; minute buckets merge (means,
rounded int means, peaks) and the stored net rate derives from the
kernel counters between row marks, falling back to agent rates at
epoch breaks.
- probe_traffic ledger: accumulate is a line port of hub traffic.rs
(baseline alignment, same-boot shrink books 0, epoch change books 0,
unseen-boot exception books the whole counters-since-boot measured
against the master last-seen stamp, per-direction period resets,
saturating adds); reads gate stale day/month stamps to zero.
- probe_meta rows are ensured for every reporting agent (B3 edits
them in place); probe_ping_* tables are created now, written by B4.
- Hourly roll_up behind a watermark (hour-aligned, late grace,
pre-retention hours skipped unfoldered) and watermark-guarded
chunked prune (minute tier 7 days or the retention window when
shorter; hourly tier to retention_days, default 7, cap 365).
- Wiring: heartbeat files probe samples after the snapshot update
(last-seen captured before touch); housekeeping hangs off the
existing robfig/cron in web.startTask (@hourly + one pass a minute
after startup, mirroring the hub boot pass); Server.stop books held
readings. Every failure path logs and degrades - a broken probe.db
never fails a heartbeat or the panel.
NAT'd agents (cloud 1:1 EIP) only ever report their private
interface address in heartbeat telemetry, so the node list showed
an unusable RFC1918 address. The master now records the source
address of every authenticated agent request (CF-Connecting-IP,
else first X-Forwarded-For hop, else RemoteAddr; change-only
writes) on the agent row as seen_ipv4/seen_ipv6, and the node
list resolves the displayed pair via service.ResolveAgentAddresses:
public telemetry wins, else a public observed source, else the
reported address as-is. CGNAT 100.64/10 observed addresses are
never promoted. Telemetry values are kept verbatim; resolution
is read-time only, and agents need no changes.