diff --git a/Cargo.lock b/Cargo.lock index 2a5c277..b7e9236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -798,6 +798,7 @@ dependencies = [ "assert_cmd", "predicates", "sandcage", + "serde_yaml", "tempfile", ] diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..841ddef --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,374 @@ +# Configuration Reference + +Sandcage uses a layered configuration system. Each layer overrides the previous one: + +1. **Compiled defaults** — all fields unset +2. **Global config** (`~/.sandcage/config.toml`) — user-wide preferences (TOML format) +3. **Project config** (`.sandcage.yml`) — per-project settings, checked into version control (YAML format) +4. **Local config** (`.sandcage.local.yml`) — personal overrides, gitignored (YAML format) + +Later layers win. Fields not set in a layer are inherited from the layer below. + +--- + +## Fields + +### env + +Environment variables passed into the container at runtime. + +- **Type:** Map of string keys to string values +- **Valid in:** Global, Project, Local +- **Behavior:** Passed as `-e KEY=VALUE` flags to `docker compose run` + +```yaml +# .sandcage.yml +env: + RUST_LOG: debug + DATABASE_URL: "postgres://localhost:5432/dev" +``` + +```toml +# ~/.sandcage/config.toml +[env] +EDITOR = "vim" +``` + +--- + +### packages + +System packages to install in the container image (apt packages). + +- **Type:** List of strings +- **Valid in:** Global, Project, Local + +```yaml +packages: + - ripgrep + - fd-find + - postgresql-client +``` + +--- + +### toolchains + +Language toolchains to install, keyed by language name and version. + +- **Type:** Map of string keys to string values +- **Valid in:** Global, Project, Local + +```yaml +toolchains: + rust: "1.78" + node: "20" + python: "3.12" +``` + +--- + +### mounts + +Additional volume mounts passed to the container. Each entry must contain a `:` separator. + +- **Type:** List of strings in Docker mount format (`:[:]`) +- **Valid in:** Global, Project, Local +- **Behavior:** Tilde (`~`) in the source path is expanded to the host home directory. Entries without `:` are skipped with a warning. + +```yaml +mounts: + - /data/models:/models:ro + - ~/.gitconfig:/home/agent/.gitconfig:ro + - /tmp/cache:/cache +``` + +--- + +### shell + +Default shell inside the container. + +- **Type:** String +- **Valid in:** Global, Project, Local + +```yaml +shell: zsh +``` + +--- + +### justfile + +Path to a justfile to make available in the container. + +- **Type:** File path (string) +- **Valid in:** Global, Project, Local + +```yaml +justfile: ./project.justfile +``` + +--- + +### dockerfiles + +Custom Dockerfile overrides for image builds. Keyed by image short name. Values can be a path to a Dockerfile or a directory containing one. + +- **Type:** Map of string keys to file paths +- **Valid in:** Global, Project (project overrides require the project to be in `trusted_projects`) +- **Behavior:** Project-level Dockerfile overrides are ignored unless the project directory is listed in `trusted_projects` in the global config. + +```yaml +# .sandcage.yml (only applied if project is trusted) +dockerfiles: + sandcage: ./docker/Dockerfile.custom +``` + +```toml +# ~/.sandcage/config.toml +[dockerfiles] +sandcage = "/home/user/my-dockerfiles/sandcage/" +``` + +--- + +### trusted_projects + +List of project directories allowed to use Dockerfile overrides. + +- **Type:** List of file paths +- **Valid in:** Global only (a project cannot self-trust) +- **Behavior:** Read exclusively from `~/.sandcage/config.toml`. Projects with `dockerfiles` set are ignored unless their path (or a parent) appears here. + +```toml +# ~/.sandcage/config.toml +trusted_projects = [ + "/home/user/projects/my-trusted-project", + "/home/user/work", +] +``` + +--- + +### container_workspace + +Override the workspace path inside the container. Must be an absolute path (starting with `/`). Relative paths are ignored with a warning. + +- **Type:** String (absolute path) +- **Valid in:** Global, Project, Local +- **Default:** `/workspace/` (derived from the host workspace folder name) + +```yaml +container_workspace: /workspace/my-app +``` + +--- + +### agent_args + +Default arguments passed to agents on every invocation. Keyed by service name. These are appended after the service name but before any extra CLI arguments. + +- **Type:** Map of service name to list of strings +- **Valid in:** Global, Project, Local +- **Behavior:** Only the args for the invoked service are used. When running with `--shell`, agent_args are skipped entirely. + +```yaml +agent_args: + claude: + - "--dangerously-skip-permissions" + codex: + - "--full-auto" +``` + +```toml +# ~/.sandcage/config.toml +[agent_args] +claude = ["--dangerously-skip-permissions"] +``` + +--- + +### ssh_mode + +Controls how SSH keys are made available inside the container. + +- **Type:** String — one of `"volume"`, `"bind"`, or `"none"` +- **Valid in:** Global, Project, Local +- **Default:** No SSH mount (equivalent to `"none"`) + +| Value | Behavior | +|----------|----------| +| `volume` | Mounts a named Docker volume (`sandcage-ssh`) at `/home/agent/.ssh` (read-only). Populate it with `sandcage setup ssh`. | +| `bind` | Bind-mounts `~/.ssh` from the host at `/home/agent/.ssh` (read-only). | +| `none` | No SSH directory is mounted. | + +```yaml +ssh_mode: volume +``` + +When using `volume` mode, run `sandcage setup ssh --refresh` after changing keys on the host. + +--- + +### ssh_keys + +Declares which SSH keys to copy into the volume when using `sandcage setup ssh`. Each entry specifies a host and identity file. + +- **Type:** List of objects with `host` (string) and `identity_file` (string) fields +- **Valid in:** Global, Project, Local + +```yaml +ssh_keys: + - host: github.com + identity_file: ~/.ssh/id_ed25519 + - host: gitea.internal + identity_file: ~/.ssh/work_gitea +``` + +--- + +### services + +Override the enabled state of built-in services. Keyed by service name. + +- **Type:** Map of service name to object with optional `enabled` (boolean) field +- **Valid in:** Global, Project, Local +- **Behavior:** Disabled services cannot be started with `sandcage ` and are excluded from generated compose files. + +```yaml +services: + gemini: + enabled: false + codex: + enabled: false +``` + +```toml +# ~/.sandcage/config.toml +[services.codex] +enabled = false +``` + +--- + +### default_services + +Controls which services `sandcage build` prepares by default (when no service names are given on the CLI). + +- **Type:** List of strings (service names) +- **Valid in:** Global, Project, Local + +```yaml +default_services: + - claude + - shell +``` + +--- + +## Services + +Sandcage ships with four built-in services, all enabled by default: + +| Service | Description | Config directory | +|----------|-----------------------|------------------| +| `claude` | Claude Code agent | `.claude` | +| `codex` | Codex agent | `.codex` | +| `gemini` | Gemini CLI agent | `.gemini` | +| `shell` | Interactive zsh shell | (none) | + +Agent services are installed on first run into the persistent home directory (`~/.sandcage/`). They auto-update on subsequent runs. + +To disable a service you do not use: + +```yaml +services: + codex: + enabled: false + gemini: + enabled: false +``` + +Disabled services are excluded from Docker compose generation and cannot be invoked. + +--- + +## Examples + +### Minimal project config + +```yaml +# .sandcage.yml +toolchains: + node: "20" +packages: + - ripgrep +``` + +### Full-featured project config + +```yaml +# .sandcage.yml +env: + RUST_LOG: debug + NODE_ENV: development + +packages: + - ripgrep + - fd-find + - postgresql-client + +toolchains: + rust: stable + node: "20" + +mounts: + - /data/models:/models:ro + +shell: zsh + +agent_args: + claude: + - "--dangerously-skip-permissions" + +services: + gemini: + enabled: false +``` + +### Local overrides (gitignored) + +```yaml +# .sandcage.local.yml +ssh_mode: volume +ssh_keys: + - host: github.com + identity_file: ~/.ssh/id_ed25519 + +env: + SECRET_API_KEY: "sk-local-dev-key" + +mounts: + - ~/.aws:/home/agent/.aws:ro +``` + +### Global config + +```toml +# ~/.sandcage/config.toml +shell = "zsh" + +[env] +EDITOR = "vim" + +[agent_args] +claude = ["--dangerously-skip-permissions"] + +[services.codex] +enabled = false + +trusted_projects = [ + "/home/user/work/trusted-project", +] +```