Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions .kiro/specs/bootstrap-ai-coding/design-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,49 @@ RUN SSH authorized_keys ← stable per user key, cached
RUN SSH host key injection ← stable per project, cached
RUN sshd_config hardening ← stable, cached
RUN mkdir /run/sshd ← stable, cached
RUN apt-get install dbus-x11 gnome-keyring libsecret-1-0 ← keyring (CC-7), cached
RUN install /etc/profile.d/dbus-keyring.sh ← keyring startup script, cached
RUN apt-get install curl ca-certificates ← agent step, cached after first build
RUN nodesource setup + nodejs ← agent step, cached after first build
RUN npm install -g @augmentcode/auggie ← agent step, cached after first build
RUN echo manifest > /bac-manifest.json ← stable when agents unchanged, cached
CMD ["/usr/sbin/sshd", "-D"] ← always last (Req 21.2)
```

### Headless Keyring (D-Bus + gnome-keyring-daemon)

The container runs a headless `gnome-keyring-daemon` so that tools using `libsecret` / D-Bus Secret Service API (Claude Code, VS Code extensions) can store and retrieve OAuth tokens without a graphical desktop.

**Installed in the base layer** (inside `NewDockerfileBuilder`), not in individual agent modules, because multiple agents and IDE extensions benefit from it.

**Packages installed:**
- `dbus-x11` — provides `dbus-launch` for starting a session bus
- `gnome-keyring` — Secret Service provider
- `libsecret-1-0` — client library (used by Node.js `keytar` / `libsecret` bindings)

**Startup mechanism:**
A shell profile script (`/etc/profile.d/dbus-keyring.sh`) is installed that:
1. Starts a D-Bus session bus via `dbus-launch` (if not already running)
2. Exports `DBUS_SESSION_BUS_ADDRESS`
3. Unlocks `gnome-keyring-daemon` with an empty password via stdin pipe

```sh
#!/bin/sh
# /etc/profile.d/dbus-keyring.sh — start D-Bus + gnome-keyring for headless SSH sessions
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
eval $(dbus-launch --sh-syntax)
export DBUS_SESSION_BUS_ADDRESS
fi
# Unlock the default keyring with an empty password
echo "" | gnome-keyring-daemon --unlock --components=secrets 2>/dev/null
```

This script runs on every SSH login (interactive shells source `/etc/profile.d/*.sh`). The keyring is per-session and uses an empty password, which is acceptable because the container is single-user and access is already gated by SSH key authentication.

**Validates: CC-7**

---

### Base Image User Inspection

`docker/client.go` exposes a helper to detect UID/GID conflicts in the base image before building (Req 10a):
Expand Down Expand Up @@ -663,3 +699,123 @@ The `TestAFindConflictingUserPullsImageIfAbsent` test (in `internal/docker`) is
| `--stop-and-remove`, container not found | Docker inspect | Informational message → stdout, exit 0 |
| Container already running | Docker inspect before create | Session summary → stdout, exit 0 |
| `--purge` user declines confirmation | Confirmation prompt | Exit 0, nothing deleted |

---

## Semantic Refactoring (Req 22–27)

Internal code quality improvements: consolidate duplicated helpers, fix misplaced responsibilities, clarify intent. No user-facing behaviour changes.

---

### PathUtil Package (Req 22)

New package `internal/pathutil` with zero internal dependencies (only stdlib):

```go
package pathutil

import (
"os"
"path/filepath"
)

// ExpandHome expands a leading "~/" to the user's home directory.
func ExpandHome(p string) string {
if len(p) >= 2 && p[:2] == "~/" {
home, _ := os.UserHomeDir()
return filepath.Join(home, p[2:])
}
return p
}
```

All packages that currently define their own `expandHome` (`naming`, `ssh`, `credentials`, `datadir`, `cmd`) remove the local copy and import `pathutil.ExpandHome`. Tests in `cmd_test` that reference `cmd.ExpandHome` switch to `pathutil.ExpandHome`.

**Validates: Req 22**

---

### ExecInContainer Client Parameter (Req 23)

The `Agent.HealthCheck` interface and `docker.ExecInContainer` function both gain a `*docker.Client` parameter:

```go
// Agent interface change:
HealthCheck(ctx context.Context, c *docker.Client, containerID string) error

// ExecInContainer signature change:
func ExecInContainer(ctx context.Context, c *Client, containerID string, cmd []string) (int, error)
```

Call chain: `cmd/root.go` (has `dockerClient`) → `agent.HealthCheck(ctx, dockerClient, containerID)` → `docker.ExecInContainer(ctx, dockerClient, containerID, cmd)`.

**Validates: Req 23**

---

### Consolidated Flag Validation (Req 24)

Replace 7 individual `cmd.Flags().Changed(...)` blocks with:

```go
if mode == ModeStop || mode == ModePurge {
var changed []string
cmd.Flags().Visit(func(f *pflag.Flag) {
changed = append(changed, f.Name)
})
if err := ValidateStartOnlyFlags(mode, changed); err != nil {
return err
}
}
```

Dead code removed: private `stringSlicesEqual` and `expandHome` wrappers. Exported `StringSlicesEqual` remains.

**Validates: Req 24**

---

### Split ListBACImages (Req 25)

```go
// ListBACImages returns images with the "bac.managed=true" label only.
func ListBACImages(ctx context.Context, c *Client) ([]image.Summary, error)

// ListBACImagesWithFallback returns labeled images, falling back to a tag-prefix
// scan for images built before labels were introduced (pre-label compatibility).
// This fallback can be removed once all users have rebuilt their images with --rebuild.
func ListBACImagesWithFallback(ctx context.Context, c *Client) ([]image.Summary, error)
```

`runPurge` uses `ListBACImagesWithFallback`. Other callers use `ListBACImages`.

**Validates: Req 25**

---

### HostBindIP Constant (Req 26)

```go
// HostBindIP is the IP address the container's SSH port is bound to on the host.
HostBindIP = "127.0.0.1"
```

Used by `CreateContainer` (port binding) and `WaitForSSH` (TCP dial target). Decouples the bind address from `KnownHostsPatterns` which remains unchanged for known_hosts entry generation.

**Validates: Req 26**

---

### CredentialPreparer File Split (Req 27)

```
internal/agent/
agent.go # Agent interface only (6 methods)
preparer.go # CredentialPreparer optional interface
registry.go # Registry functions
```

Pure file reorganization. No functional change.

**Validates: Req 27**
30 changes: 30 additions & 0 deletions .kiro/specs/bootstrap-ai-coding/requirements-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Claude Code is Anthropic's AI coding agent. It is the first and default agent mo
2. THE Claude Code module SHALL declare `<Container_User_Home>/.claude` as its Credential_Volume mount path inside the Container.
3. THE Credential_Volume SHALL be a bind-mount so that authentication tokens written inside the Container are immediately persisted to the Host Credential_Store.
4. Authentication tokens persisted in the Host Credential_Store SHALL be available in future Sessions without re-authentication.
5. NOTE: Claude Code also stores onboarding state in `~/.claude.json` (outside the credential directory). See Requirement CC-8 for how this is handled via symlink and host-side synchronisation.

---

Expand Down Expand Up @@ -89,6 +90,21 @@ Claude Code is Anthropic's AI coding agent. It is the first and default agent mo

---

### Requirement CC-7: Headless Keyring for Credential Persistence

**User Story:** As a developer, I want Claude Code to be able to read and refresh its OAuth tokens inside the container without a graphical desktop, so I don't have to re-authenticate every time I connect.

#### Acceptance Criteria

1. THE container image SHALL include a D-Bus session bus and a Secret Service–compatible keyring daemon (gnome-keyring) capable of running without a graphical display.
2. THE keyring daemon SHALL be started automatically when the Container_User's SSH session begins, using an empty password to unlock the default keyring.
3. Claude Code (and any other tool using `libsecret` / D-Bus Secret Service API) SHALL be able to store and retrieve credentials via the running keyring daemon without user interaction.
4. THE `DBUS_SESSION_BUS_ADDRESS` environment variable SHALL be set correctly for the Container_User's session so that client applications can locate the session bus.
5. THE keyring setup SHALL NOT interfere with the existing SSH-based authentication or the bind-mounted `~/.claude` credential store.
6. THE keyring packages and startup configuration SHALL be installed as part of the base container image (in `DockerfileBuilder`), not in individual agent modules, since multiple agents and IDE extensions may benefit from it.

---

### Requirement CC-6: No Core Coupling

**User Story:** As a platform maintainer, I want the Claude Code module to be fully self-contained so that removing or replacing it requires no changes to core code.
Expand All @@ -101,6 +117,20 @@ Claude Code is Anthropic's AI coding agent. It is the first and default agent mo

---

### Requirement CC-8: Onboarding State Synchronisation

**User Story:** As a developer, I want my Claude Code onboarding state to persist across container recreations, so I am not prompted to complete the onboarding flow every time the container is rebuilt.

#### Acceptance Criteria

1. Claude Code stores its onboarding state (including `hasCompletedOnboarding`) in `~/.claude.json` on the Host — a file in the home directory root, separate from the `~/.claude/` credential directory.
2. THE Claude Code module SHALL create a symlink inside the Container at `<Container_User_Home>/.claude.json` pointing to `<Container_User_Home>/.claude/claude.json`, so that Claude Code reads and writes its onboarding state through the bind-mounted Credential_Volume.
3. THE Claude Code module SHALL implement the `CredentialPreparer` interface. Its `PrepareCredentials` method SHALL copy `~/.claude.json` from the Host home directory into the Credential_Store as `claude.json`, but only when the source file exists and is newer than the destination (or the destination is absent).
4. THE combination of the symlink (inside the container) and the host-side copy (before mount) SHALL ensure that a single bind-mount on `~/.claude/` persists both OAuth tokens and onboarding state across container rebuilds and restarts.
5. IF `~/.claude.json` does not exist on the Host (first-time user), THE `PrepareCredentials` method SHALL silently skip the copy without error.

---

## Augment Code Agent

### Overview
Expand Down
15 changes: 9 additions & 6 deletions .kiro/specs/bootstrap-ai-coding/requirements-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ The core application is responsible for all orchestration: Docker lifecycle mana
2. THE Container_Image SHALL include installation steps only for Enabled_Agents; agents not in the Enabled_Agents set SHALL NOT be installed in the image.
3. WHEN a Container is started, THE CLI SHALL mount each Enabled_Agent's Credential_Store from the Host as a Credential_Volume at the path declared by that Agent via the Agent_Interface.
4. WHEN the Credential_Store directory for an Enabled_Agent does not exist on the Host, THE CLI SHALL create it before starting the Container.
5. WHEN a Container is started and the Credential_Store for an Enabled_Agent contains no existing authentication tokens, THE CLI SHALL print a message to stdout identifying that Agent by name and instructing the user to authenticate it inside the Container.
6. Credentials written by an Agent inside the Container SHALL be immediately persisted to the Host Credential_Store via the bind-mount, and SHALL be available in future Sessions without re-authentication.
5. IF an Enabled_Agent implements the optional `CredentialPreparer` interface, THE CLI SHALL call `PrepareCredentials(storePath)` after ensuring the Credential_Store directory exists and before starting the Container. This allows agents to synchronise external state (e.g. onboarding files stored outside the Credential_Store) into the mounted directory.
6. WHEN a Container is started and the Credential_Store for an Enabled_Agent contains no existing authentication tokens, THE CLI SHALL print a message to stdout identifying that Agent by name and instructing the user to authenticate it inside the Container.
7. Credentials written by an Agent inside the Container SHALL be immediately persisted to the Host Credential_Store via the bind-mount, and SHALL be available in future Sessions without re-authentication.

---

Expand Down Expand Up @@ -250,10 +251,11 @@ The core application is responsible for all orchestration: Docker lifecycle mana
1. WHEN the CLI starts a Container and no existing Container_Image is found, THE CLI SHALL build the Container_Image automatically before starting the Container.
2. THE CLI SHALL store a manifest file inside the Container_Image (at `/bac-manifest.json`) listing the Enabled_Agents used to build it.
3. WHEN the CLI starts a Container and the Enabled_Agents set does not match the manifest in the existing Container_Image, THE CLI SHALL stop, print a message to stdout informing the user that the agent configuration has changed, and instruct them to run with `--rebuild` to update the image.
4. THE CLI SHALL support a `--rebuild` flag that forces a full Container_Image rebuild regardless of the existing manifest.
5. WHEN a rebuild is triggered (automatically or via `--rebuild`), THE CLI SHALL print a message to stdout indicating that the image is being built.
6. IF the image build fails, THE CLI SHALL print the build output to stderr and exit with a non-zero exit code.
7. THE CLI SHALL enforce a maximum build duration of the Image_Build_Timeout. IF the build exceeds this deadline, THE CLI SHALL cancel the build, print a descriptive error message to stderr identifying the timeout, and exit with a non-zero exit code.
4. THE CLI SHALL support a `--rebuild` flag that forces a full Container_Image rebuild regardless of the existing manifest. WHEN `--rebuild` is used, THE CLI SHALL disable the Docker layer cache (`NoCache`) so that all Dockerfile steps are re-executed from scratch.
5. WHEN `--rebuild` is used and the Container is already running, THE CLI SHALL stop and remove the existing Container before creating a new one from the rebuilt image.
6. WHEN a rebuild is triggered (automatically or via `--rebuild`), THE CLI SHALL print a message to stdout indicating that the image is being built.
7. IF the image build fails, THE CLI SHALL print the build output to stderr and exit with a non-zero exit code.
8. THE CLI SHALL enforce a maximum build duration of the Image_Build_Timeout. IF the build exceeds this deadline, THE CLI SHALL cancel the build, print a descriptive error message to stderr identifying the timeout, and exit with a non-zero exit code.

---

Expand Down Expand Up @@ -371,3 +373,4 @@ The core application is responsible for all orchestration: Docker lifecycle mana
5. Agent modules SHALL append only `RUN` (and optionally `ENV`, `COPY`) instructions via `Install()` — never `CMD` or `FROM`.

> **Rationale:** Docker's layer cache is sequential. Any instruction that changes invalidates all layers below it. Placing `CMD` before agent `RUN` steps means every agent installation step runs uncached on every build, even when the agent configuration has not changed. With `CMD` last, all `RUN` layers are stable and cached after the first build, reducing subsequent build times from minutes to seconds.

Loading
Loading