🚀 MTProto Multi-Client, SQLite→PostgreSQL Migration, 500k-Scale Performance & Node Sync Hardening
- 📬 MTProto goes multi-client — MTProto inbounds now run on the
mtg-multiengine with one FakeTLS secret per client, per-client ad-tags, and per-client quota & expiry enforced in the sidecar; client edits hot-apply through a management API so live connections survive. - 🐘 SQLite → PostgreSQL, end to end — the PostgreSQL panel restore now accepts SQLite
.dbfiles and migration dumps directly (uploads are sniffed automatically), cross-db migration became lossless, transactional and pre-checked, and a newx-ui pgclientcommand installs or upgrades the PostgreSQL client tools. - 🚄 Built for 500k clients — batched ip-limit lookups, depleted-client disables by id, delta WebSocket stats above a snapshot threshold, and subscriptions resolved from normalized tables — pinned by a scale test suite at 500,000 clients.
- 🧠 Frontend platform overhaul — full React Hook Form migration, axios replaced with the native Fetch API, charts moved to uPlot, and Husky / lint-staged / MSW / Storybook dev tooling.
- 🛰️ Node sync hardening — host overrides adopted into the master, no more premature inbound sweeps or Postgres deadlocks in sync, client edits no longer tear down node inbounds, and node auto-renewals open a fresh quota window.
- 🌍 Outbound insight — egress metadata (IP + country) per outbound, a real-delay connection test measured on a warm connection, and a
targetStrategyfield in the outbound editor. - ⚖️ Routing & balancers — balancer-to-balancer fallback, a default outbound in basic routing, encrypted DNS presets, and private-IP
dns.serversallowed past thegeoip:privateblock rule. - 🔎 Panel QoL — text search on the inbound list and node selects, column sorting, per-client realtime speed, bulk-adding hosts to multiple inbounds, and WireGuard export split into config and links tabs.
ℹ️ Heads-up: MTProto inbounds are now natively multi-client — legacy single-secret inbounds migrate automatically to the clients model, and
tg://deep links no longer carry the remark fragment. The bundled core was bumped to xray-core v26.7.11, and the Final Mask + REALITY combination is now rejected (it crashes Xray-core). A database migration also repairs overflowed traffic counters and drops the legacy UNIQUE constraint on inbound ports.
🆕 New
- feat(mtproto): adopt mtg-multi and make MTProto inbounds multi-client
- feat(mtproto): per-client ad-tags, management-API auth, and secret sync
- feat(mtproto): enforce per-client quota & expiry via mtg-multi limits
- feat(db): import SQLite migration dumps through the PostgreSQL panel restore
- feat(server): sniff SQLite panel restore uploads and keep the fallback on failure
- feat(db): add pgclient command to install or upgrade PostgreSQL client tools
- feat(outbound): add outbound egress metadata (IP + country) (#5886) @isultanov99
- feat(outbound): add real-delay connection test mode
- feat(balancer): add balancer-to-balancer fallback support (#5586) @Ssentiago
- feat(xray): default outbound in basic routing (#5815) @rqzbeh
- feat(dns): add encrypted DNS presets (#5837) @rqzbeh
- feat(hosts): bulk-add multiple hosts to multiple inbounds (#5677) @AmirRnz
- feat(ui): per-client realtime speed (#5687) @NikanZeyaei
- feat(frontend): add text search to the inbound list
- feat(inbounds): add column sorting to the inbounds table (#5661) @isultanov99
- feat(frontend): add text search to node select components
- feat(frontend): treat WireGuard inbounds as multi-user in client actions
- feat(frontend): split WireGuard inbound export into config and links tabs
- feat(wireguard): make client allowedIPs editable with validation
- feat(reality): derive a stable per-client spiderX for shared links
- feat(web): broadcast delta client stats above a snapshot threshold
- feat(sub): show the announcement on the subscription info page
- feat(sub): serve the HTML info page for browser requests on JSON and Clash URLs
- feat(settings): let users clear stored secrets from the UI
- feat(clients): hide disabled inbounds in the client form selector
- feat(frontend): show client group in the client info modal
- feat(frontend): add targetStrategy field to the outbound editor
- feat(clients): clarify which protocols use the password field (#5809) @ecgang
- feat(tgbot): register usage, inbound, restart and clearall in the bot command menu
- feat(tgbot): show inbound remark alongside email in the online clients list
- feat(tgbot): include hostname in backup and ban-log messages
⚡ Update & improvement
- feat(xray): update xray-core to v26.7.11 and adapt panel
- chore(mtproto): bump the mtg-multi binary pin to v1.14.0
- refactor(mtproto): manage ad-tags per client only
- Frontend dev tooling (Husky, lint-staged, MSW, Storybook) + full React Hook Form migration (#5859)
- refactor(frontend): replace axios with the native Fetch API
- refactor(frontend): replace recharts with uPlot for charts
- test(scale): cover traffic poll, ws payloads, ip-limit job, sub and xray config at 500k
- refactor: modernize Go with strings.SplitSeq and maps.Copy
- refactor: use the built-in max/min to simplify the code (#5751) @alaningtrump
- chore: add golangci-lint tasks and force LF on Go files
- docs: vendor the documentation site into the monorepo
- docs: move architecture map into docs/ and refresh it against the live tree
- docs(settings): clarify Sub Port/Sub Domain double as subscription-link fallback (#5721) @volov-de
- docs: update the env vars example file (#5678) @nebulosa2007
- ci(claude-bot): auto-fix trusted PRs and easy issue bugs
- ci(claude-bot): gate write capability to trusted actors
- chore(frontend): bump version and deps
- chore(deps): bump google.golang.org/grpc to 1.82.0 (#5729)
- chore(deps): bump golang.org/x/text to 0.40.0 (#5872)
🐞 Bug fixed
- fix(node): stop client edits from tearing down node inbounds and harden reconcile fingerprints
- fix(node): stop Postgres deadlocks and deleted-client resurrection in node sync
- fix(node): never sweep a node's inbounds before their first adoption
- fix(node): adopt a node inbound's host overrides into the master
- fix(node): stop one rejected inbound from starving a node's traffic sync
- fix(node): fully delete clients on nodes instead of only detaching them
- fix(node): start a fresh quota window when a node auto-renews a client
- fix(node): stop force-restarting a node's Xray when its clients auto-disable
- fix(node): show the activated first-use deadline on the Clients page
- fix(inbound): scope port-conflict check to the stored node on update (#5833) @yukh975
- fix(panel): use the hosting node address for WireGuard client configs (#5679) @STRENCH0
- fix(inbound): reject finalmask + REALITY combo (crashes Xray-core) (#5861) @mrnickson-hue
- fix(inbounds): apply runtime changes after the DB commit (#5768) @n0ctal
- fix(xray): reconcile client auto-disable through the API instead of a forced restart
- fix(xray): force full restart for inbounds with a VLESS reverse client
- fix(inbounds): apply the legacy xhttp session-key migration when editing
- fix(xhttp): stop XMUX maxConcurrency from reverting on save
- fix(ui): make the Happy Eyeballs toggle produce a config xray actually enables
- fix(ui): keep an explicit zero happy-eyeballs delay across the round trip
- fix(web): sync the VLESS generate-key dropdown with the encryption field
- fix(routing): allow dns.servers on private IPs past the geoip:private block rule (#5774) @volov-de
- fix(balancers): keep mixed strategies on one observer (#5674) @nima1024m
- fix(logs): limit Xray log growth (#5840) @rqzbeh
- fix(client): stop duplicate client entries accumulating in inbound settings
- fix(clients): reuse stored credentials when re-adding an existing identity
- fix(clients): rename client record atomically with inbound settings
- fix(clients): finish deleting from every inbound when one fails
- fix(client): clean node_client_traffics rows when deleting a client
- fix(clients): parse only settings.clients across protocols (#5855) @n0ctal
- fix(clients): surface bulk-reset auto-enable failures (#5763) @n0ctal
- fix(clients): include Telegram ID in client list search (#5888) @mrnickson-hue
- fix(frontend): show zero client count for mtproto and wireguard inbounds
- fix(groups): keep group traffic totals stable across client resets and deletes
- fix(mtproto): stop dropping connections on client/inbound edits; add live updates + ad-tag (#5838)
- fix(mtproto): stop persisting a vestigial inbound-level secret
- fix(mtproto): drop the remark fragment from tg proxy deep links
- fix(sub): include native WireGuard clients in Clash and JSON subscriptions (#5676) @STRENCH0
- fix(wireguard): build peers in GenXrayInboundConfig so node reconcile keeps clients (#5684) @STRENCH0
- fix(sub): carry a host's Final Mask into raw share links
- fix(sub): use configured spiderX instead of always randomizing
- fix(sub): default https:// for scheme-less support and profile URLs
- fix(sub): resolve subscription clients and stats from normalized tables
- fix(sub): apply host Allow Insecure to Hysteria2 subscription links (#5866)
- fix(link): strip query and trailing slash when parsing ss:// port (#5895) @Djjanks
- fix(link): sanitize numeric quicParams taken from a share link's fm= param
- fix(link): reject non-finite and clamp out-of-range quicParams from fm=
- fix(database): make cross-db migration lossless, transactional, and pre-checked
- fix(db): clamp traffic counters below int64 max and repair overflowed rows
- fix(database): drop the legacy UNIQUE constraint on inbounds.port
- fix(db): probe dump readability before PostgreSQL import
- fix(database): stop noisy per-startup errors in the Postgres server log
- fix(job): batch ip-limit per-email lookups and persistence
- fix(job): gate ip-limit scan on clients.limit_ip instead of parsing all settings
- fix(iplimit): ban a dead connection once instead of every scan
- fix(traffic): disable depleted clients by id instead of a second full scan
- fix(traffic): persist delayed-start expiry only for converted clients
- fix(ldap): convert default total GB to bytes when auto-creating clients (#5854) @mrnickson-hue
- fix(ldap): attach auto-created clients to every configured inbound tag
- fix(tgbot): find clients by tgId regardless of settings JSON formatting
- fix(outbound): measure HTTP test delay on a warm connection
- fix(settings): repair legacy path settings that block every settings save
- fix(ui): align the subUpdates limit with the backend and show the range
- fix(frontend): stop group modals clearing selection on background refetch
- fix(web): opt panel pages out of Cloudflare Rocket Loader
- fix(api): preserve 64-bit integer schema formats (#5908) @sanmaxdev
- fix: make all self-managed file downloads/installs atomic, with real completion status (#5711) @nima1024m
- fix(docker): start crond and persist acme.sh state so cert renewal works
- fix(script): confirm auto-detected public IPv4 before issuing IP certificate
- fix(scripts): pass --force to acme.sh --installcert so it survives sudo
- fix(script): stop logging an error when Enter accepts the default ACME port
- fix(script): rename the Xray binary to xray-linux-arm32 on 32-bit ARM
- fix(script): make local PostgreSQL and fail2ban setup work on RHEL-family distros
- fix(script): stop running full system upgrades via pacman -Syu on Arch
- fix(update): avoid full dnf system upgrade (#5717) @v-2841
- fix(scripts): avoid rpm package upgrades before installs (#5750) @v-2841
- fix(script): correct hardcoded menu option numbers in x-ui.sh (#5787) @lxk955
Reports
New Contributors
- @isultanov99 made their first contribution in #5661
- @AmirRnz made their first contribution in #5677
- @STRENCH0 made their first contribution in #5679
- @v-2841 made their first contribution in #5717
- @alaningtrump made their first contribution in #5751
- @lxk955 made their first contribution in #5787
- @ecgang made their first contribution in #5809
- @yukh975 made their first contribution in #5833
- @mrnickson-hue made their first contribution in #5854
- @Djjanks made their first contribution in #5895
- @sanmaxdev made their first contribution in #5908
Full Changelog: v3.4.2...v3.5.0