diff --git a/README.md b/README.md index f668a82..5235c4e 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ hosts: stacks: plex: server-1 jellyfin: server-2 - sonarr: server-1 + grafana: server-1 # Multi-host stacks (run on multiple/all hosts) autokuma: all # Runs on ALL configured hosts diff --git a/compose-farm.example.yaml b/compose-farm.example.yaml index c035e20..ae186f0 100644 --- a/compose-farm.example.yaml +++ b/compose-farm.example.yaml @@ -26,5 +26,5 @@ stacks: traefik: server-1 # Traefik runs here plex: server-2 # Stacks on other hosts get file-provider entries jellyfin: server-2 - sonarr: server-1 - radarr: local + grafana: server-1 + nextcloud: local diff --git a/docs/architecture.md b/docs/architecture.md index c013a94..8cd5ca2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -61,7 +61,7 @@ Tracks deployment state in `compose-farm-state.yaml` (stored alongside the confi ```yaml deployed: plex: nuc - sonarr: nuc + grafana: nuc ``` Used for: @@ -207,7 +207,7 @@ Location: `compose-farm-state.yaml` (stored alongside the config file) ```yaml deployed: plex: nuc - sonarr: nuc + grafana: nuc ``` Image digests are stored separately in `dockerfarm-log.toml` (also in the config directory). diff --git a/docs/best-practices.md b/docs/best-practices.md index 1ba1784..e3f66e3 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -221,7 +221,7 @@ Keep config and data separate: /opt/appdata/ # Local: per-host app data ├── plex/ -└── sonarr/ +└── grafana/ ``` ## Performance @@ -235,7 +235,7 @@ Compose Farm runs operations in parallel. For large deployments: cf up --all # Avoid: sequential updates when possible -for svc in plex sonarr radarr; do +for svc in plex grafana nextcloud; do cf update $svc done ``` @@ -249,7 +249,7 @@ SSH connections are reused within a command. For many operations: cf update --all # Multiple commands, multiple connections (slower) -cf update plex && cf update sonarr && cf update radarr +cf update plex && cf update grafana && cf update nextcloud ``` ## Traefik Setup diff --git a/docs/commands.md b/docs/commands.md index 369db38..317d4bc 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -106,7 +106,7 @@ cf up [OPTIONS] [STACKS]... ```bash # Start specific stacks -cf up plex sonarr +cf up plex grafana # Start all stacks cf up --all @@ -359,7 +359,7 @@ cf ps [OPTIONS] [STACKS]... cf ps # Show specific stacks -cf ps plex sonarr +cf ps plex grafana # Filter by host cf ps --host nuc @@ -403,7 +403,7 @@ cf logs plex cf logs -f plex # Show last 50 lines of multiple stacks -cf logs -n 50 plex sonarr +cf logs -n 50 plex grafana # Show last 20 lines of all stacks cf logs --all diff --git a/docs/configuration.md b/docs/configuration.md index 9e8235c..15dd680 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -42,8 +42,8 @@ hosts: # Map stacks to the local host stacks: plex: local - sonarr: local - radarr: local + grafana: local + nextcloud: local ``` ### Multi-host (full example) @@ -69,8 +69,8 @@ hosts: stacks: # Single-host stacks plex: nuc - sonarr: nuc - radarr: hp + grafana: nuc + nextcloud: hp # Multi-host stacks dozzle: all # Run on ALL hosts @@ -94,7 +94,7 @@ compose_dir: /opt/compose ├── plex/ │ ├── docker-compose.yml # or compose.yaml │ └── .env # optional environment file -├── sonarr/ +├── grafana/ │ └── docker-compose.yml └── ... ``` @@ -185,8 +185,8 @@ hosts: ```yaml stacks: plex: nuc - sonarr: nuc - radarr: hp + grafana: nuc + nextcloud: hp ``` ### Multi-Host Stack @@ -229,7 +229,7 @@ For example, if your config is at `~/.config/compose-farm/compose-farm.yaml`, th ```yaml deployed: plex: nuc - sonarr: nuc + grafana: nuc ``` This file records which stacks are deployed and on which host. @@ -373,8 +373,8 @@ hosts: stacks: # Media plex: nuc - sonarr: nuc - radarr: nuc + jellyfin: nuc + immich: nuc # Infrastructure traefik: nuc diff --git a/docs/getting-started.md b/docs/getting-started.md index 79d8d4a..9b7796f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -111,9 +111,9 @@ nas:/volume1/compose /opt/compose nfs defaults 0 0 /opt/compose/ # compose_dir in config ├── plex/ │ └── docker-compose.yml -├── sonarr/ +├── grafana/ │ └── docker-compose.yml -├── radarr/ +├── nextcloud/ │ └── docker-compose.yml └── jellyfin/ └── docker-compose.yml @@ -150,8 +150,8 @@ hosts: stacks: plex: local - sonarr: local - radarr: local + grafana: local + nextcloud: local ``` #### Multi-host example @@ -171,8 +171,8 @@ hosts: # Map stacks to hosts stacks: plex: nuc - sonarr: nuc - radarr: hp + grafana: nuc + nextcloud: hp ``` Each entry in `stacks:` maps to a folder under `compose_dir` that contains a compose file. @@ -211,7 +211,7 @@ Starts all stacks on their assigned hosts. ### Start Specific Stacks ```bash -cf up plex sonarr +cf up plex grafana ``` ### Apply Configuration @@ -250,19 +250,22 @@ Create the compose file: ```bash # On any host (shared storage) -mkdir -p /opt/compose/prowlarr -cat > /opt/compose/prowlarr/docker-compose.yml << 'EOF' +mkdir -p /opt/compose/gitea +cat > /opt/compose/gitea/docker-compose.yml << 'EOF' services: - prowlarr: - image: lscr.io/linuxserver/prowlarr:latest - container_name: prowlarr + gitea: + image: docker.gitea.com/gitea:latest + container_name: gitea environment: - - PUID=1000 - - PGID=1000 + - USER_UID=1000 + - USER_GID=1000 volumes: - - /opt/config/prowlarr:/config + - /opt/config/gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro ports: - - "9696:9696" + - "3000:3000" + - "2222:22" restart: unless-stopped EOF ``` @@ -272,13 +275,13 @@ Add to config: ```yaml stacks: # ... existing stacks - prowlarr: nuc + gitea: nuc ``` Start the stack: ```bash -cf up prowlarr +cf up gitea ``` ### 2. Move a Stack to Another Host diff --git a/docs/index.md b/docs/index.md index 3950417..041e9ff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -76,7 +76,7 @@ hosts: stacks: plex: server-1 jellyfin: server-2 - sonarr: server-1 + grafana: server-1 ``` ```bash @@ -110,8 +110,8 @@ hosts: stacks: plex: nuc - sonarr: nuc - radarr: hp + grafana: nuc + nextcloud: hp ``` See [Configuration](configuration.md) for all options and the full search order. @@ -123,7 +123,7 @@ See [Configuration](configuration.md) for all options and the full search order. cf apply # Start specific stacks -cf up plex sonarr +cf up plex grafana # Check status cf ps diff --git a/docs/reddit-post.md b/docs/reddit-post.md index 4cd59e9..79268df 100644 --- a/docs/reddit-post.md +++ b/docs/reddit-post.md @@ -27,8 +27,8 @@ hosts: stacks: plex: nuc jellyfin: hp - sonarr: nuc - radarr: nuc + grafana: nuc + nextcloud: nuc ``` Then just: diff --git a/docs/traefik.md b/docs/traefik.md index f7a1de1..7a7355b 100644 --- a/docs/traefik.md +++ b/docs/traefik.md @@ -133,7 +133,7 @@ hosts: stacks: traefik: nuc # Traefik runs here plex: hp # Routed via file-provider - sonarr: hp + grafana: hp ``` With `traefik_file` set, these commands auto-regenerate the config: @@ -256,8 +256,8 @@ stacks: traefik: nuc plex: hp jellyfin: nas - sonarr: nuc - radarr: nuc + grafana: nuc + nextcloud: nuc ``` ### /opt/compose/plex/docker-compose.yml @@ -309,7 +309,7 @@ http: - url: http://192.168.1.100:8096 ``` -Note: `sonarr` and `radarr` are NOT in the file because they're on the same host as Traefik (`nuc`). +Note: `grafana` and `nextcloud` are NOT in the file because they're on the same host as Traefik (`nuc`). ## Combining with Existing Config diff --git a/tests/test_traefik.py b/tests/test_traefik.py index b784726..e600c3d 100644 --- a/tests/test_traefik.py +++ b/tests/test_traefik.py @@ -212,22 +212,22 @@ def test_generate_follows_network_mode_service_for_ports(tmp_path: Path) -> None "image": "gluetun", "ports": ["5080:5080", "9696:9696"], }, - "qbittorrent": { - "image": "qbittorrent", + "syncthing": { + "image": "syncthing", "network_mode": "service:vpn", "labels": [ "traefik.enable=true", - "traefik.http.routers.torrent.rule=Host(`torrent.example.com`)", - "traefik.http.services.torrent.loadbalancer.server.port=5080", + "traefik.http.routers.sync.rule=Host(`sync.example.com`)", + "traefik.http.services.sync.loadbalancer.server.port=5080", ], }, - "prowlarr": { - "image": "prowlarr", + "searxng": { + "image": "searxng", "network_mode": "service:vpn", "labels": [ "traefik.enable=true", - "traefik.http.routers.prowlarr.rule=Host(`prowlarr.example.com`)", - "traefik.http.services.prowlarr.loadbalancer.server.port=9696", + "traefik.http.routers.searxng.rule=Host(`searxng.example.com`)", + "traefik.http.services.searxng.loadbalancer.server.port=9696", ], }, } @@ -238,10 +238,10 @@ def test_generate_follows_network_mode_service_for_ports(tmp_path: Path) -> None assert warnings == [] # Both services should get their ports from the vpn service - torrent_servers = dynamic["http"]["services"]["torrent"]["loadbalancer"]["servers"] - assert torrent_servers == [{"url": "http://192.168.1.10:5080"}] - prowlarr_servers = dynamic["http"]["services"]["prowlarr"]["loadbalancer"]["servers"] - assert prowlarr_servers == [{"url": "http://192.168.1.10:9696"}] + sync_servers = dynamic["http"]["services"]["sync"]["loadbalancer"]["servers"] + assert sync_servers == [{"url": "http://192.168.1.10:5080"}] + searxng_servers = dynamic["http"]["services"]["searxng"]["loadbalancer"]["servers"] + assert searxng_servers == [{"url": "http://192.168.1.10:9696"}] def test_parse_external_networks_single(tmp_path: Path) -> None: diff --git a/tests/web/conftest.py b/tests/web/conftest.py index 9e06834..a093349 100644 --- a/tests/web/conftest.py +++ b/tests/web/conftest.py @@ -31,12 +31,12 @@ services: (plex_dir / ".env").write_text("PLEX_CLAIM=claim-xxx\n") # Create another stack - sonarr_dir = compose_path / "sonarr" - sonarr_dir.mkdir() - (sonarr_dir / "compose.yaml").write_text(""" + grafana_dir = compose_path / "grafana" + grafana_dir.mkdir() + (grafana_dir / "compose.yaml").write_text(""" services: - sonarr: - image: linuxserver/sonarr + grafana: + image: grafana/grafana """) return compose_path @@ -58,7 +58,7 @@ hosts: stacks: plex: server-1 - sonarr: server-2 + grafana: server-2 """) # State file must be alongside config file diff --git a/tests/web/test_htmx_browser.py b/tests/web/test_htmx_browser.py index 607bee6..a439ddd 100644 --- a/tests/web/test_htmx_browser.py +++ b/tests/web/test_htmx_browser.py @@ -110,15 +110,15 @@ def test_config(tmp_path_factory: pytest.TempPathFactory) -> Path: """Create test config and compose files. Creates a multi-host, multi-stack config for comprehensive testing: - - server-1: plex (running), sonarr (not started) - - server-2: radarr (running), jellyfin (not started) + - server-1: plex (running), grafana (not started) + - server-2: nextcloud (running), jellyfin (not started) """ tmp: Path = tmp_path_factory.mktemp("data") # Create compose dir with stacks compose_dir = tmp / "compose" compose_dir.mkdir() - for name in ["plex", "sonarr", "radarr", "jellyfin"]: + for name in ["plex", "grafana", "nextcloud", "jellyfin"]: svc = compose_dir / name svc.mkdir() (svc / "compose.yaml").write_text(f"services:\n {name}:\n image: test/{name}\n") @@ -136,14 +136,14 @@ hosts: user: docker stacks: plex: server-1 - sonarr: server-1 - radarr: server-2 + grafana: server-1 + nextcloud: server-2 jellyfin: server-2 """) - # Create state (plex and radarr running, sonarr and jellyfin not started) + # Create state (plex and nextcloud running, grafana and jellyfin not started) (tmp / "compose-farm-state.yaml").write_text( - "deployed:\n plex: server-1\n radarr: server-2\n" + "deployed:\n plex: server-1\n nextcloud: server-2\n" ) return config @@ -233,13 +233,13 @@ class TestHTMXSidebarLoading: # Verify actual stacks from test config appear stacks = page.locator("#sidebar-stacks li") - assert stacks.count() == 4 # plex, sonarr, radarr, jellyfin + assert stacks.count() == 4 # plex, grafana, nextcloud, jellyfin # Check specific stacks are present content = page.locator("#sidebar-stacks").inner_text() assert "plex" in content - assert "sonarr" in content - assert "radarr" in content + assert "grafana" in content + assert "nextcloud" in content assert "jellyfin" in content def test_dashboard_content_persists_after_sidebar_loads( @@ -268,15 +268,15 @@ class TestHTMXSidebarLoading: page.goto(server_url) page.wait_for_selector("#sidebar-stacks", timeout=5000) - # plex and radarr are in state (running) - should have success status + # plex and nextcloud are in state (running) - should have success status plex_item = page.locator("#sidebar-stacks li", has_text="plex") assert plex_item.locator(".status-success").count() == 1 - radarr_item = page.locator("#sidebar-stacks li", has_text="radarr") - assert radarr_item.locator(".status-success").count() == 1 + nextcloud_item = page.locator("#sidebar-stacks li", has_text="nextcloud") + assert nextcloud_item.locator(".status-success").count() == 1 - # sonarr and jellyfin are NOT in state (not started) - should have neutral status - sonarr_item = page.locator("#sidebar-stacks li", has_text="sonarr") - assert sonarr_item.locator(".status-neutral").count() == 1 + # grafana and jellyfin are NOT in state (not started) - should have neutral status + grafana_item = page.locator("#sidebar-stacks li", has_text="grafana") + assert grafana_item.locator(".status-neutral").count() == 1 jellyfin_item = page.locator("#sidebar-stacks li", has_text="jellyfin") assert jellyfin_item.locator(".status-neutral").count() == 1 @@ -334,20 +334,20 @@ class TestDashboardContent: stats = page.locator("#stats-cards").inner_text() - # From test config: 2 hosts, 4 stacks, 2 running (plex, radarr) + # From test config: 2 hosts, 4 stacks, 2 running (plex, nextcloud) assert "2" in stats # hosts count assert "4" in stats # stacks count def test_pending_shows_not_started_stacks(self, page: Page, server_url: str) -> None: - """Pending operations shows sonarr and jellyfin as not started.""" + """Pending operations shows grafana and jellyfin as not started.""" page.goto(server_url) page.wait_for_selector("#pending-operations", timeout=5000) pending = page.locator("#pending-operations") content = pending.inner_text().lower() - # sonarr and jellyfin are not in state, should show as not started - assert "sonarr" in content or "not started" in content + # grafana and jellyfin are not in state, should show as not started + assert "grafana" in content or "not started" in content assert "jellyfin" in content or "not started" in content def test_dashboard_monaco_loads(self, page: Page, server_url: str) -> None: @@ -485,8 +485,8 @@ class TestSidebarFilter: count_badge = page.locator("#sidebar-count") assert "(4)" in count_badge.inner_text() - # Filter to show only stacks containing "arr" (sonarr, radarr) - self._filter_sidebar(page, "arr") + # Filter to show only stacks containing "x" (plex, nextcloud) + self._filter_sidebar(page, "x") # Count should update to (2) assert "(2)" in count_badge.inner_text() @@ -512,14 +512,14 @@ class TestSidebarFilter: # Select server-1 from dropdown page.locator("#sidebar-host-select").select_option("server-1") - # Only plex and sonarr (server-1 stacks) should be visible + # Only plex and grafana (server-1 stacks) should be visible visible = page.locator("#sidebar-stacks li:not([hidden])") assert visible.count() == 2 content = visible.all_inner_texts() assert any("plex" in s for s in content) - assert any("sonarr" in s for s in content) - assert not any("radarr" in s for s in content) + assert any("grafana" in s for s in content) + assert not any("nextcloud" in s for s in content) assert not any("jellyfin" in s for s in content) def test_combined_text_and_host_filter(self, page: Page, server_url: str) -> None: @@ -530,12 +530,12 @@ class TestSidebarFilter: # Filter by server-2 host page.locator("#sidebar-host-select").select_option("server-2") - # Then filter by text "arr" (should match only radarr on server-2) - self._filter_sidebar(page, "arr") + # Then filter by text "next" (should match only nextcloud on server-2) + self._filter_sidebar(page, "next") visible = page.locator("#sidebar-stacks li:not([hidden])") assert visible.count() == 1 - assert "radarr" in visible.first.inner_text() + assert "nextcloud" in visible.first.inner_text() def test_clearing_filter_shows_all_stacks(self, page: Page, server_url: str) -> None: """Clearing filter restores all stacks.""" @@ -606,7 +606,7 @@ class TestCommandPalette: cmd_list = page.locator("#cmd-list").inner_text() # Stacks should appear as navigation options assert "plex" in cmd_list - assert "radarr" in cmd_list + assert "nextcloud" in cmd_list def test_palette_filters_on_input(self, page: Page, server_url: str) -> None: """Typing in palette filters the command list."""