---
name: mechanics-run
description: Deploy and operate web apps on Mechanics (mechanics.run), an agent-first SSH control plane. Use when creating source repos, building container images, releasing apps, exposing HTTPS routes, claiming custom domains, provisioning addons (PostgreSQL, buckets, Valkey, NATS), managing workspace secrets, or debugging deployments on mechanics.run.
---

# Mechanics (mechanics.run) howto

Mechanics is an SSH control plane, not a shell. Run one structured command per SSH connection:

```bash
ssh -T mechanics.run -- <command> [args]
```

Rules that prevent most mistakes:

- Always put `--` before the command. Without it, OpenSSH interprets leading flags such as `-h` as SSH client options.
- On a clean machine or non-interactive agent, use `-o StrictHostKeyChecking=accept-new` on the first `mechanics.run` SSH command so OpenSSH can save the host key.
- Every command answers `--help`. Help is read-only and safe; explore the whole surface freely, even before signup.
- Identity is your SSH public key, not the SSH username. Any username works, except `git`, which is the Forgejo git endpoint.
- Do not send shell syntax. Unknown commands return explicit errors.
- Prefer `--json` on release/status commands. Add `--comment "why"` to any command for audit context.
- Exit code `2` means pending: retry the same command later. Many commands print a `next` hint with the exact command to run.

## Sign up (new SSH key)

Connect bare, or run pre-auth signup. Both print a GitHub Device Flow URL and code, then exit `2` while approval is pending. On a clean machine or Codex sandbox, make the first connection with host-key accept-new:

```bash
ssh -o StrictHostKeyChecking=accept-new -T mechanics.run -- user signup
ssh -T mechanics.run
ssh -T mechanics.run -- user signup
```

If SSH prints `Host key verification failed`, Mechanics signup has not started yet. The local SSH client rejected the server host key before connecting. Retry with `StrictHostKeyChecking=accept-new` or install the verified `mechanics.run` host key in `~/.ssh/known_hosts`.

A human opens the URL, enters the code, and approves. Reconnect with the same key: the next session runs foreground onboarding (Forgejo account, workspace, private `gitops` repo) and may print a one-time Forgejo web password. Then:

```bash
ssh -T mechanics.run -- setup | sh        # install ~/.ssh/config blocks for mechanics.run and Forgejo git
ssh -T mechanics.run -- whoami            # confirm identity, namespace, gitops repo
ssh -T mechanics.run -- user doctor --wait  # repair/converge account setup if anything looks broken
```

## Ship an app from source

```bash
ssh -T mechanics.run -- repo create myapp
git remote add origin ssh://git@forgejo.mechanics.run/<user>/myapp.git
git branch -M main
git push -u origin main

ssh -T mechanics.run -- repo -r myapp scaffold ci      # commits .forgejo/workflows/ci.yaml to Forgejo
ssh -T mechanics.run -- repo -r myapp scaffold agents  # commits AGENTS.md for coding agents
git pull --ff-only origin main                          # scaffolds are committed remotely; sync them

SHA=$(git rev-parse HEAD)
ssh -T mechanics.run -- repo -r myapp build status --commit "$SHA" --json   # exits 2 while building; retry

ssh -T mechanics.run -- app create myapp --from-repo myapp
ssh -T mechanics.run -- app -a myapp release --commit "$SHA" --json
```

Requirements and semantics:

- The repo needs a root `Dockerfile`; the standard CI workflow builds it and pushes `forgejo.mechanics.run/<owner>/<repo>:<sha>`.
- Containers must bind HTTP to `0.0.0.0:$PORT`. Mechanics sets `PORT` (default `80`). If your app listens on a fixed port such as `8080`, run `app -a myapp deploy --tag <sha> --port 8080` once; later releases preserve it.
- `app release --json` exit codes: `0` commit is live, `2` retry after `retry_after`, `3` app/user state blocks the release, `4` platform state blocks it.
- To release the newest successful build without local git: `app -a myapp release --latest-success --json`.
- When `repo list` shows a fully qualified `owner/repo` (collaborated repo), keep that exact value in `repo -r <owner/repo>` and `app create --from-repo <owner/repo>`.

