222 lines
11 KiB
Markdown
222 lines
11 KiB
Markdown
|
|
# Chat-Driven Project Bootstrap
|
||
|
|
|
||
|
|
Design overview for going from "I want a new project" to a running,
|
||
|
|
container-isolated, editor-accessible huskies project in one chat command.
|
||
|
|
|
||
|
|
## Goal
|
||
|
|
|
||
|
|
A user can say to Timmy in chat:
|
||
|
|
|
||
|
|
```
|
||
|
|
new project myapp --stack rust
|
||
|
|
new project legacy-rails --git git@github.com:me/legacy-rails.git
|
||
|
|
```
|
||
|
|
|
||
|
|
and end up with:
|
||
|
|
|
||
|
|
1. A fresh docker container running the project's huskies node.
|
||
|
|
2. The project's source code bind-mounted from the host so the user can
|
||
|
|
edit it in any editor.
|
||
|
|
3. SSH into the container so editors can run LSPs, builds, and tests
|
||
|
|
inside the container — never on the host.
|
||
|
|
4. Optional git remote configured for push to GitHub or Gitea.
|
||
|
|
5. The new sled registered with the gateway, so Timmy can drive coders /
|
||
|
|
mergemaster / etc. on the project via existing chat commands.
|
||
|
|
|
||
|
|
Manual repo creation on GitHub/Gitea remains the user's job. Everything
|
||
|
|
downstream of that is orchestrated.
|
||
|
|
|
||
|
|
## Architecture at a Glance
|
||
|
|
|
||
|
|
```
|
||
|
|
┌──────────────────────┐
|
||
|
|
│ Browser / Matrix │───┐
|
||
|
|
└──────────────────────┘ │
|
||
|
|
▼
|
||
|
|
┌───────────────────────┐
|
||
|
|
│ Gateway (huskies-gw) │
|
||
|
|
│ • chat dispatcher │
|
||
|
|
│ • new-project │
|
||
|
|
│ • routing │
|
||
|
|
└─────────┬─────────────┘
|
||
|
|
│
|
||
|
|
┌─────────┴───────────────────────────────────┐
|
||
|
|
│ docker engine (host) │
|
||
|
|
│ ┌────────────┐ ┌────────────┐ ┌─────────┐ │
|
||
|
|
│ │ project-A │ │ project-B │ │ ... │ │
|
||
|
|
│ │ sled + │ │ sled + │ │ │ │
|
||
|
|
│ │ sshd + │ │ sshd + │ │ │ │
|
||
|
|
│ │ LSPs │ │ LSPs │ │ │ │
|
||
|
|
│ └─────┬──────┘ └─────┬──────┘ └─────────┘ │
|
||
|
|
└────────┼──────────────┼─────────────────────┘
|
||
|
|
│ │
|
||
|
|
bind mount │ │ bind mount
|
||
|
|
┌────────┴───┐ ┌─────┴──────┐
|
||
|
|
│ ~/code/A │ │ ~/code/B │ ◄── host
|
||
|
|
└────────────┘ └────────────┘ editor opens
|
||
|
|
these paths
|
||
|
|
```
|
||
|
|
|
||
|
|
- One container per project. The container runs the project's huskies
|
||
|
|
binary (sled), an SSH server, and the stack-appropriate LSP(s).
|
||
|
|
- Source lives on the host (e.g. `~/code/<project>`), bind-mounted into
|
||
|
|
the container at a known path. Host can git-diff, back up, or edit.
|
||
|
|
- The gateway is editor-agnostic and project-agnostic — it talks to each
|
||
|
|
sled via the existing rendezvous / CRDT-sync protocol.
|
||
|
|
|
||
|
|
## Three Personas
|
||
|
|
|
||
|
|
| Persona | What they do | What they need |
|
||
|
|
|---------|--------------|----------------|
|
||
|
|
| Chat-only user | Drives everything via Matrix/web chat | Installed huskies binary; chat client |
|
||
|
|
| Editor-using technical user | Same + edits source in their editor | SSH config to the container + editor-specific remote-dev setup |
|
||
|
|
| Multi-project user | Several projects running in parallel | Gateway-listed projects, all routable from one chat |
|
||
|
|
|
||
|
|
Chat-only users never touch SSH. Editor users go through a one-time
|
||
|
|
"copy this SSH command into your editor's remote settings" handoff at
|
||
|
|
project creation time.
|
||
|
|
|
||
|
|
## The Bootstrap Chat Command
|
||
|
|
|
||
|
|
```
|
||
|
|
new project <name> [--stack <stack>] [--git <url>] [--path <host-path>]
|
||
|
|
```
|
||
|
|
|
||
|
|
Flow:
|
||
|
|
|
||
|
|
1. **Validate**: name unique among existing projects; host path doesn't already
|
||
|
|
exist; stack (if declared) is one of the supported overlays.
|
||
|
|
2. **Allocate** a fresh per-project port range (gateway picks).
|
||
|
|
3. **Create host directory** at `--path` (default `~/huskies/<name>/`).
|
||
|
|
4. If `--git` provided, `git clone` into that directory; else `git init`.
|
||
|
|
5. **Detect stack** from cloned content if not declared:
|
||
|
|
- `Cargo.toml` → `rust`
|
||
|
|
- `package.json` → `node`
|
||
|
|
- `go.mod` → `go`
|
||
|
|
- `pyproject.toml` / `requirements.txt` / `setup.py` → `python`
|
||
|
|
- `Gemfile` → `ruby`
|
||
|
|
- `pom.xml` / `build.gradle` → `jvm`
|
||
|
|
- Multiple → pick the dominant, warn.
|
||
|
|
- None → minimal base image, user can install tooling later.
|
||
|
|
6. **Compose the container** from `huskies-project-base` + the stack
|
||
|
|
overlay (Dockerfile fragments under `docker/stacks/<stack>/`).
|
||
|
|
7. **Launch** the container with bind mount + port forwards + an
|
||
|
|
auto-generated SSH key.
|
||
|
|
8. **Seed `.huskies/project.toml`** with sensible defaults.
|
||
|
|
9. **Register** the project with the gateway (`gateway_projects` LWW-map).
|
||
|
|
10. **Reply in chat** with: project name, host path, SSH command, and
|
||
|
|
a `huskies status <name>` invocation to verify.
|
||
|
|
|
||
|
|
## Container Template
|
||
|
|
|
||
|
|
Layered:
|
||
|
|
|
||
|
|
- **`huskies-project-base`**: debian-slim + git + huskies binary + sshd
|
||
|
|
+ sudo + a `huskies` user with the SSH pubkey installed.
|
||
|
|
- **`huskies-stack-<stack>`**: per-stack additions. E.g. rust gets
|
||
|
|
`rustup` + `rust-analyzer` + `cargo-nextest`; node gets `node@22` +
|
||
|
|
`typescript-language-server`; etc.
|
||
|
|
- **Project layer**: the bind-mounted `/workspace` is the project source,
|
||
|
|
written by the host's editor, read by the in-container tooling.
|
||
|
|
|
||
|
|
The container's SSH server is bound to a host-local port (not exposed
|
||
|
|
externally). Auth is the per-project keypair generated at bootstrap;
|
||
|
|
the public key sits inside the container, the private key on host.
|
||
|
|
|
||
|
|
## Build Sandbox Model
|
||
|
|
|
||
|
|
The threat: editing code in a host-side editor causes the editor (or its
|
||
|
|
LSP plugin) to run `cargo check` / `npm install` / `pip install` /
|
||
|
|
similar, which executes arbitrary code from project dependencies —
|
||
|
|
`build.rs`, proc-macros, npm `postinstall`, Python `setup.py`, Ruby
|
||
|
|
native-extension build scripts, etc. A malicious dependency compromises
|
||
|
|
the host.
|
||
|
|
|
||
|
|
The mitigation: all build / type-check / dependency-install commands
|
||
|
|
execute **inside the project container**. The host's editor connects to
|
||
|
|
the container over SSH; rust-analyzer (or equivalent) runs inside the
|
||
|
|
container; the host process never `exec`s untrusted build scripts.
|
||
|
|
|
||
|
|
Container isolation is the docker default plus:
|
||
|
|
- No `--privileged`.
|
||
|
|
- No host bind mounts beyond the project source and the SSH key.
|
||
|
|
- No host network beyond the gateway's CRDT sync port.
|
||
|
|
- `--cap-drop=ALL` plus the minimum caps needed (probably none).
|
||
|
|
|
||
|
|
This isn't a hardened sandbox in the gvisor / Firecracker sense — a
|
||
|
|
docker-escape exploit on a compromised container still escalates to
|
||
|
|
host. For most consumer threat models (malicious crate from
|
||
|
|
crates.io / npm), docker's default isolation is sufficient. Tighter
|
||
|
|
sandboxing (gvisor) is a separate future spike if needed.
|
||
|
|
|
||
|
|
## Editor Connection — Editor-Agnostic SSH
|
||
|
|
|
||
|
|
| Editor | Connection mechanism |
|
||
|
|
|--------|----------------------|
|
||
|
|
| VSCode | Remote-SSH extension |
|
||
|
|
| JetBrains (IntelliJ/Rover) | JetBrains Gateway (SSH) |
|
||
|
|
| Zed | Built-in SSH remoting (mac/linux only today) |
|
||
|
|
| Vim/Neovim | SSH terminal session, or local nvim + LSP-over-SSH |
|
||
|
|
| Emacs | TRAMP + remote LSP via lsp-mode |
|
||
|
|
|
||
|
|
All converge on: `ssh huskies@127.0.0.1 -p <project-port> -i ~/.huskies/<name>/id_ed25519`.
|
||
|
|
That string is emitted in the bootstrap chat reply.
|
||
|
|
|
||
|
|
## Git Integration
|
||
|
|
|
||
|
|
- Initial setup is `git init` or `git clone` inside the container.
|
||
|
|
- For push: user's existing GitHub / Gitea SSH key is bind-mounted
|
||
|
|
read-only into the container at `~/.ssh/id_*`, OR the user supplies a
|
||
|
|
push token via `huskies secrets set GIT_TOKEN=...` (stored as a Fly
|
||
|
|
secret equivalent — for now, a chmod 600 file in the container).
|
||
|
|
- The container's `git` config gets `user.name` / `user.email` from the
|
||
|
|
gateway-level user identity.
|
||
|
|
|
||
|
|
## Decisions
|
||
|
|
|
||
|
|
| Decision | Choice | Alternative |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| Container per project | One container per project | One container many projects: simpler but breaks isolation, breaks per-project deps |
|
||
|
|
| Editor model | SSH-remote (any editor) | VSCode Dev Containers only: simpler config but locks out everyone else |
|
||
|
|
| Source location | Bind mount from host | Inside container only: breaks "I can also edit on my laptop" requirement |
|
||
|
|
| Stack detection | Auto from project files, override with `--stack` | Always declared: more friction at bootstrap |
|
||
|
|
| Push secrets | Bind-mounted host SSH key OR per-project token | Gateway holds tokens: bigger blast radius |
|
||
|
|
|
||
|
|
## Open Questions
|
||
|
|
|
||
|
|
1. **Per-project resource limits.** Should each container have a hard
|
||
|
|
CPU / RAM cap so a runaway agent doesn't starve the host?
|
||
|
|
2. **Lifecycle / cleanup.** If the user deletes a project from chat,
|
||
|
|
what gets removed? Container yes; host source no (data loss); git
|
||
|
|
remotes yes? Need a confirm step.
|
||
|
|
3. **Multi-tenant.** Out of scope for this design (that's huskies.dev
|
||
|
|
territory). This doc assumes single-user local-only.
|
||
|
|
4. **Windows specifics.** Bind mounts work but line-ending /
|
||
|
|
permission edge cases. Probably document "use WSL2 for best
|
||
|
|
experience" rather than fight Windows native paths.
|
||
|
|
5. **Gateway-on-host vs gateway-in-container.** The gateway today runs
|
||
|
|
in its own container. New per-project containers connect via docker
|
||
|
|
network. Need to confirm the network plumbing works for arbitrary
|
||
|
|
per-project containers, not just the manually-configured ones.
|
||
|
|
|
||
|
|
## Phasing
|
||
|
|
|
||
|
|
The work breaks naturally into:
|
||
|
|
|
||
|
|
- **Phase 0 (now):** this design doc.
|
||
|
|
- **Phase 1:** chat command exists and provisions a bare project
|
||
|
|
container (no stack overlay, no SSH, no git clone — just
|
||
|
|
"start a container, register with gateway"). Validates the
|
||
|
|
orchestration shell.
|
||
|
|
- **Phase 2:** stack-aware container template — base image + overlays;
|
||
|
|
detection from project files.
|
||
|
|
- **Phase 3:** SSH-remote editor access — sshd in the container,
|
||
|
|
per-project keypair, chat-reply emits the connection string.
|
||
|
|
- **Phase 4:** git integration — `--git <url>` clones, host SSH key
|
||
|
|
mount, push verification.
|
||
|
|
- **Phase 5:** per-project resource limits + cleanup chat commands.
|
||
|
|
|
||
|
|
Each phase ships independently and is usable on its own. Phase 1 alone
|
||
|
|
gives chat-only users a working project; later phases add the editor
|
||
|
|
and git polish.
|