From c92d986a670d6b2d507222874fc52c7495abf857 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Fri, 10 Apr 2026 07:15:14 -0400 Subject: [PATCH] Add Docker Compose fleet config for agent containerization (closes #5) - docker-compose.yml: Ollama, Gitea, Agent worker, Monitor services - Dockerfile.agent: Hermes agent worker image with health endpoint - .env.example: Environment variable template - README.md: Setup instructions and service documentation Services include health checks, persistent volumes, isolated network, and proper dependency ordering. --- .env.example | 28 +++++++++ Dockerfile.agent | 68 ++++++++++++++++++++++ README.md | 48 +++++++++++++++- docker-compose.yml | 137 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 Dockerfile.agent create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..e579aef1 --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +# .env.example +# Environment variables for fleet-ops Docker Compose +# Copy this file to .env and fill in your values. +# cp .env.example .env + +# --- Agent --- +AGENT_NAME=hermes +AGENT_LOOP_INTERVAL=30 +LOG_LEVEL=info + +# --- Ollama --- +OLLAMA_PORT=11434 + +# --- Gitea --- +GITEA_UID=1000 +GITEA_GID=1000 +GITEA_ROOT_URL=http://gitea:3000 +GITEA_DOMAIN=gitea +GITEA_WEB_PORT=3000 +GITEA_SSH_PORT=2222 + +# --- Monitor --- +MONITOR_INTERVAL=60 + +# --- Agent API Keys (add your own) --- +# OPENAI_API_KEY= +# ANTHROPIC_API_KEY= +# NOSTR_PRIVATE_KEY= diff --git a/Dockerfile.agent b/Dockerfile.agent new file mode 100644 index 00000000..b2c5b99d --- /dev/null +++ b/Dockerfile.agent @@ -0,0 +1,68 @@ +# Dockerfile.agent +# Dockerfile for the Hermes agent worker loop. +FROM python:3.12-slim + +LABEL maintainer="Timmy Foundation" +LABEL description="Hermes agent worker for fleet-ops" + +# Install system dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy requirements first for layer caching +COPY requirements-agent.txt* ./ +RUN if [ -f requirements-agent.txt ]; then \ + pip install --no-cache-dir -r requirements-agent.txt; \ + else \ + pip install --no-cache-dir requests; \ + fi + +# Copy agent source if present +COPY agent/ ./agent/ + +# Health check endpoint (simple script if agent source not provided) +COPY --chmod=755 <<'EOF' /app/healthcheck.sh +#!/bin/sh +# Start a simple health endpoint +exec python3 -c " +import http.server, json, threading, time, os + +class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == '/health': + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'status': 'ok', 'agent': os.getenv('AGENT_NAME', 'hermes')}).encode()) + else: + self.send_response(404) + self.end_headers() + def log_message(self, format, *args): pass + +s = http.server.HTTPServer(('0.0.0.0', 8080), Handler) +t = threading.Thread(target=s.serve_forever, daemon=True) +t.start() + +loop_interval = int(os.getenv('AGENT_LOOP_INTERVAL', '30')) +print(f'Agent loop starting (interval={loop_interval}s)') +while True: + try: + print(f'[{os.getenv(\"AGENT_NAME\", \"hermes\")}] tick') + time.sleep(loop_interval) + except KeyboardInterrupt: + break +s.shutdown() +" +EOF + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ + CMD curl -sf http://localhost:8080/health || exit 1 + +CMD ["/app/healthcheck.sh"] diff --git a/README.md b/README.md index b0fc214e..718df7df 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ # fleet-ops -Sovereign Fleet Operations: Ansible playbooks and infrastructure-as-code for the Wizard fleet. \ No newline at end of file +Sovereign Fleet Operations: Ansible playbooks and infrastructure-as-code for the Wizard fleet. + +## Quick Start (Docker Compose) + +### Prerequisites +- Docker and Docker Compose v2 installed +- (Optional) NVIDIA GPU + nvidia-container-toolkit for GPU-accelerated inference + +### Setup + +```bash +# Clone and enter the repo +git clone && cd fleet-ops + +# Copy the env template and edit it +cp .env.example .env +# Edit .env with your actual values (API keys, ports, agent name, etc.) + +# Start all services +docker compose up -d + +# View logs +docker compose logs -f agent + +# Stop everything +docker compose down +``` + +### Services + +| Service | Description | Default Port | +|-----------|-----------------------------------|--------------| +| `ollama` | Local LLM inference engine | 11434 | +| `gitea` | Forge (git hosting, issue tracker)| 3000, 2222 | +| `agent` | Hermes agent worker loop | 8080 | +| `monitor` | Health check reporter | (internal) | + +### Customization +- Edit `.env` to change ports, agent name, loop interval, and log level. +- Edit `docker-compose.yml` to add/remove services or adjust resource limits. +- Place agent source code in an `agent/` directory before building. + +### Files +- `docker-compose.yml` — Main compose configuration +- `Dockerfile.agent` — Agent worker image +- `.env.example` — Environment variable template +- `playbooks/` — Ansible playbooks for VPS baseline configuration \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f2d841cf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,137 @@ +--- +# docker-compose.yml +# Fleet-ops Docker Compose configuration for the Timmy agent fleet. +# +# Services: +# - ollama: Local LLM inference engine +# - gitea: Forge service (issue tracking, git hosting) +# - agent: Hermes agent worker loop +# - monitor: Health check and monitoring service +# +# Usage: +# cp .env.example .env # then edit .env with real values +# docker compose up -d +# docker compose logs -f agent + +services: + ollama: + image: ollama/ollama:latest + container_name: ollama + restart: unless-stopped + ports: + - "${OLLAMA_PORT:-11434}:11434" + volumes: + - ollama_data:/root/.ollama + networks: + - fleet + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + + gitea: + image: gitea/gitea:latest + container_name: gitea + restart: unless-stopped + environment: + - USER_UID=${GITEA_UID:-1000} + - USER_GID=${GITEA_GID:-1000} + - GITEA__server__ROOT_URL=${GITEA_ROOT_URL:-http://gitea:3000} + - GITEA__server__DOMAIN=${GITEA_DOMAIN:-gitea} + - GITEA__server__SSH_PORT=${GITEA_SSH_PORT:-2222} + - GITEA__database__DB_TYPE=sqlite3 + ports: + - "${GITEA_WEB_PORT:-3000}:3000" + - "${GITEA_SSH_PORT:-2222}:22" + volumes: + - gitea_data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + networks: + - fleet + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/version"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + + agent: + build: + context: . + dockerfile: Dockerfile.agent + container_name: agent-worker + restart: unless-stopped + env_file: + - .env + environment: + - OLLAMA_BASE_URL=http://ollama:11434 + - GITEA_BASE_URL=http://gitea:3000 + - AGENT_NAME=${AGENT_NAME:-hermes} + - AGENT_LOOP_INTERVAL=${AGENT_LOOP_INTERVAL:-30} + - LOG_LEVEL=${LOG_LEVEL:-info} + volumes: + - agent_data:/app/data + networks: + - fleet + depends_on: + ollama: + condition: service_healthy + gitea: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 15s + + monitor: + image: curlimages/curl:latest + container_name: monitor + restart: unless-stopped + entrypoint: /bin/sh + command: + - -c + - | + while true; do + echo "[monitor] Checking services..." + for svc in ollama:11434/api/tags gitea:3000/api/v1/version agent:8080/health; do + host=$$(echo "$$svc" | cut -d: -f1) + url="http://$$svc" + if curl -sf "$$url" > /dev/null 2>&1; then + echo "[monitor] $$host: OK" + else + echo "[monitor] $$host: DOWN" + fi + done + sleep $${MONITOR_INTERVAL:-60} + done + networks: + - fleet + depends_on: + - ollama + - gitea + - agent + +volumes: + ollama_data: + driver: local + gitea_data: + driver: local + agent_data: + driver: local + +networks: + fleet: + driver: bridge + name: fleet-net