# 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/ppsq --shell /usr/sbin/nologin ppsq sudo mkdir -p /opt/ppsq # copy this repo to /opt/ppsq (git clone, rsync, scp — whatever you use), then: sudo chown -R ppsq:ppsq /opt/ppsq ``` ## 2. Install dependencies (as the service user) ```bash cd /opt/ppsq sudo -u ppsq python3 -m venv venv sudo -u ppsq venv/bin/pip install --upgrade pip # avoids "no matching distribution" on old pip sudo -u ppsq 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 ppsq cp config.example.toml config.toml sudo -u ppsq chmod 600 config.toml # generate a session key: python3 -c "import secrets; print(secrets.token_urlsafe(48))" sudoedit config.toml # (or edit as the ppsq 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/ppsq` directory must stay **writable by `ppsq`** — the admin panel rewrites `config.toml`, and `jobs.db` / `*.log` are written there. ## 4. Install and start the service ```bash sudo cp /opt/ppsq/deploy/ppsq.service /etc/systemd/system/ppsq.service # if you used a different path/user, edit the unit first sudo systemctl daemon-reload sudo systemctl enable --now ppsq ``` ## 5. Verify ```bash systemctl status ppsq journalctl -u ppsq -f # startup + request logs (also in /opt/ppsq/app.log) curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/login # -> 200 ``` ## Reverse proxy (nginx + TLS) A ready-made site config is in [`ppsq.nginx.conf`](ppsq.nginx.conf) — it terminates TLS and `proxy_pass`es to `127.0.0.1:8080`. Edit the `server_name` and `ssl_certificate*` paths, then: ```bash sudo cp deploy/ppsq.nginx.conf /etc/nginx/sites-available/ppsq sudo ln -s /etc/nginx/sites-available/ppsq /etc/nginx/sites-enabled/ppsq sudo nginx -t && sudo systemctl reload nginx ``` Make sure the Okta `redirect_uri` matches the public HTTPS URL exactly (e.g. `https://ppsq.internal.example.com/authorize`), and set `[app] cookie_secure = true` and `listen = "127.0.0.1"` in `config.toml`. ## Day-to-day ```bash sudo systemctl restart ppsq # after editing [app]/[okta] (restart-only keys) sudo systemctl stop ppsq journalctl -u ppsq --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.