labtool-cf 0.1.1__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amirhossein Ziaee
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.
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: labtool-cf
3
+ Version: 0.1.1
4
+ Summary: Manage a Cloudflare Tunnel for machines behind NAT
5
+ Author-email: Your Name <you@example.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/afourteene/labtool
8
+ Keywords: cloudflare,tunnel,cloudflared,cli
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Environment :: Console
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: click>=8.1
16
+ Requires-Dist: pyyaml>=6.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0; extra == "dev"
19
+ Requires-Dist: build; extra == "dev"
20
+ Requires-Dist: twine; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # labtool
24
+
25
+ A small CLI that sets up and manages a Cloudflare Tunnel (`cloudflared`) for machines behind NAT, so they can be reached over the internet without opening any ports.
26
+
27
+ Everything is described in one file, `lab.yaml`. The tool turns it into a tunnel, DNS records and a `cloudflared` `config.yml`.
28
+
29
+ ## Requirements
30
+
31
+ - Python 3.10+
32
+ - A domain managed by Cloudflare (status: Active)
33
+ - A Cloudflare account (used once for `labtool login`)
34
+ - `cloudflared` (the tool can install it for you: `labtool install`)
35
+
36
+ ## Using your domain with Cloudflare
37
+
38
+ `labtool` creates DNS records such as `node1.example.com`, so your domain must be managed by Cloudflare (its nameservers must point to Cloudflare). You only do this once per domain.
39
+
40
+ ### Option A: you already own a domain at another registrar
41
+
42
+ 1. Create a free account at [dash.cloudflare.com](https://dash.cloudflare.com).
43
+ 2. Go to **Domains** and click **Add domain**. Enter your domain (for example `example.com`) and choose the **Free** plan.
44
+ 3. Cloudflare scans your existing DNS records. Review them and continue. Keep any records you still need (website, mail).
45
+ 4. Cloudflare shows you two nameservers, for example `xxx.ns.cloudflare.com` and `yyy.ns.cloudflare.com`.
46
+ 5. Log in to your registrar (where you bought the domain), open the domain's nameserver settings, and replace the current nameservers with the two from Cloudflare.
47
+ 6. Wait until the domain shows **Active** in the Cloudflare dashboard. This usually takes minutes, but can take up to 24 hours.
48
+
49
+ Check from the terminal:
50
+
51
+ ```bash
52
+ dig +short NS example.com
53
+ ```
54
+
55
+ The output should list the two `*.ns.cloudflare.com` servers.
56
+
57
+ ### Option B: buy the domain from Cloudflare
58
+
59
+ In the dashboard choose **Domains**, then **Buy domain**. A domain bought there is managed by Cloudflare right away; there is nothing to move.
60
+
61
+ ### Authorize labtool for that domain
62
+
63
+ Run:
64
+
65
+ ```bash
66
+ labtool login
67
+ ```
68
+
69
+ Open the printed URL, log in, and **select your domain** on the authorization page. Cloudflare saves a `cert.pem` that lets `labtool` create tunnels and DNS records for that domain only. If you own several domains, pick the one set as `domain:` in `lab.yaml`.
70
+
71
+ ### What `labtool init` does to your DNS
72
+
73
+ For every node in `lab.yaml` it creates a proxied `CNAME` record that points to the tunnel:
74
+
75
+ | Record | Type | Points to |
76
+ |---|---|---|
77
+ | `node1.example.com` | CNAME (proxied) | `<TUNNEL_ID>.cfargotunnel.com` |
78
+
79
+ Notes:
80
+
81
+ - No server IP is stored in DNS, and no port has to be opened on your machines.
82
+ - If a record with the same name already exists, `init` overwrites it. Do not reuse a hostname that serves something else.
83
+ - To use a subdomain only for this tool, set `domain: lab.example.com` in `lab.yaml`; nodes then become `node1.lab.example.com`.
84
+ - After `labtool delete`, the CNAME records stay in DNS. Remove them in the dashboard under **DNS > Records**.
85
+ - The free plan is enough. No payment method is needed for DNS and tunnels.
86
+
87
+ ### Troubleshooting the domain
88
+
89
+ | Problem | Fix |
90
+ |---|---|
91
+ | Domain stays **Pending** | Nameservers at the registrar are not changed yet, or have not propagated. Re-check step 5 and wait. |
92
+ | `labtool init` cannot create DNS records | The domain is not Active, or you selected a different domain during `labtool login`. Delete `~/.cloudflared/cert.pem` and run `labtool login` again. |
93
+ | `node1.example.com` does not resolve | The record is missing. Run `labtool init` again and check the dashboard under **DNS > Records**. |
94
+
95
+ ## Installation
96
+
97
+ ### From PyPI (recommended)
98
+
99
+ ```bash
100
+ pipx install labtool-cf
101
+ labtool -h
102
+ ```
103
+
104
+ `pipx` installs the tool in its own isolated environment, so it does not clash with system Python packages. You can also use `pip install labtool-cf` inside a virtual environment. The package is called `labtool-cf`; the command it installs is `labtool`.
105
+
106
+ Upgrade with `pipx upgrade labtool-cf`.
107
+
108
+ ### From source (development)
109
+
110
+ From the project root (the folder containing `pyproject.toml`):
111
+
112
+ ```bash
113
+ python3 -m venv venv
114
+ source venv/bin/activate
115
+ pip install -e .
116
+ labtool -h
117
+ ```
118
+
119
+ For development (tests):
120
+
121
+ ```bash
122
+ pip install pytest
123
+ pytest -v
124
+ ```
125
+
126
+ You can also run it as a module: `python -m labtool -h`.
127
+
128
+ ## Configuration: `lab.yaml`
129
+
130
+ ```yaml
131
+ domain: example.com # a domain active on Cloudflare
132
+ tunnel_name: my-tunnel # name of the tunnel
133
+ nodes:
134
+ - name: node1 # becomes node1.example.com
135
+ ip: 192.168.56.11 # address the tunnel machine can reach
136
+ - name: node2
137
+ ip: 192.168.56.12
138
+ ```
139
+
140
+ Each node is exposed as `ssh://<ip>:22` under `<name>.<domain>`.
141
+
142
+ ### Creating the file
143
+
144
+ The easiest way is to let the tool create a template for you:
145
+
146
+ ```bash
147
+ labtool config init # creates the file in your user config directory
148
+ labtool config path # shows which file will be used
149
+ ```
150
+
151
+ Then edit the file it printed (set `domain`, `tunnel_name` and `nodes`). Useful options:
152
+
153
+ - `labtool config init --local` creates `./lab.yaml` in the current folder instead.
154
+ - `labtool config init --force` overwrites an existing file (without it, an existing file is never touched).
155
+
156
+ User config directory:
157
+
158
+ | System | Path |
159
+ |---|---|
160
+ | Linux | `~/.config/labtool/lab.yaml` |
161
+ | macOS | `~/Library/Application Support/labtool/lab.yaml` |
162
+ | Windows | `%APPDATA%\labtool\lab.yaml` |
163
+
164
+ ### Where the tool looks for the file
165
+
166
+ The first match wins:
167
+
168
+ 1. The `-f PATH` option
169
+ 2. The `LABTOOL_FILE` environment variable
170
+ 3. `./lab.yaml` in the current folder
171
+ 4. The user config directory (table above)
172
+
173
+ If none exists, the command stops and tells you to run `labtool config init`.
174
+
175
+ You can keep one `lab.yaml` per project (use option 3 or `-f`), or one global file for everything (option 4). For scripts and CI, set `LABTOOL_FILE`:
176
+
177
+ ```bash
178
+ export LABTOOL_FILE=/path/to/lab.yaml
179
+ labtool init
180
+ ```
181
+
182
+ ## Quick start
183
+
184
+ ```bash
185
+ labtool install # 1. install cloudflared for this OS
186
+ labtool config init # 2. create a template lab.yaml, then edit it
187
+ labtool login # 3. authorize with Cloudflare (creates cert.pem)
188
+ labtool init # 4. create tunnel + DNS records + config.yml
189
+ labtool up # 5. run the tunnel in the background
190
+ labtool status # 6. check it
191
+ labtool down # 7. stop it
192
+ ```
193
+
194
+ ## How it works
195
+
196
+ 1. **You describe the lab** in `lab.yaml`: a domain, a tunnel name and a list of nodes (name + IP).
197
+ 2. **`labtool init`** talks to Cloudflare through the `cloudflared` binary. It creates the tunnel (once), adds a DNS record `<node>.<domain>` for every node, and writes a `config.yml` whose `ingress` rules map each hostname to `ssh://<ip>:22`. A last catch-all rule (`http_status:404`) is added automatically, because `cloudflared` requires it.
198
+ 3. **`labtool up`** starts `cloudflared tunnel run` in the background and remembers its PID. The tunnel connects *outward* to Cloudflare, so no inbound port is opened.
199
+ 4. **A client** connects to `node1.example.com`. The request reaches Cloudflare, travels through the tunnel to your machine, and is forwarded to the node's IP.
200
+ 5. **`labtool down`** stops the process; the hostnames stop working until you run `up` again.
201
+
202
+ ```
203
+ client ──ssh──► Cloudflare ──► tunnel (cloudflared on your machine) ──► node IP:22
204
+ ```
205
+
206
+ ## Commands
207
+
208
+ Every command supports `-h` / `--help`. `labtool -v` prints the version.
209
+
210
+ | Command | What it does |
211
+ |---|---|
212
+ | `install` | Installs `cloudflared` (Homebrew on macOS, `.deb` or binary on Linux, `winget` on Windows). Skips if already installed. |
213
+ | `login` | Runs `cloudflared tunnel login`. Prints a URL; open it in any browser and click Authorize. Skips if `cert.pem` already exists. |
214
+ | `init` | Logs in if needed, creates the tunnel if missing, routes DNS for every node, and writes `~/.cloudflared/config.yml`. This is the only command that creates the tunnel. |
215
+ | `up` | Starts `cloudflared tunnel run` in the background. Does nothing if already running. |
216
+ | `down` | Stops the running tunnel. |
217
+ | `status` | Shows whether the tunnel is running (and its PID). |
218
+ | `delete` | Stops and deletes the tunnel from Cloudflare. Asks for confirmation; `-y` skips the prompt. Irreversible. |
219
+
220
+ | `config init` | Creates a template `lab.yaml` in the user config directory. Options: `--local` (use `./lab.yaml`), `--force` (overwrite). |
221
+ | `config path` | Prints the `lab.yaml` that would be used. |
222
+
223
+ `init`, `up` and `delete` accept `-f, --file PATH` to choose the lab file (see "Where the tool looks for the file" above). The other commands do not read it.
224
+
225
+ All commands are safe to run more than once (idempotent).
226
+
227
+ ## Files used
228
+
229
+ | Path | Purpose |
230
+ |---|---|
231
+ | `~/.cloudflared/cert.pem` | Result of `login`. Admin access to your domain; treat it like a password. |
232
+ | `~/.cloudflared/<TUNNEL_ID>.json` | Tunnel credentials, needed on any machine that runs the tunnel. |
233
+ | `~/.cloudflared/config.yml` | Generated by `init`. |
234
+ | `lab.yaml` | Your configuration (see "Configuration" above). Not stored inside the package. |
235
+ | `~/.labtool/tunnel.pid` | PID of the background tunnel. |
236
+ | `~/.labtool/tunnel.log` | `cloudflared` output (check here if `up` fails). |
237
+
238
+ ## Connecting to a node
239
+
240
+ Install `cloudflared` on the client machine, then:
241
+
242
+ ```bash
243
+ ssh -i ~/.ssh/id_rsa \
244
+ -o ProxyCommand="cloudflared access ssh --hostname %h" \
245
+ user@node1.example.com
246
+ ```
247
+
248
+ ## Running the tunnel on another machine
249
+
250
+ - `cert.pem` is only needed for management commands (`init`, `delete`). Running a tunnel (`up`) needs only `<TUNNEL_ID>.json` and `config.yml`.
251
+ - The credentials file exists only on the machine where the tunnel was created. Either copy it to the other machine, or set a different `tunnel_name` in that machine's `lab.yaml` and run `labtool init` there to create a separate tunnel.
252
+ - Do not run the same tunnel on two machines at once unless you want traffic split between them. Stop one with `labtool down` (or `Ctrl+C`).
253
+ - `init` overwrites existing DNS records, so they will point to the tunnel you just initialised.
254
+
255
+ ## Troubleshooting
256
+
257
+ | Message | Cause and fix |
258
+ |---|---|
259
+ | `Cannot determine default origin certificate path` | No `cert.pem`. Run `labtool login`. |
260
+ | `Tunnel credentials file ... doesn't exist` | Credentials are missing on this machine. Copy the `.json`, or create a tunnel here. |
261
+ | `cloudflared exited immediately` | Check `~/.labtool/tunnel.log` (bad config or missing credentials). |
262
+ | `You have an existing certificate ... which login would overwrite` | You are already logged in. Nothing to do. |
263
+ | `lab.yaml not found` | No file in any searched location. Run `labtool config init`, or pass `-f PATH`. |
264
+ | `... is empty or has the wrong format` / `missing required key` | The file is empty or incomplete. It needs `domain`, `tunnel_name` and a non-empty `nodes` list. |
265
+ | `No module named labtool.cli` | `cli.py` must be in `src/labtool/`; then run `pip install -e .` again. |
266
+
267
+ `Ctrl+C` cancels any command cleanly (exit code 130).
268
+
269
+ ## Project layout
270
+
271
+ ```
272
+ labtool/
273
+ ├── pyproject.toml
274
+ ├── lab.yaml
275
+ ├── src/labtool/
276
+ │ ├── cli.py # click commands only (wiring)
277
+ │ ├── ui.py # colored output
278
+ │ ├── services.py # use cases: init, up, down, delete, ...
279
+ │ ├── domain.py # models and pure rules, no I/O
280
+ │ ├── config_builder.py # pure function: Lab -> tunnel config
281
+ │ ├── ports.py # interfaces
282
+ │ └── adapters/ # cloudflared, process runner, system, lab file
283
+ └── tests/ # run with pytest, using fakes (no Cloudflare needed)
284
+ ```
285
+
286
+ Dependencies point inward: `cli -> services -> domain`, and adapters implement the interfaces in `ports`. Services never import `click` or `subprocess`, which is why they can be tested with fakes.
287
+
288
+ ## Security notes
289
+
290
+ - Never commit `cert.pem`, `<TUNNEL_ID>.json`, SSH private keys, or tokens. Add them to `.gitignore` (`venv/`, `.labtool/`, `*.json`, `ssh-keys/`).
291
+ - Use key-only SSH on the nodes (`PasswordAuthentication no`).
292
+ - A tunnel hostname is public while the tunnel runs. Stop it with `labtool down` when you are not using it.
@@ -0,0 +1,270 @@
1
+ # labtool
2
+
3
+ A small CLI that sets up and manages a Cloudflare Tunnel (`cloudflared`) for machines behind NAT, so they can be reached over the internet without opening any ports.
4
+
5
+ Everything is described in one file, `lab.yaml`. The tool turns it into a tunnel, DNS records and a `cloudflared` `config.yml`.
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.10+
10
+ - A domain managed by Cloudflare (status: Active)
11
+ - A Cloudflare account (used once for `labtool login`)
12
+ - `cloudflared` (the tool can install it for you: `labtool install`)
13
+
14
+ ## Using your domain with Cloudflare
15
+
16
+ `labtool` creates DNS records such as `node1.example.com`, so your domain must be managed by Cloudflare (its nameservers must point to Cloudflare). You only do this once per domain.
17
+
18
+ ### Option A: you already own a domain at another registrar
19
+
20
+ 1. Create a free account at [dash.cloudflare.com](https://dash.cloudflare.com).
21
+ 2. Go to **Domains** and click **Add domain**. Enter your domain (for example `example.com`) and choose the **Free** plan.
22
+ 3. Cloudflare scans your existing DNS records. Review them and continue. Keep any records you still need (website, mail).
23
+ 4. Cloudflare shows you two nameservers, for example `xxx.ns.cloudflare.com` and `yyy.ns.cloudflare.com`.
24
+ 5. Log in to your registrar (where you bought the domain), open the domain's nameserver settings, and replace the current nameservers with the two from Cloudflare.
25
+ 6. Wait until the domain shows **Active** in the Cloudflare dashboard. This usually takes minutes, but can take up to 24 hours.
26
+
27
+ Check from the terminal:
28
+
29
+ ```bash
30
+ dig +short NS example.com
31
+ ```
32
+
33
+ The output should list the two `*.ns.cloudflare.com` servers.
34
+
35
+ ### Option B: buy the domain from Cloudflare
36
+
37
+ In the dashboard choose **Domains**, then **Buy domain**. A domain bought there is managed by Cloudflare right away; there is nothing to move.
38
+
39
+ ### Authorize labtool for that domain
40
+
41
+ Run:
42
+
43
+ ```bash
44
+ labtool login
45
+ ```
46
+
47
+ Open the printed URL, log in, and **select your domain** on the authorization page. Cloudflare saves a `cert.pem` that lets `labtool` create tunnels and DNS records for that domain only. If you own several domains, pick the one set as `domain:` in `lab.yaml`.
48
+
49
+ ### What `labtool init` does to your DNS
50
+
51
+ For every node in `lab.yaml` it creates a proxied `CNAME` record that points to the tunnel:
52
+
53
+ | Record | Type | Points to |
54
+ |---|---|---|
55
+ | `node1.example.com` | CNAME (proxied) | `<TUNNEL_ID>.cfargotunnel.com` |
56
+
57
+ Notes:
58
+
59
+ - No server IP is stored in DNS, and no port has to be opened on your machines.
60
+ - If a record with the same name already exists, `init` overwrites it. Do not reuse a hostname that serves something else.
61
+ - To use a subdomain only for this tool, set `domain: lab.example.com` in `lab.yaml`; nodes then become `node1.lab.example.com`.
62
+ - After `labtool delete`, the CNAME records stay in DNS. Remove them in the dashboard under **DNS > Records**.
63
+ - The free plan is enough. No payment method is needed for DNS and tunnels.
64
+
65
+ ### Troubleshooting the domain
66
+
67
+ | Problem | Fix |
68
+ |---|---|
69
+ | Domain stays **Pending** | Nameservers at the registrar are not changed yet, or have not propagated. Re-check step 5 and wait. |
70
+ | `labtool init` cannot create DNS records | The domain is not Active, or you selected a different domain during `labtool login`. Delete `~/.cloudflared/cert.pem` and run `labtool login` again. |
71
+ | `node1.example.com` does not resolve | The record is missing. Run `labtool init` again and check the dashboard under **DNS > Records**. |
72
+
73
+ ## Installation
74
+
75
+ ### From PyPI (recommended)
76
+
77
+ ```bash
78
+ pipx install labtool-cf
79
+ labtool -h
80
+ ```
81
+
82
+ `pipx` installs the tool in its own isolated environment, so it does not clash with system Python packages. You can also use `pip install labtool-cf` inside a virtual environment. The package is called `labtool-cf`; the command it installs is `labtool`.
83
+
84
+ Upgrade with `pipx upgrade labtool-cf`.
85
+
86
+ ### From source (development)
87
+
88
+ From the project root (the folder containing `pyproject.toml`):
89
+
90
+ ```bash
91
+ python3 -m venv venv
92
+ source venv/bin/activate
93
+ pip install -e .
94
+ labtool -h
95
+ ```
96
+
97
+ For development (tests):
98
+
99
+ ```bash
100
+ pip install pytest
101
+ pytest -v
102
+ ```
103
+
104
+ You can also run it as a module: `python -m labtool -h`.
105
+
106
+ ## Configuration: `lab.yaml`
107
+
108
+ ```yaml
109
+ domain: example.com # a domain active on Cloudflare
110
+ tunnel_name: my-tunnel # name of the tunnel
111
+ nodes:
112
+ - name: node1 # becomes node1.example.com
113
+ ip: 192.168.56.11 # address the tunnel machine can reach
114
+ - name: node2
115
+ ip: 192.168.56.12
116
+ ```
117
+
118
+ Each node is exposed as `ssh://<ip>:22` under `<name>.<domain>`.
119
+
120
+ ### Creating the file
121
+
122
+ The easiest way is to let the tool create a template for you:
123
+
124
+ ```bash
125
+ labtool config init # creates the file in your user config directory
126
+ labtool config path # shows which file will be used
127
+ ```
128
+
129
+ Then edit the file it printed (set `domain`, `tunnel_name` and `nodes`). Useful options:
130
+
131
+ - `labtool config init --local` creates `./lab.yaml` in the current folder instead.
132
+ - `labtool config init --force` overwrites an existing file (without it, an existing file is never touched).
133
+
134
+ User config directory:
135
+
136
+ | System | Path |
137
+ |---|---|
138
+ | Linux | `~/.config/labtool/lab.yaml` |
139
+ | macOS | `~/Library/Application Support/labtool/lab.yaml` |
140
+ | Windows | `%APPDATA%\labtool\lab.yaml` |
141
+
142
+ ### Where the tool looks for the file
143
+
144
+ The first match wins:
145
+
146
+ 1. The `-f PATH` option
147
+ 2. The `LABTOOL_FILE` environment variable
148
+ 3. `./lab.yaml` in the current folder
149
+ 4. The user config directory (table above)
150
+
151
+ If none exists, the command stops and tells you to run `labtool config init`.
152
+
153
+ You can keep one `lab.yaml` per project (use option 3 or `-f`), or one global file for everything (option 4). For scripts and CI, set `LABTOOL_FILE`:
154
+
155
+ ```bash
156
+ export LABTOOL_FILE=/path/to/lab.yaml
157
+ labtool init
158
+ ```
159
+
160
+ ## Quick start
161
+
162
+ ```bash
163
+ labtool install # 1. install cloudflared for this OS
164
+ labtool config init # 2. create a template lab.yaml, then edit it
165
+ labtool login # 3. authorize with Cloudflare (creates cert.pem)
166
+ labtool init # 4. create tunnel + DNS records + config.yml
167
+ labtool up # 5. run the tunnel in the background
168
+ labtool status # 6. check it
169
+ labtool down # 7. stop it
170
+ ```
171
+
172
+ ## How it works
173
+
174
+ 1. **You describe the lab** in `lab.yaml`: a domain, a tunnel name and a list of nodes (name + IP).
175
+ 2. **`labtool init`** talks to Cloudflare through the `cloudflared` binary. It creates the tunnel (once), adds a DNS record `<node>.<domain>` for every node, and writes a `config.yml` whose `ingress` rules map each hostname to `ssh://<ip>:22`. A last catch-all rule (`http_status:404`) is added automatically, because `cloudflared` requires it.
176
+ 3. **`labtool up`** starts `cloudflared tunnel run` in the background and remembers its PID. The tunnel connects *outward* to Cloudflare, so no inbound port is opened.
177
+ 4. **A client** connects to `node1.example.com`. The request reaches Cloudflare, travels through the tunnel to your machine, and is forwarded to the node's IP.
178
+ 5. **`labtool down`** stops the process; the hostnames stop working until you run `up` again.
179
+
180
+ ```
181
+ client ──ssh──► Cloudflare ──► tunnel (cloudflared on your machine) ──► node IP:22
182
+ ```
183
+
184
+ ## Commands
185
+
186
+ Every command supports `-h` / `--help`. `labtool -v` prints the version.
187
+
188
+ | Command | What it does |
189
+ |---|---|
190
+ | `install` | Installs `cloudflared` (Homebrew on macOS, `.deb` or binary on Linux, `winget` on Windows). Skips if already installed. |
191
+ | `login` | Runs `cloudflared tunnel login`. Prints a URL; open it in any browser and click Authorize. Skips if `cert.pem` already exists. |
192
+ | `init` | Logs in if needed, creates the tunnel if missing, routes DNS for every node, and writes `~/.cloudflared/config.yml`. This is the only command that creates the tunnel. |
193
+ | `up` | Starts `cloudflared tunnel run` in the background. Does nothing if already running. |
194
+ | `down` | Stops the running tunnel. |
195
+ | `status` | Shows whether the tunnel is running (and its PID). |
196
+ | `delete` | Stops and deletes the tunnel from Cloudflare. Asks for confirmation; `-y` skips the prompt. Irreversible. |
197
+
198
+ | `config init` | Creates a template `lab.yaml` in the user config directory. Options: `--local` (use `./lab.yaml`), `--force` (overwrite). |
199
+ | `config path` | Prints the `lab.yaml` that would be used. |
200
+
201
+ `init`, `up` and `delete` accept `-f, --file PATH` to choose the lab file (see "Where the tool looks for the file" above). The other commands do not read it.
202
+
203
+ All commands are safe to run more than once (idempotent).
204
+
205
+ ## Files used
206
+
207
+ | Path | Purpose |
208
+ |---|---|
209
+ | `~/.cloudflared/cert.pem` | Result of `login`. Admin access to your domain; treat it like a password. |
210
+ | `~/.cloudflared/<TUNNEL_ID>.json` | Tunnel credentials, needed on any machine that runs the tunnel. |
211
+ | `~/.cloudflared/config.yml` | Generated by `init`. |
212
+ | `lab.yaml` | Your configuration (see "Configuration" above). Not stored inside the package. |
213
+ | `~/.labtool/tunnel.pid` | PID of the background tunnel. |
214
+ | `~/.labtool/tunnel.log` | `cloudflared` output (check here if `up` fails). |
215
+
216
+ ## Connecting to a node
217
+
218
+ Install `cloudflared` on the client machine, then:
219
+
220
+ ```bash
221
+ ssh -i ~/.ssh/id_rsa \
222
+ -o ProxyCommand="cloudflared access ssh --hostname %h" \
223
+ user@node1.example.com
224
+ ```
225
+
226
+ ## Running the tunnel on another machine
227
+
228
+ - `cert.pem` is only needed for management commands (`init`, `delete`). Running a tunnel (`up`) needs only `<TUNNEL_ID>.json` and `config.yml`.
229
+ - The credentials file exists only on the machine where the tunnel was created. Either copy it to the other machine, or set a different `tunnel_name` in that machine's `lab.yaml` and run `labtool init` there to create a separate tunnel.
230
+ - Do not run the same tunnel on two machines at once unless you want traffic split between them. Stop one with `labtool down` (or `Ctrl+C`).
231
+ - `init` overwrites existing DNS records, so they will point to the tunnel you just initialised.
232
+
233
+ ## Troubleshooting
234
+
235
+ | Message | Cause and fix |
236
+ |---|---|
237
+ | `Cannot determine default origin certificate path` | No `cert.pem`. Run `labtool login`. |
238
+ | `Tunnel credentials file ... doesn't exist` | Credentials are missing on this machine. Copy the `.json`, or create a tunnel here. |
239
+ | `cloudflared exited immediately` | Check `~/.labtool/tunnel.log` (bad config or missing credentials). |
240
+ | `You have an existing certificate ... which login would overwrite` | You are already logged in. Nothing to do. |
241
+ | `lab.yaml not found` | No file in any searched location. Run `labtool config init`, or pass `-f PATH`. |
242
+ | `... is empty or has the wrong format` / `missing required key` | The file is empty or incomplete. It needs `domain`, `tunnel_name` and a non-empty `nodes` list. |
243
+ | `No module named labtool.cli` | `cli.py` must be in `src/labtool/`; then run `pip install -e .` again. |
244
+
245
+ `Ctrl+C` cancels any command cleanly (exit code 130).
246
+
247
+ ## Project layout
248
+
249
+ ```
250
+ labtool/
251
+ ├── pyproject.toml
252
+ ├── lab.yaml
253
+ ├── src/labtool/
254
+ │ ├── cli.py # click commands only (wiring)
255
+ │ ├── ui.py # colored output
256
+ │ ├── services.py # use cases: init, up, down, delete, ...
257
+ │ ├── domain.py # models and pure rules, no I/O
258
+ │ ├── config_builder.py # pure function: Lab -> tunnel config
259
+ │ ├── ports.py # interfaces
260
+ │ └── adapters/ # cloudflared, process runner, system, lab file
261
+ └── tests/ # run with pytest, using fakes (no Cloudflare needed)
262
+ ```
263
+
264
+ Dependencies point inward: `cli -> services -> domain`, and adapters implement the interfaces in `ports`. Services never import `click` or `subprocess`, which is why they can be tested with fakes.
265
+
266
+ ## Security notes
267
+
268
+ - Never commit `cert.pem`, `<TUNNEL_ID>.json`, SSH private keys, or tokens. Add them to `.gitignore` (`venv/`, `.labtool/`, `*.json`, `ssh-keys/`).
269
+ - Use key-only SSH on the nodes (`PasswordAuthentication no`).
270
+ - A tunnel hostname is public while the tunnel runs. Stop it with `labtool down` when you are not using it.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "labtool-cf"
7
+ version = "0.1.1"
8
+ description = "Manage a Cloudflare Tunnel for machines behind NAT"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Your Name", email = "you@example.com" }]
14
+ keywords = ["cloudflare", "tunnel", "cloudflared", "cli"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Environment :: Console",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ dependencies = ["click>=8.1", "pyyaml>=6.0"]
21
+
22
+ [project.optional-dependencies]
23
+ dev = ["pytest>=8.0", "build", "twine"]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/afourteene/labtool"
27
+
28
+ [project.scripts]
29
+ labtool = "labtool.cli:main"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
File without changes