[Unit]
Description=ifcurl preview service
Documentation=https://github.com/brunopostle/ifcurl
After=network.target

[Service]
Type=simple
User=ifcurl
Group=ifcurl
EnvironmentFile=-/etc/ifcurl/env
ExecStart=/usr/local/bin/ifcurl serve \
    --host 127.0.0.1 \
    --port 8000 \
    --allowed-hosts git.example.com
Restart=on-failure
RestartSec=5

# Cache configuration — override in /etc/ifcurl/env:
#   IFCURL_CACHE_MAX_GB=10   max bare-repo disk cache
#   IFCURL_T2_MAX=8          max IFC blobs in memory
#   IFCURL_T3_MAX=64         max selector results in memory
#   IFCURL_SANDBOX_TIMEOUT=120   max seconds per render subprocess
#   IFCURL_SANDBOX_MEMORY_MB=0   address-space cap per subprocess (0=unlimited)

# ---------------------------------------------------------------------------
# Sandbox hardening
#
# These directives apply to the service process and all child processes
# (including the render subprocesses spawned by sandbox.py).  Together they
# limit the blast radius if a crafted IFC file achieves code execution inside
# a render subprocess.
#
# ProtectSystem=strict + ReadWritePaths: the service can only write to its own
# cache directory; the Forgejo data tree (/var/lib/forgejo, /etc/forgejo) is
# read-only from this unit's perspective.
#
# NoNewPrivileges + RestrictSUIDSGID: prevent privilege escalation via
# setuid binaries or capabilities.
#
# SystemCallFilter: allow the syscalls a Python/C-extension process needs
# (@system-service covers the common POSIX set including execve via @default;
# @process adds fork/clone for ifcopenshell's geometry iterator).  execve is
# permitted because the main process needs it for git CLI calls (GitPython).
# The filter applies to child processes too (inherited across fork).
#
# RestrictAddressFamilies: the service only needs TCP/UDP to reach git hosts.
# AF_UNIX is excluded so a compromised subprocess cannot connect to the
# Forgejo Unix socket if one is present.
# ---------------------------------------------------------------------------
Environment=XDG_CACHE_HOME=/var/cache
CacheDirectory=ifcurl
NoNewPrivileges=yes
RestrictSUIDSGID=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/cache/ifcurl
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictNamespaces=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallArchitectures=native
SystemCallFilter=@system-service @process @files @io-event @network-io
SystemCallErrorNumber=EPERM

[Install]
WantedBy=multi-user.target
