Compare commits
2 commits
be1aad94ae
...
8e53b90615
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e53b90615 | ||
|
|
d7cbb4f440 |
6 changed files with 866 additions and 27 deletions
49
CHANGELOG.md
49
CHANGELOG.md
|
|
@ -1,3 +1,52 @@
|
|||
# v0.7.40-beta — Account en bereikbaarheid meteen goed
|
||||
|
||||
Na het installeren was je nog niet klaar: je moest zelf naar de webinterface om
|
||||
een beheerdersaccount te claimen, en stond `BIND` op `127.0.0.1`, dan kón je daar
|
||||
niet eens bij zonder eerst `.env` aan te passen. Het script regelt nu allebei.
|
||||
|
||||
**Beheerdersaccount.** Op een terminal vraagt het script of je er meteen één wil,
|
||||
en zo ja om een naam en wachtwoord (twee keer, met de echo uit). Automatisch gaat
|
||||
het met `--admin NAAM` plus `--admin-password-file PAD` of `SU_ADMIN_PASSWORD`.
|
||||
Is er geen wachtwoordbron én geen terminal, dan maakt het script er zelf een van
|
||||
24 tekens en zet die op het scherm.
|
||||
|
||||
Het wachtwoord kan bewust *niet* als optie mee: `--admin-password` weigert met
|
||||
uitleg. Opdrachtregelargumenten zijn voor elke gebruiker op de server zichtbaar
|
||||
met `ps` en blijven in je shell-geschiedenis staan. Onderweg naar de server gaat
|
||||
het via stdin naar `curl`, niet als argument.
|
||||
|
||||
Nieuw is ook **`--create-admin`**: alleen het account aanmaken, bij een
|
||||
installatie die al draait. Voor als het de eerste keer is misgegaan of je het
|
||||
hebt overgeslagen. De poort komt daarbij uit `.env`.
|
||||
|
||||
**Bereikbaarheid.** Het script vraagt nu waarop de interface moet luisteren:
|
||||
alleen deze server, het hele netwerk (met het gedetecteerde adres erbij), of een
|
||||
adres dat je zelf opgeeft. Installeer je opnieuw over een bestaande map, dan is
|
||||
je huidige instelling het uitgangspunt en houdt enter die vast.
|
||||
|
||||
Daarbij zat een fout die je zelf tegenkwam: een bestaande `.env` bleef *altijd*
|
||||
ongemoeid, ook als je `--bind` meegaf. De installatie leek dan te lukken terwijl
|
||||
de server op het oude adres bleef luisteren. Nu wordt doorgevoerd wat je
|
||||
expliciet meegeeft, en blijft de rest staan. Andersom rekent het script bij
|
||||
`--update` voortaan met wat er écht in `.env` staat: draaide je op poort 8080,
|
||||
dan wachtte het daarvoor op poort 5000 en meldde het het verkeerde adres.
|
||||
|
||||
Verder: `--dry-run` riep `sudo docker` aan en vroeg dus om een wachtwoord,
|
||||
terwijl het net beloofd had niets te doen — zonder terminal liep het daar vast.
|
||||
Een proefdraai spreekt de Docker-daemon nu niet meer aan. En een ongeldig
|
||||
bind-adres of een poort buiten 1–65535 wordt meteen geweigerd in plaats van als
|
||||
een onbegrijpelijke fout uit `docker compose` terug te komen.
|
||||
|
||||
De vragen worden allemaal vooraf gesteld, zodat je kunt weglopen terwijl het
|
||||
image gebouwd wordt.
|
||||
|
||||
Getest met een echte 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 tweede account dat geweigerd hoort te
|
||||
worden, en een server die niet reageert.
|
||||
|
||||
---
|
||||
|
||||
# v0.7.30 — Installeren met één regel
|
||||
|
||||
```bash
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -37,7 +37,9 @@ curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install
|
|||
```
|
||||
|
||||
Dat controleert je systeem, installeert Docker als het ontbreekt, zet Server Up
|
||||
in `/opt/server-up` en start het op `http://localhost:5000`.
|
||||
in `/opt/server-up` en start het. Onderweg vraagt het waarop de interface moet
|
||||
luisteren en of het meteen een beheerdersaccount voor je aanmaakt — die twee
|
||||
vragen komen vooraf, zodat je kunt weglopen terwijl het image gebouwd wordt.
|
||||
|
||||
Liever eerst lezen wat je uitvoert — verstandig, want het script gebruikt
|
||||
root-rechten:
|
||||
|
|
@ -58,12 +60,25 @@ cp .env.example .env
|
|||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Alle opties staan in [docs/installeren.md](docs/installeren.md):
|
||||
`--dir`, `--port`, `--bind`, `--branch`, `--token`, `--update`, `--uninstall`.
|
||||
Alle opties staan in [docs/installeren.md](docs/installeren.md): `--dir`,
|
||||
`--port`, `--bind`, `--branch`, `--token`, `--admin`, `--create-admin`,
|
||||
`--update`, `--uninstall`.
|
||||
|
||||
Open daarna de interface en **maak meteen een beheerdersaccount aan.**
|
||||
Zolang dat niet gebeurd is, kan iedereen die de pagina bereikt het account
|
||||
claimen — de container waarschuwt daar bij het opstarten ook voor.
|
||||
### Het beheerdersaccount
|
||||
|
||||
Zolang er nog geen account is, kan iedereen die de pagina bereikt het claimen —
|
||||
de container waarschuwt daar bij het opstarten ook voor. Laat het script het
|
||||
daarom meteen aanmaken:
|
||||
|
||||
```bash
|
||||
sh install.sh --admin ramon # vraagt om een wachtwoord
|
||||
SU_ADMIN_PASSWORD='...' sh install.sh --admin ramon --yes # zonder vragen
|
||||
```
|
||||
|
||||
Zonder wachtwoordbron en zonder terminal maakt het script er zelf een en toont
|
||||
die. Overgeslagen of misgegaan? `sh /opt/server-up/install.sh --create-admin
|
||||
--admin ramon` doet het achteraf. Een wachtwoord als opdrachtregeloptie wordt
|
||||
geweigerd: dat is zichtbaar in `ps`.
|
||||
|
||||
### Instellingen
|
||||
|
||||
|
|
@ -76,7 +91,10 @@ PORT=5000
|
|||
```
|
||||
|
||||
De poort bindt standaard op `127.0.0.1`. Zet hem pas open als er een reverse
|
||||
proxy met TLS voor staat.
|
||||
proxy met TLS voor staat. Achteraf wijzigen kan met
|
||||
`sh /opt/server-up/install.sh --update --bind 0.0.0.0`; pas het anders aan in
|
||||
`.env` en **niet** in `docker-compose.yml`, want dat bestand komt uit de repo en
|
||||
wordt bij elke update overschreven.
|
||||
|
||||
## Documentatie
|
||||
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.7.30
|
||||
0.7.40-beta
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ schrijven én `docker` aanroepen, dan blijft alles onder je eigen account.
|
|||
--bind ADRES Waarop de poort luistert (standaard: 127.0.0.1)
|
||||
--branch NAAM Branch om te installeren (standaard: main)
|
||||
--token TOKEN Toegangstoken, als je repo niet openbaar is
|
||||
--admin NAAM Maak meteen een beheerdersaccount met deze naam
|
||||
--admin-password-file PAD
|
||||
Lees het wachtwoord uit een bestand
|
||||
--create-admin Alleen het account aanmaken, bij een draaiende installatie
|
||||
--update Bijwerken naar de nieuwste versie
|
||||
--uninstall Stoppen en verwijderen (gegevens blijven staan)
|
||||
--yes Niets vragen
|
||||
|
|
@ -59,12 +63,15 @@ schrijven én `docker` aanroepen, dan blijft alles onder je eigen account.
|
|||
### Voorbeelden
|
||||
|
||||
```bash
|
||||
# Standaard, alleen bereikbaar vanaf de server zelf
|
||||
# Standaard: vraagt waarop het moet luisteren en of je een account wil
|
||||
sh install.sh
|
||||
|
||||
# Bereikbaar op je netwerk, andere poort
|
||||
sh install.sh --bind 0.0.0.0 --port 8080
|
||||
|
||||
# Volledig automatisch, met account
|
||||
SU_ADMIN_PASSWORD='een-lang-wachtwoord' sh install.sh --admin ramon --yes
|
||||
|
||||
# Ergens anders neerzetten
|
||||
sh install.sh --dir /srv/server-up
|
||||
|
||||
|
|
@ -77,21 +84,65 @@ sh install.sh --token jouw-forgejo-token
|
|||
|
||||
---
|
||||
|
||||
## Meteen na het installeren
|
||||
## Het beheerdersaccount
|
||||
|
||||
**Maak een beheerdersaccount aan.** Server Up beheert de Docker-daemon, en wie
|
||||
containers kan starten kan willekeurige mappen van de host mounten. Zolang er
|
||||
nog geen account is, kan iedereen die de pagina bereikt het claimen.
|
||||
Server Up beheert de Docker-daemon, en wie containers kan starten kan
|
||||
willekeurige mappen van de host mounten. Zolang er nog geen account is, kan
|
||||
iedereen die de pagina bereikt het claimen — dus hoe korter dat venster, hoe
|
||||
beter. Het script kan het account daarom meteen aanmaken.
|
||||
|
||||
De poort staat standaard op `127.0.0.1`, dus alleen bereikbaar vanaf de server
|
||||
zelf. Wil je er van buitenaf bij, zet dan eerst een reverse proxy met TLS ervoor
|
||||
en pas daarna `BIND` aan:
|
||||
Op een terminal vraagt het script erom. Automatisch kan het ook:
|
||||
|
||||
```bash
|
||||
sed -i 's/^BIND=.*/BIND=0.0.0.0/' /opt/server-up/.env
|
||||
docker compose -f /opt/server-up/docker-compose.yml up -d
|
||||
# Wachtwoord uit een bestand
|
||||
sh install.sh --admin ramon --admin-password-file /root/su-ww --yes
|
||||
|
||||
# Of uit een omgevingsvariabele
|
||||
SU_ADMIN_PASSWORD='een-lang-wachtwoord' sh install.sh --admin ramon --yes
|
||||
```
|
||||
|
||||
Geef je geen wachtwoord en is er geen terminal, dan maakt het script er zelf een
|
||||
van 24 tekens en zet die op het scherm. Schrijf die meteen over — hij staat
|
||||
nergens anders.
|
||||
|
||||
> Het wachtwoord kan **niet** als optie mee. Opdrachtregelargumenten zijn voor
|
||||
> elke gebruiker op de server zichtbaar met `ps` en blijven in je
|
||||
> shell-geschiedenis staan. `--admin-password` weigert daarom met een verwijzing
|
||||
> naar de twee manieren hierboven.
|
||||
|
||||
Ging het aanmaken mis, of heb je het overgeslagen? Dan kan het achteraf, zolang
|
||||
er nog geen account bestaat:
|
||||
|
||||
```bash
|
||||
sh /opt/server-up/install.sh --create-admin --admin ramon
|
||||
```
|
||||
|
||||
Eist minstens 10 tekens, net als de webinterface.
|
||||
|
||||
---
|
||||
|
||||
## Bereikbaarheid
|
||||
|
||||
Het script vraagt waarop de webinterface moet luisteren:
|
||||
|
||||
| Keuze | `BIND` | Wanneer |
|
||||
|---|---|---|
|
||||
| Alleen deze server | `127.0.0.1` | Standaard en veiligst. Erbij via een SSH-tunnel of een reverse proxy op dezelfde machine. |
|
||||
| Het hele netwerk | `0.0.0.0` | Direct bereikbaar op het adres van je server. Zet er een reverse proxy met TLS voor. |
|
||||
| Een specifiek adres | zelf opgeven | Bijvoorbeeld alleen je beheernetwerk. |
|
||||
|
||||
Achteraf wijzigen kan met dezelfde optie; die wordt ook doorgevoerd in een
|
||||
`.env` die er al staat:
|
||||
|
||||
```bash
|
||||
sh /opt/server-up/install.sh --update --bind 0.0.0.0
|
||||
```
|
||||
|
||||
Wat je *niet* expliciet meegeeft blijft staan zoals het was, zodat een `--update`
|
||||
je instellingen niet terugzet. Pas `BIND` daarom aan in `.env` of via deze optie
|
||||
— **niet** in `docker-compose.yml`, want dat bestand komt uit de repo en wordt
|
||||
bij elke update overschreven.
|
||||
|
||||
Zie [beveiliging.md](beveiliging.md) voor de reverse proxy, rollen en SSO.
|
||||
|
||||
---
|
||||
|
|
|
|||
360
install.sh
360
install.sh
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
set -eu
|
||||
|
||||
SCRIPT_VERSIE="1.0"
|
||||
SCRIPT_VERSIE="1.1"
|
||||
GIT_HOST="git.ramonbesselink.nl"
|
||||
GIT_PAD="bes-r/server-up"
|
||||
BRANCH="main"
|
||||
|
|
@ -24,6 +24,13 @@ 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
|
||||
|
|
@ -79,15 +86,28 @@ Gebruik: install.sh [opties]
|
|||
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
|
||||
}
|
||||
|
|
@ -96,12 +116,18 @@ UITLEG
|
|||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--dir) DOEL="${2:?--dir vereist een pad}"; shift 2 ;;
|
||||
--port) POORT="${2:?--port vereist een nummer}"; shift 2 ;;
|
||||
--bind) BIND="${2:?--bind vereist een adres}"; 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 ;;
|
||||
|
|
@ -110,6 +136,18 @@ while [ $# -gt 0 ]; do
|
|||
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
|
||||
|
|
@ -146,6 +184,18 @@ controleer_systeem() {
|
|||
}
|
||||
|
||||
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 '?'))"
|
||||
|
|
@ -216,18 +266,127 @@ haal_broncode() {
|
|||
}
|
||||
|
||||
# ── 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
|
||||
goed "Bestaande .env blijft ongemoeid"
|
||||
# BIND en PORT wel bijwerken als ze expliciet zijn meegegeven.
|
||||
# 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
|
||||
|
|
@ -276,6 +435,148 @@ wacht_op_gereed() {
|
|||
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}')"
|
||||
|
|
@ -288,9 +589,18 @@ afsluiting() {
|
|||
printf '%s Server Up draait op %s%s\n' "$C_OK" "$adres" "$C_UIT"
|
||||
printf '%s────────────────────────────────────────────────────────%s\n\n' "$C_KOP" "$C_UIT"
|
||||
|
||||
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'
|
||||
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'
|
||||
|
|
@ -326,6 +636,13 @@ verwijderen() {
|
|||
}
|
||||
|
||||
# ── 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."
|
||||
|
||||
|
|
@ -339,18 +656,45 @@ case "$ACTIE" in
|
|||
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
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ def test_help_werkt_en_noemt_alle_opties():
|
|||
r = _draai("--help")
|
||||
assert r.returncode == 0
|
||||
for optie in ("--dir", "--port", "--bind", "--branch", "--token",
|
||||
"--update", "--uninstall", "--yes", "--dry-run"):
|
||||
"--update", "--uninstall", "--yes", "--dry-run",
|
||||
"--admin", "--admin-password-file", "--create-admin"):
|
||||
assert optie in r.stdout, f"{optie} niet gedocumenteerd in --help"
|
||||
|
||||
|
||||
|
|
@ -125,6 +126,382 @@ def test_proefdraai_werkt_zonder_curl_en_tar(tmp_path):
|
|||
assert not (tmp_path / "su").exists()
|
||||
|
||||
|
||||
# ── Beheerdersaccount ────────────────────────────────────────────────────────
|
||||
|
||||
def test_wachtwoord_mag_niet_op_de_opdrachtregel():
|
||||
"""Argumenten zijn voor elke gebruiker op de server zichtbaar met 'ps' en
|
||||
blijven in de shell-geschiedenis staan."""
|
||||
r = _draai("--admin-password", "geheimgeheim")
|
||||
assert r.returncode != 0
|
||||
assert "ps" in r.stderr
|
||||
|
||||
|
||||
def test_create_admin_zonder_naam_stopt(tmp_path):
|
||||
r = _draai("--create-admin", "--dir", str(tmp_path), "--yes")
|
||||
assert r.returncode != 0
|
||||
assert "--admin" in r.stderr
|
||||
|
||||
|
||||
def test_dry_run_toont_het_aanmaken_van_het_account(tmp_path):
|
||||
r = _draai("--dry-run", "--dir", str(tmp_path / "su"), "--yes", "--admin", "ramon")
|
||||
assert "/api/auth/setup" in r.stdout
|
||||
assert "ramon" in r.stdout
|
||||
|
||||
|
||||
def test_wachtwoord_staat_niet_in_de_curl_argumenten():
|
||||
"""Het wachtwoord gaat via stdin naar curl (--data-binary @-), niet als
|
||||
argument. Anders is het alsnog zichtbaar in 'ps'."""
|
||||
tekst = SCRIPT.read_text(encoding="utf-8")
|
||||
setup = tekst[tekst.index("maak_beheerder()"):]
|
||||
setup = setup[:setup.index("\nafsluiting()")]
|
||||
assert "--data-binary @-" in setup
|
||||
assert not re.search(r'-d\s+["\']?\{', setup), "JSON als curl-argument"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("adres", ["localhost", "0.0.0.0;rm -rf /", "mijn server"])
|
||||
def test_ongeldig_bind_adres_stopt(adres):
|
||||
r = _draai("--bind", adres)
|
||||
assert r.returncode != 0
|
||||
assert "bind" in r.stderr.lower()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("adres", ["127.0.0.1", "0.0.0.0", "10.0.20.22", "::1"])
|
||||
def test_geldig_bind_adres_wordt_geaccepteerd(adres, tmp_path):
|
||||
r = _draai("--dry-run", "--dir", str(tmp_path / "su"), "--yes", "--bind", adres)
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert f"BIND={adres}" in r.stdout
|
||||
|
||||
|
||||
# ── De vragen die het script stelt ───────────────────────────────────────────
|
||||
# Interactief is de normale manier waarop iemand dit script draait, en juist
|
||||
# daar bepaalt een verkeerd antwoord waar de server straks op luistert. Daarom
|
||||
# worden de vragen hier echt gesteld, via een pseudo-terminal.
|
||||
|
||||
def _in_terminal(fragment, invoer, tmp_path, tijdslimiet=20):
|
||||
"""Draait een stukje sh dat install.sh inlaadt, met een echte terminal."""
|
||||
import os
|
||||
import pty
|
||||
import re as _re
|
||||
import select
|
||||
import signal
|
||||
import time
|
||||
|
||||
script = tmp_path / "fragment.sh"
|
||||
script.write_text(f"set --\nSU_INSTALL_ALLEEN_FUNCTIES=1 . {SCRIPT}\n{fragment}\n")
|
||||
|
||||
pid, fd = pty.fork()
|
||||
if pid == 0: # kindproces
|
||||
os.execvp("sh", ["sh", str(script)])
|
||||
os.write(fd, invoer.encode())
|
||||
uit, einde = b"", time.time() + tijdslimiet
|
||||
while time.time() < einde:
|
||||
klaar, _, _ = select.select([fd], [], [], 0.5)
|
||||
if klaar:
|
||||
try:
|
||||
blok = os.read(fd, 4096)
|
||||
except OSError: # terminal dicht: proces weg
|
||||
break
|
||||
if not blok:
|
||||
break
|
||||
uit += blok
|
||||
elif os.waitpid(pid, os.WNOHANG)[0]:
|
||||
break
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
try:
|
||||
os.waitpid(pid, 0)
|
||||
except ChildProcessError:
|
||||
pass
|
||||
return _re.sub(r"\x1b\[[0-9;]*m", "", uit.decode(errors="replace").replace("\r", ""))
|
||||
|
||||
|
||||
BIND_FRAGMENT = ('DROOG=0; JA=0; BIND_EXPLICIET=0; DOEL="$MAP"\n'
|
||||
'kies_bind\n'
|
||||
'printf "UITKOMST BIND=%s\\n" "$BIND"\n')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("invoer,verwacht", [
|
||||
("1\n", "127.0.0.1"),
|
||||
("2\n", "0.0.0.0"),
|
||||
("3\n10.0.20.99\n", "10.0.20.99"),
|
||||
("\n", "127.0.0.1"), # enter zonder bestaande .env
|
||||
])
|
||||
def test_keuzemenu_voor_het_bind_adres(invoer, verwacht, tmp_path):
|
||||
leeg = tmp_path / "leeg"
|
||||
leeg.mkdir()
|
||||
uit = _in_terminal(f'MAP="{leeg}"\n' + BIND_FRAGMENT, invoer, tmp_path)
|
||||
assert f"UITKOMST BIND={verwacht}" in uit, uit
|
||||
|
||||
|
||||
def test_enter_behoudt_de_bestaande_instelling(tmp_path):
|
||||
"""Opnieuw installeren over een bestaande map mag je server niet ongemerkt
|
||||
terugzetten op loopback."""
|
||||
bestaand = tmp_path / "bestaand"
|
||||
bestaand.mkdir()
|
||||
(bestaand / ".env").write_text("BIND=10.0.20.5\nPORT=8080\n")
|
||||
uit = _in_terminal(f'MAP="{bestaand}"\n' + BIND_FRAGMENT, "\n", tmp_path)
|
||||
assert "UITKOMST BIND=10.0.20.5" in uit, uit
|
||||
assert "Nu ingesteld: 10.0.20.5" in uit
|
||||
|
||||
|
||||
@pytest.mark.parametrize("invoer,melding", [
|
||||
("9\n", "Onbekende keuze"),
|
||||
("3\nlocalhost\n", "Ongeldig bind-adres"),
|
||||
])
|
||||
def test_menu_weigert_onzin(invoer, melding, tmp_path):
|
||||
leeg = tmp_path / "leeg"
|
||||
leeg.mkdir()
|
||||
uit = _in_terminal(f'MAP="{leeg}"\n' + BIND_FRAGMENT, invoer, tmp_path)
|
||||
assert melding in uit
|
||||
assert "UITKOMST" not in uit
|
||||
|
||||
|
||||
WW_FRAGMENT = ('DROOG=0; JA=0; BEHEERDER=""\n'
|
||||
'beheerder_vooraf\n'
|
||||
'printf "UITKOMST naam=%s lengte=%s zelf=%s\\n" '
|
||||
'"$BEHEERDER" "${#BEHEERDER_WW}" "$WW_GEGENEREERD"\n')
|
||||
|
||||
|
||||
def test_account_aanmaken_kan_geweigerd_worden(tmp_path):
|
||||
uit = _in_terminal(WW_FRAGMENT, "n\n", tmp_path)
|
||||
assert "UITKOMST naam= lengte=0 zelf=0" in uit
|
||||
|
||||
|
||||
def test_wachtwoord_twee_keer_intypen(tmp_path):
|
||||
uit = _in_terminal(WW_FRAGMENT, "j\nramon\neenlangwachtwoord\neenlangwachtwoord\n", tmp_path)
|
||||
assert "UITKOMST naam=ramon lengte=17 zelf=0" in uit, uit
|
||||
|
||||
|
||||
@pytest.mark.parametrize("invoer,melding", [
|
||||
("j\nramon\neenlangwachtwoord\nietsanders\neenlangwachtwoord\neenlangwachtwoord\n",
|
||||
"verschillen"),
|
||||
("j\nramon\nkort\nkort\neenlangwachtwoord\neenlangwachtwoord\n",
|
||||
"Minstens 10 tekens"),
|
||||
])
|
||||
def test_wachtwoord_mag_opnieuw_na_een_fout(invoer, melding, tmp_path):
|
||||
uit = _in_terminal(WW_FRAGMENT, invoer, tmp_path)
|
||||
assert melding in uit
|
||||
assert "UITKOMST naam=ramon lengte=17 zelf=0" in uit, uit
|
||||
|
||||
|
||||
def test_na_drie_pogingen_maakt_het_script_er_zelf_een(tmp_path):
|
||||
"""Blijven hangen op een prompt is erger dan een gegenereerd wachtwoord."""
|
||||
uit = _in_terminal(WW_FRAGMENT, "j\nramon\na\na\nb\nb\nc\nc\n", tmp_path)
|
||||
assert "UITKOMST naam=ramon lengte=24 zelf=1" in uit, uit
|
||||
|
||||
|
||||
def test_lege_gebruikersnaam_slaat_over(tmp_path):
|
||||
uit = _in_terminal(WW_FRAGMENT, "j\n\n", tmp_path)
|
||||
assert "Geen naam opgegeven" in uit
|
||||
assert "UITKOMST naam= lengte=0 zelf=0" in uit
|
||||
|
||||
|
||||
def test_proefdraai_vraagt_niets_en_gebruikt_geen_sudo(tmp_path):
|
||||
"""Een proefdraai beloofde dat er niets gebeurt, maar riep wél 'sudo docker'
|
||||
aan. Op een server waar sudo een wachtwoord wil, liep hij daarop vast."""
|
||||
uit = _in_terminal(
|
||||
f'DROOG=1; SUDO="sudo"; DOEL="{tmp_path}"\ncontroleer_docker\nprintf "KLAAR\\n"\n',
|
||||
"", tmp_path, tijdslimiet=15)
|
||||
assert "KLAAR" in uit, f"controleer_docker liep vast:\n{uit}"
|
||||
assert "password" not in uit.lower()
|
||||
|
||||
|
||||
# ── .env ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_bind_wordt_doorgevoerd_in_een_bestaande_env(tmp_path):
|
||||
"""Eerder bleef een bestaande .env altijd ongemoeid, ook als je --bind
|
||||
meegaf. Dan leek de installatie te lukken maar bleef de server onbereikbaar
|
||||
op het oude adres."""
|
||||
doel = tmp_path / "su"
|
||||
doel.mkdir()
|
||||
(doel / ".env").write_text("BIND=127.0.0.1\nPORT=5000\nSU_TAG=0.7.30\n")
|
||||
r = _draai("--dry-run", "--dir", str(doel), "--yes", "--bind", "0.0.0.0")
|
||||
assert "BIND=0.0.0.0 in bestaande .env zetten" in r.stdout
|
||||
|
||||
|
||||
def test_env_blijft_ongemoeid_zonder_expliciete_opties(tmp_path):
|
||||
"""Anders zet een --update je instellingen elke keer terug."""
|
||||
doel = tmp_path / "su"
|
||||
doel.mkdir()
|
||||
(doel / ".env").write_text("BIND=0.0.0.0\nPORT=8080\n")
|
||||
r = _draai("--dry-run", "--dir", str(doel), "--yes")
|
||||
assert "ongemoeid" in r.stdout
|
||||
# En hij rekent verder met wat er écht staat, niet met de standaardwaarden.
|
||||
assert "BIND=0.0.0.0, PORT=8080" in r.stdout
|
||||
|
||||
|
||||
def test_poort_uit_env_wordt_gebruikt_voor_het_slotadres(tmp_path):
|
||||
doel = tmp_path / "su"
|
||||
doel.mkdir()
|
||||
(doel / ".env").write_text("BIND=0.0.0.0\nPORT=8080\n")
|
||||
r = _draai("--dry-run", "--dir", str(doel), "--yes")
|
||||
assert ":8080" in r.stdout
|
||||
|
||||
|
||||
# ── Tegen een echt draaiende server ──────────────────────────────────────────
|
||||
# Deze tests starten de app zelf en laten het script er een account op
|
||||
# aanmaken. Dat is het enige wat aantoont dat de JSON-opbouw, de poortdetectie
|
||||
# en de foutafhandeling in samenhang kloppen.
|
||||
|
||||
geen_curl = pytest.mark.skipif(not shutil.which("curl"),
|
||||
reason="curl ontbreekt (o.a. in python:3.12-slim)")
|
||||
|
||||
|
||||
def _vrije_poort():
|
||||
import socket
|
||||
s = socket.socket()
|
||||
s.bind(("127.0.0.1", 0))
|
||||
poort = s.getsockname()[1]
|
||||
s.close()
|
||||
return poort
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def draaiende_app(tmp_path):
|
||||
"""De echte app op een vrije poort, met een lege configuratie."""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
poort = _vrije_poort()
|
||||
data = tmp_path / "data"
|
||||
data.mkdir()
|
||||
omgeving = dict(os.environ,
|
||||
SU_CONFIG=str(data / "config.json"),
|
||||
SU_AUDIT=str(data / "audit.db"),
|
||||
SU_GIT_CACHE=str(data / "git"),
|
||||
SU_SECRET=str(data / "secret.key"),
|
||||
LIBRARY_DIR=str(data / "lib"),
|
||||
DATA_DIR=str(tmp_path / "appdata"),
|
||||
BACKUP_DIR=str(tmp_path / "backups"),
|
||||
PORT=str(poort))
|
||||
proces = subprocess.Popen([sys.executable, str(WORTEL / "server-up" / "app.py")],
|
||||
env=omgeving, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, text=True)
|
||||
try:
|
||||
for _ in range(80):
|
||||
if proces.poll() is not None:
|
||||
pytest.skip(f"app start niet: {proces.communicate()[0][-500:]}")
|
||||
try:
|
||||
urllib.request.urlopen(f"http://127.0.0.1:{poort}/healthz", timeout=1)
|
||||
break
|
||||
except (urllib.error.URLError, OSError):
|
||||
time.sleep(0.25)
|
||||
else:
|
||||
pytest.skip("app reageerde niet op tijd")
|
||||
yield poort
|
||||
finally:
|
||||
proces.terminate()
|
||||
try:
|
||||
proces.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
proces.kill()
|
||||
|
||||
|
||||
def _inloggen(poort, naam, wachtwoord):
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
verzoek = urllib.request.Request(
|
||||
f"http://127.0.0.1:{poort}/api/auth/login",
|
||||
data=json.dumps({"username": naam, "password": wachtwoord}).encode(),
|
||||
headers={"Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(verzoek, timeout=5) as a:
|
||||
return a.status
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code
|
||||
|
||||
|
||||
@geen_curl
|
||||
def test_script_maakt_een_werkend_beheerdersaccount(draaiende_app, tmp_path):
|
||||
poort = draaiende_app
|
||||
inst = tmp_path / "inst"
|
||||
inst.mkdir()
|
||||
# De poort komt uit .env, niet uit een optie: zo werkt het ook als je
|
||||
# destijds met --port hebt geïnstalleerd.
|
||||
(inst / ".env").write_text(f"BIND=127.0.0.1\nPORT={poort}\n")
|
||||
ww = tmp_path / "ww"
|
||||
ww.write_text("een-heel-lang-wachtwoord\n")
|
||||
|
||||
r = _draai("--create-admin", "--dir", str(inst), "--admin", "ramon",
|
||||
"--admin-password-file", str(ww), "--yes")
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert "aangemaakt" in r.stdout
|
||||
assert _inloggen(poort, "ramon", "een-heel-lang-wachtwoord") == 200
|
||||
|
||||
|
||||
@geen_curl
|
||||
def test_wachtwoord_met_aanhalingstekens_en_backslashes(draaiende_app, tmp_path):
|
||||
"""Het wachtwoord gaat als JSON over de lijn; zonder escapen levert dit een
|
||||
kapot verzoek op of — erger — een ander wachtwoord dan je denkt."""
|
||||
poort = draaiende_app
|
||||
inst = tmp_path / "inst"
|
||||
inst.mkdir()
|
||||
(inst / ".env").write_text(f"PORT={poort}\n")
|
||||
lastig = 'pa"ss\\wo/rd$`{}[]&|;<>#!\'x'
|
||||
ww = tmp_path / "ww"
|
||||
ww.write_text(lastig + "\n")
|
||||
|
||||
r = _draai("--create-admin", "--dir", str(inst), "--admin", "lastig",
|
||||
"--admin-password-file", str(ww), "--yes")
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert "aangemaakt" in r.stdout
|
||||
assert _inloggen(poort, "lastig", lastig) == 200
|
||||
|
||||
|
||||
@geen_curl
|
||||
def test_zelf_aangemaakt_wachtwoord_wordt_getoond_en_werkt(draaiende_app, tmp_path):
|
||||
"""Zonder bestand, omgevingsvariabele of terminal maakt het script er zelf
|
||||
één aan. Die moet dan wel op het scherm komen, want hij staat nergens."""
|
||||
poort = draaiende_app
|
||||
inst = tmp_path / "inst"
|
||||
inst.mkdir()
|
||||
(inst / ".env").write_text(f"PORT={poort}\n")
|
||||
|
||||
r = _draai("--create-admin", "--dir", str(inst), "--admin", "auto", "--yes")
|
||||
assert r.returncode == 0, r.stderr
|
||||
getoond = re.search(r"Wachtwoord: (\S+)", r.stdout)
|
||||
assert getoond, f"geen wachtwoord getoond in:\n{r.stdout}"
|
||||
assert _inloggen(poort, "auto", getoond.group(1)) == 200
|
||||
|
||||
|
||||
@geen_curl
|
||||
def test_tweede_account_wordt_netjes_geweigerd(draaiende_app, tmp_path):
|
||||
poort = draaiende_app
|
||||
inst = tmp_path / "inst"
|
||||
inst.mkdir()
|
||||
(inst / ".env").write_text(f"PORT={poort}\n")
|
||||
ww = tmp_path / "ww"
|
||||
ww.write_text("een-heel-lang-wachtwoord\n")
|
||||
_draai("--create-admin", "--dir", str(inst), "--admin", "eerste",
|
||||
"--admin-password-file", str(ww), "--yes")
|
||||
|
||||
r = _draai("--create-admin", "--dir", str(inst), "--admin", "tweede",
|
||||
"--admin-password-file", str(ww), "--yes")
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert "bestond al" in r.stdout
|
||||
assert _inloggen(poort, "tweede", "een-heel-lang-wachtwoord") != 200
|
||||
|
||||
|
||||
@geen_curl
|
||||
def test_onbereikbare_server_meldt_dat_zonder_te_ontsporen(tmp_path):
|
||||
inst = tmp_path / "inst"
|
||||
inst.mkdir()
|
||||
(inst / ".env").write_text(f"PORT={_vrije_poort()}\n")
|
||||
ww = tmp_path / "ww"
|
||||
ww.write_text("een-heel-lang-wachtwoord\n")
|
||||
|
||||
r = _draai("--create-admin", "--dir", str(inst), "--admin", "ramon",
|
||||
"--admin-password-file", str(ww), "--yes")
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert "niet bereikbaar" in r.stdout
|
||||
|
||||
|
||||
def test_verwijst_naar_het_juiste_git_domein():
|
||||
tekst = SCRIPT.read_text(encoding="utf-8")
|
||||
assert 'GIT_HOST="git.ramonbesselink.nl"' in tekst
|
||||
|
|
|
|||
Loading…
Reference in a new issue