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
111 lines
4.1 KiB
Python
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")
|