Metadata-Version: 2.4
Name: nullgate
Version: 1.2.8
Summary: Disposable SSH gateway into confined directory workspaces
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: asyncssh==2.24.0
Requires-Dist: cryptography>=39
Requires-Dist: websockets
Dynamic: license-file

# Nullgate

Disposable SSH gateway into confined directory workspaces for remote containers, ephemeral developer boxes, CI nodes, and cloud instances.

## Quick start

Run Nullgate directly from PyPI without installing it first:

```bash
# Share the current directory over the default srv.us provider
uvx nullgate start

# Share a specific workspace
uvx nullgate start /workspace

# Compact form: workspace, SSH port, srv.us slot, provider
uvx nullgate start /workspace 8022 1 -t srvus
```

Then check the endpoint and print the SSH connection setup:

```bash
uvx nullgate status
uvx nullgate connect
```

Nullgate requires authentication by default (an authorized SSH key and/or a
generated password). Pass `--public-access` to allow connections without
credentials (unsafe on public endpoints).

## Features

- **Ingress Transports**: Publish endpoints via srv.us reverse tunnels (default), Upterm WebSocket relays, Cloudflare Worker relays, or Cloudflare Argo (`cloudflared`) tunnels.
- **Confinement**: SFTP and SCP workloads are restricted to the designated workspace root; parent traversals and out-of-root symlinks are blocked.
- **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.
- **Direct Shell Access**: Authenticated PTY sessions run interactively under the local user credentials.
- **Synthetic UID Resilience**: Runs seamlessly in container environments missing user records in `/etc/passwd`.
- **Persistent Key Identities**: Host keys and identity seeds persist across restarts rather than being silently regenerated.
- **Safe Process Tracking**: Process launch start times are verified alongside PIDs to prevent signaling recycled processes.

## Installation

Install from the public distribution channel:

```bash
curl -LsSf https://shadowdocks.github.io/nullgate-dist/install.sh | sh
```

The installer downloads the published wheel, verifies its SHA-256 checksum,
and installs it in an isolated `uv` tool environment. The private source
repository is not required on the target machine.

Alternatively, install the published package from PyPI:

```bash
pip install nullgate
```

## Usage

### Start

Start the gateway service and ingress transport, then report readiness once the
gateway listens, the transport stays alive, and srv.us has announced a
hostname (for that provider):

```bash
# Default srv.us transport on port 4822 sharing current directory
nullgate start

# Custom path, port, and slot
nullgate start /workspace --ssh-port 4822 --tunnel-slot 1

# The same values in compact positional form
nullgate start /workspace 4822 1

# Select specific transport provider
nullgate start . --provider upterm
nullgate start . --provider srvus
nullgate start . --provider cloudflare --relay-url https://relay.example.com
nullgate start . --provider cloudflared --public-hostname workspace.example.com --cloudflare-token "$TUNNEL_TOKEN"
```

Options:
- `-p`, `--ssh-port`: Local SSH server port.
- `-s`, `--tunnel-slot`: srv.us tunnel slot.
- `-t`, `--provider`: Public connection provider (`upterm`, `srvus`, `cloudflare`, or `cloudflared`). Defaults to `srvus`.
- `-e`, `--relay-url`: WebSocket relay URL used with `upterm` or `cloudflare`.
- `-H`, `--public-hostname`: Public hostname used with `cloudflared`.
- `--cloudflare-token`: Cloudflare tunnel credential, or set `NULLGATE_CLOUDFLARED_TOKEN`.
- `--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.
- `--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.
- `--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.
- `--no-public-access`: Require authentication.
- `--tcp-forwarding`: Allow SSH TCP tunneling (`ssh -L`; enabled by default on fresh `start`).
- `--no-tcp-forwarding`: Disable SSH TCP tunneling. `restart` reuses the saved effective setting.
- `--restrict-file-transfer`: Keep SFTP and SCP inside the workspace.
- `--no-restrict-file-transfer`: Allow host-visible file transfers.
- `--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.
- `--json`: Emit machine-readable JSON on stdout (the only stdout in JSON mode). `start --foreground --json` prints one readiness object, flushes it, then supervises.
- `--replace-stale`: Replace tracked gateway and transport processes when they are not healthy. Healthy srv.us sessions are reused.
- `--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.

A fresh `start` uses documented defaults and the current directory when no
workspace is given; only `restart` reuses saved workspace, provider, port,
and security settings. Only the hidden external-launcher flags `--notte-api`
(boolean) and `--notte-user VALUE` are accepted for compatibility; any other
unknown flag fails normally.

### Status

Display operational status, running PIDs, connection URLs, and security settings:

```bash
nullgate status
nullgate status --json
```

When stdout is not a TTY, `status` emits only a directly usable SSH fallback
command plus a `password: VALUE` line when a generated password exists; with no
live target it prints `stopped` or `pending`. `--json` is the stable full
machine interface.

### Connect

Print client connection strings and OpenSSH client configuration directives:

```bash
nullgate connect
nullgate connect --json
```

On an interactive TTY `connect` keeps the setup instructions; piped output
emits only the directly usable fallback command.

### Logs

Stream or print daemon logs:

```bash
nullgate logs
nullgate logs -f
nullgate logs gateway
nullgate logs transport
```

### Stop

Terminate running gateway and transport background processes:

```bash
nullgate stop
```

### Restart

Restart daemon instances reusing the saved workspace, provider, port, and
security settings from the previous start. A direct `--identity-seed` value
from the original start is intentionally not persisted, so `restart` cannot
reuse it; only the seed-file path is saved and revalidated.

```bash
nullgate restart
```

### Upgrade

Fetch and install the newest release:

```bash
nullgate upgrade
nullgate upgrade --version 1.0.1
```

Short command aliases are `up`, `down`, `ps`, `ssh`, `log`, and `reload`.
The earlier names `open`, `shut`, `inspect`, `enter`, `trace`, and `cycle`
remain available as compatibility aliases. Their earlier option names also
remain accepted, but new scripts should use the names documented above.
Unknown flags fail normally; only the hidden external-launcher flags
`--notte-api` (boolean) and `--notte-user VALUE` are accepted as no-ops.

## Authentication and Security Boundaries

- **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.
- **Public access (`--public-access`)**: Bypasses authentication checks entirely; any incoming connection with network access is accepted.
- **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.
- **Filesystem Confinement**: SFTP and SCP access cannot escape the designated workspace directory.
- **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.
- **Interactive Shell Privileges**: Command and terminal sessions execute with the permissions of the host account rather than inside an OS chroot.
- **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`.

