[Unit] Description=ThreatCrush Security Agent Daemon Documentation=https://threatcrush.com After=network-online.target Wants=network-online.target # A daemon that cannot start (say, an ExecStart path a pnpm upgrade removed) # gives up after 10 tries in 5 minutes instead of looping forever. StartLimitIntervalSec=300 StartLimitBurst=10 [Service] Type=simple # The path to the CLI binary is filled in by `threatcrush install-service`. ExecStart={{BIN_PATH}} daemon # always, not on-failure: earlyoom and other memory-pressure killers stop the # daemon with SIGTERM, it shuts down cleanly and exits 0, and on-failure reads # that as a deliberate stop. Seen on bbs.profullstack.com: down 8 days after # one low-memory night. `systemctl stop` still stops it for good. Restart=always RestartSec=10 KillSignal=SIGTERM TimeoutStopSec=10 # Security hardening — adjust as needed NoNewPrivileges=true ProtectSystem=full # ProtectHome is read-only, but the daemon must write its own cloud session # dir (it rotates and re-saves the access/refresh token on every refresh). # {{CONFIG_DIR}} is the daemon user's ~/.threatcrush, punched back to writable # here by `threatcrush install-service`. Without it the token refresh hit # EROFS, the heartbeat stopped, and every server showed offline after an hour. ProtectHome=read-only # # -/etc/fail2ban: when fail2ban is the firewall backend, the daemon writes its # own jail and filter (jail.d/ + filter.d/threatcrush.conf) before the first # ban. ProtectSystem=full makes /etc read-only, so every ban failed with EROFS # and read like a permissions problem. The leading '-' tells systemd to skip # the entry on boxes with no /etc/fail2ban instead of failing 226/NAMESPACE. ReadWritePaths=/etc/threatcrush /var/log/threatcrush /var/lib/threatcrush {{CONFIG_DIR}} -/etc/fail2ban # /var/run is a symlink to /run, which is a tmpfs — anything created there at # install time is gone after the next reboot. Listing it under ReadWritePaths # then makes the unit fail 226/NAMESPACE on every start. RuntimeDirectory has # systemd create (and clean up) /run/threatcrush itself, on each start. RuntimeDirectory=threatcrush RuntimeDirectoryMode=0755 PrivateTmp=true # Log directly to journald StandardOutput=journal StandardError=journal # Capabilities the daemon actually needs: # CAP_NET_RAW / CAP_NET_ADMIN — raw packet capture for network-monitor module # CAP_DAC_READ_SEARCH — read mode-640 logs (auth.log, syslog, nginx) the # daemon doesn't own without falling back to mode 666 # CAP_DAC_OVERRIDE — same idea for the rare write that needs it # CAP_CHOWN — chgrp the IPC socket to `adm` so non-root # users in that group can talk to the daemon AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN [Install] WantedBy=multi-user.target