Skip to content

Commit c7eec4b

Browse files
committed
Fix tailscale corner cases
1 parent da3ae15 commit c7eec4b

File tree

4 files changed

+37
-19
lines changed

4 files changed

+37
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Fix remote server failing to bind when other services (e.g. Tailscale) hold the same ports on different network interfaces, by falling back to `127.0.0.1` when `0.0.0.0` is unavailable.
6+
57
## 0.116.2
68

79
- Fix stopping a prompt corruping other cases of chat.

docs/config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,8 @@
354354
},
355355
"port": {
356356
"type": "integer",
357-
"description": "Port the HTTP server listens on. When unset, tries port 7777, then 7778, 7779, etc. up to 10 attempts.",
358-
"markdownDescription": "Port the HTTP server listens on. When unset, tries port `7777`, then `7778`, `7779`, etc. up to 10 attempts.",
357+
"description": "Port the HTTP server listens on. When unset, tries port 7777, then 7778, 7779, etc. up to 20 attempts.",
358+
"markdownDescription": "Port the HTTP server listens on. When unset, tries port `7777`, then `7778`, `7779`, etc. up to 20 attempts.",
359359
"examples": [
360360
7777
361361
]

docs/config/remote.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ There are three ways to connect the web frontend to your ECA session. Pick the o
3838
3. Chrome will show a **Local Network Access** permission prompt — click **Allow**
3939

4040
!!! note "Firewall"
41-
Make sure your firewall allows incoming TCP connections on ports `7777`–`7787` (the default ECA port range) from your LAN.
41+
Make sure your firewall allows incoming TCP connections on ports `7777`–`7796` (the default ECA port range) from your LAN.
4242
ECA starts using `7777` and increments for each server running on your machine.
4343

4444
=== "Tailscale / VPN"
@@ -60,14 +60,18 @@ There are three ways to connect the web frontend to your ECA session. Pick the o
6060
**Steps:**
6161

6262
1. Install Tailscale and enable [HTTPS certificates](https://tailscale.com/kb/1153/enabling-https)
63-
2. Expose the ECA port range via Tailscale serve so they are reachable over your tailnet:
63+
2. Expose the ECA port range via Tailscale HTTPS serve so they are reachable over your tailnet:
6464

6565
```bash
66-
sudo tailscale serve --bg --tcp 7777 tcp://localhost:7777
67-
sudo tailscale serve --bg --tcp 7778 tcp://localhost:7778
68-
# ... repeat for as many ports as you need (7777–7787)
66+
sudo tailscale serve --bg --https 7777 http://localhost:7777
67+
sudo tailscale serve --bg --https 7778 http://localhost:7778
68+
# ... repeat for as many ports as you need (7777–7796)
6969
```
7070

71+
!!! warning "Use `--https`, not `--tcp`"
72+
`--tcp` creates a raw TCP proxy that browsers cannot connect to.
73+
`--https` creates a proper HTTPS reverse proxy with valid certificates, which is required for browser-based access from `web.eca.dev`.
74+
7175
3. Start ECA — it will log the connection URL and auth token to stderr. The URL is a deep-link you can open directly:
7276

7377
```
@@ -117,7 +121,7 @@ All connection methods support the same config options:
117121
"enabled": true,
118122
// optional — useful for specifying custom dns like tailscale or your local ip, just for logging purposes in stderr/welcome message
119123
"host": "192.168.1.42",
120-
// optional — defaults to 7777 (auto-increments until 7787 if busy)
124+
// optional — defaults to 7777 (auto-increments until 7796 if busy)
121125
"port": 9876,
122126
// optional — a random pass is auto-generated when unset, you can use ${env:...}
123127
"password": "my-secret-token"
@@ -129,6 +133,6 @@ All connection methods support the same config options:
129133

130134
The web frontend provides a connect form where you enter the host and password (or use the deep-link URL logged by ECA).
131135

132-
**Auto-discovery** — When you enter a host and click "Discover", the web UI scans ports `7777``7787` in parallel and finds all running ECA instances automatically. This is the default port range ECA uses when auto-assigning ports.
136+
**Auto-discovery** — When you enter a host and click "Discover", the web UI scans ports `7777``7796` in parallel and finds all running ECA instances automatically. This is the default port range ECA uses when auto-assigning ports.
133137

134138
**Multi-session** — You can connect to multiple ECA instances simultaneously. Each connection appears as a tab in the top bar, letting you switch between sessions.

src/eca/remote/server.clj

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,35 @@
6363
(.isSiteLocalAddress (InetAddress/getByName ip))
6464
(catch Exception _ false)))
6565

66-
(defn ^:private try-start-jetty
67-
"Attempts to start Jetty on the given port. Returns the Server on success,
68-
nil when the port is already in use (BindException or Jetty's wrapping IOException)."
66+
(def ^:private bind-hosts
67+
"Ordered list of bind addresses to try per port.
68+
0.0.0.0 gives full connectivity; 127.0.0.1 is the fallback when another
69+
service (e.g. Tailscale) already holds the port on a specific interface."
70+
["0.0.0.0" "127.0.0.1"])
71+
72+
(defn ^:private try-start-jetty-any-host
73+
"Tries to start Jetty on the given port, attempting each bind address in
74+
bind-hosts order. Returns the Server on success, nil if all fail."
6975
^Server [handler port]
70-
(try
71-
(jetty/run-jetty handler {:port port :host "0.0.0.0" :join? false})
72-
(catch BindException _ nil)
73-
(catch IOException _ nil)))
76+
(reduce (fn [_ bind-host]
77+
(try
78+
(let [server (jetty/run-jetty handler {:port port :host bind-host :join? false})]
79+
(logger/debug logger-tag (str "Bound to " bind-host ":" port))
80+
(reduced server))
81+
(catch BindException _ nil)
82+
(catch IOException _ nil)))
83+
nil
84+
bind-hosts))
7485

7586
(defn ^:private start-with-retry
7687
"Tries sequential ports starting from base-port up to max-port-attempts.
88+
For each port, tries all bind-hosts before moving to the next port.
7789
Returns [server actual-port] on success, nil if all attempts fail."
7890
[handler base-port]
7991
(loop [port base-port
8092
attempts 0]
8193
(when (< attempts max-port-attempts)
82-
(if-let [server (try-start-jetty handler port)]
94+
(if-let [server (try-start-jetty-any-host handler port)]
8395
[server (.getLocalPort ^NetworkConnector (first (.getConnectors ^Server server)))]
8496
(do (logger/debug logger-tag (str "Port " port " in use, trying " (inc port) "..."))
8597
(recur (inc port) (inc attempts)))))))
@@ -106,8 +118,8 @@
106118
(try
107119
(if-let [[^Server jetty-server actual-port]
108120
(if user-port
109-
;; User-specified port: single attempt, no retry
110-
(if-let [server (try-start-jetty handler user-port)]
121+
;; User-specified port: single attempt, try all bind hosts
122+
(if-let [server (try-start-jetty-any-host handler user-port)]
111123
[server (.getLocalPort ^NetworkConnector (first (.getConnectors ^Server server)))]
112124
(do (logger/warn logger-tag "Port" user-port "is already in use."
113125
"Remote server will not start.")

0 commit comments

Comments
 (0)