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 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
+ [![ci](https://github.com/meabed/tailscale-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/meabed/tailscale-proxy/actions/workflows/ci.yml)
4
+ [![release](https://github.com/meabed/tailscale-proxy/actions/workflows/release.yml/badge.svg)](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
@@ -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
+ }