## Environment Variables

| Variable | Description |
|---|---|
| `NULLGATE_PASSWORD` | Gateway-process credential; normally managed internally by `nullgate start` |
| `NULLGATE_STATE_DIR` | Directory holding runtime state and logs (defaults to `~/.local/state/nullgate`) |
| `NULLGATE_IDENTITY_SEED` | Seed for deterministic Ed25519 key derivation; only used when creating the identity file, never when it already exists |
| `NULLGATE_TUNNEL_HOST` | Host address for srv.us ingress (default: `srv.us`) |
| `NULLGATE_TUNNEL_PORT` | Port for srv.us ingress (default: `22`) |
| `NULLGATE_CLOUDFLARED_TOKEN` | Credential token for `cloudflared` tunnels |
| `NULLGATE_CONFINE_SFTP` | Set to `1` to confine SFTP and SCP transfers to the workspace root |
| `NULLGATE_RENDEZVOUS_URL` | HTTP endpoint for publishing successful srv.us readiness; may contain a secret token and is never printed |
| `NULLGATE_VERSION` | Install a specific published version instead of the stable release; accepts `1.0.1` or `v1.0.1` |
| `NULLGATE_DIST_URL` | Override the public release channel used by `install.sh` |
| `NULLGATE_INSTALL_URL` | Override the installer URL used by `nullgate upgrade` |
| `NO_COLOR` | Set to any value to disable ANSI color output (non-TTY output never uses color) |

## Automation/CI

- Noninteractive runs never prompt: with stdin closed or piped, `start` skips
  the key prompt and generates the password for srv.us secure mode. Upterm
  secure mode without authorized keys fails immediately instead of hanging.
- Readiness is decisive: `start` returns success only after the gateway
  listens, the transport stays alive, and (for srv.us) a hostname is
  announced and serves an SSH banner over certificate-validated TLS. On timeout or failure both processes are stopped, recent
  transport logs go to stderr, and the exit code is nonzero.
- `start --foreground` keeps the parent alive supervising both children,
  handles SIGINT/SIGTERM by stopping them, and exits nonzero with recent log tails if
  either child dies. Background start remains the default.
- `start`, `status`, and `connect` accept `--json` for machine-readable
  output. JSON is the only stdout in JSON mode and carries the operational
  fields agents need: version, provider, running state and PIDs, port,
  workspace, URL, host, user, proxy command, public health, auth mode, TCP forwarding, and
  file-transfer mode. `--json` is the stable agent contract; human output may
  change. `start --foreground --json` prints one readiness object,
  flushes it, then supervises.
- Piped output is compact: successful `start`, `status`, and `connect` with a
  target emit only a directly usable SSH fallback command plus one
  `password: VALUE` line when a generated password exists. With no live
  usable target, `status` prints `stopped` or `pending`.
- Identity seeds: `--identity-seed-file PATH` keeps the value out of argv
and history (flag overrides `NULLGATE_IDENTITY_SEED`); `--identity-seed
VALUE` is also accepted but is visible in argv and shell history. Both apply
only to the srv.us provider, fail before launch when absent, unreadable, or
empty (direct value: nonempty after stripping), pass the seed only to the
srv.us child environment (every other child gets a seed-free environment even
when the parent exported one), and never print or persist the value. Only the
seed-file path is saved for `restart`, which revalidates the saved srv.us seed
file before stopping anything; a direct `--identity-seed` from the original
start is intentionally not persisted, so `restart` cannot reuse it. The seed
only takes effect when the identity key does not yet exist.

## Development

Run internal modules directly:

```bash
# Gateway daemon
python -m nullgate.gateway --root . --port 4822 --accept

# srv.us transport
python -m nullgate.ingress --host srv.us --port 22 --local-port 4822 --slot 1 --key ~/.ssh/id_ed25519

# Cloudflare WebSocket bridge
python -m nullgate.bridge origin wss://relay.example.com/relay/SESSION --port 4822
python -m nullgate.bridge client wss://relay.example.com/relay/SESSION

# Upterm WebSocket client route
python -m nullgate.wsroute client wss://uptermd.upterm.dev/...

# Run test suite
uv run python -m unittest discover -s tests
```

## License

MIT License. See [LICENSE](LICENSE) for details.
