roosterwijs/DEPLOY.md
Ramon 500cf6c558
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run
Herstel installatie-instructies en hard deploy-script (v0.2.03 beta)
De installatiestappen in de README waren niet uitvoerbaar en het
deploy-script kon stilzwijgend werk vernietigen.

README:
- `python manage.py migrate` liep vast op `RuntimeError: DJANGO_SECRET_KEY
  ontbreekt`. DEBUG staat standaard uit en dan is die sleutel verplicht;
  de instructies noemden `DJANGO_DEBUG` nergens. Nu toegevoegd, met uitleg.
- `createsuperuser` stond als "optioneel, voor /admin/". Dat klopt niet: de
  hele API staat op `IsAuthenticated`, dus zonder account kom je de app
  helemaal niet in. Nu gemarkeerd als verplichte stap.
- Stap voor een virtual environment toegevoegd (pip installeerde anders in
  de systeem-Python, wat op recente distributies afketst op PEP 668).
- Node-eis vermeld: Vite 8 vereist 20.19+ of 22.12+.
- Verouderde beschrijvingen bijgewerkt: de tabs van het roosterscherm
  (Week/Genereren/Instellingen in plaats van Weekrooster/Conflicten/
  Schooljaar/Instellingen), Groepsindeling is een eigen scherm en geen tab,
  en er zijn twaalf modules in plaats van "twee voorbeeldmodules".

DEPLOY.md:
- `git clone .../Roostersoftware.git` + `cd Roostersoftware` gebruikte de
  verkeerde repo-naam; dat is `roosterwijs`.
- CSRF-origin en FRONTEND_BASE_URL stonden als http-voorbeeld; achter TLS
  moet dat https zijn.
- Rechtgezet: `DJANGO_CSRF_TRUSTED_ORIGINS` blokkeert het inloggen in de app
  niet (DRF-views zijn `csrf_exempt`), maar wel de Django-admin.
- Vermeld dat er nergens automatisch een account wordt aangemaakt.

scripts/deploy.sh:
- Stopt nu als `.env` ontbreekt; anders start de stack met lege variabelen
  en faalt de backend met een onduidelijke fout.
- Stopt bij lokale wijzigingen in plaats van ze met `git reset --hard`
  weg te gooien; bewust overschrijven kan met `--force`.
- Controleert vooraf of Docker en de compose-plugin er zijn.
- Toont welke commits erbij komen (oud -> nieuw) in plaats van alleen de
  nieuwe HEAD.
- Controleert na afloop of de backend antwoordt en of er een actief account
  bestaat, met het createsuperuser-commando als dat er niet is.

frontend/Dockerfile:
- Bouwt op node:22-alpine in plaats van node:20-alpine, gelijk aan de CI en
  ruim binnen de Node-eis van Vite 8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAQ4Np13v8fwbTtKHKnwDz
2026-08-19 14:57:49 +02:00

8 KiB

Deploy-workflow (kort)

Belangrijk — gebruik de scripts. Eerdere mislukte pushes kwamen door een achtergebleven .git/index.lock die git commit blokkeerde (dan valt er niets te pushen en zegt git "up-to-date"). De scripts ruimen dat automatisch op en controleren of de push echt is doorgekomen.

1. Op je dev-machine (Windows / PowerShell), in de repo-map:

.\scripts\push.ps1 "korte omschrijving van de wijziging"

Dit verwijdert een eventuele lock, commit (alleen als er iets is), pusht naar origin/<branch> en meldt OK of LET OP (met reden) — geen stille mislukkingen meer. Krijg je "LET OP", dan staat de oorzaak in de git-uitvoer erboven (meestal een SSH-/sleutelprobleem).

2. Op de test-server, in de repo-map:

./scripts/deploy.sh

Dit controleert eerst of Docker en .env aanwezig zijn, haalt dan de laatste code op (git reset --hard origin/<branch>), herbouwt de containers (incl. migraties via de entrypoint) en controleert daarna of de frontend-bundel er staat, of de backend antwoordt en of er een actief account is om mee in te loggen.

Staan er lokale wijzigingen in de map op de server, dan stopt het script: git reset --hard zou die weggooien. Zet ze eerst veilig, of forceer bewust:

./scripts/deploy.sh --force

Nieuwe modules verschijnen daarna pas in de app nadat je ze aanzet via ⚙ → Modulebeheer.


E-mail aanzetten (wachtwoordreset-links)

Zonder configuratie gaan e-mails naar de console-log van de backend-container (zichtbaar met docker compose logs backend) — prima om te testen. Voor échte e-mail via SMTP zet je in .env op de server:

DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.jouwprovider.nl
EMAIL_PORT=587
EMAIL_HOST_USER=postvak@jouwschool.nl
EMAIL_HOST_PASSWORD=...
EMAIL_USE_TLS=1
DEFAULT_FROM_EMAIL=Roosterwijs <noreply@jouwschool.nl>
FRONTEND_BASE_URL=https://rooster.example.nl

FRONTEND_BASE_URL bepaalt de basis van de resetlink in de e-mail (zonder FRONTEND_BASE_URL wordt die afgeleid uit het verzoek). Daarna docker compose up -d (of ./scripts/deploy.sh) om de nieuwe waarden te laden.


Draaien op de test-server (Docker)

De stack bestaat uit drie containers:

Container Rol
db PostgreSQL (data in een Docker-volume)
backend Django via gunicorn
nginx serveert de React-frontend en stuurt /api + /admin door naar de backend

Je hoeft op de server niets te installeren behalve Docker zelf (met de docker compose-plugin). Python, Node, Postgres enz. zitten in de images.

