mirror of
https://github.com/basnijholt/compose-farm.git
synced 2026-02-03 14:13:26 +00:00
Rename project from sdc to compose-farm
- Package: sdc → compose_farm - CLI command: sdc → compose-farm - Config file: sdc.yaml → compose-farm.yaml - Config path: ~/.config/sdc/ → ~/.config/compose-farm/ - Updated all documentation, tests, and examples
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -37,5 +37,5 @@ ENV/
|
||||
htmlcov/
|
||||
|
||||
# Local config (don't commit real configs)
|
||||
sdc.yaml
|
||||
!examples/sdc.yaml
|
||||
compose-farm.yaml
|
||||
!examples/compose-farm.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SDC Development Guidelines
|
||||
# Compose Farm Development Guidelines
|
||||
|
||||
## Core Principles
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
## Architecture
|
||||
|
||||
```
|
||||
sdc/
|
||||
compose_farm/
|
||||
├── config.py # Pydantic models, YAML loading
|
||||
├── ssh.py # asyncssh execution, streaming
|
||||
└── cli.py # Typer commands
|
||||
@@ -22,6 +22,7 @@ sdc/
|
||||
3. **Streaming output**: Real-time stdout/stderr with `[service]` prefix
|
||||
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 execution**: When host is `localhost`/`local`, skip SSH and run locally
|
||||
|
||||
## Development Notes
|
||||
|
||||
|
||||
32
README.md
32
README.md
@@ -1,27 +1,27 @@
|
||||
# SDC - Simple Distributed Compose
|
||||
# Compose Farm
|
||||
|
||||
A minimal CLI tool to run Docker Compose commands across multiple hosts via SSH.
|
||||
|
||||
## Why SDC?
|
||||
## Why Compose Farm?
|
||||
|
||||
I run 100+ Docker Compose stacks on an LXC container that frequently runs out of memory. I needed a way to distribute services across multiple machines without the complexity of:
|
||||
|
||||
- **Kubernetes**: Overkill for my use case. I don't need pods, services, ingress controllers, or YAML manifests 10x the size of my compose files.
|
||||
- **Docker Swarm**: Effectively in maintenance modeno longer being invested in by Docker.
|
||||
- **Docker Swarm**: Effectively in maintenance mode—no longer being invested in by Docker.
|
||||
|
||||
**SDC is intentionally simple**: one YAML config mapping services to hosts, and a CLI that runs `docker compose` commands over SSH. That's it.
|
||||
**Compose Farm is intentionally simple**: one YAML config mapping services to hosts, and a CLI that runs `docker compose` commands over SSH. That's it.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install sdc
|
||||
pip install compose-farm
|
||||
# or
|
||||
uv pip install sdc
|
||||
uv pip install compose-farm
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `~/.config/sdc/sdc.yaml` (or `./sdc.yaml` in your working directory):
|
||||
Create `~/.config/compose-farm/compose-farm.yaml` (or `./compose-farm.yaml` in your working directory):
|
||||
|
||||
```yaml
|
||||
compose_dir: /opt/compose
|
||||
@@ -47,27 +47,27 @@ Compose files are expected at `{compose_dir}/{service}/docker-compose.yml`.
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
sdc up plex jellyfin
|
||||
sdc up --all
|
||||
compose-farm up plex jellyfin
|
||||
compose-farm up --all
|
||||
|
||||
# Stop services
|
||||
sdc down plex
|
||||
compose-farm down plex
|
||||
|
||||
# Pull latest images
|
||||
sdc pull --all
|
||||
compose-farm pull --all
|
||||
|
||||
# Restart (down + up)
|
||||
sdc restart plex
|
||||
compose-farm restart plex
|
||||
|
||||
# Update (pull + down + up) - the end-to-end update command
|
||||
sdc update --all
|
||||
compose-farm update --all
|
||||
|
||||
# View logs
|
||||
sdc logs plex
|
||||
sdc logs -f plex # follow
|
||||
compose-farm logs plex
|
||||
compose-farm logs -f plex # follow
|
||||
|
||||
# Show status
|
||||
sdc ps
|
||||
compose-farm ps
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Example SDC configuration
|
||||
# Copy to ~/.config/sdc/sdc.yaml or ./sdc.yaml
|
||||
# Example Compose Farm configuration
|
||||
# Copy to ~/.config/compose-farm/compose-farm.yaml or ./compose-farm.yaml
|
||||
|
||||
compose_dir: /opt/compose
|
||||
|
||||
@@ -13,6 +13,9 @@ hosts:
|
||||
# Short form (just address, user defaults to current user)
|
||||
nas02: 192.168.1.11
|
||||
|
||||
# Local execution (no SSH)
|
||||
local: localhost
|
||||
|
||||
services:
|
||||
# Map service names to hosts
|
||||
# Compose file expected at: {compose_dir}/{service}/docker-compose.yml
|
||||
@@ -1,6 +1,6 @@
|
||||
# SDC Examples
|
||||
# Compose Farm Examples
|
||||
|
||||
This folder contains example Docker Compose services for testing SDC locally.
|
||||
This folder contains example Docker Compose services for testing Compose Farm locally.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -8,28 +8,28 @@ This folder contains example Docker Compose services for testing SDC locally.
|
||||
cd examples
|
||||
|
||||
# Check status of all services
|
||||
sdc ps
|
||||
compose-farm ps
|
||||
|
||||
# Pull images
|
||||
sdc pull --all
|
||||
compose-farm pull --all
|
||||
|
||||
# Start hello-world (runs and exits)
|
||||
sdc up hello
|
||||
compose-farm up hello
|
||||
|
||||
# Start nginx (stays running)
|
||||
sdc up nginx
|
||||
compose-farm up nginx
|
||||
|
||||
# Check nginx is running
|
||||
curl localhost:8080
|
||||
|
||||
# View logs
|
||||
sdc logs nginx
|
||||
compose-farm logs nginx
|
||||
|
||||
# Stop nginx
|
||||
sdc down nginx
|
||||
compose-farm down nginx
|
||||
|
||||
# Update all (pull + restart)
|
||||
sdc update --all
|
||||
compose-farm update --all
|
||||
```
|
||||
|
||||
## Services
|
||||
@@ -39,4 +39,4 @@ sdc update --all
|
||||
|
||||
## Config
|
||||
|
||||
The `sdc.yaml` in this directory configures both services to run locally (no SSH).
|
||||
The `compose-farm.yaml` in this directory configures both services to run locally (no SSH).
|
||||
|
||||
11
examples/compose-farm.yaml
Normal file
11
examples/compose-farm.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Example Compose Farm config for local testing
|
||||
# Run from the examples directory: cd examples && compose-farm ps
|
||||
|
||||
compose_dir: .
|
||||
|
||||
hosts:
|
||||
local: localhost
|
||||
|
||||
services:
|
||||
hello: local
|
||||
nginx: local
|
||||
@@ -1,11 +0,0 @@
|
||||
# Example SDC config for local testing
|
||||
# Run from the examples directory: cd examples && sdc ps
|
||||
|
||||
compose_dir: .
|
||||
|
||||
hosts:
|
||||
local: localhost
|
||||
|
||||
services:
|
||||
hello: local
|
||||
nginx: local
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "sdc"
|
||||
name = "compose-farm"
|
||||
version = "0.1.0"
|
||||
description = "Simple Distributed Compose - run docker compose commands across hosts"
|
||||
description = "Compose Farm - run docker compose commands across multiple hosts"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Bas Nijholt", email = "bas@nijho.lt" }
|
||||
@@ -15,14 +15,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
sdc = "sdc.cli:app"
|
||||
compose-farm = "compose_farm.cli:app"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/sdc"]
|
||||
packages = ["src/compose_farm"]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py311"
|
||||
|
||||
3
src/compose_farm/__init__.py
Normal file
3
src/compose_farm/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Compose Farm - run docker compose commands across multiple hosts."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -21,8 +21,8 @@ if TYPE_CHECKING:
|
||||
T = TypeVar("T")
|
||||
|
||||
app = typer.Typer(
|
||||
name="sdc",
|
||||
help="Simple Distributed Compose - run docker compose commands across hosts",
|
||||
name="compose-farm",
|
||||
help="Compose Farm - run docker compose commands across multiple hosts",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
|
||||
@@ -63,12 +63,12 @@ def load_config(path: Path | None = None) -> Config:
|
||||
|
||||
Search order:
|
||||
1. Explicit path if provided
|
||||
2. ./sdc.yaml
|
||||
3. ~/.config/sdc/sdc.yaml
|
||||
2. ./compose-farm.yaml
|
||||
3. ~/.config/compose-farm/compose-farm.yaml
|
||||
"""
|
||||
search_paths = [
|
||||
Path("sdc.yaml"),
|
||||
Path.home() / ".config" / "sdc" / "sdc.yaml",
|
||||
Path("compose-farm.yaml"),
|
||||
Path.home() / ".config" / "compose-farm" / "compose-farm.yaml",
|
||||
]
|
||||
|
||||
if path:
|
||||
@@ -1,3 +0,0 @@
|
||||
"""Simple Distributed Compose - run docker compose commands across hosts."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from sdc.config import Config, Host, load_config
|
||||
from compose_farm.config import Config, Host, load_config
|
||||
|
||||
|
||||
class TestHost:
|
||||
|
||||
@@ -4,8 +4,8 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from sdc.config import Config, Host
|
||||
from sdc.ssh import (
|
||||
from compose_farm.config import Config, Host
|
||||
from compose_farm.ssh import (
|
||||
CommandResult,
|
||||
_is_local,
|
||||
_run_local_command,
|
||||
|
||||
78
uv.lock
generated
78
uv.lock
generated
@@ -124,6 +124,45 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compose-farm"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "asyncssh" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "ruff" },
|
||||
{ name = "types-pyyaml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "asyncssh", specifier = ">=2.14.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "typer", specifier = ">=0.9.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "mypy", specifier = ">=1.19.0" },
|
||||
{ name = "pre-commit", specifier = ">=4.5.0" },
|
||||
{ name = "pytest", specifier = ">=9.0.2" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
||||
{ name = "ruff", specifier = ">=0.14.8" },
|
||||
{ name = "types-pyyaml", specifier = ">=6.0.12.20250915" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.3"
|
||||
@@ -668,45 +707,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdc"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "asyncssh" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "ruff" },
|
||||
{ name = "types-pyyaml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "asyncssh", specifier = ">=2.14.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "typer", specifier = ">=0.9.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "mypy", specifier = ">=1.19.0" },
|
||||
{ name = "pre-commit", specifier = ">=4.5.0" },
|
||||
{ name = "pytest", specifier = ">=9.0.2" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
||||
{ name = "ruff", specifier = ">=0.14.8" },
|
||||
{ name = "types-pyyaml", specifier = ">=6.0.12.20250915" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
|
||||
Reference in New Issue
Block a user