Commit Graph

406 Commits

Author SHA1 Message Date
Bas Nijholt
0f84864a06 web: Show host name in live-stats error messages (#170)
Error rows now include the host name (e.g., "nuc: Connection refused")
and have proper id/data-host attributes so they get replaced in place
instead of accumulating on each refresh interval.
v1.18.1
2026-01-18 20:08:39 +00:00
Bas Nijholt
9c72e0937a web: Add clear button to sidebar filter (#168) v1.18.0 2026-01-18 00:11:58 +01:00
Bas Nijholt
74cc2f3245 fix: add COLUMNS and _TYPER_FORCE_DISABLE_TERMINAL for consistent output (#167) 2026-01-16 22:07:41 +01:00
Bas Nijholt
940bd9585a fix: Detect stale NFS mounts in path existence check (#166) v1.17.0 2026-01-15 12:53:21 +01:00
Bas Nijholt
dd60af61a8 docs: Add Plausible analytics (#165) 2026-01-12 17:23:08 +01:00
Bas Nijholt
2f3720949b Fix compose file resolution on remote hosts (#164) v1.16.0 2026-01-11 00:22:55 +01:00
Bas Nijholt
1e3b1d71ed Drop CF_LOCAL_HOST; limit web-stack inference to containers (#163)
* config: Add local_host and web_stack options

Allow configuring local_host and web_stack in compose-farm.yaml instead
of requiring environment variables. This makes it easier to deploy the
web UI with just a config file mount.

- local_host: specifies which host is "local" for Glances connectivity
- web_stack: identifies the web UI stack for self-update detection

Environment variables (CF_LOCAL_HOST, CF_WEB_STACK) still work as
fallback for backwards compatibility.

Closes #152

* docs: Clarify glances_stack is used by CLI and web UI

* config: Env vars override config, add docs

- Change precedence: environment variables now override config values
  (follows 12-factor app pattern)
- Document all CF_* environment variables in configuration.md
- Update example-config.yaml to mention env var overrides

* config: Consolidate env vars, prefer config options

- Update docker-compose.yml to comment out CF_WEB_STACK and CF_LOCAL_HOST
  (now prefer setting in compose-farm.yaml)
- Update init-env to comment out CF_LOCAL_HOST (can be set in config)
- Update docker-deployment.md with new "Config option" column
- Simplify troubleshooting to prefer config over env vars

* config: Generate CF_LOCAL_HOST with config alternative note

Instead of commenting out CF_LOCAL_HOST, generate it normally but add
a note in the comment that it can also be set as 'local_host' in config.

* config: Extend local_host to all web UI operations

When running the web UI in a Docker container, is_local() can't detect
which host the container is on due to different network namespaces.

Previously local_host/CF_LOCAL_HOST only affected Glances connectivity.
Now it also affects:
- Container exec/shell (runs locally instead of via SSH)
- File editing (uses local filesystem instead of SSH)

Added is_local_host() helper that checks CF_LOCAL_HOST/config.local_host
first, then falls back to is_local() detection.

* refactor: DRY get_web_stack helper, add tests

- Move get_web_stack to deps.py to avoid duplication in streaming.py
  and actions.py
- Add tests for config.local_host and config.web_stack parsing
- Add tests for is_local_host, get_web_stack, and get_local_host helpers
- Tests verify env var precedence over config values

* glances: rely on CF_WEB_STACK for container mode

Restore docker-compose env defaults and document local_host scope.

* web: ignore local_host outside container

Document container-only behavior and adjust tests.

* web: infer local host from web_stack

Drop local_host config option and update docs/tests.

* Remove CF_LOCAL_HOST override

* refactor: move web_stack helpers to Config class

- Add get_web_stack() and get_local_host_from_web_stack() as Config methods
- Remove duplicate _get_local_host_from_web_stack() from glances.py and deps.py
- Update deps.py get_web_stack() to delegate to Config method
- Add comprehensive tests for the new Config methods

* config: remove web_stack config option

The web_stack config option was redundant since:
- In Docker, CF_WEB_STACK env var is always set
- Outside Docker, the container-specific behavior is disabled anyway

Simplify by only using the CF_WEB_STACK environment variable.

* refactor: remove get_web_stack wrapper from deps

Callers now use config.get_web_stack() directly instead of
going through a pointless wrapper function.

* prompts: add rule to identify pointless wrapper functions
2026-01-10 10:48:35 +01:00
Bas Nijholt
c159549a9e web: Fix Glances connection for local host in container rows endpoint (#161) v1.15.1 2026-01-08 13:04:14 +01:00
Bas Nijholt
d65f4cf7f4 cli: Add --containers flag to stats command (#159)
* fix: Ignore _version.py in type checkers

The _version.py file is generated at build time by hatchling,
so mypy and ty can't resolve it during development.

* Update README.md

* cli: Respect --host flag in stats summary and add tests

- Fix --host filter to work in non-containers mode (was ignored)
- Filter hosts table, pending migrations, and --live queries by host
- Add tests for stats --containers functionality

* refactor: Remove redundant _format_bytes wrappers

Use format_bytes directly from glances module instead of wrapper
functions that add no value.

* Fix stats --host filtering

* refactor: Move validate_hosts to top-level imports
v1.15.0
2026-01-08 00:05:30 +01:00
Bas Nijholt
7ce2067fcb cli: Default init-env output to current directory (#160)
Previously `cf config init-env` created the .env file next to the
compose-farm.yaml config file. This was unintuitive when working in
stack subdirectories - users expected the file in their current
directory.

Now the default is to create .env in the current working directory,
which matches typical CLI tool behavior. Use `-o /path/to/.env` to
specify a different location.
2026-01-07 23:26:00 +01:00
Bas Nijholt
f32057aa7b cli: Add list command with ls alias (#158) v1.14.0 2026-01-07 15:59:30 +01:00
Bas Nijholt
c3e3aeb538 ci: Only tag as :latest when building the latest release (#157) 2026-01-07 15:30:24 +01:00
Bas Nijholt
009f3b1403 web: Fix theme resetting to first theme in list (#156) v1.13.2 2026-01-07 15:01:34 +01:00
Bas Nijholt
51f74eab42 examples: Add CoreDNS for *.local domain resolution (#155)
* examples: Add CoreDNS for *.local domain resolution

Adds a CoreDNS example that resolves *.local to the Traefik host,
making the .local routes in all examples work out of the box.

Also removes the redundant Multi-Container Stacks section from
README since paperless-ngx already demonstrates this pattern.

* examples: Add coredns .env file
2026-01-07 12:29:53 +01:00
Bas Nijholt
4acf797128 examples: Update paperless-ngx to use PostgreSQL (#153)
Match the real-world setup with Redis + PostgreSQL + App.
Remove NFS + PostgreSQL warning since it works fine in practice.
2026-01-07 03:23:34 -08:00
Andi Powers-Holmes
d167da9d63 Fix external network name parsing (#152)
* fix: external network name parsing

Compose network definitions may have a "name" field defining the actual network name,
which may differ from the key used in the compose file e.g. when overriding the default
compose network, or using a network name containing special characters that are not valid YAML keys.

Fix: check for "name" field on definition and use that if present, else fall back to key.

* tests: Add test for external network name field parsing

Covers the case where a network definition has a "name" field that
differs from the YAML key (e.g., default key with name: compose-net).

---------

Co-authored-by: Bas Nijholt <bas@nijho.lt>
v1.13.1
2026-01-07 02:48:35 -08:00
Bas Nijholt
a5eac339db compose: Quote arguments with shlex to preserve spaces (#151) 2026-01-06 15:37:55 +01:00
Bas Nijholt
9f3813eb72 docs: Add missing source files to architecture docs (#150) 2026-01-06 13:07:20 +01:00
Bas Nijholt
b9ae0ad4d5 docs: Add missing options, aliases, and config settings (#149)
- Add --pull and --build options to cf up (from #146)
- Add --no-strays option to cf apply
- Add command aliases section (a, l, r, u, p, s, c, rf, ck, tf)
- Add cf config init-env subcommand documentation
- Add glances_stack config option (from #124)
- Add Host Resource Monitoring section to architecture docs
2026-01-06 11:06:24 +01:00
Bas Nijholt
ca2a4dd6d9 cli: Add short command aliases (#148)
* cli: Add short command aliases

Add single and two-letter aliases for frequently used commands:

- a  → apply
- l  → logs
- r  → restart
- u  → update
- p  → pull
- s  → stats
- c  → compose
- rf → refresh
- ck → check
- tf → traefik-file

Aliases are hidden from --help to keep output clean.

* docs: Document command aliases in README
v1.13.0
2026-01-05 18:46:57 +01:00
Bas Nijholt
fafdce5736 docs: Clarify Docker Compose vs Compose Farm commands (#147)
* docs: Clarify Docker Compose vs Compose Farm commands

Split the Usage section into two tables:
- Docker Compose Commands: wrappers with multi-host additions
- Compose Farm Commands: orchestration Docker Compose can't do

Also update the `update` command docstring to clarify it's
a shorthand for `up --pull --build`.

* chore(docs): update TOC

* docs: Add command type distinction to commands.md

Explain that commands are either Docker Compose wrappers with
multi-host superpowers, or Compose Farm originals for orchestration.
Also update `update` description to clarify it's a shorthand.

* Update README.md
2026-01-05 18:37:41 +01:00
Bas Nijholt
6436becff9 up: Add --pull and --build flags for Docker Compose parity (#146)
* up: Add --pull and --build flags for Docker Compose parity

Add `--pull` and `--build` options to `cf up` to match Docker Compose
naming conventions. This allows users to pull images or rebuild before
starting without using the separate `update` command.

- `cf up --pull` adds `--pull always` to the compose command
- `cf up --build` adds `--build` to the compose command
- Both flags work together: `cf up --pull --build`

The `update` command remains unchanged as a convenience wrapper.

* Update README.md

* up: Run stacks in parallel when no migration needed

Refactor up_stacks to categorize stacks and run them appropriately:
- Simple stacks (no migration): run in parallel via asyncio.gather
- Multi-host stacks: run in parallel
- Migration stacks: run sequentially for clear output and rollback

This makes `cf up --all` as fast as `cf update --all` for typical use.

* refactor: DRY up command building with build_up_cmd helper

Consolidate all 'up -d' command construction into a single helper
function. Now used by up, update, and operations module.

Added tests for the helper function.

* update: Delegate to up --pull --build

Simplify update command to just call up with pull=True and build=True.
This removes duplication and ensures consistent behavior.
v1.12.0
2026-01-05 15:55:00 +01:00
Bas Nijholt
3460d8a3ea restart: Match Docker Compose semantics (#145)
* restart: Match Docker Compose semantics

Change `cf restart` from doing `down + up` to using `docker compose
restart`, matching the Docker Compose command behavior.

This provides command naming parity with Docker Compose. Users who want
the old behavior can use `cf down mystack && cf up mystack`.

- Update restart implementation to use `docker compose restart`
- Remove traefik regeneration from restart (no longer recreates containers)
- Update all documentation and help text
- Remove restart from self-update SSH handling (no longer involves down)

* web: Clarify Update tooltip uses 'recreate' not 'restart'

Avoid confusion now that 'restart' means something different.

* web: Fix Update All tooltip to use 'recreates'
v1.11.0
2026-01-05 14:29:03 +01:00
Bas Nijholt
8dabc27272 update: Only restart containers when images change (#143)
* update: Only restart containers when images change

Use `up -d --pull always --build` instead of separate pull/build/down/up
steps. This avoids unnecessary container restarts when images haven't
changed.

* Update README.md

* docs: Update update command description across all docs

Reflect new behavior: only recreates containers if images changed.

* Update README.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
v1.10.0
2026-01-05 10:06:45 +01:00
Bas Nijholt
5e08f1d712 web: Exclude web stack from Update All button (#142) 2026-01-04 19:56:41 +01:00
Bas Nijholt
8302f1d97a Unify vendor assets configuration in single JSON file (#141) v1.9.0 2025-12-31 19:02:41 +01:00
Bas Nijholt
eac9338352 Sort web stack last in bulk operations to prevent self-restart interruption (#140) v1.8.3 2025-12-31 10:10:42 +01:00
Bas Nijholt
667931dc80 docs: Add release checklist to ensure on latest main (#139) 2025-12-30 08:01:19 +01:00
Bas Nijholt
5890221528 Mark package as Linux/macOS only in classifiers (#138) v1.8.2 2025-12-30 07:54:56 +01:00
Bas Nijholt
c8fc3c2496 Remove Windows workarounds (#137) 2025-12-30 07:51:51 +01:00
Bas Nijholt
ffb7a32402 Fix Glances connectivity when web UI runs in Docker container (#135) 2025-12-30 05:35:24 +01:00
Bas Nijholt
beb1630fcf Deduplicate compose parsing (#136) 2025-12-28 17:00:32 +01:00
Bas Nijholt
2af48b2642 feat(web): add Glances integration for host resource stats (#124) v1.8.0 2025-12-28 08:37:57 +01:00
Bas Nijholt
f69993eac8 web: Rename command palette entry to "GitHub Repo" (#134)
Makes the entry searchable by typing "github" in the command palette.
2025-12-28 07:06:32 +01:00
Bas Nijholt
9bdcd143cf Prioritize dedicated SSH key over agent (#133) 2025-12-24 22:34:53 -08:00
Bas Nijholt
9230e12eb0 fix: Make SSH agent socket optional in docker-compose.yml (#132) 2025-12-24 12:22:01 -08:00
Bas Nijholt
2a923e6e81 fix: Include field name in config validation error messages (#131)
Previously, Pydantic validation errors like "Extra inputs are not
permitted" didn't show which field caused the error. Now the error
message includes the field location (e.g., "unknown_key: Extra inputs
are not permitted").
v1.7.0
2025-12-22 22:35:19 -08:00
Bas Nijholt
5f2e081298 perf: Batch snapshot collection to 1 SSH call per host (#130)
## Summary

Optimize `cf refresh` SSH calls from O(stacks) to O(hosts):
- Discovery: 1 SSH call per host (unchanged)
- Snapshots: 1 SSH call per host (was 1 per stack)

For 50 stacks across 4 hosts: 54 → 8 SSH calls.

## Changes

**Performance:**
- Use `docker ps` + `docker image inspect` instead of `docker compose images` per stack
- Batch snapshot collection by host in `collect_stacks_entries_on_host()`

**Architecture:**
- Add `build_discovery_results()` to `operations.py` (business logic)
- Keep progress bar wrapper in `cli/management.py` (presentation)
- Remove dead code: `discover_all_stacks_on_all_hosts()`, `collect_all_stacks_entries()`
2025-12-22 22:19:32 -08:00
Bas Nijholt
6fbc7430cb perf: Optimize stray detection to use 1 SSH call per host (#129)
* perf: Optimize stray detection to use 1 SSH call per host

Previously, stray detection checked each stack on each host individually,
resulting in (stacks * hosts) SSH calls. For 50 stacks across 4 hosts,
this meant ~200 parallel SSH connections, causing "Connection lost" errors.

Now queries each host once for all running compose projects using:
  docker ps --format '{{.Label "com.docker.compose.project"}}' | sort -u

This reduces SSH calls from ~200 to just 4 (one per host).

Changes:
- Add get_running_stacks_on_host() in executor.py
- Add discover_all_stacks_on_all_hosts() in operations.py
- Update _discover_stacks_full() to use the batch approach

* Remove unused function and add tests

- Remove discover_stack_on_all_hosts() which is no longer used
- Add tests for get_running_stacks_on_host()
- Add tests for discover_all_stacks_on_all_hosts()
  - Verifies it returns correct StackDiscoveryResult
  - Verifies stray detection works
  - Verifies it makes only 1 call per host (not per stack)
2025-12-22 12:09:59 -08:00
Bas Nijholt
6fdb43e1e9 Add self-healing: detect and stop stray containers (#128)
* Add self-healing: detect and stop rogue containers

Adds the ability to detect and stop "rogue" containers - stacks running
on hosts they shouldn't be according to config.

Changes:
- `cf refresh`: Now scans ALL hosts and warns about rogues/duplicates
- `cf apply`: Stops rogue containers before migrations (new phase)
- New `--no-rogues` flag to skip rogue detection

Implementation:
- Add StackDiscoveryResult for full host scanning results
- Add discover_stack_on_all_hosts() to check all hosts in parallel
- Add stop_rogue_stacks() to stop containers on unauthorized hosts
- Update tests to include new no_rogues parameter

* Update README.md

* fix: Update refresh tests for _discover_stacks_full return type

The function now returns a tuple (discovered, rogues, duplicates)
for rogue/duplicate detection. Update test mocks accordingly.

* Rename "rogue" terminology to "stray" for consistency

Terminology update across the codebase:
- rogue_hosts -> stray_hosts
- is_rogue -> is_stray
- stop_rogue_stacks -> stop_stray_stacks
- _discover_rogues -> _discover_strays
- --no-rogues -> --no-strays
- _report_rogue_stacks -> _report_stray_stacks

"Stray" better complements "orphaned" (both evoke lost things)
while clearly indicating the stack is running somewhere it
shouldn't be.

* Update README.md

* Move asyncio import to top level

* Fix remaining rogue -> stray in docstrings and README

* Refactor: Extract shared helpers to reduce duplication

1. Extract _stop_stacks_on_hosts helper in operations.py
   - Shared by stop_orphaned_stacks and stop_stray_stacks
   - Reduces ~50 lines of duplicated code

2. Refactor _discover_strays to reuse _discover_stacks_full
   - Removes duplicate discovery logic from lifecycle.py
   - Calls management._discover_stacks_full and merges duplicates

* Add PR review prompt

* Fix typos in PR review prompt

* Move import to top level (no in-function imports)

* Update README.md

* Remove obvious comments
v1.6.0
2025-12-22 10:22:09 -08:00
Bas Nijholt
620e797671 fix: Add entrypoint to create passwd entry for non-root users (#127) v1.5.2 2025-12-22 07:31:59 -08:00
Bas Nijholt
031a2af6f3 fix: Correct SSH key volume mount path in docker-compose.yml (#126) v1.5.1 2025-12-22 06:55:59 -08:00
Bas Nijholt
f69eed7721 docs(readme): position as Dockge for multi-host (#123)
* docs(readme): position as Dockge for multi-host

- Reference Dockge (which we've used) instead of Portainer
- Move Portainer mention to "Your files" bullet as contrast
- Link to Dockge repo

* docs(readme): add agentless bullet, link Dockge

- Add "Agentless" bullet highlighting SSH-only approach
- Link to Dockge as contrast (they require agents for multi-host)
- Update NOTE to focus on agentless, CLI-first positioning
2025-12-21 23:28:26 -08:00
Bas Nijholt
5a1fd4e29f docs(readme): add value propositions and fix image URL (#122)
- Add bullet points highlighting key benefits after NOTE block
- Update NOTE to position as file-based Portainer alternative
- Fix hero image URL from http to https
- Add alt text to hero image for accessibility
2025-12-21 23:17:18 -08:00
Bas Nijholt
26dea691ca feat(docker): make container user configurable via CF_UID/CF_GID (#118)
* feat(docker): make container user configurable via CF_UID/CF_GID

Add support for running compose-farm containers as a non-root user
to preserve file ownership on mounted volumes. This prevents files
like compose-farm-state.yaml and web UI config edits from being
owned by root on NFS mounts.

Set CF_UID, CF_GID, and CF_HOME environment variables to run as
your user. Defaults to root (0:0) for backwards compatibility.

* docs: document non-root user configuration for Docker

- Add CF_UID/CF_GID/CF_HOME documentation to README and getting-started
- Add XDG config volume mount for backup/log persistence across restarts
- Update SSH volume examples to use CF_HOME variable

* fix(docker): allow non-root user access and add USER env for SSH

- Add `chmod 755 /root` to Dockerfile so non-root users can access
  the installed tool at /root/.local/share/uv/tools/compose-farm
- Add USER environment variable to docker-compose.yml for SSH to work
  when running as non-root (UID not in /etc/passwd)
- Update docs to include CF_USER in the setup instructions
- Support building from local source with SETUPTOOLS_SCM_PRETEND_VERSION

* fix(docker): revert local build changes, keep only chmod 755 /root

Remove the local source build logic that was added during testing.
The only required change is `chmod 755 /root` to allow non-root users
to access the installed tool.

* docs: add .envrc.example for direnv users

* docs: mention direnv option in README and getting-started
v1.5.0
2025-12-21 22:19:40 -08:00
Bas Nijholt
56d64bfe7a fix(web): exclude orphaned stacks from running count (#119)
The dashboard showed "stopped: -1" when orphaned stacks existed because
running_count included stacks in state but removed from config. Now only
stacks that are both in config AND deployed are counted as running.
2025-12-21 21:59:05 -08:00
Bas Nijholt
5ddbdcdf9e docs(demos): update recordings and fix demo scripts (#115) 2025-12-21 19:17:16 -08:00
Bas Nijholt
dd16becad1 feat(web): add Repo command to command palette (#117)
Adds a new "Repo" command that opens the GitHub repository in a new tab,
similar to the existing "Docs" command.
2025-12-21 15:25:04 -08:00
Bas Nijholt
df683a223f fix(web): wait for terminal expand transition before scrolling (#116)
- Extracts generic `expandCollapse(toggle, scrollTarget)` function for reuse with any DaisyUI collapse
- Fixes scrolling when clicking action buttons (pull, logs, etc.) while terminal is collapsed - now waits for CSS transition before scrolling
- Fixes shell via command palette - expands Container Shell and scrolls to actual terminal (not collapse header)
- Fixes scroll position not resetting when navigating via command palette
2025-12-21 15:17:59 -08:00
Bas Nijholt
fdb00e7655 refactor(web): store backups in XDG config directory (#113)
* refactor(web): store backups in XDG config directory

Move file backups from `.backups/` alongside the file to
`~/.config/compose-farm/backups/` (respecting XDG_CONFIG_HOME).
The original file path is mirrored inside to avoid name collisions.

* docs(web): document automatic backup location

* refactor(paths): extract shared config_dir() function

* fix(web): use path anchor for Windows compatibility
2025-12-21 15:08:15 -08:00