Files
op-packages/luci-app-substore
..
2026-09-29 08:28:44 +08:00
…
2026-09-29 08:28:44 +08:00
2026-09-29 08:28:44 +08:00
2026-09-29 08:28:44 +08:00
…
2026-09-29 08:28:44 +08:00
2026-09-29 08:28:44 +08:00
2026-09-29 08:28:44 +08:00
2026-09-28 11:17:08 +08:00

luci-app-substore

English | 简体中文

Native OpenWrt / ImmortalWrt LuCI application for managing airport / proxy subscriptions. Parse nodes from a subscription, filter, deduplicate, rename and group them, then re-emit them in a format your client can consume.

Screenshot

Features

Subscription management

  • Add / edit / delete / update multiple subscription sources
  • Manual update plus per-subscription timed (cron) updates
  • Status overview: node count, last update time, error message
  • Remaining traffic & remaining duration per subscription, parsed from the subscription-userinfo response header (shown on the edit page only)
  • Subscription proxy: download the subscription through an http:// / https:// / socks4 / socks5 / socks5h proxy — useful when the source is unreachable directly
  • Combination subscription: merge an arbitrary subset of existing subscriptions (optionally with keyword include / exclude and dedup rules) into one combination that has its own name, token and subscription link; combinations are recomputed automatically when a source updates
  • Local subscription: no URL needed — paste node text (mixed YAML / URI / JSON / wg-quick .conf) or enter nodes one by one via a form whose fields adapt to the selected protocol (vmess / vless / ss / ssr / trojan / hysteria2 / tuic / wireguard / socks)

Input parsing

  • Subscription formats: URI lists, Base64, JSON, Clash YAML, sing-box JSON, V2Ray / Xray JSON, Surge / Surfboard / Loon / Quantumult X configs, LAN subscription links, and wg-quick / AmneziaWG .conf
  • Node protocols: vmess / vless / trojan / shadowsocks / ssr / hysteria2 / tuic / socks (and more)
  • WireGuard / AmneziaWG: full field import and export (private-key / public-key / pre-shared-key / ip / ipv6 / allowed-ips / reserved / persistent-keepalive / listen-port / mtu / dns) plus the amnezia-wg-option sub-block (Jc / Jmin / Jmax / S1–S4 / H1–H4 / I1–I5 / J1–J3 / Itime); you can paste the contents of a .conf file exported by an AmneziaWG client directly

Node processing

  • Browse nodes, filter by group / protocol, keyword search, sort
  • Node grouping: set a node's group inline in the Group column (saved via XHR without reload), filter via the Group dropdown
  • Per-node edit / delete (Actions column); header checkbox selects all, then the Delete button batch-deletes the selection; the Refresh button reloads the list keeping the current filters
  • Per-subscription rules applied on every update:
    • keyword include / exclude (comma-separated, multi-keyword)
    • deduplication

Network probing (Nodes page)

  • Ping (ICMP latency), TCPing (connect latency), URL test (HTTP latency)
  • Parallel probing, success count and average latency

