mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 04:36:40 +00:00
fix(web-app-weblate): let the uid-1000 containers write their static tmpfs
CI run 40979252521 Weblate#1 (swarm) never converged in its first pass:
every weblate, worker and beat task died on collectstatic with
"PermissionError: [Errno 13] Permission denied:
'/app/cache/static/weblate_fonts'". The per-container tmpfs from
a0bcd59150 sits on the weblate_cache volume, whose static mountpoint is
root-owned 0755 (nfs-volume-contents.txt: drwxr-xr-x 0:0), and runc gives
a tmpfs on an existing mountpoint that directory's mode, while Weblate
runs as uid 1000.
The three static tmpfs mounts now declare mode 1777, rendered as
tmpfs.mode 1023; test_static_root_per_container.py asserts it per
service.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5faa21c458
commit
99a7d28067
2 files changed
+22
-3
No files matched your search
@@ -24,7 +24,10 @@ static:
|
||||
mounts:
|
||||
- service: weblate
|
||||
target: &app_static_target /app/cache/static
|
||||
mode: &app_static_mode "1777"
|
||||
- service: beat
|
||||
target: *app_static_target
|
||||
mode: *app_static_mode
|
||||
- service: worker
|
||||
target: *app_static_target
|
||||
mode: *app_static_mode
|
||||
@@ -11,6 +11,7 @@ APPLICATION_ID = "web-app-weblate"
|
||||
VOLUMES = PROJECT_ROOT / "roles" / APPLICATION_ID / ROLE_FILE_META_VOLUMES
|
||||
CACHE_DIR = "/app/cache"
|
||||
STATIC_ROOT = f"{CACHE_DIR}/static"
|
||||
STATIC_MODE = 0o1777
|
||||
|
||||
|
||||
def cache_sharers() -> list[str]:
|
||||
@@ -50,15 +51,30 @@ class TestStaticRootPerContainer(unittest.TestCase):
|
||||
self.assertEqual(sharers, ["beat", "weblate", "worker"])
|
||||
for service in sharers:
|
||||
with self.subTest(service=service):
|
||||
self.assertIn(
|
||||
{"type": "tmpfs", "target": STATIC_ROOT},
|
||||
rendered_mounts(service),
|
||||
static = [
|
||||
mount
|
||||
for mount in rendered_mounts(service)
|
||||
if isinstance(mount, dict) and mount.get("target") == STATIC_ROOT
|
||||
]
|
||||
self.assertEqual(
|
||||
[mount.get("type") for mount in static],
|
||||
["tmpfs"],
|
||||
f"every Weblate task runs collectstatic into {STATIC_ROOT} on "
|
||||
f"start; on the {CACHE_DIR} volume the tasks share, concurrent "
|
||||
f"runs delete each other's hashed files and the swarm tasks "
|
||||
f"crash-loop with FileNotFoundError, so '{service}' needs a "
|
||||
f"tmpfs of its own there",
|
||||
)
|
||||
self.assertEqual(
|
||||
(static[0].get("tmpfs") or {}).get("mode"),
|
||||
STATIC_MODE,
|
||||
f"runc mounts a tmpfs with the mode of the directory beneath "
|
||||
f"it once that exists, and on the {CACHE_DIR} volume that is "
|
||||
f"the root-owned 0755 mountpoint the first task left behind; "
|
||||
f"Weblate runs as uid 1000, so collectstatic dies with "
|
||||
f"'PermissionError: ... {STATIC_ROOT}/weblate_fonts' unless "
|
||||
f"the tmpfs of '{service}' declares mode {STATIC_MODE:o}",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user