Skip to content

Commit bbb7571

Browse files
committed
release: v1.6.0-beta.2
What a two-port mini PC found the first time router mode ran on hardware. The install path mattered most: since v1.5.2 the one-liner had been leaving the panel unreachable on both 80 and 443, because nginx aborts without a certificate that install.sh never generated, and nginx is the only service publishing either port. Anyone who installed that way got a box that looked like it had done nothing. The sidecar builder shipped inert on top of that, and avahi kept the port xray needs for DNS. Router mode itself came up, served DHCP and put an access point on the air — but "auto" channel broadcast nothing while reporting success, bridging the LAN made it impossible to switch router mode back off, and a settings save that changed nothing rebuilt the whole dataplane and dropped every client. Two faults came out of building a local PPPoE concentrator, since PPPoE frames don't route and the path had never been exercised: no MSS clamping, which would have made a 1492-byte uplink hang on large transfers, and no notice when traffic leaves by a port that isn't the uplink at all. Also adds a multi-port LAN, uplink-side access to the panel and SSH for a PiTun behind another router, and a Router page that says which of the two deployments you are in. No new migrations; head stays 025.
1 parent 613a28c commit bbb7571

4 files changed

Lines changed: 102 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,104 @@ All notable user-facing changes to PiTun. Full per-release detail lives in the
44
[GitHub Releases](https://github.com/DaveBugg/PiTun/releases); this file is the
55
committed summary.
66

7+
## v1.6.0-beta.2 — 2026-08-14
8+
9+
**Beta — router mode, first hardware run.** beta.1 had never been switched on
10+
outside tests. This is what a two-port mini PC found: a clean install that left
11+
the panel unreachable, an access point that broadcast nothing while reporting
12+
success, and a router you could not switch back off. Everything below was
13+
observed on real hardware, not reasoned about.
14+
15+
Upgrading from beta.1 is worthwhile even if you never enable router mode — the
16+
installer and X-ui fixes apply to every box.
17+
18+
### Fixed — install
19+
20+
- **The one-liner install left the panel unreachable on both 80 and 443.**
21+
`nginx.conf` has carried an unconditional `listen 443 ssl` since v1.5.2, but
22+
`install.sh` never generated a certificate — the word does not appear in it.
23+
nginx aborts outright when the files are missing, and since it is the only
24+
service publishing either port, the panel was gone from *both* while the
25+
container crash-looped. Every one-liner install since v1.5.2 landed this way;
26+
it went unnoticed because our own boxes were provisioned through
27+
`03-deploy.sh`, which does generate the cert.
28+
- **The sidecar build shipped inert.** `build-sidecars.sh` referenced `$DOCKER`
29+
while declaring `DOCKER_CMD`; under `set -u` that aborts on the first loop
30+
iteration, so the dnsmasq and hostapd images were never built by any caller —
31+
the exact failure the script had just been extracted to fix.
32+
- **avahi came back within seconds** of being disabled and kept UDP/5353, which
33+
xray needs for DNS. Only the service was masked, not the socket unit that
34+
re-activates it, and masking never kills a running process.
35+
- The installer no longer claims success without looking: `compose up -d`
36+
returns 0 for a container that starts and immediately dies, so it printed
37+
"PiTun is up" over a dead stack.
38+
39+
### Fixed — router mode
40+
41+
- **"Auto" WiFi channel put nothing on the air.** `channel=0` runs hostapd's
42+
ACS, which needs noise-floor figures the driver may not report — mt7921
43+
doesn't — and hostapd then loops over every channel forever *without
44+
exiting*. The container looked healthy while no SSID existed. Auto now
45+
resolves to a fixed channel, and the start path asks the radio whether it
46+
actually entered AP mode rather than trusting that the process survived.
47+
- **Bridging the LAN stranded the operator in router mode.** Enslaved ports
48+
were dropped from the inventory, so the moment the bridge came up the
49+
assigned ports vanished, the panel reported one port on a three-port box, and
50+
validation rejected the stored configuration — including the request to turn
51+
router mode *off*. Leaving router mode is no longer validated at all: gateway
52+
is the resting state and the escape hatch.
53+
- **A save that changed nothing rebuilt the network.** Any patch touching a
54+
router key tore the bridge down and back up and restarted hostapd and
55+
dnsmasq — indistinguishable, from a client, from the router rebooting — and
56+
re-armed the confirm window on an already-confirmed router, which then
57+
reverted itself three minutes later.
58+
- **No TCP MSS clamping.** A PPPoE uplink carries 1492 bytes; LAN clients
59+
announce MSS for their own 1500. Without clamping the router depends on ICMP
60+
"fragmentation needed" reaching the sender, which the public internet drops
61+
often enough that it is not a plan. DNS resolves, ping works, small pages
62+
load, large transfers hang forever.
63+
- **The ICMP counter could not see what it counted.** It sat after
64+
`ct state established,related accept`, and the packets it exists to observe
65+
are exactly what conntrack marks RELATED — so it read zero on a healthy link
66+
and could never have detected the path-MTU black hole it was added for.
67+
- The LAN address is found on whichever member holds it, the radio may be any
68+
LAN member rather than only the primary, and the confirm countdown is driven
69+
by the box's own `seconds_left` — a box whose clock is hours off used to
70+
promise three hours where three minutes were left.
71+
72+
### Added
73+
74+
- **A LAN can be several ports.** `br-lan` bridges them into one segment —
75+
same subnet, one DHCP pool, wired and wireless clients seeing each other.
76+
The radio is joined by hostapd itself rather than from both ends, which
77+
races.
78+
- **The uplink can publish the panel and SSH** (both off by default). Useful
79+
when PiTun sits behind another router, where that "WAN" is your own network.
80+
Refused outright if the uplink address is public. Turn it on *before*
81+
switching, not after.
82+
- **A dedicated Router page** between Diagnostics and Settings, naming the two
83+
deployments — behind an existing router, or first in line facing the ISP —
84+
so it is clear which fields apply.
85+
- **Country picker with names and flags** for the WiFi regulatory domain,
86+
searchable, no new dependency.
87+
- **A banner when the panel stops answering at this address**, pointing at the
88+
LAN one — the switch to router mode is made from a page served by the port
89+
that is about to stop serving it.
90+
- **`scripts/pppoe-test-rig.sh`** — a local PPPoE concentrator on a veth pair.
91+
PPPoE frames don't route, so it cannot be tested remotely and was shipping
92+
unexercised. The rig proved the path works and found the MSS and default-route
93+
faults above.
94+
- Diagnosis now reports when traffic is leaving by a port that isn't the
95+
uplink: NAT and firewall on a link nothing uses, while every other check
96+
reads healthy.
97+
98+
### Notes
99+
100+
- No new migrations; head remains 025.
101+
- The default WiFi network name is now `PiTun`. Broadcasting stays **off**
102+
until switched on.
103+
- 13 commits. Tests: 1299 backend, 96 frontend.
104+
7105
## v1.6.0-beta.1 — 2026-08-13
8106

9107
**Beta — router mode.** PiTun can now *be* the router instead of sitting beside

backend/app/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# OpenAPI metadata, `/health` response, and `/system/status` so the
66
# frontend can display it next to the xray version. Bump this on each
77
# release — frontend keeps its own version in `frontend/package.json`.
8-
APP_VERSION = "1.6.0-beta.1"
8+
APP_VERSION = "1.6.0-beta.2"
99

1010

1111
class Settings(BaseSettings):

backend/tests/test_router_mode.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,7 @@ def test_carrier_grade_nat_counts_as_behind_something(self, monkeypatch):
19521952

19531953
def test_private_uplink_may_be_opened(self, monkeypatch):
19541954
from app.core import router_mode as rm
1955-
monkeypatch.setattr(rm.nc, "read_interface_address", lambda i: ("192.168.1.6", 24))
1955+
monkeypatch.setattr(rm.nc, "read_interface_address", lambda i: ("192.168.1.50", 24))
19561956
rm._refuse_public_wan_exposure("eth0", [80, 443], [])
19571957

19581958
def test_opening_nothing_is_never_refused(self, monkeypatch):
@@ -2136,7 +2136,7 @@ def test_switch_to_gateway_survives_an_unusable_router_config(
21362136
# bridge router mode built, or simply gone.
21372137
monkeypatch.setattr(nc, "list_interfaces", lambda: [
21382138
{"name": "eno1", "mac": "aa:bb:cc:dd:ee:01", "up": True,
2139-
"carrier": True, "ipv4": "192.168.1.6", "cidr": 24,
2139+
"carrier": True, "ipv4": "192.168.1.50", "cidr": 24,
21402140
"is_default_route": True, "wireless": False, "ap_capable": False,
21412141
"wifi_detail": "", "wifi_modes": [], "master": ""},
21422142
])

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "pitun-frontend",
33
"private": true,
4-
"version": "1.6.0-beta.1",
4+
"version": "1.6.0-beta.2",
55
"license": "BSD-3-Clause",
66
"type": "module",
77
"scripts": {

0 commit comments

Comments
 (0)