# Deploying with systemd A minimal, single-instance internal deployment. Run once as root on the target VM. The app is a single process (waitress + an in-process worker thread + SQLite) — see AGENTS.md. Do **not** run multiple instances or web-server workers. ## 1. Create the service user and lay down the code ```bash sudo useradd --system --home /opt/pps-quarantine --shell /usr/sbin/nologin pps sudo mkdir -p /opt/pps-quarantine # copy this repo to /opt/pps-quarantine (git clone, rsync, scp — whatever you use), then: sudo chown -R pps:pps /opt/pps-quarantine ``` ## 2. Install dependencies (as the service user) ```bash cd /opt/pps-quarantine sudo -u pps python3 -m venv venv sudo -u pps venv/bin/pip install --upgrade pip # avoids "no matching distribution" on old pip sudo -u pps venv/bin/pip install -r requirements.txt # version ranges; pip picks compatible builds ``` Requires **Python 3.9+**. `requirements.lock` (exact pins from the dev machine) also exists, but only install from it if that exact Python/pip combination matches — otherwise use `requirements.txt` above. ## 3. Configure ```bash sudo -u pps cp config.example.toml config.toml sudo -u pps chmod 600 config.toml # generate a session key: python3 -c "import secrets; print(secrets.token_urlsafe(48))" sudoedit config.toml # (or edit as the pps user) ``` Fill in at least: `[okta]` (issuer/client_id/client_secret/redirect_uri), `[pps]` host + API credentials, `[[auth.users]]` (your admins), a real `[app] secret_key`, and set `[app] listen = "127.0.0.1"` + `cookie_secure = true` (you'll front it with TLS). `wsgi.py` refuses to boot with the placeholder or a `<32`-char secret_key. The `config.toml` and the `/opt/pps-quarantine` directory must stay **writable by `pps`** — the admin panel rewrites `config.toml`, and `jobs.db` / `*.log` are written there. ## 4. Install and start the service ```bash sudo cp /opt/pps-quarantine/deploy/pps.service /etc/systemd/system/pps.service # if you used a different path/user, edit the unit first sudo systemctl daemon-reload sudo systemctl enable --now pps ``` ## 5. Verify ```bash systemctl status pps journalctl -u pps -f # startup + request logs (also in /opt/pps-quarantine/app.log) curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/login # -> 200 ``` ## Reverse proxy (TLS) Terminate TLS at nginx/Caddy and proxy to `127.0.0.1:8080`. Make sure the Okta `redirect_uri` matches the public HTTPS URL exactly (e.g. `https://pps.internal.example.com/authorize`). Minimal nginx: ```nginx server { listen 443 ssl; server_name pps.internal.example.com; ssl_certificate /etc/ssl/pps.crt; ssl_certificate_key /etc/ssl/pps.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; } } ``` ## Day-to-day ```bash sudo systemctl restart pps # after editing [app]/[okta] (restart-only keys) sudo systemctl stop pps journalctl -u pps --since '1 hour ago' ``` Most settings ([pps], [quarantine], users) are editable live in the admin panel and need no restart. See `docs/operations.md` for logs, backups, and credential rotation, and `docs/configuration.md` for which keys require a restart.