retry a linux account in place instead of deleting the person
POST /users/:id/provision-linux, and a terminal button on each user row. One
operation covering three needs that were all previously answered by "delete the
account and make it again":
backfill an account created before the feature existed, or while the host was not
set up for it
retry the first attempt failed for something since fixed — the traversable
ancestor chmod being the one everybody hits once
re-key replace authorized_keys with a new public key
Deleting to redo a retryable side effect throws away the password, the dashboards and
everything else keyed to the row.
The provisioning block moves out of create-user into provisionOsAccount, shared by
both entry points for the same reason app-store/members.ts is shaped that way: two
moments, one piece of work.
Found by testing the retry rather than the create: provisionUserDirs re-chmods every
directory including home, and home belongs to the MEMBER after the first successful
run — chmod requires ownership, so it threw EPERM and took every retry down before it
started. Those chmods are now a default for directories being created, not an
assertion about ones that already exist; os-user.ts sets the home's mode through sudo
and is the authority for it.
The route answers 200 with the error in the body, because the interesting cases are
partial: "the account exists and is confined but the keys failed" is not nothing
having happened, and the row shows both halves.
Verified end to end: blocked ancestor reports the chmod and leaves osUser null, the
retry after that chmod succeeds and records the row, and a re-key replaces
authorized_keys without rotating the outbound key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,7 +96,18 @@ export const provisionUserDirs = (email: string): void => {
|
||||
for (const dir of USER_DIRS) mkdirSync(join(accountDir, dir), { recursive: true });
|
||||
// Traversable, not listable: reaching `home` must not mean enumerating the platform's tree beside it.
|
||||
chmodSync(accountDir, 0o711);
|
||||
for (const dir of USER_DIRS) chmodSync(join(accountDir, dir), 0o700);
|
||||
|
||||
for (const dir of USER_DIRS) {
|
||||
try {
|
||||
chmodSync(join(accountDir, dir), 0o700);
|
||||
} catch {
|
||||
// A directory that is no longer OURS to chmod. `home` becomes the member's on the first successful
|
||||
// provision, and `chmod` requires ownership — so re-running this threw EPERM and took every RETRY down
|
||||
// before it began, which is how this was found. os-user.ts sets the home's mode through sudo and is the
|
||||
// authority for it; here the mode is a default for directories we are creating, not an assertion about
|
||||
// ones that already exist.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getTmpAttachmentsDir = (email: string) => join(DATA_PATH, email, 'attachments', 'tmp');
|
||||
|
||||
Reference in New Issue
Block a user