Skip to content

Releases: Dearonski/xkeen-panel

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 18:00

Features

  • XKeen 2.x support — the panel now detects the layout it's running on instead of trusting init_script from config.yaml: generation from the init script's name (S05xkeen → 2.x, S24xray → 1.x), the active core from name_client=, proxying mode from the netfilter hook, and the version from 01_info_variable.sh. It matters because the jameszeroX fork dropped xkeen -u and an unknown flag still exits 0 — so the update button reported success while doing nothing. Update is now /selftest (-xtest/-mtest, the core's own validator), every dispatcher call sets XKEEN_FOREGROUND=1 so 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 stale S24xray in 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.balancers pool gives Xray one sub-N outbound per subscription server and lets it walk away from a dead node on its own; selecting a server issues xray api bo instead 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 answer 403 mid-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 generates proxies and proxy-groups in the YAML config: reality becomes tls: true plus reality-opts, routing-mark is carried onto every generated proxy (XKeen checks it per proxy when Entware proxying is on), and the file is parsed as a yaml.Node so 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.json and the port / IP lists are editable from the panel — proxying ports, port exclusions, IP exclusions and gh_proxy (what makes component updates work where GitHub is blocked) previously required ssh, since xkeen -ap/-dp is interactive. Validation mirrors what XKeen itself enforces: an xkeen.policy entry without a name is rejected outright (it makes XKeen refuse to start), port entries take a single port or a 596:599 range, 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_204 perfectly well while Telegram and CloudFront turn it away. The watchdog now probes real services (Cloudflare trace, Telegram, YouTube, SoundCloud) through the active node with GET and a 15s budget. A 403 or CDN 503 counts as blocked; 404 and origin 500s do not, and network errors are retried once. health_quorum services 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_countries was only ever consulted on the single-outbound path. SelectPoolServers now filters by country (name, manual override, GeoIP), ranks by TCP latency and keeps pool_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.probeInterval scales 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: runSubscriptionRefresh called ApplyServer, which refuses to run in pool mode, and the only sync path was handlePoolFailover — six minutes of dead VPN, and only with the watchdog on. RefreshPool now runs on every refresh, automatic or manual, and pushes the new set into the running core with xray api ado/rmo, so a rotated server costs no downtime. Default subscription_refresh_interval drops 6h → 30m now that the update is free

Bug Fixes

  • A config rewrite no longer breaks the corebuildOutboundFromURI rebuilt the outbound from scratch and only spoke the settings.vnext[] dialect, so writing to a config in Xray's newer flat form converted it back and left it unreadable; worse, the rebuild dropped streamSettings.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 so sockopt, mux, sendThrough and proxySettings survive, and ApplyServer runs xkeen -xtest after the write and restores the .bak when the core's own validator rejects it — xkeen -restart returns 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 — PoolMatchesSubscription compared the ordered sequence, so millisecond jitter in latency ranking reported a drift and triggered a full rebuild every ~30 minutes, and applyPoolLive treated 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:10085 because 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.listen makes 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 from xkeen -sb on is 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 EnsurePinned seeing 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 outagesHEAD probes 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/xkeen logs where you can read it — everything the package logged went to stdout, which the Entware init script discards: the RefreshPool restart 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 and install.sh now leave the layout to detection and document what surprises people: outbound shape and raw are preserved as written, sockopt.mark survives a rewrite, comments are read but not written back, .bak is 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.sh stay Russian, since the router owner reads those
  • make deploy-ssh no longer copies the repo's config.yaml over the router's — it wiped webauthn_rp_id and took passkey login down until restored by hand

Full Changelog: v1.1.0...v1.2.0

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 26 Jun 11:10

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/BY by default) by reading Xray's own geoip.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 via watchdog_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_countries list 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 run string in busybox ps output, which doesn't match on routers where ps prints the command differently, so the panel wrongly reported Xray as down. It now detects the process via /proc, independent of ps formatting.
  • 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/webauthn for 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

Choose a tag to compare

@github-actions github-actions released this 20 Feb 22:20

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 20 Feb 21:16