Conversion & output

  • Protocol conversion: any node type → any other type
  • SSR (ssr://) input is re-emitted losslessly to SSR-capable clients only — Mihomo / Clash.Meta, Stash, Loon, Egern, Shadowrocket — and dropped for the rest (sing-box, V2Ray/Xray, Surge family), since SSR is not convertible to/from other protocols
  • 15 output formats (all implemented): Plain JSON, Stash, Clash.Meta / Mihomo YAML, Clash (original), Surfboard, Surge, Surge Mac, Loon, Egern, Shadowrocket, Quantumult X, sing-box, V2Ray / Xray, V2Ray URI, WireGuard / AmneziaWG .conf
    • Clash (original): for Dreamacro Clash / ClashX / Clash for Windows; protocols the original does not support (vless / hysteria2 / hysteria / tuic / wireguard) are filtered out
    • WireGuard / AmneziaWG .conf: wg-quick single-interface config with [Interface] / [Peer] sections and AmneziaWG obfuscation parameters, importable by AmneziaWG clients
    • sing-box / V2Ray (Xray): emits a complete, runnable config (outbounds plus routing), not just an outbounds fragment
      • sing-box: node outbounds + selector (manual switch) + urltest (auto latency test) + direct / block; route.final points at the selector, with a built-in private-IP direct rule
      • V2Ray/Xray: node outbounds + freedom (direct) / blackhole (block) + observatory + routing.balancers (leastPing auto-selection), with built-in geoip:private direct and a catch-all route
      • Deliberately excludes inbounds / dns: those bind local listening ports and override your existing DNS settings — keep them in your own config and merge this output into it
      • ⚠️ Not compatible with 2.3.x: 2.3.x emitted an outbounds-only fragment meant to be pasted into an existing config; from 2.4.0 it is a complete config you can start directly as a single file

Subscription links

  • Per-subscription random token → public download endpoint /substore/download?token=<token>&target=<format> that Passwall / OpenClash and other clients can pull directly

LuCI web UI & i18n

  • English by default, 简体中文 auto-selected when the runtime language is zh-cn

Installation

The version in the package name must match PKG_VERSION / PKG_RELEASE in the Makefile (currently 2.4.0-r2).

opkg (OpenWrt / ImmortalWrt 24.10 and earlier):

opkg install luci-app-substore-2.4.0-r2.ipk

apk (OpenWrt / ImmortalWrt 25.12+):

apk add --allow-untrusted luci-app-substore-2.4.0-r2.apk

Then open LuCI: Services → Subscriptions.

Usage

  1. Add a subscription — paste the subscription URL; optionally set up a per-subscription cron schedule, rules, or a download proxy. No remote source? Use Add local subscription: paste node text or enter nodes via the form.
  2. Update — fetch, parse and filter the nodes.
  3. Browse nodes — filter (group / protocol / keyword), sort, probe latency; tick checkboxes and hit Delete for batch deletion, edit / delete / regroup in-row, and Refresh reloads the list.
  4. Export — pick one of the 15 output formats, or copy the subscription link to feed a downstream client (Passwall / OpenClash / …).

Project layout

.
├── Makefile                      # OpenWrt package definition
├── LICENSE                       # GPL-2.0-or-later
├── root/                         # install payload
│   ├── etc/
│   │   ├── config/substore       # UCI placeholder
│   │   └── uci-defaults/99-substore
│   ├── usr/
│   │   ├── bin/substore-cron.sh  # per-subscription cron runner
│   │   ├── lib/lua/luci/
│   │   │   ├── controller/admin/substore.lua   # routes / actions
│   │   │   └── view/substore/*.htm             # templates
│   │   └── share/
│   │       ├── luci/menu.d/luci-app-substore.json
│   │       └── substore/*.lua    # core logic (no luci.* dependency)
├── po/zh-cn/substore.po          # 简体中文 translations
├── docs/                         # design & guides
└── tests/                        # self-contained Lua 5.1 unit tests

Documentation

Building

Place the package under an OpenWrt / ImmortalWrt SDK or source tree matching the target firmware, then:

cp -r luci-app-substore <openwrt-tree>/package/
make package/luci-app-substore/compile V=s

The .ipk (or .apk on apk builds) is produced under bin/packages/.../.

Development & testing

The core is plain Lua 5.1 with no luci.* dependency, so it is unit-testable without a device. Each file under tests/ is self-contained:

lua5.1 tests/run_tests.lua          # or any single test file
for f in tests/*.lua; do lua5.1 "$f" || exit 1; done

Target-device verification is required for the LuCI UI and cron behaviour — see docs/TESTING.md.

Security

SSRF protection (private / reserved / link-local ranges rejected), protocol whitelisting, response size & timeout limits, command-injection defence (whitelisted parsing + shell quoting), token-based access control on the public download endpoint, and no credentials in logs. See docs/SECURITY.md.

License

GPL-2.0-or-later — see the LICENSE file.