Files
simonandClaude Opus 5.5 cd44d8fbba Add EP final roll-call votes of Austrian MEPs
Uses the European Parliament's open data (CC BY 4.0) rather than
HowTheyVote, whose dump is ODbL share-alike and behind a robots.txt block.
Also documents the raw archive ownership step for Unraid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 01:18:58 +01:00

119 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment on Unraid
## Prerequisites
* Unraid 6.12+ with the **Docker Compose Manager** plugin (Community Applications).
* An appdata share, for example `/mnt/user/appdata/politrack`.
* Network access from the server to PyPI/npm for the image builds (`files.pythonhosted.org`,
`registry.npmjs.org`) – see "Troubleshooting".
* Optional: a reverse proxy (SWAG, Nginx Proxy Manager, Traefik or Caddy) and SSO (Authentik or Authelia)
if the site is exposed; an Ollama container for semantic search.
## Steps
1. Clone the repository onto the server (for example `/mnt/user/appdata/politrack/src`) or add it as a
stack in Compose Manager pointing at this folder.
2. `cp .env.example .env` and set at least:
* `DATA_DIR=/mnt/user/appdata/politrack` – database (`postgres/`) and raw archive (`raw/`) live here
* `POSTGRES_PASSWORD`, `POLITRACK_READER_PASSWORD`
* `POLITRACK_USER_AGENT` with a real contact address (publishers must be able to reach you)
* optional: `POLITRACK_ADMIN_TOKEN`, `POLITRACK_OLLAMA_URL`, API keys (YouTube, Meta Ad Library, X)
3. Build and start:
```bash
docker compose build
docker compose up -d
```
Docker creates `${DATA_DIR}/raw` as root, but the backend runs as a non-root user, and every fetch
then fails with "Permission denied". Hand it over once after the first `up`:
`chown -R "$(docker run --rm --entrypoint id politrack-backend -u):$(docker run --rm --entrypoint id politrack-backend -g)" "$DATA_DIR/raw"`.
`migrate` runs once (schema, seed data, source registry, job-queue tables), then `api`, `web`, `mcp`
and three workers start:
| service | queues | what runs there |
|---|---|---|
| `worker-scrape` | scrape, social | most adapters (hourly to daily) |
| `worker-bulk` | bulk | large or slow downloads: procurement, RTR, ad libraries, EC meetings, GDELT |
| `worker-enrich` | enrich | linking, events, legal detection, topics, corroboration, embeddings |
4. Initial load (otherwise the schedules pick everything up over the next day):
```bash
docker compose exec api politrack sources list # what exists, when it runs
docker compose exec api politrack run parlament.persons # members first: other adapters resolve against them
docker compose exec api politrack run wikidata.politicians
docker compose exec api politrack run europarl.meps
docker compose exec api politrack run parlament.items # votes etc. – the longest run (30-60 min)
docker compose exec api politrack run config.social_accounts
docker compose exec api politrack enrich
```
5. Open `http://<unraid-ip>:3000` (web), `http://<unraid-ip>:8000/api/docs` (API) and
`http://<unraid-ip>:8001/mcp` (MCP).
**Upgrading from the first version (v1):** the schema was rewritten (`0001_schema.sql`). Start with a
fresh database: stop the stack, move `${DATA_DIR}/postgres` away, `docker compose up -d`, re-run the
initial load. Nothing is lost that the sources can't provide again.
## Scaling and separate deployments
* More throughput: `docker compose up -d --scale worker-scrape=3`. Queueing locks ensure one adapter
never runs twice at the same time; per-host throttling is per process, so keep one replica per queue
if you are close to a publisher's rate limit.
* Another machine (different egress IP, GPU): run the same image with `POLITRACK_DATABASE_URL` pointing at
this database and `procrastinate --app=politrack.worker.app worker --queues=<queues>`. Mount a raw
archive volume there too (or set `POLITRACK_RAW_DIR` to shared storage).
## Semantic search (optional)
```bash
docker compose --profile ai up -d ollama
docker compose exec ollama ollama pull bge-m3
# .env: POLITRACK_OLLAMA_URL=http://ollama:11434
docker compose up -d
```
bge-m3 runs fine on CPU for this data volume. With an NVIDIA GPU, add a device reservation
(`deploy.resources.reservations.devices`) to the ollama service.
## Backups
* Database: a nightly `pg_dump`, e.g. with the **User Scripts** plugin:
```bash
docker exec politrack-db-1 pg_dump -U politrack -Fc politrack > /mnt/user/backups/politrack/$(date +%F).dump
find /mnt/user/backups/politrack -mtime +30 -delete
```
* Raw archive (`${DATA_DIR}/raw`): optional to back up – it saves refetching after parser fixes, and it can
be pruned (`find raw -mtime +365 -delete`); missing blobs are simply refetched.
## Exposing publicly
* Expose only `web` (and optionally `api`) through the reverse proxy. Keep `mcp` and the DB port LAN- or
VPN-only, or protect them with SSO or an API key at the proxy.
* Read [legal.md](legal.md) before going public (Impressum, privacy policy, §7b MedienG).
## Monitoring
* `docker compose exec api politrack status` – recent runs with stats and errors (table `run`).
* `GET /api/v1/admin/runs` with `Authorization: Bearer $POLITRACK_ADMIN_TOKEN`.
* Fetch log: `SELECT adapter, status, count(*) FROM fetch_log WHERE fetched_at > now() - interval '1 day' GROUP BY 1, 2;`
* Job queue: `SELECT task_name, status, count(*) FROM procrastinate_jobs GROUP BY 1, 2;`
* Uptime Kuma can poll `http://api:8000/healthz`.
## Troubleshooting
**`pip install` / `npm install` fail during the build with TLS errors, while other sites work.**
Something between the server and the internet blocks the package CDNs by host name (TLS SNI): typically
a DNS filter (Pi-hole/AdGuard, router "parental control"/"web protection"), a firewall with TLS
inspection, or antivirus web protection on the build machine. Check:
```bash
nslookup files.pythonhosted.org # resolves to a Fastly address, not 0.0.0.0 / your router?
curl -v https://pypi.org/simple/ -o /dev/null # works?
curl -v https://files.pythonhosted.org/ -o /dev/null
curl -v https://registry.npmjs.org/ -o /dev/null
```
If pypi.org works but files.pythonhosted.org / registry.npmjs.org fail during the TLS handshake, allow
these host names in the filter/firewall/AV (also `*.pythonhosted.org`, `registry.yarnpkg.com` if used). On
Windows, the System event log (source *Schannel*) and the AV's web-protection log show which component
reset the connection.