Herstel installatie-instructies en hard deploy-script (v0.2.03 beta)
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run

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
This commit is contained in:
Ramon 2026-08-19 14:57:49 +02:00
parent 1865de948e
commit 500cf6c558
7 changed files with 164 additions and 40 deletions

View file

@ -22,9 +22,18 @@ erboven (meestal een SSH-/sleutelprobleem).
./scripts/deploy.sh
```
Dit haalt de laatste code op (`git reset --hard origin/<branch>`), herbouwt de
containers (incl. migraties via de entrypoint) en controleert of er een nieuwe
frontend-bundel staat.
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**.
@ -45,7 +54,7 @@ EMAIL_HOST_USER=postvak@jouwschool.nl
EMAIL_HOST_PASSWORD=...
EMAIL_USE_TLS=1
DEFAULT_FROM_EMAIL=Roosterwijs <noreply@jouwschool.nl>
FRONTEND_BASE_URL=http://rooster-test.example.nl:8080
FRONTEND_BASE_URL=https://rooster.example.nl
```
`FRONTEND_BASE_URL` bepaalt de basis van de resetlink in de e-mail (zonder
@ -76,8 +85,8 @@ Je hoeft op de server **niets** te installeren behalve Docker zelf (met de
```
2. Haal de code op:
```bash
git clone <jouw-forgejo-url>/Roostersoftware.git
cd Roostersoftware
git clone <jouw-forgejo-url>/roosterwijs.git
cd roosterwijs
```
3. Maak het `.env`-bestand aan vanuit het voorbeeld en vul echte waarden in:
```bash
@ -88,7 +97,8 @@ Je hoeft op de server **niets** te installeren behalve Docker zelf (met de
- `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``http://<host>:8080` (of je echte URL)
- `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.
@ -116,7 +126,10 @@ pas hem daar aan als die poort bezet is.)
docker compose exec backend python manage.py createsuperuser
```
Daarna kun je inloggen op `http://<server-host>:8080/admin/`.
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
@ -164,8 +177,11 @@ zonder tussenkomst van Django).
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).
Staat hier nog een `http://`-adres, dan geeft elke POST — inclusief het
inloggen — een CSRF-fout (HTTP 403).
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.

View file

@ -11,10 +11,11 @@ Fase 3 (afwezigheid, uitzonderingen, effectief weekrooster) en Fase 4
Het Rooster-scherm is een **weekweergave met drag-and-drop** (muis én touch) en
een **touch-vriendelijk** ontwerp; vrije dagen, afwezigheid en uitzonderingen
worden automatisch verwerkt. Het Rooster heeft tabs **Weekrooster / Conflicten /
Schooljaar / Instellingen** (tijdsloten, activiteiten en locaties beheer je daar,
en het schooljaar + de kalender voer je in onder de Schooljaar-tab, zodat het menu
rustiger is). De zijbalk groepeert items onder inklapbare submenu's (Personen,
worden automatisch verwerkt. De Roostermaker heeft tabs **Week / Genereren /
Instellingen**: onder Instellingen beheer je schooljaar + kalender, tijdvakken,
vakken, lokalen, locaties en vormgeving; onder Genereren zitten de Vak-wizard en
het Auto-rooster. Conflicten verschijnen als melding boven het weekrooster, niet
als aparte tab. De zijbalk groepeert items onder inklapbare submenu's (Personen,
Groepen) en heeft onderaan een subtiel **Instellingen**-blok met Gebruikers en
Modulebeheer.
@ -22,9 +23,10 @@ Er zijn twee rooster-schermen: **Rooster** (alleen-lezen weergave, om te bekijke
en af te drukken) en **Roostermaker** (de aanpasser met drag-and-drop, tabs en
conflictdetectie). In de Roostermaker open je een blok met een **klik** om het te
bewerken (vak, locatie, doelgroep, begeleiders, opmerking) of te **kopiëren** naar
andere tijden/dagen. De tab **Indeling** verdeelt een hoofdgroep (of leerplein)
kolomsgewijs in subgroepen, met per subgroep de vakken en de verantwoordelijke(n),
en je kunt er direct subgroepen toevoegen. Beide schermen hebben een **scope-filter** (alles / per klas /
andere tijden/dagen. Het aparte scherm **Groepsindeling** (zijbalk → Organisatie)
verdeelt een hoofdgroep of leerplein kolomsgewijs in subgroepen, met per subgroep
de vakken en de verantwoordelijke(n), en je kunt er direct subgroepen toevoegen.
Beide roosterschermen hebben een **scope-filter** (alles / per klas /
per leerplein / per leerling / per personeel); de module **Printen/Exporteren**
voegt — als die aanstaat — een subtiele **Afdrukken**-knop toe die het (gefilterde)
weekrooster printvriendelijk afdrukt of als PDF bewaart.
@ -52,11 +54,14 @@ geboortedatum vast.
- **Backend** (`backend/`) — Django + Django REST Framework.
- `plugins/` — het plugin-framework: een moduleregister, opslag van de
aan/uit-status per module en een beheer-API.
- `core/` — de kern (altijd actief). **Fase 1**: modellen Persoon, Groep en
Subgroep met een volledige CRUD-API. Roosterblokken en kalender volgen in
fase 2.
- `modules/printing/` en `modules/accounts/` — twee voorbeeldmodules die
zich bij opstarten aanmelden bij het register.
- `core/` — de kern (altijd actief): personen, groepen, subgroepen,
functies, schooljaar + kalender, tijdvakken, activiteiten, locaties,
lokalen, roosterblokken en conflictdetectie, elk met een CRUD-API.
- `modules/` — twaalf modules die zich bij opstarten aanmelden bij het
register: `printing`, `accounts`, `leerplein`, `stage`, `uren`,
`blokwizard`, `personenwizard`, `autorooster`, `huisstijl`,
`importexport`, `vervanging` en `portal`. De volledige lijst staat in
`MODULE_APPS` in `backend/config/settings.py`.
- **Frontend** (`frontend/`) — React (Vite). Zijbalk met Roosterwijs-logo die
alleen menu's toont van ingeschakelde modules, beheerschermen voor
**Personen**, **Groepen** en **Subgroepen**, en een scherm **Modulebeheer**.
@ -74,18 +79,34 @@ geboortedatum vast.
Een nieuwe module toevoegen = nieuwe app maken, één regel in
`MODULE_APPS` (in `backend/config/settings.py`), klaar.
## Starten
## Starten (lokaal ontwikkelen)
> Voor een test- of productieserver gelden **andere** stappen: zie `DEPLOY.md`.
> Daar draait alles in Docker en komt de configuratie uit `.env`.
### Backend (Django, poort 8000)
```bash
cd backend
python3 -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# Zonder DJANGO_DEBUG=1 weigert Django te starten: buiten debug is
# DJANGO_SECRET_KEY verplicht. Met debug aan valt hij terug op een dev-sleutel.
export DJANGO_DEBUG=1 # Windows: $env:DJANGO_DEBUG="1"
python manage.py migrate
python manage.py createsuperuser # optioneel, voor /admin/
python manage.py createsuperuser # VERPLICHT, zie hieronder
python manage.py runserver
```
**`createsuperuser` is geen optionele stap.** De hele API staat op
`IsAuthenticated` en de frontend toont eerst een inlogscherm; zonder account kom
je nergens binnen — ook niet in de app zelf, niet alleen in `/admin/`.
Zonder `POSTGRES_DB` in de omgeving gebruikt de backend automatisch SQLite in
`backend/db.sqlite3`. `DEPLOY.md` zet PostgreSQL in via `.env`.
API-eindpunten:
- `GET /api/modules/` — alle modules met status.
- `POST /api/modules/<key>/state/` — body `{"enabled": true|false}`.
@ -111,6 +132,9 @@ API-eindpunten:
### Frontend (React, poort 5173)
Vereist **Node 20.19+ of 22.12+** — Vite 8 weigert oudere versies.
Controleer met `node -v`.
```bash
cd frontend
npm install

View file

@ -1,9 +1,10 @@
# Frontend in twee fases:
# 1) Node bouwt de React-app tot statische bestanden.
# 1) Node bouwt de React-app tot statische bestanden (Node 22: Vite 8 eist
# ^20.19 of >=22.12, en de CI bouwt ook op 22 — zelfde versie overal).
# 2) Nginx serveert die bestanden en proxyt /api en /admin naar de backend.
# --- Fase 1: build --------------------------------------------------------
FROM node:20-alpine AS build
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
# npm ci (reproduceerbaar) zodra er een package-lock.json in git staat;

View file

@ -1,12 +1,12 @@
{
"name": "roosterwijs-frontend",
"version": "0.2.2-beta",
"version": "0.2.3-beta",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roosterwijs-frontend",
"version": "0.2.2-beta",
"version": "0.2.3-beta",
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"

View file

@ -1,7 +1,7 @@
{
"name": "roosterwijs-frontend",
"private": true,
"version": "0.2.2-beta",
"version": "0.2.3-beta",
"type": "module",
"scripts": {
"dev": "vite",

View file

@ -35,7 +35,7 @@ button,a,input,select{transition:border-color .15s,background-color .15s,color .
.cog-btn{width:100%;min-height:38px;justify-content:flex-start;font-size:15px;padding:0}
.cog-btn::after{content:"Beheer en instellingen";margin-left:8px;font-size:13px}
.cog-dropdown{position:static;min-width:0;margin:4px 0 0;box-shadow:none}
.topbar::after{content:"v0.2.02 beta";order:4;margin:10px 8px 0;color:#98a2b3;font-size:10px}
.topbar::after{content:"v0.2.03 beta";order:4;margin:10px 8px 0;color:#98a2b3;font-size:10px}
.content{margin-left:var(--sidebar);width:calc(100% - var(--sidebar));max-width:1680px;padding:32px 38px 48px}
.content-wide{padding:24px 28px 42px}
h1{font-size:clamp(24px,2.4vw,30px);line-height:1.2;letter-spacing:-.025em;font-weight:750}

103
scripts/deploy.sh Normal file → Executable file
View file

@ -1,19 +1,102 @@
#!/usr/bin/env bash
# Op de test-server: haal de laatste code op en herbouw de containers.
# Gebruik: ./scripts/deploy.sh
#
# Gebruik: ./scripts/deploy.sh [--force]
# --force ook doorgaan als er lokale wijzigingen zijn
# (let op: die worden dan overschreven)
set -euo pipefail
cd "$(dirname "$0")/.."
branch="$(git rev-parse --abbrev-ref HEAD)"
echo "Branch: $branch"
git fetch origin
# Forceer exact gelijk aan de remote (voorkomt 'already up to date'-verwarring).
git reset --hard "origin/$branch"
echo "Nu op commit:"
git --no-pager log --oneline -1
force=0
for arg in "${@:-}"; do
case "$arg" in
"") ;;
--force) force=1 ;;
*) echo "Onbekende optie: $arg" >&2; exit 2 ;;
esac
done
# --- Vooraf controleren ---------------------------------------------------
if ! command -v docker >/dev/null 2>&1; then
echo "FOUT: docker is niet geïnstalleerd op deze server." >&2
exit 1
fi
if ! docker compose version >/dev/null 2>&1; then
echo "FOUT: 'docker compose' werkt niet (compose-plugin ontbreekt)." >&2
exit 1
fi
# Zonder .env start de stack met lege variabelen: compose vult dan overal
# lege strings in, de backend ziet geen POSTGRES_DB (valt terug op SQlite in
# de container) en weigert te starten omdat DJANGO_SECRET_KEY ontbreekt.
if [ ! -f .env ]; then
echo "FOUT: .env ontbreekt in $(pwd)." >&2
echo " Maak hem aan met: cp .env.example .env && nano .env" >&2
exit 1
fi
# 'git reset --hard' hieronder gooit lokale aanpassingen weg — bijvoorbeeld een
# met de hand aangepaste nginx.conf. Daarom eerst waarschuwen.
if [ -n "$(git status --porcelain)" ]; then
echo "LET OP: er staan lokale wijzigingen in deze map:"
git --no-pager status --short | sed 's/^/ /'
if [ "$force" -ne 1 ]; then
echo
echo "Die gaan verloren bij het bijwerken. Zet ze eerst veilig, of draai:" >&2
echo " ./scripts/deploy.sh --force" >&2
exit 1
fi
echo "--force opgegeven: deze wijzigingen worden overschreven."
fi
# --- Code bijwerken -------------------------------------------------------
branch="$(git rev-parse --abbrev-ref HEAD)"
oud="$(git rev-parse --short HEAD)"
echo "Branch: $branch (nu op $oud)"
git fetch origin
git reset --hard "origin/$branch"
nieuw="$(git rev-parse --short HEAD)"
if [ "$oud" = "$nieuw" ]; then
echo "Geen nieuwe commits; containers worden wel opnieuw gebouwd."
else
echo "Bijgewerkt: $oud -> $nieuw"
git --no-pager log --oneline "$oud..$nieuw" | sed 's/^/ /'
fi
# --- Bouwen en starten ----------------------------------------------------
docker compose up -d --build
echo "Controle: nieuwe frontend-bundel aanwezig?"
docker compose exec -T nginx sh -c 'ls /usr/share/nginx/html/assets/*.js >/dev/null 2>&1 && echo " frontend OK" || echo " GEEN frontend-bundel gevonden"'
# --- Controleren ----------------------------------------------------------
echo
echo "Controle: frontend-bundel aanwezig?"
if docker compose exec -T nginx sh -c 'ls /usr/share/nginx/html/assets/*.js >/dev/null 2>&1'; then
echo " frontend OK"
else
echo " GEEN frontend-bundel gevonden"
fi
echo "Controle: reageert de backend?"
docker compose exec -T backend python -c "
import urllib.request, urllib.error
try:
r = urllib.request.urlopen('http://127.0.0.1:8000/api/info/', timeout=5)
print(' backend OK (HTTP', r.status, ')')
except urllib.error.HTTPError as e:
print(' backend OK (HTTP', e.code, '- reageert)')
except Exception as e:
print(' GEEN antwoord van de backend:', e)
" || echo " Kon de backend niet bereiken — zie 'docker compose logs backend'."
echo "Controle: is er een gebruiker om mee in te loggen?"
docker compose exec -T backend python manage.py shell -c "
from django.contrib.auth import get_user_model
n = get_user_model().objects.filter(is_active=True).count()
print(' actieve gebruikers:', n)
if n == 0:
print(' LET OP: geen actief account. Maak er een met:')
print(' docker compose exec backend python manage.py createsuperuser')
" || echo " Kon de gebruikers niet tellen — zie 'docker compose logs backend'."
echo "Klaar."