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
This commit is contained in:
parent
1865de948e
commit
500cf6c558
7 changed files with 164 additions and 40 deletions
36
DEPLOY.md
36
DEPLOY.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
52
README.md
52
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "roosterwijs-frontend",
|
||||
"private": true,
|
||||
"version": "0.2.2-beta",
|
||||
"version": "0.2.3-beta",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
|
|||
|
|
@ -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
103
scripts/deploy.sh
Normal file → Executable 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."
|
||||
|
|
|
|||
Loading…
Reference in a new issue