From c4b262dc51d86e841d46f00066392b2adc52c76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 19 Aug 2026 02:07:56 +0000 Subject: [PATCH] provision-user-dirs script now sets modes, by calling the real provisioner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The script imported DATA_PATH and USER_DIRS from data-path.ts and then reimplemented the mkdir loop without the chmod that module's own provisionUserDirs applies — 711 on the account root, 700 on each subdir. A hand-provisioned account would have come out 755 at the default umask, where a UI-provisioned one is closed. Its header comment warned about exactly this drift while being an instance of it. Nothing was ever created that way here: every account on this box was made by the create-account handler and is correctly 711/700. This is a latent divergence between two paths that claim to produce identical accounts, not damage to repair. Calls provisionUserDirs(email) instead. Two consequences worth naming: re-running now re-asserts modes on existing directories, so it is also the repair; and DRY_RUN skips the call outright rather than gaining a dry-run mode in the real provisioning path to serve a script. Verified: dry run creates nothing, a fresh root comes out 711/700, and a directory chmod'd to 755 is restored on the next run. Co-Authored-By: Claude Opus 5 --- scripts/provision-user-dirs.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/scripts/provision-user-dirs.ts b/scripts/provision-user-dirs.ts index 0b576fa9..58dcee8d 100644 --- a/scripts/provision-user-dirs.ts +++ b/scripts/provision-user-dirs.ts @@ -12,14 +12,21 @@ // Docker container. That architecture is gone. What survives from it is the useful part: a user has a // root, and a home inside it. // -// Idempotent — an existing directory is left exactly as it is, so re-running is safe. +// Idempotent — an existing directory keeps its contents, so re-running is safe. It does NOT keep its +// mode: provisionUserDirs re-asserts 711/700 on every run, tolerating the EPERM from a `home` that has +// since become the member's own. Re-running is therefore also the repair. -import { mkdirSync, existsSync } from 'node:fs'; +import { existsSync } from 'node:fs'; import { join } from 'node:path'; // The list and DATA_PATH itself come from the platform rather than being restated here. The owner's // create-account handler provisions the same skeleton, and a script that drifted from it would produce // accounts that differ by how they were made. Importing data-path.ts pulls in no database and no server. -import { DATA_PATH, USER_DIRS } from '../src/servers/data-path'; +// provisionUserDirs rather than a local mkdir loop, and that is the whole point of this script: it used +// to import DATA_PATH and USER_DIRS from this very module and then reimplement the loop WITHOUT the +// chmod, so a hand-provisioned account got 755 where a UI-provisioned one got 711/700. Nothing was ever +// created that way here, but "two paths that claim to produce identical accounts" is exactly the drift +// the comment above already warned about — while doing it. +import { DATA_PATH, USER_DIRS, provisionUserDirs } from '../src/servers/data-path'; const DRY_RUN = process.env.DRY_RUN === '1'; const emails = process.argv.slice(2).filter(Boolean); @@ -44,14 +51,16 @@ for (const email of emails) { const root = join(DATA_PATH, email); console.log(`${email}${existsSync(root) ? '' : ' [new root]'}`); + // Probed BEFORE provisioning, because afterwards everything exists and the log would say so. + const existing = new Set(USER_DIRS.filter((dir) => existsSync(join(root, dir)))); + + // Skipped entirely under DRY_RUN rather than called: provisionUserDirs has no dry-run mode, and giving + // it one to serve this script would put a branch in the real provisioning path for a script's benefit. + if (!DRY_RUN) provisionUserDirs(email); + for (const dir of USER_DIRS) { - const path = join(root, dir); - if (existsSync(path)) { - console.log(` · ${dir} (exists)`); - continue; - } - if (!DRY_RUN) mkdirSync(path, { recursive: true }); - console.log(` ${DRY_RUN ? '+' : '✓'} ${dir}`); + if (existing.has(dir)) console.log(` · ${dir} (exists${DRY_RUN ? '' : ', mode re-asserted'})`); + else console.log(` ${DRY_RUN ? '+' : '✓'} ${dir}`); } console.log(''); }