Nieuw: scripts/install.sh — installeert de stack in zeven zichtbare stappen
met drie vragen, standaard in /srv/server-up (aan te passen met
ROOSTERWIJS_DIR). Het script:
- controleert git, docker, de compose-plugin en of de daemon bereikbaar is;
- maakt de doelmap aan, of legt uit welk commando daarvoor nodig is als de
rechten ontbreken (roept zelf geen sudo aan);
- kloont of werkt de checkout bij;
- laat kiezen waar de gegevens komen (zie hieronder);
- genereert .env met een willekeurige DJANGO_SECRET_KEY en een sterk
databasewachtwoord, en zet hostnaam, CSRF-origin en DJANGO_SECURE op basis
van twee vragen; een bestaande .env wordt nooit zomaar overschreven;
- bouwt en start de containers;
- meldt of er een account is en biedt aan er een aan te maken.
Opslagkeuze bij de installatie:
- docker-compose.yml gebruikt nu ${DATA_DB}, ${DATA_MEDIA} en ${DATA_STATIC}.
Een naam = Docker-volume, een pad = gewone map naast de code.
- De standaardwaarden zijn de bestaande named volumes, dus installaties die
al draaien veranderen niet en er gaan geen gegevens verloren.
- Kies je voor mappen, dan komt alles onder /srv/server-up/data/.
Verder:
- .gitignore: /data/ uitgesloten. Zonder dat zou de gegevensmap als lokale
wijziging gelden en zou deploy.sh weigeren te draaien.
- deploy.sh maakt de gegevensmappen aan als de opslagkeuze paden gebruikt,
zodat Docker ze niet als root aanmaakt.
- DEPLOY.md en README: installatie via het script, de opslagkeuze in een
tabel, en de dump/restore-stappen die nodig zijn bij het omzetten van
volumes naar mappen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAQ4Np13v8fwbTtKHKnwDz
249 lines
9.5 KiB
Markdown
249 lines
9.5 KiB
Markdown
# 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:
|
|
|
|
```powershell
|
|
.\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:
|
|
|
|
```bash
|
|
./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:
|
|
|
|
```bash
|
|
./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
|
|
|
|
Het installatiescript doet alles hieronder in zeven stappen en stelt drie
|
|
vragen. Standaard komt alles in **`/srv/server-up`**.
|
|
|
|
```bash
|
|
git clone <jouw-forgejo-url>/roosterwijs.git /tmp/roosterwijs-install
|
|
/tmp/roosterwijs-install/scripts/install.sh
|
|
```
|
|
|
|
Heb je nog geen schrijfrechten op `/srv`, dan zegt het script welk commando je
|
|
eenmalig als beheerder moet draaien. Een andere map kan met:
|
|
|
|
```bash
|
|
ROOSTERWIJS_DIR=/pad/naar/map ./scripts/install.sh
|
|
```
|
|
|
|
Het script controleert Docker, haalt de code op, vraagt waar de gegevens moeten
|
|
staan, genereert `.env` met een willekeurige `DJANGO_SECRET_KEY` en een sterk
|
|
databasewachtwoord, bouwt de containers en biedt aan een beheerder aan te maken.
|
|
|
|
### De opslagkeuze
|
|
|
|
Bij stap 4 kies je waar database, media en statics terechtkomen:
|
|
|
|
| Keuze | Waar | Wanneer |
|
|
|-------|------|---------|
|
|
| **1. Docker-volumes** | door Docker beheerd (`docker volume ls`) | standaard; afgeschermd, maar alleen bereikbaar via docker-commando's |
|
|
| **2. Gewone mappen** | `/srv/server-up/data/` | alles zichtbaar naast de code, makkelijk te back-uppen en in te zien |
|
|
|
|
De keuze staat als `DATA_DB`, `DATA_MEDIA` en `DATA_STATIC` in `.env` en is
|
|
later te wijzigen. **Let op:** omzetten verplaatst geen gegevens. Wissel je van
|
|
volumes naar mappen, maak dan eerst een dump en zet die daarna terug:
|
|
|
|
```bash
|
|
docker compose exec db pg_dump -U rooster rooster > ~/rooster.sql # vóór het omzetten
|
|
# ... DATA_* aanpassen in .env, dan:
|
|
docker compose down && docker compose up -d db
|
|
cat ~/rooster.sql | docker compose exec -T db psql -U rooster rooster
|
|
docker compose up -d
|
|
```
|
|
|
|
Zonder die stap start de app met een **lege** database — de oude gegevens staan
|
|
dan nog wel in het oude volume, maar worden niet meer gebruikt.
|
|
|
|
### Handmatig, zonder script
|
|
|
|
```bash
|
|
git clone <jouw-forgejo-url>/roosterwijs.git /srv/server-up
|
|
cd /srv/server-up
|
|
cp .env.example .env
|
|
nano .env
|
|
```
|
|
|
|
Belangrijk om aan te passen:
|
|
- `DJANGO_SECRET_KEY` — genereer met `openssl rand -base64 48`
|
|
- `DJANGO_ALLOWED_HOSTS` — de hostnaam/IP van de 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
|
|
- `DATA_DB` / `DATA_MEDIA` / `DATA_STATIC` — zie de tabel hierboven
|
|
|
|
`.env` staat in `.gitignore` en komt dus **niet** in git — dat hoort zo.
|
|
|
|
## Starten
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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`.
|