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
252 lines
10 KiB
Bash
Executable file
252 lines
10 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Roosterwijs installeren op een server.
|
|
#
|
|
# Gebruik: ./scripts/install.sh
|
|
# ROOSTERWIJS_DIR=/pad/naar/map ./scripts/install.sh
|
|
#
|
|
# Het script doorloopt zeven stappen, stelt onderweg drie vragen en raakt
|
|
# nooit iets aan zonder dat te melden. Afbreken kan altijd met Ctrl-C.
|
|
set -euo pipefail
|
|
|
|
DOELMAP="${ROOSTERWIJS_DIR:-/srv/server-up}"
|
|
|
|
# ---------------------------------------------------------------- vormgeving
|
|
# Kleuren alleen als we naar een terminal schrijven (en NO_COLOR niet gezet is),
|
|
# zodat de uitvoer in een logbestand leesbaar blijft.
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
VET=$'\033[1m'; DOF=$'\033[2m'; STOP=$'\033[0m'
|
|
GROEN=$'\033[32m'; GEEL=$'\033[33m'; ROOD=$'\033[31m'; BLAUW=$'\033[36m'
|
|
else
|
|
VET=''; DOF=''; STOP=''; GROEN=''; GEEL=''; ROOD=''; BLAUW=''
|
|
fi
|
|
|
|
LIJN=$(printf '─%.0s' $(seq 1 62))
|
|
STAP_NR=0
|
|
STAP_TOTAAL=7
|
|
|
|
kop() {
|
|
printf '\n%s%s%s\n' "$BLAUW" "$LIJN" "$STOP"
|
|
printf ' %sROOSTERWIJS%s %s· installatie%s\n' "$VET" "$STOP" "$DOF" "$STOP"
|
|
printf '%s%s%s\n' "$BLAUW" "$LIJN" "$STOP"
|
|
}
|
|
stap() { STAP_NR=$((STAP_NR + 1)); printf '\n %s[%d/%d]%s %s%s%s\n' "$BLAUW" "$STAP_NR" "$STAP_TOTAAL" "$STOP" "$VET" "$1" "$STOP"; }
|
|
ok() { printf ' %s✓%s %s\n' "$GROEN" "$STOP" "$1"; }
|
|
info() { printf ' %s·%s %s\n' "$DOF" "$STOP" "$1"; }
|
|
let_op(){ printf ' %s!%s %s\n' "$GEEL" "$STOP" "$1"; }
|
|
fout() { printf '\n %s✗ %s%s\n\n' "$ROOD" "$1" "$STOP" >&2; exit 1; }
|
|
|
|
# vraag "Tekst" "standaardwaarde" -> antwoord in $ANTWOORD
|
|
vraag() {
|
|
local tekst="$1" standaard="${2:-}" invoer
|
|
if [ -n "$standaard" ]; then
|
|
printf ' %s%s%s %s[%s]%s: ' "$VET" "$tekst" "$STOP" "$DOF" "$standaard" "$STOP"
|
|
else
|
|
printf ' %s%s%s: ' "$VET" "$tekst" "$STOP"
|
|
fi
|
|
read -r invoer </dev/tty 2>/dev/null || invoer=""
|
|
ANTWOORD="${invoer:-$standaard}"
|
|
}
|
|
|
|
# jaNee "Vraag" "j|n" -> 0 = ja, 1 = nee
|
|
jaNee() {
|
|
local tekst="$1" standaard="${2:-j}" invoer
|
|
local hint="j/N"; [ "$standaard" = "j" ] && hint="J/n"
|
|
printf ' %s%s%s %s(%s)%s: ' "$VET" "$tekst" "$STOP" "$DOF" "$hint" "$STOP"
|
|
read -r invoer </dev/tty 2>/dev/null || invoer=""
|
|
invoer="${invoer:-$standaard}"
|
|
case "$invoer" in [jJyY]*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
# Wachtwoord/sleutel genereren zonder Python op de server te vereisen.
|
|
genereer_geheim() {
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | cut -c1-50
|
|
else
|
|
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 50
|
|
fi
|
|
}
|
|
|
|
kop
|
|
|
|
# ------------------------------------------------------------- 1. vereisten
|
|
stap "Vereisten controleren"
|
|
command -v git >/dev/null 2>&1 || fout "git ontbreekt. Installeer git en probeer opnieuw."
|
|
ok "git $(git --version | awk '{print $3}')"
|
|
command -v docker >/dev/null 2>&1 || fout "docker ontbreekt. Zie https://docs.docker.com/engine/install/"
|
|
ok "docker $(docker --version | awk '{print $3}' | tr -d ,)"
|
|
docker compose version >/dev/null 2>&1 || fout "'docker compose' werkt niet. Installeer de compose-plugin."
|
|
ok "docker compose $(docker compose version --short 2>/dev/null || echo aanwezig)"
|
|
docker info >/dev/null 2>&1 || fout "Kan niet met de Docker-daemon praten. Draait Docker, en zit je in de groep 'docker'?"
|
|
ok "Docker-daemon bereikbaar"
|
|
|
|
# --------------------------------------------------------------- 2. doelmap
|
|
stap "Doelmap $DOELMAP"
|
|
if [ ! -d "$DOELMAP" ]; then
|
|
if mkdir -p "$DOELMAP" 2>/dev/null; then
|
|
ok "aangemaakt"
|
|
else
|
|
printf '\n'
|
|
let_op "Geen rechten om $DOELMAP aan te maken."
|
|
info "Draai eenmalig, als beheerder:"
|
|
printf '\n %ssudo mkdir -p %s && sudo chown "$USER":"$USER" %s%s\n\n' "$VET" "$DOELMAP" "$DOELMAP" "$STOP"
|
|
fout "Start daarna dit script opnieuw."
|
|
fi
|
|
else
|
|
[ -w "$DOELMAP" ] || fout "$DOELMAP bestaat, maar je hebt er geen schrijfrechten."
|
|
ok "bestaat en is beschrijfbaar"
|
|
fi
|
|
|
|
# ------------------------------------------------------------------ 3. code
|
|
stap "Code ophalen"
|
|
if [ -d "$DOELMAP/.git" ]; then
|
|
ok "checkout gevonden"
|
|
if jaNee "Bijwerken naar de laatste versie?" j; then
|
|
git -C "$DOELMAP" fetch origin --quiet
|
|
tak="$(git -C "$DOELMAP" rev-parse --abbrev-ref HEAD)"
|
|
git -C "$DOELMAP" reset --hard "origin/$tak" --quiet
|
|
ok "bijgewerkt naar $(git -C "$DOELMAP" rev-parse --short HEAD) op $tak"
|
|
else
|
|
info "overgeslagen; huidige versie blijft staan"
|
|
fi
|
|
elif [ -n "$(ls -A "$DOELMAP" 2>/dev/null)" ]; then
|
|
fout "$DOELMAP is niet leeg en bevat geen git-checkout. Maak de map leeg of kies een andere met ROOSTERWIJS_DIR=..."
|
|
else
|
|
herkomst=""
|
|
if git -C "$(dirname "$0")/.." rev-parse --git-dir >/dev/null 2>&1; then
|
|
herkomst="$(git -C "$(dirname "$0")/.." remote get-url origin 2>/dev/null || true)"
|
|
fi
|
|
vraag "Git-URL van de repository" "$herkomst"
|
|
[ -n "$ANTWOORD" ] || fout "Zonder URL kan de code niet worden opgehaald."
|
|
git clone "$ANTWOORD" "$DOELMAP" --quiet || fout "Klonen mislukt. Klopt de URL en heb je toegang (SSH-sleutel)?"
|
|
ok "gekloond naar $DOELMAP"
|
|
fi
|
|
cd "$DOELMAP"
|
|
|
|
# --------------------------------------------------------------- 4. opslag
|
|
stap "Waar komen de gegevens te staan?"
|
|
printf '\n'
|
|
printf ' %s1)%s %sDocker-volumes%s %s(standaard)%s\n' "$VET" "$STOP" "$VET" "$STOP" "$DOF" "$STOP"
|
|
printf ' Docker beheert de opslag. Netjes afgeschermd, maar je\n'
|
|
printf ' komt er alleen bij via docker-commandos.\n\n'
|
|
printf ' %s2)%s %sGewone mappen%s onder %s/data\n' "$VET" "$STOP" "$VET" "$STOP" "$DOELMAP"
|
|
printf ' Database, media en statics staan als zichtbare mappen\n'
|
|
printf ' naast de code. Makkelijk back-uppen en inzien.\n\n'
|
|
vraag "Keuze (1 of 2)" "1"
|
|
case "$ANTWOORD" in
|
|
2)
|
|
OPSLAG="mappen"
|
|
DATA_DB="./data/postgres"; DATA_MEDIA="./data/media"; DATA_STATIC="./data/static"
|
|
mkdir -p data/postgres data/media data/static
|
|
ok "mappen aangemaakt onder $DOELMAP/data"
|
|
;;
|
|
*)
|
|
OPSLAG="volumes"
|
|
DATA_DB="pgdata"; DATA_MEDIA="media_volume"; DATA_STATIC="static_volume"
|
|
ok "Docker beheert de opslag (volumes pgdata, media_volume, static_volume)"
|
|
;;
|
|
esac
|
|
|
|
# ------------------------------------------------------------------ 5. .env
|
|
stap "Instellingen (.env)"
|
|
if [ -f .env ]; then
|
|
ok ".env bestaat al"
|
|
if jaNee "Laten staan en ongewijzigd gebruiken?" j; then
|
|
info "bestaande instellingen blijven gelden"
|
|
if ! grep -q '^DATA_DB=' .env; then
|
|
{ echo ""; echo "# Opslagkeuze (toegevoegd door install.sh)"
|
|
echo "DATA_DB=$DATA_DB"; echo "DATA_MEDIA=$DATA_MEDIA"; echo "DATA_STATIC=$DATA_STATIC"; } >> .env
|
|
ok "opslagkeuze toegevoegd aan .env"
|
|
fi
|
|
else
|
|
cp .env ".env.backup-$(date +%Y%m%d-%H%M%S)"
|
|
ok "oude .env bewaard als .env.backup-*"
|
|
rm -f .env
|
|
fi
|
|
fi
|
|
|
|
if [ ! -f .env ]; then
|
|
vraag "Hostnaam waarop de app bereikbaar is" "rooster.example.nl"
|
|
HOSTNAAM="$ANTWOORD"
|
|
if jaNee "Draait er TLS (https) voor deze server?" j; then
|
|
SCHEMA="https"; SECURE=1
|
|
else
|
|
SCHEMA="http"; SECURE=0
|
|
fi
|
|
BASIS_URL="$SCHEMA://$HOSTNAAM"
|
|
[ "$SCHEMA" = "http" ] && BASIS_URL="$BASIS_URL:8080"
|
|
|
|
cat > .env <<ENVEOF
|
|
# Aangemaakt door scripts/install.sh op $(date '+%Y-%m-%d %H:%M').
|
|
# Dit bestand bevat geheimen en staat in .gitignore.
|
|
|
|
# --- Django ---------------------------------------------------------------
|
|
DJANGO_SECRET_KEY=$(genereer_geheim)
|
|
DJANGO_DEBUG=0
|
|
DJANGO_ALLOWED_HOSTS=$HOSTNAAM,localhost,127.0.0.1
|
|
DJANGO_CSRF_TRUSTED_ORIGINS=$BASIS_URL
|
|
|
|
# 1 = veilige cookies, HSTS en https-redirect. Vereist dat de proxy
|
|
# X-Forwarded-Proto: https meestuurt (zie DEPLOY.md).
|
|
DJANGO_SECURE=$SECURE
|
|
|
|
# --- Opslag ---------------------------------------------------------------
|
|
# Een naam = Docker-volume, een pad = gewone map naast de code.
|
|
DATA_DB=$DATA_DB
|
|
DATA_MEDIA=$DATA_MEDIA
|
|
DATA_STATIC=$DATA_STATIC
|
|
|
|
# --- PostgreSQL -----------------------------------------------------------
|
|
POSTGRES_DB=rooster
|
|
POSTGRES_USER=rooster
|
|
POSTGRES_PASSWORD=$(genereer_geheim)
|
|
|
|
# --- E-mail (wachtwoordreset) --------------------------------------------
|
|
# Leeg = e-mail gaat naar de log van de backend-container.
|
|
EMAIL_HOST=
|
|
EMAIL_PORT=587
|
|
EMAIL_HOST_USER=
|
|
EMAIL_HOST_PASSWORD=
|
|
EMAIL_USE_TLS=1
|
|
DEFAULT_FROM_EMAIL=Roosterwijs <noreply@$HOSTNAAM>
|
|
FRONTEND_BASE_URL=$BASIS_URL
|
|
ENVEOF
|
|
chmod 600 .env
|
|
ok "aangemaakt met willekeurige sleutel en databasewachtwoord"
|
|
info "hostnaam: $HOSTNAAM · $( [ "$SECURE" = 1 ] && echo 'TLS aan' || echo 'TLS uit' )"
|
|
fi
|
|
|
|
# ------------------------------------------------------------ 6. bouwen
|
|
stap "Containers bouwen en starten"
|
|
info "dit duurt de eerste keer enkele minuten"
|
|
docker compose up -d --build || fout "Bouwen mislukt. Bekijk de uitvoer hierboven."
|
|
ok "db, backend en nginx draaien"
|
|
|
|
# --------------------------------------------------------- 7. beheerder
|
|
stap "Beheerder"
|
|
aantal="$(docker compose exec -T backend python manage.py shell -c \
|
|
'from django.contrib.auth import get_user_model; print(get_user_model().objects.count())' 2>/dev/null | tr -dc '0-9')"
|
|
aantal="${aantal:-0}"
|
|
if [ "$aantal" -gt 0 ]; then
|
|
ok "er zijn al $aantal account(s); geen nieuwe nodig"
|
|
else
|
|
let_op "Er is nog geen account. Zonder account kun je nergens inloggen."
|
|
if jaNee "Nu een beheerder aanmaken?" j; then
|
|
docker compose exec backend python manage.py createsuperuser </dev/tty 2>/dev/null || \
|
|
let_op "Aanmaken afgebroken; doe het later met het commando hieronder."
|
|
fi
|
|
fi
|
|
|
|
# ------------------------------------------------------------------ klaar
|
|
printf '\n%s%s%s\n' "$GROEN" "$LIJN" "$STOP"
|
|
printf ' %s✓ Installatie afgerond%s\n' "$GROEN$VET" "$STOP"
|
|
printf '%s%s%s\n\n' "$GROEN" "$LIJN" "$STOP"
|
|
printf ' %sMap%s %s\n' "$DOF" "$STOP" "$DOELMAP"
|
|
if [ "$OPSLAG" = "mappen" ]; then
|
|
printf ' %sGegevens%s %s/data (database, media, statics)\n' "$DOF" "$STOP" "$DOELMAP"
|
|
else
|
|
printf ' %sGegevens%s Docker-volumes (docker volume ls)\n' "$DOF" "$STOP"
|
|
fi
|
|
printf ' %sBijwerken%s ./scripts/deploy.sh\n' "$DOF" "$STOP"
|
|
printf ' %sLogs%s docker compose logs -f\n' "$DOF" "$STOP"
|
|
printf ' %sBeheerder%s docker compose exec backend python manage.py createsuperuser\n\n' "$DOF" "$STOP"
|