mirror of
https://github.com/basnijholt/compose-farm.git
synced 2026-02-11 01:22:06 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f32057aa7b | ||
|
|
c3e3aeb538 |
21
.github/workflows/docker.yml
vendored
21
.github/workflows/docker.yml
vendored
@@ -68,16 +68,35 @@ jobs:
|
||||
echo "✗ Timeout waiting for PyPI"
|
||||
exit 1
|
||||
|
||||
- name: Check if latest release
|
||||
id: latest
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
# Get latest release tag from GitHub (strip 'v' prefix)
|
||||
LATEST=$(gh release view --json tagName -q '.tagName' | sed 's/^v//')
|
||||
echo "Building version: $VERSION"
|
||||
echo "Latest release: $LATEST"
|
||||
if [ "$VERSION" = "$LATEST" ]; then
|
||||
echo "is_latest=true" >> $GITHUB_OUTPUT
|
||||
echo "✓ This is the latest release, will tag as :latest"
|
||||
else
|
||||
echo "is_latest=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠ This is NOT the latest release, skipping :latest tag"
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# Only tag as 'latest' if this is the latest release (prevents re-runs of old releases from overwriting)
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=v${{ steps.version.outputs.version }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=v${{ steps.version.outputs.version }}
|
||||
type=semver,pattern={{major}},value=v${{ steps.version.outputs.version }}
|
||||
type=raw,value=latest
|
||||
type=raw,value=latest,enable=${{ steps.latest.outputs.is_latest }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
|
||||
14
CLAUDE.md
14
CLAUDE.md
@@ -17,7 +17,7 @@ src/compose_farm/
|
||||
│ ├── 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 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)
|
||||
@@ -102,6 +102,17 @@ Browser tests are marked with `@pytest.mark.browser`. They use Playwright to tes
|
||||
- **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:
|
||||
|
||||
```bash
|
||||
# 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.
|
||||
@@ -146,6 +157,7 @@ CLI available as `cf` or `compose-farm`.
|
||||
| `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 |
|
||||
|
||||
37
README.md
37
README.md
@@ -395,6 +395,7 @@ Multi-host orchestration that Docker Compose can't do:
|
||||
| `cf traefik-file` | Generate Traefik file-provider config |
|
||||
| `cf config` | Manage config files (init, show, validate, edit, symlink) |
|
||||
| `cf ssh` | Manage SSH keys (setup, status, keygen) |
|
||||
| `cf list` | List all stacks and their assigned hosts |
|
||||
|
||||
### Aliases
|
||||
|
||||
@@ -403,10 +404,11 @@ Short aliases for frequently used commands:
|
||||
| Alias | Command | Alias | Command |
|
||||
|-------|---------|-------|---------|
|
||||
| `cf a` | `apply` | `cf s` | `stats` |
|
||||
| `cf l` | `logs` | `cf c` | `compose` |
|
||||
| `cf l` | `logs` | `cf ls` | `list` |
|
||||
| `cf r` | `restart` | `cf rf` | `refresh` |
|
||||
| `cf u` | `update` | `cf ck` | `check` |
|
||||
| `cf p` | `pull` | `cf tf` | `traefik-file` |
|
||||
| `cf c` | `compose` | | |
|
||||
|
||||
Each command replaces: look up host → SSH → find compose file → run `ssh host "cd /opt/compose/plex && docker compose up -d"`.
|
||||
|
||||
@@ -511,6 +513,7 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
|
||||
│ service. │
|
||||
│ ps Show status of stacks. │
|
||||
│ stats Show overview statistics for hosts and stacks. │
|
||||
│ list List all stacks and their assigned hosts. │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
╭─ Server ─────────────────────────────────────────────────────────────────────╮
|
||||
│ web Start the web UI server. │
|
||||
@@ -1191,6 +1194,38 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>See the output of <code>cf list --help</code></summary>
|
||||
|
||||
<!-- CODE:BASH:START -->
|
||||
<!-- echo '```yaml' -->
|
||||
<!-- export NO_COLOR=1 -->
|
||||
<!-- export TERM=dumb -->
|
||||
<!-- export TERMINAL_WIDTH=90 -->
|
||||
<!-- cf list --help -->
|
||||
<!-- echo '```' -->
|
||||
<!-- CODE:END -->
|
||||
<!-- OUTPUT:START -->
|
||||
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
|
||||
```yaml
|
||||
|
||||
Usage: cf list [OPTIONS]
|
||||
|
||||
List all stacks and their assigned hosts.
|
||||
|
||||
╭─ Options ────────────────────────────────────────────────────────────────────╮
|
||||
│ --host -H TEXT Filter to stacks on this host │
|
||||
│ --simple -s Plain output (one stack per line, for scripting) │
|
||||
│ --config -c PATH Path to config file │
|
||||
│ --help -h Show this message and exit. │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
```
|
||||
|
||||
<!-- OUTPUT:END -->
|
||||
|
||||
</details>
|
||||
|
||||
**Server**
|
||||
|
||||
<details>
|
||||
|
||||
@@ -23,6 +23,7 @@ Commands are either **Docker Compose wrappers** (`up`, `down`, `stop`, `restart`
|
||||
| **Monitoring** | `ps` | Show stack status |
|
||||
| | `logs` | Show stack logs |
|
||||
| | `stats` | Show overview statistics |
|
||||
| | `list` | List stacks and hosts |
|
||||
| **Configuration** | `check` | Validate config and mounts |
|
||||
| | `refresh` | Sync state from reality |
|
||||
| | `init-network` | Create Docker network |
|
||||
@@ -45,10 +46,11 @@ Short aliases for frequently used commands:
|
||||
| Alias | Command | Alias | Command |
|
||||
|-------|---------|-------|---------|
|
||||
| `cf a` | `apply` | `cf s` | `stats` |
|
||||
| `cf l` | `logs` | `cf c` | `compose` |
|
||||
| `cf l` | `logs` | `cf ls` | `list` |
|
||||
| `cf r` | `restart` | `cf rf` | `refresh` |
|
||||
| `cf u` | `update` | `cf ck` | `check` |
|
||||
| `cf p` | `pull` | `cf tf` | `traefik-file` |
|
||||
| `cf c` | `compose` | | |
|
||||
|
||||
---
|
||||
|
||||
@@ -466,6 +468,40 @@ cf stats --live
|
||||
|
||||
---
|
||||
|
||||
### cf list
|
||||
|
||||
List all stacks and their assigned hosts.
|
||||
|
||||
```bash
|
||||
cf list [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--host, -H TEXT` | Filter to stacks on this host |
|
||||
| `--simple, -s` | Plain output for scripting (one stack per line) |
|
||||
| `--config, -c PATH` | Path to config file |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# List all stacks
|
||||
cf list
|
||||
|
||||
# Filter by host
|
||||
cf list --host nas
|
||||
|
||||
# Plain output for scripting
|
||||
cf list --simple
|
||||
|
||||
# Combine: list stack names on a specific host
|
||||
cf list --host nuc --simple
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Commands
|
||||
|
||||
### cf check
|
||||
|
||||
@@ -203,6 +203,43 @@ def stats(
|
||||
console.print(_build_summary_table(cfg, state, pending))
|
||||
|
||||
|
||||
@app.command("list", rich_help_panel="Monitoring")
|
||||
def list_(
|
||||
host: HostOption = None,
|
||||
simple: Annotated[
|
||||
bool,
|
||||
typer.Option("--simple", "-s", help="Plain output (one stack per line, for scripting)"),
|
||||
] = False,
|
||||
config: ConfigOption = None,
|
||||
) -> None:
|
||||
"""List all stacks and their assigned hosts."""
|
||||
cfg = load_config_or_exit(config)
|
||||
|
||||
stacks: list[tuple[str, str | list[str]]] = list(cfg.stacks.items())
|
||||
if host:
|
||||
stacks = [(s, h) for s, h in stacks if str(h) == host or host in str(h).split(",")]
|
||||
|
||||
if simple:
|
||||
for stack, _ in sorted(stacks):
|
||||
console.print(stack)
|
||||
else:
|
||||
# Assign colors to hosts for visual grouping
|
||||
host_colors = ["magenta", "cyan", "green", "yellow", "blue", "red"]
|
||||
unique_hosts = sorted({str(h) for _, h in stacks})
|
||||
host_color_map = {h: host_colors[i % len(host_colors)] for i, h in enumerate(unique_hosts)}
|
||||
|
||||
table = Table(title="Stacks", show_header=True, header_style="bold cyan")
|
||||
table.add_column("Stack")
|
||||
table.add_column("Host")
|
||||
|
||||
for stack, host_val in sorted(stacks):
|
||||
color = host_color_map.get(str(host_val), "white")
|
||||
table.add_row(f"[{color}]{stack}[/]", f"[{color}]{host_val}[/]")
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
# Aliases (hidden from help)
|
||||
app.command("l", hidden=True)(logs) # cf l = cf logs
|
||||
app.command("ls", hidden=True)(list_) # cf ls = cf list
|
||||
app.command("s", hidden=True)(stats) # cf s = cf stats
|
||||
|
||||
Reference in New Issue
Block a user