cluster-tunnel 0.2.0__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.
Files changed (47) hide show
  1. cluster_tunnel-0.2.0/PKG-INFO +429 -0
  2. cluster_tunnel-0.2.0/README.md +414 -0
  3. cluster_tunnel-0.2.0/cluster_tunnel/VERSION +1 -0
  4. cluster_tunnel-0.2.0/cluster_tunnel/__init__.py +7 -0
  5. cluster_tunnel-0.2.0/cluster_tunnel/budget.py +140 -0
  6. cluster_tunnel-0.2.0/cluster_tunnel/budget_templates/bwuni3.sh +68 -0
  7. cluster_tunnel-0.2.0/cluster_tunnel/budget_templates/haicore.sh +69 -0
  8. cluster_tunnel-0.2.0/cluster_tunnel/budget_templates/horeka.sh +68 -0
  9. cluster_tunnel-0.2.0/cluster_tunnel/budget_templates/intnano.sh +69 -0
  10. cluster_tunnel-0.2.0/cluster_tunnel/budget_templates/kcist.sh +68 -0
  11. cluster_tunnel-0.2.0/cluster_tunnel/cli/__init__.py +128 -0
  12. cluster_tunnel-0.2.0/cluster_tunnel/cli/assets/logo_image.txt +7 -0
  13. cluster_tunnel-0.2.0/cluster_tunnel/cli/assets/logo_text.txt +8 -0
  14. cluster_tunnel-0.2.0/cluster_tunnel/cli/base.py +124 -0
  15. cluster_tunnel-0.2.0/cluster_tunnel/cli/configcmd.py +60 -0
  16. cluster_tunnel-0.2.0/cluster_tunnel/cli/constants.py +3 -0
  17. cluster_tunnel-0.2.0/cluster_tunnel/cli/context.py +247 -0
  18. cluster_tunnel-0.2.0/cluster_tunnel/cli/display.py +121 -0
  19. cluster_tunnel-0.2.0/cluster_tunnel/cli/errors.py +43 -0
  20. cluster_tunnel-0.2.0/cluster_tunnel/cli/execution.py +120 -0
  21. cluster_tunnel-0.2.0/cluster_tunnel/cli/transfer.py +70 -0
  22. cluster_tunnel-0.2.0/cluster_tunnel/cli/tunnel.py +288 -0
  23. cluster_tunnel-0.2.0/cluster_tunnel/cli/webui.py +18 -0
  24. cluster_tunnel-0.2.0/cluster_tunnel/cmdlog.py +73 -0
  25. cluster_tunnel-0.2.0/cluster_tunnel/config.example.yaml +111 -0
  26. cluster_tunnel-0.2.0/cluster_tunnel/config.py +197 -0
  27. cluster_tunnel-0.2.0/cluster_tunnel/constants.py +56 -0
  28. cluster_tunnel-0.2.0/cluster_tunnel/paths.py +46 -0
  29. cluster_tunnel-0.2.0/cluster_tunnel/popup.py +337 -0
  30. cluster_tunnel-0.2.0/cluster_tunnel/session.py +48 -0
  31. cluster_tunnel-0.2.0/cluster_tunnel/ssh.py +175 -0
  32. cluster_tunnel-0.2.0/cluster_tunnel/transfer.py +102 -0
  33. cluster_tunnel-0.2.0/cluster_tunnel.egg-info/PKG-INFO +429 -0
  34. cluster_tunnel-0.2.0/cluster_tunnel.egg-info/SOURCES.txt +45 -0
  35. cluster_tunnel-0.2.0/cluster_tunnel.egg-info/dependency_links.txt +1 -0
  36. cluster_tunnel-0.2.0/cluster_tunnel.egg-info/entry_points.txt +2 -0
  37. cluster_tunnel-0.2.0/cluster_tunnel.egg-info/requires.txt +8 -0
  38. cluster_tunnel-0.2.0/cluster_tunnel.egg-info/top_level.txt +1 -0
  39. cluster_tunnel-0.2.0/pyproject.toml +59 -0
  40. cluster_tunnel-0.2.0/setup.cfg +4 -0
  41. cluster_tunnel-0.2.0/tests/test_budget.py +118 -0
  42. cluster_tunnel-0.2.0/tests/test_budget_script.py +67 -0
  43. cluster_tunnel-0.2.0/tests/test_cli.py +323 -0
  44. cluster_tunnel-0.2.0/tests/test_config.py +108 -0
  45. cluster_tunnel-0.2.0/tests/test_popup.py +97 -0
  46. cluster_tunnel-0.2.0/tests/test_ssh.py +52 -0
  47. cluster_tunnel-0.2.0/tests/test_transfer.py +71 -0
