Skip to content
Open
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
6 changes: 3 additions & 3 deletions .kiro/specs/bootstrap-ai-coding/design-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ graph TD
BuildResAgent["internal/agents/buildresources\n(pseudo-agent module)"]
FutureAgent["internal/agents/other\n(future agent module)"]
DockerDaemon["Docker Daemon"]
Container["Container\n(sshd + dev user + enabled agents)"]
Container["Container\n(sshd + container user + enabled agents)"]

User -->|"bac <path> [--agents ...]"| CLI
CLI --> Naming
Expand All @@ -36,7 +36,7 @@ graph TD

The core packages (`internal/cmd`, `internal/naming`, `internal/docker`, `internal/ssh`, `internal/datadir`, `internal/agent`) have **no import dependency** on any package under `internal/agents/`. Agent modules are wired in exclusively via `main.go` blank imports.

> **Note (Req 28 — Module Consolidation):** The former `internal/credentials` and `internal/portfinder` packages have been merged into `internal/datadir`. Both dealt with per-project persistent state (credential paths, port selection/persistence) and had only `cmd/root.go` as their consumer. Consolidating them reduces package count without introducing import cycles or mixing unrelated concerns.
> **Note (Module Consolidation):** The former `internal/credentials` and `internal/portfinder` packages have been merged into `internal/datadir`. Both dealt with per-project persistent state (credential paths, port selection/persistence) and had only `cmd/root.go` as their consumer. Consolidating them reduces package count without introducing import cycles or mixing unrelated concerns.

### Package Layout

Expand Down Expand Up @@ -117,7 +117,7 @@ sequenceDiagram
else No image or --rebuild
CLI->>Docker: Inspect Base_Container_Image for UID/GID conflict (Req 10a)
alt Conflicting_Image_User found (existing user has Host_User UID or GID)
CLI->>User: "User '<name>' (UID/GID) already exists in base image. Rename to 'dev'? [y/N]"
CLI->>User: "User '<name>' (UID/GID) already exists in base image. Rename to '<host-username>'? [y/N]"
alt User confirms rename
CLI->>CLI: Set user_strategy = rename (use usermod -l in Dockerfile)
else User declines
Expand Down
2 changes: 1 addition & 1 deletion .kiro/specs/bootstrap-ai-coding/design-build-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ RUN echo manifest > /bac-manifest.json ← manifest
FROM bac-base:latest
RUN SSH host key injection ← per-project (core Req 13)
RUN SSH authorized_keys ← per-user key (core Req 4)
RUN sshd_config hardening ← stable
RUN sshd_config hardening + Port/ListenAddress ← per-project (Req 26.2)
RUN mkdir /run/sshd ← stable
CMD ["/usr/sbin/sshd", "-D"] ← always last (Req 21.2)
```
31 changes: 29 additions & 2 deletions .kiro/specs/bootstrap-ai-coding/design-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,25 @@ func validateRestartPolicy(policy string) error {

**Application in `docker/runner.go`** (`CreateContainer`):

The `ContainerSpec.RestartPolicy` field is mapped to the Docker SDK's `container.RestartPolicy` struct in `HostConfig`:
The `ContainerSpec.RestartPolicy` field is mapped to the Docker SDK's `container.RestartPolicy` struct in `HostConfig`. The network mode depends on `ContainerSpec.HostNetworkOff` (Req 26):

```go
import "github.com/docker/docker/api/types/container"

// Host network mode (default: HostNetworkOff == false)
hostConfig := &container.HostConfig{
// ... existing port bindings, mounts, etc.
NetworkMode: "host", // Req 26: share host network namespace
Mounts: mounts,
RestartPolicy: container.RestartPolicy{
Name: container.RestartPolicyMode(spec.RestartPolicy),
},
// No PortBindings — host network mode makes them unnecessary (Req 26.4)
}

