Warn when installing on a public-IP box in local mode
A piped `curl | bash` defaults to local mode and previously said nothing about the transfer port being internet-exposed. Now the installer detects a routable public IPv4 (excluding RFC1918, loopback, link-local and CGNAT/Tailscale) and, in local mode, prints the exact ufw/nftables commands to lock 53317 to the tailnet — plus a container-on-host note, a --pin tip, and an app-layer verify command (raw TCP/nc lie behind providers that SYN-ACK every port). It never changes the firewall outside remote mode. README gains a "Public-IP boxes" section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2c058031fd
commit
e57c426327
2 changed files with 84 additions and 2 deletions
48
README.md
48
README.md
|
|
@ -62,6 +62,11 @@ interface in the firewall (`ufw`), so the box is reachable over your tailnet onl
|
||||||
not the open internet. Non-interactive installs (e.g. piped `curl | bash`) default
|
not the open internet. Non-interactive installs (e.g. piped `curl | bash`) default
|
||||||
to local; force a choice with `OMARCHY_SEND_MODE=local` or `OMARCHY_SEND_MODE=remote`.
|
to local; force a choice with `OMARCHY_SEND_MODE=local` or `OMARCHY_SEND_MODE=remote`.
|
||||||
|
|
||||||
|
If you install in local mode but the box has a **public IP**, the installer detects
|
||||||
|
it and prints a warning with the exact commands to lock the port down — it never
|
||||||
|
changes your firewall without remote mode. See
|
||||||
|
[Public-IP boxes](#public-ip-boxes-firewall-the-port) below.
|
||||||
|
|
||||||
> The installer is a short shell script fetched over HTTPS; read it first if you
|
> The installer is a short shell script fetched over HTTPS; read it first if you
|
||||||
> prefer — it lives at [`install.sh`](install.sh) in this repo.
|
> prefer — it lives at [`install.sh`](install.sh) in this repo.
|
||||||
|
|
||||||
|
|
@ -140,8 +145,47 @@ The receiver already listens on all interfaces, so it's reachable at its Tailsca
|
||||||
IP with nothing else to configure. Sending and receiving both work, because the
|
IP with nothing else to configure. Sending and receiving both work, because the
|
||||||
probe is a two-way handshake (each side learns the other).
|
probe is a two-way handshake (each side learns the other).
|
||||||
|
|
||||||
> On a box with a public IP, don't leave `53317` open to the internet — install in
|
#### Public-IP boxes: firewall the port
|
||||||
> **remote** mode (above) to firewall it to the tailnet, and/or set a `--pin`.
|
|
||||||
|
The receiver binds **all interfaces**, so on a box with a public IP, port `53317`
|
||||||
|
is reachable from the open internet while the TUI is running. Don't leave it that
|
||||||
|
way. Three ways to handle it:
|
||||||
|
|
||||||
|
- **Easiest:** install in **remote** mode — `OMARCHY_SEND_MODE=remote bash install.sh`
|
||||||
|
— and the installer applies the `ufw` rules for you (when a real `tailscale0`
|
||||||
|
interface is present).
|
||||||
|
- **Manually**, restrict the port to the tailnet:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ufw allow in on tailscale0 to any port 53317 # tailnet only
|
||||||
|
ufw deny 53317 # everything else
|
||||||
|
```
|
||||||
|
|
||||||
|
Or the `nftables` equivalent (inet filter, input chain):
|
||||||
|
|
||||||
|
```
|
||||||
|
iifname "tailscale0" tcp dport 53317 accept
|
||||||
|
tcp dport 53317 drop
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Inside a container** (e.g. Docker `--network host` with userspace-networking
|
||||||
|
Tailscale, where there's no `tailscale0` and no `CAP_NET_ADMIN`): you can't
|
||||||
|
firewall from in there — apply it on the **host**. If the host already
|
||||||
|
default-denies inbound (only opens e.g. 22/80/443), `53317` is already blocked
|
||||||
|
from the internet yet still reachable over the tailnet (tailscaled delivers it
|
||||||
|
via loopback) — nothing more to do.
|
||||||
|
|
||||||
|
Always set a **`--pin`** as a second layer regardless.
|
||||||
|
|
||||||
|
> **Verifying** the port is closed: don't trust `nc -z`, `telnet`, or
|
||||||
|
> `/dev/tcp` — some hosting providers (Hostinger, DigitalOcean, …) answer the TCP
|
||||||
|
> handshake (SYN/ACK) for *every* port at their network edge, so those tools
|
||||||
|
> report a firewalled port as "open". Only an **app-layer** probe is truthful:
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> curl -sk https://<public-ip>:53317/api/localsend/v2/info # should time out / hang
|
||||||
|
> curl -sk https://<tailnet-ip>:53317/api/localsend/v2/info # returns device info
|
||||||
|
> ```
|
||||||
|
|
||||||
### Right-click send (Nautilus)
|
### Right-click send (Nautilus)
|
||||||
|
|
||||||
|
|
|
||||||
38
install.sh
38
install.sh
|
|
@ -270,6 +270,13 @@ if [ -f /.dockerenv ] || grep -qaE 'docker|containerd|kubepods' /proc/1/cgroup 2
|
||||||
IN_CONTAINER=1
|
IN_CONTAINER=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# A routable public IPv4 means $PORT is reachable from the internet unless
|
||||||
|
# firewalled. Excludes loopback, link-local, RFC1918 and CGNAT/Tailscale
|
||||||
|
# (100.64.0.0/10). Empty when the box is purely on private/tailnet addresses.
|
||||||
|
PUBLIC_IP="$(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 \
|
||||||
|
| grep -vE '^(10\.|127\.|169\.254\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.)' \
|
||||||
|
| head -n1 || true)"
|
||||||
|
|
||||||
# ---- remote server: restrict the port to the Tailscale network -----------
|
# ---- remote server: restrict the port to the Tailscale network -----------
|
||||||
# On a public-IP box, port 53317 would otherwise be reachable from the internet
|
# On a public-IP box, port 53317 would otherwise be reachable from the internet
|
||||||
# (the receiver binds all interfaces). Lock it to the Tailscale interface so it
|
# (the receiver binds all interfaces). Lock it to the Tailscale interface so it
|
||||||
|
|
@ -323,6 +330,37 @@ if [ "$MODE" = "remote" ]; then
|
||||||
echo " Tip: a PIN adds a second layer — run with --pin <code> (or set it in Settings)."
|
echo " Tip: a PIN adds a second layer — run with --pin <code> (or set it in Settings)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---- local mode on a public-IP box: inform, don't touch the firewall -----
|
||||||
|
# We never change the firewall outside remote mode, but a public IP means the
|
||||||
|
# port is internet-exposed while the TUI is open — so surface it with the exact
|
||||||
|
# commands to lock it down. (Covers the silent `curl | bash` default-to-local
|
||||||
|
# case, where the interactive remote prompt never ran.)
|
||||||
|
if [ "$MODE" != "remote" ] && [ -n "$PUBLIC_IP" ]; then
|
||||||
|
iface="${TS_IFACE:-tailscale0}"
|
||||||
|
echo
|
||||||
|
echo "⚠ Heads up: this machine has a public IP ($PUBLIC_IP) and was installed in"
|
||||||
|
echo " LOCAL mode, so port $PORT was NOT firewalled. The receiver binds all"
|
||||||
|
echo " interfaces, so $PORT is reachable from the internet while the TUI is open."
|
||||||
|
echo " The installer won't change your firewall without remote mode — lock it to"
|
||||||
|
echo " your tailnet yourself (recommended):"
|
||||||
|
if [ "$IN_CONTAINER" = "1" ]; then
|
||||||
|
echo " • You're in a container — apply on the HOST, not in here: ufw deny $PORT"
|
||||||
|
echo " (if the host already default-denies inbound, $PORT is already blocked"
|
||||||
|
echo " from the internet yet still reachable over the tailnet via loopback)."
|
||||||
|
elif command -v ufw >/dev/null 2>&1; then
|
||||||
|
echo " • ufw allow in on $iface to any port $PORT"
|
||||||
|
echo " • ufw deny $PORT"
|
||||||
|
else
|
||||||
|
echo " • nftables (inet filter, input chain):"
|
||||||
|
echo " iifname \"$iface\" tcp dport $PORT accept"
|
||||||
|
echo " tcp dport $PORT drop"
|
||||||
|
fi
|
||||||
|
echo " Or re-run to firewall it automatically: OMARCHY_SEND_MODE=remote bash install.sh"
|
||||||
|
echo " And/or set a PIN: omarchy-send --pin <code>"
|
||||||
|
echo " Verify with an app-layer probe (raw TCP/nc lie behind some providers):"
|
||||||
|
echo " curl -sk https://<public-ip>:$PORT/api/localsend/v2/info # should time out"
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
case ":$PATH:" in
|
case ":$PATH:" in
|
||||||
*":$BIN_DIR:"*) : ;;
|
*":$BIN_DIR:"*) : ;;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue