ansible: add a Logdy always-up log-viewer role (browse ~/.ward/agent-logs, closed runs included) #420

Closed
opened 2026-07-01 06:52:15 +00:00 by coilysiren · 2 comments
Owner

Filed from the read-only director surface (she/her).

Goal

Add Logdy as an always-up container on the Mac, mirroring the existing dozzle role, to browse the drained agent-run logs under ~/.ward/agent-logs/ in a web UI - including closed runs, which Dozzle cannot show (it reads the live docker socket only). Kai picked Logdy specifically for this: a filesystem-reading, Dozzle-like browser over the drained tree.

Template: the dozzle role

ansible/roles/dozzle/ is the exact shape to clone:

  • defaults/main.yml - dozzle_enabled: false, dozzle_dir, dozzle_port: 8080.
  • files/compose.yaml - loopback-bound web UI, restart: unless-stopped.
  • tasks/main.yml - docker check -> install compose -> docker compose up -d -> wait_for the port.
  • Enabled via group_vars/mac.yml (per the dozzle defaults comment).

Make a parallel logdy role.

Key differences from dozzle (do NOT just copy)

  1. Reads the filesystem, not the docker socket. Dozzle mounts /var/run/docker.sock:ro. Logdy instead bind-mounts the drained logs read-only: ${HOME}/.ward/agent-logs:/logs:ro. No docker socket.
  2. Different port. 8080 is dozzle's. Use a new default (logdy_port, e.g. 8081), loopback-bound like dozzle (127.0.0.1:${LOGDY_PORT}:<container-port>).
  3. Must serve CLOSED runs, not just tail live ones. This is the whole point (browse dead containers). Logdy's follow is tail-f-style; pin the invocation that full-reads the existing files under /logs (the console.log / transcript.jsonl across every <container>/ subdir) AND tails currently-writing ones. Verify against current Logdy docs (image logdy/logdy per its docs) - do not assume follow alone replays history.
  4. Aggregate per-container into one stream with the container dir as a label/column (Logdy supports parsed columns/sources), so it reads like Dozzle's per-container view but over the filesystem.

Wiring

  • New role ansible/roles/logdy/ (defaults, files/compose.yaml, tasks/main.yml) mirroring dozzle.
  • Enable on the Mac: add logdy_enabled: true (+ logdy_port) wherever dozzle_enabled: true lives (group_vars/mac.yml).
  • Add the role to the same playbook that includes dozzle.
  • Update infra docs/FEATURES.md (the dozzle tasks comment points there) with the Logdy entry.

Security parity

Match dozzle's posture exactly: loopback-only port bind (no LAN exposure) and a read-only mount. The drained transcript.jsonl can carry tool I/O, so loopback-only is the guard - same reason dozzle binds 127.0.0.1 only. Nothing here forwards off-host (that stays the default-OFF WARD_AGENT_TELEMETRY path); this role is local browsing only.

Mode

headless. Self-contained ansible role addition, clear template, direction settled. The one uncertain bit is the exact Logdy container command to full-read + tail the tree - pin it against current Logdy docs and record it in the retrospective.

Filed from the read-only director surface (she/her). ## Goal Add **Logdy** as an always-up container on the Mac, mirroring the existing `dozzle` role, to browse the drained agent-run logs under `~/.ward/agent-logs/` in a web UI - **including closed runs**, which Dozzle cannot show (it reads the live docker socket only). Kai picked Logdy specifically for this: a filesystem-reading, Dozzle-like browser over the drained tree. ## Template: the `dozzle` role `ansible/roles/dozzle/` is the exact shape to clone: - `defaults/main.yml` - `dozzle_enabled: false`, `dozzle_dir`, `dozzle_port: 8080`. - `files/compose.yaml` - loopback-bound web UI, `restart: unless-stopped`. - `tasks/main.yml` - docker check -> install compose -> `docker compose up -d` -> `wait_for` the port. - Enabled via `group_vars/mac.yml` (per the dozzle defaults comment). Make a parallel `logdy` role. ## Key differences from dozzle (do NOT just copy) 1. **Reads the filesystem, not the docker socket.** Dozzle mounts `/var/run/docker.sock:ro`. Logdy instead bind-mounts the drained logs **read-only**: `${HOME}/.ward/agent-logs:/logs:ro`. No docker socket. 2. **Different port.** 8080 is dozzle's. Use a new default (`logdy_port`, e.g. `8081`), loopback-bound like dozzle (`127.0.0.1:${LOGDY_PORT}:<container-port>`). 3. **Must serve CLOSED runs, not just tail live ones.** This is the whole point (browse dead containers). Logdy's `follow` is tail-f-style; **pin the invocation that full-reads the existing files** under `/logs` (the `console.log` / `transcript.jsonl` across every `<container>/` subdir) AND tails currently-writing ones. Verify against current Logdy docs (image `logdy/logdy` per its docs) - do not assume `follow` alone replays history. 4. **Aggregate per-container into one stream** with the container dir as a label/column (Logdy supports parsed columns/sources), so it reads like Dozzle's per-container view but over the filesystem. ## Wiring - New role `ansible/roles/logdy/` (`defaults`, `files/compose.yaml`, `tasks/main.yml`) mirroring dozzle. - Enable on the Mac: add `logdy_enabled: true` (+ `logdy_port`) wherever `dozzle_enabled: true` lives (`group_vars/mac.yml`). - Add the role to the same playbook that includes `dozzle`. - Update infra `docs/FEATURES.md` (the dozzle tasks comment points there) with the Logdy entry. ## Security parity Match dozzle's posture exactly: **loopback-only** port bind (no LAN exposure) and a **read-only** mount. The drained `transcript.jsonl` can carry tool I/O, so loopback-only is the guard - same reason dozzle binds `127.0.0.1` only. Nothing here forwards off-host (that stays the default-OFF `WARD_AGENT_TELEMETRY` path); this role is local browsing only. ## Mode headless. Self-contained ansible role addition, clear template, direction settled. The one uncertain bit is the exact Logdy container command to full-read + tail the tree - pin it against current Logdy docs and record it in the retrospective.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-infrastructure-420 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-01T06:52:16Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-infrastructure-420` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-01T06:52:16Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - new logdy ansible role (build-from-source, follow --full-read over ~/.ward/agent-logs, loopback 8081) landed on main, #420 closed.

