Compare commits
6
Commits
f85fce7202
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8c2cf37e9 | ||
|
|
10efff2298 | ||
|
|
503747ffb1 | ||
|
|
ecbcbe693c | ||
|
|
41d5f12df0 | ||
|
|
fa5cbc1ec5 |
No files matched your search
@@ -93,6 +93,10 @@ Every physical host, VM/LXC, and running service in the fleet, re-derived from t
|
|||||||
Packer builds a hardened base image, Terraform provisions each VM from it, and Ansible configures and continuously re-hardens every host after that — three tools, three separate responsibilities, connected by one tag that threads through all three stages instead of one tool trying to do every job. Terraform's part in that link ends at creation — it writes the tags file once, and that file then drives Ansible independently for the rest of that host's life.
|
Packer builds a hardened base image, Terraform provisions each VM from it, and Ansible configures and continuously re-hardens every host after that — three tools, three separate responsibilities, connected by one tag that threads through all three stages instead of one tool trying to do every job. Terraform's part in that link ends at creation — it writes the tags file once, and that file then drives Ansible independently for the rest of that host's life.
|
||||||
→ [docs/iac-pipeline.md](docs/iac-pipeline.md)
|
→ [docs/iac-pipeline.md](docs/iac-pipeline.md)
|
||||||
|
|
||||||
|
### 🎧 Kodi <-> Music Assistant Smart Queue Bridge
|
||||||
|
Kodi stays the only music player, UI, and ratings authority; Music Assistant runs invisibly behind it as a similarity engine, never a player, never writing anything back to the library. A small daemon watches Kodi's live queue and auto-refills it with similar tracks via MA's recommendation API when it runs low. Three real issues surfaced only once it was tested against a live Kodi session — not exercised in isolation — including one where the daemon had silently never worked at all.
|
||||||
|
→ [docs/kodi-music-assistant-bridge.md](docs/kodi-music-assistant-bridge.md)
|
||||||
|
|
||||||
### 🔔 Notifications
|
### 🔔 Notifications
|
||||||
Every alert in the fleet — from apps, from hosts, from security events — funnels through one Node-RED instance and one shared formatter before reaching Slack, so a notification reads the same regardless of which of a dozen-plus sources triggered it. A dedicated role puts every host onto the same pipeline for OS-level health, independent of any single application. One shared endpoint handles every app's webhook regardless of shape — a source field where an app provides one, structural fingerprinting of the payload where it doesn't — instead of standing up a dedicated integration per app.
|
Every alert in the fleet — from apps, from hosts, from security events — funnels through one Node-RED instance and one shared formatter before reaching Slack, so a notification reads the same regardless of which of a dozen-plus sources triggered it. A dedicated role puts every host onto the same pipeline for OS-level health, independent of any single application. One shared endpoint handles every app's webhook regardless of shape — a source field where an app provides one, structural fingerprinting of the payload where it doesn't — instead of standing up a dedicated integration per app.
|
||||||
→ [docs/notifications.md](docs/notifications.md)
|
→ [docs/notifications.md](docs/notifications.md)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 430 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Do not edit this file with editors other than draw.io -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="background: transparent; background-color: transparent; color-scheme: light dark;" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="624px" height="632px" viewBox="0 0 624 632" content="<mxfile host="embed.diagrams.net" scale="1" border="0">
 <diagram name="Refill Cycle" id="0">
 <mxGraphModel dx="1398" dy="830" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="700" math="0" shadow="0">
 <root>
 <mxCell id="0" />
 <mxCell id="1" parent="0" />
 <mxCell id="poll" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" value="Poll each Kodi instance&lt;br&gt;(every 240s)" vertex="1">
 <mxGeometry height="60" width="220" x="340" y="40" as="geometry" />
 </mxCell>
 <mxCell id="active" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" value="Active player + queue&lt;br&gt;length on this instance" vertex="1">
 <mxGeometry height="60" width="220" x="340" y="150" as="geometry" />
 </mxCell>
 <mxCell id="gate" parent="1" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" value="&lt;font style=&quot;font-size: 11px;&quot;&gt;Remaining &amp;gt;&lt;br&gt;REFILL_THRESHOLD (5)?&lt;/font&gt;" vertex="1">
 <mxGeometry height="90" width="190" x="355" y="260" as="geometry" />
 </mxCell>
 <mxCell id="resolve" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;" value="Resolve current track&lt;br&gt;in MA (music/search)" vertex="1">
 <mxGeometry height="60" width="220" x="340" y="400" as="geometry" />
 </mxCell>
 <mxCell id="similar" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;" value="Query similar_tracks&lt;br&gt;(large candidate pool)" vertex="1">
 <mxGeometry height="60" width="220" x="340" y="500" as="geometry" />
 </mxCell>
 <mxCell id="inject" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" value="Filter + translate paths,&lt;br&gt;Playlist.Add up to 10" vertex="1">
 <mxGeometry height="60" width="220" x="340" y="600" as="geometry" />
 </mxCell>
 <mxCell id="e1" edge="1" parent="1" source="poll" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;strokeColor=#333333;" target="active">
 <mxGeometry relative="1" as="geometry" />
 </mxCell>
 <mxCell id="e2" edge="1" parent="1" source="active" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;strokeColor=#333333;" target="gate">
 <mxGeometry relative="1" as="geometry" />
 </mxCell>
 <mxCell id="e3" edge="1" parent="1" source="gate" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;strokeColor=#b85450;exitX=1;exitY=0.5;labelBackgroundColor=#1C1D1E;exitDx=0;exitDy=0;" target="poll" value="&lt;span style=&quot;background-color: light-dark(rgb(255, 255, 255), rgb(28, 29, 30));&quot;&gt;yes &amp;mdash; skip&lt;/span&gt;">
 <mxGeometry relative="1" x="0.0698" y="-33" as="geometry">
 <mxPoint as="offset" />
 <Array as="points">
 <mxPoint x="60Line truncated
