Metadata-Version: 2.4
Name: sitecard
Version: 0.1.3
Summary: Issue temporary SSH access cards for development workspaces
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: asyncssh==2.24.0
Requires-Dist: cryptography>=39
Requires-Dist: websockets

# Sitecard

Issue temporary SSH access cards for development sites and workspaces.

This directory holds only the static sanitized README staged into the
generated `sitecard` distribution. The distributable package itself is
generated by `scripts/build-sitecard-dist.sh` and never checked in.

## What it is

Sitecard opens temporary SSH access to a selected development workspace for
remote debugging and compatibility testing. The site is the workspace host;
the card is the short-lived access path. Fresh Sitecard sessions allow public
access by default because they are disposable. Pass `--no-public-access` to
require credentials.

- Distribution name: `sitecard`
- Version: `0.1.3`
- Description: `Issue temporary SSH access cards for development workspaces`
- Requires Python `>=3.10`
- Console script: `sitecard = sitecard.commands:main`

## What it is not

The generated distribution contains no deployment helpers, local secrets,
target addresses, assessment keys, tests, or unrelated files.

## Build

From the repository root:

```sh
sh scripts/build-sitecard-dist.sh
```

Artifacts land in `dist/sitecard/`. The build fails if any
case-insensitive occurrence of the internal project name remains in
staging files, archive entry names, or archive file contents.

## Authenticated access (`--no-public-access`)

Public mode is the default and needs no keys.

For `--no-public-access` with a non-Upterm provider, provision two
operator-supplied public key files inside Sitecard state (the
pre-existing `0700` state directory: `$SITECARD_STATE_DIR/sitecard`
when `SITECARD_STATE_DIR` is set, otherwise
`$XDG_STATE_HOME/sitecard`, falling back to
`$HOME/.local/state/sitecard`):

```sh
STATE="${SITECARD_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}}/sitecard"
umask 077
mkdir -p "$STATE"
chmod 700 "$STATE"
printf '%s\n' 'ssh-ed25519 AAAA...primary-operator-key primary' > "$STATE/authorized_keys"
printf '%s\n' 'ssh-ed25519 AAAA...distinct-recovery-key recovery' > "$STATE/recovery_authorized_keys"
chmod 600 "$STATE/authorized_keys" "$STATE/recovery_authorized_keys"
```

Rules:

- `authorized_keys` is the primary operator key file.
- `recovery_authorized_keys` is the independent recovery key file.
- Both files hold public keys only, mode `0600`, one OpenSSH public
  key per line.
- The recovery key must be a distinct operator-supplied public key.
  Never copy the primary key into the recovery file and never generate
  a private key the operator cannot access.
- Recovery is optional; when the file is absent or empty only the
  primary file is used.
- Recovery applies to authenticated non-Upterm transports only. It is
  not used with the upterm provider, which authenticates via
  relay-issued certificates.
- Authenticated non-Upterm sessions launch the gateway with
  `--authorized-keys <state>/authorized_keys` and
  `--recovery-authorized-keys <state>/recovery_authorized_keys`.
- Host SSH files such as `~/.ssh/authorized_keys` or the root host SSH
  file are never read or written for authenticated Sitecard sessions.

## Use

```sh
sitecard -h
sitecard serve --help
sitecard serve .
```

`serve` is a true alias of `start` with identical arguments.

## Dev Tunnel hosting

Install Microsoft's `devtunnel` CLI on the host before starting. Sitecard does
not download tools, create tunnels, or change their access policies. Provision
HTTP port 8081 with anonymous connect access on an authenticated machine.
Transfer a fresh host-scoped token as an exact private file to the remote host
if it cannot perform OAuth login. Keep mode `0600` and refresh before expiry.

Run on the **remote workspace host**:

```sh
sitecard serve . -t ms -i "$TUNNEL_ID" -k "$HOST_TOKEN_FILE" --accept
```

`ms` aliases `devtunnel`. The token goes through stdin, never argv. Sitecard
starts a loopback SSH gateway plus a WebSocket bridge, then hosts the existing
tunnel without creating ports again. `--accept` allows SSH without credentials.
It prints the HTTPS SSH URL and a single OpenSSH command with
`sitecard tunnel-proxy` as ProxyCommand. Install Sitecard on the SSH client;
no client Microsoft CLI, `devtunnel connect`, or local forwarded port is needed.

The Dev Tunnel service stays in the foreground and uses retained child handles,
not `/proc`, `ps`, PID files, or the detached supervisor. Ctrl-C stops its own
children. Rerun the command to restart; the detached-mode `stop/status/restart`
commands do not manage it. Other providers retain their existing lifecycle.

## Public model API and optional config

Also provision HTTP port 8080 with anonymous connect access, then run:

```sh
sitecard serve . -m -p 8080 -t ms -i "$TUNNEL_ID" -k "$HOST_TOKEN_FILE" -a --accept
```

This prints a public `/v1` base URL for the Responses API, forwarding through
the host's `HTTPS_PROXY` and trusting its `SSL_CERT_FILE` or
`REQUESTS_CA_BUNDLE`. Clients need no proxy configuration or private CA copying.
`-a` allows model requests without credentials. Add `--no-ssh` for models only.
The model can be selected in JSON, `?model=...`, or `/model/<model>/v1/responses`.
Chat Completions conversion is not supported. Pi should use `openai-responses`.

`sitecard serve . -c sitecard.toml` loads equivalent `[relay]`, `[tunnel]`,
and `[ssh]` settings with explicit CLI overrides. TOML needs Python 3.11+;
CLI flags work on Python 3.10. The checked-in `RELAY_GUIDE.md`, also included
in the source distribution, covers provisioning, TOML contents, Pi config,
SSH, and token renewal. Anonymous URLs expose workspace/model access to anyone
who obtains them; the model relay does not forward arbitrary upstream URLs.

## Remote model proxy access

When a model works only through the remote machine's managed HTTP proxy,
copying a token does not reproduce that access. First test ordinary forwarding:

```sh
ssh -N -L 127.0.0.1:43706:127.0.0.1:43705 -p 4822 "$SSH_TARGET"
```

Replace the destination with the proxy address actually reachable from the
gateway. `--tcp-forwarding` enables this route; it is enabled by default.
Loopback refers to the gateway's network namespace, not another container's.
If a remote SSH command can reach the proxy but direct-TCP/IP forwarding
cannot, try the SSH exec fallback on the client:

```sh
sitecard forward "$SSH_TARGET" --ssh-port 4822 --listen-port 43706
```

This uses the remote SSH command's `HTTPS_PROXY`, connects there using existing
`python3`, and streams binary traffic over SSH stdin/stdout. No remote package
or CLI downloads are needed. Use `--proxy-env http_proxy` for a different
environment variable. It supports an `http://` proxy without URL credentials,
including HTTPS destinations via CONNECT. It does not support TLS-to-proxy or
SOCKS proxy URLs. SSH key authentication and a previously verified host key
are required because each connection uses noninteractive SSH. The local
listener is loopback-only; each connection opens one remote SSH command.

Point the model client at `http://127.0.0.1:43706` as its HTTP proxy, preserving
the original model endpoint URL and destination port. For clients honoring
environment variables, set `HTTPS_PROXY` and `HTTP_PROXY`; ensure `NO_PROXY`
does not bypass the intended destination. This forwards access, not credentials,
and does not change the model protocol or automate model requests. If Python
in the SSH exec context cannot reach the proxy either, this fallback cannot
cross that namespace boundary; run the gateway there or expose a reachable
proxy address. Do not publish the proxy as an anonymous public tunnel.
