flyboat 2.0.0

Container environment manager for development
Documentation
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build and Test Commands

```bash
cargo build              # Build the project
cargo test               # Run all tests
cargo test <test_name>   # Run a single test
cargo clippy             # Lint (all warnings are denied)
cargo fmt --check        # Check formatting
```

## Project Overview

Flyboat is a container environment manager for development. It wraps Docker/Podman to provide consistent, reproducible development environments with security-first defaults.

**Key concepts:**
- **Environments** live in `~/.flyboat/env/` with a `Dockerfile` and `dev_env.yaml` (supports nested tree structure)
- **Global config** at `~/.flyboat/config.yaml` sets container engine and default arch
- Supports both Docker and Podman (auto-detects, prefers Podman for rootless)
- Container naming: `flyboat-<env>[-<arch>]-<postfix>`
- Image naming: `flyboat-<env>[-<arch>]`

## CLI Commands

```bash
flyboat run <env>        # Build and start container
flyboat build <env>      # Build container image only
flyboat connect <env>    # Connect to existing container
flyboat list [--json]    # List available environments
flyboat status           # Show disk usage and running containers
flyboat clean            # Remove flyboat images and dangling containers
```

**Global flags:** `-v, --verbose` for verbose output

## Environment Structure

Environments can be organized in a nested tree structure:

```
~/.flyboat/env/
├── rust/                    # Root-level env: name="rust", full_name="rust"
│   ├── Dockerfile
│   └── dev_env.yaml
├── my_collection/           # Namespace folder (no Dockerfile = ignored)
│   ├── python/              # Nested env: name="python", full_name="my_collection/python"
│   │   ├── Dockerfile
│   │   └── dev_env.yaml
│   └── rust/                # Nested env: name="rust", full_name="my_collection/rust"
│       ├── Dockerfile
│       └── dev_env.yaml
└── tools/web/node/          # Deep nesting: full_name="tools/web/node"
    ├── Dockerfile
    └── dev_env.yaml
```

**Key behaviors:**
- A folder is a valid environment if it has BOTH `Dockerfile` AND `dev_env.yaml`
- Folders without these files are treated as namespace folders (ignored)
- Containers can have subfolders (nested containers are allowed)
- Namespace delimiter: `/` (configurable via `NAMESPACE_DELIMITER` constant in `environment.rs`)
- `Environment.namespace` is `Vec<String>` (e.g., `["my_collection"]` or `["a", "b", "c"]`)
- `Environment.full_name()` method computes the full name from namespace + name

**Example:**
```bash
flyboat run rust                    # Works if only one "rust" exists
flyboat run my_collection/rust      # Always works for that specific env
# If ambiguous: prints "Ambiguous name used, multiple matches where found:" with options
```

## Architecture

```
src/
├── main.rs           # CLI entry, command dispatch
├── lib.rs            # Public exports, EnvFeaturizedName, sanitize_name(), is_valid_name()
├── cli.rs            # clap definitions (Cli, Command, *Args structs)
├── error.rs          # Error enum with thiserror
├── environment.rs    # EnvironmentManager - discovers and resolves environments
├── config/
│   ├── mod.rs        # Module exports
│   ├── paths.rs      # Paths struct (~/.flyboat/*)
│   ├── dev_env.rs    # EnvConfig - per-environment dev_env.yaml schema
│   ├── global.rs     # GlobalConfig - global config.yaml schema
│   └── template.rs   # TemplateConfig - template file configuration
├── docker/
│   ├── mod.rs        # Module exports, execute_command helpers
│   ├── engine.rs     # Engine enum (Docker/Podman), detection, extra args
│   ├── build.rs      # BuildCommand, arch_to_platform(), image_name()
│   ├── run.rs        # RunCommand, MountSpec, container_name()
│   └── execute_command.rs  # Command execution utilities
├── template/
│   ├── mod.rs        # Module exports (ProcessContext, process_templates)
│   ├── processor.rs  # Template processing logic, variable substitution
│   ├── security.rs   # Path validation, traversal prevention
│   └── random.rs     # Cryptographically secure random generation
└── commands/         # One file per subcommand
    ├── mod.rs
    ├── run.rs        # Build + run container
    ├── build.rs      # Build image only
    ├── connect.rs    # Attach to running container
    ├── list.rs       # List environments
    ├── status.rs     # Show disk/container status
    └── clean.rs      # Cleanup images/containers
```

**Data flow for `flyboat run <env>`:**
1. `EnvironmentManager::new()` discovers environments in `~/.flyboat/env/`
2. `EnvFeaturizedName::parse()` extracts name and features from `name+feature1+feature2`
3. `manager.search_result(&name)` resolves environment (returns `Rc<Environment>` or error with suggestions)
4. Features are applied via `config.apply_features()`
5. Determines engine from CLI → global config → auto-detect
6. `BuildCommand` builds the image
7. `RunCommand` runs the container with mounts, ports, network