@@ -0,0 +1,429 @@
1
+ Metadata-Version: 2.4
2
+ Name: cluster-tunnel
3
+ Version: 0.2.0
4
+ Summary: Authenticated SSH tunnels + session-scoped compute-budget guard for HPC clusters, as a CLI for coding agents.
5
+ Author-email: Jonas Teufel <jonseb1998@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: rich_click<=2.0.0,>=1.8.8
9
+ Requires-Dist: pydantic>=2.5.3
10
+ Requires-Dist: pyyaml>=6.0
11
+ Requires-Dist: appdirs<=2.0.0,>=1.0.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: bump-my-version>=0.28.0; extra == "dev"
15
+
16
+ # cluster-tunnel (`ctun`)
17
+
18
+ `ctun` keeps an authenticated SSH connection to an OTP-protected HPC cluster **alive
19
+ in the background**, so you (and your coding agents) can run commands on the cluster
20
+ **without re-entering a one-time password every time** — and it enforces a
21
+ **per-session compute budget** so automated job submission can't overspend.
22
+
23
+ You authenticate once. After that, every `ctun -t <cluster> run -- <command>` reuses
24
+ the live connection with zero re-authentication, and any job submission is checked
25
+ against a budget before it reaches the scheduler.
26
+
27
+ ```console
28
+ $ ctun -t horeka login -i # interactive login once: password + OTP popup
29
+ $ ctun -t horeka run -- squeue --me # reuse the live tunnel, no re-auth
30
+ $ ctun -t horeka run -- sbatch train.sh # submitted only if within budget
31
+ $ ctun -t horeka status # tunnel state + budget used
32
+ $ ctun -t horeka logout # close the tunnel
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Contents
38
+
39
+ - [How it works](#how-it-works)
40
+ - [Requirements](#requirements)
41
+ - [Install](#install)
42
+ - [Quickstart](#quickstart)
43
+ - [Commands](#commands)
44
+ - [Configuration](#configuration)
45
+ - [The compute-budget guard](#the-compute-budget-guard)
46
+ - [Using ctun with coding agents](#using-ctun-with-coding-agents)
47
+ - [Files and locations](#files-and-locations)
48
+
49
+ ---
50
+
51
+ ## How it works
52
+
53
+ `ctun` is built on OpenSSH **connection multiplexing** (`ControlMaster` /
54
+ `ControlPersist`). Authentication is a property of the *connection*, not of each
55
+ command:
56
+
57
+ - **`login`** opens a single **master connection** and leaves a control socket alive
58
+ in the background. This is the only step that needs your password + OTP.
59
+ - Every later **`run`** opens a lightweight channel *inside* that already-authenticated
60
+ connection — no password, no OTP, near-instant.
61
+
62
+ `ctun` itself is **stateless** — it runs once per command and exits. The persistent
63
+ state lives in the background ssh master, a small config file, and a per-cluster
64
+ session record. The connection lives as long as the network and the cluster's session
65
+ policy allow; when it eventually drops, `run` fails with a clear message and you simply
66
+ `login` again.
67
+
68
+ ---
69
+
70
+ ## Requirements
71
+
72
+ - **Linux or macOS** with an **OpenSSH client** (`ssh`) — version 7.x+.
73
+ - **Python ≥ 3.10**.
74
+ - For the optional pop-up login dialog (`login --interactive`): a graphical display and
75
+ a working **Tk** (provided by your system Python's `tkinter`).
76
+
77
+ ---
78
+
79
+ ## Install
80
+
81
+ Using [uv](https://docs.astral.sh/uv/) (recommended — installs `ctun` onto your PATH):
82
+
83
+ ```console
84
+ $ git clone https://github.com/aimat-lab/cluster-tunnel
85
+ $ cd cluster-tunnel
86
+ $ uv tool install .
87
+ $ ctun --help
88
+ ```
89
+
90
+ Or with pip:
91
+
92
+ ```console
93
+ $ pip install . # into the current environment
94
+ ```
95
+
96
+ For local development:
97
+
98
+ ```console
99
+ $ uv sync # create .venv and install
100
+ $ uv run ctun --help
101
+ $ uv run pytest # run the test suite
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Quickstart
107
+
108
+ **1. Create your config:**
109
+
110
+ ```console
111
+ $ ctun config --init
112
+ Config ready at /home/you/.config/cluster-tunnel/config.yaml
113
+ ```
114
+
115
+ **2. Add your cluster** (`ctun config` opens it in `$EDITOR`):
116
+
117
+ ```yaml
118
+ clusters:
119
+ horeka:
120
+ host: horeka.scc.kit.edu
121
+ user: ab1234
122
+ requires_otp: true # set false for clusters that don't use an OTP
123
+ description: |
124
+ HoreKa @ KIT. GPU jobs go to partition 'accelerated'.
125
+ ```
126
+
127
+ > The starter config (`ctun config --init`) already ships several commented
128
+ > example clusters. It's usually easiest to just **uncomment one and fill in your
129
+ > own username** rather than write a block from scratch.
130
+
131
+ **3. Log in once** — `-i/--interactive` pops up a small window for your password + OTP
132
+ (and works even when `ctun` has no terminal of its own, e.g. when an agent runs it):
133
+
134
+ ```console
135
+ $ ctun -t horeka login -i
136
+ Tunnel to 'horeka' established.
137
+ Session budget: unguarded (no limit set).
138
+ ```
139
+
140
+ **4. Run commands through the tunnel** — note the `--`, after which everything is sent
141
+ verbatim to the cluster:
142
+
143
+ ```console
144
+ $ ctun -t horeka run -- squeue --me
145
+ $ ctun -t horeka run -- sbatch train.sh
146
+ ```
147
+
148
+ **5. Check status, then log out when done:**
149
+
150
+ ```console
151
+ $ ctun -t horeka status
152
+ $ ctun -t horeka logout
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Commands
158
+
159
+ The cluster is selected with the global **`-t` / `--target`** option, placed **before**
160
+ the subcommand: `ctun -t <cluster> <command> …`.
161
+
162
+ Global options:
163
+
164
+ | Option | Meaning |
165
+ |---|---|
166
+ | `-t, --target <name>` | Cluster to act on (a key under `clusters:` in the config). |
167
+ | `-c, --config <path>` | Use an alternate config file (overrides `$CTUN_CONFIG`). |
168
+ | `-V, --verbose` | Enable debug logging. |
169
+ | `--version` | Print the version. |
170
+
171
+ ### `status` — tunnel and session state
172
+
173
+ ```console
174
+ $ ctun -t horeka status # one cluster (detailed)
175
+ $ ctun status # all configured clusters
176
+ $ ctun -t horeka status -j|--json # machine-readable
177
+ ```
178
+
179
+ Shows whether the tunnel is live, when the session started, and the budget limit.
180
+ Use it first whenever you lack context.
181
+
182
+ ### `config` — manage the config file
183
+
184
+ ```console
185
+ $ ctun config # open in $EDITOR
186
+ $ ctun config -i|--init # create a starter config if none exists
187
+ $ ctun config -p|--path # print the config file path
188
+ $ ctun config -s|--show # print the resolved config
189
+ $ ctun config --validate # check for errors + warn on missing budget
190
+ # scripts / invalid guard_commands regexes
191
+ ```
192
+
193
+ ### `login` — authenticate and open the tunnel
194
+
195
+ ```console
196
+ $ ctun -t horeka login [-i|--interactive] [-l|--limit N] [--timeout S]
197
+ ```
198
+
199
+ - Opens the persistent master connection.
200
+ - `-i, --interactive` **(recommended)** shows a small pop-up window (password + OTP +
201
+ limit) instead of prompting in the terminal, then feeds the secrets to ssh for you.
202
+ This is the easiest way to log in, and the only one that works when `ctun` is invoked by
203
+ a tool that has no terminal of its own (e.g. a coding agent) — a person at the machine
204
+ fills in the window. The password field may be left blank for clusters that need no
205
+ password. `--timeout S` bounds how long it waits (default 120s).
206
+ - Plain `ctun -t <cluster> login` (no `-i`) prompts for the password + OTP in the terminal.
207
+ - `-l, --limit N` sets this session's compute budget (overrides the cluster's configured
208
+ `session_limit`).
209
+
210
+ A **session** lasts for the lifetime of one authentication. Logging in again (after the
211
+ tunnel drops) starts a fresh session and resets the budget window.
212
+
213
+ ### `run` — run a command through the tunnel
214
+
215
+ ```console
216
+ $ ctun -t horeka run [--tty] [-n|--dry-run] -- <command> [args…]
217
+ ```
218
+
219
+ - Everything after `--` is forwarded to the cluster exactly as written, so flags like
220
+ `--me` or `--time=01:00:00` pass straight through.
221
+ - The command's **exit code is propagated** as `ctun`'s exit code, and output is streamed
222
+ live.
223
+ - `--tty` allocates a pseudo-terminal for interactive remote programs.
224
+ - `-n, --dry-run` reports the budget decision without executing; it exits with the same code
225
+ it *would* have used (see below), so it doubles as a pre-flight check.
226
+ - If there is no live tunnel, `run` **fails immediately** with a message telling you to
227
+ `login` — it never silently prompts for a password.
228
+
229
+ ```console
230
+ $ ctun -t horeka run -- squeue --me
231
+ $ ctun -t horeka run -- bash -c 'cd $WORK && sbatch job.sh'
232
+ ```
233
+
234
+ #### Exit codes
235
+
236
+ So a caller (especially a coding agent) can tell *why* `run` stopped, ctun's own
237
+ failures use distinct exit codes and also print a stable `ctun-error: <marker>` line to
238
+ stderr. A successful command's own exit code passes straight through, so these codes are
239
+ reserved for ctun's pre-flight failures:
240
+
241
+ | Exit code | `ctun-error:` marker | Meaning |
242
+ |---|---|---|
243
+ | `10` | `login_required` | No live tunnel — run `login` again. |
244
+ | `11` | `budget_exhausted` | A guarded command was blocked: session usage is at/over the limit. |
245
+ | `12` | `budget_guard_error` | A guarded command was blocked because the budget could not be verified (fail-closed). |
246
+ | *N* | — | The remote command's **own** exit code (0–255), propagated unchanged. |
247
+ | `2` | — | Usage error (e.g. no command after `--`). |
248
+
249
+ ```console
250
+ $ ctun -t horeka run -- sbatch big.sh ; echo "exit=$?"
251
+ ctun-error: budget_exhausted
252
+ Error: Submission blocked on 'horeka': compute budget exhausted.
253
+ ...
254
+ exit=11
255
+ ```
256
+
257
+ ### `upload` / `download` — transfer files over the tunnel
258
+
259
+ ```console
260
+ $ ctun -t horeka upload [-n|--dry-run] <local-src> <remote-dest> [-- <rsync args>]
261
+ $ ctun -t horeka download [-n|--dry-run] <remote-src> <local-dest> [-- <rsync args>]
262
+ ```
263
+
264
+ Move files in or out using **rsync over the live tunnel** — it rides the existing
265
+ authenticated connection, so there's **no re-authentication** (no password, no OTP).
266
+
267
+ - **`upload`** copies a local path → the cluster; **`download`** copies a cluster path →
268
+ local. The direction decides which side is remote, so remote paths are written **bare**
269
+ (no `host:` prefix); a relative remote path resolves to your remote `$HOME`.
270
+ - Copies **recursively** (`rsync -r`). Trailing slashes follow rsync's semantics: `data/`
271
+ copies the *contents* of `data`, `data` copies the directory itself.
272
+ - Transfers are **not** subject to the budget guard (moving data isn't compute), and they
273
+ **fail closed**: with no live tunnel the command exits `10` (`login_required`) rather than
274
+ prompting.
275
+ - rsync's own exit code is propagated. Extra rsync flags can follow `--`, e.g.
276
+ `-- --exclude='*.tmp' -z` or `-- --info=progress2` for a progress display.
277
+
278
+ ```console
279
+ $ ctun -t horeka upload ./dataset $WORK/dataset # push a folder
280
+ $ ctun -t horeka download logs/run42.out ./run42.out # pull a result file
281
+ ```
282
+
283
+ ### `info` — briefing for a cluster
284
+
285
+ ```console
286
+ $ ctun -t horeka info [-j|--json]
287
+ ```
288
+
289
+ Prints the cluster's description, its (advisory) restrictions, and the current budget
290
+ usage vs. limit. Works even without a live tunnel.
291
+
292
+ ### `logout` — close the tunnel
293
+
294
+ ```console
295
+ $ ctun -t horeka logout # one cluster
296
+ $ ctun logout # all configured clusters
297
+ ```
298
+
299
+ Tears down the master connection and clears the session record. Without a
300
+ target, it logs out every configured cluster.
301
+
302
+ To run `ctun logout` **automatically on every logout, reboot, or shutdown**,
303
+ install the systemd user hook in [`systemd/`](./systemd/) (`cd systemd &&
304
+ ./install.sh`). See [systemd/README.md](./systemd/README.md) for details.
305
+
306
+ ---
307
+
308
+ ## Configuration
309
+
310
+ Config lives at `~/.config/cluster-tunnel/config.yaml` (override with `$CTUN_CONFIG` or
311
+ `-c/--config`). A fully annotated example:
312
+
313
+ ```yaml
314
+ # Defaults inherited by every cluster (each is overridable per cluster).
315
+ defaults:
316
+ control_persist: "12h" # how long the tunnel stays alive; "yes" = until reboot/logout
317
+ server_alive_interval: 60 # keepalive seconds; fights idle disconnects
318
+ terminal: auto # reserved for future use
319
+
320
+ # Optional preamble surfaced in `ctun info` (e.g. guidance for an agent).
321
+ agent:
322
+ preamble: |
323
+ Respect the stated budget and restrictions before submitting jobs.
324
+
325
+ # One entry per cluster; the key (e.g. "horeka") is the name you pass to -t.
326
+ clusters:
327
+ horeka:
328
+ host: horeka.scc.kit.edu # cluster login host
329
+ user: ab1234 # your username on the cluster
330
+ ssh_alias: horeka # optional: reuse a Host block from ~/.ssh/config instead of host/user
331
+ identity_file: ~/.ssh/horeka # optional: SSH key to use
332
+ control_persist: "24h" # optional per-cluster override
333
+
334
+ description: |
335
+ HoreKa @ KIT. GPU jobs go to partition 'accelerated'.
336
+
337
+ restrictions: # advisory only — shown by `ctun info`, never enforced
338
+ max_job_runtime: "48:00:00"
339
+ max_gpus_per_job: 4
340
+ allowed_partitions: [accelerated, cpuonly]
341
+
342
+ budget: # omit this whole block to run the cluster unguarded
343
+ script: budget/horeka.sh # script that reports usage since the session started
344
+ session_limit: 500 # default budget; overridden by `login --limit`
345
+ unit: jobh # label for display (bundled scripts report job-hours)
346
+ guard_commands: [sbatch, srun, salloc] # commands subject to the budget check
347
+ fail_mode: closed # if the budget script errors: "closed" blocks, "open" allows
348
+ ```
349
+
350
+ **Connecting:** if `ssh_alias` is set, `ctun` uses that `~/.ssh/config` Host entry for the
351
+ hostname, user, and key; otherwise it uses `host` + `user` (+ optional `identity_file`).
352
+
353
+ **Restrictions** are informational. They are shown by `ctun info` as guidance but are
354
+ never enforced by `ctun` — the only hard enforcement is the budget guard below.
355
+
356
+ ---
357
+
358
+ ## The compute-budget guard
359
+
360
+ The guard limits how much compute a **session** consumes — where a session is the period
361
+ since you last logged in. It is a **threshold** check: once usage reaches the limit, new
362
+ job submissions are blocked. (It does not try to predict whether a particular job will
363
+ overshoot.)
364
+
365
+ Before running a command whose first word is in `guard_commands` (`sbatch`, `srun`,
366
+ `salloc`), `ctun`:
367
+
368
+ 1. Runs the cluster's **budget script** on the login node (over the live tunnel).
369
+ 2. Reads the single number it prints — the usage since the session started.
370
+ 3. Blocks the command if `used ≥ limit`; otherwise lets it run.
371
+
372
+ Commands that aren't job submissions (`ls`, `squeue`, `sacct`, …) always pass through. If
373
+ no limit is set for the cluster, it runs unguarded.
374
+
375
+ The default metric is **job-hours**: the Slurm job *wall-clock* time you have run since the
376
+ session started, counting only the portion of each job inside the session window (concurrent
377
+ jobs add up). The bundled budget scripts (`ctun config --init` copies them next to your
378
+ config) compute this from `sacct` and work on any cluster with Slurm accounting enabled.
379
+
380
+ You normally don't write a budget script yourself — `ctun config --init` copies the bundled
381
+ ones next to your config. For a different metric (GPU-hours, core-hours, …) adapt a bundled
382
+ script: it runs on the login node, receives `$1` session-start epoch, `$2` cluster, `$3`
383
+ username, and must print a single number. If it exits non-zero, `fail_mode` decides
384
+ (`closed` blocks the submission, `open` allows it). See [DESIGN.md](./DESIGN.md) for details.
385
+
386
+ Example of the guard in action:
387
+
388
+ ```console
389
+ $ ctun -t horeka run -- sbatch big.sh
390
+ BLOCKED on horeka: session budget exhausted: 503.2 >= 500.0 jobh. Command not submitted.
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Using ctun with coding agents
396
+
397
+ `ctun` exists so coding agents can drive HPC clusters autonomously. The pattern:
398
+
399
+ - **A human logs in once.** Run `ctun -t <cluster> login` yourself, or have the agent call
400
+ `ctun -t <cluster> login --interactive`, which pops a window for a present human to type
401
+ the password — the agent never sees the secret.
402
+ - **The agent then prefixes every cluster command** with `ctun -t <cluster> run -- …`. No
403
+ re-authentication is needed for the life of the tunnel, and the commands compose with
404
+ normal shell scripting and pipes.
405
+ - **The budget guard is the safety net.** Even if the agent tries to submit more work, job
406
+ submissions are blocked once the session limit is reached. `run` also **fails closed**:
407
+ if the tunnel has dropped, the agent gets a clear error and must request a fresh `login`
408
+ rather than anything proceeding unauthenticated.
409
+ - **`ctun -t <cluster> info`** gives the agent a concise briefing — the cluster
410
+ description, advisory restrictions, and remaining budget — to read before it starts.
411
+
412
+ All agents must run `ctun` on the **same machine** where the tunnel was opened, since the
413
+ control socket is local to that machine.
414
+
415
+ ---
416
+
417
+ ## Files and locations
418
+
419
+ | Path | Purpose |
420
+ |---|---|
421
+ | `~/.config/cluster-tunnel/config.yaml` | Your configuration. |
422
+ | `~/.config/cluster-tunnel/budget/<cluster>.sh` | Per-cluster budget scripts. |
423
+ | `~/.cache/cluster-tunnel/sockets/<cluster>` | SSH control sockets (the live tunnels). |
424
+ | `~/.cache/cluster-tunnel/sessions/<cluster>.json` | Internal session state (start time, limit). |
425
+
426
+ ---
427
+
428
+ *Design notes: see [PLAN.md](./PLAN.md) for the motivation and [DESIGN.md](./DESIGN.md)
429
+ for the detailed technical specification.*