diff --git a/Dockerfile b/Dockerfile index e228114..16d1180 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,14 +14,18 @@ RUN apt-get update && apt-get install -y \ COPY Cargo.toml Cargo.lock ./ # Create dummy main.rs to build dependencies RUN mkdir src && echo "fn main() {}" > src/main.rs -RUN cargo build --release --locked +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + cargo build --release --locked RUN rm -rf src # 2. Copy source code COPY . . # Touch main.rs to force rebuild RUN touch src/main.rs -RUN cargo build --release --locked && \ +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + cargo build --release --locked && \ strip target/release/zeroclaw # ── Stage 2: Permissions & Config Prep ─────────────────────── diff --git a/dev/README.md b/dev/README.md index d1486e0..7645e0d 100644 --- a/dev/README.md +++ b/dev/README.md @@ -5,13 +5,13 @@ A fully containerized development sandbox for ZeroClaw agents. This environment ## Directory Structure - **`agent/`**: (Merged into root Dockerfile) - - The development image is built from the root `Dockerfile` using the `dev` stage (`target: dev`). - - Based on `debian:bookworm-slim` (unlike production `distroless`). - - Includes `bash`, `curl`, and debug tools. + - The development image is built from the root `Dockerfile` using the `dev` stage (`target: dev`). + - Based on `debian:bookworm-slim` (unlike production `distroless`). + - Includes `bash`, `curl`, and debug tools. - **`sandbox/`**: Dockerfile for the simulated user environment. - - Based on `ubuntu:22.04`. - - Pre-loaded with `git`, `python3`, `nodejs`, `npm`, `gcc`, `make`. - - Simulates a real developer machine. + - Based on `ubuntu:22.04`. + - Pre-loaded with `git`, `python3`, `nodejs`, `npm`, `gcc`, `make`. + - Simulates a real developer machine. - **`docker-compose.yml`**: Defines the services and `dev-net` network. - **`cli.sh`**: Helper script to manage the lifecycle. @@ -20,42 +20,53 @@ A fully containerized development sandbox for ZeroClaw agents. This environment Run all commands from the repository root using the helper script: ### 1. Start Environment + ```bash ./dev/cli.sh up ``` + Builds the agent from source and starts both containers. ### 2. Enter Agent Container (`zeroclaw-dev`) + ```bash ./dev/cli.sh agent ``` + Use this to run `zeroclaw` CLI commands manually, debug the binary, or check logs internally. + - **Path**: `/zeroclaw-data` - **User**: `nobody` (65534) ### 3. Enter Sandbox (`sandbox`) + ```bash ./dev/cli.sh shell ``` + Use this to act as the "user" or "environment" the agent interacts with. + - **Path**: `/home/developer/workspace` - **User**: `developer` (sudo-enabled) ### 4. Development Cycle + 1. Make changes to Rust code in `src/`. 2. Rebuild the agent: - ```bash - ./dev/cli.sh build - ``` + ```bash + ./dev/cli.sh build + ``` 3. Test changes inside the container: - ```bash - ./dev/cli.sh agent - # inside container: - zeroclaw --version - ``` + ```bash + ./dev/cli.sh agent + # inside container: + zeroclaw --version + ``` ### 5. Persistence & Shared Workspace + The local `playground/` directory (in repo root) is mounted as the shared workspace: + - **Agent**: `/zeroclaw-data/workspace` - **Sandbox**: `/home/developer/workspace` @@ -64,8 +75,77 @@ Files created by the agent are visible to the sandbox user, and vice versa. The agent configuration lives in `target/.zeroclaw` (mounted to `/zeroclaw-data/.zeroclaw`), so settings persist across container rebuilds. ### 6. Cleanup + Stop containers and remove volumes and generated config: + ```bash ./dev/cli.sh clean ``` + **Note:** This removes `target/.zeroclaw` (config/DB) but leaves the `playground/` directory intact. To fully wipe everything, manually delete `playground/`. + +## Local CI/CD (Docker-Only) + +Use this when you want CI-style validation without relying on GitHub Actions and without running Rust toolchain commands on your host. + +### 1. Build the local CI image + +```bash +./dev/ci.sh build-image +``` + +### 2. Run full local CI pipeline + +```bash +./dev/ci.sh all +``` + +This runs inside a container: + +- `cargo fmt --all -- --check` +- `cargo clippy --locked --all-targets -- -D clippy::correctness` +- `cargo test --locked --verbose` +- `cargo build --release --locked --verbose` +- `cargo deny check licenses sources` +- `cargo audit` +- Docker smoke build (`docker build --target dev ...` + `--version` check) + +### 3. Run targeted stages + +```bash +./dev/ci.sh lint +./dev/ci.sh test +./dev/ci.sh build +./dev/ci.sh deny +./dev/ci.sh audit +./dev/ci.sh security +./dev/ci.sh docker-smoke +``` + +Note: local `deny` focuses on license/source policy; advisory scanning is handled by `audit`. + +### 4. Enter CI container shell + +```bash +./dev/ci.sh shell +``` + +### 5. Optional shortcut via existing dev CLI + +```bash +./dev/cli.sh ci +./dev/cli.sh ci lint +``` + +### Isolation model + +- Rust compilation, tests, and audit/deny tools run in `zeroclaw-local-ci` container. +- Your host filesystem is mounted at `/workspace`; no host Rust toolchain is required. +- Cargo build artifacts are written to container volume `/ci-target` (not your host `target/`). +- Docker smoke stage uses your Docker daemon to build image layers, but build steps execute in containers. + +### Build cache notes + +- Both `Dockerfile` and `dev/ci/Dockerfile` use BuildKit cache mounts for Cargo registry/git data. +- Local CI reuses named Docker volumes for Cargo registry/git and target outputs. +- The CI image keeps Rust toolchain defaults from `rust:1.92-slim` (no custom `CARGO_HOME`/`RUSTUP_HOME` overrides), preventing repeated toolchain bootstrapping on each run. diff --git a/dev/ci.sh b/dev/ci.sh new file mode 100755 index 0000000..9424287 --- /dev/null +++ b/dev/ci.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ -f "dev/docker-compose.ci.yml" ]; then + COMPOSE_FILE="dev/docker-compose.ci.yml" +elif [ -f "docker-compose.ci.yml" ] && [ "$(basename "$(pwd)")" = "dev" ]; then + COMPOSE_FILE="docker-compose.ci.yml" +else + echo "❌ Run this script from repo root or dev/ directory." + exit 1 +fi + +compose_cmd=(docker compose -f "$COMPOSE_FILE") + +run_in_ci() { + local cmd="$1" + "${compose_cmd[@]}" run --rm local-ci bash -c "$cmd" +} + +print_help() { + cat <<'EOF' +ZeroClaw Local CI in Docker + +Usage: ./dev/ci.sh + +Commands: + build-image Build/update the local CI image + shell Open an interactive shell inside the CI container + lint Run rustfmt + clippy (container only) + test Run cargo test (container only) + build Run release build smoke check (container only) + audit Run cargo audit (container only) + deny Run cargo deny check (container only) + security Run cargo audit + cargo deny (container only) + docker-smoke Build and verify runtime image (host docker daemon) + all Run lint, test, build, security, docker-smoke + clean Remove local CI containers and volumes +EOF +} + +if [ $# -lt 1 ]; then + print_help + exit 1 +fi + +case "$1" in + build-image) + "${compose_cmd[@]}" build local-ci + ;; + + shell) + "${compose_cmd[@]}" run --rm local-ci bash + ;; + + lint) + run_in_ci "cargo fmt --all -- --check && cargo clippy --locked --all-targets -- -D clippy::correctness" + ;; + + test) + run_in_ci "cargo test --locked --verbose" + ;; + + build) + run_in_ci "cargo build --release --locked --verbose" + ;; + + audit) + run_in_ci "cargo audit" + ;; + + deny) + run_in_ci "cargo deny check licenses sources" + ;; + + security) + run_in_ci "cargo deny check licenses sources" + run_in_ci "cargo audit" + ;; + + docker-smoke) + docker build --target dev -t zeroclaw-local-smoke:latest . + docker run --rm zeroclaw-local-smoke:latest --version + ;; + + all) + run_in_ci "cargo fmt --all -- --check && cargo clippy --locked --all-targets -- -D clippy::correctness" + run_in_ci "cargo test --locked --verbose" + run_in_ci "cargo build --release --locked --verbose" + run_in_ci "cargo deny check licenses sources" + run_in_ci "cargo audit" + docker build --target dev -t zeroclaw-local-smoke:latest . + docker run --rm zeroclaw-local-smoke:latest --version + ;; + + clean) + "${compose_cmd[@]}" down -v --remove-orphans + ;; + + *) + print_help + exit 1 + ;; +esac diff --git a/dev/ci/Dockerfile b/dev/ci/Dockerfile new file mode 100644 index 0000000..4e6adb8 --- /dev/null +++ b/dev/ci/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1.7 + +FROM rust:1.92-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + pkg-config \ + libssl-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN rustup toolchain install 1.92 --profile minimal --component rustfmt --component clippy + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + cargo install --locked cargo-audit && \ + cargo install --locked cargo-deny --version 0.18.5 + +WORKDIR /workspace + +CMD ["bash"] diff --git a/dev/cli.sh b/dev/cli.sh index 3426417..ec9aad5 100755 --- a/dev/cli.sh +++ b/dev/cli.sh @@ -46,6 +46,7 @@ function print_help { echo -e " ${GREEN}agent${NC} Enter Agent (ZeroClaw CLI)" echo -e " ${GREEN}logs${NC} View logs" echo -e " ${GREEN}build${NC} Rebuild images" + echo -e " ${GREEN}ci${NC} Run local CI checks in Docker (see ./dev/ci.sh)" echo -e " ${GREEN}clean${NC} Stop and wipe workspace data" } @@ -94,6 +95,15 @@ case "$1" in echo -e "${GREEN}✅ Rebuild complete.${NC}" ;; + ci) + shift + if [ "$BASE_DIR" = "." ]; then + ./ci.sh "${@:-all}" + else + ./dev/ci.sh "${@:-all}" + fi + ;; + clean) echo -e "${RED}⚠️ WARNING: This will delete 'target/.zeroclaw' data and Docker volumes.${NC}" read -p "Are you sure? (y/N) " -n 1 -r diff --git a/dev/docker-compose.ci.yml b/dev/docker-compose.ci.yml new file mode 100644 index 0000000..2078726 --- /dev/null +++ b/dev/docker-compose.ci.yml @@ -0,0 +1,23 @@ +name: zeroclaw-local-ci + +services: + local-ci: + build: + context: .. + dockerfile: dev/ci/Dockerfile + container_name: zeroclaw-local-ci + working_dir: /workspace + environment: + - CARGO_TERM_COLOR=always + - PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + - CARGO_TARGET_DIR=/ci-target + volumes: + - ..:/workspace + - cargo-registry:/usr/local/cargo/registry + - cargo-git:/usr/local/cargo/git + - ci-target:/ci-target + +volumes: + cargo-registry: + cargo-git: + ci-target: diff --git a/src/tools/git_operations.rs b/src/tools/git_operations.rs index bf4e62c..c197eff 100644 --- a/src/tools/git_operations.rs +++ b/src/tools/git_operations.rs @@ -2,7 +2,6 @@ use super::traits::{Tool, ToolResult}; use crate::security::{AutonomyLevel, SecurityPolicy}; use async_trait::async_trait; use serde_json::json; -use std::path::Path; use std::sync::Arc; /// Git operations tool for structured repository management. @@ -556,6 +555,7 @@ impl Tool for GitOperationsTool { mod tests { use super::*; use crate::security::SecurityPolicy; + use std::path::Path; use tempfile::TempDir; fn test_tool(dir: &Path) -> GitOperationsTool {