tailscale-proxy 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/launcher.js +34 -0
- package/package.json +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohamed Meabed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# tailscale-proxy (`tsp`)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/meabed/tailscale-proxy/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/meabed/tailscale-proxy/actions/workflows/release.yml)
|
|
5
|
+
|
|
6
|
+
Discover your local dev servers by **port**, and expose them through a **single
|
|
7
|
+
Tailscale entry** — privately (Serve, tailnet-only) or publicly (Funnel) — routed
|
|
8
|
+
by **project name**.
|
|
9
|
+
|
|
10
|
+
No per-app wiring: just run your servers (`node`, `bun`, `deno`, `python`, `php`, `ruby`, `go`, `java`, …) and `tsp` finds the ones listening in a port range, derives a path slug
|
|
11
|
+
from each project's folder, and routes to them under one hostname:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
https://<node>.ts.net/<project>/foo
|
|
15
|
+
└────┬────┘
|
|
16
|
+
tsp strips the segment, forwards → 127.0.0.1:<port>/foo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
It re-scans every few seconds (so servers that come and go are picked up), keeps a
|
|
20
|
+
service around for a few scans before de-registering (no flapping on restarts), and
|
|
21
|
+
streams SSE / proxies WebSocket upgrades. Zero runtime dependencies — one small Go
|
|
22
|
+
binary.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 0. One-time: install Tailscale, sign in, enable Funnel (see Requirements below)
|
|
30
|
+
tailscale up
|
|
31
|
+
|
|
32
|
+
# 1. Start any dev servers (each in its own project folder → that's the URL path)
|
|
33
|
+
cd ~/sites/portfolio && npx serve -l 3000 # static site (node)
|
|
34
|
+
cd ~/apps/web && npx next dev -p 4000 # Next.js app (node)
|
|
35
|
+
cd ~/apps/api && bun run dev # API (bun, e.g. :4100)
|
|
36
|
+
|
|
37
|
+
# 2. Check your environment, then share them all through one Tailscale URL
|
|
38
|
+
npx tailscale-proxy doctor
|
|
39
|
+
npx tailscale-proxy # "start" is the default command
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Services:
|
|
44
|
+
https://bigfoot.tail-scale.ts.net/portfolio/ → 127.0.0.1:3000
|
|
45
|
+
https://bigfoot.tail-scale.ts.net/web/ → 127.0.0.1:4000
|
|
46
|
+
https://bigfoot.tail-scale.ts.net/api/ → 127.0.0.1:4100
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Open any of those from anywhere (Funnel) or from your tailnet (`--private` Serve).
|
|
50
|
+
<kbd>Ctrl-C</kbd> resets the Serve/Funnel entry on exit.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Save preferences once, then a bare `tsp` uses them:
|
|
54
|
+
npx tailscale-proxy configure --ports 3000-9000 --runtimes node,bun,python
|
|
55
|
+
npx tailscale-proxy
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> Non-JS servers (`python3 -m http.server`, `php -S`, `ruby -run -e httpd`) are
|
|
59
|
+
> included with `--all` or `--runtimes node,python,…`.
|
|
60
|
+
|
|
61
|
+
**👉 Full setup + lots of real examples: [docs/EXAMPLES.md](docs/EXAMPLES.md)**
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
| Method | Command |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| **npx** (no install) | `npx tailscale-proxy <command>` |
|
|
70
|
+
| **npm** (global) | `npm i -g tailscale-proxy` |
|
|
71
|
+
| **Homebrew** | `brew install meabed/tap/tsp` |
|
|
72
|
+
|
|
73
|
+
Supported: **macOS, Linux, Windows, WSL** (amd64 + arm64).
|
|
74
|
+
(`go install github.com/meabed/tailscale-proxy@latest` also works if you have Go.)
|
|
75
|
+
|
|
76
|
+
Update later with **`tsp update`** — it self-updates a standalone binary, or prints
|
|
77
|
+
`brew upgrade tsp` / `npm i -g tailscale-proxy@latest` for managed installs.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Commands
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
tsp [flags] Default: run "start" with your saved config
|
|
85
|
+
tsp start Discover services, run the proxy, and expose it
|
|
86
|
+
tsp status Serve/Funnel status + the current service map
|
|
87
|
+
tsp list Discovered services (slug → runtime, port, project, URL)
|
|
88
|
+
tsp reset Remove the Serve/Funnel entry and exit
|
|
89
|
+
tsp doctor Check tailscale, exposure readiness, and discovery
|
|
90
|
+
tsp configure Save defaults to ~/.tailscale-proxy/config.json
|
|
91
|
+
tsp update Update to the latest release
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run `tsp start --help` for all flags. Global: `-h/--help`, `-v/--version`.
|
|
95
|
+
|
|
96
|
+
### `start` flags (defaults come from your config)
|
|
97
|
+
|
|
98
|
+
| Flag | Default | Meaning |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `--ports <lo-hi\|port>` | `3000-5000` | Port range **or a single port** to scan |
|
|
101
|
+
| `--all` | off | Include all listeners, not just web runtimes |
|
|
102
|
+
| `--runtimes <list>` | all known | Restrict to specific runtimes, e.g. `node,bun,python` |
|
|
103
|
+
| `--private` | off | Expose privately via Tailscale **Serve** (default: **Funnel**) |
|
|
104
|
+
| `--port <n>` | `8443` | Local proxy HTTP port |
|
|
105
|
+
| `--interval <sec>` | `20` | Re-scan period |
|
|
106
|
+
| `--https-port <n>` | `443` | Public/tailnet HTTPS port (Funnel: `443`/`8443`/`10000`) |
|
|
107
|
+
| `--deregister-cycles <n>` | `5` | Missing scans before a gone service is removed |
|
|
108
|
+
| `--forward-host` | off | Forward the public host to apps via `X-Forwarded-Host/Proto`. Default presents a **local** request so apps behave exactly like `localhost` |
|
|
109
|
+
| `--bg` | off | Run detached (logs → `./tsp.log`) |
|
|
110
|
+
| `--proxy-only` | off | Run the proxy only; print the `tailscale` command |
|
|
111
|
+
| `--log-requests` | on | Log each proxied request |
|
|
112
|
+
| `--quiet` | off | Disable per-request logging |
|
|
113
|
+
|
|
114
|
+
On startup `tsp` prints whether it loaded your config and the effective parameters,
|
|
115
|
+
then logs each discovered service and any de-registration:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
Using config: /Users/me/.tailscale-proxy/config.json
|
|
119
|
+
ports=3000-5000 mode=public (Funnel) proxy=127.0.0.1:8443 https=443
|
|
120
|
+
interval=20s runtimes=default (node,bun,deno,python,ruby,php,go,java,…) deregister-after=5 scans log-requests=true
|
|
121
|
+
|
|
122
|
+
2026/05/31 02:05:48 discovered help-ai-web node :4983 ~/work/help-ai/apps/web
|
|
123
|
+
2026/05/31 02:05:49 200 GET /help-ai-web/ → 127.0.0.1:4983 (6ms)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Request logs are colorized by status on a terminal (set `NO_COLOR` to disable).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Configuration
|
|
131
|
+
|
|
132
|
+
`tsp configure [flags]` writes `~/.tailscale-proxy/config.json` (created on first
|
|
133
|
+
use). Flags override config at runtime; the file is the source of defaults.
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"ports": "3000-5000", "all": false, "runtimes": "", "private": false,
|
|
138
|
+
"port": 8443, "interval": 20, "httpsPort": 443,
|
|
139
|
+
"logRequests": true, "deregisterCycles": 5, "forwardHost": false
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Requirements
|
|
146
|
+
|
|
147
|
+
1. **[Tailscale](https://tailscale.com/download)**, logged in:
|
|
148
|
+
```bash
|
|
149
|
+
tailscale up # opens a browser to sign up / log in
|
|
150
|
+
```
|
|
151
|
+
2. For **public** exposure (Funnel) — *not* needed for `--private` Serve:
|
|
152
|
+
- Enable **HTTPS certificates**: admin console → DNS → MagicDNS + HTTPS
|
|
153
|
+
([docs](https://tailscale.com/kb/1153/enabling-https)).
|
|
154
|
+
- Grant the **`funnel`** node attribute in your tailnet policy file
|
|
155
|
+
(admin console → Access controls):
|
|
156
|
+
```jsonc
|
|
157
|
+
{ "nodeAttrs": [ { "target": ["autogroup:member"], "attr": ["funnel"] } ] }
|
|
158
|
+
```
|
|
159
|
+
([Funnel docs](https://tailscale.com/kb/1223/funnel))
|
|
160
|
+
3. **`lsof`** on macOS/Linux (macOS ships it; Linux: `apt/dnf install lsof`).
|
|
161
|
+
Windows uses `netstat`/`tasklist` (built in).
|
|
162
|
+
|
|
163
|
+
Run `tsp doctor` — it checks all of the above and prints the exact fix link.
|
|
164
|
+
Step-by-step with screenshots-worth-of-detail: **[docs/EXAMPLES.md](docs/EXAMPLES.md)**.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## How it works
|
|
169
|
+
|
|
170
|
+
1. Every `--interval` seconds, `tsp` lists listening TCP sockets in the range
|
|
171
|
+
(macOS/Linux via `lsof`+`ps`, Windows via `netstat`+`tasklist`), classifies the
|
|
172
|
+
runtime, and derives a slug from the nearest project-root folder
|
|
173
|
+
(`package.json`/`.git`/…), de-duplicating collisions.
|
|
174
|
+
2. A `net/http` reverse proxy matches the first path segment to a service, strips
|
|
175
|
+
it, rewrites `Host`, and forwards to `127.0.0.1:<port>` (streaming + WebSocket
|
|
176
|
+
preserved, bounded connection pool).
|
|
177
|
+
3. `tailscale serve|funnel --bg <proxy-port>` exposes the proxy. On exit the entry
|
|
178
|
+
is reset.
|
|
179
|
+
|
|
180
|
+
More in [docs/HOW-IT-WORKS.md](docs/HOW-IT-WORKS.md).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Troubleshooting
|
|
185
|
+
|
|
186
|
+
`tsp doctor` first. Common issues (full list in
|
|
187
|
+
[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)):
|
|
188
|
+
|
|
189
|
+
- **Works from my phone but not my Mac** — from the host, MagicDNS resolves
|
|
190
|
+
`<node>.ts.net` to your tailnet IP, so requests may not traverse the public
|
|
191
|
+
Funnel. Test from outside your tailnet (see the doc for how to force it locally).
|
|
192
|
+
- **No services found** — start a dev server in range, widen `--ports`, or `--all`.
|
|
193
|
+
- **`lsof` not found** — install it (`apt/dnf install lsof`).
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
go test ./... # run the test suite
|
|
201
|
+
go vet ./... # static checks
|
|
202
|
+
go build -o tsp . # build the binary
|
|
203
|
+
goreleaser release --snapshot --clean --skip=publish # full cross-platform build
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
CI builds, vets, and race-tests on Linux/macOS/Windows and cross-compiles all six
|
|
207
|
+
release targets on every push. Releases are tag-driven — see
|
|
208
|
+
[docs/RELEASING.md](docs/RELEASING.md).
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
[MIT](LICENSE) © Mohamed Meabed
|
package/bin/launcher.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
// Map Node's platform/arch to our per-platform package + binary name.
|
|
7
|
+
function resolveBinary() {
|
|
8
|
+
const platform = process.platform; // 'darwin' | 'linux' | 'win32'
|
|
9
|
+
const arch = process.arch; // 'x64' | 'arm64'
|
|
10
|
+
const pkg = `tailscale-proxy-${platform}-${arch}`;
|
|
11
|
+
const exe = platform === "win32" ? "tsp.exe" : "tsp";
|
|
12
|
+
try {
|
|
13
|
+
return require.resolve(`${pkg}/bin/${exe}`);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const bin = resolveBinary();
|
|
20
|
+
if (!bin) {
|
|
21
|
+
console.error(
|
|
22
|
+
`tailscale-proxy: no prebuilt binary for ${process.platform}-${process.arch}.\n` +
|
|
23
|
+
`Install from source: go install github.com/meabed/tailscale-proxy@latest\n` +
|
|
24
|
+
`or download a release: https://github.com/meabed/tailscale-proxy/releases`
|
|
25
|
+
);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const res = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
30
|
+
if (res.error) {
|
|
31
|
+
console.error(res.error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
process.exit(res.status === null ? 1 : res.status);
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tailscale-proxy",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Discover local dev servers by port and expose them through one Tailscale Serve/Funnel entry, routed by project name.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tailscale",
|
|
7
|
+
"tailscale-funnel",
|
|
8
|
+
"tailscale-serve",
|
|
9
|
+
"funnel",
|
|
10
|
+
"serve",
|
|
11
|
+
"reverse-proxy",
|
|
12
|
+
"proxy",
|
|
13
|
+
"dev",
|
|
14
|
+
"discovery",
|
|
15
|
+
"localhost",
|
|
16
|
+
"tunnel"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/meabed/tailscale-proxy#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/meabed/tailscale-proxy/issues",
|
|
21
|
+
"email": "mo@meabed.com"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/meabed/tailscale-proxy.git"
|
|
26
|
+
},
|
|
27
|
+
"funding": {
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/meabed"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "Mohamed Meabed",
|
|
34
|
+
"email": "mo@meabed.com",
|
|
35
|
+
"url": "https://meabed.com"
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"tailscale-proxy": "bin/launcher.js",
|
|
39
|
+
"tsp": "bin/launcher.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"bin/launcher.js",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"lint": "gofmt -l . && go vet ./...",
|
|
47
|
+
"format": "gofmt -w .",
|
|
48
|
+
"test": "go test ./...",
|
|
49
|
+
"test-ci": "go test -race -count=1 ./...",
|
|
50
|
+
"build:binaries": "goreleaser release --clean --snapshot --skip=publish,announce",
|
|
51
|
+
"build:npm": "node npm/build-platform-packages.mjs",
|
|
52
|
+
"prepare": "if [ -z \"$CI\" ] && [ -e .git ] && [ -x ./node_modules/.bin/husky ]; then ./node_modules/.bin/husky; fi",
|
|
53
|
+
"release": "semantic-release",
|
|
54
|
+
"release:dry": "semantic-release --dry-run",
|
|
55
|
+
"release:local": "node scripts/release-local.mjs"
|
|
56
|
+
},
|
|
57
|
+
"lint-staged": {
|
|
58
|
+
"*.go": [
|
|
59
|
+
"gofmt -w"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
64
|
+
"@semantic-release/exec": "^7.1.0",
|
|
65
|
+
"@semantic-release/github": "^12.0.8",
|
|
66
|
+
"@semantic-release/npm": "^13.1.5",
|
|
67
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
68
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
69
|
+
"husky": "^9.1.7",
|
|
70
|
+
"lint-staged": "^17.0.6",
|
|
71
|
+
"semantic-release": "^25.0.3"
|
|
72
|
+
},
|
|
73
|
+
"optionalDependencies": {
|
|
74
|
+
"tailscale-proxy-darwin-arm64": "0.0.1",
|
|
75
|
+
"tailscale-proxy-darwin-x64": "0.0.1",
|
|
76
|
+
"tailscale-proxy-linux-x64": "0.0.1",
|
|
77
|
+
"tailscale-proxy-linux-arm64": "0.0.1",
|
|
78
|
+
"tailscale-proxy-win32-x64": "0.0.1",
|
|
79
|
+
"tailscale-proxy-win32-arm64": "0.0.1"
|
|
80
|
+
},
|
|
81
|
+
"packageManager": "bun@1.3.14",
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">= 18.0"
|
|
84
|
+
},
|
|
85
|
+
"publishConfig": {
|
|
86
|
+
"access": "public",
|
|
87
|
+
"provenance": true
|
|
88
|
+
}
|
|
89
|
+
}
|