nullgate 1.2.4__tar.gz → 1.2.6__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 (31) hide show
  1. {nullgate-1.2.4/src/nullgate.egg-info → nullgate-1.2.6}/PKG-INFO +77 -12
  2. {nullgate-1.2.4 → nullgate-1.2.6}/README.md +76 -11
  3. nullgate-1.2.6/src/nullgate/__init__.py +1 -0
  4. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/commands.py +64 -39
  5. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/session.py +362 -34
  6. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/transports.py +73 -20
  7. {nullgate-1.2.4 → nullgate-1.2.6/src/nullgate.egg-info}/PKG-INFO +77 -12
  8. nullgate-1.2.6/tests/test_commands.py +2347 -0
  9. nullgate-1.2.4/src/nullgate/__init__.py +0 -1
  10. nullgate-1.2.4/tests/test_commands.py +0 -770
  11. {nullgate-1.2.4 → nullgate-1.2.6}/LICENSE +0 -0
  12. {nullgate-1.2.4 → nullgate-1.2.6}/pyproject.toml +0 -0
  13. {nullgate-1.2.4 → nullgate-1.2.6}/setup.cfg +0 -0
  14. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/account.py +0 -0
  15. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/bridge.py +0 -0
  16. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/client_config.py +0 -0
  17. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/gateway.py +0 -0
  18. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/ingress.py +0 -0
  19. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/runtime.py +0 -0
  20. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate/wsroute.py +0 -0
  21. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate.egg-info/SOURCES.txt +0 -0
  22. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate.egg-info/dependency_links.txt +0 -0
  23. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate.egg-info/entry_points.txt +0 -0
  24. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate.egg-info/requires.txt +0 -0
  25. {nullgate-1.2.4 → nullgate-1.2.6}/src/nullgate.egg-info/top_level.txt +0 -0
  26. {nullgate-1.2.4 → nullgate-1.2.6}/tests/test_account.py +0 -0
  27. {nullgate-1.2.4 → nullgate-1.2.6}/tests/test_bridge.py +0 -0
  28. {nullgate-1.2.4 → nullgate-1.2.6}/tests/test_gateway.py +0 -0
  29. {nullgate-1.2.4 → nullgate-1.2.6}/tests/test_ingress.py +0 -0
  30. {nullgate-1.2.4 → nullgate-1.2.6}/tests/test_install.py +0 -0
  31. {nullgate-1.2.4 → nullgate-1.2.6}/tests/test_wsroute.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nullgate
3
- Version: 1.2.4
3
+ Version: 1.2.6
4
4
  Summary: Disposable SSH gateway into confined directory workspaces
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -36,8 +36,9 @@ uvx nullgate status
36
36
  uvx nullgate connect
37
37
  ```
38
38
 
39
- Nullgate allows connections without credentials by default. Use
40
- `--no-public-access` to require an authorized SSH key or generated password.
39
+ Nullgate requires authentication by default (an authorized SSH key and/or a
40
+ generated password). Pass `--public-access` to allow connections without
41
+ credentials (unsafe on public endpoints).
41
42
 
42
43
  ## Features
43
44
 
@@ -71,7 +72,9 @@ pip install nullgate
71
72
 
72
73
  ### Start
73
74
 
74
- Launch the gateway service and ingress transport in the background:
75
+ Start the gateway service and ingress transport, then report readiness once the
76
+ gateway listens, the transport stays alive, and srv.us has announced a
77
+ hostname (for that provider):
75
78
 
76
79
  ```bash
77
80
  # Default srv.us transport on port 4822 sharing current directory
@@ -97,12 +100,24 @@ Options:
97
100
  - `-e`, `--relay-url`: WebSocket relay URL used with `upterm` or `cloudflare`.
98
101
  - `-H`, `--public-hostname`: Public hostname used with `cloudflared`.
99
102
  - `--cloudflare-token`: Cloudflare tunnel credential, or set `NULLGATE_CLOUDFLARED_TOKEN`.
