#!/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/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/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 < 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/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"