Files
compose-farm/CLAUDE.md
2026-01-07 15:59:30 +01:00

7.6 KiB

Compose Farm Development Guidelines

Core Principles

  • KISS: Keep it simple. This is a thin wrapper around docker compose over SSH.
  • YAGNI: Don't add features until they're needed. No orchestration, no service discovery, no health checks.
  • DRY: Reuse patterns. Common CLI options are defined once, SSH logic is centralized.

Architecture

src/compose_farm/
├── cli/               # CLI subpackage
│   ├── __init__.py    # Imports modules to trigger command registration
│   ├── app.py         # Shared Typer app instance, version callback
│   ├── common.py      # Shared helpers, options, progress bar utilities
│   ├── config.py      # Config subcommand (init, show, path, validate, edit, symlink)
│   ├── lifecycle.py   # up, down, stop, pull, restart, update, apply, compose commands
│   ├── management.py  # refresh, check, init-network, traefik-file commands
│   ├── monitoring.py  # logs, ps, stats, list commands
│   ├── ssh.py         # SSH key management (setup, status, keygen)
│   └── web.py         # Web UI server command
├── compose.py         # Compose file parsing (.env, ports, volumes, networks)
├── config.py          # Pydantic models, YAML loading
├── console.py         # Shared Rich console instances
├── executor.py        # SSH/local command execution, streaming output
├── glances.py         # Glances API integration for host resource stats
├── logs.py            # Image digest snapshots (dockerfarm-log.toml)
├── operations.py      # Business logic (up, migrate, discover, preflight checks)
├── paths.py           # Path utilities, config file discovery
├── registry.py        # Container registry client for update checking
├── ssh_keys.py        # SSH key path constants and utilities
├── state.py           # Deployment state tracking (which stack on which host)
├── traefik.py         # Traefik file-provider config generation from labels
└── web/               # Web UI (FastAPI + HTMX)

Web UI Icons

Icons use Lucide. Add new icons as macros in web/templates/partials/icons.html by copying SVG paths from their site. The action_btn, stat_card, and collapse macros in components.html accept an optional icon parameter.

HTMX Patterns

  • Multi-element refresh: Use custom events, not hx-swap-oob. Elements have hx-trigger="cf:refresh from:body" and JS calls document.body.dispatchEvent(new CustomEvent('cf:refresh')). Simpler to debug/test.
  • SPA navigation: Sidebar uses hx-boost="true" to AJAX-ify links.
  • Attribute inheritance: Set hx-target/hx-swap on parent elements.

Key Design Decisions

  1. Hybrid SSH approach: asyncssh for parallel streaming with prefixes; native ssh -t for raw mode (progress bars)
  2. Parallel by default: Multiple stacks run concurrently via asyncio.gather
  3. Streaming output: Real-time stdout/stderr with [stack] prefix using Rich
  4. SSH key auth only: Uses ssh-agent, no password handling (YAGNI)
  5. NFS assumption: Compose files at same path on all hosts
  6. Local IP auto-detection: Skips SSH when target host matches local machine's IP
  7. State tracking: Tracks where stacks are deployed for auto-migration
  8. Pre-flight checks: Verifies NFS mounts and Docker networks exist before starting/migrating

Code Style

  • Imports at top level: Never add imports inside functions unless they are explicitly marked with # noqa: PLC0415 and a comment explaining it speeds up CLI startup. Heavy modules like pydantic, yaml, and rich.table are lazily imported to keep cf --help fast.

Development Commands

Use just for common tasks. Run just to list available commands:

Command Description
just install Install dev dependencies
just test Run all tests
just test-cli Run CLI tests (parallel)
just test-web Run web UI tests (parallel)
just lint Lint, format, and type check
just web Start web UI (port 9001)
just doc Build and serve docs (port 9002)
just clean Clean build artifacts

Testing

Run tests with just test or uv run pytest. Browser tests require Chromium (system-installed or via playwright install chromium):

# Unit tests only (parallel)
uv run pytest -m "not browser" -n auto

# Browser tests only (parallel)
uv run pytest -m browser -n auto

# All tests
uv run pytest

Browser tests are marked with @pytest.mark.browser. They use Playwright to test HTMX behavior, JavaScript functionality (sidebar filter, command palette, terminals), and content stability during navigation.

Communication Notes

  • Clarify ambiguous wording (e.g., homophones like "right"/"write", "their"/"there").

Git Safety

  • Never amend commits.
  • NEVER merge anything into main. Always commit directly or use fast-forward/rebase.
  • Never force push.

SSH Agent in Remote Sessions

When pushing to GitHub via SSH fails with "Permission denied (publickey)", fix the SSH agent socket:

# Find and set the correct SSH agent socket
SSH_AUTH_SOCK=$(ls -t ~/.ssh/agent/s.*.sshd.* 2>/dev/null | head -1) git push origin branch-name

This is needed because the SSH agent socket path changes between sessions.

Pull Requests

  • Never include unchecked checklists (e.g., - [ ] ...) in PR descriptions. Either omit the checklist or use checked items.
  • NEVER run gh pr merge. PRs are merged via the GitHub UI, not the CLI.

Releases

Use gh release create to create releases. The tag is created automatically.

# IMPORTANT: Ensure you're on latest origin/main before releasing!
git fetch origin
git checkout origin/main

# Check current version
git tag --sort=-v:refname | head -1

# Create release (minor version bump: v0.21.1 -> v0.22.0)
gh release create v0.22.0 --title "v0.22.0" --notes "release notes here"

Versioning:

  • Patch (v0.21.0 → v0.21.1): Bug fixes
  • Minor (v0.21.1 → v0.22.0): New features, non-breaking changes

Write release notes manually describing what changed. Group by features and bug fixes.

Commands Quick Reference

CLI available as cf or compose-farm.

Command Description
up Start stacks (docker compose up -d), auto-migrates if host changed
down Stop stacks (docker compose down). Use --orphaned to stop stacks removed from config
stop Stop services without removing containers (docker compose stop)
pull Pull latest images
restart Restart running containers (docker compose restart)
update Pull, build, recreate only if changed (up -d --pull always --build)
apply Make reality match config: migrate stacks + stop orphans. Use --dry-run to preview
compose Run any docker compose command on a stack (passthrough)
logs Show stack logs
ps Show status of all stacks
stats Show overview (hosts, stacks, pending migrations; --live for container counts)
list List stacks and hosts (--simple for scripting, --host to filter)
refresh Update state from reality: discover running stacks, capture image digests
check Validate config, traefik labels, mounts, networks; show host compatibility
init-network Create Docker network on hosts with consistent subnet/gateway
traefik-file Generate Traefik file-provider config from compose labels
config Manage config files (init, init-env, show, path, validate, edit, symlink)
ssh Manage SSH keys (setup, status, keygen)
web Start web UI server