## Security Defaults

- Default network: `none` (most restrictive) in dev_env.yaml
- Ports bind to `127.0.0.1` only
- Prevents running from `/`, `/root`, `/home`, or `$HOME` directories
- Podman uses `--userns keep-id:uid=3400,gid=3400`
- Template path traversal (`..`) is rejected
- Paths are canonicalized and verified to stay within env folder
- `bytes` charset in templates requires `encoding: base64`

## Global Configuration

Location: `~/.flyboat/config.yaml`

```yaml
container_engine: docker|podman  # Default: docker (auto-detects if not set, prefers podman)
arch: x86|arm64|i386             # Optional - uses system default if not set
default_env: string              # Optional - default environment when not specified
```

**Resolution order for container engine:**
1. CLI flag `--engine`
2. Global config `container_engine`
3. Auto-detect (prefers Podman for rootless)

**Resolution order for architecture:**
1. CLI flag `--arch` or `-a`
2. Global config `arch`
3. System default

## dev_env.yaml Schema

```yaml
name: string              # Required
disable: bool             # Default: false - excludes env from list/run/build
description: string
version: string           # Default: "1.0.0"
author: string            # Optional
source: string            # Optional URL
aliases: [string]         # Alternative names
settings:
  network: none|bridge|custom  # Default: none
  disk_access: mount|readonly|tmpfs|none  # Default: none
  entrypoint: string      # Default: bash
  working_dir: string     # Optional (default mount path: /project)
  artifacts_folder: bool  # Default: false
mounts:
  - host_path: string
    container_path: string
    read_only: bool       # Default: true
custom_args: [string]     # Extra docker run args
features:                 # Named config overlays (see Features section)
  feature_name:
    settings: ...         # Partial settings to override
    mounts: [...]         # Additional mounts to append
    custom_args: [...]    # Additional args to append
    templates: [...]      # Additional templates to append
templates:                # Template file processing
  - source: string        # Template file path (relative to env folder)
    overwrite: if_not_exists|on_build|on_run  # Default: if_not_exists
    variables:
      var_name:
        type: fixed
        value: string
        encoding: base64  # Optional
      var_name:
        type: random
        charset: alphanumeric|lowercase|uppercase|mixedcase|numbers|hex|bytes
        length: number    # 0-65535
        encoding: base64  # Required for bytes charset
```

## Template Processing

Templates allow generating config files with `{{var_name}}` placeholders replaced by fixed or random values.

**Output naming:** `config.example.toml` → `config.toml` (removes `.example` segment)

**Overwrite modes:**
- `if_not_exists` - Only generate if output doesn't exist (default)
- `on_build` - Generate during `flyboat build`
- `on_run` - Generate during `flyboat run` (fresh random values each run)

**Security:**
- Path traversal (`..`) is rejected
- Paths are canonicalized and verified to stay within env folder
- `bytes` charset requires `encoding: base64`

**Undefined variables:** Warning printed, `{{var_name}}` left as literal in output

## Features System

Features allow defining config variants in a single `dev_env.yaml` file. Activate features via CLI using `+` syntax.

**Example dev_env.yaml:**
```yaml
name: rust
settings:
  network: none

features:
  gpu:
    custom_args: ["--gpus=all"]
  network:
    settings:
      network: bridge
  dev:
    settings:
      disk_access: mount
    custom_args: ["-e", "DEBUG=1"]
```

**CLI usage:**
```bash
flyboat run rust+gpu           # Apply gpu feature
flyboat run rust+gpu+network   # Apply both in order (gpu first, then network)
flyboat build rust+dev         # Build with dev feature
```

**Merge semantics:**
- `settings.*` fields: **Replace** (later features override earlier)
- `mounts`, `custom_args`, `templates`: **Append** (accumulate)

**Verbose output:**
```
Taking base configuration 'rust'
  - Applying feature 'gpu'
  - Applying feature 'network'
```

**Key files:**
- `EnvFeaturizedName::parse()` in `lib.rs` - parses `name+feature1+feature2` syntax
- `EnvConfig::apply_feature()` in `config/dev_env.rs` - applies single feature
- `FeatureConfig`, `PartialEnvSettings` structs in `config/dev_env.rs`

## Code Style

- Rust edition 2024
- `#![forbid(unsafe_code)]` - no unsafe code allowed
- `clippy::all = "deny"` - all clippy warnings are errors
- Use `thiserror` for error definitions
- Use `Rc<Environment>` for shared environment references
- Use `IndexMap` for ordered maps (features preserve insertion order)