Skip to content
Merged
134 changes: 134 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,140 @@ func TestCreate_StepLogger(t *testing.T) {

**Reference:** See `pkg/runtime/podman/steplogger_test.go` and step logger tests in `create_test.go`, `start_test.go`, `stop_test.go`, `remove_test.go`.

### Podman Runtime Configuration

The Podman runtime supports configurable image and agent settings through JSON files stored in the runtime's storage directory. This allows customization of the base image, installed packages, sudo permissions, and agent setup.

**Key Components:**
- **Config Interface** (`pkg/runtime/podman/config/config.go`): Interface for managing Podman runtime configuration
- **ImageConfig** (`pkg/runtime/podman/config/types.go`): Base image configuration (Fedora version, packages, sudo binaries, custom RUN commands)
- **AgentConfig** (`pkg/runtime/podman/config/types.go`): Agent-specific configuration (packages, RUN commands, terminal command)
- **Defaults** (`pkg/runtime/podman/config/defaults.go`): Default configurations for image and Claude agent

**Configuration Storage:**

Configuration files are stored in the runtime's storage directory:
```text
<storage-dir>/runtimes/podman/config/
├── image.json # Base image configuration
└── claude.json # Agent-specific configuration (e.g., for Claude Code)
```

**Configuration Files:**

**image.json** - Base image configuration:
```json
{
"version": "latest",
"packages": ["which", "procps-ng", "wget2", "@development-tools", "jq", "gh", "golang", "golangci-lint", "python3", "python3-pip"],
"sudo": ["/usr/bin/dnf", "/bin/nice", "/bin/kill", "/usr/bin/kill", "/usr/bin/killall"],
"run_commands": []
}
```

Fields:
- `version` (required) - Fedora version tag (e.g., "latest", "40", "41")
- `packages` (optional) - DNF packages to install
- `sudo` (optional) - Absolute paths to binaries the user can run with sudo (creates single `ALLOWED` Cmnd_Alias)
- `run_commands` (optional) - Custom shell commands to execute during image build (before agent setup)

**claude.json** - Agent-specific configuration:
```json
{
"packages": [],
"run_commands": [
"curl -fsSL --proto-redir '-all,https' --tlsv1.3 https://claude.ai/install.sh | bash",
"mkdir /home/claude/.config"
],
"terminal_command": ["claude"]
}
```

Fields:
- `packages` (optional) - Additional packages for the agent (merged with image packages)
- `run_commands` (optional) - Commands to set up the agent (executed after image setup)
- `terminal_command` (required) - Command to launch the agent (must have at least one element)

**Using the Config Interface:**

```go
import "github.com/kortex-hub/kortex-cli/pkg/runtime/podman/config"

// Create config manager (in Initialize method)
configDir := filepath.Join(storageDir, "config")
cfg, err := config.NewConfig(configDir)
if err != nil {
return fmt.Errorf("failed to create config: %w", err)
}

// Generate default configs if they don't exist
if err := cfg.GenerateDefaults(); err != nil {
return fmt.Errorf("failed to generate defaults: %w", err)
}

// Load configurations (in Create method)
imageConfig, err := cfg.LoadImage()
if err != nil {
return fmt.Errorf("failed to load image config: %w", err)
}

agentConfig, err := cfg.LoadAgent("claude")
if err != nil {
return fmt.Errorf("failed to load agent config: %w", err)
}
```

**Validation:**

The config system validates:
- Image version cannot be empty
- Sudo binaries must be absolute paths
- Terminal command must have at least one element
- All fields are optional except `version` (ImageConfig) and `terminal_command` (AgentConfig)

**Default Generation:**

- Default configs are auto-generated on first runtime initialization
- Existing config files are never overwritten - customizations are preserved
- Default image config includes common development tools and packages
- Default Claude config installs Claude Code from the official install script

**Containerfile Generation:**

The config system is used to generate Containerfiles dynamically:

```go
import "github.com/kortex-hub/kortex-cli/pkg/runtime/podman"

// Generate Containerfile content from configs
containerfileContent := generateContainerfile(imageConfig, agentConfig)

// Generate sudoers file content from sudo binaries
sudoersContent := generateSudoers(imageConfig.Sudo)
```

The `generateContainerfile` function creates a Containerfile with:
- Base image: `registry.fedoraproject.org/fedora:<version>`
- Merged packages from image and agent configs
- User/group setup (hardcoded as `claude:claude`)
- Sudoers configuration with single `ALLOWED` Cmnd_Alias
- Custom RUN commands from both configs (image commands first, then agent commands)

**Hardcoded Values:**

These values are not configurable:
- Base image registry: `registry.fedoraproject.org/fedora` (only version tag is configurable)
- Container user: `claude`
- Container group: `claude`
- User UID/GID: Matched to host user's UID/GID at build time

**Design Principles:**
- Follows interface-based design pattern with unexported implementation
- Uses nested JSON structure for clarity
- Validates all configurations on load to catch errors early
- Separate concerns: base image vs agent-specific settings
- Extensible: easy to add new agent configurations (e.g., `goose.json`, `cursor.json`)

### Config System

The config system manages workspace configuration stored in the `.kortex` directory. It provides an interface for reading and validating workspace settings including environment variables and mount points.
Expand Down
139 changes: 139 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,145 @@ kortex-cli start <workspace-id>

**Note:** When using `--output json`, all progress spinners are hidden to avoid polluting the JSON output.

#### Customizing Podman Runtime Configuration

The Podman runtime is fully configurable through JSON files. When you first use the Podman runtime, default configuration files are automatically created in your storage directory.

**Configuration Location:**

```text
$HOME/.kortex-cli/runtimes/podman/config/
├── image.json # Base image configuration
└── claude.json # Agent-specific configuration
```

Or if using a custom storage directory:

```text
<storage-dir>/runtimes/podman/config/
```

##### Base Image Configuration (`image.json`)

Controls the container's base image, packages, and sudo permissions.

**Structure:**

```json
{
"version": "latest",
"packages": [
"which",
"procps-ng",
"wget2",
"@development-tools",
"jq",
"gh",
"golang",
"golangci-lint",
"python3",
"python3-pip"
],
"sudo": [
"/usr/bin/dnf",
"/bin/nice",
"/bin/kill",
"/usr/bin/kill",
"/usr/bin/killall"
],
"run_commands": []
}
```

**Fields:**

- `version` (required) - Fedora version tag
- Examples: `"latest"`, `"40"`, `"41"`
- The base registry `registry.fedoraproject.org/fedora` is hardcoded and cannot be changed

- `packages` (optional) - DNF packages to install
- Array of package names
- Can include package groups with `@` prefix (e.g., `"@development-tools"`)
- Empty array is valid if no packages needed

- `sudo` (optional) - Binaries the `claude` user can run with sudo
- Must be absolute paths (e.g., `"/usr/bin/dnf"`)
- Creates a single `ALLOWED` command alias in sudoers
- Empty array disables all sudo access

- `run_commands` (optional) - Custom shell commands to run during image build
- Executed as RUN instructions in the Containerfile
- Run before agent-specific commands
- Useful for additional setup steps

##### Agent Configuration (`claude.json`)

Controls agent-specific packages and installation steps.

**Structure:**

```json
{
"packages": [],
"run_commands": [
"curl -fsSL --proto-redir '-all,https' --tlsv1.3 https://claude.ai/install.sh | bash",
"mkdir /home/claude/.config"
],
"terminal_command": [
"claude"
]
}
```

**Fields:**

- `packages` (optional) - Additional packages specific to this agent
- Merged with packages from `image.json`
- Useful for agent-specific dependencies

- `run_commands` (optional) - Commands to set up the agent
- Executed after image configuration commands
- Typically used for agent installation

- `terminal_command` (required) - Command to launch the agent
- Must have at least one element
- Can include flags: `["claude", "--verbose"]`

##### Applying Configuration Changes

Configuration changes take effect when you **register a new workspace with `init`**. The Containerfile is generated and the image is built during workspace registration, using the configuration files that exist at that time.

**To apply new configuration:**

1. Edit the configuration files:
```bash
# Edit base image configuration
nano ~/.kortex-cli/runtimes/podman/config/image.json

# Edit agent configuration
nano ~/.kortex-cli/runtimes/podman/config/claude.json
```

2. Register a new workspace (this creates the Containerfile and builds the image):
```bash
kortex-cli init /path/to/project --runtime podman
```

3. Start the workspace:
```bash
kortex-cli start <workspace-id>
```

**Notes:**

- The first `init` command using Podman creates default config files automatically
- Config files are never overwritten once created - your customizations are preserved
- The Containerfile and image are built during `init`, not `start`
- Each workspace's image is built once using the configuration at registration time
- To rebuild a workspace with new config, remove and re-register it
- Validation errors in config files will cause workspace registration to fail with a descriptive message
- The generated Containerfile is automatically copied to `/home/claude/Containerfile` inside the container for reference

## Workspace Configuration

Each workspace can optionally include a configuration file that customizes the environment and mount behavior for that specific workspace. The configuration is stored in a `workspace.json` file within the workspace's configuration directory (typically `.kortex` in the sources directory).
Expand Down
Loading
Loading