nullgate 1.1.1__tar.gz → 1.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 (31) hide show
  1. {nullgate-1.1.1/src/nullgate.egg-info → nullgate-1.2.0}/PKG-INFO +41 -37
  2. {nullgate-1.1.1 → nullgate-1.2.0}/README.md +40 -36
  3. nullgate-1.2.0/src/nullgate/__init__.py +1 -0
  4. nullgate-1.2.0/src/nullgate/commands.py +246 -0
  5. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/session.py +13 -10
  6. {nullgate-1.1.1 → nullgate-1.2.0/src/nullgate.egg-info}/PKG-INFO +41 -37
  7. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_commands.py +44 -20
  8. nullgate-1.1.1/src/nullgate/__init__.py +0 -1
  9. nullgate-1.1.1/src/nullgate/commands.py +0 -179
  10. {nullgate-1.1.1 → nullgate-1.2.0}/LICENSE +0 -0
  11. {nullgate-1.1.1 → nullgate-1.2.0}/pyproject.toml +0 -0
  12. {nullgate-1.1.1 → nullgate-1.2.0}/setup.cfg +0 -0
  13. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/account.py +0 -0
  14. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/bridge.py +0 -0
  15. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/client_config.py +0 -0
  16. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/gateway.py +0 -0
  17. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/ingress.py +0 -0
  18. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/runtime.py +0 -0
  19. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/transports.py +0 -0
  20. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate/wsroute.py +0 -0
  21. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate.egg-info/SOURCES.txt +0 -0
  22. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate.egg-info/dependency_links.txt +0 -0
  23. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate.egg-info/entry_points.txt +0 -0
  24. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate.egg-info/requires.txt +0 -0
  25. {nullgate-1.1.1 → nullgate-1.2.0}/src/nullgate.egg-info/top_level.txt +0 -0
  26. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_account.py +0 -0
  27. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_bridge.py +0 -0
  28. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_gateway.py +0 -0
  29. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_ingress.py +0 -0
  30. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_install.py +0 -0
  31. {nullgate-1.1.1 → nullgate-1.2.0}/tests/test_wsroute.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nullgate
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Disposable SSH gateway into confined directory workspaces
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -18,7 +18,7 @@ Disposable SSH gateway into confined directory workspaces for remote containers,
18
18
 
19
19
  - **Ingress Transports**: Publish endpoints via Upterm WebSocket relays (default), srv.us reverse tunnels, Cloudflare Worker relays, or Cloudflare Argo (`cloudflared`) tunnels.
20
20
  - **Confinement**: SFTP and SCP workloads are restricted to the designated workspace root; parent traversals and out-of-root symlinks are blocked.
21
- - **Host file visibility**: Relative transfer paths begin in the workspace root while absolute paths address host locations, matching shell behavior; add `--confine-sftp` for strict workspace-only transfers.
21
+ - **Host file visibility**: Relative transfer paths begin in the workspace root while absolute paths address host locations, matching shell behavior; add `--restrict-file-transfer` for strict workspace-only transfers.
22
22
  - **Direct Shell Access**: Authenticated PTY sessions run interactively under the local user credentials.
23
23
  - **Synthetic UID Resilience**: Runs seamlessly in container environments missing user records in `/etc/passwd`.
24
24
  - **Persistent Key Identities**: Host keys and identity seeds persist across restarts rather than being silently regenerated.
@@ -44,77 +44,77 @@ pip install nullgate
44
44
 
45
45
  ## Usage
46
46
 
47
- ### Open
47
+ ### Start
48
48
 
49
49
  Launch the gateway service and ingress transport in the background:
50
50
 
51
51
  ```bash
52
52
  # Default Upterm transport on port 4822 sharing current directory
53
- nullgate open
53
+ nullgate start
54
54
 
55
55
  # Custom path, port, and slot
56
- nullgate open /workspace --port 4822 --slot 1
56
+ nullgate start /workspace --ssh-port 4822 --tunnel-slot 1
57
57
 
58
58
  # Select specific transport provider
59
- nullgate open . --transport upterm
60
- nullgate open . --transport srvus
61
- nullgate open . --transport cloudflare --endpoint https://relay.example.com
62
- nullgate open . --transport cloudflared --hostname workspace.example.com --token "$TUNNEL_TOKEN"
59
+ nullgate start . --provider upterm
60
+ nullgate start . --provider srvus
61
+ nullgate start . --provider cloudflare --relay-url https://relay.example.com
62
+ nullgate start . --provider cloudflared --public-hostname workspace.example.com --cloudflare-token "$TUNNEL_TOKEN"
63
63
  ```
64
64
 
65
65
  Options:
66
- - `--transport`: Ingress provider (`upterm`, `srvus`, `cloudflare`, `cloudflared`). Defaults to `upterm`.
67
- - `--endpoint`: WebSocket or relay endpoint URL (used with `upterm` or `cloudflare`).
68
- - `--hostname`: Public ingress hostname for `cloudflared`.
69
- - `--token`: Argo tunnel authentication credential (or set via `NULLGATE_CLOUDFLARED_TOKEN`).
70
- - `--accept`: Skip credential checks and admit any client connection.
71
- - `--no-accept`: Restore authentication when saved settings previously enabled unrestricted access.
72
- - `--allow-tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
73
- - `--no-allow-tcp-forwarding`: Disable forwarding when it was enabled in saved settings.
74
- - `--confine-sftp`: Confine SFTP and SCP transfers to the workspace root.
75
- - `--no-confine-sftp`: Restore host-visible transfers when confinement was saved.
76
-
77
- ### Inspect
66
+ - `--provider`: Public connection provider (`upterm`, `srvus`, `cloudflare`, or `cloudflared`). Defaults to `upterm`.
67
+ - `--relay-url`: WebSocket relay URL used with `upterm` or `cloudflare`.
68
+ - `--public-hostname`: Public hostname used with `cloudflared`.
69
+ - `--cloudflare-token`: Cloudflare tunnel credential, or set `NULLGATE_CLOUDFLARED_TOKEN`.
70
+ - `--public-access`: Allow connections without credentials. This is unsafe on public endpoints.
71
+ - `--no-public-access`: Require authentication.
72
+ - `--tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
73
+ - `--no-tcp-forwarding`: Disable SSH TCP tunneling.
74
+ - `--restrict-file-transfer`: Keep SFTP and SCP inside the workspace.
75
+ - `--no-restrict-file-transfer`: Allow host-visible file transfers.
76
+
77
+ ### Status
78
78
 
79
79
  Display operational status, running PIDs, connection URLs, and security settings:
80
80
 
81
81
  ```bash
82
- nullgate inspect
82
+ nullgate status
83
83
  ```
