[Unit]
# WHP delete-user worker - isolated per-job instance.
#
# Instance name is the job_id ("delete-user-<username>-YYYYMMDD-HHMMSS-XXXXXXXX").
# The panel writes the per-job parameter file
#   /var/lib/whp/delete-user/%i.params
# before invoking `systemctl start whp-delete-user@%i.service`. That file
# defines DELETE_USER_JOB_ID for the worker; the spec JSON lives at
# /docker/whp/sql/delete-user-jobs/%i.json and the JSONL progress stream at
# /docker/whp/sql/delete-user-jobs/%i.jsonl.
#
# Why a systemd unit instead of running the work inline in the panel:
# UserResourceTeardown calls docker stop/rm per site, haproxy_manager API
# per FQDN, MySQL DROP USER/DROP DATABASE per resource, and PowerDNS zone
# delete per domain. The aggregate easily exceeds 60s for users with many
# sites/DBs, which is past mod_proxy_fcgi's default timeout — the browser
# then sees ERR_INCOMPLETE_CHUNKED_ENCODING even though the back-end
# completed. Worker model returns 202 immediately, JSONL stream drives
# the progress modal, kernel sandbox enforces blast radius.
#
# Pattern mirrors configs/whp-bulk-sites@.service (2026.05.198) and
# configs/whp-cpanel-import@.service.
Description=WHP Delete-User Worker (%i) - sandboxed
Documentation=file:///root/whp/configs/whp-delete-user@.service
After=network.target docker.service
Wants=docker.service

[Service]
Type=oneshot
RemainAfterExit=no

EnvironmentFile=/var/lib/whp/delete-user/%i.params

# Worker runs as root because the work calls userdel, rm -rf /docker/users/<u>,
# docker stop/rm, mysql DROP, haproxy_manager API, PowerDNS, certbot cleanup.
# Same posture as the inline pre-async path. Isolation below limits what root
# can DO, not what user it runs as.
User=root
Group=root

ExecStart=/usr/bin/php /root/whp/scripts/run-delete-user.php ${DELETE_USER_JOB_ID}

# ---------------------------------------------------------------------------
# Filesystem isolation
# ---------------------------------------------------------------------------
ProtectSystem=strict
ProtectHome=read-only

# RW holes the worker legitimately needs:
#   /docker/users          - rm -rf /docker/users/<username>
#   /docker/whp/sql        - JSONL progress stream + spec JSON
#   /docker/whp/web        - includes/safety-net.php
#   /var/log               - PHP error_log + /var/log/whp-delete-user-<id>.log
#   /var/log/whp           - WHP audit/cleanup logs
#   /var/lib/whp           - per-job params file
#   /docker/powerdns/logs  - pdns-integration zone removal log
#   /etc/letsencrypt       - ssl cleanup
#   /etc/httpd/conf.d      - vhost cleanup (shared httpd mode)
#   /var/spool/mail        - mail spool removal
#   /run, /var/run         - docker socket
#   /tmp                   - PHP CLI temp + session save path; required by
#                            the PHP runtime to start at all
#   /etc                   - userdel writes /etc/passwd, /etc/shadow,
#                            /etc/group, /etc/gshadow
ReadWritePaths=/docker/users /docker/whp/sql /docker/whp/web /var/log /var/log/whp /var/lib/whp /docker/powerdns/logs /etc/letsencrypt /etc/httpd/conf.d /var/spool/mail /run /var/run /tmp /etc

# ---------------------------------------------------------------------------
# Kernel / privilege isolation
# ---------------------------------------------------------------------------
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectProc=invisible

NoNewPrivileges=true
RestrictSUIDSGID=true
RestrictRealtime=true

RestrictNamespaces=~user

LockPersonality=true
MemoryDenyWriteExecute=true

PrivateTmp=false

# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
StandardOutput=append:/var/log/whp-delete-user-%i.log
StandardError=append:/var/log/whp-delete-user-%i.log

# Worker can run several minutes for a user with many sites/DBs. The worker's
# own per-step progress reporting is the upper bound; no service timeout.
TimeoutStartSec=infinity

# Graceful SIGTERM window for the cancel handler to flush a JSONL row.
TimeoutStopSec=30
KillSignal=SIGTERM
SendSIGKILL=yes

[Install]
# Template; instances activated on demand via
# `systemctl start whp-delete-user@<job_id>.service`. No WantedBy needed.
