diff --git a/AI.md b/AI.md
index 8727e37..e972c0a 100644
--- a/AI.md
+++ b/AI.md
@@ -1,476 +1,697 @@
-## Project Overview
+# CasjaysDev Docker Application Image Specification (casjaysdevdocker)
-**ClawLama** is a CPU-optimized, multi-arch (amd64/arm64), single-container AI assistant that bundles [OpenClaw](https://github.com/openclaw/openclaw) and [Ollama](https://ollama.com) into one self-contained Docker image. Zero-cost, fully local, privacy-first — GPU accelerated when available, fully functional without it.
+**Name**: {name}
-**Image:** `docker.io/casjaysdevdocker/clawlama:latest`
-**Base:** `debian:bookworm-slim`
-**Platforms:** `linux/amd64`, `linux/arm64`
-**Single container:** Both Ollama and OpenClaw run inside one image, no compose required.
+**About this file:** This is the complete, authoritative specification for a CasjaysDev
+Docker **application image** repository (`casjaysdevdocker/{name}`). It is a master
+template — copied into an app image repo as that repo's `AI.md`. It is **permanent** —
+never delete it from a repo that carries it.
-**Source Reference:** Based on [iam-veeramalla's OpenClaw + Ollama guide](https://gist.github.com/iam-veeramalla/9d10f968038ee76d5bc374b44f0cf8bb).
+**Note:** `{name}` in this file is a reference token, not setup-time replacement text. Its
+value is always the repo directory basename (`basename "$PWD"`).
+
+**Maintenance procedure:** The bootstrap/update runbook (regenerating files after upstream
+template changes, creating new repos) is NOT in this file — it lives in the
+`dockersrc-bootstrap` agent (it handles both repo families via `REPO_TYPE` detection).
+This file defines the standards that procedure enforces.
---
-## Problem Statement
+# PART INDEX
-Running OpenClaw with a local Ollama model requires manual multi-step setup: installing OpenClaw, installing Ollama, pulling a model, writing a JSON config, and wiring everything together. ClawLama eliminates this friction by packaging everything into a single container — just `docker run`.
+| PART | Title |
+|------|-------|
+| 0 | Critical rules |
+| 1 | Repository model & structure |
+| 2 | Template system reference |
+| 3 | Tooling — gen-dockerfile & gen-script |
+| 4 | `.env.scripts` reference |
+| 5 | Runtime system — setup scripts, entrypoint, init.d |
+| 6 | README.md standard layout |
+| 7 | CI/CD workflows |
+| 8 | Verification & commit |
---
-## Architecture
+# PART 0: CRITICAL RULES
+
+## Org mapping
+
+| System | Org | Example |
+|--------|-----|---------|
+| GitHub (source) | `casjaysdevdocker` | `https://github.com/casjaysdevdocker/{name}` |
+| Docker Hub (push) | `casjaysdevdocker` | `casjaysdevdocker/{name}` |
+
+`casjaysdevdocker` repos are **applications** (gitea, opengist, super-productivity,
+ampache, aria2, …). They always build FROM the pre-built, multi-arch `casjaysdev/*` base
+images — never directly from upstream distro images. The bases themselves live in the
+separate `dockersrc` org (GitHub `dockersrc/{base}` → Docker Hub `casjaysdev/{base}`) —
+see the base specification (`DOCKERSRC.md`).
+
+## Non-negotiable rules
+
+1. **`AI.md` is permanent** — never delete it from the repo.
+2. **Generated files are owned by the template system** — never hand-tune content that
+ `gen-dockerfile` regenerates (see PART 1 ownership table); fix the upstream
+ `gen-dockerfile` template instead, then regenerate.
+3. **Hand-crafted files are owned by the repo** — `gen-dockerfile` must never overwrite
+ app-specific init.d scripts, custom bin scripts, a `05-custom.sh` with real content, or
+ a hand-crafted README (PART 6).
+4. **Removed OCI labels stay removed** (PART 2) — never re-add `base.name`,
+ `schema-version`, or duplicate `authors`/`source` entries.
+5. **`image.url` is a browsable page** — `https://hub.docker.com/r/casjaysdevdocker/{name}`.
+ `docker.io` is only a registry pull host; it is never a label URL.
+6. **`image.source` and `image.documentation` are the GitHub repo** —
+ `https://github.com/casjaysdevdocker/{name}`.
+7. **One Dockerfile, one file set** — app repos build one image (`latest` + date tag);
+ version variants (`Dockerfile.{ver}`) belong to base repos only.
+8. **Always `FROM casjaysdev/`** — never pull an upstream distro image directly;
+ the base repos exist so every app shares one patched, multi-arch foundation.
+9. **Only `root/`, `tmp/`, and `usr/` may exist at `rootfs/` top level** (PART 1).
+10. **Maintenance runs through the `dockersrc-bootstrap` agent** — do not improvise the
+ update procedure from memory.
+
+---
+
+# PART 1: REPOSITORY MODEL & STRUCTURE
+
+## What an app image repo is
+
+A `casjaysdevdocker/{name}` repo containerizes one application on top of a
+`casjaysdev/*` base. It publishes a single image (`casjaysdevdocker/{name}:latest` plus a
+date tag) — no per-version Dockerfile variants. The application itself is installed in
+`05-custom.sh` and started by one or more init.d service scripts.
+
+## Standard tree
```
-┌───────────────────────────────────────────────────────────┐
-│ docker.io/casjaysdevdocker/clawlama:latest │
-│ debian:bookworm-slim | linux/amd64, linux/arm64 │
-│ │
-│ ┌─────────────────────────────────────────────────────┐ │
-│ │ entrypoint.sh │ │
-│ │ • Detect arch (amd64/arm64) + GPU (nvidia-smi) │ │
-│ │ • Generate openclaw.json from env vars │ │
-│ │ • Pull model if not cached │ │
-│ │ • Start Ollama (background) │ │
-│ │ • Wait for Ollama health │ │
-│ │ • Start OpenClaw gateway (foreground) │ │
-│ └─────────────────────────────────────────────────────┘ │
-│ │
-│ ┌──────────────┐ ┌───────────────────┐ │
-│ │ OpenClaw │───▶│ Ollama (CPU/GPU) │ │
-│ │ Gateway + │ │ localhost:11434 │ │
-│ │ Agent │ │ Auto-detects GPU │ │
-│ │ :18789 │ │ at runtime │ │
-│ └──────────────┘ └───────────────────┘ │
-│ │ │ │
-│ ▼ ▼ │
-│ ┌─────────────┐ ┌────────────────┐ │
-│ │ /data/ │ │ /data/ │ │
-│ │ workspace/ │ │ ollama/ │ │
-│ │ (volume) │ │ (volume) │ │
-│ └─────────────┘ └────────────────┘ │
-└───────────────────────────────────────────────────────────┘
+{name}/
+├── AI.md # This specification (permanent)
+├── Dockerfile # [generated] single build file
+├── .dockerignore # [generated]
+├── .env.scripts # [generated] build config
+├── .gitattributes # [generated]
+├── .gitea/workflows/
+│ └── build.yml # [generated] gen-dockerfile actions
+├── .gitignore # [generated]
+├── LICENSE.md # License (WTFPL / app's own license)
+├── README.md # [generated*] standard layout (PART 6)
+└── rootfs/ # Container filesystem overlay
+ ├── root/docker/setup/ # [generated*] build-time setup scripts 00–07
+ ├── tmp/ # staged files installed at build time (optional)
+ └── usr/local/
+ ├── bin/ # [generated*] entrypoint.sh, pkmgr, symlink, copy,
+ │ # healthcheck + [hand-crafted] app-specific scripts
+ └── etc/docker/
+ ├── env/ # [hand-crafted] build/runtime env fragments (optional)
+ ├── functions/
+ │ └── entrypoint.sh # [generated] entrypoint function library
+ └── init.d/ # [hand-crafted] runtime init scripts (one per service)
```
-### Single-Image Architecture
+`[generated]` — safe to regenerate; local edits will be lost.
+`[generated*]` — regenerated from the template, EXCEPT files carrying repo-specific
+content (`05-custom.sh` with a real body, extra bin scripts, a hand-crafted README) —
+those follow the hand-crafted rules in PARTs 5 and 6.
+`[hand-crafted]` — never overwritten by the template system.
-Both Ollama and OpenClaw run inside one container. The entrypoint manages process lifecycle:
+App repos may additionally carry project files (`IDEA.md`, `CLAUDE.md`, `TODO.AI.md`)
+per the global project conventions — they are repo-owned and never touched by tooling.
-1. **Ollama** starts as a background process, binding to `localhost:11434`.
-2. **OpenClaw** starts as the foreground process after Ollama is healthy, connecting to `http://localhost:11434/v1`.
-3. If Ollama crashes, the entrypoint detects it and exits (container restarts via Docker's restart policy).
-4. Signals (SIGTERM/SIGINT) are forwarded to both processes for clean shutdown.
+## rootfs top-level policy
-### GPU Runtime Detection
+The only valid directories at the `rootfs/` root are `root/`, `tmp/`, and `usr/`.
+Anything else is a leftover from old patterns. Migration map:
-The image ships **no GPU libraries**. GPU acceleration is achieved through NVIDIA Container Toolkit runtime passthrough:
+| Old rootfs path | Correct rootfs path |
+|-----------------|---------------------|
+| `rootfs/etc/{path}` | `rootfs/tmp/etc/{path}` |
+| `rootfs/config/{path}` | `rootfs/tmp/etc/{path}` |
+| `rootfs/data/{path}` | `rootfs/tmp/var/{path}` |
+| `rootfs/var/{path}` | `rootfs/tmp/var/{path}` |
+| `rootfs/opt/{path}` | `rootfs/tmp/opt/{path}` |
+| `rootfs/share/{path}` | `rootfs/usr/local/share/{path}` |
-- At startup, entrypoint runs `nvidia-smi` to detect GPU availability.
-- **GPU found:** Ollama automatically uses CUDA via the mounted NVIDIA runtime. Logs report GPU model and VRAM.
-- **No GPU:** Ollama falls back to CPU inference. No errors, no warnings — this is the expected default path.
-- User enables GPU by passing `--gpus all` to `docker run` (requires NVIDIA Container Toolkit on host).
+`rootfs/usr/local/share/template-files/` is retired — the `DEFAULT_TEMPLATE_DIR`,
+`DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, and `DEFAULT_CONF_DIR` variables were removed
+from the template system; the entrypoint installs staged files from `rootfs/tmp/etc/`
+at container start instead.
-### Ports
+## Repo type detection
-- `18789` — OpenClaw gateway (exposed)
-- `11434` — Ollama API (internal only by default; expose with `-p 11434:11434` for debugging)
-
-### Installation Method (No curl | sh)
-
-- **Ollama:** Latest release binary downloaded directly from GitHub at build time: `https://github.com/ollama/ollama/releases/latest/download/ollama-linux-${TARGETARCH}`. No version pinning — always gets the newest stable release.
-- **OpenClaw:** Installed via `npm install -g openclaw@latest`. Always gets the newest published version.
-- **Node.js:** Current LTS from [NodeSource apt repository](https://deb.nodesource.com) (`node_lts.x`) with GPG key verification. Auto-advances to next LTS major (e.g., 22 → 24) when Node promotes it.
-
----
-
-## Features
-
-1. **Single container, single command** — `docker run -d docker.io/casjaysdevdocker/clawlama:latest` launches both Ollama and OpenClaw. No compose file required for basic use.
-2. **debian:bookworm-slim base** — Minimal Debian with glibc for full Ollama SIMD compatibility (AVX/AVX2 on amd64, NEON on arm64).
-3. **Direct binary installation (no curl | sh):**
- - Ollama: latest release binary from GitHub releases (`/releases/latest/download/`), selected per `TARGETARCH`.
- - OpenClaw: `npm install -g openclaw@latest`.
- - Node.js: current LTS from NodeSource apt repo (`node_lts.x`) with GPG key verification.
-4. **Pre-configured OpenClaw ↔ Ollama wiring** — OpenClaw config auto-generated at startup pointing to `http://localhost:11434/v1` with zero-cost pricing.
-5. **Persistent volumes** — Two mount points: `/data/ollama` (model store), `/data/workspace` (OpenClaw). Survive restarts.
-6. **Default model: `gpt-oss:20b`** — Automatically pulled on first launch before OpenClaw starts.
-7. **Health checks** — Container `HEALTHCHECK` verifies both Ollama API and OpenClaw gateway.
-8. **Environment variable overrides:**
- - `CLAWLAMA_MODEL` — Model to pull and use (default: `gpt-oss:20b`)
- - `CLAWLAMA_CONTEXT_WINDOW` — Context window size (default: `131072`)
- - `CLAWLAMA_MAX_TOKENS` — Max output tokens (default: `8192`)
- - `CLAWLAMA_MAX_CONCURRENT` — Agent concurrency (default: `4`)
- - `CLAWLAMA_SUBAGENT_CONCURRENT` — Subagent concurrency (default: `8`)
- - `CLAWLAMA_OPENCLAW_PORT` — OpenClaw gateway port (default: `18789`)
- - `OLLAMA_NUM_THREADS` — CPU threads for inference (default: auto-detect physical cores)
- - `OLLAMA_NUM_PARALLEL` — Max parallel requests (default: `1`)
- - `OLLAMA_MAX_LOADED_MODELS` — Models in memory (default: `1`)
- - `OLLAMA_HOST` — Ollama bind address (default: `127.0.0.1:11434`)
-9. **Multi-arch (amd64 + arm64)** — Single manifest tag built with `docker buildx`. Ollama binary selected by `TARGETARCH`. All scripts POSIX shell.
-10. **Runtime GPU detection** — Entrypoint probes `nvidia-smi`. GPU used automatically if available via `--gpus all`. No GPU libs in image; NVIDIA Container Toolkit handles passthrough. CPU is the default and primary path.
-11. **Model swap without rebuild** — Changing `CLAWLAMA_MODEL` and restarting pulls the new model and regenerates config.
-12. **CPU performance auto-tuning** — Entrypoint auto-detects physical cores, available RAM, and sets `OLLAMA_NUM_THREADS` optimally if unset. Logs detected values.
-13. **Telegram integration helper** — Optional `CLAWLAMA_TELEGRAM_BOT_TOKEN` env var auto-configures Telegram channel.
-14. **Full tool profile with layered restrictions** — All OpenClaw tools enabled via `profile: "full"`. Git and `/etc` restrictions enforced via TOOLS.md (soft) and container filesystem (hard).
-15. **Startup banner** — Print connection info, detected arch, CPU cores, RAM, GPU status, and model to stdout.
-16. **Quantized model recommendations** — README documents CPU-friendly models by RAM tier:
- - 8 GB RAM: 7B Q4 variants
- - 16 GB RAM: `gpt-oss:20b` (default) or 13B Q5
- - 32+ GB RAM: 20B+ full or 34B Q4
-17. **docker-compose.yml included** — Provided for users who prefer compose, with volume mounts and restart policy pre-configured.
-18. **Multi-model support** — Comma-separated `CLAWLAMA_MODELS` env var configures multiple models in the OpenClaw provider config.
-19. **Backup/restore scripts** — Shell scripts to tar `/data` volumes for migration.
-20. **Portainer/Dockge compatible** — Compose file works with popular Docker management UIs.
-21. **Architecture detection in logs** — Log detected arch and SIMD instruction sets (AVX, AVX2, AVX-512, NEON) for performance troubleshooting.
-22. **GPU VRAM-aware model selection** — When GPU detected, log VRAM and suggest optimal model/quantization for available resources.
-
----
-
-## File Structure
-
-```
-clawlama/
-├── AI.md # This spec
-├── TODO.AI.md # Task tracking
-├── Dockerfile # Multi-stage, multi-arch (amd64 + arm64)
-├── docker-compose.yml # Optional compose file for convenience
-├── .env.example # Template environment variables
-├── rootfs/
-│ ├── usr/local/bin/
-│ │ ├── entrypoint.sh # Main entrypoint: detect GPU, gen config, start services
-│ │ └── healthcheck.sh # Health check script for HEALTHCHECK instruction
-│ └── etc/clawlama/
-│ ├── openclaw.template.json # OpenClaw config template (envsubst-ready)
-│ └── TOOLS.md # Agent tool usage rules (git deny, /etc deny)
-├── scripts/
-│ ├── build.sh # Multi-arch buildx build + push
-│ ├── backup.sh # Backup /data volumes
-│ └── restore.sh # Restore /data volumes
-└── README.md # User-facing documentation
-```
-
----
-
-## Quick Start
+A repo is an **app** repo when no `Dockerfile.*` variant files exist:
```bash
-# CPU-only (default)
-docker run -d \
- --name clawlama \
- -v clawlama-data:/data \
- -p 18789:18789 \
- docker.io/casjaysdevdocker/clawlama:latest
-
-# With NVIDIA GPU acceleration
-docker run -d \
- --name clawlama \
- --gpus all \
- -v clawlama-data:/data \
- -p 18789:18789 \
- docker.io/casjaysdevdocker/clawlama:latest
-
-# Custom model + expose Ollama API for debugging
-docker run -d \
- --name clawlama \
- -v clawlama-data:/data \
- -p 18789:18789 \
- -p 11434:11434 \
- -e CLAWLAMA_MODEL=qwen2:7b \
- -e OLLAMA_HOST=0.0.0.0:11434 \
- docker.io/casjaysdevdocker/clawlama:latest
+if find . -maxdepth 1 -name 'Dockerfile.*' -type f | grep -q -- .; then
+ REPO_TYPE="base"
+else
+ REPO_TYPE="app"
+fi
```
---
-## Dockerfile Sketch
+# PART 2: TEMPLATE SYSTEM REFERENCE
-```dockerfile
-# ── Stage 1: Build dependencies ──────────────────────────────
-FROM debian:bookworm-slim AS builder
+Templates ship with `gen-dockerfile`, installed at
+`/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/`
+(`$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout). To inspect what the current
+templates produce, generate a fresh reference tree in a temp dir:
-ARG TARGETARCH
-
-# Install Node.js LTS from NodeSource apt repo (no curl | sh)
-RUN apt-get update && apt-get install -y --no-install-recommends \
- ca-certificates curl gnupg gettext-base && \
- mkdir -p /etc/apt/keyrings && \
- curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
- | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
- echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_lts.x nodistro main" \
- > /etc/apt/sources.list.d/nodesource.list && \
- apt-get update && apt-get install -y --no-install-recommends nodejs && \
- rm -rf /var/lib/apt/lists/*
-
-# Download latest Ollama binary directly (no curl | sh, no version pinning)
-RUN curl -fsSL -o /usr/local/bin/ollama \
- "https://github.com/ollama/ollama/releases/latest/download/ollama-linux-${TARGETARCH}" && \
- chmod +x /usr/local/bin/ollama
-
-# Install latest OpenClaw via npm
-RUN npm install -g openclaw@latest
-
-# ── Stage 2: Runtime ─────────────────────────────────────────
-FROM debian:bookworm-slim
-
-# Install Node.js LTS runtime (same repo method, no dev packages)
-RUN apt-get update && apt-get install -y --no-install-recommends \
- ca-certificates curl gnupg tini procps gettext-base && \
- mkdir -p /etc/apt/keyrings && \
- curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
- | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
- echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_lts.x nodistro main" \
- > /etc/apt/sources.list.d/nodesource.list && \
- apt-get update && apt-get install -y --no-install-recommends nodejs && \
- rm -rf /var/lib/apt/lists/*
-
-# Copy Ollama binary
-COPY --from=builder /usr/local/bin/ollama /usr/local/bin/ollama
-
-# Copy OpenClaw global install
-COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
-COPY --from=builder /usr/bin/openclaw /usr/bin/openclaw
-
-# Copy rootfs overlay
-COPY rootfs/ /
-
-# Create non-root user and data directories
-RUN groupadd -r clawlama && useradd -r -g clawlama -m clawlama && \
- mkdir -p /data/ollama /data/workspace && \
- chown -R clawlama:clawlama /data
-
-# Hard restriction: make /etc read-only for non-root users
-RUN chmod -R a-w /etc
-
-# Environment defaults (CPU-optimized)
-ENV CLAWLAMA_MODEL=gpt-oss:20b \
- CLAWLAMA_CONTEXT_WINDOW=131072 \
- CLAWLAMA_MAX_TOKENS=8192 \
- CLAWLAMA_MAX_CONCURRENT=4 \
- CLAWLAMA_SUBAGENT_CONCURRENT=8 \
- CLAWLAMA_OPENCLAW_PORT=18789 \
- OLLAMA_HOST=127.0.0.1:11434 \
- OLLAMA_MODELS=/data/ollama \
- OLLAMA_NUM_PARALLEL=1 \
- OLLAMA_MAX_LOADED_MODELS=1
-
-VOLUME ["/data"]
-EXPOSE 18789
-
-HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
- CMD /usr/local/bin/healthcheck.sh
-
-ENTRYPOINT ["tini", "--"]
-CMD ["/usr/local/bin/entrypoint.sh"]
+```bash
+gen-dockerfile /tmp/gen-dockerfile/{org}/{repo} {distro}
```
-**Notes:**
-- `tini` is the PID 1 init for proper signal handling — no zombie processes.
-- `TARGETARCH` is automatically set by `docker buildx` (`amd64` or `arm64`).
-- `curl` used only for apt key download and direct binary fetch — never piped to shell.
-- Builder stage is discarded; runtime image contains only what's needed.
-- `OLLAMA_MODELS=/data/ollama` ensures models persist in the volume.
-- `/etc` made non-writable via `chmod` — hard enforcement of `/etc` write protection.
-- Entrypoint copies `TOOLS.md` into `/data/workspace/TOOLS.md` on first run (OpenClaw reads this as agent instructions).
+See `gen-dockerfile --help` for supported distros/types. Keep this PART in sync whenever
+the templates change.
----
+## Template inventory
-## OpenClaw Configuration
+The template name selects the base OS family; for an app repo the resulting pull URL is
+always the matching `casjaysdev/*` image:
-The following JSON config is generated at container startup from environment variables via `envsubst`:
+| Template | Final stage | Init / PID 1 | App pulls FROM |
+|----------|-------------|--------------|----------------|
+| `alpine.template` | `scratch.template` | tini | `casjaysdev/alpine` |
+| `debian.template` | `scratch.template` | tini | `casjaysdev/debian` |
+| `ubuntu.template` | `scratch.template` | tini | `casjaysdev/ubuntu` |
+| `rhel.template` | `scratch.template` | tini | `casjaysdev/almalinux` |
+| `archlinux.template` | `scratch.template` | tini | `casjaysdev/archlinux` (multi-arch manifest) |
+| `web.template` | `systemd.template` | `/sbin/init` | `casjaysdev/web` |
+| `xorg.template` | `systemd.template` | `/sbin/init` | `casjaysdev/xorg` |
-```json
-{
- "models": {
- "providers": {
- "ollama": {
- "baseUrl": "http://localhost:11434/v1",
- "apiKey": "ollama-local",
- "api": "openai-completions",
- "models": [
- {
- "id": "${CLAWLAMA_MODEL}",
- "name": "${CLAWLAMA_MODEL}",
- "reasoning": false,
- "input": ["text"],
- "cost": {
- "input": 0,
- "output": 0,
- "cacheRead": 0,
- "cacheWrite": 0
- },
- "contextWindow": ${CLAWLAMA_CONTEXT_WINDOW},
- "maxTokens": ${CLAWLAMA_MAX_TOKENS}
- }
- ]
- }
- }
- },
- "agents": {
- "defaults": {
- "model": {
- "primary": "ollama/${CLAWLAMA_MODEL}"
- },
- "workspace": "/data/workspace",
- "maxConcurrent": ${CLAWLAMA_MAX_CONCURRENT},
- "subagents": {
- "maxConcurrent": ${CLAWLAMA_SUBAGENT_CONCURRENT}
- }
- }
- },
- "tools": {
- "profile": "full",
- "exec": {
- "security": "full",
- "ask": "off",
- "backgroundMs": 10000,
- "timeoutSec": 1800,
- "applyPatch": {
- "enabled": true,
- "workspaceOnly": true
- }
- },
- "fs": {
- "workspaceOnly": false
- },
- "elevated": {
- "enabled": false
- }
- }
-}
+Default template for app repos is `alpine` unless the application needs systemd, a GUI
+stack, or a distro-specific package.
+
+## Final-stage templates
+
+`scratch.template` — all non-GUI templates.
+- `ENTRYPOINT [ "tini", "-p", "SIGTERM","--", "/usr/local/bin/entrypoint.sh" ]`
+- `STOPSIGNAL SIGRTMIN+3`
+
+`systemd.template` — `web` and `xorg` (systemd is PID 1; tini is redundant).
+- `ENTRYPOINT [ "/sbin/init" ]`
+- `STOPSIGNAL SIGRTMIN+3`
+- No `tini_provider` stage, no `COPY --from=tini_provider` line.
+
+Both are identical apart from `ENTRYPOINT`. OCI labels, `ENV HOSTNAME`, and
+`VOLUME`/`EXPOSE`/`HEALTHCHECK` are the same in both.
+
+## OCI label standard
+
+Both final-stage templates emit these labels (no others):
+
+```
+LABEL maintainer="${GEN_DOCKERFILE_MAINTAINER}"
+LABEL org.opencontainers.image.vendor="${GEN_DOCKERFILE_VENDOR:-CasjaysDev}"
+LABEL org.opencontainers.image.authors="${GEN_DOCKERFILE_AUTHOR:-CasjaysDev}"
+LABEL org.opencontainers.image.licenses="${LICENSE}"
+LABEL org.opencontainers.image.title="${IMAGE_NAME}"
+LABEL org.opencontainers.image.description="Containerized version of ${IMAGE_NAME}"
+LABEL org.opencontainers.image.created="${BUILD_DATE}"
+LABEL org.opencontainers.image.version="${BUILD_VERSION}"
+LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
+LABEL org.opencontainers.image.url="${GEN_DOCKERFILE_HUB_REPO}"
+LABEL org.opencontainers.image.source="${GEN_DOCKERFILE_GIT_REPO}"
+LABEL org.opencontainers.image.documentation="${GEN_DOCKERFILE_GIT_REPO}"
+LABEL org.opencontainers.image.vcs-type="Git"
+LABEL com.github.containers.toolbox="false"
```
-### Tool Permission Policy
+Shell-expanded values (no `\`) are evaluated at template-render time by `gen-dockerfile`.
+Dollar-escaped values (`\${...}`) become literal Docker `ARG`/`ENV` references in the
+generated `Dockerfile`.
-OpenClaw's tool policy operates at the **tool level** (allow/deny entire tools like `exec`, `read`, `write`), not at the command or path level. To enforce the desired restrictions (no `git commit/push/reset --hard`, no writes to `/etc`), ClawLama uses a **layered approach**:
+Resolved values for a `casjaysdevdocker` repo pushing to Docker Hub:
-| Layer | Mechanism | What It Enforces |
-|-------|-----------|------------------|
-| **Tool profile** | `tools.profile: "full"` | All tools enabled: `group:fs`, `group:runtime`, `group:ui`, `group:sessions`, `group:memory`, `group:automation`, `web_search`, `web_fetch` |
-| **Exec security** | `tools.exec.security: "full"` | Shell commands auto-approved (no prompt). Agent has full exec access. |
-| **Workspace TOOLS.md** | Agent instruction file | Soft restrictions: instructs agent to never run `git commit`, `git push`, `git reset --hard` |
-| **Container filesystem** | Dockerfile `RUN chmod` / read-only mounts | Hard restriction: `/etc` read-only at container level, preventing writes regardless of agent behavior |
-| **Elevated mode** | `tools.elevated.enabled: false` | No host-level exec breakout (good hygiene) |
-| **Workspace scope** | `tools.exec.applyPatch.workspaceOnly: true` | `apply_patch` operations restricted to workspace directory |
-
-### Tool Groups (Reference)
-
-OpenClaw's built-in tool groups for use in `tools.allow` / `tools.deny`:
-
-| Group | Tools |
+| Label | Value |
|-------|-------|
-| `group:runtime` | `exec`, `bash`, `process` |
-| `group:fs` | `read`, `write`, `edit`, `apply_patch` |
-| `group:sessions` | `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` |
-| `group:memory` | `memory_search`, `memory_get` |
-| `group:ui` | `browser`, `canvas` |
-| `group:automation` | `cron`, `gateway` |
-| `group:messaging` | `message` |
-| `group:nodes` | `nodes` |
+| `url` | `https://hub.docker.com/r/casjaysdevdocker/{name}` — browsable Hub page; `gen-dockerfile` derives it from the registry host (`docker.io` → `hub.docker.com/r/`) |
+| `source` | `https://github.com/casjaysdevdocker/{name}` |
+| `documentation` | `https://github.com/casjaysdevdocker/{name}` |
-### Git Restrictions (via TOOLS.md)
+Older app repos may still carry `url="https://docker.io/casjaysdevdocker/{name}"` — that
+is the stale form; regeneration corrects it. Removed labels (never re-add):
+- `org.opencontainers.image.base.name` — belongs on the base image, not this image
+- `org.opencontainers.image.schema-version` — non-spec; redundant with `version`
+- Any duplicate `authors` or `source` entries
-Since OpenClaw has no command-pattern deny list for `exec`, git restrictions are enforced via the workspace `TOOLS.md` file — an agent instruction document that OpenClaw injects into the system prompt:
+## HOSTNAME convention
-```markdown
-
-# Tool Usage Rules
+All templates set `ENV HOSTNAME="casjaysdevdocker-${IMAGE_NAME}"` in every stage that
+declares it. The prefix is always `casjaysdevdocker-`, never `casjaysdev-`.
-## Git Restrictions (MANDATORY)
-- NEVER run `git commit` in any form
-- NEVER run `git push` in any form
-- NEVER run `git reset --hard` in any form
-- All other git commands are allowed (status, diff, log, add, branch, checkout, clone, pull, stash, etc.)
+## `GEN_DOCKERFILE_APP_DIR` and pull URL logic
-## Filesystem Restrictions
-- Do NOT write to or delete files in /etc/
-- The /etc directory is read-only at the container level
+`GEN_DOCKERFILE_APP_DIR` is auto-detected by `gen-dockerfile` from the parent directory
+of `$PWD` (the org the checkout lives in):
+
+```bash
+GEN_DOCKERFILE_APP_DIR="${GEN_DOCKERFILE_APP_DIR:-$(basename -- "$(dirname -- "$PWD")")}"
```
-**Important:** `TOOLS.md` is a **soft restriction** — the LLM is instructed not to run these commands, but it is not technically blocked by OpenClaw's tool policy engine. For hard enforcement, users should enable exec approvals (`tools.exec.ask: "always"`). The container-level `/etc` read-only mount is a **hard restriction** regardless.
+It selects the `GEN_DOCKER_SPECIFY_IMAGE_SOURCE_*` defaults:
-### `/etc` Protection (via Container)
+- `casjaysdevdocker/*` repos → `FROM casjaysdev/:latest` (pre-built, multi-arch)
+- `dockersrc/*` and all other orgs → `FROM :latest` (upstream official images)
-Since OpenClaw's `tools.fs.workspaceOnly` is an all-or-nothing toggle, and we want reads everywhere but writes denied only to `/etc`, this is enforced at the **Docker layer**:
+App repos must resolve to the `casjaysdev/*` branch — a checkout outside
+`~/Projects/*/casjaysdevdocker/` needs `GEN_DOCKERFILE_APP_DIR="casjaysdevdocker"`
+exported before calling `gen-dockerfile`, or the regenerated Dockerfile silently reverts
+to upstream distro pulls (rule 8 violation).
+
+## Arch Linux apps
+
+`casjaysdev/archlinux` is a multi-arch manifest (`linux/amd64` + `linux/arm64`), so app
+repos use a single `FROM ${PULL_URL}:${DISTRO_VERSION} AS build` — the three-stage
+`base-${TARGETARCH}` FROM block belongs to the base repo only.
+
+## `web.template` / `xorg.template` notes
+
+`web` apps inherit the systemd + noVNC stack (`SERVICE_PORT="5800"`,
+`EXPOSE_PORTS="5800 5900"` defaults); `xorg` apps inherit the systemd + Xorg stack. App
+packages go in `ENV_PACKAGES` / `02-packages.sh`, never by editing the template's stack
+list.
+
+## `debian.template` / `ubuntu.template` — RUN continuation
+
+The first `RUN` block must have `; \` after the `echo` line so
+`export DEBIAN_FRONTEND=noninteractive` executes before `apt-get`:
```dockerfile
-RUN chmod -R a-w /etc
+RUN set -e; \
+ echo "Updating the system"; \
+ export DEBIAN_FRONTEND=noninteractive; \
+ apt-get update && apt-get upgrade -yy && apt-get dist-upgrade -yy
```
-### Override Path
+Without the `; \` the export is a no-op and `apt-get` may prompt interactively.
+
+## Template resolution order
+
+1. `$GEN_DOCKERFILE_CONFIG_DIR/templates/.template` (user override)
+2. `/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/.template`
+ (installed; `$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout)
+
+`template_options.source` is sourced after `__set_variables`, allowing template-specific
+variable overrides.
+
+---
+
+# PART 3: TOOLING — gen-dockerfile & gen-script
+
+## `gen-dockerfile`
+
+```
+Usage: gen-dockerfile [options] [dir] [template] [repo-name] [git-repo-url]
+```
+
+| Flag | Meaning |
+|------|---------|
+| `--update` | Rewrite `.env.scripts` (add/drop vars against the current template) and update ARG/LABEL lines in the `Dockerfile`. Touches no other file. |
+| `--nogit` | Do not init or commit a git repo — required inside an existing repo. |
+| `--dir PATH` | Operate on / write output to PATH instead of `$PWD`. |
+| `--template NAME` | Template to use (`alpine`, `debian`, `ubuntu`, `rhel`, `archlinux`, `scratch`, `web`, `xorg`). Defaults to `alpine`. |
+| `--repo NAME` | Registry repo name (image basename). Defaults to the directory name. |
+| `--org NAME` | Registry owner / GitHub org (`--user` is an alias). Prefix `git:` or `reg:` to scope to one system; bare value sets both. For app repos both are `casjaysdevdocker`. |
+| `--registry URL` | Registry provider URL (e.g. `https://docker.io`). |
+| `--tag VERSION` | Image version tag (default `latest`). |
+| `--add-tags TAGS` | Comma-separated additional tags (`USE_DATE` = auto date tag). |
+| `--distro-name IMG` | Base image pull URL (overrides `ENV_PULL_URL`). |
+| `--distro-version T` | Base image tag (overrides `ENV_DISTRO_TAG`). |
+| `--startup FILE` | Generate an init.d service script at `rootfs/usr/local/etc/docker/init.d/FILE` via `gen-script other/start-service`. |
+| `--dockerfile` | Regenerate the Dockerfile only. |
+| `--force` | Overwrite existing files without prompting. |
+
+Resolution order when a value is not given by a flag: flags → git remote → project dirs →
+defaults.
+
+Special subcommand — `gen-dockerfile actions` writes `.gitea/workflows/build.yml` from
+the existing `Dockerfile` (PART 7). App repos have no versioned `build.{ver}.yml` files.
+
+## `gen-script`
+
+```
+Usage: gen-script [options] [template] [filename]
+```
+
+| Flag / env var | Meaning |
+|----------------|---------|
+| `--dir PATH` | Write the generated file to `PATH/filename`. |
+| `-n` / `--name VALUE` | Service name substituted into the template — fills `REPLACE_SERVICE_NAME` in `other/start-service`, pre-populating `SERVICE_NAME=` without a sed step. |
+| `GEN_SCRIPT_OVERWRITE="Y"` | Overwrite the output without prompting (default `"A"` = ask). Required when the target exists, even with `GEN_SCRIPT_EDITFILE="N"`. |
+| `GEN_SCRIPT_EDITFILE="N"` | Suppress the interactive editor after generation. `-e`/`--no` sets BOTH this AND `GEN_SCRIPT_OVERWRITE="Y"`; the env var alone does not. |
+| `other/start-service` | Template path — positional arg 1, slash-joined words, matching the `@@Template` header. |
+| `filename` | Output basename — positional arg 2, combined with `--dir`. |
+
+Other flags: `-k`/`--keep` (never overwrite), `--replace` (new header replaces old),
+`-d`/`--desc` (header description), `-p`/`--prev` (copy header metadata from a file).
+
+---
+
+# PART 4: `.env.scripts` REFERENCE
+
+Generated at the repo root; sourced by `gen-dockerfile` and by CI at build time. App
+repos carry exactly one. It is a pure `KEY="value"` file — no logic.
+
+## Variables
+
+| Variable | Purpose |
+|----------|---------|
+| `ENV_DOCKERFILE` | Dockerfile to build (`Dockerfile`) |
+| `ENV_REGISTRY_REPO` | Image name in the registry (`{name}`) |
+| `ENV_REGISTRY_ORG` | Registry namespace — `casjaysdevdocker` for app repos |
+| `ENV_REGISTRY_URL` | Registry base URL (`https://docker.io`) — pull/push host, never a label URL |
+| `ENV_REGISTRY_PUSH` | Full push path `org/repo` (`casjaysdevdocker/{name}`) |
+| `ENV_ADD_IMAGE_PUSH` | Extra push destinations |
+| `ENV_GIT_REPO_URL` | Full Git repo URL — `https://github.com/casjaysdevdocker/{name}`; feeds the `source`/`documentation` labels, so a wrong value here regresses labels on regeneration |
+| `ENV_USE_TEMPLATE` | Template name (`alpine`, `debian`, …) — the authoritative record of which base family the app builds on |
+| `ENV_PULL_URL` | Base image to pull FROM (`casjaysdev/`) |
+| `ENV_DISTRO_TAG` | Tag for the pull image (`latest`) |
+| `ENV_IMAGE_TAG` | Default image tag (`latest`) |
+| `ENV_ADD_TAGS` | Additional comma-separated tags; `USE_DATE` auto-generates a date tag |
+| `ENV_PACKAGES` | Space-separated package list |
+| `ENV_VENDOR` / `ENV_AUTHOR` / `ENV_MAINTAINER` | Label metadata |
+| `SERVICE_PORT` | Primary exposed port — apps normally set this |
+| `EXPOSE_PORTS` | Additional exposed ports |
+| `PHP_VERSION` / `NODE_VERSION` / `NODE_MANAGER` | Runtime versions (`system` default) |
+| `WWW_ROOT_DIR` | Web root (`/usr/local/share/httpd/default`) |
+| `DOCKER_ENTYPOINT_PORTS_WEB` / `DOCKER_ENTYPOINT_PORTS_SRV` | Ports passed to the entrypoint |
+| `DOCKER_ENTYPOINT_HEALTH_APPS` / `DOCKER_ENTYPOINT_HEALTH_ENDPOINTS` | Healthcheck targets |
+
+## Legacy variable auto-migration
+
+`gen-dockerfile` calls `__migrate_env_script` on every run, renaming old variables:
+
+| Old name | Current name |
+|----------|-------------|
+| `ENV_IMAGE_NAME` | `ENV_REGISTRY_REPO` |
+| `ENV_IMAGE_PUSH` | `ENV_REGISTRY_PUSH` |
+| `ENV_HUB_BASE` | `ENV_REGISTRY_URL` |
+| `ENV_ORG_NAME` | `ENV_REGISTRY_ORG` |
+
+Never use the old names in new files. Retired variables that must not reappear anywhere:
+`DEFAULT_TEMPLATE_DIR`, `DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, `DEFAULT_CONF_DIR`.
+
+---
+
+# PART 5: RUNTIME SYSTEM — SETUP SCRIPTS, ENTRYPOINT, INIT.D
+
+## Build-time setup scripts (`rootfs/root/docker/setup/`)
+
+Run in order inside the build stage:
+
+| Script | Role |
+|--------|------|
+| `00-init.sh` | Initialize base directory structure and environment |
+| `01-system.sh` | Repos, locales, timezone, system settings |
+| `02-packages.sh` | App-specific packages, package managers, language runtimes |
+| `03-files.sh` | Install staged files (`rootfs/tmp/etc/*` → `/etc/*`), permissions, symlinks |
+| `04-users.sh` | Create service users/groups |
+| `05-custom.sh` | Application install logic — the heart of an app repo |
+| `06-post.sh` | Post-install configuration |
+| `07-cleanup.sh` | Remove build deps, caches, temp files |
+
+**`05-custom.sh` ownership:** the upstream template ships an empty stub. An app repo's
+`05-custom.sh` carries the application install (download/build, users, default config) —
+that content exists only in the repo's git history, never in the template. On
+regeneration, keep the existing body and pull forward only boilerplate (version-stamp
+header, `set` line, shellcheck-disable line). The same rule applies to any other `0*.sh`
+found to contain real logic beyond the stub.
+
+## Entrypoint flow
+
+```
+tini → /usr/local/bin/entrypoint.sh
+├─ Load /usr/local/etc/docker/functions/entrypoint.sh
+├─ Source env: /root/env.sh, /usr/local/etc/docker/env/*.sh, /config/env/*.sh
+├─ Seed /config and /data on first run
+├─ __start_init_scripts — source every init.d/*.sh in sort order
+├─ Handle `healthcheck` command
+└─ Execute main application
+```
+
+`rootfs/usr/local/bin/` generated set: `entrypoint.sh`, `pkmgr`, `symlink`, `copy`,
+`healthcheck`. `pkmgr` wraps the native package manager (`apk`, `apt-get`, `dnf`,
+`pacman`) behind `pkmgr update|install|remove|clean`.
+
+## App-specific bin scripts
+
+Extra scripts in `rootfs/usr/local/bin/` that `gen-dockerfile` does not generate are
+repo-owned. Their `@@Template` header governs maintenance:
+
+- `@@Template : shell/sh` — boilerplate synced from `$TEMPLATE_DIR/scripts/shell/sh`;
+ `#!/usr/bin/env sh`, `set -e` only (`pipefail` is a bashism — must NOT appear)
+- `@@Template : shell/bash` — synced from `shell/bash`; `set -eo pipefail` required
+- No `@@Template` header — hand-written; never modified by tooling
+
+## init.d scripts — critical rules
+
+**Each service gets its own numbered init.d script. Never merge or remove services.**
+`__start_init_scripts` sources every `*.sh` in sort order — multi-process apps have one
+script per daemon (e.g. gitea: `05-dockerd.sh`, `08-gitea.sh`, `zz-act_runner.sh`).
+
+init.d scripts are **regenerated, never patched in place** — old copies may call functions
+removed from the current `functions/entrypoint.sh`. Generate fresh via
+`gen-script other/start-service` (or `gen-dockerfile --startup`), then restore the
+app-specific values. They are `#!/usr/bin/env bash` with `set -eo pipefail`.
+
+Required variables in every init.d script:
-Users can override tool policy by bind-mounting a custom config:
```bash
-docker run -v ./my-openclaw.json:/data/workspace/.openclaw/openclaw.json ...
+SERVICE_NAME="myapp"
+EXEC_CMD_BIN='myapp'
+EXEC_CMD_ARGS=''
+EXEC_PRE_SCRIPT=''
+SERVICE_USES_PID=''
+IS_WEB_SERVER="no"
+IS_DATABASE_SERVICE="no"
+USES_DATABASE_SERVICE="no"
+DATABASE_SERVICE_TYPE="sqlite"
+RUNAS_USER="root"
```
----
+Directory variables:
-## Technical Constraints
+```bash
+DATA_DIR="/data/$SERVICE_NAME"
+CONF_DIR="/config/$SERVICE_NAME"
+ETC_DIR="/etc/$SERVICE_NAME"
+LOG_DIR="/data/logs/$SERVICE_NAME"
+TMP_DIR="/tmp/$SERVICE_NAME"
+RUN_DIR="/run/$SERVICE_NAME"
+ROOT_FILE_PREFIX="/config/secure/auth/root"
+USER_FILE_PREFIX="/config/secure/auth/user"
+```
-- **CPU-optimized, GPU-optional** — Image ships zero GPU libraries. Ollama runs CPU inference by default. GPU is activated automatically when user passes `--gpus all` and NVIDIA Container Toolkit is installed on host. No image rebuild needed.
-- **Direct binary installation only** — No `curl | sh` or `curl | bash` anywhere in the Dockerfile. All software installed via apt packages (Node.js via NodeSource repo with GPG key), direct binary download (Ollama from GitHub releases), and npm package manager (OpenClaw).
-- **No version pinning** — Ollama uses `/releases/latest/download/`, OpenClaw uses `@latest`, Node.js uses `node_lts.x`. Each `docker build` picks up the newest stable versions. No version ARGs to maintain.
-- **Multi-arch manifest** — Published image contains both `linux/amd64` and `linux/arm64`. Built with `docker buildx`.
-- **Platform-specific performance:**
- - **amd64:** Ollama leverages AVX/AVX2/AVX-512 SIMD when available (most x86_64 CPUs from 2013+).
- - **arm64:** Ollama leverages NEON SIMD (all ARMv8+). Apple Silicon performs well; Raspberry Pi 5 is functional but slower.
-- **Node.js LTS** required for OpenClaw (currently ≥ 22).
-- **Single-process entrypoint pattern** — Ollama runs as background process, OpenClaw as foreground. Entrypoint handles process supervision, signal forwarding, and crash detection. No external supervisor (s6, supervisord) required.
-- **Ollama must be healthy before OpenClaw starts** — Entrypoint polls `localhost:11434` with retry loop before launching OpenClaw.
-- **No external API keys required** — entire stack is zero-cost by design.
-- **Model storage** can be large (20B model ≈ 12-15 GB); `/data/ollama` volume mount is mandatory, not tmpfs.
-- **RAM requirements (CPU inference):**
- - 7B Q4 model: ~4-6 GB RAM minimum
- - 13B Q4 model: ~8-10 GB RAM minimum
- - 20B model: ~16 GB RAM minimum (default)
- - Recommend at least 2 GB headroom above model size for OpenClaw + Node.js + OS.
-- **RAM requirements (GPU inference):** Model must fit in VRAM. Partial offload (CPU+GPU split) is handled automatically by Ollama.
-- **`apiKey: "ollama-local"`** — Ollama doesn't require auth but OpenClaw config requires a non-empty value; this is a dummy placeholder.
-- **Soft vs hard restrictions** — OpenClaw's tool policy operates at tool granularity only. Git command restrictions are soft (TOOLS.md). `/etc` write protection is hard (OS-level chmod). For maximum safety, enable `tools.exec.ask: "always"`.
-- **Image size target** — Under 500 MB compressed (excluding pulled models).
+## Hook functions
+
+The `start-service` template generates all outer hooks fully implemented — customise via
+the matching `*_local()` stub, which each outer hook calls automatically if defined:
+
+| Outer hook (do not redefine) | Customise via |
+|------------------------------|---------------|
+| `__run_precopy` | `__run_precopy_local` |
+| `__execute_prerun` | `__execute_prerun_local` |
+| `__run_pre_execute_checks` | `__run_pre_execute_checks_local` |
+| `__update_conf_files` | `__update_conf_files_local` |
+| `__pre_execute` | `__pre_execute_local` |
+| `__post_execute` | `__post_execute_local` |
+| `__pre_message` | `__pre_message_local` |
+| `__update_ssl_conf` | `__update_ssl_conf_local` |
+| `__create_service_env` | — |
+| `__run_start_script` | — |
+| `__run_secure_function` | — |
+
+## PID sentinel guard
+
+Every init.d script must guard on exactly this sentinel — leading dot, no underscores in
+the filename portion; any other form silently skips the guard:
+
+```bash
+if [ ! -f "/run/.start_init_scripts.pid" ]; then
+ echo "__start_init_scripts function hasn't been Initialized" >&2
+ SERVICE_IS_RUNNING="no"
+ __script_exit 1
+fi
+```
+
+## Volumes
+
+- `/config` — persistent configuration
+- `/data` — persistent application data
---
-## Security Considerations
+# PART 6: README.md STANDARD LAYOUT
-- OpenClaw gateway should NOT be exposed to the public internet without authentication.
-- Ollama API binds to `127.0.0.1` inside the container by default — not accessible from host unless explicitly exposed.
-- All data stays local — no telemetry, no cloud calls, no API key leakage.
-- Container runs as non-root user (`clawlama`) where possible. Entrypoint drops privileges after setup.
-- OpenClaw's prompt injection surface is inherited; users should review OpenClaw's security docs before enabling messaging integrations.
-- When GPU passthrough is enabled (`--gpus all`), the container gains access to host GPU devices — standard NVIDIA Container Toolkit security model applies.
+App image layout (`casjaysdevdocker/{name}` → `casjaysdevdocker/{name}`). Substitute
+`{name}` and `{port}` (the value of `SERVICE_PORT`); omit all `-p`/`ports:` sections only
+in the rare case `SERVICE_PORT` is empty.
+
+**Hand-crafted README exception:** a repo whose README deliberately diverges from this
+layout (full env-var tables, app-specific quick-start flags — e.g. gitea) owns its README.
+Update its facts (image name, org, ports, URLs), never rewrite its structure back to the
+generated layout.
+
+````markdown
+## 👋 Welcome to {name} 🚀
+
+{name} README
+
+
+## Install my system scripts
+
+```shell
+ sudo bash -c "$(curl -q -LSsf "https://github.com/systemmgr/installer/raw/main/install.sh")"
+ sudo systemmgr --config && sudo systemmgr install scripts
+```
+
+## Automatic install/update
+
+```shell
+dockermgr update {name}
+```
+
+## Install and run container
+
+```shell
+dockerHome="/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes"
+mkdir -p "$dockerHome"
+git clone "https://github.com/dockermgr/{name}" "$HOME/.local/share/CasjaysDev/dockermgr/{name}"
+cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/{name}/volumes/." "$dockerHome/"
+docker run -d \
+--restart always \
+--privileged \
+--name casjaysdevdocker-{name}-latest \
+--hostname {name} \
+-e TZ=${TIMEZONE:-America/New_York} \
+-v "$dockerHome/data:/data:z" \
+-v "$dockerHome/config:/config:z" \
+-p {port}:{port} \
+casjaysdevdocker/{name}:latest
+```
+
+## via docker-compose
+
+```yaml
+services:
+ ProjectName:
+ image: casjaysdevdocker/{name}
+ container_name: casjaysdevdocker-{name}
+ environment:
+ - TZ=America/New_York
+ - HOSTNAME={name}
+ volumes:
+ - "/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes/data:/data:z"
+ - "/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes/config:/config:z"
+ ports:
+ - {port}:{port}
+ restart: always
+```
+
+## Get source files
+
+```shell
+dockermgr download src casjaysdevdocker/{name}
+```
+
+OR
+
+```shell
+git clone "https://github.com/casjaysdevdocker/{name}" "$HOME/Projects/github/casjaysdevdocker/{name}"
+```
+
+## Build container
+
+```shell
+cd "$HOME/Projects/github/casjaysdevdocker/{name}"
+buildx
+```
+
+## Authors
+
+🤖 casjay: [Github](https://github.com/casjay) 🤖
+⛵ casjaysdevdocker: [Github](https://github.com/casjaysdevdocker) [Docker](https://hub.docker.com/u/casjaysdevdocker) ⛵
+````
---
-## Success Criteria
+# PART 7: CI/CD WORKFLOWS
-1. `docker pull docker.io/casjaysdevdocker/clawlama:latest` succeeds on both amd64 and arm64 hosts.
-2. `docker run -d -v clawlama-data:/data -p 18789:18789 docker.io/casjaysdevdocker/clawlama:latest` brings up both services with no manual intervention.
-3. Ollama model is pulled automatically on first run using CPU inference.
-4. OpenClaw agent responds to queries using the local Ollama model within 120 seconds of container start (CPU inference baseline; faster with GPU).
-5. Changing `CLAWLAMA_MODEL` env var and restarting pulls the new model and regenerates config.
-6. Container restart preserves all workspace data and downloaded models via `/data` volume.
-7. `docker stop && docker start` recovers to working state.
-8. Runs successfully on: x86_64 Linux server (cloud VM), Apple Silicon Mac (Docker Desktop), Raspberry Pi 5 (arm64).
-9. When launched with `--gpus all` on an NVIDIA host, Ollama detects and uses GPU — verified in logs.
-10. When launched without `--gpus` on any host, Ollama runs CPU-only — no GPU-related errors in logs.
+## Generated workflow (`gen-dockerfile actions`)
+
+`gen-dockerfile actions` writes `.gitea/workflows/build.yml` from the current
+`Dockerfile`. App repos get the single `build.yml` only — no versioned variants. All
+actions are SHA-pinned — never tag-pinned.
+
+- **Triggers:** `push` to `main`, monthly schedule, `workflow_dispatch`
+- **Registry strategy:** always logs in to the Gitea registry via the auto-provided
+ `GITEA_TOKEN`; conditionally logs in to Docker Hub when `vars.DOCKER_USERNAME` is set
+ (`vars.DOCKER_USERNAME` + `secrets.DOCKER_PASSWORD`; `vars.DOCKER_REGISTRY` overrides
+ the registry, `vars.DOCKER_ORG` the namespace)
+- **Platforms:** `linux/amd64,linux/arm64`
+- **build-args:** only `BUILD_DATE`, `GIT_COMMIT`, `BUILD_VERSION`
+- **Tags pushed:** date tag (`yymm`) + `latest` to both registries
+- **Annotations:** mirror the OCI label standard (PART 2), with `url`/`source`/
+ `documentation` set to the workflow's repository URL
+
+## Legacy workflow (`docker.yaml`)
+
+A hand-crafted `.gitea/workflows/docker.yaml` may exist in older repos — reference copy in
+the org-level `.github` repo. **Never overwrite it, and never use it as a template for new
+work** — it uses tag-pinned actions and retired secret names. All new/updated workflows
+come from `gen-dockerfile actions`.
---
-## Out of Scope
+# PART 8: VERIFICATION & COMMIT
-- **Baking GPU libraries into the image** — GPU support is via NVIDIA Container Toolkit runtime passthrough only.
-- **AMD ROCm / Intel Arc GPU support** — Only NVIDIA GPUs supported via container toolkit.
-- Custom OpenClaw skill development (users add their own post-deploy).
-- Building or fine-tuning custom Ollama models.
-- Production-grade reverse proxy / TLS termination (user's responsibility).
-- OpenClaw's built-in onboarding wizard (`openclaw onboard`) — replaced by container auto-config.
-- iMessage / BlueBubbles / platform-specific integrations requiring host OS access.
+## Syntax gates
----
+Every touched script must pass before commit:
-## References
+```bash
+for f in rootfs/usr/local/bin/*; do
+ [ -f "$f" ] || continue
+ case "$(head -1 "$f")" in
+ *bash*) bash -n "$f" || exit 1 ;;
+ *sh*) sh -n "$f" || exit 1 ;;
+ esac
+done
-- [OpenClaw GitHub](https://github.com/openclaw/openclaw)
-- [Ollama GitHub](https://github.com/ollama/ollama)
-- [Source Gist — iam-veeramalla](https://gist.github.com/iam-veeramalla/9d10f968038ee76d5bc374b44f0cf8bb)
-- [OpenClaw Docker Docs](https://github.com/openclaw/openclaw#docker)
-- [Ollama Docker Image](https://hub.docker.com/r/ollama/ollama)
+bash -n rootfs/usr/local/etc/docker/functions/entrypoint.sh
+
+for f in rootfs/root/docker/setup/0*.sh rootfs/usr/local/etc/docker/init.d/*.sh; do
+ [ -f "$f" ] || continue
+ bash -n "$f" || exit 1
+done
+```
+
+## Dead-reference gates
+
+After any regeneration:
+
+1. No script references an env var removed from `.env.scripts` (diff-driven check).
+2. No script calls a function absent from both the current
+ `functions/entrypoint.sh` and the script itself.
+3. No `__copy_templates` calls remain (retired with `DEFAULT_TEMPLATE_DIR`).
+4. `Dockerfile` still pulls `FROM casjaysdev/*` (rule 8) — an upstream distro pull means
+ `GEN_DOCKERFILE_APP_DIR` resolved wrong during regeneration.
+
+## Commit
+
+```bash
+git status --porcelain
+git diff --stat
+```
+
+Write `.git/COMMIT_MESS` from the actual diff — subject ≤64 chars, body as
+`- path: change` bullets covering every changed file. Then:
+
+```bash
+gitcommit --dir "$(git rev-parse --show-toplevel)" all
+```
+
+`git commit` / `git push` directly are forbidden. Never commit with a failing syntax
+gate.
diff --git a/IDEA.md b/IDEA.md
new file mode 100644
index 0000000..8727e37
--- /dev/null
+++ b/IDEA.md
@@ -0,0 +1,476 @@
+## Project Overview
+
+**ClawLama** is a CPU-optimized, multi-arch (amd64/arm64), single-container AI assistant that bundles [OpenClaw](https://github.com/openclaw/openclaw) and [Ollama](https://ollama.com) into one self-contained Docker image. Zero-cost, fully local, privacy-first — GPU accelerated when available, fully functional without it.
+
+**Image:** `docker.io/casjaysdevdocker/clawlama:latest`
+**Base:** `debian:bookworm-slim`
+**Platforms:** `linux/amd64`, `linux/arm64`
+**Single container:** Both Ollama and OpenClaw run inside one image, no compose required.
+
+**Source Reference:** Based on [iam-veeramalla's OpenClaw + Ollama guide](https://gist.github.com/iam-veeramalla/9d10f968038ee76d5bc374b44f0cf8bb).
+
+---
+
+## Problem Statement
+
+Running OpenClaw with a local Ollama model requires manual multi-step setup: installing OpenClaw, installing Ollama, pulling a model, writing a JSON config, and wiring everything together. ClawLama eliminates this friction by packaging everything into a single container — just `docker run`.
+
+---
+
+## Architecture
+
+```
+┌───────────────────────────────────────────────────────────┐
+│ docker.io/casjaysdevdocker/clawlama:latest │
+│ debian:bookworm-slim | linux/amd64, linux/arm64 │
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ entrypoint.sh │ │
+│ │ • Detect arch (amd64/arm64) + GPU (nvidia-smi) │ │
+│ │ • Generate openclaw.json from env vars │ │
+│ │ • Pull model if not cached │ │
+│ │ • Start Ollama (background) │ │
+│ │ • Wait for Ollama health │ │
+│ │ • Start OpenClaw gateway (foreground) │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ ┌──────────────┐ ┌───────────────────┐ │
+│ │ OpenClaw │───▶│ Ollama (CPU/GPU) │ │
+│ │ Gateway + │ │ localhost:11434 │ │
+│ │ Agent │ │ Auto-detects GPU │ │
+│ │ :18789 │ │ at runtime │ │
+│ └──────────────┘ └───────────────────┘ │
+│ │ │ │
+│ ▼ ▼ │
+│ ┌─────────────┐ ┌────────────────┐ │
+│ │ /data/ │ │ /data/ │ │
+│ │ workspace/ │ │ ollama/ │ │
+│ │ (volume) │ │ (volume) │ │
+│ └─────────────┘ └────────────────┘ │
+└───────────────────────────────────────────────────────────┘
+```
+
+### Single-Image Architecture
+
+Both Ollama and OpenClaw run inside one container. The entrypoint manages process lifecycle:
+
+1. **Ollama** starts as a background process, binding to `localhost:11434`.
+2. **OpenClaw** starts as the foreground process after Ollama is healthy, connecting to `http://localhost:11434/v1`.
+3. If Ollama crashes, the entrypoint detects it and exits (container restarts via Docker's restart policy).
+4. Signals (SIGTERM/SIGINT) are forwarded to both processes for clean shutdown.
+
+### GPU Runtime Detection
+
+The image ships **no GPU libraries**. GPU acceleration is achieved through NVIDIA Container Toolkit runtime passthrough:
+
+- At startup, entrypoint runs `nvidia-smi` to detect GPU availability.
+- **GPU found:** Ollama automatically uses CUDA via the mounted NVIDIA runtime. Logs report GPU model and VRAM.
+- **No GPU:** Ollama falls back to CPU inference. No errors, no warnings — this is the expected default path.
+- User enables GPU by passing `--gpus all` to `docker run` (requires NVIDIA Container Toolkit on host).
+
+### Ports
+
+- `18789` — OpenClaw gateway (exposed)
+- `11434` — Ollama API (internal only by default; expose with `-p 11434:11434` for debugging)
+
+### Installation Method (No curl | sh)
+
+- **Ollama:** Latest release binary downloaded directly from GitHub at build time: `https://github.com/ollama/ollama/releases/latest/download/ollama-linux-${TARGETARCH}`. No version pinning — always gets the newest stable release.
+- **OpenClaw:** Installed via `npm install -g openclaw@latest`. Always gets the newest published version.
+- **Node.js:** Current LTS from [NodeSource apt repository](https://deb.nodesource.com) (`node_lts.x`) with GPG key verification. Auto-advances to next LTS major (e.g., 22 → 24) when Node promotes it.
+
+---
+
+## Features
+
+1. **Single container, single command** — `docker run -d docker.io/casjaysdevdocker/clawlama:latest` launches both Ollama and OpenClaw. No compose file required for basic use.
+2. **debian:bookworm-slim base** — Minimal Debian with glibc for full Ollama SIMD compatibility (AVX/AVX2 on amd64, NEON on arm64).
+3. **Direct binary installation (no curl | sh):**
+ - Ollama: latest release binary from GitHub releases (`/releases/latest/download/`), selected per `TARGETARCH`.
+ - OpenClaw: `npm install -g openclaw@latest`.
+ - Node.js: current LTS from NodeSource apt repo (`node_lts.x`) with GPG key verification.
+4. **Pre-configured OpenClaw ↔ Ollama wiring** — OpenClaw config auto-generated at startup pointing to `http://localhost:11434/v1` with zero-cost pricing.
+5. **Persistent volumes** — Two mount points: `/data/ollama` (model store), `/data/workspace` (OpenClaw). Survive restarts.
+6. **Default model: `gpt-oss:20b`** — Automatically pulled on first launch before OpenClaw starts.
+7. **Health checks** — Container `HEALTHCHECK` verifies both Ollama API and OpenClaw gateway.
+8. **Environment variable overrides:**
+ - `CLAWLAMA_MODEL` — Model to pull and use (default: `gpt-oss:20b`)
+ - `CLAWLAMA_CONTEXT_WINDOW` — Context window size (default: `131072`)
+ - `CLAWLAMA_MAX_TOKENS` — Max output tokens (default: `8192`)
+ - `CLAWLAMA_MAX_CONCURRENT` — Agent concurrency (default: `4`)
+ - `CLAWLAMA_SUBAGENT_CONCURRENT` — Subagent concurrency (default: `8`)
+ - `CLAWLAMA_OPENCLAW_PORT` — OpenClaw gateway port (default: `18789`)
+ - `OLLAMA_NUM_THREADS` — CPU threads for inference (default: auto-detect physical cores)
+ - `OLLAMA_NUM_PARALLEL` — Max parallel requests (default: `1`)
+ - `OLLAMA_MAX_LOADED_MODELS` — Models in memory (default: `1`)
+ - `OLLAMA_HOST` — Ollama bind address (default: `127.0.0.1:11434`)
+9. **Multi-arch (amd64 + arm64)** — Single manifest tag built with `docker buildx`. Ollama binary selected by `TARGETARCH`. All scripts POSIX shell.
+10. **Runtime GPU detection** — Entrypoint probes `nvidia-smi`. GPU used automatically if available via `--gpus all`. No GPU libs in image; NVIDIA Container Toolkit handles passthrough. CPU is the default and primary path.
+11. **Model swap without rebuild** — Changing `CLAWLAMA_MODEL` and restarting pulls the new model and regenerates config.
+12. **CPU performance auto-tuning** — Entrypoint auto-detects physical cores, available RAM, and sets `OLLAMA_NUM_THREADS` optimally if unset. Logs detected values.
+13. **Telegram integration helper** — Optional `CLAWLAMA_TELEGRAM_BOT_TOKEN` env var auto-configures Telegram channel.
+14. **Full tool profile with layered restrictions** — All OpenClaw tools enabled via `profile: "full"`. Git and `/etc` restrictions enforced via TOOLS.md (soft) and container filesystem (hard).
+15. **Startup banner** — Print connection info, detected arch, CPU cores, RAM, GPU status, and model to stdout.
+16. **Quantized model recommendations** — README documents CPU-friendly models by RAM tier:
+ - 8 GB RAM: 7B Q4 variants
+ - 16 GB RAM: `gpt-oss:20b` (default) or 13B Q5
+ - 32+ GB RAM: 20B+ full or 34B Q4
+17. **docker-compose.yml included** — Provided for users who prefer compose, with volume mounts and restart policy pre-configured.
+18. **Multi-model support** — Comma-separated `CLAWLAMA_MODELS` env var configures multiple models in the OpenClaw provider config.
+19. **Backup/restore scripts** — Shell scripts to tar `/data` volumes for migration.
+20. **Portainer/Dockge compatible** — Compose file works with popular Docker management UIs.
+21. **Architecture detection in logs** — Log detected arch and SIMD instruction sets (AVX, AVX2, AVX-512, NEON) for performance troubleshooting.
+22. **GPU VRAM-aware model selection** — When GPU detected, log VRAM and suggest optimal model/quantization for available resources.
+
+---
+
+## File Structure
+
+```
+clawlama/
+├── AI.md # This spec
+├── TODO.AI.md # Task tracking
+├── Dockerfile # Multi-stage, multi-arch (amd64 + arm64)
+├── docker-compose.yml # Optional compose file for convenience
+├── .env.example # Template environment variables
+├── rootfs/
+│ ├── usr/local/bin/
+│ │ ├── entrypoint.sh # Main entrypoint: detect GPU, gen config, start services
+│ │ └── healthcheck.sh # Health check script for HEALTHCHECK instruction
+│ └── etc/clawlama/
+│ ├── openclaw.template.json # OpenClaw config template (envsubst-ready)
+│ └── TOOLS.md # Agent tool usage rules (git deny, /etc deny)
+├── scripts/
+│ ├── build.sh # Multi-arch buildx build + push
+│ ├── backup.sh # Backup /data volumes
+│ └── restore.sh # Restore /data volumes
+└── README.md # User-facing documentation
+```
+
+---
+
+## Quick Start
+
+```bash
+# CPU-only (default)
+docker run -d \
+ --name clawlama \
+ -v clawlama-data:/data \
+ -p 18789:18789 \
+ docker.io/casjaysdevdocker/clawlama:latest
+
+# With NVIDIA GPU acceleration
+docker run -d \
+ --name clawlama \
+ --gpus all \
+ -v clawlama-data:/data \
+ -p 18789:18789 \
+ docker.io/casjaysdevdocker/clawlama:latest
+
+# Custom model + expose Ollama API for debugging
+docker run -d \
+ --name clawlama \
+ -v clawlama-data:/data \
+ -p 18789:18789 \
+ -p 11434:11434 \
+ -e CLAWLAMA_MODEL=qwen2:7b \
+ -e OLLAMA_HOST=0.0.0.0:11434 \
+ docker.io/casjaysdevdocker/clawlama:latest
+```
+
+---
+
+## Dockerfile Sketch
+
+```dockerfile
+# ── Stage 1: Build dependencies ──────────────────────────────
+FROM debian:bookworm-slim AS builder
+
+ARG TARGETARCH
+
+# Install Node.js LTS from NodeSource apt repo (no curl | sh)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates curl gnupg gettext-base && \
+ mkdir -p /etc/apt/keyrings && \
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
+ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
+ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_lts.x nodistro main" \
+ > /etc/apt/sources.list.d/nodesource.list && \
+ apt-get update && apt-get install -y --no-install-recommends nodejs && \
+ rm -rf /var/lib/apt/lists/*
+
+# Download latest Ollama binary directly (no curl | sh, no version pinning)
+RUN curl -fsSL -o /usr/local/bin/ollama \
+ "https://github.com/ollama/ollama/releases/latest/download/ollama-linux-${TARGETARCH}" && \
+ chmod +x /usr/local/bin/ollama
+
+# Install latest OpenClaw via npm
+RUN npm install -g openclaw@latest
+
+# ── Stage 2: Runtime ─────────────────────────────────────────
+FROM debian:bookworm-slim
+
+# Install Node.js LTS runtime (same repo method, no dev packages)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates curl gnupg tini procps gettext-base && \
+ mkdir -p /etc/apt/keyrings && \
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
+ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
+ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_lts.x nodistro main" \
+ > /etc/apt/sources.list.d/nodesource.list && \
+ apt-get update && apt-get install -y --no-install-recommends nodejs && \
+ rm -rf /var/lib/apt/lists/*
+
+# Copy Ollama binary
+COPY --from=builder /usr/local/bin/ollama /usr/local/bin/ollama
+
+# Copy OpenClaw global install
+COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
+COPY --from=builder /usr/bin/openclaw /usr/bin/openclaw
+
+# Copy rootfs overlay
+COPY rootfs/ /
+
+# Create non-root user and data directories
+RUN groupadd -r clawlama && useradd -r -g clawlama -m clawlama && \
+ mkdir -p /data/ollama /data/workspace && \
+ chown -R clawlama:clawlama /data
+
+# Hard restriction: make /etc read-only for non-root users
+RUN chmod -R a-w /etc
+
+# Environment defaults (CPU-optimized)
+ENV CLAWLAMA_MODEL=gpt-oss:20b \
+ CLAWLAMA_CONTEXT_WINDOW=131072 \
+ CLAWLAMA_MAX_TOKENS=8192 \
+ CLAWLAMA_MAX_CONCURRENT=4 \
+ CLAWLAMA_SUBAGENT_CONCURRENT=8 \
+ CLAWLAMA_OPENCLAW_PORT=18789 \
+ OLLAMA_HOST=127.0.0.1:11434 \
+ OLLAMA_MODELS=/data/ollama \
+ OLLAMA_NUM_PARALLEL=1 \
+ OLLAMA_MAX_LOADED_MODELS=1
+
+VOLUME ["/data"]
+EXPOSE 18789
+
+HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
+ CMD /usr/local/bin/healthcheck.sh
+
+ENTRYPOINT ["tini", "--"]
+CMD ["/usr/local/bin/entrypoint.sh"]
+```
+
+**Notes:**
+- `tini` is the PID 1 init for proper signal handling — no zombie processes.
+- `TARGETARCH` is automatically set by `docker buildx` (`amd64` or `arm64`).
+- `curl` used only for apt key download and direct binary fetch — never piped to shell.
+- Builder stage is discarded; runtime image contains only what's needed.
+- `OLLAMA_MODELS=/data/ollama` ensures models persist in the volume.
+- `/etc` made non-writable via `chmod` — hard enforcement of `/etc` write protection.
+- Entrypoint copies `TOOLS.md` into `/data/workspace/TOOLS.md` on first run (OpenClaw reads this as agent instructions).
+
+---
+
+## OpenClaw Configuration
+
+The following JSON config is generated at container startup from environment variables via `envsubst`:
+
+```json
+{
+ "models": {
+ "providers": {
+ "ollama": {
+ "baseUrl": "http://localhost:11434/v1",
+ "apiKey": "ollama-local",
+ "api": "openai-completions",
+ "models": [
+ {
+ "id": "${CLAWLAMA_MODEL}",
+ "name": "${CLAWLAMA_MODEL}",
+ "reasoning": false,
+ "input": ["text"],
+ "cost": {
+ "input": 0,
+ "output": 0,
+ "cacheRead": 0,
+ "cacheWrite": 0
+ },
+ "contextWindow": ${CLAWLAMA_CONTEXT_WINDOW},
+ "maxTokens": ${CLAWLAMA_MAX_TOKENS}
+ }
+ ]
+ }
+ }
+ },
+ "agents": {
+ "defaults": {
+ "model": {
+ "primary": "ollama/${CLAWLAMA_MODEL}"
+ },
+ "workspace": "/data/workspace",
+ "maxConcurrent": ${CLAWLAMA_MAX_CONCURRENT},
+ "subagents": {
+ "maxConcurrent": ${CLAWLAMA_SUBAGENT_CONCURRENT}
+ }
+ }
+ },
+ "tools": {
+ "profile": "full",
+ "exec": {
+ "security": "full",
+ "ask": "off",
+ "backgroundMs": 10000,
+ "timeoutSec": 1800,
+ "applyPatch": {
+ "enabled": true,
+ "workspaceOnly": true
+ }
+ },
+ "fs": {
+ "workspaceOnly": false
+ },
+ "elevated": {
+ "enabled": false
+ }
+ }
+}
+```
+
+### Tool Permission Policy
+
+OpenClaw's tool policy operates at the **tool level** (allow/deny entire tools like `exec`, `read`, `write`), not at the command or path level. To enforce the desired restrictions (no `git commit/push/reset --hard`, no writes to `/etc`), ClawLama uses a **layered approach**:
+
+| Layer | Mechanism | What It Enforces |
+|-------|-----------|------------------|
+| **Tool profile** | `tools.profile: "full"` | All tools enabled: `group:fs`, `group:runtime`, `group:ui`, `group:sessions`, `group:memory`, `group:automation`, `web_search`, `web_fetch` |
+| **Exec security** | `tools.exec.security: "full"` | Shell commands auto-approved (no prompt). Agent has full exec access. |
+| **Workspace TOOLS.md** | Agent instruction file | Soft restrictions: instructs agent to never run `git commit`, `git push`, `git reset --hard` |
+| **Container filesystem** | Dockerfile `RUN chmod` / read-only mounts | Hard restriction: `/etc` read-only at container level, preventing writes regardless of agent behavior |
+| **Elevated mode** | `tools.elevated.enabled: false` | No host-level exec breakout (good hygiene) |
+| **Workspace scope** | `tools.exec.applyPatch.workspaceOnly: true` | `apply_patch` operations restricted to workspace directory |
+
+### Tool Groups (Reference)
+
+OpenClaw's built-in tool groups for use in `tools.allow` / `tools.deny`:
+
+| Group | Tools |
+|-------|-------|
+| `group:runtime` | `exec`, `bash`, `process` |
+| `group:fs` | `read`, `write`, `edit`, `apply_patch` |
+| `group:sessions` | `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` |
+| `group:memory` | `memory_search`, `memory_get` |
+| `group:ui` | `browser`, `canvas` |
+| `group:automation` | `cron`, `gateway` |
+| `group:messaging` | `message` |
+| `group:nodes` | `nodes` |
+
+### Git Restrictions (via TOOLS.md)
+
+Since OpenClaw has no command-pattern deny list for `exec`, git restrictions are enforced via the workspace `TOOLS.md` file — an agent instruction document that OpenClaw injects into the system prompt:
+
+```markdown
+
+# Tool Usage Rules
+
+## Git Restrictions (MANDATORY)
+- NEVER run `git commit` in any form
+- NEVER run `git push` in any form
+- NEVER run `git reset --hard` in any form
+- All other git commands are allowed (status, diff, log, add, branch, checkout, clone, pull, stash, etc.)
+
+## Filesystem Restrictions
+- Do NOT write to or delete files in /etc/
+- The /etc directory is read-only at the container level
+```
+
+**Important:** `TOOLS.md` is a **soft restriction** — the LLM is instructed not to run these commands, but it is not technically blocked by OpenClaw's tool policy engine. For hard enforcement, users should enable exec approvals (`tools.exec.ask: "always"`). The container-level `/etc` read-only mount is a **hard restriction** regardless.
+
+### `/etc` Protection (via Container)
+
+Since OpenClaw's `tools.fs.workspaceOnly` is an all-or-nothing toggle, and we want reads everywhere but writes denied only to `/etc`, this is enforced at the **Docker layer**:
+
+```dockerfile
+RUN chmod -R a-w /etc
+```
+
+### Override Path
+
+Users can override tool policy by bind-mounting a custom config:
+```bash
+docker run -v ./my-openclaw.json:/data/workspace/.openclaw/openclaw.json ...
+```
+
+---
+
+## Technical Constraints
+
+- **CPU-optimized, GPU-optional** — Image ships zero GPU libraries. Ollama runs CPU inference by default. GPU is activated automatically when user passes `--gpus all` and NVIDIA Container Toolkit is installed on host. No image rebuild needed.
+- **Direct binary installation only** — No `curl | sh` or `curl | bash` anywhere in the Dockerfile. All software installed via apt packages (Node.js via NodeSource repo with GPG key), direct binary download (Ollama from GitHub releases), and npm package manager (OpenClaw).
+- **No version pinning** — Ollama uses `/releases/latest/download/`, OpenClaw uses `@latest`, Node.js uses `node_lts.x`. Each `docker build` picks up the newest stable versions. No version ARGs to maintain.
+- **Multi-arch manifest** — Published image contains both `linux/amd64` and `linux/arm64`. Built with `docker buildx`.
+- **Platform-specific performance:**
+ - **amd64:** Ollama leverages AVX/AVX2/AVX-512 SIMD when available (most x86_64 CPUs from 2013+).
+ - **arm64:** Ollama leverages NEON SIMD (all ARMv8+). Apple Silicon performs well; Raspberry Pi 5 is functional but slower.
+- **Node.js LTS** required for OpenClaw (currently ≥ 22).
+- **Single-process entrypoint pattern** — Ollama runs as background process, OpenClaw as foreground. Entrypoint handles process supervision, signal forwarding, and crash detection. No external supervisor (s6, supervisord) required.
+- **Ollama must be healthy before OpenClaw starts** — Entrypoint polls `localhost:11434` with retry loop before launching OpenClaw.
+- **No external API keys required** — entire stack is zero-cost by design.
+- **Model storage** can be large (20B model ≈ 12-15 GB); `/data/ollama` volume mount is mandatory, not tmpfs.
+- **RAM requirements (CPU inference):**
+ - 7B Q4 model: ~4-6 GB RAM minimum
+ - 13B Q4 model: ~8-10 GB RAM minimum
+ - 20B model: ~16 GB RAM minimum (default)
+ - Recommend at least 2 GB headroom above model size for OpenClaw + Node.js + OS.
+- **RAM requirements (GPU inference):** Model must fit in VRAM. Partial offload (CPU+GPU split) is handled automatically by Ollama.
+- **`apiKey: "ollama-local"`** — Ollama doesn't require auth but OpenClaw config requires a non-empty value; this is a dummy placeholder.
+- **Soft vs hard restrictions** — OpenClaw's tool policy operates at tool granularity only. Git command restrictions are soft (TOOLS.md). `/etc` write protection is hard (OS-level chmod). For maximum safety, enable `tools.exec.ask: "always"`.
+- **Image size target** — Under 500 MB compressed (excluding pulled models).
+
+---
+
+## Security Considerations
+
+- OpenClaw gateway should NOT be exposed to the public internet without authentication.
+- Ollama API binds to `127.0.0.1` inside the container by default — not accessible from host unless explicitly exposed.
+- All data stays local — no telemetry, no cloud calls, no API key leakage.
+- Container runs as non-root user (`clawlama`) where possible. Entrypoint drops privileges after setup.
+- OpenClaw's prompt injection surface is inherited; users should review OpenClaw's security docs before enabling messaging integrations.
+- When GPU passthrough is enabled (`--gpus all`), the container gains access to host GPU devices — standard NVIDIA Container Toolkit security model applies.
+
+---
+
+## Success Criteria
+
+1. `docker pull docker.io/casjaysdevdocker/clawlama:latest` succeeds on both amd64 and arm64 hosts.
+2. `docker run -d -v clawlama-data:/data -p 18789:18789 docker.io/casjaysdevdocker/clawlama:latest` brings up both services with no manual intervention.
+3. Ollama model is pulled automatically on first run using CPU inference.
+4. OpenClaw agent responds to queries using the local Ollama model within 120 seconds of container start (CPU inference baseline; faster with GPU).
+5. Changing `CLAWLAMA_MODEL` env var and restarting pulls the new model and regenerates config.
+6. Container restart preserves all workspace data and downloaded models via `/data` volume.
+7. `docker stop && docker start` recovers to working state.
+8. Runs successfully on: x86_64 Linux server (cloud VM), Apple Silicon Mac (Docker Desktop), Raspberry Pi 5 (arm64).
+9. When launched with `--gpus all` on an NVIDIA host, Ollama detects and uses GPU — verified in logs.
+10. When launched without `--gpus` on any host, Ollama runs CPU-only — no GPU-related errors in logs.
+
+---
+
+## Out of Scope
+
+- **Baking GPU libraries into the image** — GPU support is via NVIDIA Container Toolkit runtime passthrough only.
+- **AMD ROCm / Intel Arc GPU support** — Only NVIDIA GPUs supported via container toolkit.
+- Custom OpenClaw skill development (users add their own post-deploy).
+- Building or fine-tuning custom Ollama models.
+- Production-grade reverse proxy / TLS termination (user's responsibility).
+- OpenClaw's built-in onboarding wizard (`openclaw onboard`) — replaced by container auto-config.
+- iMessage / BlueBubbles / platform-specific integrations requiring host OS access.
+
+---
+
+## References
+
+- [OpenClaw GitHub](https://github.com/openclaw/openclaw)
+- [Ollama GitHub](https://github.com/ollama/ollama)
+- [Source Gist — iam-veeramalla](https://gist.github.com/iam-veeramalla/9d10f968038ee76d5bc374b44f0cf8bb)
+- [OpenClaw Docker Docs](https://github.com/openclaw/openclaw#docker)
+- [Ollama Docker Image](https://hub.docker.com/r/ollama/ollama)