From acc4193071f61bf4cab56c01be27d54a39e3e2d7 Mon Sep 17 00:00:00 2001 From: nexxo Date: Sun, 2 Aug 2026 01:03:23 +0200 Subject: [PATCH] www-data gehoerte sein eigenes Heimatverzeichnis nicht: npm ci brach das Deployment mit 243 ab --- deploy/update.sh | 23 +++++++++++++++++ docker/php/Dockerfile | 17 ++++++++++++- .../DeploymentRunsAsTheAppUserTest.php | 25 ++++++++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/deploy/update.sh b/deploy/update.sh index cc19f5b..1f53610 100755 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -99,6 +99,29 @@ normalise_ownership() { [ -d "$d" ] || continue find "$d" ! -user www-data -exec chown www-data:www-data {} + 2>/dev/null || true done + + # Das Heimatverzeichnis von www-data und die Zwischenspeicher darin. + # + # Der Dockerfile vergibt www-data per `usermod -o -u` eine neue Nummer, + # schreibt aber keine vorhandene Datei um: /var/www blieb bei der alten + # aus dem Basis-Abbild, und damit ist das Heimatverzeichnis für seinen + # eigenen Benutzer nicht beschreibbar. npm legt seinen Cache unter + # ~/.npm an, durfte das nicht und brach ein Deployment mit EACCES ab — + # errno -13, also Code 243 — mitten im Wartungsmodus. Composer trifft + # dasselbe und verzeiht es still, weshalb es nur auffiel, als sich zum + # ersten Mal seit Langem package.json änderte und npm ci überhaupt lief. + # + # Der Dockerfile legt beides inzwischen selbst richtig an. Das hier ist + # für Server, die noch auf einem älteren Abbild stehen — sie sollen + # sich beim nächsten Deployment heilen, statt darauf zu warten. + # + # /var/www nur eine Ebene tief: darunter liegt der ganze Checkout, der + # oben schon einzeln behandelt wird. + mkdir -p /var/www/.npm /var/www/.composer 2>/dev/null || true + find /var/www -maxdepth 0 ! -user www-data -exec chown www-data:www-data {} + 2>/dev/null || true + for d in /var/www/.npm /var/www/.composer; do + find "$d" ! -user www-data -exec chown www-data:www-data {} + 2>/dev/null || true + done ' >/dev/null 2>&1 || true } diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index fd4b7c9..c4c3ada 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -30,8 +30,23 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # --- match host uid/gid so bind-mounted files stay writable --------------- +# +# `usermod -o -u` vergibt eine neue Nummer — es schreibt aber KEINE einzige +# vorhandene Datei um. /var/www ist das Heimatverzeichnis von www-data und +# gehört danach weiterhin der Nummer 33 aus dem Basis-Abbild, während www-data +# 1000 (oder was HOST_UID sagt) ist. Das Heimatverzeichnis des Benutzers ist +# für ihn selbst also nicht beschreibbar. +# +# Aufgefallen an einem Deployment, das mit Code 243 abbrach: npm legt seinen +# Zwischenspeicher unter ~/.npm an, durfte das nicht, und meldete EACCES — +# errno -13, und 256 minus 13 ist 243. Der Server blieb im Wartungsmodus +# stehen. Composer hat dasselbe Problem und verzeiht es stillschweigend, was +# erklärt, warum es jahrelang niemandem auffiel: es trifft nur den, der hart +# scheitert, und nur wenn sich package.json überhaupt einmal ändert. RUN groupmod -o -g "${HOST_GID}" www-data \ - && usermod -o -u "${HOST_UID}" -g "${HOST_GID}" www-data + && usermod -o -u "${HOST_UID}" -g "${HOST_GID}" www-data \ + && mkdir -p /var/www/.npm /var/www/.composer \ + && chown -R www-data:www-data /var/www # --- config ---------------------------------------------------------------- COPY docker/php/php.ini /usr/local/etc/php/conf.d/zz-clupilot.ini diff --git a/tests/Feature/DeploymentRunsAsTheAppUserTest.php b/tests/Feature/DeploymentRunsAsTheAppUserTest.php index 94836c3..b5a32fa 100644 --- a/tests/Feature/DeploymentRunsAsTheAppUserTest.php +++ b/tests/Feature/DeploymentRunsAsTheAppUserTest.php @@ -95,7 +95,13 @@ it('repairs ownership before it needs it, not after', function () { // node_modules/.vite-temp under a www-data-owned node_modules failed // the build in maintenance mode. ->and($update)->toContain('! -user www-data -exec chown www-data:www-data') - ->and($update)->toContain('node_modules'); + ->and($update)->toContain('node_modules') + // Und das Heimatverzeichnis von www-data. Es stand nicht auf der Liste, + // weil es außerhalb des Checkouts liegt — und genau daran brach ein + // Deployment mit Code 243 ab: npm wollte ~/.npm anlegen und durfte + // nicht (EACCES, errno -13; 256 minus 13 ist 243). Der Server blieb im + // Wartungsmodus stehen, die Kundenseite antwortete mit 500. + ->and($update)->toContain('/var/www/.npm'); // Before the first unprivileged step, or it cannot help: composer and npm // would already have failed on a root-owned vendor directory. @@ -106,3 +112,20 @@ it('repairs ownership before it needs it, not after', function () { ->and($maintenance)->not->toBeFalse() ->and($repair)->toBeLessThan($maintenance); }); + +it('gibt www-data sein eigenes Heimatverzeichnis, statt ihm nur eine neue Nummer zu geben', function () { + // `usermod -o -u` vergibt die Nummer und schreibt KEINE vorhandene Datei + // um. Ohne das chown danach gehört /var/www weiter der Nummer 33 aus dem + // Basis-Abbild, und www-data kann in seinem eigenen Zuhause nichts anlegen + // — weder ~/.npm noch ~/.composer. + $dockerfile = File::get(base_path('docker/php/Dockerfile')); + + $usermod = strpos($dockerfile, 'usermod'); + $chown = strpos($dockerfile, 'chown -R www-data:www-data /var/www'); + + expect($usermod)->not->toBeFalse() + ->and($chown)->not->toBeFalse() + // Danach, nicht davor: vor der Umnummerierung würde es auf die alte + // Nummer chownen und wäre unmittelbar wieder falsch. + ->and($chown)->toBeGreaterThan($usermod); +});