Het installatiescript kan nu allebei zelf regelen, zodat je na afloop niet meer naar de webinterface hoeft om een account te claimen en niet meer in .env hoeft te duiken om erbij te kunnen. Beheerdersaccount: - Op een terminal wordt gevraagd of je er meteen een wil, met naam en wachtwoord (twee keer, echo uit). - Automatisch via --admin NAAM plus --admin-password-file of SU_ADMIN_PASSWORD; zonder bron en zonder terminal maakt het script er zelf een van 24 tekens en toont die. - --admin-password weigert bewust: argumenten zijn zichtbaar in 'ps'. Het wachtwoord gaat via stdin naar curl, niet als argument. - Nieuw --create-admin voor een installatie die al draait. Bereikbaarheid: - Keuzemenu voor BIND (deze server / hele netwerk / eigen adres), met het gedetecteerde serveradres erbij. - Bij opnieuw installeren is de bestaande instelling het uitgangspunt, zodat enter je server niet ongemerkt terugzet op loopback. Opgeloste fouten: - Een bestaande .env bleef altijd ongemoeid, ook met --bind: de installatie leek te lukken terwijl de server op het oude adres bleef luisteren. - --update rekende met de standaardpoort in plaats van met wat er in .env staat, waardoor de wachtlus en het slotadres niet klopten. - --dry-run riep 'sudo docker' aan en vroeg dus om een wachtwoord terwijl het net beloofd had niets te doen; zonder terminal liep het daarop vast. - Ongeldig bind-adres en poort buiten 1-65535 worden nu meteen geweigerd. Getest met een pseudo-terminal voor de vragen en tegen een echt draaiende server voor het aanmaken van het account, inclusief een wachtwoord vol aanhalingstekens en backslashes, een geweigerd tweede account en een server die niet reageert. De e2e-tests slaan zichzelf over waar curl ontbreekt, zoals in het CI-image. 992 tests groen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
700 lines
28 KiB
Bash
Executable file
700 lines
28 KiB
Bash
Executable file
#!/bin/sh
|
|
# Server Up — installeren, bijwerken of verwijderen op een Linux-server.
|
|
#
|
|
# curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh
|
|
#
|
|
# Liever eerst lezen wat je uitvoert (aan te raden):
|
|
#
|
|
# curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh -o install.sh
|
|
# less install.sh
|
|
# sh install.sh
|
|
#
|
|
# POSIX sh, dus het werkt onder dash, ash en bash.
|
|
|
|
set -eu
|
|
|
|
SCRIPT_VERSIE="1.1"
|
|
GIT_HOST="git.ramonbesselink.nl"
|
|
GIT_PAD="bes-r/server-up"
|
|
BRANCH="main"
|
|
DOEL="/opt/server-up"
|
|
POORT="5000"
|
|
BIND="127.0.0.1"
|
|
TOKEN=""
|
|
JA=0
|
|
DROOG=0
|
|
ACTIE="install"
|
|
BEHEERDER=""
|
|
BEHEERDER_WW=""
|
|
BEHEERDER_WW_BESTAND=""
|
|
# Expliciet meegegeven waarden overschrijven wél een bestaande .env; de rest
|
|
# blijft staan, zodat een --update je instellingen niet terugzet.
|
|
BIND_EXPLICIET=0
|
|
POORT_EXPLICIET=0
|
|
|
|
# ── Uitvoer ──────────────────────────────────────────────────────────────────
|
|
# Kleuren alleen als we naar een terminal schrijven; in een pipe of logbestand
|
|
# leveren escape-codes onleesbare rommel op.
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
C_OK=$(printf '\033[32m'); C_WAARSCHUW=$(printf '\033[33m')
|
|
C_FOUT=$(printf '\033[31m'); C_KOP=$(printf '\033[1m'); C_UIT=$(printf '\033[0m')
|
|
else
|
|
C_OK=''; C_WAARSCHUW=''; C_FOUT=''; C_KOP=''; C_UIT=''
|
|
fi
|
|
|
|
log() { printf '%s\n' " $*"; }
|
|
stap() { printf '\n%s%s%s\n' "$C_KOP" "$*" "$C_UIT"; }
|
|
goed() { printf ' %s✔%s %s\n' "$C_OK" "$C_UIT" "$*"; }
|
|
waarschuw() { printf ' %s!%s %s\n' "$C_WAARSCHUW" "$C_UIT" "$*"; }
|
|
fout() { printf '\n %s✖ %s%s\n\n' "$C_FOUT" "$*" "$C_UIT" >&2; exit 1; }
|
|
# Bij --dry-run alleen waarschuwen: je wilt de hele voorvertoning zien, ook op
|
|
# een machine waar Docker (nog) niet staat.
|
|
fout_tenzij_droog() {
|
|
if [ "$DROOG" -eq 1 ]; then waarschuw "$*"; else fout "$*"; fi
|
|
}
|
|
|
|
heeft() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
# Voert uit, of toont alleen wat er zou gebeuren bij --dry-run.
|
|
doe() {
|
|
if [ "$DROOG" -eq 1 ]; then
|
|
printf ' [droog] %s\n' "$*"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
vraag() {
|
|
# vraag "Vraag?" -> 0 bij ja. Met --yes of zonder terminal: altijd ja.
|
|
[ "$JA" -eq 1 ] && return 0
|
|
[ -t 0 ] || return 0
|
|
printf ' %s [J/n] ' "$1"
|
|
read -r antwoord </dev/tty || return 0
|
|
case "$antwoord" in [nN]*) return 1 ;; *) return 0 ;; esac
|
|
}
|
|
|
|
gebruik() {
|
|
cat <<UITLEG
|
|
Server Up installatiescript ${SCRIPT_VERSIE}
|
|
|
|
Gebruik: install.sh [opties]
|
|
|
|
--dir PAD Installatiemap (standaard: ${DOEL})
|
|
--port POORT Poort voor de webinterface (standaard: ${POORT})
|
|
--bind ADRES Waarop de poort luistert (standaard: ${BIND})
|
|
Gebruik 0.0.0.0 om er van buiten de server bij te kunnen,
|
|
maar zet er dan wel een reverse proxy met TLS voor.
|
|
--branch NAAM Branch om te installeren (standaard: ${BRANCH})
|
|
--token TOKEN Toegangstoken, als je git-repo niet openbaar is
|
|
--admin NAAM Maak meteen een beheerdersaccount met deze naam
|
|
--admin-password-file PAD
|
|
Lees het wachtwoord uit een bestand. Kan ook via de
|
|
omgevingsvariabele SU_ADMIN_PASSWORD. Geef je geen van
|
|
beide, dan wordt ernaar gevraagd; kan dat niet (geen
|
|
terminal), dan wordt er één aangemaakt en getoond.
|
|
--create-admin Alleen het beheerdersaccount aanmaken, bij een installatie
|
|
die al draait. Werkt zolang er nog geen account bestaat.
|
|
--update Bijwerken naar de nieuwste versie
|
|
--uninstall Stoppen en verwijderen (je gegevens blijven staan)
|
|
--yes Niets vragen, overal ja op antwoorden
|
|
--dry-run Alleen tonen wat er zou gebeuren
|
|
--help Deze uitleg
|
|
|
|
Het wachtwoord staat bewust niet als optie op de opdrachtregel: die is voor
|
|
iedereen op de server te zien met 'ps' en blijft in je shell-geschiedenis staan.
|
|
|
|
Voorbeelden:
|
|
sh install.sh
|
|
sh install.sh --bind 0.0.0.0 --port 8080
|
|
sh install.sh --admin ramon
|
|
SU_ADMIN_PASSWORD=... sh install.sh --admin ramon --yes
|
|
sh install.sh --update
|
|
UITLEG
|
|
}
|
|
|
|
# ── Argumenten ───────────────────────────────────────────────────────────────
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dir) DOEL="${2:?--dir vereist een pad}"; shift 2 ;;
|
|
--port) POORT="${2:?--port vereist een nummer}"; POORT_EXPLICIET=1; shift 2 ;;
|
|
--bind) BIND="${2:?--bind vereist een adres}"; BIND_EXPLICIET=1; shift 2 ;;
|
|
--branch) BRANCH="${2:?--branch vereist een naam}"; shift 2 ;;
|
|
--token) TOKEN="${2:?--token vereist een waarde}"; shift 2 ;;
|
|
--admin) BEHEERDER="${2:?--admin vereist een gebruikersnaam}"; shift 2 ;;
|
|
--admin-password-file)
|
|
BEHEERDER_WW_BESTAND="${2:?--admin-password-file vereist een pad}"; shift 2 ;;
|
|
--admin-password)
|
|
fout "Gebruik --admin-password-file of SU_ADMIN_PASSWORD; een wachtwoord op de opdrachtregel is voor iedereen zichtbaar met 'ps'." ;;
|
|
--update) ACTIE="update"; shift ;;
|
|
--uninstall) ACTIE="uninstall"; shift ;;
|
|
--create-admin) ACTIE="create-admin"; shift ;;
|
|
--yes|-y) JA=1; shift ;;
|
|
--dry-run) DROOG=1; shift ;;
|
|
--help|-h) gebruik; exit 0 ;;
|
|
*) fout "Onbekende optie: $1 (gebruik --help)" ;;
|
|
esac
|
|
done
|
|
|
|
case "$POORT" in ''|*[!0-9]*) fout "Ongeldige poort: $POORT" ;; esac
|
|
[ "$POORT" -ge 1 ] && [ "$POORT" -le 65535 ] || fout "Poort buiten bereik: $POORT"
|
|
|
|
# Docker publiceert op een adres, niet op een hostnaam. Alleen cijfers, punten
|
|
# en dubbele punten (IPv6) toestaan, zodat een typefout hier opvalt en niet pas
|
|
# als 'docker compose up' er een onbegrijpelijke fout over geeft.
|
|
geldig_bind() {
|
|
case "$1" in
|
|
''|*[!0-9.:a-fA-F]*) return 1 ;;
|
|
*) return 0 ;;
|
|
esac
|
|
}
|
|
geldig_bind "$BIND" || fout "Ongeldig bind-adres: $BIND (verwacht een IP-adres, bijvoorbeeld 127.0.0.1 of 0.0.0.0)"
|
|
|
|
# ── Rechten ──────────────────────────────────────────────────────────────────
|
|
# Alleen sudo gebruiken waar het echt nodig is: kan de gebruiker zelf al in de
|
|
# doelmap schrijven en docker aanroepen, dan blijft alles onder zijn eigen account.
|
|
SUDO=""
|
|
bepaal_rechten() {
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
return 0
|
|
fi
|
|
bovenliggend="$(dirname "$DOEL")"
|
|
if [ -w "$bovenliggend" ] || { [ -d "$DOEL" ] && [ -w "$DOEL" ]; }; then
|
|
if docker info >/dev/null 2>&1; then
|
|
goed "Draait onder je eigen account; sudo is niet nodig"
|
|
return 0
|
|
fi
|
|
fi
|
|
if heeft sudo; then
|
|
SUDO="sudo"
|
|
log "sudo wordt gebruikt voor ${DOEL} en docker"
|
|
else
|
|
fout "Geen root en geen sudo. Draai als root, of kies met --dir een map waar je zelf in mag schrijven."
|
|
fi
|
|
}
|
|
|
|
# ── Vereisten ────────────────────────────────────────────────────────────────
|
|
controleer_systeem() {
|
|
stap "Systeem controleren"
|
|
bepaal_rechten
|
|
[ "$(uname -s)" = "Linux" ] || waarschuw "Dit script is voor Linux; op $(uname -s) is het onbeproefd."
|
|
for hulp in curl tar; do
|
|
heeft "$hulp" || fout_tenzij_droog "'$hulp' ontbreekt. Installeer het en probeer opnieuw."
|
|
done
|
|
goed "$(uname -s) $(uname -m)"
|
|
}
|
|
|
|
controleer_docker() {
|
|
# Bij een proefdraai niets aanroepen wat via sudo loopt: dan staat er
|
|
# ineens een wachtwoordvraag op het scherm terwijl we net beloofd hebben
|
|
# dat er niets gebeurt — en zonder terminal blijft hij daarop hangen.
|
|
if [ "$DROOG" -eq 1 ]; then
|
|
if heeft docker; then
|
|
goed "Docker aanwezig (de daemon wordt bij een proefdraai niet aangesproken)"
|
|
else
|
|
waarschuw "Docker is niet geïnstalleerd; het script zou aanbieden dat te doen."
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
if heeft docker; then
|
|
if $SUDO docker info >/dev/null 2>&1; then
|
|
goed "Docker draait ($($SUDO docker version --format '{{.Server.Version}}' 2>/dev/null || echo '?'))"
|
|
else
|
|
fout_tenzij_droog "Docker is geïnstalleerd maar de daemon reageert niet. Start hem met: systemctl start docker"
|
|
fi
|
|
else
|
|
waarschuw "Docker is niet geïnstalleerd."
|
|
if vraag "Nu installeren via het officiële script van docker.com?"; then
|
|
stap "Docker installeren"
|
|
doe sh -c "curl -fsSL https://get.docker.com | $SUDO sh"
|
|
doe $SUDO systemctl enable --now docker || true
|
|
heeft docker || [ "$DROOG" -eq 1 ] || fout "Installeren van Docker is niet gelukt."
|
|
goed "Docker geïnstalleerd"
|
|
else
|
|
fout_tenzij_droog "Docker is vereist. Zie https://docs.docker.com/engine/install/"
|
|
fi
|
|
fi
|
|
|
|
if $SUDO docker compose version >/dev/null 2>&1; then
|
|
goed "Compose-plugin aanwezig"
|
|
else
|
|
fout_tenzij_droog "De docker-compose-plugin ontbreekt. Installeer 'docker-compose-plugin' via je pakketbeheerder."
|
|
fi
|
|
}
|
|
|
|
# ── Broncode ophalen ─────────────────────────────────────────────────────────
|
|
bron_url() {
|
|
if [ -n "$TOKEN" ]; then
|
|
printf 'https://%s@%s/%s.git' "$TOKEN" "$GIT_HOST" "$GIT_PAD"
|
|
else
|
|
printf 'https://%s/%s.git' "$GIT_HOST" "$GIT_PAD"
|
|
fi
|
|
}
|
|
|
|
haal_broncode() {
|
|
stap "Broncode ophalen"
|
|
if [ -d "$DOEL/.git" ]; then
|
|
log "Bestaande installatie bijwerken in $DOEL"
|
|
doe $SUDO git -C "$DOEL" fetch --depth 1 origin "$BRANCH"
|
|
doe $SUDO git -C "$DOEL" reset --hard "origin/$BRANCH"
|
|
goed "Bijgewerkt naar de nieuwste $BRANCH"
|
|
return 0
|
|
fi
|
|
|
|
doe $SUDO mkdir -p "$DOEL"
|
|
if heeft git; then
|
|
# Zonder .git kunnen we later niet netjes bijwerken, dus git heeft de
|
|
# voorkeur boven een los archief.
|
|
if doe $SUDO git clone --depth 1 --branch "$BRANCH" "$(bron_url)" "$DOEL"; then
|
|
goed "Gekloond naar $DOEL"
|
|
return 0
|
|
fi
|
|
waarschuw "Klonen mislukte; ik probeer het archief."
|
|
fi
|
|
|
|
archief="https://${GIT_HOST}/${GIT_PAD}/archive/${BRANCH}.tar.gz"
|
|
log "Downloaden: $archief"
|
|
if [ "$DROOG" -eq 0 ]; then
|
|
tijdelijk="$(mktemp -d)"
|
|
curl -fsSL ${TOKEN:+-H "Authorization: token $TOKEN"} "$archief" \
|
|
| tar xz -C "$tijdelijk" --strip-components=1 \
|
|
|| fout "Ophalen mislukt. Is de repo openbaar? Zo niet, gebruik --token."
|
|
$SUDO cp -r "$tijdelijk/." "$DOEL/"
|
|
rm -rf "$tijdelijk"
|
|
fi
|
|
goed "Uitgepakt naar $DOEL"
|
|
}
|
|
|
|
# ── Instellingen ─────────────────────────────────────────────────────────────
|
|
server_ip() { hostname -I 2>/dev/null | awk '{print $1}'; }
|
|
|
|
kies_bind() {
|
|
# Alleen vragen als er nog iets te kiezen valt: met --bind, met --yes of
|
|
# zonder terminal is de keuze al gemaakt.
|
|
[ "$BIND_EXPLICIET" -eq 1 ] && return 0
|
|
[ "$JA" -eq 1 ] && return 0
|
|
[ "$DROOG" -eq 1 ] && return 0
|
|
[ -e /dev/tty ] || return 0
|
|
|
|
# Installeer je opnieuw over een bestaande map, dan is je huidige keuze het
|
|
# uitgangspunt — anders zet enter je server ongemerkt terug op loopback.
|
|
huidig=""
|
|
if [ -f "$DOEL/.env" ]; then
|
|
lees_env_terug "$DOEL/.env"
|
|
huidig="$BIND"
|
|
fi
|
|
|
|
ip="$(server_ip)"
|
|
stap "Bereikbaarheid"
|
|
printf ' Waarop moet de webinterface luisteren?\n\n'
|
|
printf ' 1) Alleen deze server (127.0.0.1) — veiligst\n'
|
|
printf ' Erbij via een SSH-tunnel of een reverse proxy op deze machine.\n'
|
|
printf ' 2) Het hele netwerk (0.0.0.0)\n'
|
|
printf ' Direct bereikbaar op http://%s:%s — zet er wel TLS voor.\n' "${ip:-<server-ip>}" "$POORT"
|
|
printf ' 3) Een specifiek adres\n\n'
|
|
if [ -n "$huidig" ]; then
|
|
printf ' Nu ingesteld: %s\n' "$huidig"
|
|
printf ' Keuze [enter = zo laten]: '
|
|
else
|
|
printf ' Keuze [1]: '
|
|
fi
|
|
read -r keuze </dev/tty || keuze=""
|
|
case "$keuze" in
|
|
1) BIND="127.0.0.1" ;;
|
|
2) BIND="0.0.0.0" ;;
|
|
3) printf ' Adres: '
|
|
read -r adres </dev/tty || adres=""
|
|
geldig_bind "$adres" || fout "Ongeldig bind-adres: $adres"
|
|
BIND="$adres" ;;
|
|
'') BIND="${huidig:-127.0.0.1}" ;;
|
|
*) fout "Onbekende keuze: $keuze" ;;
|
|
esac
|
|
BIND_EXPLICIET=1
|
|
goed "Luistert op $BIND"
|
|
}
|
|
|
|
zet_env_regel() {
|
|
# zet_env_regel BESTAND SLEUTEL WAARDE — vervangt de regel of voegt hem toe.
|
|
# Waarden zijn hier altijd al gevalideerd (cijfers, punten, dubbele punten).
|
|
$SUDO sh -c "sed -i '/^$2=/d' '$1' && printf '%s=%s\n' '$2' '$3' >> '$1'"
|
|
}
|
|
|
|
lees_env_terug() {
|
|
# Wat er in .env staat is de waarheid, niet wat dit script als standaard
|
|
# heeft. Zonder dit klopt bij --update de wachtlus en het slotadres niet
|
|
# meer zodra je ooit een andere poort hebt gekozen.
|
|
[ -f "$1" ] || return 0
|
|
# Lezen kan meestal zonder sudo; alleen als het bestand van root is en
|
|
# afgeschermd, is de omweg nodig.
|
|
if [ -r "$1" ]; then
|
|
_lees="cat"
|
|
elif [ -n "$SUDO" ]; then
|
|
_lees="$SUDO cat"
|
|
elif heeft sudo; then
|
|
_lees="sudo cat"
|
|
else
|
|
_lees="cat"
|
|
fi
|
|
_inhoud="$($_lees "$1" 2>/dev/null || true)"
|
|
_poort="$(printf '%s\n' "$_inhoud" | sed -n 's/^PORT=//p' | tail -1)"
|
|
_bind="$(printf '%s\n' "$_inhoud" | sed -n 's/^BIND=//p' | tail -1)"
|
|
if [ "$POORT_EXPLICIET" -eq 0 ]; then
|
|
case "$_poort" in ''|*[!0-9]*) ;; *) POORT="$_poort" ;; esac
|
|
fi
|
|
if [ "$BIND_EXPLICIET" -eq 0 ] && [ -n "$_bind" ] && geldig_bind "$_bind"; then
|
|
BIND="$_bind"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
schrijf_env() {
|
|
stap "Instellingen"
|
|
env_bestand="$DOEL/.env"
|
|
|
|
if [ -f "$env_bestand" ]; then
|
|
# Een bestaande .env bevat jouw instellingen; die blijven staan. Alleen
|
|
# wat je nu expliciet meegeeft wordt doorgevoerd — dat is precies het
|
|
# verschil dat eerder ontbrak, waardoor --bind niets deed zodra er al
|
|
# een .env stond.
|
|
gewijzigd=0
|
|
if [ "$BIND_EXPLICIET" -eq 1 ]; then
|
|
if [ "$DROOG" -eq 1 ]; then
|
|
printf ' [droog] BIND=%s in bestaande .env zetten\n' "$BIND"
|
|
else
|
|
zet_env_regel "$env_bestand" BIND "$BIND"
|
|
fi
|
|
gewijzigd=1
|
|
fi
|
|
if [ "$POORT_EXPLICIET" -eq 1 ]; then
|
|
if [ "$DROOG" -eq 1 ]; then
|
|
printf ' [droog] PORT=%s in bestaande .env zetten\n' "$POORT"
|
|
else
|
|
zet_env_regel "$env_bestand" PORT "$POORT"
|
|
fi
|
|
gewijzigd=1
|
|
fi
|
|
if [ "$gewijzigd" -eq 1 ]; then
|
|
goed "Bestaande .env bijgewerkt (BIND=$BIND, PORT=$POORT)"
|
|
else
|
|
lees_env_terug "$env_bestand"
|
|
goed "Bestaande .env blijft ongemoeid (BIND=$BIND, PORT=$POORT)"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
if [ "$DROOG" -eq 1 ]; then
|
|
printf ' [droog] .env aanmaken met BIND=%s PORT=%s\n' "$BIND" "$POORT"
|
|
return 0
|
|
fi
|
|
|
|
if [ -f "$DOEL/.env.example" ]; then
|
|
$SUDO cp "$DOEL/.env.example" "$env_bestand"
|
|
else
|
|
printf '' | $SUDO tee "$env_bestand" >/dev/null
|
|
fi
|
|
$SUDO sh -c "sed -i '/^BIND=/d;/^PORT=/d' '$env_bestand'"
|
|
$SUDO sh -c "printf 'BIND=%s\nPORT=%s\n' '$BIND' '$POORT' >> '$env_bestand'"
|
|
goed "Aangemaakt: $env_bestand (BIND=$BIND, PORT=$POORT)"
|
|
}
|
|
|
|
# ── Starten ──────────────────────────────────────────────────────────────────
|
|
start() {
|
|
stap "Bouwen en starten"
|
|
if [ "$DROOG" -eq 0 ] && [ ! -f "$DOEL/docker-compose.yml" ]; then
|
|
fout "Geen docker-compose.yml in $DOEL — het ophalen van de broncode is misgegaan."
|
|
fi
|
|
log "Dit duurt de eerste keer enkele minuten."
|
|
doe $SUDO docker compose -f "$DOEL/docker-compose.yml" --project-directory "$DOEL" \
|
|
up -d --build --remove-orphans
|
|
goed "Container gestart"
|
|
}
|
|
|
|
wacht_op_gereed() {
|
|
[ "$DROOG" -eq 1 ] && return 0
|
|
stap "Wachten tot Server Up reageert"
|
|
printf ' '
|
|
i=0
|
|
while [ "$i" -lt 45 ]; do
|
|
if curl -fsS "http://127.0.0.1:${POORT}/healthz" >/dev/null 2>&1; then
|
|
# Spatie na de dubbele punt toestaan: of json compact is,
|
|
# hangt af van de Flask-versie.
|
|
versie="$(curl -fsS "http://127.0.0.1:${POORT}/healthz" 2>/dev/null \
|
|
| sed -n 's/.*"version" *: *"\([^"]*\)".*/\1/p')"
|
|
printf '\n'
|
|
goed "Server Up${versie:+ }${versie:-} draait"
|
|
return 0
|
|
fi
|
|
# Een puntje per poging, zodat je ziet dat er nog iets gebeurt.
|
|
printf '.'
|
|
i=$((i + 1))
|
|
sleep 2
|
|
done
|
|
printf '\n'
|
|
waarschuw "Server Up reageerde niet binnen anderhalve minuut."
|
|
log "Bekijk de logs met: $SUDO docker compose -f $DOEL/docker-compose.yml logs --tail=50"
|
|
return 1
|
|
}
|
|
|
|
# ── Beheerdersaccount ────────────────────────────────────────────────────────
|
|
# Tussen "container draait" en "eerste account" ligt een venster waarin iedereen
|
|
# die de pagina bereikt zichzelf beheerder kan maken. Dat venster hoort zo kort
|
|
# mogelijk te zijn, dus kan het script het account meteen aanmaken.
|
|
WW_GEGENEREERD=0
|
|
BEHEERDER_WW_TOON=""
|
|
|
|
json_tekst() {
|
|
# Escapet een waarde voor gebruik tussen JSON-aanhalingstekens.
|
|
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
|
}
|
|
|
|
genereer_wachtwoord() {
|
|
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 24
|
|
}
|
|
|
|
vraag_wachtwoord() {
|
|
# Twee keer intypen, met de echo uit. Mislukt dat, dan valt hij terug op
|
|
# een gegenereerd wachtwoord in plaats van er een half account van te maken.
|
|
oude_stty=""
|
|
if stty -g </dev/tty >/dev/null 2>&1; then oude_stty="$(stty -g </dev/tty)"; fi
|
|
poging=0
|
|
while [ "$poging" -lt 3 ]; do
|
|
poging=$((poging + 1))
|
|
if [ -n "$oude_stty" ]; then stty -echo </dev/tty; fi
|
|
printf ' Wachtwoord voor %s (minstens 10 tekens): ' "$BEHEERDER"
|
|
read -r ww1 </dev/tty || ww1=""
|
|
printf '\n Nogmaals: '
|
|
read -r ww2 </dev/tty || ww2=""
|
|
if [ -n "$oude_stty" ]; then stty "$oude_stty" </dev/tty; fi
|
|
printf '\n'
|
|
if [ "$ww1" != "$ww2" ]; then
|
|
waarschuw "De twee invoeren verschillen."
|
|
elif [ "${#ww1}" -lt 10 ]; then
|
|
waarschuw "Minstens 10 tekens, dat is ook wat de server eist."
|
|
else
|
|
BEHEERDER_WW="$ww1"
|
|
return 0
|
|
fi
|
|
done
|
|
waarschuw "Geen bruikbaar wachtwoord ingevoerd; er wordt er een aangemaakt."
|
|
return 1
|
|
}
|
|
|
|
bepaal_wachtwoord() {
|
|
# Volgorde: bestand, omgevingsvariabele, vragen, anders zelf aanmaken.
|
|
[ -n "$BEHEERDER_WW" ] && return 0
|
|
if [ -n "$BEHEERDER_WW_BESTAND" ]; then
|
|
[ -r "$BEHEERDER_WW_BESTAND" ] || fout "Kan $BEHEERDER_WW_BESTAND niet lezen."
|
|
BEHEERDER_WW="$(head -n 1 "$BEHEERDER_WW_BESTAND")"
|
|
[ -n "$BEHEERDER_WW" ] || fout "$BEHEERDER_WW_BESTAND is leeg."
|
|
return 0
|
|
fi
|
|
if [ -n "${SU_ADMIN_PASSWORD:-}" ]; then
|
|
BEHEERDER_WW="$SU_ADMIN_PASSWORD"
|
|
return 0
|
|
fi
|
|
if [ "$JA" -eq 0 ] && [ -e /dev/tty ]; then
|
|
vraag_wachtwoord && return 0
|
|
fi
|
|
BEHEERDER_WW="$(genereer_wachtwoord)"
|
|
[ -n "$BEHEERDER_WW" ] || fout "Kon geen wachtwoord aanmaken (/dev/urandom niet leesbaar). Gebruik --admin-password-file."
|
|
WW_GEGENEREERD=1
|
|
return 0
|
|
}
|
|
|
|
vraag_beheerder() {
|
|
# Niets meegegeven, maar wel een terminal? Dan hier aanbieden, want dit is
|
|
# het moment waarop het uitmaakt.
|
|
[ -n "$BEHEERDER" ] && return 0
|
|
[ "$JA" -eq 1 ] && return 0
|
|
[ "$DROOG" -eq 1 ] && return 0
|
|
[ -e /dev/tty ] || return 0
|
|
|
|
stap "Beheerdersaccount"
|
|
if ! vraag "Nu meteen een beheerdersaccount aanmaken?"; then
|
|
return 0
|
|
fi
|
|
printf ' Gebruikersnaam: '
|
|
read -r naam </dev/tty || naam=""
|
|
naam="$(printf '%s' "$naam" | tr -d '[:space:]')"
|
|
if [ -z "$naam" ]; then
|
|
waarschuw "Geen naam opgegeven; overgeslagen."
|
|
return 0
|
|
fi
|
|
BEHEERDER="$naam"
|
|
return 0
|
|
}
|
|
|
|
beheerder_vooraf() {
|
|
# Alles wat om invoer vraagt gebeurt vóór het bouwen, zodat je daarna kunt
|
|
# weglopen terwijl het image gebouwd wordt.
|
|
[ "$DROOG" -eq 1 ] && return 0
|
|
vraag_beheerder
|
|
[ -n "$BEHEERDER" ] || return 0
|
|
bepaal_wachtwoord
|
|
return 0
|
|
}
|
|
|
|
maak_beheerder() {
|
|
[ -n "$BEHEERDER" ] || return 0
|
|
|
|
if [ "$DROOG" -eq 1 ]; then
|
|
stap "Beheerdersaccount"
|
|
printf ' [droog] account %s aanmaken via POST /api/auth/setup\n' "$BEHEERDER"
|
|
return 0
|
|
fi
|
|
|
|
bepaal_wachtwoord
|
|
|
|
# Het wachtwoord gaat via stdin naar curl, niet via de opdrachtregel:
|
|
# argumenten zijn voor elke gebruiker op de server zichtbaar in 'ps'.
|
|
antwoord="$(mktemp 2>/dev/null || echo /tmp/su-setup.$$)"
|
|
code="$(printf '{"username":"%s","password":"%s"}' \
|
|
"$(json_tekst "$BEHEERDER")" "$(json_tekst "$BEHEERDER_WW")" \
|
|
| curl -sS -o "$antwoord" -w '%{http_code}' \
|
|
-X POST -H 'Content-Type: application/json' \
|
|
--data-binary @- \
|
|
"http://127.0.0.1:${POORT}/api/auth/setup" 2>/dev/null || true)"
|
|
# Bij een mislukte verbinding schrijft curl zelf al "000"; kan curl helemaal
|
|
# niet draaien, dan blijft het leeg. Beide gevallen zijn hetzelfde verhaal.
|
|
case "$code" in ''|*[!0-9]*) code="000" ;; esac
|
|
|
|
case "$code" in
|
|
200) goed "Beheerder '$BEHEERDER' aangemaakt"
|
|
# Alleen een zelf aangemaakt wachtwoord moet nog getoond worden;
|
|
# een ingetypt of meegegeven wachtwoord kent de gebruiker al.
|
|
if [ "$WW_GEGENEREERD" -eq 1 ]; then
|
|
BEHEERDER_WW_TOON="$BEHEERDER_WW"
|
|
fi ;;
|
|
409) waarschuw "Er bestond al een account; er is niets gewijzigd."
|
|
BEHEERDER=""; WW_GEGENEREERD=0 ;;
|
|
000) waarschuw "Server Up was niet bereikbaar; account niet aangemaakt."
|
|
BEHEERDER=""; WW_GEGENEREERD=0 ;;
|
|
*) melding="$(sed -n 's/.*"msg" *: *"\([^"]*\)".*/\1/p' "$antwoord" 2>/dev/null)"
|
|
waarschuw "Aanmaken mislukt (HTTP $code)${melding:+: }${melding:-}"
|
|
BEHEERDER=""; WW_GEGENEREERD=0 ;;
|
|
esac
|
|
rm -f "$antwoord"
|
|
BEHEERDER_WW=""
|
|
}
|
|
|
|
afsluiting() {
|
|
if [ "$BIND" = "0.0.0.0" ]; then
|
|
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
adres="http://${ip:-<server-ip>}:${POORT}"
|
|
else
|
|
adres="http://localhost:${POORT}"
|
|
fi
|
|
|
|
printf '\n%s────────────────────────────────────────────────────────%s\n' "$C_KOP" "$C_UIT"
|
|
printf '%s Server Up draait op %s%s\n' "$C_OK" "$adres" "$C_UIT"
|
|
printf '%s────────────────────────────────────────────────────────%s\n\n' "$C_KOP" "$C_UIT"
|
|
|
|
if [ -n "$BEHEERDER" ]; then
|
|
printf ' %s✔%s Inloggen als: %s%s%s\n' "$C_OK" "$C_UIT" "$C_KOP" "$BEHEERDER" "$C_UIT"
|
|
if [ "$WW_GEGENEREERD" -eq 1 ]; then
|
|
printf ' Wachtwoord: %s%s%s\n' "$C_KOP" "$BEHEERDER_WW_TOON" "$C_UIT"
|
|
printf ' %sSchrijf dit nu over — het staat nergens anders.%s\n' "$C_WAARSCHUW" "$C_UIT"
|
|
fi
|
|
printf '\n'
|
|
else
|
|
printf ' %sMaak nu meteen een beheerdersaccount aan.%s\n' "$C_WAARSCHUW" "$C_UIT"
|
|
printf ' Zolang dat niet gebeurd is, kan iedereen die deze pagina\n'
|
|
printf ' bereikt het account claimen.\n\n'
|
|
fi
|
|
|
|
if [ "$BIND" = "127.0.0.1" ]; then
|
|
printf ' Alleen bereikbaar vanaf deze server zelf. Van buitenaf erbij?\n'
|
|
printf ' 1. Zet een reverse proxy met TLS ervoor\n'
|
|
printf ' 2. sed -i "s/^BIND=.*/BIND=0.0.0.0/" %s/.env\n' "$DOEL"
|
|
printf ' 3. %s docker compose -f %s/docker-compose.yml up -d\n\n' "${SUDO:-}" "$DOEL"
|
|
else
|
|
printf ' %sDe interface staat open op je netwerk. Server Up beheert%s\n' "$C_WAARSCHUW" "$C_UIT"
|
|
printf ' Docker als root, dus zet er een reverse proxy met TLS voor.\n\n'
|
|
fi
|
|
|
|
printf ' Bijwerken: sh %s/install.sh --update\n' "$DOEL"
|
|
printf ' Logs: %s docker compose -f %s/docker-compose.yml logs -f\n' "${SUDO:-}" "$DOEL"
|
|
printf ' Documentatie: %s/README.md\n\n' "$DOEL"
|
|
}
|
|
|
|
verwijderen() {
|
|
stap "Server Up verwijderen"
|
|
[ -d "$DOEL" ] || fout "Niets gevonden in $DOEL"
|
|
if ! vraag "Container stoppen en verwijderen? Je gegevens blijven staan."; then
|
|
log "Afgebroken."
|
|
exit 0
|
|
fi
|
|
doe $SUDO docker compose -f "$DOEL/docker-compose.yml" --project-directory "$DOEL" \
|
|
down --remove-orphans
|
|
goed "Container gestopt en verwijderd"
|
|
printf '\n'
|
|
log "Je gegevens staan nog in het docker-volume 'su-data' en in je stackmappen."
|
|
log "Alles definitief weg? Dan:"
|
|
log " $SUDO docker volume rm su-data"
|
|
log " $SUDO rm -rf $DOEL"
|
|
printf '\n'
|
|
}
|
|
|
|
# ── Hoofdprogramma ───────────────────────────────────────────────────────────
|
|
# De testsuite laadt dit bestand om losse functies te kunnen aanroepen — vooral
|
|
# de vragen die bepalen waarop de server straks luistert. Zonder deze uitgang
|
|
# zou het script bij het inladen meteen een hele installatie beginnen.
|
|
if [ -n "${SU_INSTALL_ALLEEN_FUNCTIES:-}" ]; then
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
|
|
printf '\n%sServer Up — installatiescript %s%s\n' "$C_KOP" "$SCRIPT_VERSIE" "$C_UIT"
|
|
[ "$DROOG" -eq 1 ] && waarschuw "Proefdraai: er wordt niets gewijzigd."
|
|
|
|
case "$ACTIE" in
|
|
uninstall)
|
|
bepaal_rechten
|
|
verwijderen
|
|
;;
|
|
update)
|
|
[ -d "$DOEL" ] || fout "Geen installatie gevonden in $DOEL. Installeer eerst zonder --update."
|
|
controleer_systeem
|
|
controleer_docker
|
|
haal_broncode
|
|
# Ook bij bijwerken: expliciete --bind/--port doorvoeren, en anders
|
|
# overnemen wat er al staat.
|
|
schrijf_env
|
|
start
|
|
wacht_op_gereed || true
|
|
printf '\n'
|
|
goed "Bijgewerkt."
|
|
;;
|
|
create-admin)
|
|
# Losse actie voor een installatie die al draait: handig als het
|
|
# aanmaken tijdens de installatie is misgegaan, of overgeslagen.
|
|
# Docker en schrijfrechten zijn hier niet nodig — alleen .env lezen
|
|
# en één verzoek naar de draaiende server — dus geen bepaal_rechten.
|
|
lees_env_terug "$DOEL/.env"
|
|
if [ -z "$BEHEERDER" ]; then
|
|
if [ -e /dev/tty ] && [ "$JA" -eq 0 ]; then
|
|
printf '\n Gebruikersnaam: '
|
|
read -r naam </dev/tty || naam=""
|
|
BEHEERDER="$(printf '%s' "$naam" | tr -d '[:space:]')"
|
|
fi
|
|
[ -n "$BEHEERDER" ] || fout "Geef de gebruikersnaam op met --admin NAAM."
|
|
fi
|
|
stap "Beheerdersaccount"
|
|
maak_beheerder
|
|
if [ -n "$BEHEERDER" ] && [ "$WW_GEGENEREERD" -eq 1 ]; then
|
|
printf '\n Wachtwoord: %s%s%s\n' "$C_KOP" "$BEHEERDER_WW_TOON" "$C_UIT"
|
|
printf ' %sSchrijf dit nu over — het staat nergens anders.%s\n\n' "$C_WAARSCHUW" "$C_UIT"
|
|
fi
|
|
;;
|
|
install)
|
|
controleer_systeem
|
|
controleer_docker
|
|
kies_bind
|
|
beheerder_vooraf
|
|
haal_broncode
|
|
schrijf_env
|
|
start
|
|
wacht_op_gereed || true
|
|
maak_beheerder
|
|
afsluiting
|
|
;;
|
|
esac
|