100
- - `--public-access`: Allow connections without credentials. Enabled by default and unsafe on public endpoints.
103
+ - `--identity-seed VALUE`: srv.us identity seed value (visible in argv and shell history; flag overrides seed file and `NULLGATE_IDENTITY_SEED`; other providers ignore it). Prefer `--identity-seed-file PATH` to avoid history exposure. The direct value is never printed or saved; `restart` cannot reuse it because only the seed-file path is persisted. The seed only matters when the identity key does not yet exist.
104
+ - `--identity-seed-file PATH`: Read the srv.us identity seed from a file (overrides `NULLGATE_IDENTITY_SEED`; other providers ignore it). The file must hold nonempty UTF-8 text; only the path is saved for `restart`, never the seed value. `restart` revalidates the saved file before stopping the running session. The seed only matters when the identity key does not yet exist.
105
+ - `--public-access`: Allow connections without credentials (unsafe on public endpoints). Off by default; only `restart` reuses a saved setting, a fresh `start` always defaults to authenticated access.
101
106
  - `--no-public-access`: Require authentication.
102
- - `--tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
103
- - `--no-tcp-forwarding`: Disable SSH TCP tunneling.
107
+ - `--tcp-forwarding`: Allow SSH TCP tunneling (`ssh -L`; enabled by default on fresh `start`).
108
+ - `--no-tcp-forwarding`: Disable SSH TCP tunneling. `restart` reuses the saved effective setting.
104
109
  - `--restrict-file-transfer`: Keep SFTP and SCP inside the workspace.
105
110
  - `--no-restrict-file-transfer`: Allow host-visible file transfers.
111
+ - `--foreground`: Stay in the foreground and supervise gateway and transport instead of exiting after readiness. The parent handles SIGINT and SIGTERM and stops both children, exiting nonzero with recent log tails if either child dies.
112
+ - `--json`: Emit machine-readable JSON on stdout (the only stdout in JSON mode). `start --foreground --json` prints one readiness object, flushes it, then supervises.
113
+ - `--replace-stale`: Replace tracked gateway and transport processes when they are not healthy. Healthy srv.us sessions are reused.
114
+ - `--rendezvous-url URL`: After srv.us passes its public SSH-over-TLS check, POST a minimal readiness document. Defaults to `NULLGATE_RENDEZVOUS_URL` and is saved for `restart`; the URL is never printed.
115
+
116
+ A fresh `start` uses documented defaults and the current directory when no
117
+ workspace is given; only `restart` reuses saved workspace, provider, port,
118
+ and security settings. Only the hidden external-launcher flags `--notte-api`
119
+ (boolean) and `--notte-user VALUE` are accepted for compatibility; any other
120
+ unknown flag fails normally.
106
121
 
107
122
  ### Status
108
123
 
@@ -110,16 +125,26 @@ Display operational status, running PIDs, connection URLs, and security settings
110
125
 
111
126
  ```bash
112
127
  nullgate status
128
+ nullgate status --json
113
129
  ```
114
130
 
131
+ When stdout is not a TTY, `status` emits only a directly usable SSH fallback
132
+ command plus a `password: VALUE` line when a generated password exists; with no
133
+ live target it prints `stopped` or `pending`. `--json` is the stable full
134
+ machine interface.
135
+
115
136
  ### Connect
116
137
 
117
138
  Print client connection strings and OpenSSH client configuration directives:
118
139
 
119
140
  ```bash
120
141
  nullgate connect
142
+ nullgate connect --json
121
143
  ```
122
144
 
145
+ On an interactive TTY `connect` keeps the setup instructions; piped output
146
+ emits only the directly usable fallback command.
147
+
123
148
  ### Logs
124
149
 
125
150
  Stream or print daemon logs:
@@ -141,7 +166,10 @@ nullgate stop
141
166
 
142
167
  ### Restart
143
168
 
144
- Restart daemon instances reusing active or saved workspace settings:
169
+ Restart daemon instances reusing the saved workspace, provider, port, and
170
+ security settings from the previous start. A direct `--identity-seed` value
171
+ from the original start is intentionally not persisted, so `restart` cannot
172
+ reuse it; only the seed-file path is saved and revalidated.
145
173
 
146
174
  ```bash
147
175
  nullgate restart
@@ -160,12 +188,12 @@ Short command aliases are `up`, `down`, `ps`, `ssh`, `log`, and `reload`.
160
188
  The earlier names `open`, `shut`, `inspect`, `enter`, `trace`, and `cycle`
161
189
  remain available as compatibility aliases. Their earlier option names also
162
190
  remain accepted, but new scripts should use the names documented above.
163
- Unknown flags and their optional values are ignored for compatibility with
164
- external launchers.
191
+ Unknown flags fail normally; only the hidden external-launcher flags
192
+ `--notte-api` (boolean) and `--notte-user VALUE` are accepted as no-ops.
165
193
 