## Expose over HTTPS

```bash
ssh -T mechanics.run -- route create web --app myapp --json   # generated host under mechanics.build
ssh -T mechanics.run -- route wait web                        # exits 2 while converging
ssh -T mechanics.run -- route check web                       # public HTTPS reachability probe
ssh -T mechanics.run -- route list
```

### Custom domain

The domain claim is separate from the route and is first-write-wins:

```bash
ssh -T mechanics.run -- domain claim www.example.com   # prints an _acme-challenge CNAME to publish
# HUMAN ACTION: publish the printed DNS CNAME record
ssh -T mechanics.run -- domain claim www.example.com   # re-run to verify; also: domain status www.example.com
ssh -T mechanics.run -- route create web --app myapp --domain www.example.com --json
```

## Addons (managed services)

```bash
ssh -T mechanics.run -- addon type list
ssh -T mechanics.run -- addon create db --type postgres    # also: bucket, valkey, nats
ssh -T mechanics.run -- addon -a db wait
ssh -T mechanics.run -- addon -a db info
ssh -T mechanics.run -- addon -a db credentials            # prints the Kubernetes secret reference to wire into the app
```

Wire the printed secret reference into the app's deployment overlay in your `gitops` repo (env vars via `secretKeyRef`), then release or sync the app.

## Workspace secrets

Values go to OpenBao and materialize as Kubernetes Secrets; they are never committed to git and never printed back:

```bash
printf 'API_KEY=abc123\n' | ssh -T mechanics.run -- secret set myapp-env
ssh -T mechanics.run -- secret info myapp-env
ssh -T mechanics.run -- secret wait myapp-env
```

## Debug a deployment

```bash
ssh -T mechanics.run -- app -a myapp info                 # sync, health, image drift, pods, diagnostics
ssh -T mechanics.run -- app -a myapp logs --tail 200
ssh -T mechanics.run -- app -a myapp events
ssh -T mechanics.run -- app -a myapp diagnose --json      # deployment, pod, event, digest evidence
ssh -T mechanics.run -- app -a myapp sync --wait          # ask ArgoCD to refresh
```

kubectl is for read/debug only; deploy through Mechanics. Save the kubeconfig explicitly (Mechanics never touches `~/.kube/config`):

```bash
ssh -T mechanics.run -- user kubeconfig > kubeconfig.yaml
KUBECONFIG=kubeconfig.yaml kubectl get pods
```

For native TCP access to a workspace Service or pod, use stock SSH local forwarding; helper commands print ready-to-paste tunnels:

```bash
ssh -T mechanics.run -- app -a myapp connect
ssh -T mechanics.run -- addon -a db connect
ssh -N -L 5432:<service>.<namespace>:5432 mechanics.run
```

## Everyday reads

```bash
ssh -T mechanics.run -- repo list --json
ssh -T mechanics.run -- app list --json
ssh -T mechanics.run -- repo -r myapp builds
ssh -T mechanics.run -- repo -r myapp image
ssh -T mechanics.run -- quota     # ceilings, live status, per-object limits
ssh -T mechanics.run -- usage     # observed CPU, memory, storage, network
```

## Cleanup

Destructive commands require `--force` and support `--dry-run`:

```bash
ssh -T mechanics.run -- app -a myapp undeploy --dry-run   # remove deployments/routes, keep config
ssh -T mechanics.run -- app -a myapp delete --force       # remove app GitOps tree; source repo is kept
ssh -T mechanics.run -- repo -r myapp delete --force      # remove the source repository
```

When unsure about any command's arguments or side effects, run it with `--help` first: it never mutates anything.
