Compare commits

..
Author SHA1 Message Date
jschoubben 287e5e6d3f bookshelf: its config dir is placed, its image is the one ace runs
The config directory was the stated path /services/bookshelf/config; it
is now a pathless directory the mesh places (0700, 1000:1000), and the
route binding lives in a placed state dir, as in jackett and searxng.
/var/lib/mesh/bookshelf stays for the broker secret.

The image is pinned to the digest ace runs (hardcover, 0.4.21.182,
ls36). The old pin was a February build: older than the database it
would open, which Readarr-family apps migrate forward only.

The sidecar dials ${port:8787}, the port the mesh assigned, not the
software's own (the same one-line change as #154).

Not carried from HAL: its install hook seeded config.xml with an API key
(the image writes its own on first start, and the data keeps it) and
registered nzbget, qbittorrent and jackett through the API with wrong
hosts and ports, so ace's bookshelf has no download client and no
indexer today. Wiring them is provisioning work that depends on the
download-client and jackett providers, and on where ace's books and
downloads live (hq 153).

Verified: catalogue tests with MESH_CATALOGUE; a scratch resolution with
ace's assignment composes books.zurag.be and keeps the port to the
mesh; a throwaway container at the pinned digest on a fresh 0700
1000:1000 dir answered /ping and /api/v1/system/status (401 on a wrong
key); client.ts typechecks strict, found the key in config.xml and read
the queue.
2026-09-30 11:58:37 +02:00
2 changed files with 33 additions and 53 deletions
+11 -6
View File
@@ -39,10 +39,15 @@
"path": "/var/lib/mesh/bookshelf",
"mode": "0700"
},
{
"id": "state",
"type": "directory",
"mode": "0700",
"place": "."
},
{
"id": "config",
"type": "directory",
"path": "/services/bookshelf/config",
"mode": "0700",
"owner": "1000:1000"
},
@@ -50,7 +55,7 @@
"id": "server",
"type": "container",
"name": "bookshelf",
"image": "ghcr.io/pennydreadful/bookshelf@sha256:388eecc94362580eae31ee0a454be6af516f8a311f8432a521c202fb475f4359",
"image": "ghcr.io/pennydreadful/bookshelf@sha256:67498dd5ece516867d72ee642abd6c1a66b36a135c8f7da0127109564372beb1",
"env": {
"PUID": "1000",
"PGID": "1000",
@@ -60,7 +65,7 @@
"8787"
],
"volumes": [
"/services/bookshelf/config:/config",
"${dir:config}:/config",
"/services/media/books:/books",
"/services/media/downloads:/downloads"
]
@@ -72,11 +77,11 @@
"network": "host",
"volumes": [
"/var/lib/mesh/bookshelf/broker:/run/secrets/broker:ro",
"/services/bookshelf/config:/var/lib/bookshelf/config:ro"
"${dir:config}:/var/lib/bookshelf/config:ro"
],
"env": {
"MESH_BROKER_FILE": "/run/secrets/broker",
"MESH_BOOKSHELF_URL": "http://127.0.0.1:8787",
"MESH_BOOKSHELF_URL": "http://127.0.0.1:${port:8787}",
"MESH_BOOKSHELF_CONFIG_DIR": "/var/lib/bookshelf/config"
},
"artifact": "runtime"
@@ -92,7 +97,7 @@
}
},
"binds": {
"route": "/var/lib/mesh/bookshelf/route.json"
"route": "${dir:state}/route.json"
},
"build": {
"on": [
+22 -47
View File
@@ -1,26 +1,6 @@
{
"module": "icecast",
"version": "1",
"requires": [
"route",
"secret"
],
"contributes": {
"route": {
"label": "icecast",
"endpoint": "stream"
}
},
"binds": {
"route": "${dir:state}/route.json"
},
"secrets": {
"secret": {
"source": "${dir:state}/source.secret",
"admin": "${dir:state}/admin.secret",
"relay": "${dir:state}/relay.secret"
}
},
"capabilities": [
"container-runtime"
],
@@ -37,7 +17,7 @@
"port": 8000,
"protocol": "tcp",
"from": "mesh",
"why": "streams in from sources (HTTP PUT) and out to listeners, plus the status and admin pages; a public name is its route"
"why": "streams in from sources and out to listeners"
}
],
"resources": [
@@ -50,43 +30,28 @@
{
"id": "state",
"type": "directory",
"mode": "0700",
"place": "."
"path": "/var/lib/icecast-module",
"mode": "0700"
},
{
"id": "logs",
"type": "directory",
"mode": "0700",
"owner": "100:101"
},
{
"id": "server-conf",
"id": "server-env",
"type": "file",
"path": "${dir:state}/icecast.xml",
"path": "/var/lib/icecast-module/server.env",
"mode": "0600",
"content": "<icecast>\n <!-- Written by the mesh (modules/icecast). Passwords arrive as secrets rendered into this file,\n never as environment: the image's entrypoint seds ICECAST_* variables into the file only\n when they are set, and none are. -->\n <location>Earth</location>\n <admin>icemaster@localhost</admin>\n <limits>\n <clients>100</clients>\n <sources>2</sources>\n <queue-size>524288</queue-size>\n <client-timeout>30</client-timeout>\n <header-timeout>15</header-timeout>\n <source-timeout>10</source-timeout>\n <burst-on-connect>1</burst-on-connect>\n <burst-size>65535</burst-size>\n </limits>\n <authentication>\n <source-password>${secret:source}</source-password>\n <relay-password>${secret:relay}</relay-password>\n <admin-user>admin</admin-user>\n <admin-password>${secret:admin}</admin-password>\n </authentication>\n <!-- The name icecast writes into playlists (.m3u/.xspf: http://<hostname>:<port>/<mount>) and\n would announce to YP (none configured). A machine's own name belongs to its assignment, and\n an assignment merges only into JSON; this XML cannot take it, so the neutral default stays. -->\n <hostname>localhost</hostname>\n <listen-socket>\n <port>8000</port>\n </listen-socket>\n <http-headers>\n <header name=\"Access-Control-Allow-Origin\" value=\"*\" />\n </http-headers>\n <fileserve>1</fileserve>\n <paths>\n <basedir>/usr/share/icecast</basedir>\n <logdir>/var/log/icecast</logdir>\n <webroot>/usr/share/icecast/web</webroot>\n <adminroot>/usr/share/icecast/admin</adminroot>\n <alias source=\"/\" destination=\"/status.xsl\"/>\n </paths>\n <logging>\n <accesslog>access.log</accesslog>\n <errorlog>error.log</errorlog>\n <loglevel>3</loglevel>\n <logsize>10000</logsize>\n </logging>\n <security>\n <chroot>0</chroot>\n <!-- Starts as root, reads this 0600 root-owned file, then drops to the image's icecast user\n (uid 100, group icecast 101) before serving. -->\n <changeowner>\n <user>icecast</user>\n <group>icecast</group>\n </changeowner>\n </security>\n</icecast>\n"
},
{
"id": "net",
"type": "network",
"name": "icecast"
"content": "ICECAST_SOURCE_PASSWORD=${secret:source}\nICECAST_ADMIN_PASSWORD=${secret:admin}\nICECAST_RELAY_PASSWORD=${secret:relay}\nICECAST_ADMIN_USERNAME=admin\n"
},
{
"id": "server",
"type": "container",
"name": "icecast",
"image": "infiniteproject/icecast@sha256:cd506cf3dfe31ce05fd37d7e672dbd1213e7255cc93d28ecf5a3b547af4e162c",
"network": "icecast",
"env-file": [
"/var/lib/icecast-module/server.env"
],
"ports": [
"8000"
],
"volumes": [
"${dir:state}/icecast.xml:/etc/icecast.xml:ro",
"${dir:logs}:/var/log/icecast"
],
"restart-on": [
"server-conf"
]
"secrets-in-environment": "the image seds ICECAST_*_PASSWORD into icecast.xml and has no _FILE; convertible by mounting a generated icecast.xml, not yet done"
},
{
"id": "runtime-config",
@@ -100,14 +65,14 @@
"id": "runtime",
"type": "container",
"name": "mesh-icecast",
"network": "icecast",
"network": "host",
"volumes": [
"/var/lib/mesh/icecast/broker:/run/secrets/broker:ro",
"/var/lib/mesh/icecast/config.json:/run/config/config.json:ro"
],
"env": {
"MESH_BROKER_FILE": "/run/secrets/broker",
"MESH_ICECAST_URL": "http://icecast:8000",
"MESH_ICECAST_URL": "http://127.0.0.1:8000",
"MESH_ICECAST_CONFIG_FILE": "/run/config/config.json"
},
"restart-on": [
@@ -136,5 +101,15 @@
"from": "Dockerfile"
}
]
},
"requires": [
"secret"
],
"secrets": {
"secret": {
"source": "/var/lib/icecast-module/source.secret",
"admin": "/var/lib/icecast-module/admin.secret",
"relay": "/var/lib/icecast-module/relay.secret"
}
}
}