Initial deploy setup
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-21 14:00:00 +02:00
commit b8091e59bd
285 changed files with 27854 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
# Back-up en herstel
## Wat moet worden beschermd
Prioriteit:
1. PostgreSQL-database of lokale SQLite-database;
2. productie-`.env` en reverse-proxyconfiguratie, versleuteld en apart;
3. `media/` met gebruikersdocumenten/snapshots wanneer die functie actief is;
4. `config-data/` met niet-geheime bron- en profielconfiguratie;
5. immutable image/tag en de bijbehorende repositoryrelease.
Redis is een werkqueue/cache en hoeft normaal niet te worden hersteld. Ollama-modellen kunnen opnieuw worden gedownload; behoud alleen wanneer bandbreedte of modelbeschikbaarheid dat vereist.
## Minimumniveau
- database dagelijks;
- configuratie na iedere wijziging;
- minstens 7 dagelijkse en 4 wekelijkse herstelpunten;
- back-up op een ander fysiek of logisch opslagdoel;
- checksums op ieder archief;
- minstens per kwartaal een volledige hersteltest;
- gevoelige back-ups versleuteld at rest en tijdens transport.
## Repositoryscripts
```bash
./scripts/backup.sh
```
Het script gebruikt `pg_dump` wanneer `DATABASE_URL` PostgreSQL is en anders een kopie van `local/db.sqlite3`. Het maakt daarnaast een bestandsarchief en SHA-256-lijst. Voor productie moet de uitvoerdirectory naar een beschermd extern volume worden gesynchroniseerd; een back-up naast de live database is geen volwaardige back-up.
Herstel één databasebestand:
```bash
./scripts/restore.sh /pad/naar/vacatureradar-YYYYMMDDTHHMMSSZ.sql.gz
# of
./scripts/restore.sh /pad/naar/vacatureradar-YYYYMMDDTHHMMSSZ.sqlite3
```
## PostgreSQL-consistente back-up
Aanbevolen vanuit een beheercontainer of host met clienttools:
```bash
pg_dump --format=custom --no-owner --no-acl "$DATABASE_URL" \
> vacatureradar-$(date -u +%Y%m%dT%H%M%SZ).dump
sha256sum vacatureradar-*.dump > SHA256SUMS
```
De huidige scriptvariant gebruikt gecomprimeerde SQL voor brede compatibiliteit. Een custom-formatdump maakt selectiever herstel mogelijk; kies één formaat en test exact dat formaat.
## Veilige herstelprocedure
1. Meld onderhoud en blokkeer externe toegang.
2. Stop scheduler en worker; stop daarna web zodat geen writes meer gebeuren.
3. Maak een forensische kopie van de defecte huidige database/volumes.
4. Verifieer checksum, datum, appversie en migratieniveau van de back-up.
5. Herstel naar een lege database of aparte testinstantie.
6. Gebruik de code/image die bij het back-upmoment hoort.
7. Voer migraties alleen vooruit uit nadat de restore op dat oude niveau goed opent.
8. Draai health, modelcounts, fixture-import en applicatiesmoke-test.
9. Start web, daarna worker en precies één scheduler.
10. Documenteer RPO/RTO, verloren wijzigingen en vervolgactie.
## Hersteltest
Een hersteltest is pas geslaagd wanneer:
- checksum is geverifieerd;
- database start zonder reparaties;
- migratiestatus verklaarbaar is;
- aantallen gebruikers, profielen, jobs, bronaliassen, sollicitaties en outboxrecords plausibel zijn;
- een bestaande vacaturedetailpagina opent;
- een fixture idempotent importeert;
- een score en digest kunnen worden aangemaakt;
- geen live mailbox of bron onbedoeld wordt gepolld in de testomgeving.
Gebruik in een hersteltest `IMAP_ENABLED=0`, `CELERY_TASK_ALWAYS_EAGER=1`, consolemail en geen live source polling.
## Verwijdering en privacy
Back-ups verlengen feitelijk de bewaartermijn. Documenteer hoe verwijderde persoonsgegevens na de normale rotatie ook uit back-ups verdwijnen. Gebruik korte retentie voor ruwe vacaturemails en bewaar geen mailboxcredentials in databasedumps.
+36
View File
@@ -0,0 +1,36 @@
# Geodata import voor Belgische locaties
Deze module gebruikt lokaal beheerde postcode/gemeentedata om locatiecoördinaten te kunnen afleiden.
## CLI
```bash
python manage.py import_geodata <pad-naar-geodata.csv> \
--source-name local \
--dataset-version 2026-01-01 \
--license-name "Naam van licentie"
--license-url "https://..."
--replace
```
- Gebruik `--validate-only` om validatie uit te voeren zonder databasewijzigingen.
- Gebruik `--replace` wanneer een bron volledig opnieuw opgebouwd moet worden.
## Verplichte CSV-kolommen
- `postal_code` (exact 4 cijfers)
- `municipality`
- `region`
- `latitude`
- `longitude`
## Validatie
- Niet-numerieke coördinaten worden geweigerd.
- Latitude en longitude moeten binnen geldige range vallen.
- Dubbels op `postal_code + municipality` in de invoer worden afgekeurd.
- Bestaande lookupregels van dezelfde `source_name`/`dataset-version` blokkeren import zonder `--replace`.
## Fallback
Zonder geïmporteerde geodata blijft afstandsafleiding op "onbekend".
+40
View File
@@ -0,0 +1,40 @@
# Gitea push-to-deploy (self-hosted, geen handmatige variabelenvulling)
Push naar `main` of `master` triggert automatisch:
- checkout op de server runner
- `bash scripts/deploy_docker.sh`
- deploy met `docker compose up -d --build`
Deze workflow gebruikt defaults en hoeft niet te wachten op repo-secrets voor host/ssh/credentials.
## Vereiste eenmalige setup op de server
1. Start en registreer een self-hosted Gitea-runner voor deze repo.
2. Zorg dat die runner Docker/Compose kan draaien in de projectmap waar de runner draait.
3. Laat éénmaal dit script draaien op de server:
```bash
bash scripts/deploy_docker.sh
```
Daarna is het gedrag volledig push-only:
- `git push` naar `main` of `master` → automatische deploy
## Defaults
- `APP_HOST`: hostname van de server (fallback: `hostname`)
- `APP_SCHEME`: `http`
- `DJANGO_DEBUG`: afgeleid uit `APP_SCHEME`
- `0` als `APP_SCHEME=https`
- `1` als `APP_SCHEME=http`
- `DEPLOY_COMPOSE_FILE`: `docker-compose.yml`
- `APP_ORIGINS`, `APP_HOSTS`, `APP_PORT`: optioneel en veilig overnemen als op runner gezet
## Endpoint na deploy
- `http://<runner-host>:1226/health/ready/`
- `http://<runner-host>:1226/health/live/`
Voor productie: zet later op de runner de gewenste runtime-omgeving (bijv. reverse proxy + TLS) en herstart push.
+101
View File
@@ -0,0 +1,101 @@
# Lokale ontwikkeling
## Ondersteunde omgeving
- Linux, macOS of WSL2;
- Python 3.12 of 3.13;
- `uv` voor dependency- en lockfilebeheer;
- optioneel Docker Compose voor PostgreSQL, Redis en de volledige stack.
Tests gebruiken standaard SQLite, eager Celery en een in-memory e-mailbackend. Ze hebben geen internet, Redis, mailbox of Ollama nodig.
## Eerste start
```bash
./scripts/codex_bootstrap.sh
uv run python manage.py runserver 0.0.0.0:8080
```
Het script maakt alleen voor lokale ontwikkeling een `.env`, installeert de gelockte dependencies, migreert, laadt veilige demodata en draait alle kwaliteitsgates. De lokaal aangemaakte login is `admin / codex-local-only`. Verwijder of wijzig deze credentials vóór ieder gedeeld of bereikbaar gebruik.
## Handmatige start
```bash
uv sync --all-groups
mkdir -p local media logs backups
cp .env.example .env
# Laat DATABASE_URL leeg voor SQLite en vul een unieke DJANGO_SECRET_KEY in.
uv run python manage.py migrate
uv run python manage.py bootstrap_instance --with-demo
uv run python manage.py runserver 0.0.0.0:8080
```
## Veelgebruikte commando's
```bash
make help
make test
make lint
make format
make verify
uv run python manage.py shell
uv run python manage.py seed_sources
uv run python manage.py import_job_fixture fixtures/pages/sample_jsonld_job.html
```
De volgende achtergrondprocessen zijn alleen nodig wanneer `CELERY_TASK_ALWAYS_EAGER=0`:
```bash
uv run celery -A config worker -l INFO -Q high,default,low
uv run celery -A config beat -l INFO
```
## Demo opnieuw opbouwen
```bash
./scripts/reset_demo.sh
```
Dit verwijdert uitsluitend `local/db.sqlite3`. Gebruik het niet tegen een productievolume.
## Testen per laag
```bash
uv run pytest tests/unit
uv run pytest tests/integration
uv run pytest tests/security
uv run pytest tests/integration/test_pipeline.py -q
uv run pytest --cov=apps --cov=config --cov-report=term-missing
```
Voeg bij iedere parserwijziging een bronfixture toe. Tests mogen alleen een echte netwerkcall doen in een expliciet afgescheiden, handmatig gestarte smoke-test die niet in CI draait.
## Databasewijzigingen
```bash
uv run python manage.py makemigrations
uv run python manage.py migrate
uv run python manage.py makemigrations --check --dry-run
```
Controleer de gegenereerde migratie. Voor destructieve wijzigingen is een ADR, datamigratie, herstelpad en back-uptest verplicht.
## Static files
In development serveert Django static files. Productie gebruikt `collectstatic` in de container en WhiteNoise:
```bash
uv run python manage.py collectstatic --noinput
```
## Troubleshooting
**`uv` ontbreekt:** installeer `uv` volgens de officiële instructies van de gekozen werkmachine en heropen de shell.
**Database lock op SQLite:** stop tweede web/workerprocessen of gebruik lokaal PostgreSQL voor parallel werk.
**Redis connection refused:** zet voor eenvoudige lokale runs `CELERY_TASK_ALWAYS_EAGER=1`, of start Redis/Compose.
**Geen digest zichtbaar:** met console-backend staat de mail in de terminal; controleer daarnaast `DigestOutbox` in Django admin.
**Bron wordt overgeslagen:** controleer `policy=allow`, een toegestane status, `next_run_at`, de domeinpolicy en de platformdenylist. Forceer nooit een fetch om een veiligheidsblokkade te omzeilen.
+118
View File
@@ -0,0 +1,118 @@
# Operationeel runbook
## Dagelijkse controle
Controleer bij voorkeur automatisch en minstens wekelijks handmatig:
- `/health/ready/` is 200;
- web, worker, scheduler, PostgreSQL en Redis zijn actief;
- vrije ruimte op database-, media-, log- en back-upvolume;
- bronnen in `quarantined`, `paused` of met oplopende `failure_count`;
- `DigestOutbox` met status `failed` of langdurig `pending`;
- meest recente geslaagde back-up en laatste hersteltest;
- onverwachte login- of policyfouten in logs.
## Incidentprioriteiten
| Niveau | Voorbeeld | Eerste actie |
|---|---|---|
| P1 | datalek, secret gepubliceerd, ongeautoriseerde netwerkfetch | stop web/worker/scheduler, isoleer netwerk, roteer secrets |
| P2 | database niet beschikbaar, imports corrupt, herhaalde mail | stop schrijvende processen, behoud bewijs, maak snapshot |
| P3 | één adapter kapot, digest vertraagd, bron in quarantaine | pauzeer bron/functie, herstel met fixture en test |
| P4 | copy/layout/probleem zonder datarisico | registreer backlogtaak en plan normale release |
## Web niet ready
1. Controleer liveness. Faalt die ook, inspecteer webcontainer/proces en poortbinding.
2. Bij `database=error:*`: controleer PostgreSQL-status, disk, credentials en netwerknaam.
3. Test vanuit webcontainer of DNS `postgres` resolveert en poort 5432 bereikbaar is.
4. Draai geen migraties herhaald blind; lees eerst de laatste stacktrace.
5. Herstart alleen de falende laag. Een volledige stackrestart kan diagnostiek wissen.
## Worker verwerkt niets
1. Controleer Redis en `CELERY_BROKER_URL`.
2. Controleer workerqueues `high,default,low`.
3. Inspecteer actieve en gereserveerde taken:
```bash
celery -A config inspect ping
celery -A config inspect active
celery -A config inspect reserved
```
4. Kijk naar timeouts, policyblokkades en retryloops.
5. Start niet meerdere schedulers; dat kan dubbele planning veroorzaken, ook al zijn imports idempotent.
## Bron faalt of gaat in quarantaine
1. Laat quarantaine staan; forceer geen fetch.
2. Lees alleen categorie, status, URL-host en beperkte foutmelding in `SourceRun`.
3. Controleer of het domein, redirectdoel, robots-/voorwaardenstatus of contenttype veranderde.
4. Download geen pagina met een onbeveiligde shellopdracht vanaf de productiehost.
5. Reproduceer met een gesaneerde fixture in een ontwikkelomgeving.
6. Werk parser/policy en securitytests bij.
7. Zet bron pas terug op `trial`, voer één begrensde run uit en promoveer daarna eventueel naar `active`.
## IMAP-import stopt
1. Zet `IMAP_ENABLED=0` om lockout of herhaalde fouten te beperken.
2. Controleer host, poort, TLS, mailboxnaam en app-password.
3. Verifieer dat de mailbox niet is hernoemd en het account niet is vergrendeld.
4. Test eerst read-only met `IMAP_MARK_SEEN=0`.
5. Een reeds verwerkte mail is idempotent via message identity; verwijder geen `EmailMessageRecord` om opnieuw te proberen zonder analyse.
## Digest ontbreekt of dubbel
- Controleer actief profiel, digesttijd/zone en `DIGEST_RECIPIENT`.
- Controleer `DigestOutbox` op datum en status.
- Bij SMTP-fout: corrigeer configuratie en retry dezelfde outbox gecontroleerd.
- Maak geen tweede outboxrecord handmatig; uniciteit per profiel/datum is de duplicaatbarrière.
- Een lege digest is toegestaan wanneer geen vacature boven de ingestelde drempel valt.
## Verdachte of kwaadaardige vacature-inhoud
1. Open de originele bron niet automatisch in een ingelogde browser.
2. Zet bij twijfel het raw document op `quarantined` zodat retentie het bewijs niet wist.
3. Controleer opgeslagen gesaniteerde tekst en veldherkomst.
4. Voeg de payload als geanonimiseerde fixture toe wanneer een parser/sanitizerfout bestaat.
5. Laat AI-output nooit een commando, URL-fetch of configuratiemutatie veroorzaken.
## Sollicitatiedossier exporteren en opschonen
1. Open de sollicitatielijst in een ingelogde gebruiker-sessie en ga naar het dossier.
2. Controleer dat de timeline alleen mutaties voor dat dossier bevat (status, notities, contact, snapshot).
3. Exporteer met het dossier-exportendpoint om een zip met `application.json`, `timeline.csv`, `application_print.html` te controleren.
4. Controleer het zipbestand lokaal op:
- correcte dossier-ID;
- geen raw e-mailbody of bron-payload;
- geen externe assets of scriptrequests in HTML.
5. Verwijderen mag alleen op gebruikersniveau (`delete` endpoint); herhaalde delete-aanroepen moeten zonder fout uitkomen.
## Schijfruimte laag
1. Stop scheduler en worker wanneer de database- of PostgreSQL-volume kritiek vol is.
2. Controleer oude back-ups, containerlogs, raw-documentretentie en Ollama-modellen.
3. Verwijder nooit willekeurig PostgreSQL-bestanden.
4. Laat `cleanup_raw_documents` alleen niet-gequarantaineerde verlopen documenten opruimen.
5. Vergroot volume of herstel rotatie, start daarna database, web, worker en scheduler in die volgorde.
## Secret vermoedelijk gelekt
1. Stop externe toegang en disable betrokken account/token.
2. Roteer Django secret, adminwachtwoord, database-, SMTP-, IMAP- en registrycredentials voor zover betrokken.
3. Trek oude sessies in door sessies te verwijderen of het secret te wijzigen.
4. Controleer logs en source-runs op misbruik, zonder gevoelige payload verder te kopiëren.
5. Maak een incidentnotitie met exacte tijden, scope, rotaties en herstelvalidatie.
## Herstelvalidatie
Na ieder incident:
```bash
python manage.py check
python manage.py migrate --plan
python manage.py shell -c "from apps.core.health import readiness; print(readiness())"
```
Controleer daarna login, één fixture-import, scoreberekening, bronlijst, sollicitatielijst en mailbackend. Start live polling pas nadat deze controles slagen.
+146
View File
@@ -0,0 +1,146 @@
# Bron onboarden
Een nieuwe bron wordt nooit direct als onbeperkt actief beschouwd. Gebruik het traject **candidate → trial → active** en ga bij veiligheids- of kwaliteitsproblemen naar **quarantined**.
## Toegestane broncategorieën
Voorkeursvolgorde:
1. openbare carrièrepagina van de feitelijke werkgever;
2. openbare, door de werkgever bedoelde ATS-vacaturepagina;
3. expliciete RSS/Atom-feed of sitemap met vacatures;
4. vacaturemail die de gebruiker zelf heeft geactiveerd;
5. handmatige browserimport door de gebruiker.
Niet onboarden zonder expliciete, aantoonbare toestemming:
- ingelogde zoekresultaten of profielpagina's;
- pagina's achter CAPTCHA, anti-bot challenge of toegangscontrole;
- denylistplatformen via directe crawling;
- verborgen/private endpoints die alleen door reverse engineering zijn gevonden;
- bronnen waarvan robots/voorwaarden of technische signalen automatisering verbieden.
## Reviewchecklist
Leg per bron vast:
- naam, bronsoort, hoofddomein en exacte start-URL;
- eigenaar/eindwerkgever en eventuele ATS-provider;
- publieke toegankelijkheid zonder login;
- datum en samenvatting van robots- en voorwaardenreview;
- toegestane paden en eventuele uitgesloten paden;
- parserstrategie: JSON-LD, RSS, gespecialiseerd ATS of generieke HTML;
- verwachte frequentie en minimale interval;
- maximaal documentvolume en paginatie;
- welke velden daadwerkelijk aanwezig zijn;
- retentiebehoefte en mogelijke persoonsgegevens;
- contact-/user-agentinformatie indien passend;
- rollback/quarantainecriterium.
`robots.txt` alleen is geen volledige juridische toestemming, en afwezigheid ervan is geen automatische toestemming. Het technische bronbeleid ondersteunt een conservatieve beslissing; de beheerder blijft verantwoordelijk voor de bronreview.
## Candidate aanmaken
Gebruik Django admin of `seed_sources` met een gecontroleerd YAML-record. Begin met:
```yaml
name: Voorbeeld Werkgever
source_type: employer
base_url: https://careers.example.org/jobs
status: candidate
policy: review
parser_key: auto
strict_mode: true
honor_robots: true
crawl_interval_minutes: 720
minimum_interval_seconds: 30
max_concurrency: 1
```
Zet `policy: allow` pas na review. Een onbekende bron blijft in strict mode zonder fetch.
## Fixture vóór live request
Bewaar een gesaneerd voorbeeld onder `fixtures/pages`, `fixtures/feeds` of `fixtures/emails`. Verwijder trackingtokens, persoonsgegevens en niet-noodzakelijke volledige teksten. Schrijf tests voor:
- normale vacature;
- ontbrekende optionele velden;
- nul vacatures;
- gewijzigde markup of meerdere jobs;
- kwaadaardige HTML en onveilige links;
- idempotente replay;
- parserconfidence en waarschuwingen.
## Trialrun
1. Zet bron op `trial` en `policy=allow`.
2. Voer één handmatige run uit via de retryknop of Celerytask.
3. Controleer status, final URL, bytes, parser, warnings en tellers.
4. Open alleen gesaniteerde jobweergave; vergelijk steekproefsgewijs met de publieke bron.
5. Controleer canonieke URL, werkgever, locatie, datum, verloopdatum, taal en duplicaten.
6. Verifieer dat redirectdoelen, rate limit en conditional requests correct zijn.
7. Laat minimaal twee geplande cycli goed verlopen vóór promotie naar `active`.
## Automatische quarantainecriteria
Een bron moet worden gepauzeerd of in quarantaine gezet bij:
- redirect naar denylist, login, private adresruimte of onverwacht domein;
- herhaalde 401/403/429, CAPTCHA of anti-botpagina;
- contenttype/grootte buiten beleid;
- parseroutput met plotseling nul jobs terwijl de bron zichtbaar jobs bevat;
- abnormale volumestijging of duplicaatstorm;
- HTML-sanitization/securityfout;
- voorwaardenwijziging of verlopen bronreview;
- opeenvolgende fouten boven de vastgelegde drempel.
## Gespecialiseerde ATS-adapter
Voeg alleen een adapter toe wanneer meerdere bronnen hetzelfde stabiele publieke formaat gebruiken of de generieke adapter onvoldoende bewijs levert. De adapter:
- krijgt geen credentials;
- gebruikt uitsluitend gedocumenteerde publieke jobdata of publieke pagina's;
- implementeert het interne adaptercontract;
- heeft providerfixtures en contracttests;
- valt veilig terug zonder globale pipeline te breken;
- documenteert paginatie, sluitingssignalen en rate limits.
## Bron verwijderen
Pauzeer eerst. Behoud canonieke vacatures en herkomst zolang productretentie dat vereist; verwijder niet blind clusters die ook andere aliassen hebben. Verwijder raw documents volgens retentie, trek de policy in en noteer de reden en datum.
## Reviewbeleid bij bronbeoordeling
- Een bron mag alleen automatisch gefetcht worden wanneer een actuele `SourcePolicyReview` bestaat met geldige reden, scope en vervaldatum.
- Terms review blijft menselijk, niet automatisch door AI of heuristiek.
- Bij ontbrekende of verlopen review of bij expliciet `pause`/`deny` besluit blokkeert de taakuitvoering.
## Handmatige import in de interface
Voor uitzonderlijke vacatures kan de beheerder handmatig importeren via de bronpagina:
- navigeer naar `Brongezondheid` en gebruik het "Handmatige import" formulier;
- gebruik de bookmarklet om de huidige pagina-URL te vullen in `source_url`;
- of plak direct relevante vacaturetekst in het tekstveld wanneer fetch niet is toegestaan.
De bookmarklet stuurt alleen `source_url` naar de import-URL, zonder secret of broninhoud.
Na import toont de bronlijst:
- de gekozen modus (`url` of `paste`);
- bron-ID/bron-URL;
- aantallen herkend, nieuw en duplicaat;
- waarschuwingen;
- links naar vacaturedetail voor direct vervolg.
## Providerdetails (VR-106)
Voor het onboarden van publieke ATS-bronnen moet het bronrecord minimaal een van de volgende providerspecificaties gebruiken:
- Greenhouse (`*.greenhouse.io` / `*.boards.greenhouse.io`) — max 15 requests/min, alleen publiek toegankelijke jobdata.
- Lever (`jobs.lever.co`) — max 60 requests/min, alleen `jobs.lever.co` en officiële publieke endpoints.
- Recruitee (`*.recruitee.com`) — max 60 requests/min, geen login/partner endpoint.
- SmartRecruiters (`*.smartrecruiters.com`) — max 30 requests/min, alleen publieke vacaturepagina/feeds.
- Workable (`apply.workable.com`) — max 30 requests/min, alleen publieke vacaturepagina/feeds.
Bij twijfel altijd op `review` blijven en eerst via een trialrun met gesloten evaluatiecriteria (gesloten job, lege joblijst, markeringswijziging) valideren.
+163
View File
@@ -0,0 +1,163 @@
# Unraid-deployment
Deze handleiding gebruikt `docker-compose.unraid.yml` als declaratieve bron van waarheid. Unraid-installaties verschillen in gebruikte Compose Manager, reverse proxy en shares; behoud de hieronder genoemde volumes, secrets en netwerkgrenzen ook wanneer de UI de services afzonderlijk aanmaakt.
## Vooraf
Maak deze directories:
```text
/mnt/user/appdata/vacatureradar/config
/mnt/user/appdata/vacatureradar/media
/mnt/user/appdata/vacatureradar/logs
/mnt/user/appdata/vacatureradar/postgres
/mnt/user/appdata/vacatureradar/redis
/mnt/user/appdata/vacatureradar/ollama # alleen bij lokale AI
/mnt/user/backups/vacatureradar
```
Gebruik bij voorkeur een cache-backed `appdata`-share en neem die afzonderlijk in je back-upregime op. De PostgreSQL-datadir mag niet gelijktijdig door file-syncsoftware worden gemuteerd.
## Imagekeuze
De Unraid-compose bevat bewust `ghcr.io/CHANGE_ME/vacatureradar:latest`. Codex kan code, Dockerfile en CI afwerken zonder registrycredentials, maar voor productie moet een van deze paden worden gekozen:
1. bouw lokaal op de Unraid-host en vervang `image:` door een vaste lokale tag;
2. publiceer vanuit CI naar een private registry en pin op een immutable digest;
3. gebruik `build:`, mits de gekozen Compose-plugin builds betrouwbaar ondersteunt.
Gebruik in productie geen zwevende `latest` zonder gecontroleerd rollbackpad.
## Configuratiebestand
Kopieer of genereer `.env` naar:
De deploy-helper `scripts/deploy_docker.sh` vult ontbrekende sleutels op basis van veilige defaults.
Zet voor publieke productie vooraf de gewenste variabelen:
```text
/mnt/user/appdata/vacatureradar/config/.env
APP_HOST=jobs.example.be
APP_SCHEME=https
DJANGO_DEBUG=0
SESSION_COOKIE_SECURE=1
CSRF_COOKIE_SECURE=1
SECURE_SSL_REDIRECT=1
```
Gebruik daarna de helper (zie hieronder).
Beperk de bestandsrechten van `.env` tot de beheerder. Voeg geen secrets toe aan de ZIP, Git, screenshots of supportlogs.
## Eerste uitrol
1. Controleer alle `CHANGE_ME`-waarden.
2. Gebruik de deploy-helper voor een snelle eerste start:
```bash
cd /app/VacatureRadar
APP_HOST=jobs.example.be APP_SCHEME=https DJANGO_DEBUG=0 \
SESSION_COOKIE_SECURE=1 CSRF_COOKIE_SECURE=1 SECURE_SSL_REDIRECT=1 \
bash scripts/deploy_docker.sh
```
3. Start eerst PostgreSQL en Redis.
4. Start web; de entrypoint voert migraties uit.
5. Start worker en scheduler.
6. Controleer `GET /health/live/` en `GET /health/ready/`.
6. Voer eenmalig in de webcontainer uit:
```bash
python manage.py bootstrap_instance
python manage.py collectstatic --noinput
```
7. Meld lokaal aan en wijzig het bootstrapwachtwoord.
8. Activeer nog geen live bron of mailbox voordat bronbeleid, retentie en back-up zijn gecontroleerd.
## Releaseverificatie en upgradepad
VR-117 voegt `./scripts/release_verify.sh` toe als release-check met artifacts voor package, checksum, configdiff, sbom en upgrade/rollback.
Het script bewaart:
- `release-artifacts/<ts>/artifacts/release-smoke.json`
- `release-artifacts/<ts>/artifacts/smoke-restored-validate.json`
- `release-artifacts/<ts>/artifacts/upgrade-rollback.md`
- `release-artifacts/<ts>/artifacts/checksums.txt`
- `release-artifacts/<ts>/artifacts/config-diff.txt`
De restorecontrole vergelijkt primaire en gerestoreerde modelcounts in beide smoke-rapporten.
## Reverse proxy en TLS
Expose de applicatie niet rechtstreeks op internet. Plaats Nginx Proxy Manager, Traefik, Caddy of een gelijkwaardig beheerd reverse-proxyprofiel voor poort 1226. Vereisten:
- geldig TLS-certificaat;
- alleen HTTPS extern;
- `X-Forwarded-Proto` correct doorgeven;
- request-bodylimiet klein houden; er is geen algemene upload-API;
- optioneel extra access control/VPN voor persoonlijke installatie;
- geen publieke toegang tot PostgreSQL, Redis of Ollama.
Wanneer een proxy op hetzelfde Docker-netwerk draait, publiceer poort 1226 alleen intern. Wanneer Unraid routing een hostpoort vereist, beperk die via firewall/VLAN tot de proxy of het beheernetwerk.
## Mailbox
Gebruik een aparte mailbox of alias, niet de hoofdmailbox. Begin met:
```dotenv
IMAP_ENABLED=0
IMAP_MARK_SEEN=0
```
Na een succesvolle fixture- en read-onlytest:
```dotenv
IMAP_ENABLED=1
IMAP_HOST=<host>
IMAP_USER=<account>
IMAP_PASSWORD=<app-password>
IMAP_MAILBOX=INBOX
IMAP_USE_SSL=1
```
Het account heeft alleen mailboxrechten nodig. Gebruik waar beschikbaar een app-password en schakel interactieve login op die mailbox niet uit zolang herstel nodig is.
## Ollama
Ollama is optioneel. Start de composeprofile pas wanneer de deterministische kern goed werkt:
```bash
docker compose -f docker-compose.unraid.yml --profile ai up -d
```
Pin een modelnaam in `OLLAMA_MODEL`; zonder model blijft `OLLAMA_ENABLED=0`. Stel Ollama niet extern bloot. Modeloutput wordt als onbetrouwbare gestructureerde data behandeld en mag geen harde regel of sollicitatieactie uitvoeren.
## Upgraden
1. Maak en verifieer een database- en configuratieback-up.
2. Lees `CHANGELOG.md` en de releaseartefacten.
3. Als de release nog niet geverifieerd is, voer `./scripts/release_verify.sh` lokaal uit.
4. Pull/bouw de nieuwe immutable image.
5. Stop scheduler en worker, daarna web.
6. Start web en laat migraties uitvoeren.
7. Controleer health, login en release-smoke-rapport.
8. Start worker en scheduler.
9. Bewaar de vorige image/digest totdat één volledige schedulercyclus goed verliep.
## Rollback
Bij een codefout zonder incompatibele migratie: pin de vorige image en herstart web/worker/scheduler. Bij een incompatibele datamigratie: stop alle schrijvers, herstel de vooraf gemaakte databaseback-up en gebruik de overeenkomende vorige image. Voer nooit een restore uit terwijl de worker of scheduler schrijft.
## Productiechecklist
- [ ] alle `CHANGE_ME`-waarden verwijderd;
- [ ] debug uit en secure cookies aan;
- [ ] TLS en host/origin exact ingesteld;
- [ ] PostgreSQL/Redis/Ollama niet publiek;
- [ ] dagelijkse back-up plus periodieke hersteltest;
- [ ] aparte IMAP-mailbox met minimale rechten;
- [ ] bronpolicy per actieve bron op `allow` met reviewdatum;
- [ ] adminwachtwoord gewijzigd;
- [ ] healthmonitoring en vrije schijfruimtebewaking;
- [ ] retentiejob en logrotatie gecontroleerd.