Releases: Dearonski/xkeen-panel
Release list
v1.2.0
Features
-
XKeen 2.x support — the panel now detects the layout it's running on instead of trusting
init_scriptfromconfig.yaml: generation from the init script's name (S05xkeen→ 2.x,S24xray→ 1.x), the active core fromname_client=, proxying mode from the netfilter hook, and the version from01_info_variable.sh. It matters because the jameszeroX fork droppedxkeen -uand an unknown flag still exits0— so the update button reported success while doing nothing. Update is now/selftest(-xtest/-mtest, the core's own validator), every dispatcher call setsXKEEN_FOREGROUND=1so the panel waits for the real restart instead of a backgrounded re-exec, and process detection follows the active core through/opt/var/run/<core>.pid. A staleS24xrayin an old config is ignored rather than obeyed -
Xray balancer pool — failover no longer means rewriting the config and restarting the core, which drops every live connection. A
routing.balancerspool gives Xray onesub-Noutbound per subscription server and lets it walk away from a dead node on its own; selecting a server issuesxray api boinstead of editing files, so switching is instant. Traffic is pinned to a single node so your exit IP stays stable — per-connection balancing is incompatible with anything IP-bound, and CDNs answer403mid-session when the source keeps moving. Plain Xray-core, no Beta channel needed -
Mihomo core support — on XKeen 2.x running
xkeen -mihomo, selecting a server used to silently edit an Xray config the core never reads. The panel now generatesproxiesandproxy-groupsin the YAML config: reality becomestls: trueplusreality-opts,routing-markis carried onto every generated proxy (XKeen checks it per proxy when Entware proxying is on), and the file is parsed as ayaml.Nodeso comments, key order and unrecognised sections survive a write. The watchdog stops rewriting configs entirely here — the url-test proxy-group does the switching inside the core -
xkeen.jsonand the port / IP lists are editable from the panel — proxying ports, port exclusions, IP exclusions andgh_proxy(what makes component updates work where GitHub is blocked) previously required ssh, sincexkeen -ap/-dpis interactive. Validation mirrors what XKeen itself enforces: anxkeen.policyentry without a name is rejected outright (it makes XKeen refuse to start), port entries take a single port or a596:599range, IP entries require a prefix length. Comments and blank lines pass through untouched — the shipped lists are commented examples you uncomment -
Health probing through the active node — connectivity checks can't see a whole class of failure: an exit whose IP a CDN blocks answers
generate_204perfectly well while Telegram and CloudFront turn it away. The watchdog now probes real services (Cloudflare trace, Telegram, YouTube, SoundCloud) through the active node withGETand a 15s budget. A403or CDN503counts as blocked;404and origin500s do not, and network errors are retried once.health_quorumservices failing (default 2) condemns the node and pins the next best one, capped at one rotation per 30 minutes so a service that turns away every datacentre IP can't walk the panel through the whole pool
Improvements
-
The pool is the best N of your subscription, not all of it — a real subscription built an 81-node pool, four of them Russian, because
auto_switch_avoid_countrieswas only ever consulted on the single-outbound path.SelectPoolServersnow filters by country (name, manual override, GeoIP), ranks by TCP latency and keepspool_max_nodes(default 10). Unreachable servers are kept as a tail rather than dropped — a failed probe may just mean your own uplink is down, which is exactly when a pool gets rebuilt, and an empty pool is worse than a stale one.observatory.probeIntervalscales with pool size (1m / 3m / 5m), since observatory probes each node separately -
Subscription changes reach the pool — a pool is generated from subscription URIs, so a rotated server left it pointing at an endpoint that no longer answers, and with every node stale the balancer had no healthy target left. Nothing resynced it:
runSubscriptionRefreshcalledApplyServer, which refuses to run in pool mode, and the only sync path washandlePoolFailover— six minutes of dead VPN, and only with the watchdog on.RefreshPoolnow runs on every refresh, automatic or manual, and pushes the new set into the running core withxray api ado/rmo, so a rotated server costs no downtime. Defaultsubscription_refresh_intervaldrops 6h → 30m now that the update is free
Bug Fixes
-
A config rewrite no longer breaks the core —
buildOutboundFromURIrebuilt the outbound from scratch and only spoke thesettings.vnext[]dialect, so writing to a config in Xray's newer flat form converted it back and left it unreadable; worse, the rebuild droppedstreamSettings.sockopt, which XKeen validates on every real outbound, so the panel could leave XKeen unable to start. The shape in use is detected and kept, the generated outbound is merged over the existing one sosockopt,mux,sendThroughandproxySettingssurvive, andApplyServerrunsxkeen -xtestafter the write and restores the.bakwhen the core's own validator rejects it —xkeen -restartreturns long before a broken core fails, so the panel used to report success. Writes land via temp file plus rename, and a config with more than one proxy outbound is refused: that's a balancer pool, and replacing its first member silently breaks it -
Telegram no longer dies at random — the log read
Пул обновлён: +0, -0, but that line was the symptom: the tag set was unchanged while 9 of 10 tags had been reassigned to different servers. Two mistakes compounded —PoolMatchesSubscriptioncompared the ordered sequence, so millisecond jitter in latency ranking reported a drift and triggered a full rebuild every ~30 minutes, andapplyPoolLivetreated every surviving tag as "replaced", removing and re-adding all ten outbounds in the running core. Removing an outbound closes the connections riding on it, and a long-lived MTProto session doesn't survive that. Membership is now compared as a set, a server already in the pool keeps its tag, live members are preferred over equally good newcomers so jitter can't evict a working node, and only tags whose endpoint actually changed are replaced -
Xray binds the API port itself — pinning failed with
failed to dial 127.0.0.1:10085because nothing was listening: the api block reached the gRPC service through a dokodemo-door inbound plus a routing rule, and on Xray 26.7 that combination binds no port.api.listenmakes the core bind directly, which also drops the inbound XKeen would otherwise scan when deriving transparent proxy ports and removes the dependency on rule ordering across merged config files. Existing pools are migrated in place on startup — a pool whose subscription hasn't drifted never reaches the refresh path, so the old block would keep failing to pin indefinitely. A foreign api block fromxkeen -sb onis still left alone -
The pin survives a core restart, and is verified by endpoint — a balancer override lives only in Xray's memory and has no TTL, so every restart silently reverted the pool to per-connection selection. It's re-applied when the core loses it, and a pool built from the UI is pinned as soon as the core is back up rather than at the next watchdog tick. The pin is also stored as an endpoint, not just a tag: a tag is a slot, not an identity, so a refresh that kept the tag but put a different server behind it left
EnsurePinnedseeing what it expected and staying quiet. Manual selection in pool mode is fixed too — it mapped a subscription index straight onto a pool index, wrong ever since the pool became the best N -
Health checks stop hiding outages —
HEADprobes reported SoundCloud unreachable on an exit that was working fine (plenty of sites answer it slowly or not at all), one service was probed per round so each was sampled every 40 minutes and two consecutive failures took over an hour to establish, and any single success wiped a failure count mid-outage. All services are probed per round now, failure counts drain one step at a time, and the quorum is configurable instead of hard-coded -
internal/xkeenlogs where you can read it — everything the package logged went to stdout, which the Entware init script discards: theRefreshPoolrestart fallback, every core restart, the "no server answered" path, suppressed rotations and outright pinning failures were all invisible after the fact. They go to the panel log now
Docs
- README describes the 2.x layout — it still pointed at the abandoned Jenya-XKeen repo and told you to set
init_script: /opt/etc/init.d/S24xray, a path that doesn't exist on the fork. Both README andinstall.shnow leave the layout to detection and document what surprises people: outbound shape andraware preserved as written,sockopt.marksurvives a rewrite, comments are read but not written back,.bakis kept, and every write is checked with the core's own parser before a restart
Internal / Dependencies
- Remaining Russian code comments translated to English; user-facing text (UI strings, log lines, errors) and the commentary in
config.yaml/install.shstay Russian, since the router owner reads those make deploy-sshno longer copies the repo'sconfig.yamlover the router's — it wipedwebauthn_rp_idand took passkey login down until restored by hand
Full Changelog: v1.1.0...v1.2.0
v1.1.0
Features
- Geo-aware automatic switching — the watchdog no longer just fails over to the next server in the list: it TCP-probes the subscription and moves you to the lowest-latency live server, and can switch proactively when the active server's ping stays high (default >
1000ms), not only on a full outage. It avoids servers in blocked countries (RU/BYby default) by reading Xray's owngeoip.dat, so an automatic switch can never drop you onto a Russian exit, and it keeps a short TTL blacklist of the server it just left so it can't flap back. Enabled out of the box viawatchdog_auto_start. - Passkey (WebAuthn) login — sign in with Face ID / Touch ID / a security key, passwordless and with no TOTP code, alongside the existing username + password + TOTP. Register a passkey from the dashboard while logged in; user verification is required, and it works both on your local domain and through the KeenDNS cloud port (
:8443). The implementation went through a dedicated security review. - Subscription auto-update — the panel periodically re-downloads your subscription (
subscription_refresh_interval, default 6h) to keep the server list fresh. It restarts Xray only if your active server actually changed, and routes any forced replacement through the same geo filter so a refresh can't silently move you onto a blocked-country server. - Per-server country flags — servers now show a country flag detected from their name (flag emoji / keywords), with a manual override for cryptic names; the same
auto_switch_avoid_countrieslist drives both the flag and the auto-switch safety check.
Improvements
- Lighter on the router — latency probing is now concurrency-bounded instead of spawning one goroutine per server (which could exhaust RAM on a large subscription), the Xray-status check is cached, and release builds use
-trimpath. - Infinite scroll in the server list — replaces the "show more" button, so long subscriptions load as you scroll.
- Sturdier live updates — the SSE event stream reconnects with exponential backoff instead of hammering the router every 3 seconds, and the watchdog log file is rotated so it can't grow without bound.
Bug Fixes
- Xray no longer shows "offline" while it's running — the status check matched the literal
xray runstring inbusybox psoutput, which doesn't match on routers wherepsprints the command differently, so the panel wrongly reported Xray as down. It now detects the process via/proc, independent ofpsformatting. - Refresh keeps you on your server — refreshing the subscription matched the active server by list position, so a provider reordering or removing servers could silently switch you onto a different one. The active server is now tracked by its link identity and re-found after every refresh.
Internal / Dependencies
- Adds
github.com/go-webauthn/webauthnfor passkey support. - New test suite across all packages (53 tests, race-clean) plus three rounds of adversarial code review (autopilot, passkey, and the follow-up fixes) hardening the release.
Full Changelog: v1.0.1...v1.1.0
v1.0.1
Full Changelog: https://github.com/Dearonski/xkeen-panel/commits/v1.0.1
v1.0.0
Full Changelog: https://github.com/Dearonski/xkeen-panel/commits/v1.0.0