Both routers are past opkg: mini_router runs ImmortalWrt 25.12.1 and main_router OpenWrt 25.12.0, both with apk-tools 3.0.5, and main_router has no `opkg` binary at all. The 24.10 lane was building and signing a feed no device could consume. Removed jobs `build` and `release` with the scripts only they called (ci/build-feed.sh, ci/sdk-build.sh, ci/make-index.sh, ci/install-usign.sh) and the usign trust anchor dist/shater-feed.pub. A committed public key is an instruction: it invites the old install path for a feed that is no longer produced. The key is retired, not revoked -- git history keeps it, KEY_BUILD still holds the secret half, and a usign secret contains its own public half, so the identity is reconstructible if a 24.10 device ever needs serving. D7 is marked SUPERSEDED by the new D22 rather than deleted. Separately: the rolling `apk-latest-<arch>` release was frozen at 0.2.0 from 2026-07-24 while every tag run published its versioned release correctly. The publish loop was an either/or -- `TAG=apk-latest-<arch>` when VER=latest (workflow_dispatch only), ELSE `TAG=apk-<ver>-<arch>` -- so a `v*` tag run never touched the rolling pointer. Asset replacement was never the problem; ci/gitea-release.sh already deletes before recreating. A router pinned to the rolling URL sat on 0.2.0 while `apk update` reported success: silent staleness, the failure mode this repo keeps having to close. The rolling pointer is now published on EVERY run, tag runs included, and a new assert reads the release back over the API afterwards: our three tag-versioned packages at the built version plus the index and the key must be present (exit 13), and no package asset at any other version may survive (exit 14). Same class of check as sdk-build-apk.sh's package-version assert, added for the same reason -- the previous failure mode was silent. KEY_BUILD can now be deleted from the Gitea repo secrets; nothing references it. Docs state plainly that mini_router is deliberately pinned to a versioned URL and that the hand-edit per release is the price of pinning. Known consequence: the x86_64 QEMU testbed is still OpenWrt 24.10.3 and can no longer install our packages. Its 25.12 rebuild is in flight separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4PcWfrBRyg4eWN58axaGN
5.2 KiB
shater
A self-hosted internet-control appliance for OpenWrt routers. One box turns a home or office network into a transparent VPN gateway, a network-wide ad/tracker/malware blocker, per-device parental control, and a live traffic dashboard — all local, all configured from a rich built-in web panel.
The primary README is Russian — README.md. This is a condensed English mirror.
What it is
shater is a network proxy stack for OpenWrt / ImmortalWrt / BananaWRT routers (Banana Pi BPI-R3, BPI-R4 and compatible). It transparently routes all LAN traffic through a proxy (split by domain/geo/client), filters DNS, gathers statistics, and is managed from a built-in web panel.
The engine is a fork of sing-box via
sing-box-lx, compiled into a single Go
binary shaterd together with the control plane, DNS filter, stats aggregator and
the web panel itself. Broad protocol set: VLESS/VMess/Trojan/Shadowsocks,
Reality/XTLS, WireGuard, AmneziaWG 2.0, Hysteria2, TUIC, XHTTP, MASQUE/CONNECT-IP.
A thin LuCI launcher (mini-dashboard + "Open panel" button) hands the browser a
single-use token into the standalone SPA the daemon serves on its own port
(default :8088).
Highlights
- Transparent TPROXY data plane (TCP + UDP), SNI/Host/QUIC sniffing, no DNS leaks.
- First-match routing by source / destination / list / geo / client → outbound / selector / chain / direct / block; node groups with balancer/observatory; multi-hop chains; per-rule egress.
- Fail-closed kill-switch (dead group → block, never a silent direct leak); own
inet shaternft table; atomic apply withnft -cvalidation and commit-confirm auto-rollback. - DNS filtering & blocklists with flexible sources (inline / file / url /
geosite), compiled
.srsmatcher; Block-DoH/DoT to stop filter bypass. - Subscriptions (Clash / sing-box / Xray-JSON) and manual nodes; node health board.
- Per-device control (proxy/blocklist toggles, exit country, per-device block/allow, schedules) and per-domain/client/device statistics from in-process DNS events.
Full list with MVP/T1/T2 tags — docs-shater/FEATURES.md.
Install
One signed apk feed (OpenWrt / ImmortalWrt / BananaWRT 25.12+), one
release per arch. Verbatim commands, the manual .apk install and the
rolling-vs-pinned choice are in
docs-shater/INSTALL.md.
wget -O /etc/apk/keys/shater-apk.pem "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/shater-apk.pem"
echo "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/packages.adb" > /etc/apk/repositories.d/shater.list
apk update && apk add luci-app-shater # -> shater-core -> shaterd
apk-latest-<arch> is a moving pointer refreshed by every release run — install
once and apk update && apk upgrade shaterd shater-core luci-app-shater byedpi
keeps the router current. Point the repo line at apk-vX.Y.Z-<arch> instead to
pin a build; that file then has to be edited by hand for every upgrade.
shater ships inert (globals off) so install never breaks connectivity. After
configuring nodes/rules: uci set shater.globals.enabled=1 && uci commit shater,
then shaterd apply and shaterd confirm.
Build from source
scripts/build-shaterd.sh [VERSION] [--fast] builds the SPA (Vite), embeds it via
//go:embed, cross-builds musl-static {amd64, arm64} and UPX-packs the artifact
into openwrt/shaterd/files/. Details in
docs-shater/INSTALL.md.
Repository layout
| Path | What |
|---|---|
shater/ |
Go control plane, DNS filter, stats aggregator, engine host |
panel/ |
Admin SPA (Vite + React + TS) and its Go server |
openwrt/ |
Packages: shaterd, shater-core, luci-app-shater, byedpi |
docs-shater/ |
Product documentation |
scripts/, ci/, .gitea/workflows/ |
Build script, apk feed/release scripts, CI |
SPECS/, docs-lx/ |
Engine-fork constitution/specs and feature-config reference |
docs/, mkdocs.yml |
Upstream sing-box docs (mkdocs) — kept as-is |
adapter/ cmd/ dns/ route/ option/ protocol/ transport/ … |
sing-box-lx engine tree |
CI, upstream & license
CI (.gitea/workflows/release.yml) builds all 4 packages and publishes a signed
per-arch apk repo (EC key shater-apk.pem). A vX.Y.Z tag → the pinnable
apk-vX.Y.Z-<arch>; every run also refreshes the rolling apk-latest-<arch> and
asserts over the API that it really serves the version just built.
The engine is the sing-box-lx fork — a thin downstream of upstream sing-box that
lives by rebase, never merge; its constitution is
SPECS/CONSTITUTION.md. Licensed under
GPL-3.0, like upstream sing-box. Unofficial fork, not affiliated with
SagerNet.