84
84
 
85
- ### Enter
85
+ ### Connect
86
86
 
87
87
  Print client connection strings and OpenSSH client configuration directives:
88
88
 
89
89
  ```bash
90
- nullgate enter
90
+ nullgate connect
91
91
  ```
92
92
 
93
- ### Trace
93
+ ### Logs
94
94
 
95
95
  Stream or print daemon logs:
96
96
 
97
97
  ```bash
98
- nullgate trace
99
- nullgate trace -f
100
- nullgate trace gateway
101
- nullgate trace transport
98
+ nullgate logs
99
+ nullgate logs -f
100
+ nullgate logs gateway
101
+ nullgate logs transport
102
102
  ```
103
103
 
104
- ### Shut
104
+ ### Stop
105
105
 
106
106
  Terminate running gateway and transport background processes:
107
107
 
108
108
  ```bash
109
- nullgate shut
109
+ nullgate stop
110
110
  ```
111
111
 
112
- ### Cycle
112
+ ### Restart
113
113
 
114
114
  Restart daemon instances reusing active or saved workspace settings:
115
115
 
116
116
  ```bash
117
- nullgate cycle
117
+ nullgate restart
118
118
  ```
119
119
 
120
120
  ### Upgrade
@@ -126,13 +126,17 @@ nullgate upgrade
126
126
  nullgate upgrade --version 1.0.1
127
127
  ```
128
128
 
129
+ The earlier command names `open`, `shut`, `inspect`, `enter`, `trace`, and
130
+ `cycle` remain available as compatibility aliases. Their earlier option names
131
+ also remain accepted, but new scripts should use the names documented above.
132
+
129
133
  ## Authentication and Security Boundaries
130
134
 
131
135
  - **Authentication Baseline**: Access requires authorized SSH keys in `~/.ssh/authorized_keys` or a high-entropy secret created in the runtime directory.
132
- - **Unrestricted Mode (`--accept`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
133
- - **Upterm Transport**: Relies on authorized SSH public keys or `--accept` (Upterm does not support password authentication). The relay itself is trusted for inbound multiplexing.
136
+ - **Public access (`--public-access`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
137
+ - **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.
134
138
  - **Filesystem Confinement**: SFTP and SCP access cannot escape the designated workspace directory.
135
- - **Host Transfer Mode (default)**: Relative SFTP and SCP paths begin in the workspace directory, while absolute paths refer to host filesystem locations subject to OS user permissions. Pass `--confine-sftp` to limit transfers to the workspace namespace and reject symlink escapes.
139
+ - **Host Transfer Mode (default)**: Relative SFTP and SCP paths begin in the workspace directory, while absolute paths refer to host filesystem locations subject to OS user permissions. Pass `--restrict-file-transfer` to limit transfers to the workspace namespace and reject symlink escapes.
136
140
  - **Interactive Shell Privileges**: Command and terminal sessions execute with the permissions of the host account rather than inside an OS chroot.
137
141
  - **State Storage**: Daemon PIDs and logs are maintained in `gateway.pid`, `transport.pid`, `gateway.log`, and `transport.log`, with session records kept in `manifest.json` and `settings.json`.
138
142
 
@@ -140,7 +144,7 @@ nullgate upgrade --version 1.0.1
140
144
 
141
145
  | Variable | Description |
142
146
  |---|---|
143
- | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate open` |
147
+ | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate start` |
144
148
  | `NULLGATE_STATE_DIR` | Directory holding runtime state and logs (defaults to `~/.local/state/nullgate`) |
145
149
  | `NULLGATE_IDENTITY_SEED` | Seed value used for deterministic Ed25519 key derivation |
146
150
  | `NULLGATE_TUNNEL_HOST` | Host address for srv.us ingress (default: `srv.us`) |
@@ -6,7 +6,7 @@ Disposable SSH gateway into confined directory workspaces for remote containers,
6
6
 
7
7
  - **Ingress Transports**: Publish endpoints via Upterm WebSocket relays (default), srv.us reverse tunnels, Cloudflare Worker relays, or Cloudflare Argo (`cloudflared`) tunnels.
8
8
  - **Confinement**: SFTP and SCP workloads are restricted to the designated workspace root; parent traversals and out-of-root symlinks are blocked.
9
- - **Host file visibility**: Relative transfer paths begin in the workspace root while absolute paths address host locations, matching shell behavior; add `--confine-sftp` for strict workspace-only transfers.
9
+ - **Host file visibility**: Relative transfer paths begin in the workspace root while absolute paths address host locations, matching shell behavior; add `--restrict-file-transfer` for strict workspace-only transfers.
10
10
  - **Direct Shell Access**: Authenticated PTY sessions run interactively under the local user credentials.
11
11
  - **Synthetic UID Resilience**: Runs seamlessly in container environments missing user records in `/etc/passwd`.
12
12
  - **Persistent Key Identities**: Host keys and identity seeds persist across restarts rather than being silently regenerated.
@@ -32,77 +32,77 @@ pip install nullgate
32
32
 
33
33
  ## Usage
34
34
 
35
- ### Open
35
+ ### Start
36
36
 
37
37
  Launch the gateway service and ingress transport in the background:
38
38
 
39
39
  ```bash
40
40
  # Default Upterm transport on port 4822 sharing current directory
41
- nullgate open
41
+ nullgate start
42
42
 
43
43
  # Custom path, port, and slot
44
- nullgate open /workspace --port 4822 --slot 1
44
+ nullgate start /workspace --ssh-port 4822 --tunnel-slot 1
45
45
 
46
46
  # Select specific transport provider
