[Unit] Description=Curator personal media library Wants=network-online.target After=network-online.target [Service] Type=simple WorkingDirectory=/home/claw/pi-workspaces/curator Environment=PYTHONPATH=/home/claw/pi-workspaces/curator Environment=CURATOR_PI_WORKSPACE=/home/claw/pi-workspaces/curator Environment=CURATOR_PI_SESSION_DIR=/home/claw/.local/share/pi-curator/sessions Environment=CURATOR_PI_MODEL=zenmux/openai/gpt-5.6-luna Environment=CURATOR_PI_THINKING=high Environment=CURATOR_PI_FALLBACK_MODEL=zenmux/x-ai/grok-4.6 Environment=CURATOR_PI_TIMEOUT_SECONDS=120 Environment=CURATOR_WECHAT_ARTICLE_BASE_URL=http://192.168.50.145:8091 EnvironmentFile=/home/claw/.config/curator/curator.env ExecStart=/usr/bin/python3 -m curator serve Restart=on-failure RestartSec=5 TimeoutStartSec=60 TimeoutStopSec=20 # --- filesystem ------------------------------------------------------------- # The whole hierarchy is read-only apart from the paths listed below. Verified # with systemd-run before being applied: the database, library, staging, backup # and cover directories are writable and pi starts cleanly. ProtectSystem=strict ProtectHome=read-only ReadWritePaths=/home/claw/.local/share/curator ReadWritePaths=/mnt/truenas/multimedia/books ReadWritePaths=/mnt/truenas/multimedia/curator ReadWritePaths=/home/claw/.local/share/pi-curator # pi takes a lock beside ~/.pi/agent/settings.json on startup. With a read-only # home it cannot, and then reports the settings file as invalid and ignores it -- # which would silently discard the agent's configuration. ReadWritePaths=/home/claw/.pi # The agent's own prompt and launch contract are read-only to the service that # runs it, so a compromised agent cannot rewrite the rules it runs under. ReadOnlyPaths=/home/claw/pi-workspaces/curator PrivateTmp=true UMask=0077 # --- privileges ------------------------------------------------------------- NoNewPrivileges=true ProtectKernelTunables=true ProtectKernelModules=true ProtectKernelLogs=true ProtectControlGroups=true ProtectClock=true ProtectProc=invisible RestrictSUIDSGID=true RestrictRealtime=true RestrictNamespaces=true LockPersonality=true MemoryDenyWriteExecute=false # node's JIT needs writable-executable pages, so MemoryDenyWriteExecute cannot # be enabled while pi runs as a child of this service. # --- resources -------------------------------------------------------------- # pi is a node process and the service may run several sequentially. These are # ceilings that turn a runaway into a restart instead of host memory pressure. MemoryMax=3G MemoryHigh=2G TasksMax=512 # NOTE: the listener is still on CURATOR_HOST=0.0.0.0, which reaches every # interface including six docker bridges, and access genuinely arrives from both # the LAN and 127.0.0.1. The defence is now authentication (plan P2-6): every # route except /api/health and /login requires CURATOR_WEB_TOKEN, which is what # makes an unaudited LAN host into a read-only observer instead of a write # primitive. Do not unset CURATOR_WEB_TOKEN while the port is not loopback. [Install] WantedBy=default.target