diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c0167f2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,102 @@ +# Speedtest Docker - AI Agent Instructions + +## Pre-Commit Verification + +**CRITICAL: Run the CI script before every commit to verify the container builds and runs correctly.** + +```bash +./bin/ci.sh +``` + +This script will: +1. Build the Docker image +2. Start a test container +3. Verify nginx responds on port 8080 +4. Check the HTML page loads with expected content +5. Verify dark theme support +6. Check for errors in container logs + +**DO NOT COMMIT if the CI script fails.** Fix the issues first. + +## Project Overview + +This is a lightweight Docker container (~35MB) that: +- Runs Ookla Speedtest CLI on a configurable cron schedule +- Generates interactive graphs using Plotly.js (client-side rendering) +- Serves results via nginx (unprivileged) +- Supports automatic dark/light theme switching + +## Architecture + +- **Base**: `nginxinc/nginx-unprivileged:alpine-slim` +- **Speedtest**: Ookla official CLI (downloaded at build time) +- **Cron**: dcron for scheduling +- **Graphs**: Plotly.js from CDN, data embedded in HTML +- **Security**: Runs as non-root (nginx user, uid 101) + +## Key Files + +| File | Purpose | +|------|---------| +| `Dockerfile` | Container build instructions | +| `docker-compose.yml` | Local development orchestration | +| `speedtest_runner.sh` | Runs speedtest, saves to CSV | +| `generate_html.sh` | Generates static HTML from CSV data | +| `entrypoint.sh` | Container startup orchestration | +| `bin/ci.sh` | **Pre-commit verification script** | + +## Common Issues + +### Permission Denied Errors +The container runs as non-root. All directories that need writing must have 777 permissions or be owned by uid 101. + +### Nginx Configuration +- Uses port 8080 (unprivileged) +- HTML served from `/app/html/` +- Configuration in `/etc/nginx/conf.d/default.conf` + +### Build Failures +If `docker build` fails: +1. Check the nginx-unprivileged base image is accessible +2. Verify apk packages are available +3. Ensure speedtest CLI download URLs are valid + +## Development Workflow + +1. Make changes to source files +2. **Run `./bin/ci.sh`** to verify +3. Fix any failures +4. Commit with descriptive message +5. Push + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `CRON_SCHEDULE` | `*/30 * * * *` | Speedtest frequency | +| `HOME` | `/tmp` | Required for non-root operation | +| `DATA_FILE` | `/data/speedtest.csv` | CSV data location | +| `HTML_FILE` | `/app/html/index.html` | Generated HTML location | + +## Testing Locally + +```bash +# Build and run +docker-compose up -d + +# View logs +docker-compose logs -f + +# Access dashboard +open http://localhost:8080 +``` + +## CI Checklist + +Before committing, verify: +- [ ] `./bin/ci.sh` passes completely +- [ ] Container builds without errors +- [ ] Container starts and stays running +- [ ] HTTP endpoint responds +- [ ] HTML contains "Speedtest Results" +- [ ] No errors in container logs diff --git a/bin/ci.sh b/bin/ci.sh new file mode 100755 index 0000000..40254e2 --- /dev/null +++ b/bin/ci.sh @@ -0,0 +1,98 @@ +#!/bin/bash +set -e + +echo "=== Speedtest Docker CI ===" +echo "" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test configuration +TEST_IMAGE="speedtest-ci-test" +TEST_CONTAINER="speedtest-ci-container" +TEST_PORT="18080" + +cleanup() { + echo "Cleaning up..." + docker rm -f "$TEST_CONTAINER" 2>/dev/null || true + docker rmi "$TEST_IMAGE" 2>/dev/null || true +} + +# Ensure cleanup on exit +trap cleanup EXIT + +echo "Step 1: Building Docker image..." +if docker build -t "$TEST_IMAGE" . 2>&1; then + echo -e "${GREEN}✓ Build successful${NC}" +else + echo -e "${RED}✗ Build failed${NC}" + exit 1 +fi + +echo "" +echo "Step 2: Starting container..." +if docker run -d \ + --name "$TEST_CONTAINER" \ + -p "$TEST_PORT:8080" \ + "$TEST_IMAGE" 2>&1; then + echo -e "${GREEN}✓ Container started${NC}" +else + echo -e "${RED}✗ Container failed to start${NC}" + exit 1 +fi + +echo "" +echo "Step 3: Waiting for nginx to be ready..." +RETRIES=30 +while [ $RETRIES -gt 0 ]; do + if curl -s "http://localhost:$TEST_PORT" > /dev/null 2>&1; then + echo -e "${GREEN}✓ Nginx is responding${NC}" + break + fi + RETRIES=$((RETRIES - 1)) + if [ $RETRIES -eq 0 ]; then + echo -e "${RED}✗ Nginx failed to respond${NC}" + echo "Container logs:" + docker logs "$TEST_CONTAINER" + exit 1 + fi + sleep 1 +done + +echo "" +echo "Step 4: Verifying HTML response..." +RESPONSE=$(curl -s "http://localhost:$TEST_PORT") +if echo "$RESPONSE" | grep -q "Speedtest Results"; then + echo -e "${GREEN}✓ HTML page loads correctly${NC}" +else + echo -e "${RED}✗ HTML page does not contain expected content${NC}" + echo "Response:" + echo "$RESPONSE" | head -20 + exit 1 +fi + +echo "" +echo "Step 5: Checking dark theme support..." +if echo "$RESPONSE" | grep -q "color-scheme: light dark"; then + echo -e "${GREEN}✓ Dark theme support detected${NC}" +else + echo -e "${YELLOW}⚠ Dark theme support not detected${NC}" +fi + +echo "" +echo "Step 6: Checking container logs for errors..." +if docker logs "$TEST_CONTAINER" 2>&1 | grep -i "error\|fatal" > /dev/null; then + echo -e "${RED}✗ Errors found in container logs${NC}" + docker logs "$TEST_CONTAINER" + exit 1 +else + echo -e "${GREEN}✓ No errors in logs${NC}" +fi + +echo "" +echo "================================" +echo -e "${GREEN}All CI checks passed!${NC}" +echo "================================"