feat(curator): vendor the application backend as the scenario's tracked source
The curator Python backend (package, tests, systemd units, config templates, scripts) now lives under scenarios/curator/backend and is the single source of truth; the live checkout at the workspace path is a runtime copy. Exported from the app repo's tracked tree via git archive (no history, .pi/venv/caches excluded). 149 unit tests pass from the new location. profile.toml backend is now repo-relative (scenarios/curator/backend); verify-generated.sh resolves a relative backend against REPO_ROOT. verify-no-secrets ASSIGN heuristic now requires value entropy so vendored kwargs like token=extraction_token no longer false-positive. README documents the backend/ layout and the operator-owned app rollout step.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
[Unit]
|
||||
Description=Curator database backup and retention
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/home/claw/pi-workspaces/curator
|
||||
Environment=PYTHONPATH=/home/claw/pi-workspaces/curator
|
||||
EnvironmentFile=/home/claw/.config/curator/curator.env
|
||||
ExecStart=/usr/bin/python3 -m curator backup
|
||||
TimeoutStartSec=15m
|
||||
|
||||
# Local disk only; no network is needed, so egress stays closed.
|
||||
IPAddressDeny=any
|
||||
|
||||
ProtectSystem=strict
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths=/home/claw/.local/share/curator
|
||||
ReadWritePaths=/mnt/truenas/multimedia/curator
|
||||
PrivateTmp=true
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
RestrictNamespaces=true
|
||||
LockPersonality=true
|
||||
MemoryMax=1G
|
||||
TasksMax=64
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Run Curator database backup daily
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:15:00
|
||||
Persistent=true
|
||||
RandomizedDelaySec=10m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,30 @@
|
||||
[Unit]
|
||||
Description=Curator cover image refresh
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/home/claw/pi-workspaces/curator
|
||||
Environment=PYTHONPATH=/home/claw/pi-workspaces/curator
|
||||
EnvironmentFile=/home/claw/.config/curator/curator.env
|
||||
ExecStart=/usr/bin/python3 -m curator refresh-covers
|
||||
# Network-bound and safe to fail. A stall here used to delay the backup, which
|
||||
# shared the same oneshot unit and ran first.
|
||||
TimeoutStartSec=30m
|
||||
|
||||
ProtectSystem=strict
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths=/home/claw/.local/share/curator
|
||||
PrivateTmp=true
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
RestrictNamespaces=true
|
||||
LockPersonality=true
|
||||
MemoryMax=1G
|
||||
TasksMax=64
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Run Curator cover refresh daily
|
||||
|
||||
[Timer]
|
||||
# After the backup window, so the two never contend for the database.
|
||||
OnCalendar=*-*-* 04:15:00
|
||||
Persistent=true
|
||||
RandomizedDelaySec=20m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Curator database backup and retention
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/home/claw/pi-workspaces/curator
|
||||
Environment=PYTHONPATH=/home/claw/pi-workspaces/curator
|
||||
EnvironmentFile=/home/claw/.config/curator/curator.env
|
||||
ExecStart=/usr/bin/python3 -m curator maintain
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Run Curator maintenance daily
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:15:00
|
||||
Persistent=true
|
||||
RandomizedDelaySec=10m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
[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
|
||||
Reference in New Issue
Block a user