166
194
  ## Authentication and Security Boundaries
167
195
 
168
- - **Authentication Baseline**: Public access is enabled by default. Pass `--no-public-access` to require authorized SSH keys from `~/.ssh/authorized_keys` or a high-entropy secret created in the runtime directory.
196
+ - **Authentication Baseline**: Authenticated access is the default. Pass `--public-access` to allow connections without credentials (unsafe on public endpoints). Without it, srv.us secure startup uses authorized SSH keys from `~/.ssh/authorized_keys` and/or a generated high-entropy password stored in the runtime directory.
169
197
  - **Public access (`--public-access`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
170
198
  - **Upterm Transport**: Relies on authorized SSH public keys or `--public-access` (Upterm does not support password authentication). The relay itself is trusted for inbound multiplexing.
171
199
  - **Filesystem Confinement**: SFTP and SCP access cannot escape the designated workspace directory.
@@ -179,14 +207,51 @@ external launchers.
179
207
  |---|---|
180
208
  | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate start` |
181
209
  | `NULLGATE_STATE_DIR` | Directory holding runtime state and logs (defaults to `~/.local/state/nullgate`) |
182
- | `NULLGATE_IDENTITY_SEED` | Seed value used for deterministic Ed25519 key derivation |
210
+ | `NULLGATE_IDENTITY_SEED` | Seed for deterministic Ed25519 key derivation; only used when creating the identity file, never when it already exists |
183
211
  | `NULLGATE_TUNNEL_HOST` | Host address for srv.us ingress (default: `srv.us`) |
184
212
  | `NULLGATE_TUNNEL_PORT` | Port for srv.us ingress (default: `22`) |
185
213
  | `NULLGATE_CLOUDFLARED_TOKEN` | Credential token for `cloudflared` tunnels |
186
214
  | `NULLGATE_CONFINE_SFTP` | Set to `1` to confine SFTP and SCP transfers to the workspace root |
215
+ | `NULLGATE_RENDEZVOUS_URL` | HTTP endpoint for publishing successful srv.us readiness; may contain a secret token and is never printed |
187
216
  | `NULLGATE_VERSION` | Install a specific published version instead of the stable release; accepts `1.0.1` or `v1.0.1` |
188
217
  | `NULLGATE_DIST_URL` | Override the public release channel used by `install.sh` |
189
218
  | `NULLGATE_INSTALL_URL` | Override the installer URL used by `nullgate upgrade` |
219
+ | `NO_COLOR` | Set to any value to disable ANSI color output (non-TTY output never uses color) |
220
+
221
+ ## Automation/CI
222
+
223
+ - Noninteractive runs never prompt: with stdin closed or piped, `start` skips
224
+ the key prompt and generates the password for srv.us secure mode. Upterm
225
+ secure mode without authorized keys fails immediately instead of hanging.
226
+ - Readiness is decisive: `start` returns success only after the gateway
227
+ listens, the transport stays alive, and (for srv.us) a hostname is
228
+ announced and serves an SSH banner over certificate-validated TLS. On timeout or failure both processes are stopped, recent
229
+ transport logs go to stderr, and the exit code is nonzero.
230
+ - `start --foreground` keeps the parent alive supervising both children,
231
+ handles SIGINT/SIGTERM by stopping them, and exits nonzero with recent log tails if
232
+ either child dies. Background start remains the default.
233
+ - `start`, `status`, and `connect` accept `--json` for machine-readable
234
+ output. JSON is the only stdout in JSON mode and carries the operational
235
+ fields agents need: version, provider, running state and PIDs, port,
236
+ workspace, URL, host, user, proxy command, public health, auth mode, TCP forwarding, and
237
+ file-transfer mode. `--json` is the stable agent contract; human output may
238
+ change. `start --foreground --json` prints one readiness object,
239
+ flushes it, then supervises.
240
+ - Piped output is compact: successful `start`, `status`, and `connect` with a
241
+ target emit only a directly usable SSH fallback command plus one
242
+ `password: VALUE` line when a generated password exists. With no live
243
+ usable target, `status` prints `stopped` or `pending`.
244
+ - Identity seeds: `--identity-seed-file PATH` keeps the value out of argv
245
+ and history (flag overrides `NULLGATE_IDENTITY_SEED`); `--identity-seed
246
+ VALUE` is also accepted but is visible in argv and shell history. Both apply
247
+ only to the srv.us provider, fail before launch when absent, unreadable, or
248
+ empty (direct value: nonempty after stripping), pass the seed only to the
249
+ srv.us child environment (every other child gets a seed-free environment even
250
+ when the parent exported one), and never print or persist the value. Only the
251
+ seed-file path is saved for `restart`, which revalidates the saved srv.us seed
252
+ file before stopping anything; a direct `--identity-seed` from the original
253
+ start is intentionally not persisted, so `restart` cannot reuse it. The seed
254
+ only takes effect when the identity key does not yet exist.
190
255
 
191
256
  ## Development
192
257
 
@@ -24,8 +24,9 @@ uvx nullgate status
24
24
  uvx nullgate connect
25
25
  ```
26
26
 
27
- Nullgate allows connections without credentials by default. Use
28
- `--no-public-access` to require an authorized SSH key or generated password.
27
+ Nullgate requires authentication by default (an authorized SSH key and/or a
28
+ generated password). Pass `--public-access` to allow connections without
29
+ credentials (unsafe on public endpoints).
29
30
 
30
31
  ## Features
31
32
 
@@ -59,7 +60,9 @@ pip install nullgate
59
60
 
60
61
  ### Start
61
62
 
62
- Launch the gateway service and ingress transport in the background:
63
+ Start the gateway service and ingress transport, then report readiness once the
64
+ gateway listens, the transport stays alive, and srv.us has announced a
65
+ hostname (for that provider):
63
66
 
64
67
  ```bash
65
68
  # Default srv.us transport on port 4822 sharing current directory
@@ -85,12 +88,24 @@ Options:
85
88
  - `-e`, `--relay-url`: WebSocket relay URL used with `upterm` or `cloudflare`.
86
89
  - `-H`, `--public-hostname`: Public hostname used with `cloudflared`.
87
90
  - `--cloudflare-token`: Cloudflare tunnel credential, or set `NULLGATE_CLOUDFLARED_TOKEN`.
88
- - `--public-access`: Allow connections without credentials. Enabled by default and unsafe on public endpoints.
91
+ - `--identity-seed VALUE`: srv.us identity seed value (visible in argv and shell history; flag overrides seed file and `NULLGATE_IDENTITY_SEED`; other providers ignore it). Prefer `--identity-seed-file PATH` to avoid history exposure. The direct value is never printed or saved; `restart` cannot reuse it because only the seed-file path is persisted. The seed only matters when the identity key does not yet exist.
92
+ - `--identity-seed-file PATH`: Read the srv.us identity seed from a file (overrides `NULLGATE_IDENTITY_SEED`; other providers ignore it). The file must hold nonempty UTF-8 text; only the path is saved for `restart`, never the seed value. `restart` revalidates the saved file before stopping the running session. The seed only matters when the identity key does not yet exist.
93
+ - `--public-access`: Allow connections without credentials (unsafe on public endpoints). Off by default; only `restart` reuses a saved setting, a fresh `start` always defaults to authenticated access.
89
94
  - `--no-public-access`: Require authentication.
90
- - `--tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
91
- - `--no-tcp-forwarding`: Disable SSH TCP tunneling.
95
+ - `--tcp-forwarding`: Allow SSH TCP tunneling (`ssh -L`; enabled by default on fresh `start`).
96
+ - `--no-tcp-forwarding`: Disable SSH TCP tunneling. `restart` reuses the saved effective setting.
92
97
  - `--restrict-file-transfer`: Keep SFTP and SCP inside the workspace.
93
98
  - `--no-restrict-file-transfer`: Allow host-visible file transfers.
99
+ - `--foreground`: Stay in the foreground and supervise gateway and transport instead of exiting after readiness. The parent handles SIGINT and SIGTERM and stops both children, exiting nonzero with recent log tails if either child dies.
100
+ - `--json`: Emit machine-readable JSON on stdout (the only stdout in JSON mode). `start --foreground --json` prints one readiness object, flushes it, then supervises.
101
+ - `--replace-stale`: Replace tracked gateway and transport processes when they are not healthy. Healthy srv.us sessions are reused.
102
+ - `--rendezvous-url URL`: After srv.us passes its public SSH-over-TLS check, POST a minimal readiness document. Defaults to `NULLGATE_RENDEZVOUS_URL` and is saved for `restart`; the URL is never printed.
103
+
104
+ A fresh `start` uses documented defaults and the current directory when no
105
+ workspace is given; only `restart` reuses saved workspace, provider, port,
106
+ and security settings. Only the hidden external-launcher flags `--notte-api`
107
+ (boolean) and `--notte-user VALUE` are accepted for compatibility; any other
108
+ unknown flag fails normally.
94
109
 
95
110
  ### Status
96
111
 
@@ -98,16 +113,26 @@ Display operational status, running PIDs, connection URLs, and security settings
98
113
 
99
114
  ```bash
100
115
  nullgate status
116
+ nullgate status --json
101
117
  ```
102
118
 
119
+ When stdout is not a TTY, `status` emits only a directly usable SSH fallback
120
+ command plus a `password: VALUE` line when a generated password exists; with no
121
+ live target it prints `stopped` or `pending`. `--json` is the stable full
122
+ machine interface.
123
+
103
124
  ### Connect
104
125
 
105
126
  Print client connection strings and OpenSSH client configuration directives:
106
127
 
107
128
  ```bash
108
129
  nullgate connect
130
+ nullgate connect --json
109
131
  ```
110
132
 
133
+ On an interactive TTY `connect` keeps the setup instructions; piped output
134
+ emits only the directly usable fallback command.
135
+
111
136
  ### Logs
112
137
 
113
138
  Stream or print daemon logs:
@@ -129,7 +154,10 @@ nullgate stop
129
154
 
130
155
  ### Restart
131
156
 
132
- Restart daemon instances reusing active or saved workspace settings:
157
+ Restart daemon instances reusing the saved workspace, provider, port, and
158
+ security settings from the previous start. A direct `--identity-seed` value
159
+ from the original start is intentionally not persisted, so `restart` cannot
160
+ reuse it; only the seed-file path is saved and revalidated.
133
161
 
134
162
  ```bash
135
163
  nullgate restart
@@ -148,12 +176,12 @@ Short command aliases are `up`, `down`, `ps`, `ssh`, `log`, and `reload`.
148
176
  The earlier names `open`, `shut`, `inspect`, `enter`, `trace`, and `cycle`
149
177
  remain available as compatibility aliases. Their earlier option names also
150
178
  remain accepted, but new scripts should use the names documented above.
151
- Unknown flags and their optional values are ignored for compatibility with
152
- external launchers.
179
+ Unknown flags fail normally; only the hidden external-launcher flags
180
+ `--notte-api` (boolean) and `--notte-user VALUE` are accepted as no-ops.
153
181
 
154
182
  ## Authentication and Security Boundaries
155
183
 
156
- - **Authentication Baseline**: Public access is enabled by default. Pass `--no-public-access` to require authorized SSH keys from `~/.ssh/authorized_keys` or a high-entropy secret created in the runtime directory.
184
+ - **Authentication Baseline**: Authenticated access is the default. Pass `--public-access` to allow connections without credentials (unsafe on public endpoints). Without it, srv.us secure startup uses authorized SSH keys from `~/.ssh/authorized_keys` and/or a generated high-entropy password stored in the runtime directory.
157
185
  - **Public access (`--public-access`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
158
186
  - **Upterm Transport**: Relies on authorized SSH public keys or `--public-access` (Upterm does not support password authentication). The relay itself is trusted for inbound multiplexing.
159
187
  - **Filesystem Confinement**: SFTP and SCP access cannot escape the designated workspace directory.
@@ -167,14 +195,51 @@ external launchers.
167
195
  |---|---|
168
196
  | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate start` |
169
197
  | `NULLGATE_STATE_DIR` | Directory holding runtime state and logs (defaults to `~/.local/state/nullgate`) |
170
- | `NULLGATE_IDENTITY_SEED` | Seed value used for deterministic Ed25519 key derivation |
198
+ | `NULLGATE_IDENTITY_SEED` | Seed for deterministic Ed25519 key derivation; only used when creating the identity file, never when it already exists |
171
199
  | `NULLGATE_TUNNEL_HOST` | Host address for srv.us ingress (default: `srv.us`) |
172
200
  | `NULLGATE_TUNNEL_PORT` | Port for srv.us ingress (default: `22`) |
173
201
  | `NULLGATE_CLOUDFLARED_TOKEN` | Credential token for `cloudflared` tunnels |
174
202
  | `NULLGATE_CONFINE_SFTP` | Set to `1` to confine SFTP and SCP transfers to the workspace root |
203
+ | `NULLGATE_RENDEZVOUS_URL` | HTTP endpoint for publishing successful srv.us readiness; may contain a secret token and is never printed |
175
204
  | `NULLGATE_VERSION` | Install a specific published version instead of the stable release; accepts `1.0.1` or `v1.0.1` |
176
205
  | `NULLGATE_DIST_URL` | Override the public release channel used by `install.sh` |
177
206
  | `NULLGATE_INSTALL_URL` | Override the installer URL used by `nullgate upgrade` |
207
+ | `NO_COLOR` | Set to any value to disable ANSI color output (non-TTY output never uses color) |
208
+
209
+ ## Automation/CI
210
+
211
+ - Noninteractive runs never prompt: with stdin closed or piped, `start` skips
212
+ the key prompt and generates the password for srv.us secure mode. Upterm
213
+ secure mode without authorized keys fails immediately instead of hanging.
214
+ - Readiness is decisive: `start` returns success only after the gateway
215
+ listens, the transport stays alive, and (for srv.us) a hostname is
216
+ announced and serves an SSH banner over certificate-validated TLS. On timeout or failure both processes are stopped, recent
217
+ transport logs go to stderr, and the exit code is nonzero.
218
+ - `start --foreground` keeps the parent alive supervising both children,
219
+ handles SIGINT/SIGTERM by stopping them, and exits nonzero with recent log tails if
220
+ either child dies. Background start remains the default.
221
+ - `start`, `status`, and `connect` accept `--json` for machine-readable
222
+ output. JSON is the only stdout in JSON mode and carries the operational
223
+ fields agents need: version, provider, running state and PIDs, port,
224
+ workspace, URL, host, user, proxy command, public health, auth mode, TCP forwarding, and
225
+ file-transfer mode. `--json` is the stable agent contract; human output may
226
+ change. `start --foreground --json` prints one readiness object,
227
+ flushes it, then supervises.
228
+ - Piped output is compact: successful `start`, `status`, and `connect` with a
229
+ target emit only a directly usable SSH fallback command plus one
230
+ `password: VALUE` line when a generated password exists. With no live
231
+ usable target, `status` prints `stopped` or `pending`.
232
+ - Identity seeds: `--identity-seed-file PATH` keeps the value out of argv
233
+ and history (flag overrides `NULLGATE_IDENTITY_SEED`); `--identity-seed
234
+ VALUE` is also accepted but is visible in argv and shell history. Both apply
235
+ only to the srv.us provider, fail before launch when absent, unreadable, or
236
+ empty (direct value: nonempty after stripping), pass the seed only to the
237
+ srv.us child environment (every other child gets a seed-free environment even
238
+ when the parent exported one), and never print or persist the value. Only the
239
+ seed-file path is saved for `restart`, which revalidates the saved srv.us seed
240
+ file before stopping anything; a direct `--identity-seed` from the original
241
+ start is intentionally not persisted, so `restart` cannot reuse it. The seed
242
+ only takes effect when the identity key does not yet exist.
178
243
 
179
244
  ## Development
180
245
 
@@ -0,0 +1 @@
1
+ __version__ = "1.2.6"
@@ -118,7 +118,7 @@ def build_parser() -> argparse.ArgumentParser:
118
118
  dest="allow_tcp_forwarding",
119
119
  action=argparse.BooleanOptionalAction,
120
120
  default=None,
121
- help="Allow SSH port forwarding.",
121
+ help="Allow SSH port forwarding (enabled by default; --no-tcp-forwarding disables).",
122
122
  )
123
123
  start_parser.add_argument(
124
124
  "--allow-tcp-forwarding",
@@ -161,6 +161,54 @@ def build_parser() -> argparse.ArgumentParser:
161
161
  help="Cloudflare tunnel token (for the cloudflared provider).",
162
162
  )
163
163
  start_parser.add_argument("--token", dest="token", help=argparse.SUPPRESS)
164
+ seed_group = start_parser.add_mutually_exclusive_group()
165
+ seed_group.add_argument(
166
+ "--identity-seed",
167
+ dest="identity_seed",
168
+ metavar="VALUE",
169
+ default=None,
170
+ help="srv.us identity seed value (visible in argv/history; ignored by other providers).",
171
+ )
172
+ seed_group.add_argument(
173
+ "--identity-seed-file",
174
+ dest="identity_seed_file",
175
+ metavar="PATH",
176
+ default=None,
177
+ help="File holding the srv.us identity seed (overrides NULLGATE_IDENTITY_SEED; ignored by other providers).",
178
+ )
179
+ start_parser.add_argument(
180
+ "--foreground",
181
+ dest="foreground",
182
+ action="store_true",
183
+ help="Stay in the foreground and supervise gateway and transport.",
184
+ )
185
+ start_parser.add_argument(
186
+ "--json",
187
+ dest="json",
188
+ action="store_true",
189
+ help="Emit machine-readable JSON on stdout.",
190
+ )
191
+ start_parser.add_argument(
192
+ "--replace-stale",
193
+ action="store_true",
194
+ help="Replace tracked processes when they are not publicly healthy.",
195
+ )
196
+ start_parser.add_argument(
197
+ "--rendezvous-url",
198
+ metavar="URL",
199
+ default=None,
200
+ help="Publish srv.us readiness to an HTTP endpoint.",
201
+ )
202
+ start_parser.add_argument(
203
+ "--notte-api", dest="notte_api", action="store_true", help=argparse.SUPPRESS
204
+ )
205
+ start_parser.add_argument(
206
+ "--notte-user",
207
+ dest="notte_user",
208
+ metavar="VALUE",
209
+ default=None,
210
+ help=argparse.SUPPRESS,
211
+ )
164
212
 
165
213
  sub.add_parser(
166
214
  "stop",
@@ -169,19 +217,31 @@ def build_parser() -> argparse.ArgumentParser:
169
217
  help="Stop SSH access (aliases: down, shut).",
170
218
  )
171
219
 
172
- sub.add_parser(
220
+ status_parser = sub.add_parser(
173
221
  "status",
174
222
  aliases=["ps", "inspect"],
175
223
  description="Show service health, connection details, and security settings.",
176
224
  help="Show current status (aliases: ps, inspect).",
177
225
  )
226
+ status_parser.add_argument(
227
+ "--json",
228
+ dest="json",
229
+ action="store_true",
230
+ help="Emit machine-readable JSON on stdout.",
231
+ )
178
232
 
179
- sub.add_parser(
233
+ connect_parser = sub.add_parser(
180
234
  "connect",
181
235
  aliases=["ssh", "enter"],
182
236
  description="Show how to connect with SSH.",
183
237
  help="Show SSH connection instructions (aliases: ssh, enter).",
184
238
  )
239
+ connect_parser.add_argument(
240
+ "--json",
241
+ dest="json",
242
+ action="store_true",
243
+ help="Emit machine-readable JSON on stdout.",
244
+ )
185
245
 
186
246
  logs_parser = sub.add_parser(
187
247
  "logs",
@@ -236,47 +296,12 @@ def build_parser() -> argparse.ArgumentParser:
236
296
  return parser
237
297
 
238
298
 
239
- def _known_options(parser: argparse.ArgumentParser) -> set[str]:
240
- options = set(parser._option_string_actions)
241
- for action in parser._actions:
242
- if isinstance(action, argparse._SubParsersAction):
243
- for child in action.choices.values():
244
- options.update(_known_options(child))
245
- return options
246
-
247
-
248
- def _discard_unknown_options(
249
- parser: argparse.ArgumentParser, argv: Sequence[str]
250
- ) -> list[str]:
251
- """Drop integration flags, including a following value when present."""
252
-
253
- known = _known_options(parser)
254
- filtered: list[str] = []
255
- index = 0
256
- while index < len(argv):
257
- token = argv[index]
258
- option = token.split("=", 1)[0]
259
- if token.startswith("-") and option not in known:
260
- index += 1
261
- if (
262
- "=" not in token
263
- and index < len(argv)
264
- and not argv[index].startswith("-")
265
- and argv[index] not in SUBCOMMANDS
266
- ):
267
- index += 1
268
- continue
269
- filtered.append(token)
270
- index += 1
271
- return filtered
272
-
273
-
274
299
  def main(argv: Sequence[str] | None = None) -> int:
275
300
  parser = build_parser()
276
301
  supplied = list(sys.argv[1:] if argv is None else argv)
277
302
  if supplied in (["--help"], ["api", "--help"]):
278
303
  supplied = ["start"]
279
- args = parser.parse_args(_discard_unknown_options(parser, supplied))
304
+ args = parser.parse_args(supplied)
280
305
 
281
306
  handlers = {
282
307
  "start": session.cmd_open,