Three-layer transparent gateway for OpenWrt 24.10.x / 25.04 / 25.12.x.
Package manager is detected automatically (opkg on 24.10.x, apk on 25.x).
- mihomo via
nikki— VLESS+Reality transport, fake-IP DNS, rule-based routing - zapret via
remittor— DPI bypass for YouTube / SmartTV, no VPN tunneling - AdGuard Home — LAN-wide ad/tracker/telemetry filter with DoH upstreams
- Force-DNS firewall redirect — intercepts hard-coded
:53from clients
Primary documentation is Russian: README_RU.md. This page is a short orientation.
- OpenWrt release in
SUPPORTED_RELEASES(default:24.10.0 24.10.1 24.10.2 25.04.0 25.12.0 25.12.1 25.12.2). Extend via env:SUPPORTED_RELEASES="25.12.3" sh install.sh. DISTRIB_ARCHinSUPPORTED_ARCHES(MIPS / aarch64 / arm / x86_64 / i386 variants)./overlayon USB/SD/NVMe (ext4) ≥ 2 GiB — extroot must be set up before running the installer.- Active swap ≥ 1 GiB (1.5 GiB recommended).
- ≥ 200 MB RAM, root SSH, working WAN.
- Clean OpenWrt: no
xray/sing-box/mihomo/passwall*/podkoprunning;:53held by stockdnsmasqor free. - VLESS+Reality URL:
vless://UUID@host:port?type=tcp&security=reality&pbk=...&fp=chrome&sni=...&sid=...&flow=xtls-rprx-vision#label.
Extroot setup is covered in README_RU.md §Подготовка.
wget -O /tmp/install.sh https://raw.githubusercontent.com/PrEvIeS/openwrt_vless/main/install.sh
sh /tmp/install.shInteractive mode prompts for the VLESS URL only. Non-interactive example:
sh install.sh --non-interactive \
--vless-url 'vless://UUID@host:443?type=tcp&security=reality&pbk=KEY&sni=www.google.com&sid=DEADBEEF&flow=xtls-rprx-vision&fp=chrome#label'Override priority: --vless-* CLI flag > URL value > fallback default.
| Flag | Purpose |
|---|---|
--vless-url URL |
Full VLESS Reality URL (required unless every field is set via --vless-* overrides) |
--vless-server/port/uuid/pubkey/sid/sni/flow/fp |
Override individual fields |
--nfqws-opt STR |
Custom zapret NFQWS strategy (default is a starting set, see DEFAULT_NFQWS_OPT in install.sh) |
--no-zapret / --no-adguard / --no-force-dns / --no-i18n |
Skip a layer |
--force-config |
Overwrite existing AdGuardHome.yaml, nikki profile and snapshot. Operator's AGH bind_port is preserved across rewrites; everything else (DNS upstreams, blocklists, theme) is reset. |
--non-interactive |
Fail instead of prompting |
-h, --help |
Show usage |
--no-adguard requires --no-force-dns — Force DNS redirects clients to LAN_IP:53, which is empty without AGH (dnsmasq is on :54, mihomo on :1053). Installer refuses at preflight if only --no-adguard is set.
State-file /etc/openwrt-setup-state makes each step idempotent — re-running install.sh skips completed steps unless --force-config is passed.
- First-time setup (passwd / SSH key / TZ if not yet set)
- Preflight — release + architecture + package manager
- Preflight — extroot + swap
- Preflight — conflict probes (rival proxies,
:53owner, LAN iface, internet) - Collect and parse VLESS URL, validate fields
- Pre-install state snapshot at
/root/openwrt-mihomo-backup/(chmod 700) - Base packages (
curl,block-mount, USB-storage kmods, …) nikkifeed + mihomo packages (GitHub-releases fallback if feed DNS-blocked)zapretviaremittor/update-pkg.shadguardhomeluci-theme-argonluci-app-statistics+ collectd modules- SQM cake (
luci-app-sqm+kmod-sched-cake, queue defaults seeded only if absent) - Configure: nikki profile + UCI mixin / zapret NFQWS / dnsmasq →
:54/ AGH upstream / Force-DNS DNAT - Service order:
S50nikki → S60adguardhome → S99zapret - Enable + start
- Self-test (ports, daemons, firewall rule) + summary
Routing rules in mihomo profile (first match wins):
- LAN / RFC1918 →
DIRECT - YouTube domain-suffix list →
YOUTUBEselector (defaultVLESS-REALITY) geosite:ru-available-only-inside(yandex.net, kinopoisk-ru.clstorage, cdnvideo.ru …) →DIRECT.ru/.рф/.su/geoip:RU→DIRECTgeosite:ru-blocked(runetfreedom antifilter + re:filter) →PROXYMATCH→FINALselector (defaultVLESS-REALITY)
geosite.dat / geoip.dat are pre-downloaded from runetfreedom/v2ray-rules-dat before mihomo first start (chicken-and-egg: empty caches block startup DNS).
Source: runetfreedom/russia-v2ray-rules-dat, release branch — community-curated v2fly-format geosite.dat + geoip.dat, refreshed daily by upstream CI from antifilter + re:filter.
| geosite category | Routes to | Coverage |
|---|---|---|
ru-available-only-inside |
DIRECT (before .ru / GEOIP) |
RU-only services: Yandex / Kinopoisk / Okko / Wink, CDN domains .yandex.net, cdnvideo.ru, kinopoisk-ru.clstorage.net — .com/.net of Russian infra not caught by .ru TLD. |
ru-blocked |
PROXY → VLESS-Reality (after .ru / GEOIP) |
Domains blocked in RU — antifilter + re:filter (~10⁴ entries). |
geoip:RU |
DIRECT |
IP-block fallback. |
Files live in /etc/nikki/run/{geosite,geoip}.dat (~66 + ~21 MiB). Auto-update every 24h via geo-update-interval. Force refresh without restart:
SECRET=$(uci get nikki.mixin.api_secret)
curl -X POST -H "Authorization: Bearer $SECRET" http://127.0.0.1:9090/configs/geoTo use a different rules repo, edit geox-url in /etc/nikki/profiles/main.yaml. v2fly-format protobuf is required (Xray / sing-box format is binary-compatible).
Note: runetfreedom/russia-v2ray-custom-routing-list is not what we use — that repo doesn't ship a mihomo-compatible build (release/mihomo/*.list URLs return 404).
The installer targets IPv4-only routing. What this means in practice:
- mihomo fake-IP works only for A records. AAAA responses are passed through without fake-IP, so v6 destinations are not matched against the geosite / YouTube / geoip rules.
- zapret is pinned to
disable_ipv6=1in UCI, the nfqws hook only inspects v4 traffic. - Force-DNS redirect is v4 only (
ip natDNAT, no ip6nat rule).
Net effect: if a LAN client has working IPv6 to a blocked destination, the browser will prefer v6 (Happy Eyeballs) and bypass VLESS entirely. Recommendation: disable IPv6 on the LAN side in LuCI → Network → Interfaces → LAN — set IPv6 assignment length to disabled and turn off the RA/DHCPv6 services.
A proper --ipv6 {bypass,drop,route} policy flag is tracked on the roadmap.
| Phase | Exit | Behavior |
|---|---|---|
| Preflight refuse (release / arch / pkg / extroot / conflicts / VLESS URL) | 2 | nothing written, fix environment and re-run |
| Error after snapshot (mutations begun) | 1 | installer prints sh uninstall.sh hint, no auto-rollback |
| Self-test FAIL | 1 | per-check report printed, state kept, run uninstall.sh to revert |
No --force flag, no retry loops, no auto-rollback.
- Open the AdGuard Home wizard at
http://<LAN_IP>:3000— set admin port to:8080, DNS bind to all interfaces:53, create a password. - If YouTube is slow or blocked, tune the zapret strategy:
service zapret stop /opt/zapret/blockcheck.sh # → update NFQWS_OPT in LuCI → Services → Zapret
sh uninstall.sh
# or, for full cleanup:
sh uninstall.sh --remove-packages --remove-state --purge-config --restore-crontabUCI values are restored symbolically from snapshot.env. --purge-config additionally removes /etc/config/{nikki,zapret,adguardhome} (default keeps them with enabled=0 for reinstall). extroot and swap are never touched.
MIT. Third-party packages keep their upstream licenses (mihomo / nikki / zapret / AdGuard Home).