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