From 638fb24593892cbc48cf17b54b42dd2fa1f18733 Mon Sep 17 00:00:00 2001 From: Bart Van Geyt Date: Sun, 9 Aug 2026 04:35:32 +0200 Subject: [PATCH] fix(control-panel/sftp): create shared fpm group before useradd Provisioning failed on a stock host because `useradd -g ` (82, the alpine www-data gid) requires a group at that gid, which no host baseline creates. Add ensure_fpm_group(): create a `heleos-web` group at fpm_gid if absent, before the SFTP user is created. Idempotent; resolves the open item noted in site-templates/README.md. Co-Authored-By: Claude Opus 4.8 --- control-panel/src/heleos/sftp.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/control-panel/src/heleos/sftp.py b/control-panel/src/heleos/sftp.py index a068477..3045370 100644 --- a/control-panel/src/heleos/sftp.py +++ b/control-panel/src/heleos/sftp.py @@ -19,6 +19,11 @@ from .runner import Runner SSHD_DROPIN = "/etc/ssh/sshd_config.d/sftp-{customer}.conf" +# Host group (at fpm_gid) shared by php-fpm and every customer's SFTP user. The +# alpine images run www-data as uid/gid 82, but a stock host has no group at that +# gid, so useradd -g would fail. Created once per host. +FPM_GROUP_NAME = "heleos-web" + def _user_exists(runner: Runner, user: str) -> bool: if runner.dry_run: @@ -27,6 +32,20 @@ def _user_exists(runner: Runner, user: str) -> bool: return out.strip().isdigit() +def _group_exists(runner: Runner, gid: int) -> bool: + if runner.dry_run: + return False + out = runner.run(["getent", "group", str(gid)], check=False, capture=True) + return bool(out.strip()) + + +def ensure_fpm_group(runner: Runner, cfg: Config) -> None: + """Ensure a group with gid == fpm_gid exists so the SFTP user's primary group + (and the setgid web root) can share it with php-fpm. Idempotent.""" + if not _group_exists(runner, cfg.fpm_gid): + runner.run(["groupadd", "-g", str(cfg.fpm_gid), FPM_GROUP_NAME]) + + def set_web_ownership(runner: Runner, cfg: Config, site: Site) -> None: web = context.webroot(cfg, site) runner.run(["chown", "-R", f"{cfg.fpm_uid}:{cfg.fpm_gid}", web]) @@ -45,6 +64,7 @@ def ensure_customer_account(runner: Runner, cfg: Config, customer: str, home = f"{cfg.customers_root}/{customer}" created = not _user_exists(runner, user) if created: + ensure_fpm_group(runner, cfg) # useradd -g needs this group runner.run([ "useradd", "-M", "-N", "-g", str(cfg.fpm_gid), # primary group = fpm group