47
- nullgate open . --transport upterm
48
- nullgate open . --transport srvus
49
- nullgate open . --transport cloudflare --endpoint https://relay.example.com
50
- nullgate open . --transport cloudflared --hostname workspace.example.com --token "$TUNNEL_TOKEN"
47
+ nullgate start . --provider upterm
48
+ nullgate start . --provider srvus
49
+ nullgate start . --provider cloudflare --relay-url https://relay.example.com
50
+ nullgate start . --provider cloudflared --public-hostname workspace.example.com --cloudflare-token "$TUNNEL_TOKEN"
51
51
  ```
52
52
 
53
53
  Options:
54
- - `--transport`: Ingress provider (`upterm`, `srvus`, `cloudflare`, `cloudflared`). Defaults to `upterm`.
55
- - `--endpoint`: WebSocket or relay endpoint URL (used with `upterm` or `cloudflare`).
56
- - `--hostname`: Public ingress hostname for `cloudflared`.
57
- - `--token`: Argo tunnel authentication credential (or set via `NULLGATE_CLOUDFLARED_TOKEN`).
58
- - `--accept`: Skip credential checks and admit any client connection.
59
- - `--no-accept`: Restore authentication when saved settings previously enabled unrestricted access.
60
- - `--allow-tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
61
- - `--no-allow-tcp-forwarding`: Disable forwarding when it was enabled in saved settings.
62
- - `--confine-sftp`: Confine SFTP and SCP transfers to the workspace root.
63
- - `--no-confine-sftp`: Restore host-visible transfers when confinement was saved.
64
-
65
- ### Inspect
54
+ - `--provider`: Public connection provider (`upterm`, `srvus`, `cloudflare`, or `cloudflared`). Defaults to `upterm`.
55
+ - `--relay-url`: WebSocket relay URL used with `upterm` or `cloudflare`.
56
+ - `--public-hostname`: Public hostname used with `cloudflared`.
57
+ - `--cloudflare-token`: Cloudflare tunnel credential, or set `NULLGATE_CLOUDFLARED_TOKEN`.
58
+ - `--public-access`: Allow connections without credentials. This is unsafe on public endpoints.
59
+ - `--no-public-access`: Require authentication.
60
+ - `--tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
61
+ - `--no-tcp-forwarding`: Disable SSH TCP tunneling.
62
+ - `--restrict-file-transfer`: Keep SFTP and SCP inside the workspace.
63
+ - `--no-restrict-file-transfer`: Allow host-visible file transfers.
64
+
65
+ ### Status
66
66
 
67
67
  Display operational status, running PIDs, connection URLs, and security settings:
68
68
 
69
69
  ```bash
70
- nullgate inspect
70
+ nullgate status
71
71
  ```
72
72
 
73
- ### Enter
73
+ ### Connect
74
74
 
75
75
  Print client connection strings and OpenSSH client configuration directives:
76
76
 
77
77
  ```bash
78
- nullgate enter
78
+ nullgate connect
79
79
  ```
80
80
 
81
- ### Trace
81
+ ### Logs
82
82
 
83
83
  Stream or print daemon logs:
84
84
 
85
85
  ```bash
86
- nullgate trace
87
- nullgate trace -f
88
- nullgate trace gateway
89
- nullgate trace transport
86
+ nullgate logs
87
+ nullgate logs -f
88
+ nullgate logs gateway
89
+ nullgate logs transport
90
90
  ```
91
91
 
92
- ### Shut
92
+ ### Stop
93
93
 
94
94
  Terminate running gateway and transport background processes:
95
95
 
96
96
  ```bash
97
- nullgate shut
97
+ nullgate stop
98
98
  ```
99
99
 
100
- ### Cycle
100
+ ### Restart
101
101
 
102
102
  Restart daemon instances reusing active or saved workspace settings:
103
103
 
104
104
  ```bash
105
- nullgate cycle
105
+ nullgate restart
106
106
  ```
107
107
 
108
108
  ### Upgrade
@@ -114,13 +114,17 @@ nullgate upgrade
114
114
  nullgate upgrade --version 1.0.1
115
115
  ```
116
116
 
117
+ The earlier command names `open`, `shut`, `inspect`, `enter`, `trace`, and
118
+ `cycle` remain available as compatibility aliases. Their earlier option names
119
+ also remain accepted, but new scripts should use the names documented above.
120
+
117
121
  ## Authentication and Security Boundaries
118
122
 
119
123
  - **Authentication Baseline**: Access requires authorized SSH keys in `~/.ssh/authorized_keys` or a high-entropy secret created in the runtime directory.
