server-up/tests/test_install_script.py
Ramon 2862f7844c
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 35s
Deploy server-up (prod) / deploy (push) Failing after 40s
v0.7.30-beta - installatiescript voor Linux
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
2026-07-27 19:30:45 +02:00

111 lines
4.1 KiB
Python

"""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")