|
||||||
|
After Width: | Height: | Size: 162 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<!-- verified: 2026-09-18
|
<!-- verified: 2026-09-18
|
||||||
rocky-docker-lxc@7dd5025 : caddy/Caddyfile
|
rocky-docker-lxc@d225776 : caddy/conf/Caddyfile
|
||||||
caddy-vm@9369f8b : caddy/Caddyfile
|
caddy-vm@c5c3420 : caddy/conf/Caddyfile
|
||||||
trilium : 2wCPV6NXQa3l, 0mu75aAXGH8X, fscYadnUtmD5, QMTh5G7ldlLV, TIbCwYGitboy, Cl4TWBZ7ytYr, 6cJurv76PNDR, nAPJq9A1iuX8, DvKxK0gHXsng
|
trilium : 2wCPV6NXQa3l, 0mu75aAXGH8X, fscYadnUtmD5, QMTh5G7ldlLV, TIbCwYGitboy, Cl4TWBZ7ytYr, 6cJurv76PNDR, nAPJq9A1iuX8, DvKxK0gHXsng
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- verified: 2026-09-18
|
<!-- verified: 2026-09-18
|
||||||
iac@f770e1c : ansible/roles_metadata.yml
|
iac@ae315de : ansible/roles_metadata.yml
|
||||||
ansible@24e1bf0 : roles/os_update, playbooks/os_update_vms.yml, playbooks/os_update_pve.yml, playbooks/os_update_pbs.yml, playbooks/app_update.yml
|
ansible@24e1bf0 : roles/os_update, playbooks/os_update_vms.yml, playbooks/os_update_pve.yml, playbooks/os_update_pbs.yml, playbooks/app_update.yml
|
||||||
trilium : xJRSQovMncQr, 6AbdS9cUE45T, bW84ccRoAJ5i, p45qNP2sIuhA, 6oESvJYzj1at
|
trilium : xJRSQovMncQr, 6AbdS9cUE45T, bW84ccRoAJ5i, p45qNP2sIuhA, 6oESvJYzj1at
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- verified: 2026-09-06
|
<!-- verified: 2026-09-06
|
||||||
iac@49ff427 : ansible/roles/bootstrap_roles/firewall_ssh, ansible/roles/secure_vault/firewall_rich_rules_per_vm, ansible/roles/cis_hardening/password_policy
|
iac@49d9773 : ansible/roles/bootstrap_roles/firewall_ssh, ansible/roles/secure_vault/firewall_rich_rules_per_vm, ansible/roles/cis_hardening/password_policy
|
||||||
trilium : 0mu75aAXGH8X, o3DDWo1v54vI, oEbKGleDKuvb, EpssgVXxqIbE, 0R8GeBmH7boV, TIbCwYGitboy, fscYadnUtmD5, yUJqswizuET2, QMTh5G7ldlLV, jBPOx07B1Y2Z, Cl4TWBZ7ytYr, 6cJurv76PNDR, kwbbLjeI6ody, MSVD5o5HiJ69, kA00FQiQWVem
|
trilium : 0mu75aAXGH8X, o3DDWo1v54vI, oEbKGleDKuvb, EpssgVXxqIbE, 0R8GeBmH7boV, TIbCwYGitboy, fscYadnUtmD5, yUJqswizuET2, QMTh5G7ldlLV, jBPOx07B1Y2Z, Cl4TWBZ7ytYr, 6cJurv76PNDR, kwbbLjeI6ody, MSVD5o5HiJ69, kA00FQiQWVem
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- verified: 2026-09-07
|
<!-- verified: 2026-09-07
|
||||||
ansible@9a4ba6d : roles/homelab_inventory_hardware, roles/homelab_inventory_vms, roles/homelab_inventory_services, playbooks/homelab_inventory_hardware.yml, playbooks/homelab_inventory_vms.yml, playbooks/homelab_inventory_services.yml
|
ansible@9a4ba6d : roles/homelab_inventory_hardware, roles/homelab_inventory_vms, roles/homelab_inventory_services, playbooks/homelab_inventory_hardware.yml, playbooks/homelab_inventory_vms.yml, playbooks/homelab_inventory_services.yml
|
||||||
rocky-docker-lxc@145d3df : caddy/Caddyfile, dashy/docker-compose.yaml
|
rocky-docker-lxc@d225776 : caddy/conf/Caddyfile, dashy/docker-compose.yaml
|
||||||
trilium : O0d4fqw0mr05
|
trilium : O0d4fqw0mr05
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
iac@f770e1c : packer/rocky10-proxmox-template, terraform, ansible/roles_metadata.yml, ansible/playbooks/provisioning.yml, ansible/host_vars/technitium/host_tags.yml, ansible/roles/cis_hardening/password_policy
|
iac@ae315de : packer/rocky10-proxmox-template, terraform, ansible/roles_metadata.yml, ansible/playbooks/provisioning.yml, ansible/host_vars/technitium/host_tags.yml, ansible/roles/cis_hardening/password_policy
|
||||||
trilium : c5JADrRVvjIn, f1uTcvXvq0u3, 7anU4VULpp7n, sovEBDmn16hh, sqVGFejKPOFn, kA00FQiQWVem, MSVD5o5HiJ69, NwwrSew7mP2z, QgviHP8r14t2
|
trilium : c5JADrRVvjIn, f1uTcvXvq0u3, 7anU4VULpp7n, sovEBDmn16hh, sqVGFejKPOFn, kA00FQiQWVem, MSVD5o5HiJ69, NwwrSew7mP2z, QgviHP8r14t2
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<!-- verified: 2026-10-16
|
||||||
|
rocky-docker-lxc@1ede96f : kodi/bridge.py, kodi/docker-compose.yaml, kodi/.env, music-assistant/docker-compose.yaml
|
||||||
|
trilium : Y2VzE58wL8w1
|
||||||
|
-->
|
||||||
|
|
||||||
|
# 🎧 Kodi <-> Music Assistant Smart Queue Bridge
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Kodi stays the only music player, UI, and ratings authority on this fleet — ratings and library metadata live in a shared MariaDB, untouched. Music Assistant runs invisibly behind it as a pure similarity engine, never opened day-to-day, never a player, never writing anything back to the library. A small daemon watches Kodi's live playback queue and auto-refills it with musically similar tracks when it runs low, using MA purely as a recommendation backend.
|
||||||
|
|
||||||
|
## Why This Shape, Not The Obvious One
|
||||||
|
|
||||||
|
Two more obvious designs were deliberately rejected. The first: let MA write ratings and tags back into the audio files directly, so it could double as a second library manager. MA doesn't support this upstream, and even if it did, it would create two competing sources of truth for the same library — exactly the failure mode this design avoids. The second: make Kodi a playback target *for* Music Assistant, so MA could drive playback directly. The one community project for this integration (`MAserver-Kodi-Provider`) is archived and abandoned — betting a working setup on dead third-party code wasn't worth it.
|
||||||
|
|
||||||
|
What's left is the actual design: Kodi remains the single source of truth for everything a listener touches (library, ratings, playback), and MA is reduced to a disposable, stateless recommendation service — it could be deleted and rebuilt from an empty config tomorrow without losing a single rating or play count.
|
||||||
|
|
||||||
|
## How A Refill Cycle Actually Works
|
||||||
|
|
||||||
|
`kodi-ma-bridge` is a small asyncio daemon, deployed as part of the same `kodi` stack as Kodi's MariaDB (not a separate stack — the two were merged once the bridge was built, since the bridge only exists to serve Kodi):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker ps --filter name=kodi --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
|
||||||
|
NAMES STATUS IMAGE
|
||||||
|
kodi-ma-bridge Up 18 minutes python:3.13-slim
|
||||||
|
kodi-mariadb Up 38 hours (healthy) <fleet registry>/mariadb:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Every 60 seconds (`POLL_INTERVAL_SECONDS=60`), it checks each configured Kodi instance:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker exec kodi-ma-bridge env | grep -E "POLL_INTERVAL|REFILL|MA_BASE_URL|KODI_INSTANCES"
|
||||||
|
KODI_INSTANCES=[personal-device-1]:<host>:8115,[personal-device-2]:<host>:8115
|
||||||
|
MA_BASE_URL=http://music-assistant:8095
|
||||||
|
POLL_INTERVAL_SECONDS=60
|
||||||
|
REFILL_COUNT=10
|
||||||
|
REFILL_THRESHOLD=5
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Ask Kodi's JSON-RPC API (`Player.GetActivePlayers`) whether anything's playing. If the instance is unreachable or idle, skip silently — these are personal desktops, not always-on servers, so "nothing there right now" is normal, not an error condition.
|
||||||
|
2. If something's playing, check how many tracks remain after the current position (`Playlist.GetItems` + `Player.GetProperties`). Above `REFILL_THRESHOLD` (5 remaining), stop here — nothing to do yet.
|
||||||
|
3. Below the threshold: read the current track's artist/title and resolve it to an MA library item via `music/search`.
|
||||||
|
4. Ask MA's `music/tracks/similar_tracks` for a large candidate pool, filter out anything already queued or recently added, translate MA's library-relative path to Kodi's SMB path, and push up to `REFILL_COUNT` (10) new tracks straight into the live playlist via `Playlist.Add`.
|
||||||
|
|
||||||
|
That queue-length gate is the one real branch in the whole design — everything else is a straight pipeline — so it's worth seeing as a diagram rather than another paragraph of prose:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Three Real Issues, Found By Actually Testing It
|
||||||
|
|
||||||
|
The bridge sat deployed and silently non-functional for weeks before this. All three of the following were found by doing the one thing that actually proves a system works: playing real music on a real machine and watching what happened, not just exercising the API calls in isolation.
|
||||||
|
|
||||||
|
**1 — the daemon never once successfully resolved a track.** Kodi's own JSON-RPC API wraps every response in a `result` key, so the bridge's generic API-calling helper was written to unwrap it the same way for *every* call, including the ones to MA. MA's REST API doesn't use that envelope at all — it returns the payload flat, at the top level:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s -X POST http://music-assistant:8095/api -H "Content-Type: application/json" \
|
||||||
|
-d '{"message_id":"t","command":"music/search","args":{"search_query":"Oasis - Champagne Supernova","media_types":["track"],"limit":1,"library_only":true}}' \
|
||||||
|
| python3 -c "import json,sys; d=json.load(sys.stdin); print('top-level keys:', list(d.keys())); print('has result key:', 'result' in d)"
|
||||||
|
top-level keys: ['artists', 'albums', 'genres', 'tracks', 'playlists', 'radio', 'audiobooks', 'podcasts', 'sound_effects']
|
||||||
|
has result key: False
|
||||||
|
```
|
||||||
|
|
||||||
|
`data.get("result")` against that shape silently returns `None` every time — no exception, no log line, just a daemon that looked alive and never did anything. Confirmed live against a real Kodi playback session before touching a line of code; fixed by returning MA's response body directly instead of unwrapping a key that was never there.
|
||||||
|
|
||||||
|
**2 — even after the fix, MA only ever offered one real match for a mainstream track.** The bridge asked MA for a small candidate pool (15 tracks) and assumed that was the full ranked result. It isn't — MA runs its own internal similarity-distance filter over whatever raw pool size you request, so most of a small batch gets discarded before it ever reaches the caller:
|
||||||
|
|
||||||
|
```
|
||||||
|
limit=10 -> 1 match
|
||||||
|
limit=15 -> 1 match (the bridge's original request size)
|
||||||
|
limit=20 -> 1 match
|
||||||
|
limit=30 -> 2 matches
|
||||||
|
limit=50 -> 5 matches
|
||||||
|
limit=100 -> 6 matches
|
||||||
|
limit=150 -> 8 matches
|
||||||
|
limit=200 -> 10 matches
|
||||||
|
```
|
||||||
|
|
||||||
|
The real candidate pool for that seed track was there the whole time — a solid run of mid-90s Britpop and alt-rock (The Verve, The Cranberries, Ocean Colour Scene, Beck, Manic Street Preachers) — it just never got a large enough raw pool to survive MA's own filtering. Fixed by requesting 200 candidates up front; the bridge's own `REFILL_COUNT` cap still bounds how many actually get queued.
|
||||||
|
|
||||||
|
**3 — a real blind-spot window between the queue running dry and the bridge noticing.** With both of the above fixed and the daemon genuinely working, the original 4-minute poll interval turned out to be too coarse on its own: during one active listening session the live queue hit 0 remaining tracks twice before the next scheduled poll caught it and refilled —
|
||||||
|
|
||||||
|
```
|
||||||
|
[personal-device] queue low (0 left), added 10 tracks seeded from 'Primal Scream - Shine Like Stars'
|
||||||
|
[personal-device] queue low (0 left), added 10 tracks seeded from 'Kula Shaker - Govinda'
|
||||||
|
```
|
||||||
|
|
||||||
|
`REFILL_THRESHOLD=5` only guarantees a refill *attempt* starts with 5 tracks of runway left — it says nothing about how long that runway lasts in wall-clock time, and a 4-minute gap between checks was long enough, on short tracks, to burn through that runway before the next poll ever ran. Playback itself never actually stopped in practice, but the margin was thinner than the design assumed. Fixed by shortening the poll interval to 60 seconds rather than widening the threshold — each idle poll is a handful of cheap JSON-RPC calls, so checking 4x more often costs effectively nothing.
|
||||||
|
|
||||||
|
## Proof It Actually Works
|
||||||
|
|
||||||
|
Confirmed end-to-end against a live Kodi session, mid-album, queue genuinely running low:
|
||||||
|
|
||||||
|
```
|
||||||
|
[personal-device] queue low (2 left), added 8 tracks seeded from 'Oasis - Champagne Supernova'
|
||||||
|
```
|
||||||
|
|
||||||
|
And the live playlist, read directly from Kodi right after:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s -u <user>:*** -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc":"2.0","method":"Playlist.GetItems","params":{"playlistid":0,"properties":["title","artist"]},"id":1}' \
|
||||||
|
http://<host>:8115/jsonrpc
|
||||||
|
...
|
||||||
|
02. The Verve - Sonnet
|
||||||
|
The Day We Caught the Train
|
||||||
|
Dreams
|
||||||
|
Loser
|
||||||
|
You Stole the Sun From My Heart
|
||||||
|
Movin' On Up
|
||||||
|
Blue Orchid
|
||||||
|
Buck Rogers
|
||||||
|
Reptilia
|
||||||
|
Use Somebody
|
||||||
|
```
|
||||||
|
|
||||||
|
A real, coherent 90s/00s indie-and-alt-rock run, auto-queued with zero manual input, directly behind the Oasis album that was already playing:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Similarity Engine
|
||||||
|
|
||||||
|
Matching is powered by MA's `sonic_analysis` provider (on-device CLAP audio-embedding analysis — no cloud service, no internet dependency for recommendations) feeding the `sonic_similarity` plugin, confirmed enabled against the running library:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker exec music-assistant python3 -c "..."
|
||||||
|
sonic_analysis -> enabled: True
|
||||||
|
sonic_similarity -> enabled: True
|
||||||
|
filesystem_local -> enabled: True
|
||||||
|
```
|
||||||
|
|
||||||
|
The library syncs every 12 hours and diffs the file list, so a replaced file (an upgraded rip, say) is picked up as a new file and the stale entry pruned automatically — no manual re-indexing required.
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
Both the Kodi JSON-RPC password and the MA API token are Vault-sourced via the fleet's standard 3-file docker-secrets pattern (see [`docs/secret-distribution.md`](secret-distribution.md)) — nothing hardcoded in the compose file or the bridge script itself.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
netbird-lxc@13b5d80 : netbird/Caddyfile, netbird/docker-compose.yaml, netbird/config.yaml.ctmpl
|
netbird-lxc@ea8967e : netbird/conf/Caddyfile, netbird/docker-compose.yaml, netbird/config.yaml.ctmpl
|
||||||
iac@f770e1c : ansible/roles/secure_vault/netbird_agent, terraform/containers.tf
|
iac@ae315de : ansible/roles/secure_vault/netbird_agent, terraform/containers.tf
|
||||||
trilium : qWLU4xqYLdJ3, oYuIjaczDrZA, nAPJq9A1iuX8, Cl4TWBZ7ytYr
|
trilium : qWLU4xqYLdJ3, oYuIjaczDrZA, nAPJq9A1iuX8, Cl4TWBZ7ytYr
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -56,9 +56,10 @@ The real path a request takes:
|
|||||||
And this is the actual splitter doing that work — the real config, not a description of the logic:
|
And this is the actual splitter doing that work — the real config, not a description of the logic:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cat Caddyfile
|
$ cat conf/Caddyfile
|
||||||
{
|
{
|
||||||
admin off
|
# Container loopback only (port not published) - `caddy reload` needs it.
|
||||||
|
admin localhost:2019
|
||||||
auto_https off
|
auto_https off
|
||||||
servers {
|
servers {
|
||||||
protocols h1 h2 h2c
|
protocols h1 h2 h2c
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
automation-vm@7c057de : mosquitto
|
automation-vm@7c057de : mosquitto
|
||||||
iac@f770e1c : ansible/roles/monitoring/mqtt_client
|
iac@ae315de : ansible/roles/monitoring/mqtt_client
|
||||||
trilium : tt4idCVJPpZM, EAU5LUwFFQ0E, rGfAb4CddKMi, otSPaU6IfjlP, klrmnlh3Wrnd
|
trilium : tt4idCVJPpZM, EAU5LUwFFQ0E, rGfAb4CddKMi, otSPaU6IfjlP, klrmnlh3Wrnd
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
iac@f770e1c : ansible/roles/monitoring/observability_agent
|
iac@ae315de : ansible/roles/monitoring/observability_agent
|
||||||
trilium : eeiUz4iTEgPL, zquji4XOpw5M, s5tWZshDEpQb, oWr1jJhvqitv
|
trilium : eeiUz4iTEgPL, zquji4XOpw5M, s5tWZshDEpQb, oWr1jJhvqitv
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
ansible@24e1bf0 : host_vars/rocky-docker/docker_stacks.yml, host_vars/netbird/docker_stacks.yml, roles/container_update, playbooks/container_update.yml
|
ansible@24e1bf0 : host_vars/rocky-docker/docker_stacks.yml, host_vars/netbird/docker_stacks.yml, roles/container_update, playbooks/container_update.yml
|
||||||
iac@f770e1c : ansible/roles/secure_vault/vault_agent/defaults/main.yml
|
iac@ae315de : ansible/roles/secure_vault/vault_agent/defaults/main.yml
|
||||||
trilium : dcRUArk5dWH8, bW84ccRoAJ5i, hWiIGZIlvQiN
|
trilium : dcRUArk5dWH8, bW84ccRoAJ5i, hWiIGZIlvQiN
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
iac@f770e1c : ansible/roles/secure_vault/ssh_ca_client, ansible/roles/secure_vault/vault_agent/defaults/main.yml, ansible/roles/secure_vault/SecureVault – Vault Contract.md, ansible/roles/secure_vault/securevault-vault-facts.md
|
iac@ae315de : ansible/roles/secure_vault/ssh_ca_client, ansible/roles/secure_vault/vault_agent/defaults/main.yml, ansible/roles/secure_vault/SecureVault – Vault Contract.md, ansible/roles/secure_vault/securevault-vault-facts.md
|
||||||
ansible@24e1bf0 : roles/app_update/defaults/main.yml
|
ansible@24e1bf0 : roles/app_update/defaults/main.yml
|
||||||
trilium : DvKxK0gHXsng, wovVrgvoDVy2, GW8BMoTKtRBe, 1seDR2nBmxKO
|
trilium : DvKxK0gHXsng, wovVrgvoDVy2, GW8BMoTKtRBe, 1seDR2nBmxKO
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
iac@f770e1c : ansible/roles/secure_vault, ansible/group_vars/all/vault_provisioner.yml, ansible/roles/monitoring/mqtt_client
|
iac@ae315de : ansible/roles/secure_vault, ansible/group_vars/all/vault_provisioner.yml, ansible/roles/monitoring/mqtt_client
|
||||||
rocky-docker-lxc@7dd5025 : caddy/Caddyfile
|
rocky-docker-lxc@d225776 : caddy/conf/Caddyfile
|
||||||
trilium : Eu0wns00QOBG, wovVrgvoDVy2, 1seDR2nBmxKO, GW8BMoTKtRBe, JbKe9CHzOP9D, Tu7FX48fxSQZ, DvKxK0gHXsng, otSPaU6IfjlP
|
trilium : Eu0wns00QOBG, wovVrgvoDVy2, 1seDR2nBmxKO, GW8BMoTKtRBe, JbKe9CHzOP9D, Tu7FX48fxSQZ, DvKxK0gHXsng, otSPaU6IfjlP
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- verified: 2026-09-19
|
<!-- verified: 2026-09-19
|
||||||
iac@f770e1c : ansible/roles/monitoring/wazuh_agent_tuning, ansible/roles/monitoring/docker_bench, ansible/roles/cis_hardening
|
iac@ae315de : ansible/roles/monitoring/wazuh_agent_tuning, ansible/roles/monitoring/docker_bench, ansible/roles/cis_hardening
|
||||||
wazuh-vm@07ed482 : wazuh/docker-compose.yml
|
wazuh-vm@07ed482 : wazuh/docker-compose.yml
|
||||||
trilium : KpvtX1USDhkY, TstjWVV5Q1TS, OtIgVyQC5lJg, MSVD5o5HiJ69, kA00FQiQWVem, oY5CbyuxZRyS
|
trilium : KpvtX1USDhkY, TstjWVV5Q1TS, OtIgVyQC5lJg, MSVD5o5HiJ69, kA00FQiQWVem, oY5CbyuxZRyS
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!-- verified: 2026-09-18
|
<!-- verified: 2026-09-18
|
||||||
rocky-docker-lxc@7dd5025 : caddy/Caddyfile
|
rocky-docker-lxc@d225776 : caddy/conf/Caddyfile
|
||||||
caddy-vm@9369f8b : caddy/Caddyfile
|
caddy-vm@c5c3420 : caddy/conf/Caddyfile
|
||||||
iac@f770e1c : ansible/roles/bootstrap_roles/docker_git_setup
|
iac@ae315de : ansible/roles/bootstrap_roles/docker_git_setup
|
||||||
trilium : baWhWTUejvY6, hWiIGZIlvQiN, nAPJq9A1iuX8
|
trilium : baWhWTUejvY6, hWiIGZIlvQiN, nAPJq9A1iuX8
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${HTTPS_PORT}:443"
|
- "${HTTPS_PORT}:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
- ./conf:/etc/caddy:ro
|
||||||
- /path/to/caddy-internal/caddy_data:/data
|
- /path/to/caddy-internal/caddy_data:/data
|
||||||
- /path/to/caddy-internal/caddy_config:/config
|
- /path/to/caddy-internal/caddy_config:/config
|
||||||
- /path/to/caddy-internal/logs:/var/log/caddy
|
- /path/to/caddy-internal/logs:/var/log/caddy
|
||||||
- ./certs/pki_int_ca.crt:/etc/caddy/certs/pki_int_ca.crt:ro
|
- ./certs:/etc/caddy/certs:ro
|
||||||
networks:
|
networks:
|
||||||
- caddy-net
|
- caddy-net
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${HTTPS_PORT}:443"
|
- "${HTTPS_PORT}:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
- ./conf:/etc/caddy:ro
|
||||||
- /path/to/caddy/caddy_data:/data
|
- /path/to/caddy/caddy_data:/data
|
||||||
- /path/to/caddy/caddy_config:/config
|
- /path/to/caddy/caddy_config:/config
|
||||||
- /etc/caddy/certs:/etc/caddy/certs
|
- /etc/caddy/certs:/etc/caddy/certs
|
||||||
|
|||||||
Reference in new issue
Block a user