120
- - **Unrestricted Mode (`--accept`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
121
- - **Upterm Transport**: Relies on authorized SSH public keys or `--accept` (Upterm does not support password authentication). The relay itself is trusted for inbound multiplexing.
124
+ - **Public access (`--public-access`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
125
+ - **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.
122
126
  - **Filesystem Confinement**: SFTP and SCP access cannot escape the designated workspace directory.
123
- - **Host Transfer Mode (default)**: Relative SFTP and SCP paths begin in the workspace directory, while absolute paths refer to host filesystem locations subject to OS user permissions. Pass `--confine-sftp` to limit transfers to the workspace namespace and reject symlink escapes.
127
+ - **Host Transfer Mode (default)**: Relative SFTP and SCP paths begin in the workspace directory, while absolute paths refer to host filesystem locations subject to OS user permissions. Pass `--restrict-file-transfer` to limit transfers to the workspace namespace and reject symlink escapes.
124
128
  - **Interactive Shell Privileges**: Command and terminal sessions execute with the permissions of the host account rather than inside an OS chroot.
125
129
  - **State Storage**: Daemon PIDs and logs are maintained in `gateway.pid`, `transport.pid`, `gateway.log`, and `transport.log`, with session records kept in `manifest.json` and `settings.json`.
126
130
 
@@ -128,7 +132,7 @@ nullgate upgrade --version 1.0.1
128
132
 
129
133
  | Variable | Description |
130
134
  |---|---|
131
- | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate open` |
135
+ | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate start` |
132
136
  | `NULLGATE_STATE_DIR` | Directory holding runtime state and logs (defaults to `~/.local/state/nullgate`) |
133
137
  | `NULLGATE_IDENTITY_SEED` | Seed value used for deterministic Ed25519 key derivation |
134
138
  | `NULLGATE_TUNNEL_HOST` | Host address for srv.us ingress (default: `srv.us`) |
@@ -0,0 +1 @@
1
+ __version__ = "1.2.0"
@@ -0,0 +1,246 @@
1
+ """Command-line parser and entry point dispatch for Nullgate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from typing import Sequence
8
+
9
+ from nullgate import session, transports
10
+
11
+ SUBCOMMANDS = (
12
+ "start",
13
+ "stop",
14
+ "status",
15
+ "connect",
16
+ "logs",
17
+ "restart",
18
+ "upgrade",
19
+ "open",
20
+ "shut",
21
+ "inspect",
22
+ "enter",
23
+ "trace",
24
+ "cycle",
25
+ )
26
+
27
+
28
+ def build_parser() -> argparse.ArgumentParser:
29
+ parser = argparse.ArgumentParser(
30
+ prog="nullgate",
31
+ description="Disposable SSH gateway into confined directory workspaces.",
32
+ )
33
+ parser.add_argument(
34
+ "-V",
35
+ "--version",
36
+ action="version",
37
+ version=f"nullgate {session._version()}",
38
+ )
39
+ sub = parser.add_subparsers(dest="command", required=True, metavar="command")
40
+
41
+ start_parser = sub.add_parser(
42
+ "start",
43
+ aliases=["open"],
44
+ description="Start SSH access to a workspace.",
45
+ help="Start SSH access to a workspace (alias: open).",
46
+ )
47
+ start_parser.add_argument(
48
+ "workspace",
49
+ nargs="?",
50
+ help="Directory to make available (default: current directory).",
51
+ )
52
+ start_parser.add_argument(
53
+ "--ssh-port",
54
+ dest="port",
55
+ type=int,
56
+ default=None,
57
+ help=f"Local SSH server port (default: {session.DEFAULT_PORT}).",
58
+ )
59
+ start_parser.add_argument("--port", dest="port", type=int, help=argparse.SUPPRESS)
60
+ start_parser.add_argument(
61
+ "--tunnel-slot",
62
+ dest="slot",
63
+ type=int,
64
+ default=None,
65
+ help=f"srv.us tunnel slot (default: {session.DEFAULT_SLOT}).",
66
+ )
67
+ start_parser.add_argument("--slot", dest="slot", type=int, help=argparse.SUPPRESS)
68
+ start_parser.add_argument(
69
+ "--provider",
70
+ dest="transport",
71
+ choices=transports.VALID_TRANSPORTS,
72
+ default=None,
73
+ help=f"Public connection provider (default: {transports.DEFAULT_TRANSPORT}).",
74
+ )
75
+ start_parser.add_argument(
76
+ "--transport",
77
+ dest="transport",
78
+ choices=transports.VALID_TRANSPORTS,
79
+ help=argparse.SUPPRESS,
80
+ )
81
+ start_parser.add_argument(
82
+ "--public-access",
83
+ dest="accept",
84
+ action=argparse.BooleanOptionalAction,
85
+ default=None,
86
+ help="Allow connections without SSH credentials (unsafe on public endpoints).",
87
+ )
88
+ start_parser.add_argument(
89
+ "--accept",
90
+ dest="accept",
91
+ action=argparse.BooleanOptionalAction,
92
+ default=None,
93
+ help=argparse.SUPPRESS,
94
+ )
95
+ start_parser.add_argument(
96
+ "--tcp-forwarding",
97
+ dest="allow_tcp_forwarding",
98
+ action=argparse.BooleanOptionalAction,
99
+ default=None,
100
+ help="Allow SSH port forwarding.",
101
+ )
102
+ start_parser.add_argument(
103
+ "--allow-tcp-forwarding",
104
+ dest="allow_tcp_forwarding",
105
+ action=argparse.BooleanOptionalAction,
106
+ default=None,
107
+ help=argparse.SUPPRESS,
108
+ )
109
+ start_parser.add_argument(
110
+ "--restrict-file-transfer",
111
+ dest="confine_sftp",
112
+ action=argparse.BooleanOptionalAction,
113
+ default=None,
114
+ help="Keep SFTP and SCP transfers inside the workspace.",
115
+ )
116
+ start_parser.add_argument(
117
+ "--confine-sftp",
118
+ dest="confine_sftp",
119
+ action=argparse.BooleanOptionalAction,
120
+ default=None,
121
+ help=argparse.SUPPRESS,
122
+ )
123
+ start_parser.add_argument(
124
+ "--relay-url",
125
+ dest="endpoint",
126
+ help="WebSocket relay URL (for upterm or cloudflare providers).",
127
+ )
128
+ start_parser.add_argument("--endpoint", dest="endpoint", help=argparse.SUPPRESS)
129
+ start_parser.add_argument(
130
+ "--public-hostname",
131
+ dest="hostname",
132
+ help="Public hostname (for the cloudflared provider).",
133
+ )
134
+ start_parser.add_argument("--hostname", dest="hostname", help=argparse.SUPPRESS)
135
+ start_parser.add_argument(
136
+ "--cloudflare-token",
137
+ dest="token",
138
+ help="Cloudflare tunnel token (for the cloudflared provider).",
139
+ )
140
+ start_parser.add_argument("--token", dest="token", help=argparse.SUPPRESS)
141
+
142
+ sub.add_parser(
143
+ "stop",
144
+ aliases=["shut"],
145
+ description="Stop SSH access and its connection provider.",
146
+ help="Stop SSH access (alias: shut).",
147
+ )
148
+
149
+ sub.add_parser(
150
+ "status",
151
+ aliases=["inspect"],
152
+ description="Show service health, connection details, and security settings.",
153
+ help="Show current status (alias: inspect).",
154
+ )
155
+
156
+ sub.add_parser(
157
+ "connect",
158
+ aliases=["enter"],
159
+ description="Show how to connect with SSH.",
160
+ help="Show SSH connection instructions (alias: enter).",
161
+ )
162
+
163
+ logs_parser = sub.add_parser(
164
+ "logs",
165
+ aliases=["trace"],
166
+ description="View or follow service logs.",
167
+ help="View or follow service logs (alias: trace).",
168
+ )
169
+ logs_parser.add_argument(
170
+ "target",
171
+ nargs="?",
172
+ default="all",
173
+ choices=["all", "gateway", "transport"],
174
+ help="Target log stream to inspect (default all).",
175
+ )
176
+ logs_parser.add_argument(
177
+ "-f",
178
+ "--follow",
179
+ dest="f",
180
+ action="store_true",
181
+ help="Follow log stream output continuously.",
182
+ )
183
+
184
+ sub.add_parser(
185
+ "restart",
186
+ aliases=["cycle"],
187
+ description="Restart SSH access using the saved settings.",
188
+ help="Restart using saved settings (alias: cycle).",
189
+ )
190
+
191
+ upgrade_parser = sub.add_parser(
192
+ "upgrade",
193
+ description="Fetch and install latest Nullgate release.",
194
+ help="Fetch and install latest Nullgate release.",
195
+ )
196
+ upgrade_parser.add_argument(
197
+ "--version",
198
+ help="Install a specific published version instead of the latest stable release.",
199
+ )
200
+
201
+ proxy_parser = sub.add_parser(
202
+ "proxy",
203
+ description=argparse.SUPPRESS,
204
+ )
205
+ proxy_parser.add_argument("url")
206
+
207
+ upterm_proxy = sub.add_parser(
208
+ "upterm-proxy",
209
+ description=argparse.SUPPRESS,
210
+ )
211
+ upterm_proxy.add_argument("url")
212
+
213
+ return parser
214
+
215
+
216
+ def main(argv: Sequence[str] | None = None) -> int:
217
+ parser = build_parser()
218
+ args = parser.parse_args(argv)
219
+
220
+ handlers = {
221
+ "start": session.cmd_open,
222
+ "open": session.cmd_open,
223
+ "stop": session.cmd_shut,
224
+ "shut": session.cmd_shut,
225
+ "status": session.cmd_inspect,
226
+ "inspect": session.cmd_inspect,
227
+ "connect": session.cmd_enter,
228
+ "enter": session.cmd_enter,
229
+ "logs": session.cmd_trace,
230
+ "trace": session.cmd_trace,
231
+ "restart": session.cmd_cycle,
232
+ "cycle": session.cmd_cycle,
233
+ "upgrade": session.cmd_upgrade,
234
+ "proxy": transports.cmd_proxy,
235
+ "upterm-proxy": transports.cmd_upterm_proxy,
236
+ }
237
+
238
+ handler = handlers.get(args.command)
239
+ if handler is None:
240
+ parser.print_help(sys.stderr)
241
+ return 2
242
+ return handler(args)
243
+
244
+
245
+ if __name__ == "__main__":
246
+ sys.exit(main())
@@ -1,6 +1,6 @@
1
1
  """Session lifecycle orchestration for Nullgate.
2
2
 
3
- Provides handlers for open, shut, inspect, enter, trace, cycle, and upgrade.
3
+ Provides handlers for start, stop, status, connect, logs, restart, and upgrade.
4
4
  """
5
5
 
6
6
  from __future__ import annotations
@@ -96,14 +96,14 @@ def _print_url_row(color: _Color, known: bool, url: str) -> None:
96
96
  if known:
97
97
  print(f" url {url}")
98
98
  else:
99
- print(" url pending " + _Color(sys.stdout.isatty()).dim("(nullgate trace transport -f)"))
99
+ print(" url pending " + _Color(sys.stdout.isatty()).dim("(nullgate logs transport -f)"))
100
100
 
101
101
 
102
102
  def _print_auth_rows(color: _Color, state: Path) -> None:
103
103
  accept = st.manifest_get("accept")
104
104
  allow = st.manifest_get("allow_tcp_forwarding")
105
105
  if accept == "1":
106
- print(" auth none " + color.dim("(--accept); anyone can connect"))
106
+ print(" auth none " + color.dim("(--public-access); anyone can connect"))
107
107
  else:
108
108
  password_file = state / "password"
109
109
  if password_file.is_file():
@@ -116,12 +116,12 @@ def _print_auth_rows(color: _Color, state: Path) -> None:
116
116
  if allow == "1":
117
117
  print(" forward enabled")
118
118
  else:
119
- print(" forward disabled " + color.dim("(--allow-tcp-forwarding)"))
119
+ print(" forward disabled " + color.dim("(--tcp-forwarding)"))
120
120
  confine = st.manifest_get("confine_sftp")
121
121
  if not confine:
122
122
  confine = st.read_settings().get("confine_sftp")
123
123
  if confine == "1":
124
- print(" sftp confined " + color.dim("(--confine-sftp)"))
124
+ print(" sftp confined " + color.dim("(--restrict-file-transfer)"))
125
125
  else:
126
126
  print(" sftp host")
127
127
 
@@ -203,8 +203,8 @@ def _show_status() -> int:
203
203
  _print_auth_rows(color, state)
204
204
  if target is not None:
205
205
  print()
206
- print(" enter " + color.bold(f"ssh {target.user or current_username()}@{target.host}"))
207
- print(" " + color.dim("first time here: nullgate enter"))
206
+ print(" connect " + color.bold(f"ssh {target.user or current_username()}@{target.host}"))
207
+ print(" " + color.dim("first time here: nullgate connect"))
208
208
  return 0 if (gateway_running and transport_running) else 1
209
209
 
210
210
 
@@ -269,7 +269,10 @@ def cmd_open(args: Any) -> int:
269
269
  transports.prompt_authorized_key()
270
270
  keys = Path(os.path.expanduser("~/.ssh/authorized_keys"))
271
271
  if not keys.is_file() or not keys.stat().st_size:
272
- die("The upterm transport requires an SSH authorized key or the --accept flag")
272
+ die(
273
+ "The upterm provider requires an SSH authorized key or "
274
+ "the --public-access flag"
275
+ )
273
276
  (state / "password").unlink(missing_ok=True)
274
277
  password = ""
275
278
  else:
@@ -409,7 +412,7 @@ def cmd_enter(args: Any = None) -> int:
409
412
  transport = st.manifest_get("transport") or transports.DEFAULT_TRANSPORT
410
413
  target = transports.resolve_target(transport, state)
411
414
  if target is None:
412
- die("No published address yet; check nullgate inspect.")
415
+ die("No published address yet; check `nullgate status`.")
413
416
  color = _colorizer()
414
417
  ssh_user = target.user or current_username()
415
418
  print("Run once on the machine you are connecting from:")
@@ -451,7 +454,7 @@ def cmd_cycle(args: Any = None) -> int:
451
454
  del args
452
455
  st.setup_state()
453
456
  if not st.read_settings():
454
- die("No saved configuration found to cycle; run open with desired options first.")
457
+ die("No saved settings found; run `nullgate start` with the desired options first.")
455
458
  cmd_shut()
456
459
  return cmd_open(
457
460
  argparse.Namespace(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nullgate
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Disposable SSH gateway into confined directory workspaces
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -18,7 +18,7 @@ Disposable SSH gateway into confined directory workspaces for remote containers,
18
18
 
19
19
  - **Ingress Transports**: Publish endpoints via Upterm WebSocket relays (default), srv.us reverse tunnels, Cloudflare Worker relays, or Cloudflare Argo (`cloudflared`) tunnels.
20
20
  - **Confinement**: SFTP and SCP workloads are restricted to the designated workspace root; parent traversals and out-of-root symlinks are blocked.
21
- - **Host file visibility**: Relative transfer paths begin in the workspace root while absolute paths address host locations, matching shell behavior; add `--confine-sftp` for strict workspace-only transfers.
21
+ - **Host file visibility**: Relative transfer paths begin in the workspace root while absolute paths address host locations, matching shell behavior; add `--restrict-file-transfer` for strict workspace-only transfers.
22
22
  - **Direct Shell Access**: Authenticated PTY sessions run interactively under the local user credentials.
23
23
  - **Synthetic UID Resilience**: Runs seamlessly in container environments missing user records in `/etc/passwd`.
24
24
  - **Persistent Key Identities**: Host keys and identity seeds persist across restarts rather than being silently regenerated.
@@ -44,77 +44,77 @@ pip install nullgate
44
44
 
45
45
  ## Usage
46
46
 
47
- ### Open
47
+ ### Start
48
48
 
49
49
  Launch the gateway service and ingress transport in the background:
50
50
 
51
51
  ```bash
52
52
  # Default Upterm transport on port 4822 sharing current directory
53
- nullgate open
53
+ nullgate start
54
54
 
55
55
  # Custom path, port, and slot
56
- nullgate open /workspace --port 4822 --slot 1
56
+ nullgate start /workspace --ssh-port 4822 --tunnel-slot 1
57
57
 
58
58
  # Select specific transport provider
59
- nullgate open . --transport upterm
60
- nullgate open . --transport srvus
61
- nullgate open . --transport cloudflare --endpoint https://relay.example.com
62
- nullgate open . --transport cloudflared --hostname workspace.example.com --token "$TUNNEL_TOKEN"
59
+ nullgate start . --provider upterm
60
+ nullgate start . --provider srvus
61
+ nullgate start . --provider cloudflare --relay-url https://relay.example.com
62
+ nullgate start . --provider cloudflared --public-hostname workspace.example.com --cloudflare-token "$TUNNEL_TOKEN"
63
63
  ```
64
64
 
65
65
  Options:
66
- - `--transport`: Ingress provider (`upterm`, `srvus`, `cloudflare`, `cloudflared`). Defaults to `upterm`.
67
- - `--endpoint`: WebSocket or relay endpoint URL (used with `upterm` or `cloudflare`).
68
- - `--hostname`: Public ingress hostname for `cloudflared`.
69
- - `--token`: Argo tunnel authentication credential (or set via `NULLGATE_CLOUDFLARED_TOKEN`).
70
- - `--accept`: Skip credential checks and admit any client connection.
71
- - `--no-accept`: Restore authentication when saved settings previously enabled unrestricted access.
72
- - `--allow-tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
73
- - `--no-allow-tcp-forwarding`: Disable forwarding when it was enabled in saved settings.
74
- - `--confine-sftp`: Confine SFTP and SCP transfers to the workspace root.
75
- - `--no-confine-sftp`: Restore host-visible transfers when confinement was saved.
76
-
77
- ### Inspect
66
+ - `--provider`: Public connection provider (`upterm`, `srvus`, `cloudflare`, or `cloudflared`). Defaults to `upterm`.
67
+ - `--relay-url`: WebSocket relay URL used with `upterm` or `cloudflare`.
68
+ - `--public-hostname`: Public hostname used with `cloudflared`.
69
+ - `--cloudflare-token`: Cloudflare tunnel credential, or set `NULLGATE_CLOUDFLARED_TOKEN`.
70
+ - `--public-access`: Allow connections without credentials. This is unsafe on public endpoints.
71
+ - `--no-public-access`: Require authentication.
72
+ - `--tcp-forwarding`: Enable SSH TCP tunneling (`ssh -L`).
73
+ - `--no-tcp-forwarding`: Disable SSH TCP tunneling.
74
+ - `--restrict-file-transfer`: Keep SFTP and SCP inside the workspace.
75
+ - `--no-restrict-file-transfer`: Allow host-visible file transfers.
76
+
77
+ ### Status
78
78
 
79
79
  Display operational status, running PIDs, connection URLs, and security settings:
80
80
 
81
81
  ```bash
82
- nullgate inspect
82
+ nullgate status
83
83
  ```
84
84
 
85
- ### Enter
85
+ ### Connect
86
86
 
87
87
  Print client connection strings and OpenSSH client configuration directives:
88
88
 
89
89
  ```bash
90
- nullgate enter
90
+ nullgate connect
91
91
  ```
92
92
 
93
- ### Trace
93
+ ### Logs
94
94
 
95
95
  Stream or print daemon logs:
96
96
 
97
97
  ```bash
98
- nullgate trace
99
- nullgate trace -f
100
- nullgate trace gateway
101
- nullgate trace transport
98
+ nullgate logs
99
+ nullgate logs -f
100
+ nullgate logs gateway
101
+ nullgate logs transport
102
102
  ```
103
103
 
104
- ### Shut
104
+ ### Stop
105
105
 
106
106
  Terminate running gateway and transport background processes:
107
107
 
108
108
  ```bash
109
- nullgate shut
109
+ nullgate stop
110
110
  ```
111
111
 
112
- ### Cycle
112
+ ### Restart
113
113
 
114
114
  Restart daemon instances reusing active or saved workspace settings:
115
115
 
116
116
  ```bash
117
- nullgate cycle
117
+ nullgate restart
118
118
  ```
119
119
 
120
120
  ### Upgrade
@@ -126,13 +126,17 @@ nullgate upgrade
126
126
  nullgate upgrade --version 1.0.1
127
127
  ```
128
128
 
129
+ The earlier command names `open`, `shut`, `inspect`, `enter`, `trace`, and
130
+ `cycle` remain available as compatibility aliases. Their earlier option names
131
+ also remain accepted, but new scripts should use the names documented above.
132
+
129
133
  ## Authentication and Security Boundaries
130
134
 
131
135
  - **Authentication Baseline**: Access requires authorized SSH keys in `~/.ssh/authorized_keys` or a high-entropy secret created in the runtime directory.
132
- - **Unrestricted Mode (`--accept`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
133
- - **Upterm Transport**: Relies on authorized SSH public keys or `--accept` (Upterm does not support password authentication). The relay itself is trusted for inbound multiplexing.
136
+ - **Public access (`--public-access`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
137
+ - **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.
134
138
  - **Filesystem Confinement**: SFTP and SCP access cannot escape the designated workspace directory.
135
- - **Host Transfer Mode (default)**: Relative SFTP and SCP paths begin in the workspace directory, while absolute paths refer to host filesystem locations subject to OS user permissions. Pass `--confine-sftp` to limit transfers to the workspace namespace and reject symlink escapes.
139
+ - **Host Transfer Mode (default)**: Relative SFTP and SCP paths begin in the workspace directory, while absolute paths refer to host filesystem locations subject to OS user permissions. Pass `--restrict-file-transfer` to limit transfers to the workspace namespace and reject symlink escapes.
136
140
  - **Interactive Shell Privileges**: Command and terminal sessions execute with the permissions of the host account rather than inside an OS chroot.
137
141
  - **State Storage**: Daemon PIDs and logs are maintained in `gateway.pid`, `transport.pid`, `gateway.log`, and `transport.log`, with session records kept in `manifest.json` and `settings.json`.
138
142
 
@@ -140,7 +144,7 @@ nullgate upgrade --version 1.0.1
140
144
 
141
145
  | Variable | Description |
142
146
  |---|---|
143
- | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate open` |
147
+ | `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate start` |
144
148
  | `NULLGATE_STATE_DIR` | Directory holding runtime state and logs (defaults to `~/.local/state/nullgate`) |
145
149
  | `NULLGATE_IDENTITY_SEED` | Seed value used for deterministic Ed25519 key derivation |
146
150
  | `NULLGATE_TUNNEL_HOST` | Host address for srv.us ingress (default: `srv.us`) |
@@ -122,7 +122,7 @@ def state_dir(state_base):
122
122
  class CLILifecycleTests(unittest.TestCase):
123
123
  def test_redesigned_defaults_and_boolean_overrides(self):
124
124
  parser = commands.build_parser()
125
- defaults = parser.parse_args(["open"])
125
+ defaults = parser.parse_args(["start"])
126
126
  self.assertEqual(session.DEFAULT_PORT, 4822)
127
127
  self.assertEqual(transports.DEFAULT_TRANSPORT, "upterm")
128
128
  self.assertIsNone(defaults.workspace)
@@ -133,13 +133,37 @@ class CLILifecycleTests(unittest.TestCase):
133
133
  self.assertIsNone(defaults.confine_sftp)
134
134
 
135
135
  restricted = parser.parse_args(
136
- ["open", "--no-accept", "--no-allow-tcp-forwarding", "--no-confine-sftp"]
136
+ [
137
+ "start",
138
+ "--no-public-access",
139
+ "--no-tcp-forwarding",
140
+ "--restrict-file-transfer",
141
+ ]
137
142
  )
138
143
  self.assertFalse(restricted.accept)
139
144
  self.assertFalse(restricted.allow_tcp_forwarding)
140
- self.assertFalse(restricted.confine_sftp)
141
- confined = parser.parse_args(["open", "--confine-sftp"])
142
- self.assertTrue(confined.confine_sftp)
145
+ self.assertTrue(restricted.confine_sftp)
146
+
147
+ clearer = parser.parse_args(
148
+ [
149
+ "start",
150
+ "--ssh-port", "2222",
151
+ "--tunnel-slot", "2",
152
+ "--provider", "upterm",
153
+ "--relay-url", "wss://relay.example",
154
+ ]
155
+ )
156
+ self.assertEqual(clearer.port, 2222)
157
+ self.assertEqual(clearer.slot, 2)
158
+ self.assertEqual(clearer.transport, "upterm")
159
+ self.assertEqual(clearer.endpoint, "wss://relay.example")
160
+
161
+ legacy = parser.parse_args(
162
+ ["open", "--no-accept", "--no-allow-tcp-forwarding", "--confine-sftp"]
163
+ )
164
+ self.assertFalse(legacy.accept)
165
+ self.assertFalse(legacy.allow_tcp_forwarding)
166
+ self.assertTrue(legacy.confine_sftp)
143
167
 
144
168
  upgrade = parser.parse_args(["upgrade"])
145
169
  self.assertIsNone(upgrade.version)
@@ -318,13 +342,13 @@ class CLILifecycleTests(unittest.TestCase):
318
342
  try:
319
343
  code, out, err = run_cli(
320
344
  [
321
- "open",
345
+ "start",
322
346
  str(root),
323
- "--port",
347
+ "--ssh-port",
324
348
  str(ssh_port),
325
- "--slot",
349
+ "--tunnel-slot",
326
350
  str(relay_forward),
327
- "--transport",
351
+ "--provider",
328
352
  "srvus",
329
353
  ],
330
354
  env=env,
@@ -336,28 +360,28 @@ class CLILifecycleTests(unittest.TestCase):
336
360
  self.assertEqual(key.stat().st_mode & 0o777, 0o600)
337
361
  self.assertTrue((state / "password").is_file())
338
362
 
339
- code, out, err = run_cli(["inspect"], env=env)
363
+ code, out, err = run_cli(["status"], env=env)
340
364
  self.assertEqual(code, 0)
341
365
  self.assertIn("gateway running pid", out)
342
366
  self.assertIn("transport running pid", out)
343
367
  self.assertIn(f"url https://{SRVUS_HOST}/", out)
344
368
  self.assertNotIn("\x1b[", out)
345
- self.assertIn(f"enter ssh {getpass.getuser()}@{SRVUS_HOST}", out)
369
+ self.assertIn(f"connect ssh {getpass.getuser()}@{SRVUS_HOST}", out)
346
370
  self.assertIn("keys disabled", out)
347
- self.assertNotIn("trace transport -f", out)
371
+ self.assertNotIn("logs transport -f", out)
348
372
 
349
- code, out, err = run_cli(["enter"], env=env)
373
+ code, out, err = run_cli(["connect"], env=env)
350
374
  self.assertEqual(code, 0, err)
351
375
  self.assertIn("openssl s_client", out)
352
376
  self.assertIn("-quiet", out)
353
377
  self.assertIn("-no_ign_eof", out)
354
378
  self.assertIn("Host *.srv.us", out)
355
379
 
356
- code, out, err = run_cli(["trace", "transport"], env=env)
380
+ code, out, err = run_cli(["logs", "transport"], env=env)
357
381
  self.assertEqual(code, 0)
358
382
  self.assertIn("remote forward ready", out)
359
383
 
360
- code, out, err = run_cli(["shut"], env=env)
384
+ code, out, err = run_cli(["stop"], env=env)
361
385
  self.assertEqual(code, 0)
362
386
  self.assertIn("Nullgate stopped.", out)
363
387
  finally:
@@ -367,7 +391,7 @@ class CLILifecycleTests(unittest.TestCase):
367
391
  with contextlib.suppress(OSError):
368
392
  os.kill(pid, 9)
369
393
 
370
- code, out, err = run_cli(["inspect"], env=env)
394
+ code, out, err = run_cli(["status"], env=env)
371
395
  self.assertEqual(code, 1)
372
396
  self.assertIn("gateway stopped", out)
373
397
  self.assertIn("transport stopped", out)
@@ -483,7 +507,7 @@ class CLILifecycleTests(unittest.TestCase):
483
507
  )
484
508
  )
485
509
  code, out, err = run_cli(["inspect"], env=env)
486
- self.assertIn("auth none (--accept); anyone can connect", out)
510
+ self.assertIn("auth none (--public-access); anyone can connect", out)
487
511
 
488
512
  def test_open_refuses_occupied_port(self):
489
513
  with tempfile.TemporaryDirectory() as temp:
@@ -548,7 +572,7 @@ class CLILifecycleTests(unittest.TestCase):
548
572
  env = make_env(home, state_base)
549
573
  code, out, err = run_cli(["cycle"], env=env)
550
574
  self.assertNotEqual(code, 0)
551
- self.assertIn("No saved configuration found to cycle", err)
575
+ self.assertIn("No saved settings found", err)
552
576
 
553
577
  def test_inspect_non_tty_has_no_ansi(self):
554
578
  with tempfile.TemporaryDirectory() as temp:
@@ -572,8 +596,8 @@ class CLILifecycleTests(unittest.TestCase):
572
596
  code, out, err = run_cli(["inspect"], env=env)
573
597
  self.assertNotIn("\x1b[", out)
574
598
  self.assertIn("url ssh://glacier.vctx.io", out)
575
- self.assertIn("first time here: nullgate enter", out)
576
- code, out, err = run_cli(["enter"], env=env)
599
+ self.assertIn("first time here: nullgate connect", out)
600
+ code, out, err = run_cli(["connect"], env=env)
577
601
  self.assertEqual(code, 0, err)
578
602
  self.assertIn("cloudflared must be installed", out)
579
603
  self.assertNotIn("\x1b[", out)
@@ -1 +0,0 @@
1
- __version__ = "1.1.1"
@@ -1,179 +0,0 @@
1
- """Command-line parser and entry point dispatch for Nullgate."""
2
-
3
- from __future__ import annotations
4
-
5
- import argparse
6
- import sys
7
- from typing import Sequence
8
-
9
- from nullgate import session, transports
10
-
11
- SUBCOMMANDS = ("open", "shut", "inspect", "enter", "trace", "cycle", "upgrade")
12
-
13
-
14
- def build_parser() -> argparse.ArgumentParser:
15
- parser = argparse.ArgumentParser(
16
- prog="nullgate",
17
- description="Disposable SSH gateway into confined directory workspaces.",
18
- )
19
- parser.add_argument(
20
- "-V",
21
- "--version",
22
- action="version",
23
- version=f"nullgate {session._version()}",
24
- )
25
- sub = parser.add_subparsers(dest="command", required=True, metavar="command")
26
-
27
- open_parser = sub.add_parser(
28
- "open",
29
- description="Launch gateway daemon and ingress transport.",
30
- help="Launch gateway daemon and ingress transport.",
31
- )
32
- open_parser.add_argument(
33
- "workspace",
34
- nargs="?",
35
- help="Directory exposed by the gateway (default: current directory).",
36
- )
37
- open_parser.add_argument(
38
- "--port",
39
- type=int,
40
- default=None,
41
- help=f"Local SSH listen port (default: {session.DEFAULT_PORT}).",
42
- )
43
- open_parser.add_argument(
44
- "--slot",
45
- type=int,
46
- default=None,
47
- help=f"Ingress slot number (default: {session.DEFAULT_SLOT}).",
48
- )
49
- open_parser.add_argument(
50
- "--transport",
51
- choices=transports.VALID_TRANSPORTS,
52
- default=None,
53
- help=f"Ingress transport provider (default {transports.DEFAULT_TRANSPORT}).",
54
- )
55
- open_parser.add_argument(
56
- "--accept",
57
- action=argparse.BooleanOptionalAction,
58
- default=None,
59
- help="Permit any client without credentials; --no-accept restores authentication.",
60
- )
61
- open_parser.add_argument(
62
- "--allow-tcp-forwarding",
63
- action=argparse.BooleanOptionalAction,
64
- default=None,
65
- help="Allow SSH port forwarding; use --no-allow-tcp-forwarding to disable it.",
66
- )
67
- open_parser.add_argument(
68
- "--confine-sftp",
69
- action=argparse.BooleanOptionalAction,
70
- default=None,
71
- help="Confine SFTP and SCP to the workspace; --no-confine-sftp restores host visibility.",
72
- )
73
- open_parser.add_argument(
74
- "--endpoint",
75
- help="WebSocket ingress endpoint URL (for upterm or cloudflare transports).",
76
- )
77
- open_parser.add_argument(
78
- "--hostname",
79
- help="Public hostname (for cloudflared transport).",
80
- )
81
- open_parser.add_argument(
82
- "--token",
83
- help="Cloudflare Argo tunnel token (for cloudflared transport).",
84
- )
85
-
86
- sub.add_parser(
87
- "shut",
88
- description="Halt running gateway and transport services.",
89
- help="Halt running gateway and transport services.",
90
- )
91
-
92
- sub.add_parser(
93
- "inspect",
94
- description="Display service health, access URLs, and authentication status.",
95
- help="Display service health, access URLs, and authentication status.",
96
- )
97
-
98
- sub.add_parser(
99
- "enter",
100
- description="Show SSH connection strings and client setup commands.",
101
- help="Show SSH connection strings and client setup commands.",
102
- )
103
-
104
- trace_parser = sub.add_parser(
105
- "trace",
106
- description="View or stream service operational logs.",
107
- help="View or stream service operational logs.",
108
- )
109
- trace_parser.add_argument(
110
- "target",
111
- nargs="?",
112
- default="all",
113
- choices=["all", "gateway", "transport"],
114
- help="Target log stream to inspect (default all).",
115
- )
116
- trace_parser.add_argument(
117
- "-f",
118
- "--follow",
119
- dest="f",
120
- action="store_true",
121
- help="Follow log stream output continuously.",
122
- )
123
-
124
- sub.add_parser(
125
- "cycle",
126
- description="Restart services using active or saved configuration.",
127
- help="Restart services using active or saved configuration.",
128
- )
129
-
130
- upgrade_parser = sub.add_parser(
131
- "upgrade",
132
- description="Fetch and install latest Nullgate release.",
133
- help="Fetch and install latest Nullgate release.",
134
- )
135
- upgrade_parser.add_argument(
136
- "--version",
137
- help="Install a specific published version instead of the latest stable release.",
138
- )
139
-
140
- proxy_parser = sub.add_parser(
141
- "proxy",
142
- description=argparse.SUPPRESS,
143
- )
144
- proxy_parser.add_argument("url")
145
-
146
- upterm_proxy = sub.add_parser(
147
- "upterm-proxy",
148
- description=argparse.SUPPRESS,
149
- )
150
- upterm_proxy.add_argument("url")
151
-
152
- return parser
153
-
154
-
155
- def main(argv: Sequence[str] | None = None) -> int:
156
- parser = build_parser()
157
- args = parser.parse_args(argv)
158
-
159
- handlers = {
160
- "open": session.cmd_open,
161
- "shut": session.cmd_shut,
162
- "inspect": session.cmd_inspect,
163
- "enter": session.cmd_enter,
164
- "trace": session.cmd_trace,
165
- "cycle": session.cmd_cycle,
166
- "upgrade": session.cmd_upgrade,
167
- "proxy": transports.cmd_proxy,
168
- "upterm-proxy": transports.cmd_upterm_proxy,
169
- }
170
-
171
- handler = handlers.get(args.command)
172
- if handler is None:
173
- parser.print_help(sys.stderr)
174
- return 2
175
- return handler(args)
176
-
177
-
178
- if __name__ == "__main__":
179
- sys.exit(main())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes