v0.7.30-beta - installatiescript voor Linux
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 35s
Deploy server-up (prod) / deploy (push) Failing after 40s

curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh

install.sh controleert het systeem, biedt aan Docker te installeren als dat
ontbreekt, kloont de repo (of pakt het tar-archief uit als git ontbreekt), maakt
.env aan, bouwt en start, wacht op /healthz en toont waar je terecht kunt met de
waarschuwing meteen een account aan te maken.

- Opties: --dir, --port, --bind, --branch, --token, --update, --uninstall,
  --yes, --dry-run.
- --update haalt nieuwe code op en herstart; .env, stacks en gegevens blijven.
- --uninstall stopt de container maar laat gegevens staan, en vertelt hoe je
  die alsnog opruimt.
- Gebruikt alleen sudo waar nodig: kun je zelf in de doelmap schrijven en
  docker aanroepen, dan blijft alles onder je eigen account.

Tijdens het testen gevonden en verholpen:
- `docker compose --project-directory` zoekt zonder -f het compose-bestand in de
  huidige map; bij `curl | sh` is dat je thuismap. Nu altijd -f erbij.
- De wachtlus gaf anderhalve minuut geen teken van leven; nu een punt per
  poging en 90 in plaats van 120 seconden.
- Bij --dry-run braken ontbrekende Docker-onderdelen de voorvertoning af; nu
  waarschuwingen zodat je de hele gang van zaken ziet.
- De versie-uitlezing uit /healthz tolereert nu ook json met spaties.

Documentatie: docs/installeren.md met alle opties, bijwerken, verwijderen en een
probleemoplostabel; README verwijst ernaar.

Tests: syntaxis onder sh/dash/bash, geen bashismen, --help noemt alle opties,
ongeldige invoer stopt, een proefdraai maakt niets aan, en de installatieregel
in README en docs is identiek.

958 tests groen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
This commit is contained in:
Ramon 2026-07-27 19:30:45 +02:00
parent 2ba15418ab
commit 2862f7844c
7 changed files with 693 additions and 5 deletions

View file

