Changelog
August 28, 2026 · View on GitHub
All notable user-facing changes should be recorded here.
Unreleased
-
Added subscribed rule sets for sing-box and mihomo (issue #3): paste a community rule-list URL, pick an exit, and the traffic it describes leaves through that exit. Both cores use their native path, so refreshing never restarts anything — mihomo gets a
rule-providersentry it refreshes on its own interval, and sing-box gets a localsourcerule set, which it reloads by itself when the file changes (1.10+). Xray has no rule-set concept, so its rules are inlined intoconfig.jsoninstead, which is also the only core that has to be restarted when a list changes. -
Added
manager.sh --ruleset-updateand a daily timer that keeps the lists current. The refresh cost differs per core and the timer respects that: mihomo needs nothing (the core refreshes its own provider), sing-box only needs its rule-set file rewritten, and Xray is rebuilt and restarted — but only when the content actually changed and Xray really has that rule set bound, so a no-op fetch never drops anyone's connections. The timer carries a randomized delay of up to an hour, because every install pulling the same community repository at the same second is not a nice thing to do. -
Rule lists are parsed once in a shared layer rather than trusted blindly, and the preflight report is shown before anything is applied. Client-only types (
PROCESS-NAME,USER-AGENT, …) andIP-ASN, which only mihomo understands, are counted and reported as dropped instead of being silently skipped — a rule set that half-applies is the worst kind to debug. Malformed entries are filtered out too, since one bad line makes a core reject the whole config. -
Both generated forms deliberately split domains and IPs into separate rules. In sing-box a rule's fields are ANDed, so
domainandip_cidrwritten into one headless rule would mean "domain AND IP", which never matches;domain,domain_suffixanddomain_keywordshare one matcher and are ORed, so those do belong together. The same trap applies to Xray and is what the second step has to get right. -
Rule sets are capped at 5000 rules, and the reason is stated in the UI: a full routing table belongs on the client, where domestic traffic is decided before it ever crosses to the VPS, and mihomo matches
classicalrules linearly, which costs CPU under load. Server-side rule sets are for choosing the exit — OpenAI, Netflix and the like. -
Updating compares the rule count against the stored one first and refuses a change that shrinks by more than half or grows more than fivefold. Injecting a third-party URL into routing hands the "which traffic leaves where" decision to whoever maintains that repository, so a wild swing is treated as a signal to stop and ask rather than something to apply. Only https URLs are accepted.
-
Fixed Xray and sing-box installs failing outright: the download URL came out as
.../download/[step] Fetching the latest version...\nv26.3.27/Xray-linux-64.zip. The version resolvers return the tag on stdout while also reporting progress withlog_step, andlog_info/log_warn/log_step/log_okall wrote to stdout, sotag=$(_xray_resolve_tag ...)swallowed the log line along with the tag. All log helpers now write to stderr, the waylog_erroralready did — logs are diagnostics, not return values, so this removes the whole class of bug rather than patching the two call sites. mihomo was never affected: it resolves its tag inline instead of through a command substitution. -
Fixed the Telegram bind token being generated empty, which silently disabled the check it was supposed to enforce.
tr -dc 'A-Za-z0-9' < /dev/urandomexits with "Illegal byte sequence" in a UTF-8 locale unlessLC_ALL=Cis set, and the generator was missing it, so/token <port>could store an empty token — while the bind path treats an empty stored token as "no token required" and lets anyone bind to that port. The generator now reuses the hardenedrand_str, an empty token is refused instead of stored, and the failure is reported rather than passing silently. -
Hardened
rand_str, which produces every password, PSK and camouflage path in the project: it now verifies it actually got the requested length, falls back toodplus bash string operations (deliberately nottr, which is what failed above), and errors out instead of returning an empty string, so a node can never be created with an empty password. -
Fixed two
set -o pipefailhazards whereheadexiting early leaves the upstream process with SIGPIPE and the pipeline returning 141: the Telegram token generator (measured non-zero 20 times out of 20, which aborted the bot daemon through a bare assignment underset -e) and the nginx dynamic-module lookup. -
Added a VPNGate residential-IP exit, shared by Xray, sing-box, and mihomo. It fetches the public VPNGate server list, classifies each candidate IP through ip-api's batch endpoint, and keeps the ones that are actually home broadband — datacenter nodes and VPNGate's own relays at
219.100.37.xare demoted, because those are hosted, widely known, and already blocked by the services people want to unlock. Theproxyflag is deliberately not used for that verdict (nearly every VPNGate node is in public proxy databases, so it would reject the entire list); it is shown as a risk hint instead. Candidates are tried in order until one connects and its exit IP can actually be probed. The exit country is always chosen by the operator from a numbered list of the countries that actually have nodes (with their node counts) rather than defaulted to, since the exit country is what decides which catalogue a streaming service shows. -
The residential tunnel is an openvpn client on its own
psmvg0device with--route-nopull, writing only into a dedicated routing table: the main table is never touched, so SSH and every existing node keep using the datacenter link. Cores reach it by marking connections (sockopt.markon Xray,routing_markon sing-box,routing-markon mihomo) — not by binding to the interface, which needsCAP_NET_RAWthat none of the three systemd units carry, whileSO_MARKonly needs theCAP_NET_ADMINthey already have. Because the outbound refers to a mark rather than a node, rotating to a different residential IP never rewrites any core config. When the tunnel is down the table keeps only a blackhole route, so matched traffic fails closed instead of leaking back out through the datacenter IP. -
Failover is automatic and stays inside the chosen country. VPNGate nodes are volunteers' own machines that can be powered off, renumbered, or unplugged at any moment, so a watchdog probes the tunnel every two minutes and, after two consecutive failures, moves to another node in the same country — drifting to a different country would silently change which regional catalogue the unlock rules reach. The dead node is put on a six-hour cooldown so rotation does not keep landing back on it, and when every same-country candidate is exhausted the list for that country is refetched before giving up. OpenVPN is also given
ping/ping-restart, because a peer that loses power never sends a FIN and would otherwise leave the tunnel hanging. -
VPNGate nodes still run AES-128-CBC with SHA-1 certificates, which OpenSSL 3 refuses by default, so generated configs relax
tls-cipheranddata-ciphers— but only on OpenVPN 2.5+, since those options do not exist on the 2.4 that ships with Debian 10 / Ubuntu 20.04. A first failure that looks like a legacy-algorithm rejection is retried once withproviders legacy default. Compression directives from the official config are kept as-is: they describe the framing the far end expects, so dropping them breaks the tunnel. -
Removed the shipped Reality camouflage defaults (
www.cloudflare.comfor Xray/sing-box/mihomo Reality,www.microsoft.comfor XHTTP reality-layer) on every server-side path, interactive andpsm nodealike. Cloudflare's own domain is the exact case the Xray docs warn about, and any hardcoded well-known domain can move behind a CDN later without the script noticing — while everyone sharing one default is itself a fingerprint. Xray now defaults to mapping-engine discovery, which finds same-ASN targets and so naturally avoids both CDN frontends and large content-distribution sources, matching upstream's "steal a certificate from your own ASN" best practice. sing-box and mihomo have no discovery path, so they require an explicit target instead. The non-interactive CLI fails with a clear message rather than silently building a node on a borrowed default. Client-side outbound SNI prompts are unchanged — they are not affected by this. -
Hardened the Nginx 443 SNI multiplexer against being used as a free relay. Unknown SNI used to fall through to the Reality inbound, and REALITY forwards every connection that fails authentication straight to its
dest— so anyone who could reach port 443 made the server pay for a round trip todest. Whendestsits on a multi-tenant CDN frontend, an attacker only had to put an arbitrary CDN-hosted hostname in the ClientHello to get a free tunnel into that entire CDN. Unknown SNI is now dropped without opening any upstream connection, and existing installs are migrated on the next Nginx sync. Mounted nodes are unaffected: each one already has an explicit SNI entry, and a prober using the node's real serverName still gets the full genuine REALITY fallback. -
Added multi-tenant CDN frontend detection for Reality camouflage targets across Xray, sing-box, and mihomo. The check probes the target's IP with unrelated SNIs and flags it when a valid certificate comes back for a hostname the target does not own — testing the property that actually matters (does this IP serve arbitrary tenants?) rather than tracking CDN IP ranges or ASNs. It warns rather than blocks, since the verdict can misfire and the risk is the operator's to weigh.
-
Added REALITY fallback rate limiting (
limitFallback*on Xray,limit-fallback-*on mihomo) for nodes whose target was flagged as a shared CDN frontend. It only touches connections that fail REALITY authentication; authenticated clients are unaffected. This is a backstop, not a fix: both parameters are per-connection, so an abuser who reconnects in a loop bypasses them. sing-box has no equivalent option, so nodes on it are warned when a flagged target is combined with direct-listen mode, which leaves no protection layer at all. -
Documented the CDN-fronted
destproblem in all four READMEs, which so far only existed in this changelog and in code comments. There is now a FAQ entry explaining why REALITY's unconditional fallback turns a CDN-fronteddestinto a free port forwarder, what the three layers of defence are, and where they do not hold (per-connectionlimitFallback, and sing-box direct-listen having no backstop at all). The 443 port-reuse diagram was also stale: it still showed unmatched SNI falling through to the decoy site, which stopped being true when unknown SNI became a blackhole. -
Added a Xray release channel choice at install/upgrade time (stable or preview). XTLS has marked every release since v26.3.27 as a pre-release, so the stable channel can lag months behind; preview installs the newest build. Stable stays the default and non-interactive runs always use it.
-
Added a per-node
minClientVersetting for Xray Reality nodes. Xray v26.4.13 and newer default it to 26.3.27, which makes the server refuse clients running an older core — including the cores bundled in many phone apps. The installer now warns about this when the preview channel is chosen, and the Reality menu lets you relax the threshold per node. Leaving it unset writes no field at all, so older Xray builds are unaffected. -
Fixed the Xray installer falling back to
v24.9.30, a release from 2024, and parameterised the fallback message so the version appears in one place only. -
Replaced
www.apple.comwith a neutral placeholder in the twelve Reality/XHTTP camouflage SNI and target prompts across all four languages. Xray now warns that targets containingapple,icloud, ormicrosoft(and.cn/.ru/.irsuffixes) raise the risk of the server IP being blocked, so the prompts no longer suggest one. The watchdog advice that names these domains as ones to avoid is unchanged. -
Bumped the mihomo fallback version to the current stable and parameterised its message. No mihomo compatibility changes were needed: all five listener types validate cleanly on both v1.19.27 and v1.19.30.
-
Fixed the sing-box installer falling back to
v1.14.0, a tag that does not exist upstream: when the GitHub API is unreachable the fallback is now the latest real stable tag, so the download no longer 404s. -
Added a sing-box release channel choice at install/upgrade time (stable or preview). The preview channel installs the latest beta/rc, which is currently the only way to reach protocols that have not been stabilised upstream yet, such as the Snell inbound (requires 1.14+, still in beta). Stable remains the default and non-interactive runs always use it.
-
Added a downgrade guard that refuses to silently install a sing-box build without Snell support while Snell nodes are configured, which would otherwise leave the config failing validation and the service unable to start.
-
Fixed the sing-box Snell v5 inbound writing
obfs_host, a field the Snell inbound does not accept (it belongs to the outbound). sing-box rejected the whole config withunknown field "obfs_host", so obfuscated v5 nodes could never be added. The value is still stored and used for the Surge/Clash client exports. -
Switched remote rule-set downloads to
http_clienton sing-box 1.14+, where the previousdownload_detouris deprecated (removed in 1.16). Installs older than 1.14 keepdownload_detour, since they do not recognise the new field. -
Fixed dual-stack WARP egress verification showing only the IPv6 exit IP: probing is now done per address family via IP-literal endpoints (IPv4 through 1.1.1.1, IPv6 through 2606:4700:4700::1111), so a dual-stack egress reports both its IPv4 and IPv6 WARP exit IPs.
-
Added WARP exit-IP verification to sing-box and mihomo, matching Xray: after setup the real egress IP and warp status are probed and shown per family, and a new "Show real WARP exit IP" menu item allows re-checking at any time.
-
mihomo WARP setup now registers a WARP identity when missing and supports choosing the egress address family (IPv4 / IPv6 / dual-stack), including an IPv6 tunnel address.
-
Extended Nginx 443 SNI multiplexing to sing-box and mihomo Reality/AnyTLS nodes, letting them share the public 443 port alongside Xray nodes.
-
Extended per-node traffic metering and quota enforcement (with Telegram quota warnings and automatic pause) to sing-box and mihomo nodes.
-
Added a core-services section to the Telegram daily health report covering every proxy core and Nginx.
-
Added
psm doctorwith human-readable and stable JSON reports for system, dependency, configuration, core-service, disk, and certificate health. -
Added a non-interactive
psm nodeCLI covering 14 Xray/sing-box/mihomo node types with CRUD, export, JSON input/output, default credential redaction, locking, port checks, and transactional rollback. -
Expanded Chinese and English README files with clearer positioning, target users, reasons to choose PSM, uninstall behavior, system write paths, FAQ, and project resource links.
-
Added GitHub Issue templates for installation failures, bug reports, and feature requests to make user feedback easier to triage.
-
Added
CHANGELOG.mdto track user-visible changes by commit/date until formal versioned releases are introduced.
2026-07-03
Fix uninstall cleanup and reinstall detection
- Fixed incomplete uninstall cleanup so PSM-owned cron entries, systemd units, firewall/Fail2ban wiring, shortcut commands, and the
/opt/psminstall directory can be removed cleanly. - Added cleanup coverage for additional PSM-managed components such as Snell, ss-rust, and PSM-managed Docker Compose apps.
- Added bootstrap repair detection for stale partial installs, so rerunning the one-line installer can rerun setup when
/opt/psmexists but the install is incomplete.
2026-07-02
Fix nginx/fail2ban/xray cross-distro bugs and cert rate-limit handling
- Improved cross-distro compatibility across Nginx, Fail2ban, Xray, Docker, system tuning, traffic accounting, and related modules.
- Hardened certificate handling to reduce accidental Let's Encrypt rate-limit risk during acme.sh removal and reinstall flows.
- Improved package/repository handling for RHEL-family systems, including EPEL-dependent dependencies.
- Fixed multiple service-management and config-generation edge cases across proxy, security, backup, and routing modules.
Initial commit: JQ's Proxy Stack Manager
- Added the initial PSM codebase with one-line install, interactive
psmmenu, update, uninstall, and shared utility framework. - Added multi-protocol management for Xray Reality / Vision / XHTTP / SS2022, Hysteria2, Snell, and shadowsocks-rust.
- Added Nginx SNI routing, SSL certificate management, Cloudflare DNS/DDNS/Tunnel/Access, Docker app management, traffic accounting, expiry management, Telegram Bot, backup/restore, and security hardening.
- Added Chinese and English README files, templates, and initial project structure.