The role itself was a clean clone of dozzle's shape, so the wiring (defaults, compose, tasks, sync.yml, group_vars, FEATURES.md) went fast. The real work was the part the issue flagged as uncertain: pinning the container invocation. Two things fought back.

First, the image. The issue assumed logdy/logdy "per its docs" but told me to verify - and it does not exist. No logdy/logdy on Docker Hub, no ghcr package, no docker section in the README. The only documented containerised path is building from the upstream Dockerfile, so I pinned the build context to logdyhq/logdy-core#v0.17.1 and cache it as logdy-local:v0.17.1. Glad the issue said "do not assume," because I nearly did.

Second, follow semantics. Confirmed against the CLI docs: plain follow only tails new lines, so --full-read is mandatory to replay closed runs - it reads existing content then tails, tagging each line by origin file, which gives the per-run view for free. The rough edge: follow takes a fixed file list with no glob or directory-watch, so the set is resolved once at container start. Runs that drain AFTER start need a re-converge / docker compose restart logdy to appear. I filed #421 for live discovery and documented the workaround.

Confidence: medium-high on the role structure and the flags (all doc-verified, pre-commit green). Lower on the exact runtime behaviour, since I could not exec it on the Mac from this container - the build-from-git-context and the empty-tree stdin fallback are both untested against a live docker. Worth a real converge on kais-macbook to confirm the UI comes up on 8081 and the closed runs render. One nit I left deliberate: the $files word-split in the shell command assumes run-dir paths have no spaces (container ids never do), which holds but is the kind of thing that bites if the tree layout ever changes.

WARD-OUTCOME: done - new `logdy` ansible role (build-from-source, follow --full-read over ~/.ward/agent-logs, loopback 8081) landed on main, #420 closed. The role itself was a clean clone of dozzle's shape, so the wiring (defaults, compose, tasks, sync.yml, group_vars, FEATURES.md) went fast. The real work was the part the issue flagged as uncertain: pinning the container invocation. Two things fought back. First, the image. The issue assumed `logdy/logdy` "per its docs" but told me to verify - and it does not exist. No `logdy/logdy` on Docker Hub, no ghcr package, no docker section in the README. The only documented containerised path is building from the upstream Dockerfile, so I pinned the build context to `logdyhq/logdy-core#v0.17.1` and cache it as `logdy-local:v0.17.1`. Glad the issue said "do not assume," because I nearly did. Second, `follow` semantics. Confirmed against the CLI docs: plain `follow` only tails new lines, so `--full-read` is mandatory to replay closed runs - it reads existing content then tails, tagging each line by origin file, which gives the per-run view for free. The rough edge: `follow` takes a fixed file list with no glob or directory-watch, so the set is resolved once at container start. Runs that drain AFTER start need a re-converge / `docker compose restart logdy` to appear. I filed #421 for live discovery and documented the workaround. Confidence: medium-high on the role structure and the flags (all doc-verified, pre-commit green). Lower on the exact runtime behaviour, since I could not exec it on the Mac from this container - the build-from-git-context and the empty-tree stdin fallback are both untested against a live docker. Worth a real converge on kais-macbook to confirm the UI comes up on 8081 and the closed runs render. One nit I left deliberate: the `$files` word-split in the shell command assumes run-dir paths have no spaces (container ids never do), which holds but is the kind of thing that bites if the tree layout ever changes.
Sign in to join this conversation.
No description provided.