@ -1,3 +1,41 @@
# v0.7.30-beta — Installeren met één regel
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh
```
Het script controleert je systeem, biedt aan Docker te installeren als dat
ontbreekt, haalt de code op, maakt `.env` aan, bouwt het image, start de
container en wacht tot `/healthz` antwoordt. Daarna toont het waar je terecht
kunt — met de waarschuwing dat je meteen een beheerdersaccount moet aanmaken.
Opties: `--dir`, `--port`, `--bind`, `--branch`, `--token`, `--update`,
`--uninstall`, `--yes` en `--dry-run`.
**`--dry-run`** toont de hele gang van zaken zonder iets te wijzigen. Handig om
te zien wat er gaat gebeuren voordat je een script met root-rechten loslaat op
je server; `docs/installeren.md` zet die stap dan ook vooraan.
**Bijwerken** gaat met `sh /opt/server-up/install.sh --update`: nieuwe code
ophalen, opnieuw bouwen, herstarten. Je `.env`, stacks en gegevens blijven staan.
**Verwijderen** stopt de container maar laat je gegevens met rust, en vertelt
daarna hoe je die alsnog opruimt.
Het script gebruikt alleen `sudo` waar dat nodig is: kun je zelf al in de
doelmap schrijven én docker aanroepen, dan blijft alles onder je eigen account.
Nieuw: `README.md` verwijst er nu naar, plus `docs/installeren.md` met alle
opties en een probleemoplostabel.
Getest onder `sh`, `dash` en `bash`, met een testsuite die controleert dat er
geen bashismen in sluipen, dat een typefout in de opties netjes stopt, en dat
een proefdraai echt niets aanmaakt. Twee dingen die daarbij naar boven kwamen:
de wachtlus gaf anderhalve minuut lang geen enkel teken van leven (nu puntjes),
en `docker compose --project-directory` zoekt zonder `-f` het compose-bestand
alsnog in de huidige map — wat bij `curl | sh` je thuismap is.
---
# v0.7.20-beta — Instellingen wijzigen, koppelen achteraf, en een defect uit v0.7.00 # v0.7.20-beta — Instellingen wijzigen, koppelen achteraf, en een defect uit v0.7.00
## 🔴 Gedeeld netwerk botste met `network_mode` ## 🔴 Gedeeld netwerk botste met `network_mode`

View file

@ -33,13 +33,35 @@ IP-adres in je netwerk geven.
## Installeren ## Installeren
```bash ```bash
git clone https://github.com/bes-r/server-up.git curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh
cd server-up ```
cp .env.example .env # instellingen; zie hieronder
Dat controleert je systeem, installeert Docker als het ontbreekt, zet Server Up
in `/opt/server-up` en start het op `http://localhost:5000`.
Liever eerst lezen wat je uitvoert — verstandig, want het script gebruikt
root-rechten:
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh -o install.sh
less install.sh
sh install.sh --dry-run # toont wat het zou doen, zonder iets te wijzigen
sh install.sh
```
Handmatig kan ook:
```bash
git clone https://git.ramonbesselink.nl/bes-r/server-up.git /opt/server-up
cd /opt/server-up
cp .env.example .env
docker compose up -d --build docker compose up -d --build
``` ```
Open daarna `http://localhost:5000` en **maak meteen een beheerdersaccount aan.** Alle opties staan in [docs/installeren.md](docs/installeren.md):
`--dir`, `--port`, `--bind`, `--branch`, `--token`, `--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 Zolang dat niet gebeurd is, kan iedereen die de pagina bereikt het account
claimen — de container waarschuwt daar bij het opstarten ook voor. claimen — de container waarschuwt daar bij het opstarten ook voor.
@ -60,6 +82,7 @@ proxy met TLS voor staat.
| Document | Waarover | | Document | Waarover |
|---|---| |---|---|
| [docs/installeren.md](docs/installeren.md) | Het installatiescript, opties, bijwerken en verwijderen |
| [docs/beveiliging.md](docs/beveiliging.md) | Accounts, rollen, SSO, reverse proxy, wat wel en niet is afgedekt | | [docs/beveiliging.md](docs/beveiliging.md) | Accounts, rollen, SSO, reverse proxy, wat wel en niet is afgedekt |
| [docs/apps-maken.md](docs/apps-maken.md) | Eigen apps toevoegen: het volledige templateformaat | | [docs/apps-maken.md](docs/apps-maken.md) | Eigen apps toevoegen: het volledige templateformaat |
| [docs/netwerken.md](docs/netwerken.md) | Stacks een eigen IP geven met macvlan of ipvlan | | [docs/netwerken.md](docs/netwerken.md) | Stacks een eigen IP geven met macvlan of ipvlan |
@ -82,6 +105,16 @@ apps/mijn-app/
Het volledige formaat staat in [docs/apps-maken.md](docs/apps-maken.md). Het volledige formaat staat in [docs/apps-maken.md](docs/apps-maken.md).
## Bijwerken en verwijderen
```bash
sh /opt/server-up/install.sh --update # nieuwste versie ophalen en herstarten
sh /opt/server-up/install.sh --uninstall # stoppen; je gegevens blijven staan
```
Draait Server Up vanaf een registry-image, dan kan bijwerken ook met een knop in
de interface — zie [docs/updates.md](docs/updates.md).
## Ontwikkelen ## Ontwikkelen
```bash ```bash

View file

@ -1 +1 @@
0.7.20-beta 0.7.30-beta

View file

@ -2,6 +2,7 @@
| Document | Waarover, en wanneer je het nodig hebt | | Document | Waarover, en wanneer je het nodig hebt |
|---|---| |---|---|
| [installeren.md](installeren.md) | Installeren met één regel, alle opties van het script, bijwerken en verwijderen. |
| [beveiliging.md](beveiliging.md) | Accounts, rollen, reverse-proxy-SSO en TLS. **Lees dit voordat je Server Up van buiten je netwerk bereikbaar maakt** — toegang tot de interface staat gelijk aan root op de host. | | [beveiliging.md](beveiliging.md) | Accounts, rollen, reverse-proxy-SSO en TLS. **Lees dit voordat je Server Up van buiten je netwerk bereikbaar maakt** — toegang tot de interface staat gelijk aan root op de host. |
| [apps-maken.md](apps-maken.md) | Eigen apps toevoegen: het volledige `template.json`-formaat, de compose-syntaxis, apps aan elkaar koppelen en onderdelen optioneel maken. | | [apps-maken.md](apps-maken.md) | Eigen apps toevoegen: het volledige `template.json`-formaat, de compose-syntaxis, apps aan elkaar koppelen en onderdelen optioneel maken. |
| [netwerken.md](netwerken.md) | Stacks een eigen IP-adres geven in je LAN met macvlan of ipvlan. Inclusief de valkuil dat de host zijn eigen macvlan-containers niet bereikt. | | [netwerken.md](netwerken.md) | Stacks een eigen IP-adres geven in je LAN met macvlan of ipvlan. Inclusief de valkuil dat de host zijn eigen macvlan-containers niet bereikt. |

149
docs/installeren.md Normal file
View file

@ -0,0 +1,149 @@
# Installeren
## In één regel
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh
```
Dat installeert Server Up in `/opt/server-up`, bereikbaar op
`http://localhost:5000`.
**Liever eerst zien wat je uitvoert?** Verstandig — je haalt een script binnen
dat root-rechten gebruikt:
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh -o install.sh
less install.sh
sh install.sh
```
Wil je alleen weten wát het zou doen, zonder dat er iets verandert:
```bash
sh install.sh --dry-run
```
---
## Wat het script doet
1. Controleert of je op Linux zit en of `curl` en `tar` aanwezig zijn
2. Kijkt of Docker draait — zo niet, biedt het aan die te installeren via het
officiële script van docker.com (het vraagt eerst)
3. Kloont de repo naar `/opt/server-up` (of pakt het tar-archief uit als git
ontbreekt)
4. Maakt `.env` aan op basis van `.env.example`, met jouw poort en bind-adres
5. Bouwt het image en start de container
6. Wacht tot `/healthz` antwoordt en toont waar je terecht kunt
Het gebruikt alleen `sudo` waar dat nodig is. Kun je zelf al in de doelmap
schrijven én `docker` aanroepen, dan blijft alles onder je eigen account.
---
## Opties
```
--dir PAD Installatiemap (standaard: /opt/server-up)
--port POORT Poort voor de webinterface (standaard: 5000)
--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
--update Bijwerken naar de nieuwste versie
--uninstall Stoppen en verwijderen (gegevens blijven staan)
--yes Niets vragen
--dry-run Alleen tonen wat er zou gebeuren
```
### Voorbeelden
```bash
# Standaard, alleen bereikbaar vanaf de server zelf
sh install.sh
# Bereikbaar op je netwerk, andere poort
sh install.sh --bind 0.0.0.0 --port 8080
# Ergens anders neerzetten
sh install.sh --dir /srv/server-up
# De beta-branch, zonder vragen
sh install.sh --branch dev --yes
# Uit een repo die niet openbaar is
sh install.sh --token jouw-forgejo-token
```
---
## Meteen na het installeren
**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.
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:
```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
```
Zie [beveiliging.md](beveiliging.md) voor de reverse proxy, rollen en SSO.
---
## Bijwerken
```bash
sh /opt/server-up/install.sh --update
```
Haalt de nieuwste versie op, bouwt opnieuw en herstart. Je `.env` blijft staan,
net als je stacks en gegevens.
Draait Server Up vanaf een registry-image, dan kun je ook bijwerken vanuit de
interface zelf — zie [updates.md](updates.md).
---
## Verwijderen
```bash
sh /opt/server-up/install.sh --uninstall
```
Stopt en verwijdert de container. **Je gegevens blijven staan**: het
docker-volume `su-data` (instellingen, accounts, auditlog) en je stackmappen
onder `/opt/serverup`. Het script vertelt daarna hoe je die alsnog opruimt.
---
## Handmatig, zonder script
```bash
git clone https://git.ramonbesselink.nl/bes-r/server-up.git /opt/server-up
cd /opt/server-up
cp .env.example .env
$EDITOR .env
docker compose up -d --build
```
---
## Als er iets misgaat
| Melding | Wat er aan de hand is |
|---|---|
| `Geen root en geen sudo` | Draai als root, of kies met `--dir` een map waar je zelf in mag schrijven |
| `Docker is geïnstalleerd maar de daemon reageert niet` | `systemctl start docker` |
| `De docker-compose-plugin ontbreekt` | Installeer `docker-compose-plugin` via je pakketbeheerder |
| `Ophalen mislukt. Is de repo openbaar?` | Gebruik `--token` met een Forgejo-token dat de repo mag lezen |
| Reageert niet binnen anderhalve minuut | `docker compose -f /opt/server-up/docker-compose.yml logs --tail=50` |
Laadt de interface zonder opmaak, dan is het downloaden van de front-end-
bestanden tijdens de build misgegaan. Server Up meldt dat bij het opstarten in
zijn eigen log; opnieuw bouwen lost het meestal op.

356
install.sh Executable file
View file

@ -0,0 +1,356 @@
#!/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.0"
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"
# ── 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
--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
Voorbeelden:
sh install.sh
sh install.sh --bind 0.0.0.0 --port 8080
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}"; shift 2 ;;
--bind) BIND="${2:?--bind vereist een adres}"; shift 2 ;;
--branch) BRANCH="${2:?--branch vereist een naam}"; shift 2 ;;
--token) TOKEN="${2:?--token vereist een waarde}"; shift 2 ;;
--update) ACTIE="update"; shift ;;
--uninstall) ACTIE="uninstall"; 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
# ── 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 "'$hulp' ontbreekt. Installeer het en probeer opnieuw."
done
goed "$(uname -s) $(uname -m)"
}
controleer_docker() {
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 ─────────────────────────────────────────────────────────────
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.
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
}
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"
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 [ "$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 ───────────────────────────────────────────────────────────
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
start
wacht_op_gereed || true
printf '\n'
goed "Bijgewerkt."
;;
install)
controleer_systeem
controleer_docker
haal_broncode
schrijf_env
start
wacht_op_gereed || true
afsluiting
;;
esac

View file

@ -0,0 +1,111 @@
"""Het installatiescript: syntaxis, opties en de dingen die stuk mogen gaan.
Een installer die je met `curl | sh` draait moet in elk geval syntactisch
kloppen onder de shells die distributies als /bin/sh gebruiken, en niet
stilzwijgend het verkeerde doen bij een typefout in de opties.
"""
import re
import shutil
import subprocess
from pathlib import Path
import pytest
WORTEL = Path(__file__).resolve().parent.parent
SCRIPT = WORTEL / "install.sh"
def _draai(*args, **kw):
return subprocess.run(["sh", str(SCRIPT), *args], capture_output=True,
text=True, timeout=60, **kw)
def test_script_bestaat_en_is_uitvoerbaar():
assert SCRIPT.is_file()
assert SCRIPT.stat().st_mode & 0o111, "niet uitvoerbaar"
@pytest.mark.parametrize("shell", ["sh", "dash", "bash"])
def test_syntaxis_klopt_onder(shell):
"""Debian en Ubuntu gebruiken dash als /bin/sh; bashismen breken daar."""
if not shutil.which(shell):
pytest.skip(f"{shell} niet aanwezig")
r = subprocess.run([shell, "-n", str(SCRIPT)], capture_output=True, text=True)
assert r.returncode == 0, r.stderr
def test_geen_bashismen():
tekst = SCRIPT.read_text(encoding="utf-8")
for patroon, uitleg in [
(r"\[\[", "[[ ]] is bash-only"),
(r"^\s*declare ", "declare is bash-only"),
(r"\$\{[A-Za-z_]+\[", "arrays zijn bash-only"),
(r"==", "== in test is bash-only"),
]:
treffers = [l for l in tekst.splitlines()
if re.search(patroon, l) and not l.strip().startswith("#")]
assert not treffers, f"{uitleg}: {treffers[:2]}"
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"):
assert optie in r.stdout, f"{optie} niet gedocumenteerd in --help"
def test_onbekende_optie_stopt():
r = _draai("--bestaatniet")
assert r.returncode != 0
assert "Onbekende optie" in r.stderr
@pytest.mark.parametrize("poort", ["abc", "", "80a", "-1"])
def test_ongeldige_poort_stopt(poort):
r = _draai("--port", poort)
assert r.returncode != 0
# Een lege waarde valt al af bij het uitlezen van de optie, de rest bij de
# controle daarna; beide meldingen gaan over de poort.
assert "poort" in r.stderr.lower() or "port" in r.stderr.lower()
def test_update_zonder_installatie_zegt_dat(tmp_path):
r = _draai("--update", "--dir", str(tmp_path / "bestaatniet"))
assert r.returncode != 0
assert "Geen installatie gevonden" in r.stderr
def test_dry_run_wijzigt_niets(tmp_path):
doel = tmp_path / "server-up"
r = _draai("--dry-run", "--dir", str(doel), "--yes")
assert not doel.exists(), "proefdraai heeft toch iets aangemaakt"
assert "[droog]" in r.stdout
def test_dry_run_toont_de_hele_gang_van_zaken(tmp_path):
"""Ook op een machine zonder Docker moet je de voorvertoning kunnen zien."""
r = _draai("--dry-run", "--dir", str(tmp_path / "su"), "--yes")
for kop in ("Systeem controleren", "Broncode ophalen", "Instellingen",
"Bouwen en starten"):
assert kop in r.stdout, f"stap ontbreekt: {kop}"
def test_verwijst_naar_het_juiste_git_domein():
tekst = SCRIPT.read_text(encoding="utf-8")
assert 'GIT_HOST="git.ramonbesselink.nl"' in tekst
def test_compose_wordt_met_expliciet_bestand_aangeroepen():
"""`--project-directory` alleen laat compose het bestand nog in de huidige
map zoeken; met `curl | sh` is dat je thuismap."""
for regel in SCRIPT.read_text(encoding="utf-8").splitlines():
if "docker compose" in regel and ("up -d" in regel or "down " in regel):
assert "-f " in regel, f"geen expliciet compose-bestand: {regel.strip()}"
def test_readme_en_docs_noemen_dezelfde_installatieregel():
regel = ("curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/"
"raw/branch/main/install.sh")
assert regel in (WORTEL / "README.md").read_text(encoding="utf-8")
assert regel in (WORTEL / "docs" / "installeren.md").read_text(encoding="utf-8")