// Bridge mode (HostNetworkOff == true, i.e. --host-network-off IS set)
hostConfig := &container.HostConfig{
PortBindings: portBindings, // maps container:22 → host:SSH_Port
Mounts: mounts,
RestartPolicy: container.RestartPolicy{
Name: container.RestartPolicyMode(spec.RestartPolicy),
},
Expand All @@ -343,6 +355,21 @@ hostConfig := &container.HostConfig{
4. Passes it to `ContainerSpec.RestartPolicy` when constructing the spec
5. `docker/runner.go` applies it in `CreateContainer`

**`--host-network-off` flag (Req 26):**

```go
rootCmd.Flags().BoolVar(&flagHostNetworkOff, "host-network-off", false,
"Disable host network mode; use bridge networking with port mapping")
```

**Threading from CLI to runner:**

1. `cmd/root.go` reads `--host-network-off` flag value (default: `false`)
2. Passes it as the `hostNetworkOff` parameter to `runStart`
3. `runStart` sets `ContainerSpec.HostNetworkOff` when constructing the spec
4. `docker/runner.go` selects `NetworkMode: "host"` or bridge + port bindings in `CreateContainer`
5. `runStart` passes it to `NewInstanceImageBuilder` to control whether sshd_config includes `Port`/`ListenAddress` directives

**Behaviour with `--stop-and-remove`:**

When `--stop-and-remove` is used, the container is stopped via `docker stop` (which sends SIGTERM) and then removed. A container with `unless-stopped` policy that was explicitly stopped will NOT restart on reboot — Docker tracks the "stopped by user" state. Removal deletes the container entirely, so there is nothing to restart.
Expand Down
21 changes: 12 additions & 9 deletions .kiro/specs/bootstrap-ai-coding/design-data-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Config struct {
NoUpdateKnownHosts bool
NoUpdateSSHConfig bool
RestartPolicy string // Docker restart policy (default: "unless-stopped")
HostNetworkOff bool // Req 26: when true, use bridge mode instead of host network
CredStoreOverrides map[string]string
HostInfo *hostinfo.Info // Req 22: runtime-resolved host user identity
}
Expand All @@ -52,14 +53,16 @@ type Config struct {

```go
type ContainerSpec struct {
Name string
ImageTag string
Dockerfile string
Mounts []Mount
SSHPort int
Labels map[string]string
RestartPolicy string // Req 25: Docker restart policy name
HostInfo *hostinfo.Info // Req 22: runtime-resolved host user identity (UID, GID, Username, HomeDir)
Name string
ImageTag string
Dockerfile string
Mounts []Mount
SSHPort int
Labels map[string]string
NoCache bool // When true, disable Docker layer cache during image build
HostNetworkOff bool // Req 26: when true, use bridge mode; when false (default), use host network
RestartPolicy string // Req 25: Docker restart policy name
HostInfo *hostinfo.Info // Req 22: runtime-resolved host user identity (UID, GID, Username, HomeDir)
}

type Mount struct {
Expand Down Expand Up @@ -93,7 +96,7 @@ type SessionSummary struct {
| `--stop-and-remove` and `--purge` both set | CLI-1 | Descriptive error → stderr, exit 1 |
| START or STOP mode and `<project-path>` absent | CLI-2 | Usage message → stderr, exit 1 |
| PURGE mode and `<project-path>` provided | CLI-2 | Descriptive error → stderr, exit 1 |
| STOP or PURGE mode and any of `--agents`, `--port`, `--ssh-key`, `--rebuild`, `--no-update-known-hosts`, `--no-update-ssh-config`, `--verbose`, `--docker-restart-policy` set | CLI-3 | Descriptive error naming the incompatible flag(s) → stderr, exit 1 |
| STOP or PURGE mode and any of `--agents`, `--port`, `--ssh-key`, `--rebuild`, `--no-update-known-hosts`, `--no-update-ssh-config`, `--verbose`, `--docker-restart-policy`, `--host-network-off` set | CLI-3 | Descriptive error naming the incompatible flag(s) → stderr, exit 1 |
| `--port` value outside 1024–65535 | CLI-5 | Descriptive error → stderr, exit 1 |
| `--agents` parses to empty list | CLI-6 | Descriptive error → stderr, exit 1 |
| `--agents` contains unknown agent ID | CLI-6 | Unknown ID + available IDs → stderr, exit 1 |
Expand Down
29 changes: 27 additions & 2 deletions .kiro/specs/bootstrap-ai-coding/design-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ func NewBaseImageBuilder(info *hostinfo.Info, strategy UserStrategy,

// NewInstanceImageBuilder produces the Dockerfile for bac-<name>:latest.
// Starts with FROM bac-base:latest, adds only per-project SSH config + CMD.
// When hostNetworkOff is false (default), sshd_config includes Port and ListenAddress
// directives for host network mode. When true, sshd uses default port 22 (bridge mode).
func NewInstanceImageBuilder(info *hostinfo.Info,
publicKey, hostKeyPriv, hostKeyPub string) *DockerfileBuilder
publicKey, hostKeyPriv, hostKeyPub string, sshPort int, hostNetworkOff bool) *DockerfileBuilder
```

The existing `NewDockerfileBuilder` is replaced by these two functions. Agent `Install()` methods are called on the base builder only. The instance builder has no agent steps — it's just SSH key injection + CMD.
Expand Down Expand Up @@ -116,13 +118,36 @@ When `--rebuild` is set:
3. Build Instance_Image (inherits fresh base)
4. Create and start new container

### `--host-network-off` and Instance_Image

The `--host-network-off` flag (Req 26) affects the Instance_Image content:

- **Default (host network mode):** `NewInstanceImageBuilder` appends `Port <SSH_Port>` and `ListenAddress 127.0.0.1` to sshd_config. The container is created with `NetworkMode: "host"` and no port bindings.
- **`--host-network-off` set (bridge mode):** `NewInstanceImageBuilder` omits the `Port` and `ListenAddress` directives — sshd uses its default port 22. The container is created with bridge networking and Docker port bindings (`127.0.0.1:<SSH_Port>` → container port 22).

Changing `--host-network-off` between invocations produces a different Instance_Image (different sshd_config). The CLI detects this mismatch and requires `--rebuild` to regenerate the Instance_Image.

### `--stop-and-remove` Behavior

No change to image handling. Only the container is stopped/removed. Both Base_Image and Instance_Image remain cached for fast restart.

### `--purge` Behavior

Removes all images (both `bac-base:latest` and all `bac-<name>:latest` instance images) via the existing `bac.managed` label filter.
Image removal proceeds in dependency order:

1. Remove Instance_Images (have `bac.container` label) — children of Base_Image
2. `ImagesPrune(dangling=true)` — removes untagged intermediate build layers that still reference Base_Image
3. Remove Base_Image (no `bac.container` label) — suppress "No such image" errors (image already removed by prune in step 2)

Docker refuses to delete a parent image while children exist. Dangling build cache layers count as children. "No such image" errors in step 3 are skipped because the prune already removed those images.

#### Reasoning

- **`bac.container` label as partition key:** All bac-managed images carry `bac.managed=true`. Instance_Images additionally carry `bac.container=<name>`. This distinguishes children from parent without inspecting image ancestry or parsing FROM directives at runtime.
- **Dangling prune between steps 1 and 3:** `--rebuild` creates new layers and old Instance_Image layers become dangling (untagged). These still reference `bac-base` as their parent in Docker's image graph. Without pruning, Base_Image removal fails with "image has dependent child images." The `dangling=true` filter only removes untagged, unreferenced images — it cannot remove tagged images from other tools.
- **Suppressing "No such image":** The prune in step 2 may remove images that were in the original image list (captured before removal began). Step 3 then attempts to remove an already-gone image. This is the desired outcome, so the error is skipped.

**Validates: TL-7**

### Constants Addition

Expand Down
16 changes: 12 additions & 4 deletions .kiro/specs/bootstrap-ai-coding/design-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,11 @@

---

#### Property 22b: SSH port is always bound to the selected host port
#### Property 22b: SSH port configuration matches the selected network mode

*For any* valid port number, the container spec SHALL contain a port binding mapping container port `constants.ContainerSSHPort/tcp` to that host port.
*For any* valid port number and network mode setting: WHEN host network mode is active, the Instance_Image Dockerfile SHALL contain sshd_config directives setting `Port <SSH_Port>` and `ListenAddress 127.0.0.1`, and the container spec SHALL use `NetworkMode: "host"` with no `PortBindings` or `ExposedPorts`. WHEN bridge mode is active, the Instance_Image Dockerfile SHALL NOT contain `Port` or `ListenAddress` directives, and the container spec SHALL contain a port binding mapping container port `constants.ContainerSSHPort/tcp` to `constants.HostBindIP:<SSH_Port>`.

**Validates: Req 12.4**
**Validates: Req 12.4, Req 26.2, Req 26.4, Req 26.6**

---

Expand Down Expand Up @@ -291,7 +291,7 @@

#### Property 34: START-only flags in STOP or PURGE mode always produce errors (CLI-3)

*For any* invocation in STOP or PURGE mode where any of `--agents`, `--port`, `--ssh-key`, `--rebuild`, `--no-update-known-hosts`, or `--no-update-ssh-config` is set, the CLI SHALL return a non-nil error identifying the incompatible flag(s).
*For any* invocation in STOP or PURGE mode where any of `--agents`, `--port`, `--ssh-key`, `--rebuild`, `--no-update-known-hosts`, `--no-update-ssh-config`, `--verbose`, `--docker-restart-policy`, or `--host-network-off` is set, the CLI SHALL return a non-nil error identifying the incompatible flag(s).

**Validates: CLI-3**

Expand Down Expand Up @@ -329,6 +329,14 @@

---

#### Property 57: --host-network-off controls network mode and sshd_config

*For any* container creation: WHEN `HostNetworkOff` is `false` (default), the `ContainerSpec` SHALL produce a container with `NetworkMode: "host"` and no port bindings, and the Instance_Image Dockerfile SHALL contain `Port <SSH_Port>` and `ListenAddress 127.0.0.1` in sshd_config. WHEN `HostNetworkOff` is `true`, the `ContainerSpec` SHALL produce a container with default bridge networking and port bindings mapping `constants.ContainerSSHPort/tcp` to `constants.HostBindIP:<SSH_Port>`, and the Instance_Image Dockerfile SHALL NOT contain `Port` or `ListenAddress` directives.

**Validates: Req 26.1, 26.2, 26.4, 26.6, 26.7, 26.13**

---

### Agent Module Properties

#### Property 27: All registered agents satisfy the Agent interface
Expand Down
6 changes: 3 additions & 3 deletions .kiro/specs/bootstrap-ai-coding/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The design is split across multiple focused files:

## Related Documents

- `requirements-core.md` — core application requirements (Req 1–22, including Req 22: Dynamic Container User Identity)
- `requirements-agents.md` — agent module requirements (CC-1–CC-6 for Claude Code, AC-1–AC-6 for Augment Code)
- `requirements-cli-combinations.md` — valid and invalid CLI flag combinations (CLI-1–CLI-6)
- `requirements-core.md` — core application requirements (Req 1–26, including Req 22: Dynamic Container User Identity, Req 25: Restart Policy, Req 26: Host Network Mode)
- `requirements-agents.md` — agent module requirements (CC-1–CC-8 for Claude Code, AC-1–AC-6 for Augment Code, BR-1–BR-6 for Build Resources)
- `requirements-cli-combinations.md` — valid and invalid CLI flag combinations (CLI-1–CLI-7)
- `requirements-two-layer-image.md` — two-layer Docker image requirements (TL-1–TL-11)
3 changes: 2 additions & 1 deletion .kiro/specs/bootstrap-ai-coding/requirements-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ Build Resources is a pseudo-agent that does not provide an AI coding tool. Inste
- `uv --version`
- `cmake --version`
- `javac -version`
- `go version` (executed via `bash -lc` to pick up `/etc/profile.d/golang.sh`)
- `/usr/local/go/bin/go version`
2. THE Health_Check SHALL be invoked by the core after the Container starts.
3. IF any Health_Check command fails, THE core SHALL report the failure to the user with a descriptive error message identifying the Build Resources agent and the specific tool that failed.

Expand All @@ -311,3 +311,4 @@ Build Resources is a pseudo-agent that does not provide an AI coding tool. Inste

1. THE `constants.DefaultAgents` value SHALL include `"build-resources"` so that the module is enabled by default when the `--agents` flag is omitted.
2. THE user SHALL be able to exclude Build Resources by specifying `--agents` without `build-resources` in the list.

Loading
Loading