Files
shater/openwrt/shater-core/Makefile
T
omarandClaude Opus 5 cbda0fee0a fix(netplane): arm the fail-closed plane before the daemon can
The plane only ever existed while the daemon did. It starts at 99, after fw4 has
already loaded lan→wan ACCEPT, and only reaches ArmHold after waiting out its
predecessor, migrating the schema, building the engine and reading UCI — with a
UPX-compressed binary decompressing off flash first. Every boot therefore had a
window with no protection at all, landing exactly when Wi-Fi comes up and every
client reconnects. A restart, a reload or a package upgrade opened the same
window on purpose: Teardown does not consult the kill switch, and the init script
guarantees the interval is non-empty.

The holding plane is now persisted to /etc/shater/boot.nft on every apply and
loaded by a small service at 21, right after fw4 and netifd. Its presence is the
arm token: it exists only while the last applied config was enabled AND
fail-closed, and goes away the moment either stops being true. Writes are
content-gated — the cron reconcile runs a minute — and atomic, because the one
boot that reads this file is the boot after a power cut.

The service refuses to arm four ways so it can never brick a box, and its
enabled-check reads /etc/rc.d directly rather than asking rc.common, which would
take a blocking flock in the middle of boot. On exit the daemon re-arms only for
restart and reload, read from a snapshot of rc.common's action; anything else,
including an unknown one, degrades to a real stop that also disarms.

An unreadable config used to leave the router bare forever: the arm call sat in
the branch that requires a successful read, and nothing downstream could recover
it. It now arms from the same path.

A network nobody named was neither diverted nor blocked — the divert set is built
from inbounds and rule sources, and the same set scopes the fail-closed drops. It
is now enumerated from the interfaces whose firewall zone the operator forwards
to a WAN zone — their own statement that those clients reach the internet through
this box — and reported critically, by name, with both resolutions. Deliberately
not closed automatically: this router cannot know a guest SSID was meant to be
off the tunnel, and guessing is an outage. A device name that resolved to nothing
is reported the same way, for the same reason: there is no fail-closed action
available for a device we cannot name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 15:39:16 +03:00

102 lines
4.5 KiB
Makefile

#
# shater-core — data-plane glue for the Shater transparent-proxy stack (v0.2).
#
# Ships the "железно" (rock-solid) static layer that the single Go binary
# `shaterd` sits under: the procd init that supervises `shaterd run` (which
# embeds the sing-box engine + control-plane + DNS in-process), the auto-update
# cron loop, the hotplug hook that re-persists policy routing, the sysctl knobs
# TPROXY needs, one-time rt_tables seeding, and a minimal inert UCI default.
#
# Pure scripts + config => PKGARCH:=all. Nothing is compiled here.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=shater-core
# Version comes from the git tag via ci/version.sh -> SHATER_PKG_VERSION /
# SHATER_PKG_RELEASE in the SDK build env (see openwrt/shaterd/Makefile for the
# full rationale — bug B4: v0.2.2…v0.2.6 all shipped as 0.2.0-r3). The literals
# are the manual/offline fallback only.
PKG_VERSION:=$(if $(SHATER_PKG_VERSION),$(SHATER_PKG_VERSION),0.2.0)
PKG_RELEASE:=$(if $(SHATER_PKG_RELEASE),$(SHATER_PKG_RELEASE),1)
PKG_MAINTAINER:=Shater <maqrota@icloud.com>
PKG_LICENSE:=GPL-2.0-or-later
include $(INCLUDE_DIR)/package.mk
define Package/shater-core
SECTION:=net
CATEGORY:=Network
TITLE:=Shater transparent-proxy data-plane glue
URL:=https://github.com/shater
# v0.2 collapses the old xrayctl + xray-core + dnsmasq-full trio into ONE Go
# binary, shaterd, which embeds the sing-box engine, the control-plane AND an
# in-process DNS server. So we no longer depend on:
# - xrayctl / xray-core -> replaced by shaterd
# - dnsmasq-full -> the engine owns the :53 hijack listener now
# We still need the kernel TPROXY modules and ip-full for policy routing:
# shaterd : the daemon our init supervises (`shaterd run`)
# kmod-nft-tproxy : kernel TPROXY (shaterd emits the `inet shater` rules)
# kmod-nft-socket : socket match used by the tproxy divert chain
# ip-full : `ip rule`/`ip route`/rt_tables for policy routing
# nftables-json : shaterd shells out to `nft`, and netplane/stats.go
# parses `nft -j list ...` — the JSON output only exists
# in the json variant (the -nft variant has no libjansson).
# fw4 already pulls it on stock images; declare it so a
# slimmed image cannot silently break counters/sets.
# ca-bundle : the daemon is CGO_ENABLED=0, so crypto/x509 has no
# host cert fallback — without /etc/ssl/certs every
# HTTPS subscription / .srs ruleset fetch fails.
DEPENDS:=+shaterd +kmod-nft-tproxy +kmod-nft-socket +ip-full +nftables-json +ca-bundle
PKGARCH:=all
endef
define Package/shater-core/description
Static data-plane glue for the Shater sing-box-based transparent proxy: procd
init (supervises `shaterd run`, which owns the engine + nft table `inet shater`
+ policy routing + in-process DNS), an auto-update cron loop with a dead-engine
watchdog, an ifup/ifdown hotplug hook that re-persists ip rules & routes,
TPROXY sysctl settings, a minimal inert UCI default (globals disabled until
configured), and idempotent first-boot setup. Designed to never break
connectivity: fully inert until explicitly enabled.
endef
# /etc/config/shater is user-editable desired state -> preserve on upgrade.
define Package/shater-core/conffiles
/etc/config/shater
endef
# Nothing to fetch or build.
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
endef
define Build/Compile
endef
define Package/shater-core/install
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/etc/init.d/shater $(1)/etc/init.d/shater
$(INSTALL_BIN) ./files/etc/init.d/shater-cron $(1)/etc/init.d/shater-cron
# START=21 one-shot that loads the persisted fail-closed plane before fw4's
# `lan -> wan ACCEPT` can be the only thing on the box (the main init is
# START=99, i.e. seconds of plaintext forwarding on every boot).
$(INSTALL_BIN) ./files/etc/init.d/shater-armor $(1)/etc/init.d/shater-armor
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
$(INSTALL_BIN) ./files/etc/hotplug.d/iface/99-shater $(1)/etc/hotplug.d/iface/99-shater
$(INSTALL_DIR) $(1)/etc/sysctl.d
$(INSTALL_DATA) ./files/etc/sysctl.d/99-shater.conf $(1)/etc/sysctl.d/99-shater.conf
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/shater $(1)/etc/config/shater
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) ./files/etc/uci-defaults/30_shater-core $(1)/etc/uci-defaults/30_shater-core
endef
$(eval $(call BuildPackage,shater-core))