Web panel for managing XKeen/Xray on Keenetic routers.
Supports both the jameszeroX fork (XKeen 2.x, S05xkeen) and the original
Skrill0 layout (1.x, S24xray) — the layout, the active proxy core
(Xray/Mihomo) and the proxying mode are detected at startup.
- Subscription management — add URL, refresh server list
- Server selection — switch active server, with the config validated
(
xkeen -xtest) and rolled back before anything restarts - Balancer pool — build a
leastPingpool out of the subscription so Xray picks the node itself and leaves a dead one without a restart - XKeen settings — edit
xkeen.json, proxying ports and IP exclusions - Latency check — real-time per-server ping streaming (SSE)
- Watchdog — automatic connection monitoring and failover to next server
- Real-time logs — via Server-Sent Events, no polling
- Authentication — JWT + TOTP (two-factor)
Connect to your router via SSH and run:
curl -sL https://raw.githubusercontent.com/Dearonski/xkeen-panel/main/install.sh | shIf architecture is not detected automatically:
# Keenetic Giga, Ultra, Peak and other ARM64 models
curl -sL https://raw.githubusercontent.com/Dearonski/xkeen-panel/main/install.sh | sh -s aarch64
# Keenetic with MIPS (older models)
curl -sL https://raw.githubusercontent.com/Dearonski/xkeen-panel/main/install.sh | sh -s mipsel# 1. Download binary (choose your architecture)
curl -L -o /opt/sbin/xkeen-panel \
https://github.com/Dearonski/xkeen-panel/releases/latest/download/xkeen-panel-aarch64
chmod +x /opt/sbin/xkeen-panel
# 2. Create directories
mkdir -p /opt/etc/xkeen-panel/data
# 3. Create config
cat > /opt/etc/xkeen-panel/config.yaml << 'EOF'
port: 3000
data_dir: /opt/etc/xkeen-panel/data
xkeen_path: /opt/sbin/xkeen
outbounds_file: /opt/etc/xray/configs/04_outbounds.json
check_interval: 120
check_url: https://www.google.com
max_fails: 3
log_file: /opt/var/log/xkeen-panel.log
EOF
# 4. Create log directory
mkdir -p /opt/var/log
# 5. Create init script for autostart
cat > /opt/etc/init.d/S99xkeen-panel << 'EOF'
#!/bin/sh
ENABLED=yes
PROCS="xkeen-panel"
ARGS="-config /opt/etc/xkeen-panel/config.yaml"
DESC="XKeen Panel"
PREARGS=""
. /opt/etc/init.d/rc.func
EOF
chmod +x /opt/etc/init.d/S99xkeen-panel
# 6. Start
/opt/etc/init.d/S99xkeen-panel start- Open
http://<router IP>:3000in your browser - Create an account (username + password)
- Scan the QR code for TOTP (Google Authenticator, Aegis, etc.)
- Log in with username, password and code from the app
# Start / stop / restart
/opt/etc/init.d/S99xkeen-panel start
/opt/etc/init.d/S99xkeen-panel stop
/opt/etc/init.d/S99xkeen-panel restart
# Logs
tail -f /opt/var/log/xkeen-panel.log# Stop the panel
/opt/etc/init.d/S99xkeen-panel stop
# Download new version
curl -L -o /opt/sbin/xkeen-panel \
https://github.com/Dearonski/xkeen-panel/releases/latest/download/xkeen-panel-aarch64
chmod +x /opt/sbin/xkeen-panel
# Start
/opt/etc/init.d/S99xkeen-panel startOr re-run install.sh — it won't overwrite your config.
File: /opt/etc/xkeen-panel/config.yaml
| Parameter | Default | Description |
|---|---|---|
port |
3000 |
Web panel port |
data_dir |
/opt/etc/xkeen-panel/data |
Data directory |
xkeen_path |
/opt/sbin/xkeen |
Path to XKeen binary |
outbounds_file |
/opt/etc/xray/configs/04_outbounds.json |
Xray outbounds config |
xray_config_dir |
auto | Xray config directory |
routing_file |
auto | Routing config; auto-detected among the config files |
mihomo_config |
auto | /opt/etc/mihomo/config.yaml |
xkeen_json |
auto | /opt/etc/xkeen/xkeen.json |
xray_api_addr |
127.0.0.1:10085 |
Xray gRPC API, used to pin a pool node |
check_interval |
120 |
Watchdog check interval (seconds) |
check_url |
https://www.google.com |
URL for connection check |
max_fails |
3 |
Consecutive failures before server switch |
Paths left empty are detected on startup, so an install that moved its configs only needs the ones that differ.
- The outbound shape is preserved. Xray accepts both
settings.vnext[]and the newer flatsettings.{address,port,id}; whichever your config uses is kept, and so israwas the spelling of thetcptransport. streamSettings.sockoptsurvives. XKeen validatessockopt.markon every real outbound — strict PBR refuses to start without it — so the panel merges its changes over the existing outbound instead of replacing it.- Comments are read, not written. Xray configs legally carry
//and/* */; the panel parses through them, but a file it writes comes back without them. The previous content is always kept as.baknext to the file. - Every write is validated. After writing,
xkeen -xtest(or-mtest) runs the core's own parser; a rejected config is restored from the backup and the error is shown instead of restarting into a broken state.
# Requirements
go 1.25+
node 20+
# Build for all architectures
make build-all
# Or specific target
make build-arm64 # Keenetic Giga/Ultra/Peak
make build-mipsel # Keenetic older models
# Output: build/xkeen-panel-{aarch64,mipsel}- Backend: Go, chi router, JWT, TOTP, SSE
- Frontend: React 19, Vite, Tailwind CSS 4, TanStack Query
- Frontend is embedded into the binary via
go:embed