locadot 1.5.7 → 1.6.0-beta.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/CHANGELOG.md +45 -0
- package/LICENSE +21 -21
- package/README.md +184 -138
- package/dist/constants/index.js +31 -11
- package/dist/constants/template.js +60 -63
- package/dist/core.js +12 -11
- package/dist/dashboard/api.js +221 -0
- package/dist/dashboard/escape.js +13 -0
- package/dist/dashboard/index.js +110 -0
- package/dist/dashboard/page.js +1201 -0
- package/dist/index.js +77 -117
- package/dist/lib/commands.js +259 -81
- package/dist/lib/hosts.js +100 -0
- package/dist/lib/http.js +86 -52
- package/dist/lib/locadot-file.js +54 -88
- package/dist/lib/localhost.js +90 -21
- package/dist/lib/registry.js +96 -0
- package/dist/lib/system.js +74 -0
- package/dist/proxy.js +93 -112
- package/dist/server.js +185 -0
- package/dist/types.js +2 -0
- package/dist/utils/certs.js +126 -29
- package/dist/utils/file.js +79 -152
- package/dist/utils/logger.js +8 -10
- package/dist/utils/startup.js +122 -75
- package/dist/utils/trust.js +239 -0
- package/package.json +7 -6
- package/dist/hooks/useDebounce.js +0 -14
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
The version in `package.json` is still 1.5.7. The owner decides the bump. **2.0.0** is suggested because of the
|
|
6
|
+
breaking items below.
|
|
7
|
+
|
|
8
|
+
### Breaking
|
|
9
|
+
- The proxy binds to `127.0.0.1` and `::1` only. Set `LOCADOT_BIND=0.0.0.0` for the old LAN-exposed behaviour.
|
|
10
|
+
- The registry format is now v2 (`{version, hosts:{host:{target,…}}}`). The old `{host: port}` files are migrated
|
|
11
|
+
automatically, but older locadot versions can't read the new format.
|
|
12
|
+
- `-h` means `--host` on every command. Help is `--help` only.
|
|
13
|
+
- Bare `localhost` is reserved for the dashboard and can't be mapped.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- A control panel at `https://localhost` with a dark theme. You can add, edit and remove mappings, toggle CA trust and start-at-boot, see root/admin and privileged-port status,
|
|
17
|
+
tail and clear logs, and stop the proxy. (FEAT-05)
|
|
18
|
+
- A token-protected JSON API for scripts and AI agents (`POST/PUT/DELETE /api/hosts`, `/api/startup`, `/api/trust`, `/api/logs`, `/api/proxy/stop`),
|
|
19
|
+
and `locadot token` to print the token. (FEAT-05)
|
|
20
|
+
- Generic targets: `--target` takes a port, `host:port` or any http(s) URL (`google.localhost` → `https://google.com`).
|
|
21
|
+
`--insecure` accepts self-signed upstreams. Redirects and cookies are rewritten back to the `.localhost` name. (ENH-08)
|
|
22
|
+
- A dashboard at `http(s)://localhost` shows each domain, its target, health, hits, errors and latency, with `/api/status`,
|
|
23
|
+
`/api/hosts` and `/healthz`. (ENH-09)
|
|
24
|
+
- New commands and options: `status [--json]`, `doctor [--host]`, `open [host]`, `start`, `trust`, `untrust`,
|
|
25
|
+
`logs -n/--no-follow`, `list --json`, the `rm`/`ls` aliases, `--no-start`, and a full `path` listing. (ENH-02, ENH-04, ENH-10, FEAT-01, FEAT-02)
|
|
26
|
+
- A trusted local CA with one certificate per domain served by SNI, pre-generated when a mapping loads. (BUG-02)
|
|
27
|
+
- Environment variables `LOCADOT_HOME`, `LOCADOT_HTTP_PORT`, `LOCADOT_HTTPS_PORT`, `LOCADOT_BIND` and `LOCADOT_LOG_LEVEL`.
|
|
28
|
+
- Test suite (`pnpm test`) and a `prepublishOnly` build.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- Upstream `Connection`/`Upgrade` headers are no longer forwarded. Apache sites proxied through locadot kept dropping the connection and lost assets. (BUG-12)
|
|
32
|
+
- `stop`, `restart` and `kill` now actually stop the proxy. (BUG-01)
|
|
33
|
+
- A failed start is reported with the log tail and exits 1. (BUG-07)
|
|
34
|
+
- `update` accepts http targets and `add` accepts https ones. (BUG-03)
|
|
35
|
+
- Ports are validated. (BUG-06)
|
|
36
|
+
- An unknown host passed to `update` or `remove` exits 1. (BUG-10)
|
|
37
|
+
- An unmapped host no longer causes a double response or a crash. (BUG-04)
|
|
38
|
+
- The 502 page escapes the Host header. (BUG-05)
|
|
39
|
+
- `startup:*` is consistent, and a proxy started at boot is visible to the CLI. (BUG-08)
|
|
40
|
+
- Logs are kept across a restart. (BUG-09)
|
|
41
|
+
- An empty lock file is no longer read as a running proxy. (BUG-11)
|
|
42
|
+
- Every command exits non-zero on failure. (ENH-01)
|
|
43
|
+
- Registry writes are atomic and locked, and a corrupt registry is backed up instead of wiped. (ENH-03)
|
|
44
|
+
- The log is rotated above 5 MB. (ENH-05)
|
|
45
|
+
- The proxy no longer loses its stderr when the CLI exits, and http-proxy's deprecation noise is silenced. (ENH-11)
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Avinash Kumar
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Avinash Kumar
|
|
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
CHANGED
|
@@ -1,138 +1,184 @@
|
|
|
1
|
-
# locadot 🔐
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1
|
+
# locadot 🔐
|
|
2
|
+
|
|
3
|
+
HTTPS custom domains for local development. Point `https://app.localhost` at your dev server on port 3000, or
|
|
4
|
+
`https://google.localhost` at any upstream URL, and see every mapping on a dashboard at `https://localhost`.
|
|
5
|
+
|
|
6
|
+
## ✨ Features
|
|
7
|
+
|
|
8
|
+
- ✅ **Trusted HTTPS for every domain.** locadot creates one local CA and issues a certificate per domain, served by SNI.
|
|
9
|
+
Run `locadot trust` once to add the CA to your system and browsers.
|
|
10
|
+
- 🔁 **Any destination.** A local port (`--port 3000`), a host and port (`--target 192.168.1.5:8080`), or any URL
|
|
11
|
+
(`--target https://google.com`). Redirects and cookies are rewritten so the browser stays on the `.localhost` name.
|
|
12
|
+
- 📋 **Control panel at `https://localhost`.** Add, edit and remove mappings, see health and traffic, and toggle CA trust and
|
|
13
|
+
start-at-boot. Includes a token-protected JSON API, so scripts and AI agents can do everything the page does.
|
|
14
|
+
- 🔌 **WebSockets** are proxied too, so HMR and dev-server live reload work.
|
|
15
|
+
- 🩺 **`status` and `doctor`** show why a domain isn't working: port conflicts, permissions, CA trust, a target that isn't answering.
|
|
16
|
+
- 🛡️ **Local by default.** The proxy binds to `127.0.0.1` / `::1` only. Domains must end in `.localhost`, which browsers
|
|
17
|
+
resolve to your machine with no hosts-file edits.
|
|
18
|
+
- 🖥️ Works on Linux, macOS and Windows. Optionally starts at boot or logon.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 Quick start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx locadot trust # once: trust the locadot CA (asks for your password)
|
|
26
|
+
npx locadot add --host app.localhost --port 3000 # https://app.localhost → http://localhost:3000
|
|
27
|
+
npx locadot add --host google.localhost --target https://google.com
|
|
28
|
+
npx locadot open # opens the dashboard at https://localhost
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Linux:** ports 80/443 need root. Either run locadot with `sudo`, or allow unprivileged binding once:
|
|
32
|
+
> `sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80` (persist it in `/etc/sysctl.d/`). `locadot doctor` checks this for you.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 📦 Commands
|
|
37
|
+
|
|
38
|
+
### Mappings
|
|
39
|
+
|
|
40
|
+
| Command | What it does |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `locadot add --host <name>.localhost --port <port>` | Map a domain to `http://localhost:<port>`. Starts the proxy if it isn't running. |
|
|
43
|
+
| `locadot add --host <name>.localhost --target <url>` | Map a domain to any upstream: `3000`, `127.0.0.1:8080`, `http://10.0.0.5:8080/app`, `https://google.com`. |
|
|
44
|
+
| `locadot update --host <name>.localhost --port/--target …` | Change the destination of an existing domain. |
|
|
45
|
+
| `locadot remove --host <name>.localhost` (`rm`) | Remove a domain. |
|
|
46
|
+
| `locadot list` (`ls`, `host`) `[--json]` | Show all mappings. |
|
|
47
|
+
| `locadot clear:hosts` | Remove all mappings. |
|
|
48
|
+
| `locadot open [host]` | Open a domain, or the dashboard when no host is given, in your browser. |
|
|
49
|
+
|
|
50
|
+
Options for `add` / `update`:
|
|
51
|
+
|
|
52
|
+
| Option | Meaning |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `-p, --port <port>` | Shorthand for `--target http://localhost:<port>`. |
|
|
55
|
+
| `-t, --target <url>` | Any http(s) upstream. A bare `host:port` means `http://host:port`. |
|
|
56
|
+
| `-k, --insecure` | Don't verify the TLS certificate of an `https` target (self-signed upstreams). |
|
|
57
|
+
| `--no-start` | Save the mapping without starting the proxy. |
|
|
58
|
+
|
|
59
|
+
### Proxy
|
|
60
|
+
|
|
61
|
+
| Command | What it does |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `locadot start` | Start the central proxy. Fails loudly with the reason if it can't bind its ports. |
|
|
64
|
+
| `locadot stop` | Stop the proxy. Mappings and logs are kept. |
|
|
65
|
+
| `locadot restart` | Stop, then start. |
|
|
66
|
+
| `locadot kill` | Stop the proxy, remove all mappings and clear the logs. |
|
|
67
|
+
| `locadot status [--json]` | Is it running, its PID, ports, number of hosts, CA trust, start-at-boot, dashboard URL. |
|
|
68
|
+
| `locadot doctor [--host h]` | Check the proxy, ports, permissions, CA trust, the registry and every target. Exits 1 if anything fails. |
|
|
69
|
+
|
|
70
|
+
### Certificates
|
|
71
|
+
|
|
72
|
+
| Command | What it does |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `locadot trust` | Add the locadot CA to the system trust store, plus the Chrome/Firefox NSS stores on Linux when `certutil` is installed. |
|
|
75
|
+
| `locadot untrust` | Remove it again. |
|
|
76
|
+
|
|
77
|
+
### Logs, files and startup
|
|
78
|
+
|
|
79
|
+
| Command | What it does |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `locadot logs [-n 50] [--no-follow]` | Print recent proxy logs and follow new ones. `watch:logs` is an alias. |
|
|
82
|
+
| `locadot clear:logs` | Clear the log file. |
|
|
83
|
+
| `locadot path` | Show every file locadot uses. `path:logs` and `path:hosts` print a single path. |
|
|
84
|
+
| `locadot token` | Print the dashboard API token, for scripts and AI agents. |
|
|
85
|
+
| `locadot startup:enable` / `startup:disable` / `startup:status` | Start the proxy at boot (Linux cron, macOS LaunchAgent) or logon (Windows scheduled task). |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 📋 Dashboard & control panel
|
|
90
|
+
|
|
91
|
+
Open `https://localhost` or `http://localhost` (or run `locadot open`). Requests for bare `localhost` / `127.0.0.1` / `::1`
|
|
92
|
+
are answered by locadot itself, not proxied. The page is a dark control panel:
|
|
93
|
+
|
|
94
|
+
- **System:** whether the proxy runs as root/admin, whether ports below 1024 can be bound (with the Linux sysctl fix when they
|
|
95
|
+
can't), CA trust with an on/off toggle, start-at-boot with an on/off toggle, ports, bind addresses and state dir. It also has a **Stop proxy** button.
|
|
96
|
+
- **Proxies:** add a mapping (host + port / host:port / URL, and an optional "insecure TLS" box), edit a target inline, or remove one. The table shows
|
|
97
|
+
each source → destination with up/down, status, latency, hits, errors, last access and average ms. It refreshes every 5 s.
|
|
98
|
+
- **Logs:** a live tail with refresh and clear.
|
|
99
|
+
- **CLI / API snippets** you can copy.
|
|
100
|
+
|
|
101
|
+
Trust and start-at-boot may need elevation. locadot asks the OS for it (polkit on Linux, the password dialog on macOS, UAC on Windows).
|
|
102
|
+
If that isn't possible, for example on a headless Linux box, the panel shows the exact command to run instead: `sudo locadot trust`,
|
|
103
|
+
`locadot startup:enable`, and so on.
|
|
104
|
+
|
|
105
|
+
### JSON API (for scripts and AI agents)
|
|
106
|
+
|
|
107
|
+
Everything the page does is a plain JSON API on the dashboard origin. The read routes are open to local callers. Every mutating route needs the
|
|
108
|
+
`X-Locadot-Token` header. Get the token with `locadot token`. It lives in `<state dir>/.locadot-token` (mode 0600) and is regenerated
|
|
109
|
+
each time the proxy starts.
|
|
110
|
+
|
|
111
|
+
| Method & path | Body | Does |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| `GET /api/status` | | Proxy info, uptime, host count, and `system` (`platform`, `isRoot`, `canBindPrivileged`, `unprivilegedPortStart`, `caTrusted`, `startup`, `stateDir`). |
|
|
114
|
+
| `GET /api/hosts` | | Every mapping with urls, probe (`up`, `status`, `ms`) and stats. |
|
|
115
|
+
| `GET /api/logs?lines=200` | | `{ lines: [...] }` |
|
|
116
|
+
| `POST /api/hosts` | `{ "host": "app.localhost", "target": "3000", "insecure": false }` | Add a mapping (201; 409 if it exists). |
|
|
117
|
+
| `PUT /api/hosts/:host` | `{ "target": "https://example.com", "insecure": false }` | Change a mapping (404 if unknown). |
|
|
118
|
+
| `DELETE /api/hosts/:host` | | Remove a mapping. |
|
|
119
|
+
| `POST /api/startup` | `{ "enabled": true }` | Start at boot on/off. |
|
|
120
|
+
| `POST /api/trust` | `{ "trusted": true }` | Trust or untrust the CA. |
|
|
121
|
+
| `POST /api/logs/clear` | | Clear the log. |
|
|
122
|
+
| `POST /api/proxy/stop` | | Stop the proxy. Use `locadot start` to bring it back. |
|
|
123
|
+
|
|
124
|
+
Errors are `{ "error": "...", "hint": "<CLI command>" }` with a 4xx/5xx status.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
TOKEN=$(locadot token)
|
|
128
|
+
curl -X POST http://localhost/api/hosts -H "X-Locadot-Token: $TOKEN" \
|
|
129
|
+
-H 'Content-Type: application/json' -d '{"host":"app.localhost","target":"3000"}'
|
|
130
|
+
curl -X DELETE http://localhost/api/hosts/app.localhost -H "X-Locadot-Token: $TOKEN"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Why a token:** any website you visit can make your browser send requests to `localhost`. Mutating routes therefore reject requests that:
|
|
134
|
+
- lack the token, which other sites can't read;
|
|
135
|
+
- carry a foreign `Origin`, including your own `*.localhost` apps;
|
|
136
|
+
- are marked `Sec-Fetch-Site: cross-site`;
|
|
137
|
+
- have a non-JSON body.
|
|
138
|
+
|
|
139
|
+
The dashboard is served only for the bare `localhost` host names, which also blocks DNS rebinding.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ⚙️ Configuration
|
|
144
|
+
|
|
145
|
+
All optional, via environment variables. Set them for the CLI; the proxy it starts inherits them.
|
|
146
|
+
|
|
147
|
+
| Variable | Default | Purpose |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `LOCADOT_HOME` | OS app-data dir (`~/.config/locadot` on Linux) | Where the registry, logs, lock file and certs live. |
|
|
150
|
+
| `LOCADOT_HTTP_PORT` | `80` | HTTP port of the proxy. |
|
|
151
|
+
| `LOCADOT_HTTPS_PORT` | `443` | HTTPS port of the proxy. |
|
|
152
|
+
| `LOCADOT_BIND` | `127.0.0.1,::1` | Comma-separated addresses to listen on. Use `0.0.0.0` to expose your mappings to your LAN (not recommended). |
|
|
153
|
+
| `LOCADOT_LOG_LEVEL` | `info` | winston log level (`debug` logs every WebSocket upgrade). |
|
|
154
|
+
|
|
155
|
+
With non-standard ports, URLs include the port: `https://app.localhost:8443`.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## ⚠️ Notes on remote targets
|
|
160
|
+
|
|
161
|
+
`--target https://google.com` proxies the upstream with `Host` rewritten to the upstream's name.
|
|
162
|
+
`Location` redirects pointing at the upstream's own host, and cookie domains, are rewritten to the `.localhost` name. A
|
|
163
|
+
redirect to a *different* domain (for example `google.com` → `www.google.com`) takes the browser there directly. To stay on
|
|
164
|
+
locadot, map the final host instead (`--target https://www.google.com`). Some sites refuse to be framed or proxied; that's up to
|
|
165
|
+
the site.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 🛠️ Contributing
|
|
170
|
+
|
|
171
|
+
The code guide, architecture, and the tracker of bugs, enhancements, features and tech debt live in
|
|
172
|
+
[`tasks/README.md`](tasks/README.md). Start there.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
pnpm install
|
|
176
|
+
pnpm build # the CLI spawns dist/core.js, so build before trying proxy changes
|
|
177
|
+
pnpm test # unit + end-to-end tests; runs on spare ports, never touches 80/443
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Pull requests are welcome.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
##### Made with ❤️ to make secure local development simple.
|
package/dist/constants/index.js
CHANGED
|
@@ -6,17 +6,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const appdata_path_1 = __importDefault(require("appdata-path"));
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// LOCADOT_HOME / LOCADOT_*_PORT let tests and hosts where 80/443 are taken run an isolated instance.
|
|
10
|
+
const PACKAGE_PATH = process.env.LOCADOT_HOME
|
|
11
|
+
? path_1.default.resolve(process.env.LOCADOT_HOME)
|
|
12
|
+
: (0, appdata_path_1.default)("locadot");
|
|
13
|
+
const envPort = (name, fallback) => {
|
|
14
|
+
const value = Number(process.env[name]);
|
|
15
|
+
return Number.isInteger(value) && value > 0 && value < 65536 ? value : fallback;
|
|
16
|
+
};
|
|
13
17
|
class Constants {
|
|
14
18
|
}
|
|
15
19
|
Constants.paths = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
HOME: PACKAGE_PATH,
|
|
21
|
+
LOCK_FILE: path_1.default.join(PACKAGE_PATH, ".locadot.lock"),
|
|
22
|
+
REGISTRY_FILE: path_1.default.join(PACKAGE_PATH, ".locadot-registry.json"),
|
|
23
|
+
REGISTRY_LOCK: path_1.default.join(PACKAGE_PATH, ".locadot-registry.lock"),
|
|
24
|
+
LOGS: path_1.default.join(PACKAGE_PATH, ".locadot.log"),
|
|
25
|
+
CERT_DIR: path_1.default.join(PACKAGE_PATH, "certs"),
|
|
26
|
+
// Secret for the dashboard's mutating API; rotated on every proxy start.
|
|
27
|
+
API_TOKEN: path_1.default.join(PACKAGE_PATH, ".locadot-token"),
|
|
28
|
+
};
|
|
29
|
+
Constants.server = {
|
|
30
|
+
httpPort: envPort("LOCADOT_HTTP_PORT", 80),
|
|
31
|
+
httpsPort: envPort("LOCADOT_HTTPS_PORT", 443),
|
|
32
|
+
// Loopback only unless the user opts in; see tasks/enhancements ENH-06.
|
|
33
|
+
bind: (process.env.LOCADOT_BIND || "127.0.0.1,::1")
|
|
34
|
+
.split(",")
|
|
35
|
+
.map((s) => s.trim())
|
|
36
|
+
.filter(Boolean),
|
|
19
37
|
};
|
|
38
|
+
// Hosts that serve the dashboard instead of being proxied.
|
|
39
|
+
Constants.dashboardHosts = ["localhost", "127.0.0.1", "::1"];
|
|
20
40
|
Constants.platform = () => {
|
|
21
41
|
const currentPlatform = os_1.default.platform();
|
|
22
42
|
if (currentPlatform === "win32") {
|
|
@@ -33,10 +53,10 @@ Constants.platform = () => {
|
|
|
33
53
|
}
|
|
34
54
|
};
|
|
35
55
|
Constants.proxyInfo = {
|
|
36
|
-
invalidHost: "❌ Invalid domain.
|
|
37
|
-
hostExist: "❌ Domain already
|
|
38
|
-
hostNotFound: "❌ Domain not found.
|
|
56
|
+
invalidHost: "❌ Invalid domain. Use a subdomain of localhost like dev.localhost or google.localhost (bare `localhost` is reserved for the dashboard).",
|
|
57
|
+
hostExist: "❌ Domain already mapped. Use `locadot update` to change its target.",
|
|
58
|
+
hostNotFound: "❌ Domain not found. Run `locadot list` to see mapped domains.",
|
|
39
59
|
proxyClose: "☑️ Successfully stopped all locadot instances.",
|
|
40
|
-
softClose: "☑️ Successfully stopped central proxy
|
|
60
|
+
softClose: "☑️ Successfully stopped central proxy.",
|
|
41
61
|
};
|
|
42
62
|
exports.default = Constants;
|
|
@@ -1,67 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.proxyNotFound = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
<html lang="en">
|
|
7
|
-
<head>
|
|
8
|
-
<meta charset="UTF-8" />
|
|
9
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
-
<title
|
|
11
|
-
<style>
|
|
12
|
-
body {
|
|
13
|
-
background-color: #121212;
|
|
14
|
-
color: #f8d7da;
|
|
15
|
-
font-family: 'Segoe UI', sans-serif;
|
|
16
|
-
margin: 0;
|
|
17
|
-
padding: 20px;
|
|
18
|
-
display: flex;
|
|
19
|
-
align-items: center;
|
|
20
|
-
justify-content: center;
|
|
21
|
-
min-height: 90vh;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</
|
|
53
|
-
<body>
|
|
54
|
-
<div class="error-box">
|
|
55
|
-
<strong style="color: #ff6b6b;">Connection failed:</strong>
|
|
56
|
-
Proxy does not exist for
|
|
57
|
-
<code>${host}</code>.<br/><br/>
|
|
58
|
-
Please use
|
|
59
|
-
<code>npx locadot --host ${host} --port PORT</code><br/><br/>
|
|
60
|
-
<a href="https://github.com/avinashid/locadot#readme" target="_blank">
|
|
61
|
-
📘 View on GitHub
|
|
62
|
-
</a>
|
|
63
|
-
</div>
|
|
64
|
-
</body>
|
|
65
|
-
</html>
|
|
3
|
+
exports.upstreamDown = exports.proxyNotFound = void 0;
|
|
4
|
+
const escape_1 = require("../dashboard/escape");
|
|
5
|
+
const page = (title, body, dashboardUrl) => `<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
+
<title>${(0, escape_1.escapeHtml)(title)}</title>
|
|
11
|
+
<style>
|
|
12
|
+
body {
|
|
13
|
+
background-color: #121212;
|
|
14
|
+
color: #f8d7da;
|
|
15
|
+
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 20px;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
min-height: 90vh;
|
|
22
|
+
}
|
|
23
|
+
.error-box {
|
|
24
|
+
background-color: #1e1e1e;
|
|
25
|
+
border: 1px solid #d9534f;
|
|
26
|
+
padding: 24px;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
max-width: 640px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.7);
|
|
31
|
+
line-height: 1.6;
|
|
32
|
+
}
|
|
33
|
+
.error-box code {
|
|
34
|
+
background: #2e2e2e;
|
|
35
|
+
color: #ffd5d5;
|
|
36
|
+
padding: 2px 6px;
|
|
37
|
+
border-radius: 4px;
|
|
38
|
+
font-family: monospace;
|
|
39
|
+
}
|
|
40
|
+
.error-box a { color: #61dafb; text-decoration: none; font-weight: bold; }
|
|
41
|
+
.error-box a:hover { text-decoration: underline; }
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div class="error-box">
|
|
46
|
+
${body}
|
|
47
|
+
<br/><br/>
|
|
48
|
+
<a href="${(0, escape_1.escapeHtml)(dashboardUrl)}">📋 Open the locadot dashboard</a> ·
|
|
49
|
+
<a href="https://github.com/avinashid/locadot#readme" target="_blank" rel="noopener noreferrer">📘 Docs</a>
|
|
50
|
+
</div>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
66
53
|
`;
|
|
54
|
+
const proxyNotFound = (host, dashboardUrl) => page("locadot: host not mapped", `<strong style="color: #ff6b6b;">Not mapped:</strong>
|
|
55
|
+
no locadot mapping exists for <code>${(0, escape_1.escapeHtml)(host)}</code>.<br/><br/>
|
|
56
|
+
Add one with<br/>
|
|
57
|
+
<code>npx locadot add --host ${(0, escape_1.escapeHtml)(host)} --port PORT</code><br/>
|
|
58
|
+
or<br/>
|
|
59
|
+
<code>npx locadot add --host ${(0, escape_1.escapeHtml)(host)} --target https://example.com</code>`, dashboardUrl);
|
|
67
60
|
exports.proxyNotFound = proxyNotFound;
|
|
61
|
+
const upstreamDown = (host, target, reason, dashboardUrl) => page("locadot: upstream unreachable", `<strong style="color: #ff6b6b;">Upstream unreachable:</strong>
|
|
62
|
+
<code>${(0, escape_1.escapeHtml)(host)}</code> → <code>${(0, escape_1.escapeHtml)(target)}</code><br/><br/>
|
|
63
|
+
${(0, escape_1.escapeHtml)(reason)}. Is the app running?`, dashboardUrl);
|
|
64
|
+
exports.upstreamDown = upstreamDown;
|
package/dist/core.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const logger_1 = __importDefault(require("./utils/logger"));
|
|
8
|
-
async function run() {
|
|
9
|
-
logger_1.default.info(`🔄 Starting central proxy.`);
|
|
10
|
-
await proxy_1.default.startCentralProxy();
|
|
11
|
-
logger_1.default.info("☑️ Central proxy started.");
|
|
2
|
+
// Entry point of the detached proxy process. `--home <dir>` lets boot-time
|
|
3
|
+
// launchers (root cron, SYSTEM task) use the invoking user's state dir.
|
|
4
|
+
const homeIndex = process.argv.indexOf("--home");
|
|
5
|
+
if (homeIndex > -1 && process.argv[homeIndex + 1]) {
|
|
6
|
+
process.env.LOCADOT_HOME = process.argv[homeIndex + 1];
|
|
12
7
|
}
|
|
13
|
-
|
|
8
|
+
process.env.LOCADOT_ROLE = "proxy";
|
|
9
|
+
// Required after the env is set: constants read it at load time.
|
|
10
|
+
const { startCentralProxy } = require("./server");
|
|
11
|
+
startCentralProxy().catch((error) => {
|
|
12
|
+
console.error("❌ Failed to start central proxy", error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|