Skip to content

DmitriyStroganov/LAS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

luci-app-singbox

License: GPL-2.0 OpenWrt sing-box Lua

A LuCI web interface for managing sing-box on OpenWrt. Focused on VLESS + REALITY — the most censorship-resistant proxy protocol available today — with selective traffic splitting, GeoSite rule presets, multi-server failover and real-time monitoring via the built-in Clash API.

Why VLESS+REALITY? REALITY disguises TLS traffic as a legitimate connection to a real, popular website (the "SNI target"), making it indistinguishable from normal HTTPS even under active DPI probing. Combined with xtls-rprx-vision flow it has near-zero overhead.


Table of contents


Features

  • VLESS + REALITY with xtls-rprx-vision flow, uTLS fingerprint emulation (chrome/firefox/safari/edge)
  • Selective traffic splitting — route only blocked services through VPN, keep local traffic direct
  • GeoSite presets — one-click rules for YouTube, Google, Telegram, Instagram, Twitter/X, Facebook, OpenAI, Spotify, Discord, Twitch, Netflix, GitHub, Microsoft, Apple, Amazon, WhatsApp and more
  • Multi-server with auto-failoverurltest group picks the lowest-latency server automatically
  • Real-time dashboard — running status, active server, uptime, TUN state, traffic, connections (powered by sing-box's built-in Clash API)
  • Subscription import — paste a URL, get all VLESS servers imported as separate UCI sections (base64-encoded vless:// links, deduplicated by name)
  • Custom routing rules — GeoSite / IP-CIDR / domain matching with action VPN / Direct / Block
  • Network-wide ad blocking via geosite-category-ads-all
  • Live log viewer — tail /tmp/sing-box.log with level filter, search and auto-refresh
  • Validation on apply — every config is checked with sing-box check before deployment, so a typo never takes the router offline

Screenshots

All screenshots are PNG, taken on OpenWrt 24.10 with the Cascade theme. They live in docs/screenshots/.

Tab What you see
Overview Running state, active server, uptime, TUN, live traffic, sortable connections table
Servers VLESS+REALITY server grid with live latency probes, subscription import
Routing Rule grid with per-row Type/Action editors, preset picker modal
Logs Live log tail with level filter, search, auto-refresh
Settings TUN / DNS / URL test / Clash API / Subscription config

Requirements

Component Version Notes
OpenWrt 22.03+ tested on 24.10
sing-box 1.11.0+ needed for rule_set and the new DNS resolver schema
kmod-tun any for the TUN interface
jq any URL-encoding of server tags in latency probes
Free overlay ~250 KB runtime config + logs live in /tmp

Installation

Option A — One-shot installer (recommended)

The installer pre-checks the router (OpenWrt version, sing-box availability in feeds, free overlay, kmod-tun), then downloads the latest .ipk and runs opkg install. Run it on the router itself:

# Latest release — auto-detected from GitHub
sh -c "$(wget -qO- https://github.com/DmitriyStroganov/LAS/releases/download/v1.0.9/install.sh)"

# Or pin a specific version:
sh install.sh --version 1.0.9

# Or check the environment without installing anything:
sh -c "$(wget -qO- https://github.com/DmitriyStroganov/LAS/releases/download/v1.0.9/install.sh)" -- --check

Pre-check output (typical healthy router):

== Environment check ==
✓ OpenWrt 24.10.2
✓ Architecture: mipsel_24kc
✓ kmod-tun loaded
✓ sing-box 1.13.13 already installed
✓ Free overlay: 27 MB
== Pre-check complete ==

If a check fails the installer explains what to fix (add a feed, free overlay space, upgrade OpenWrt) and exits without touching opkg.

Option B — Prebuilt .ipk (manual)

Download from Releases, copy to the router, then:

scp luci-app-singbox_*.ipk root@router:/tmp/
ssh root@router opkg install /tmp/luci-app-singbox_*.ipk

opkg will pull sing-box and the rest of the DEPENDS automatically from your configured feeds. The service is not enabled by default.

Option C — Build from source (OpenWrt buildroot)

# Inside your OpenWrt source tree
git clone https://github.com/DmitriyStroganov/LAS.git \
  feeds/luci/applications/luci-app-singbox

./scripts/feeds update luci
./scripts/feeds install luci-app-singbox

make package/luci-app-singbox/compile V=s
# Result: bin/packages/<arch>/luci/luci-app-singbox_*.ipk

Option D — Manual install (no build system)

Copy the contents of files/ onto the router preserving paths:

scp -r files/* root@router:/
ssh root@router '
  chmod +x /etc/init.d/sing-box \
           /etc/uci-defaults/singbox-migrate \
           /usr/share/singbox/*.sh \
           /usr/share/ucode/rpc/singbox.uc
  /etc/init.d/rpcd restart
'

If /etc/sing-box/config.json already exists from a previous raw sing-box setup, the singbox-migrate uci-defaults script imports the first REALITY outbound into UCI on next boot.


Quick start

  1. Open LuCI → Services → sing-box.
  2. Open the Servers tab. Replace the disabled example (example-server) with your own REALITY credentials, or import them from a subscription URL (see Adding a server below).
  3. Open the Routing tab. Enable the GeoSite presets you need (YouTube, Telegram, etc.). Leave the default action on Direct so only blocked services go through VPN.
  4. Hit Apply Config on the Overview tab. The script validates the generated config and restarts sing-box.
  5. Verify on the same tab: status dot turns green, traffic counters move, an active server appears next to "Active server".

Adding a server

Open Services → sing-box → Servers. Two paths:

A. Manual entry — click Add Server at the bottom of the grid. Fill in:

Field What to put Example
Name Any short identifier (latin, no spaces) frankfurt-1
Address Hostname or IP of your REALITY upstream 1.2.3.4
Port Usually 443 443
UUID Per-account UUID from your provider 00000000-0000-0000-0000-000000000000
Flow xtls-rprx-vision for modern servers, None for older xtls-rprx-vision
SNI The decoy hostname REALITY will masquerade as www.microsoft.com
uTLS Fingerprint Browser to impersonate at the TLS layer chrome
REALITY Public Key base64url key from your server config REPLACE_WITH_YOUR_REALITY_PUBLIC_KEY
REALITY Short ID 8-16 hex chars from your server config 0123456789abcdef

Click Save then Save & Apply at the bottom — this regenerates the config and restarts sing-box.

B. Subscription import — see next section.

Once at least two servers are enabled, an urltest group called auto is created automatically and picks the lowest-latency one. The Overview tab shows which one is currently active.

Importing servers from a subscription

If your provider gives you a subscription URL (a link that returns base64-encoded vless://... lines), do this:

  1. Servers tab → scroll to "Import Subscription" section.
  2. Paste the URL into Subscription URL.
  3. Click Import Servers. The backend fetches the URL, base64-decodes it, parses each vless:// link, and adds each as a new config server section.
  4. Already-known servers (matched by name) are skipped — re-importing the same URL is idempotent.
  5. The URL is persisted in UCI, so the next time you open Settings → Subscription → Subscription URL it will be pre-filled.
  6. A success notification lists the imported names; the page reloads and the new servers appear in the grid.

To enable automatic background refresh, flip Settings → Subscription → Auto Update and set an Update Interval (e.g. 24h). A cron-driven re-import will then keep the server list in sync with your provider.

Adding routing rules

Open Services → sing-box → Routing.

A. One-click GeoSite presets — click Add Rule (manual). A modal opens with two halves:

  • Top half — Quick add, GeoSite presets — scrollable list with checkboxes for common services (YouTube, Google, Instagram, Twitter/X, Facebook, Telegram, OpenAI, SoundCloud, Spotify, Discord, Twitch, Netflix, GitHub, Microsoft, Apple, Amazon, WhatsApp, plus an Ad Block entry that ships with Action = Block).
  • Bottom half — Custom rule — for when the preset list doesn't have what you need.

Tick the presets you want, optionally fill the custom-rule form, then click Add Selected / Custom. Already-installed presets are silently skipped (matched by match value), so the modal is also idempotent.

B. Inline edit — every rule row in the grid is editable in place:

  • Enabled — toggle without leaving the page.
  • TypeGeoSite, IP CIDR, or Domain.
  • Match — for GeoSite, the rule-set tag (e.g. geosite-youtube); for IP CIDR, one or more CIDRs space-separated (e.g. 91.108.0.0/16 149.154.160.0/20); for Domain, one or more domains (e.g. example.com .blocked.org).
  • ActionVPN (route through proxy), Direct (bypass proxy), Block (reject — uses the modern action:reject rule, not the deprecated block outbound).

Don't forget Save & Apply to regenerate the config and reload sing-box.

Default behaviour

Out of the box (right after install) the package ships with a sensible set of defaults so the most common case — "route blocked services through VPN, everything else direct" — works once you add a server:

  • Default action (general.final_outbound) = Direct. Anything that doesn't match a rule goes through the regular ISP path, not the VPN.
  • TUN stack = gvisor (works on every architecture including MIPS).
  • Auto-route = on — sing-box captures all egress traffic via sing-tun and decides per-rule.
  • Strict-route = on — prevents local subnet leaks.
  • DNS — direct DNS = auto (uses the ISP resolver discovered via DHCP); tunnel DNS = 8.8.8.8 (only used for domains matched by VPN rules).
  • Clash API = on, port 9090 on 127.0.0.1 only — drives the Overview dashboard and the Servers latency probes.
  • urltest = on — when 2+ servers are enabled, the lowest-latency one is selected automatically (3-minute test interval, 50 ms tolerance).
  • Log rotation = on, max 1 MB, trimmed every 10 minutes by cron. Without rotation /tmp/sing-box.log would fill the tmpfs.
  • Shipped routing rules (all enabled by default):
    • YouTube, Google, Instagram, Twitter/X, Facebook, Telegram (domains + IPs), OpenAI, SoundCloud → VPN
    • Ad Block (geosite-category-ads-all) → Block
  • Shipped example server (example-server) is disabled with placeholder credentials — replace it or delete it before going live.

To change any of these defaults use the Settings tab or edit /etc/config/singbox directly.


How it works

                          ┌──────────────────────────────────────┐
                          │            LuCI browser UI           │
                          │  overview / servers / routing /      │
                          │  logs / settings  (JS views)         │
                          └─────────────────┬────────────────────┘
                                            │ fs.exec()  +  UCI get/set
                                            ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │                          Backend (shell + ucode)                    │
   │                                                                     │
   │  generate-config.sh   status.sh   update-subscription.sh            │
   │       │                  │                  │                       │
   │       │ UCI → JSON       │ Clash API        │ base64-decode         │
   │       ▼                  ▼                  ▼                       │
   │  /etc/sing-box/      JSON status       uci add server               │
   │  config.json         for dashboard                                     │
   │       │                                                             │
   │       │ sing-box check  ◄── validation gate                          │
   │       ▼                                                             │
   │  /etc/init.d/sing-box  (procd)                                      │
   └─────────────────────────────────┬───────────────────────────────────┘
                                     ▼
                          ┌────────────────────────┐
                          │      sing-box          │
                          │  ┌──────────────────┐  │
                          │  │ tun-in inbound   │  │
                          │  └────────┬─────────┘  │
                          │           ▼            │
                          │  ┌──────────────────┐  │
                          │  │ urltest / vless  │──┼──► REALITY upstreams
                          │  └────────┬─────────┘  │
                          │           ▼            │
                          │  ┌──────────────────┐  │
                          │  │ Clash API :9090  │──┼──► status.sh / dashboard
                          │  └──────────────────┘  │
                          └────────────────────────┘

UCI is the single source of truth. All persistent state lives in /etc/config/singbox. The generate-config.sh script reads UCI, renders sing-box JSON, validates it with sing-box check, and only then atomically replaces /etc/sing-box/config.json. A typo or a missing field never makes it to the running daemon.


Configuration reference

The package ships a working example config at files/etc/config/singbox. Below is the schema. Anonymous sections can be repeated.

Section Type Key options
general (named) enabled, log_level, log_output, stack (gvisor/system), tun_address, tun_mtu, auto_route, strict_route, clash_api, clash_api_port, final_outbound
urltest (named) enabled, interval, tolerance, test_url
dns (named) direct_server (or auto = ISP), tunnel_server, strategy (prefer_ipv4 / prefer_ipv6 / ipv4_only)
server (anonymous) name, enabled, type=vless, server, port, uuid, flow, sni, utls_fingerprint, reality_public_key, reality_short_id
rule (anonymous) name, enabled, type (geosite / ip_cidr / domain), match, outbound (auto / direct / block)
subscription (named) enabled, url, auto_update, update_interval

Example:

config server
	option name 'my-server'
	option enabled '1'
	option type 'vless'
	option server 'example.com'
	option port '443'
	option uuid '00000000-0000-0000-0000-000000000000'
	option flow 'xtls-rprx-vision'
	option sni 'www.microsoft.com'
	option utls_fingerprint 'chrome'
	option reality_public_key 'base64url-encoded-key'
	option reality_short_id '0123456789abcdef'

config rule
	option name 'YouTube'
	option enabled '1'
	option type 'geosite'
	option match 'geosite-youtube'
	option outbound 'auto'

The RPC layer (ubus call rpc.singbox.<method>) is documented in the header comment of files/usr/share/ucode/rpc/singbox.uc.


Supported protocols

Protocol Status
VLESS + REALITY ✅ Full support
VMess 🚧 Planned
Trojan 🚧 Planned
Shadowsocks 🚧 Planned
Hysteria2 ❌ Not supported (UDP blocked by DPI in most censoring networks)

Adding a protocol means: a new option type in servers.js, a builder in generate-config.sh, and a parser branch in update-subscription.sh.


Development

Project layout

.
├── Makefile                                  # OpenWrt package definition
├── files/
│   ├── etc/
│   │   ├── config/singbox                    # default UCI config (shipped, replaceable)
│   │   ├── init.d/sing-box                   # procd init script
│   │   └── uci-defaults/singbox-migrate      # one-shot migration from raw sing-box
│   └── usr/
│       └── share/
│           ├── luci/menu.d/                  # LuCI menu registration
│           ├── rpcd/acl.d/                   # rpcd ACL
│           ├── singbox/
│           │   ├── generate-config.sh        # UCI → JSON (validated)
│           │   ├── status.sh                 # Clash API client (status/servers/test)
│           │   └── update-subscription.sh    # base64 VLESS import
│           └── ucode/rpc/singbox.uc          # ubus RPC surface (status/test/logs/...)
│   └── www/luci-static/resources/view/singbox/
│       ├── overview.js                       # dashboard
│       ├── servers.js                        # CRUD + import
│       ├── routing.js                        # rules + presets
│       ├── logs.js                           # live log viewer
│       └── settings.js                       # advanced settings
├── po/                                       # translation templates (empty by default)
├── README.md
└── LICENSE

Local checks (no OpenWrt needed)

The shell scripts target POSIX sh (verified with dash -n). Validate before pushing:

# POSIX shell syntax
for f in files/etc/init.d/sing-box files/etc/uci-defaults/* files/usr/share/singbox/*.sh; do
  sh -n "$f" && dash -n "$f"
done

# JSON
python3 -c "import json,glob; [json.load(open(f)) for f in glob.glob('files/**/*.json', recursive=True)]"

# ucode (needs the tree-sitter-ucode grammar, or build ucode from OpenWrt source)
ucode -P files/usr/share/ucode/rpc/singbox.uc   # parse-only

Adding a translation

# Create po/templates/singbox.pot from translatable strings, then:
msginit -l ru_RU.UTF-8 -i po/templates/singbox.pot -o po/ru/singbox.po
# Edit po/ru/singbox.po, rebuild the package.

Troubleshooting

Symptom Likely cause / fix
Status dot stays red after Apply Check Logs tab. The most common cause is a wrong REALITY public_key or sni.
sing-box check failed on Apply The error message is shown inline. Usually a missing required option (UUID/port).
Latency shows - for all servers The Clash API is not reachable. Confirm clash_api = 1 and the port is open locally.
TUN never comes up kmod-tun not installed, or auto_route disabled while no manual routes exist.
Subscription import reports 0 imported All server names already exist in UCI (import is deduplicated by fragment/name).
Service won't survive reboot Run /etc/init.d/sing-box enable. The migrate hook does this only once on first boot.

Logs: LuCI → Services → sing-box → Logs, or logread -e sing-box / tail -f /tmp/sing-box.log.


Contributing

PRs welcome. Please:

  1. Fork → feature branch (feat/…, fix/…, docs/…).
  2. Keep shell POSIX (sh/dash), no bashisms.
  3. Run the syntax checks above before pushing.
  4. Don't commit translation .po files for languages you don't actually translate — they rot.
  5. If you add a protocol, update the Supported protocols table and the README schema.

See CONTRIBUTING.md for details.


Credits

This package was designed and iteratively debugged on a real OpenWrt router with extensive help from GLM-5.2 (Zhipu AI) — including the generate-config.sh UCI → JSON pipeline, the procd init script with USB-wait, the rpcd ACL, the ucode rpcd backend, the OpenWrt tar.gz .ipk packaging, the Streaming-endpoint-safe http_get for the Clash API, the action:reject migration off the deprecated block outbound, the log rotation machinery, and the LuCI front-end (E() semantics, form.GridSection quirks, TypedSection vs NamedSection for anonymous UCI sections, view.extend module-load order).

The development conversation (with all its false starts, debug sessions, and incremental fixes) is captured in the commit history — 9 tagged releases in one day.

Other standing-on-the-shoulders-of:

License

GPL-2.0-or-later — same as the OpenWrt LuCI framework.

PKGNICK/PKG_MAINTAINER live in the Makefile. Set them in your fork before publishing a release.