Eenmalig: server klaarzetten

  1. Zorg dat Docker + Compose op de test-server staan:

    docker --version
    docker compose version
    
  2. Haal de code op:

    git clone <jouw-forgejo-url>/roosterwijs.git
    cd roosterwijs
    
  3. Maak het .env-bestand aan vanuit het voorbeeld en vul echte waarden in:

    cp .env.example .env
    nano .env
    

    Belangrijk om aan te passen:

    • DJANGO_SECRET_KEY — genereer met python3 -c "import secrets; print(secrets.token_urlsafe(50))"
    • DJANGO_ALLOWED_HOSTS — de hostnaam/IP van de test-server
    • DJANGO_CSRF_TRUSTED_ORIGINS — exact de origin die de browser gebruikt, dus https://<host> achter TLS (of http://<host>:8080 zonder TLS)
    • POSTGRES_PASSWORD — een sterk wachtwoord

    .env staat in .gitignore en komt dus niet in git — dat hoort zo.

Starten

docker compose up -d --build

Wat er dan gebeurt: Postgres start, de backend wacht tot de database er is, voert automatisch de migraties en collectstatic uit, en gunicorn + nginx komen op. De app is bereikbaar op:

http://<server-host>:8080

(Poort 8080 staat ingesteld in docker-compose.yml bij de nginx-service; pas hem daar aan als die poort bezet is.)

Eerste keer: beheerder aanmaken

docker compose exec backend python manage.py createsuperuser

Dit account heb je hoe dan ook nodig: de hele API staat op IsAuthenticated, dus zonder gebruiker kom je ook de app zelf niet in — niet alleen /admin/. Zonder dit commando wordt er nergens automatisch een account aangemaakt; entrypoint.sh draait alleen migraties en collectstatic.

Updaten na nieuwe code

git pull
docker compose up -d --build

Migraties draaien automatisch mee bij het opstarten van de backend.

Handige commando's

Doel Commando
Logs volgen docker compose logs -f
Alleen backend-logs docker compose logs -f backend
Status containers docker compose ps
Stoppen docker compose down
Stoppen incl. database wissen docker compose down -v (let op: gegevens weg!)
Migratie handmatig docker compose exec backend python manage.py migrate
Django-shell docker compose exec backend python manage.py shell

Aandachtspunten

  • HTTPS/domein: deze opzet draait op poort 8080 zonder TLS. Voor productie zet je er een reverse proxy (bv. Caddy of Traefik) vóór die HTTPS regelt, of je breidt de nginx-config uit met certificaten. De nginx-container is daar al het logische punt voor. Zet daarna DJANGO_SECURE=1 in .env — dat activeert veilige cookies, HSTS en een https-redirect in Django.

    Zie hieronder wat er dan verder moet kloppen.

Draaien achter TLS: checklist

Met DJANGO_SECURE=1 moeten drie dingen kloppen, anders lukt inloggen niet terwijl het inlogscherm zelf gewoon laadt (dat wordt door nginx geserveerd, zonder tussenkomst van Django).

  1. De proxy moet X-Forwarded-Proto: https meesturen. Django leest die header (SECURE_PROXY_SSL_HEADER) om te bepalen of de verbinding veilig is. Ontbreekt hij, dan denkt Django dat het http is en stuurt het elk verzoek naar /api en /admin door naar https — waarna de proxy het weer als http aanlevert: een oneindige redirect-lus (ERR_TOO_MANY_REDIRECTS) en dus een mislukte login. Caddy en Traefik zetten deze header standaard; controleer het als je een eigen nginx/Apache ervoor hebt.
  2. DJANGO_CSRF_TRUSTED_ORIGINS moet de https-origin zijn, exact zoals de browser hem gebruikt (bv. https://rooster.example.nl, zonder poort op 443). Dit geldt voor de Django-admin en andere gewone Django-formulieren: staat hier een http://-adres, dan weigert /admin/ je aanmelding met een CSRF-fout (HTTP 403). Het inloggen in de app zelf loopt hier níet op stuk — DRF-views zijn csrf_exempt en SessionAuthentication controleert CSRF pas als er al een sessie is.
  3. DJANGO_ALLOWED_HOSTS moet de publieke hostnaam bevatten, anders volgt een 400 Bad Request.

Controleren of de header goed aankomt:

docker compose logs backend | grep -i "Mislukte inlogpoging"   # bereikt de login de backend?
curl -sI https://<jouw-domein>/api/auth/me/ | head -n 1        # 200/401 = goed, 301 = lus

Krijg je bij die curl een 301, dan komt X-Forwarded-Proto niet goed door.

  • Beperk poort 8080 tot de proxy: in docker-compose.yml staat "8080:80", wat op álle netwerkinterfaces luistert. Daardoor is de app ook rechtstreeks via http://<server>:8080 te bereiken — buiten TLS om. Draait je reverse proxy op dezelfde machine, maak er dan "127.0.0.1:8080:80" van. Draait de proxy op een andere host, laat het dan staan en zet er een firewall op: nginx vertrouwt de X-Forwarded-Proto van de aanroeper.
  • Inloggen verplicht: de hele API vereist een ingelogde gebruiker; de frontend toont eerst een inlogscherm. Maak gebruikers aan via createsuperuser (zie boven) of in de Django-admin. Modulebeheer is voorbehouden aan beheerders (is_staff).
  • Backups: de database leeft in het volume pgdata. Maak hiervan backups, bijvoorbeeld met docker compose exec db pg_dump -U rooster rooster > backup.sql.
  • DJANGO_DEBUG blijft 0 op de server. Zet hem nooit op 1 in productie.
  • Test eerst hier, dan productie: bouw het image één keer, test op deze server, en draai exact dezelfde code/compose op de productieserver met een eigen .env.