v0.10.20-beta - self-update, tags, tokens, --base-dir en --doctor
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 17m46s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 17m46s
- bijwerken vanuit de interface kon nooit werken: selfupdate schreef SU_TAG naar <werkmap>/.env, maar die map is nergens in de container gemount. De helper-container mount hem wel en zet de tag nu - een vastgepinde versie kon niet bijgewerkt worden: `git reset --hard origin/<tag>` bestaat niet. Resetten gaat nu naar FETCH_HEAD, wat voor takken en tags allebei klopt - de token stond in .git/config en in ps; hij gaat nu via een bestand met modus 0600 dat na afloop weer weg is (git via een credential-helper, curl via --config). Schema en host komen uit bron_url, want git zoekt op exact die combinatie - nieuwe optie --base-dir PAD: de hoofdmap bij de installatie zetten in plaats van achteraf in .env, waarna je de data alsnog moet verhuizen. Relatieve paden en systeemmappen worden geweigerd - nieuwe actie --doctor: een installatie doorlichten zonder iets te wijzigen — container, bereikbaarheid op het ingestelde adres, het account versus SU_UID in .env, of BASE_DIR echt gekoppeld is, de rechten van .env en de vrije ruimte. Exitcode 1 bij fouten - de CI draait --doctor na elke deploy tegen de echte installatie Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
This commit is contained in:
parent
15213708a4
commit
f8274ebeff
9 changed files with 656 additions and 51 deletions
|
|
@ -236,6 +236,27 @@ jobs:
|
||||||
cd "$DEPLOY_DIR" && docker compose logs --tail=200
|
cd "$DEPLOY_DIR" && docker compose logs --tail=200
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
# `--doctor` is ruim honderd regels shell die alleen tegen een échte
|
||||||
|
# installatie draaien. De pytest-suite komt niet verder dan "er is geen
|
||||||
|
# Docker"; hier wel.
|
||||||
|
- name: install.sh --doctor doorlichten
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
uit="$(sh "$DEPLOY_DIR/install.sh" --doctor --dir "$DEPLOY_DIR" 2>&1 || true)"
|
||||||
|
printf '%s\n' "$uit"
|
||||||
|
# De exitcode toetsen we bewust niet: deze runner zit zelf in een
|
||||||
|
# container, dus de bereikbaarheidscontrole kan niet slagen — die
|
||||||
|
# kijkt naar 127.0.0.1 op de host. Of de deploy gezond is, is al
|
||||||
|
# vastgesteld door de healthcheck-stap hierboven. Wat hier wél moet
|
||||||
|
# kloppen is dat het doorlichten van begin tot eind draait.
|
||||||
|
for kop in "Installatie" "Docker" "Container" "Account" "Mappen" \
|
||||||
|
"Bijwerken vanuit de interface"; do
|
||||||
|
printf '%s\n' "$uit" | grep -q "^$kop$" || {
|
||||||
|
echo "FOUT: --doctor kwam niet tot de sectie '$kop'"; exit 1; }
|
||||||
|
done
|
||||||
|
echo "--doctor liep van begin tot eind"
|
||||||
|
|
||||||
- name: Oude images opruimen (behoud laatste 5)
|
- name: Oude images opruimen (behoud laatste 5)
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -1,3 +1,41 @@
|
||||||
|
# v0.10.20-beta — Bijwerken vanuit de interface, en een script dat nakijkt
|
||||||
|
|
||||||
|
Het tweede deel van de controle op het installatiepad.
|
||||||
|
|
||||||
|
**Bijwerken vanuit de interface kon nooit werken.** `selfupdate` schreef
|
||||||
|
`SU_TAG` naar `<werkmap>/.env` — maar die map is nergens in de container
|
||||||
|
gemount. Er zijn maar drie mounts: de docker-socket, het `su-data`-volume en
|
||||||
|
`BASE_DIR`. De installatiemap zit daar niet bij, dus schrijven mislukte. Dat
|
||||||
|
viel niet op omdat een zelfgebouwd image de knop toch al uitschakelt; zette je
|
||||||
|
`SU_IMAGE` naar een registry — de manier die `docs/updates.md` noemt om hem aan
|
||||||
|
te zetten — dan faalde hij altijd. De helper-container mount die map wél en
|
||||||
|
draait als root, dus die zet de tag nu.
|
||||||
|
|
||||||
|
**Een vastgepinde versie kon nooit bijgewerkt worden.** `--branch v0.9.00-beta`
|
||||||
|
installeerde prima, maar bijwerken deed `git reset --hard origin/v0.9.00-beta`
|
||||||
|
en tags krijgen geen `origin/`-ref: "fatal: ambiguous argument". Het resetten
|
||||||
|
gaat nu naar `FETCH_HEAD`, wat voor takken en tags allebei klopt. De test liet
|
||||||
|
zien dat het na een tag-installatie óók voor takken stuk was.
|
||||||
|
|
||||||
|
**De token stond in `.git/config` en in `ps`.** In de URL schrijft git hem
|
||||||
|
verbatim in de config, waar hij blijft staan; als argument staat hij in
|
||||||
|
`/proc/<pid>/cmdline` en leest elke gebruiker op de server hem met `ps`. Dat is
|
||||||
|
precies waarom dit script `--admin-password` weigert. Hij gaat nu via een
|
||||||
|
bestand met modus 0600 dat na afloop weer weg is — git leest het via een
|
||||||
|
credential-helper, curl via `--config`; alleen het pad staat op de opdrachtregel.
|
||||||
|
|
||||||
|
Twee nieuwe opties:
|
||||||
|
|
||||||
|
- **`--base-dir PAD`** zet de hoofdmap voor stacks, appdata en backups bij de
|
||||||
|
installatie, in plaats van achteraf in `.env` — waarna je de data alsnog moet
|
||||||
|
verhuizen. Relatieve paden en systeemmappen worden geweigerd.
|
||||||
|
- **`--doctor`** licht een bestaande installatie door zonder iets te wijzigen:
|
||||||
|
container, bereikbaarheid *op het ingestelde adres*, onder welk account hij
|
||||||
|
draait versus wat er in `.env` staat, of `BASE_DIR` echt gekoppeld is (bestaan
|
||||||
|
is niet genoeg), de rechten van `.env`, en hoeveel ruimte er over is. Exitcode
|
||||||
|
1 bij fouten. De CI draait hem na elke deploy tegen de echte installatie —
|
||||||
|
ruim honderd regels shell die je nergens anders getest krijgt.
|
||||||
|
|
||||||
# v0.10.10-beta — Het installatiescript loog op drie plekken
|
# v0.10.10-beta — Het installatiescript loog op drie plekken
|
||||||
|
|
||||||
Een controle van het hele installatiepad leverde drie fouten op die alledrie op
|
Een controle van het hele installatiepad leverde drie fouten op die alledrie op
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.10.10-beta
|
0.10.20-beta
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ schrijven én `docker` aanroepen, dan blijft alles onder je eigen account.
|
||||||
--bind ADRES Waarop de poort luistert (standaard: 127.0.0.1)
|
--bind ADRES Waarop de poort luistert (standaard: 127.0.0.1)
|
||||||
--branch NAAM Branch om te installeren (standaard: main)
|
--branch NAAM Branch om te installeren (standaard: main)
|
||||||
--token TOKEN Toegangstoken, als je repo niet openbaar is
|
--token TOKEN Toegangstoken, als je repo niet openbaar is
|
||||||
|
--base-dir PAD Hoofdmap voor stacks, appdata en backups (standaard:
|
||||||
|
/opt/serverup)
|
||||||
--user NAAM Account waaronder Server Up draait ('serverup' wordt zo nodig
|
--user NAAM Account waaronder Server Up draait ('serverup' wordt zo nodig
|
||||||
aangemaakt; 'root' is de oude situatie)
|
aangemaakt; 'root' is de oude situatie)
|
||||||
--admin NAAM Maak meteen een beheerdersaccount met deze naam
|
--admin NAAM Maak meteen een beheerdersaccount met deze naam
|
||||||
|
|
@ -58,6 +60,7 @@ schrijven én `docker` aanroepen, dan blijft alles onder je eigen account.
|
||||||
--create-admin Alleen het account aanmaken, bij een draaiende installatie
|
--create-admin Alleen het account aanmaken, bij een draaiende installatie
|
||||||
--update Bijwerken naar de nieuwste versie
|
--update Bijwerken naar de nieuwste versie
|
||||||
--uninstall Stoppen en verwijderen (gegevens blijven staan)
|
--uninstall Stoppen en verwijderen (gegevens blijven staan)
|
||||||
|
--doctor Een bestaande installatie doorlichten
|
||||||
--yes Niets vragen
|
--yes Niets vragen
|
||||||
--dry-run Alleen tonen wat er zou gebeuren
|
--dry-run Alleen tonen wat er zou gebeuren
|
||||||
```
|
```
|
||||||
|
|
@ -133,7 +136,14 @@ komen is een aparte keuze: dat is `BASE_DIR` in `.env`, standaard
|
||||||
/opt/serverup/backups de backups
|
/opt/serverup/backups de backups
|
||||||
```
|
```
|
||||||
|
|
||||||
Wil je alles onder `/srv`, zet dat dan vóór de eerste installatie in `.env`:
|
Wil je alles onder `/srv`, geef dat dan bij de installatie mee:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sh install.sh --base-dir /srv/serverup
|
||||||
|
```
|
||||||
|
|
||||||
|
Achteraf kan het ook, maar dan verhuist je data niet mee — daarvoor is
|
||||||
|
Instellingen → Paden (zie hieronder):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo 'BASE_DIR=/srv/serverup' >> .env
|
echo 'BASE_DIR=/srv/serverup' >> .env
|
||||||
|
|
@ -165,6 +175,32 @@ zien. Ligt de nieuwe map buiten de huidige `BASE_DIR`, zet die dan eerst in
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Doorlichten
|
||||||
|
|
||||||
|
Werkt er iets niet, of wil je weten of alles klopt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sh /opt/server-up/install.sh --doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
Hij kijkt alleen; er wordt niets gewijzigd. Wat hij nagaat:
|
||||||
|
|
||||||
|
- of er een installatie staat, en of `.env` afgeschermd is (daar staan tokens in)
|
||||||
|
- of de Docker-daemon reageert en de container draait en gezond is
|
||||||
|
- of de webinterface antwoordt **op het adres waarop compose publiceert**, niet
|
||||||
|
op een aanname
|
||||||
|
- onder welk account hij draait, en of dat overeenkomt met `SU_UID` in `.env` —
|
||||||
|
staat er iets anders, dan is de container nog niet hercreëerd
|
||||||
|
- of `BASE_DIR` echt in de container gekoppeld is. Bestaan is niet genoeg: een
|
||||||
|
niet-gekoppelde map bestaat wél binnen de container, maar wat daar geschreven
|
||||||
|
wordt komt nooit op de host terecht
|
||||||
|
- hoeveel er in `stacks`, `appdata` en `backups` staat, van wie die mappen zijn,
|
||||||
|
en hoeveel ruimte er over is
|
||||||
|
|
||||||
|
De exitcode is 1 als er fouten zijn, zodat je hem in een controle kunt hangen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Het beheerdersaccount
|
## Het beheerdersaccount
|
||||||
|
|
||||||
Server Up beheert de Docker-daemon, en wie containers kan starten kan
|
Server Up beheert de Docker-daemon, en wie containers kan starten kan
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,11 @@ PORT=5000
|
||||||
registry en werkt de updateknop. Staat het er niet, dan bouwt de server lokaal
|
registry en werkt de updateknop. Staat het er niet, dan bouwt de server lokaal
|
||||||
zoals voorheen en meldt de interface netjes waarom bijwerken niet kan.
|
zoals voorheen en meldt de interface netjes waarom bijwerken niet kan.
|
||||||
|
|
||||||
|
> Server Up schrijft `SU_TAG` niet zelf in je `.env`, en dat is met opzet: de
|
||||||
|
> installatiemap is nergens in de container gemount. De helper-container die de
|
||||||
|
> hercreatie doet, mount hem wél en zet de tag daar. Tot v0.10.20 probeerde
|
||||||
|
> Server Up het zelf, en faalde dat dus altijd.
|
||||||
|
|
||||||
Eerste keer overstappen van lokaal bouwen naar de registry:
|
Eerste keer overstappen van lokaal bouwen naar de registry:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
261
install.sh
261
install.sh
|
|
@ -38,6 +38,7 @@ SU_UID=""
|
||||||
SU_GID=""
|
SU_GID=""
|
||||||
SU_NAAM=""
|
SU_NAAM=""
|
||||||
BASE_DIR="${BASE_DIR:-}"
|
BASE_DIR="${BASE_DIR:-}"
|
||||||
|
BASE_DIR_EXPLICIET=0
|
||||||
# Heeft de server na het starten daadwerkelijk geantwoord? Bepaalt het slot en
|
# Heeft de server na het starten daadwerkelijk geantwoord? Bepaalt het slot en
|
||||||
# de exitcode; bij --dry-run is er niets om op te wachten.
|
# de exitcode; bij --dry-run is er niets om op te wachten.
|
||||||
GEREED=1
|
GEREED=1
|
||||||
|
|
@ -114,6 +115,9 @@ Gebruik: install.sh [opties]
|
||||||
maar zet er dan wel een reverse proxy met TLS voor.
|
maar zet er dan wel een reverse proxy met TLS voor.
|
||||||
--branch NAAM Branch om te installeren (standaard: ${BRANCH})
|
--branch NAAM Branch om te installeren (standaard: ${BRANCH})
|
||||||
--token TOKEN Toegangstoken, als je git-repo niet openbaar is
|
--token TOKEN Toegangstoken, als je git-repo niet openbaar is
|
||||||
|
--base-dir PAD Hoofdmap voor stacks, appdata en backups
|
||||||
|
(standaard: /opt/serverup). Later wijzigen kan via
|
||||||
|
Instellingen → Paden, die de data meeverhuist.
|
||||||
--user NAAM Account waaronder Server Up draait ('serverup' wordt zo
|
--user NAAM Account waaronder Server Up draait ('serverup' wordt zo
|
||||||
nodig aangemaakt; 'root' is de oude situatie). Zonder deze
|
nodig aangemaakt; 'root' is de oude situatie). Zonder deze
|
||||||
optie wordt ernaar gevraagd.
|
optie wordt ernaar gevraagd.
|
||||||
|
|
@ -126,6 +130,7 @@ Gebruik: install.sh [opties]
|
||||||
--create-admin Alleen het beheerdersaccount aanmaken, bij een installatie
|
--create-admin Alleen het beheerdersaccount aanmaken, bij een installatie
|
||||||
die al draait. Werkt zolang er nog geen account bestaat.
|
die al draait. Werkt zolang er nog geen account bestaat.
|
||||||
--update Bijwerken naar de nieuwste versie
|
--update Bijwerken naar de nieuwste versie
|
||||||
|
--doctor Een bestaande installatie doorlichten
|
||||||
--uninstall Stoppen en verwijderen (je gegevens blijven staan)
|
--uninstall Stoppen en verwijderen (je gegevens blijven staan)
|
||||||
--yes Niets vragen, overal ja op antwoorden
|
--yes Niets vragen, overal ja op antwoorden
|
||||||
--dry-run Alleen tonen wat er zou gebeuren
|
--dry-run Alleen tonen wat er zou gebeuren
|
||||||
|
|
@ -153,12 +158,15 @@ while [ $# -gt 0 ]; do
|
||||||
--token) TOKEN="${2:?--token vereist een waarde}"; shift 2 ;;
|
--token) TOKEN="${2:?--token vereist een waarde}"; shift 2 ;;
|
||||||
--user) GEBRUIKER="${2:?--user vereist een accountnaam}"
|
--user) GEBRUIKER="${2:?--user vereist een accountnaam}"
|
||||||
GEBRUIKER_EXPLICIET=1; shift 2 ;;
|
GEBRUIKER_EXPLICIET=1; shift 2 ;;
|
||||||
|
--base-dir) BASE_DIR="${2:?--base-dir vereist een pad}"
|
||||||
|
BASE_DIR_EXPLICIET=1; shift 2 ;;
|
||||||
--admin) BEHEERDER="${2:?--admin vereist een gebruikersnaam}"; shift 2 ;;
|
--admin) BEHEERDER="${2:?--admin vereist een gebruikersnaam}"; shift 2 ;;
|
||||||
--admin-password-file)
|
--admin-password-file)
|
||||||
BEHEERDER_WW_BESTAND="${2:?--admin-password-file vereist een pad}"; shift 2 ;;
|
BEHEERDER_WW_BESTAND="${2:?--admin-password-file vereist een pad}"; shift 2 ;;
|
||||||
--admin-password)
|
--admin-password)
|
||||||
fout "Gebruik --admin-password-file of SU_ADMIN_PASSWORD; een wachtwoord op de opdrachtregel is voor iedereen zichtbaar met 'ps'." ;;
|
fout "Gebruik --admin-password-file of SU_ADMIN_PASSWORD; een wachtwoord op de opdrachtregel is voor iedereen zichtbaar met 'ps'." ;;
|
||||||
--update) ACTIE="update"; shift ;;
|
--update) ACTIE="update"; shift ;;
|
||||||
|
--doctor) ACTIE="doctor"; shift ;;
|
||||||
--uninstall) ACTIE="uninstall"; shift ;;
|
--uninstall) ACTIE="uninstall"; shift ;;
|
||||||
--create-admin) ACTIE="create-admin"; shift ;;
|
--create-admin) ACTIE="create-admin"; shift ;;
|
||||||
--yes|-y) JA=1; shift ;;
|
--yes|-y) JA=1; shift ;;
|
||||||
|
|
@ -182,6 +190,21 @@ geldig_bind() {
|
||||||
}
|
}
|
||||||
geldig_bind "$BIND" || fout "Ongeldig bind-adres: $BIND (verwacht een IP-adres, bijvoorbeeld 127.0.0.1 of 0.0.0.0)"
|
geldig_bind "$BIND" || fout "Ongeldig bind-adres: $BIND (verwacht een IP-adres, bijvoorbeeld 127.0.0.1 of 0.0.0.0)"
|
||||||
|
|
||||||
|
# BASE_DIR wordt in de container op zichzelf gemount, dus een relatief pad zou
|
||||||
|
# binnen de container ergens anders uitkomen dan op de host. En een handvol
|
||||||
|
# systeemmappen wil je hier niet per ongeluk vullen met appdata.
|
||||||
|
if [ -n "$BASE_DIR" ]; then
|
||||||
|
case "$BASE_DIR" in
|
||||||
|
/*) ;;
|
||||||
|
*) fout "--base-dir vereist een volledig pad, beginnend met / (kreeg: $BASE_DIR)" ;;
|
||||||
|
esac
|
||||||
|
BASE_DIR="${BASE_DIR%/}"
|
||||||
|
case "$BASE_DIR" in
|
||||||
|
''|/|/etc|/bin|/sbin|/usr|/var|/tmp|/root|/boot|/dev|/proc|/sys)
|
||||||
|
fout "--base-dir mag niet $BASE_DIR zijn; kies een eigen map, bijvoorbeeld /srv/serverup." ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# Waar bereiken wíj de server na het starten? Compose publiceert op $BIND, dus
|
# Waar bereiken wíj de server na het starten? Compose publiceert op $BIND, dus
|
||||||
# met een specifiek adres luistert er niets op 127.0.0.1. Daar toch naartoe
|
# met een specifiek adres luistert er niets op 127.0.0.1. Daar toch naartoe
|
||||||
# vragen leverde "reageerde niet binnen anderhalve minuut" en een overgeslagen
|
# vragen leverde "reageerde niet binnen anderhalve minuut" en een overgeslagen
|
||||||
|
|
@ -270,21 +293,66 @@ controleer_docker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Broncode ophalen ─────────────────────────────────────────────────────────
|
# ── Broncode ophalen ─────────────────────────────────────────────────────────
|
||||||
bron_url() {
|
# De token gaat nooit in de URL en nooit in een argument. In de URL schrijft git
|
||||||
if [ -n "$TOKEN" ]; then
|
# hem verbatim in .git/config, waar hij blijft staan; als argument staat hij in
|
||||||
printf 'https://%s@%s/%s.git' "$TOKEN" "$GIT_HOST" "$GIT_PAD"
|
# /proc/<pid>/cmdline en is hij voor elke gebruiker op de server te lezen met
|
||||||
|
# 'ps'. Dat is precies waarom dit script --admin-password weigert.
|
||||||
|
#
|
||||||
|
# In plaats daarvan: een bestand met modus 0600 dat na afloop weer weg is. Git
|
||||||
|
# leest het via een credential-helper, curl via --config; in beide gevallen
|
||||||
|
# staat alleen het pád op de opdrachtregel.
|
||||||
|
bron_url() { printf 'https://%s/%s.git' "$GIT_HOST" "$GIT_PAD"; }
|
||||||
|
|
||||||
|
TOKEN_BESTAND=""
|
||||||
|
CURL_BESTAND=""
|
||||||
|
|
||||||
|
opruimen() {
|
||||||
|
[ -n "$TOKEN_BESTAND" ] && rm -f "$TOKEN_BESTAND"
|
||||||
|
[ -n "$CURL_BESTAND" ] && rm -f "$CURL_BESTAND"
|
||||||
|
TOKEN_BESTAND=""; CURL_BESTAND=""
|
||||||
|
}
|
||||||
|
trap opruimen EXIT HUP INT TERM
|
||||||
|
|
||||||
|
zet_token_klaar() {
|
||||||
|
[ -n "$TOKEN" ] || return 0
|
||||||
|
[ -n "$TOKEN_BESTAND" ] && return 0
|
||||||
|
TOKEN_BESTAND="$(mktemp "${TMPDIR:-/tmp}/su-token.XXXXXX")"
|
||||||
|
CURL_BESTAND="$(mktemp "${TMPDIR:-/tmp}/su-curl.XXXXXX")"
|
||||||
|
chmod 600 "$TOKEN_BESTAND" "$CURL_BESTAND"
|
||||||
|
# Schema en host uit bron_url halen in plaats van https hard in te vullen:
|
||||||
|
# git zoekt de opgeslagen inloggegevens op exact schema + host, dus een
|
||||||
|
# verschil daarin betekent stilzwijgend geen inloggegevens.
|
||||||
|
_url="$(bron_url)"
|
||||||
|
_schema="${_url%%://*}"
|
||||||
|
_rest="${_url#*://}"
|
||||||
|
_host="${_rest%%/*}"
|
||||||
|
# Formaat van `git credential store`: één URL per regel met de gegevens erin.
|
||||||
|
printf '%s://%s:x-oauth-basic@%s\n' "$_schema" "$TOKEN" "$_host" > "$TOKEN_BESTAND"
|
||||||
|
printf 'header = "Authorization: token %s"\n' "$TOKEN" > "$CURL_BESTAND"
|
||||||
|
}
|
||||||
|
|
||||||
|
git_doe() {
|
||||||
|
# git_doe <argumenten> — met de token uit het bestand als die er is.
|
||||||
|
if [ -n "$TOKEN_BESTAND" ]; then
|
||||||
|
doe $SUDO git -c "credential.helper=store --file=$TOKEN_BESTAND" "$@"
|
||||||
else
|
else
|
||||||
printf 'https://%s/%s.git' "$GIT_HOST" "$GIT_PAD"
|
doe $SUDO git "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
haal_broncode() {
|
haal_broncode() {
|
||||||
stap "Broncode ophalen"
|
stap "Broncode ophalen"
|
||||||
|
zet_token_klaar
|
||||||
if [ -d "$DOEL/.git" ]; then
|
if [ -d "$DOEL/.git" ]; then
|
||||||
log "Bestaande installatie bijwerken in $DOEL"
|
log "Bestaande installatie bijwerken in $DOEL"
|
||||||
doe $SUDO git -C "$DOEL" fetch --depth 1 origin "$BRANCH"
|
git_doe -C "$DOEL" fetch --depth 1 origin "$BRANCH"
|
||||||
doe $SUDO git -C "$DOEL" reset --hard "origin/$BRANCH"
|
# FETCH_HEAD en niet origin/$BRANCH: een tag krijgt geen origin/-ref,
|
||||||
goed "Bijgewerkt naar de nieuwste $BRANCH"
|
# dus `--branch v0.9.00-beta` installeerde prima maar kon daarna nooit
|
||||||
|
# meer bijgewerkt worden ("fatal: ambiguous argument"). FETCH_HEAD wijst
|
||||||
|
# na een fetch altijd naar wat er net opgehaald is, tak of tag.
|
||||||
|
git_doe -C "$DOEL" reset --hard FETCH_HEAD
|
||||||
|
goed "Bijgewerkt naar $BRANCH"
|
||||||
|
opruimen
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -292,8 +360,9 @@ haal_broncode() {
|
||||||
if heeft git; then
|
if heeft git; then
|
||||||
# Zonder .git kunnen we later niet netjes bijwerken, dus git heeft de
|
# Zonder .git kunnen we later niet netjes bijwerken, dus git heeft de
|
||||||
# voorkeur boven een los archief.
|
# voorkeur boven een los archief.
|
||||||
if doe $SUDO git clone --depth 1 --branch "$BRANCH" "$(bron_url)" "$DOEL"; then
|
if git_doe clone --depth 1 --branch "$BRANCH" "$(bron_url)" "$DOEL"; then
|
||||||
goed "Gekloond naar $DOEL"
|
goed "Gekloond naar $DOEL"
|
||||||
|
opruimen
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
waarschuw "Klonen mislukte; ik probeer het archief."
|
waarschuw "Klonen mislukte; ik probeer het archief."
|
||||||
|
|
@ -303,12 +372,13 @@ haal_broncode() {
|
||||||
log "Downloaden: $archief"
|
log "Downloaden: $archief"
|
||||||
if [ "$DROOG" -eq 0 ]; then
|
if [ "$DROOG" -eq 0 ]; then
|
||||||
tijdelijk="$(mktemp -d)"
|
tijdelijk="$(mktemp -d)"
|
||||||
curl -fsSL ${TOKEN:+-H "Authorization: token $TOKEN"} "$archief" \
|
curl -fsSL ${CURL_BESTAND:+--config "$CURL_BESTAND"} "$archief" \
|
||||||
| tar xz -C "$tijdelijk" --strip-components=1 \
|
| tar xz -C "$tijdelijk" --strip-components=1 \
|
||||||
|| fout "Ophalen mislukt. Is de repo openbaar? Zo niet, gebruik --token."
|
|| fout "Ophalen mislukt. Is de repo openbaar? Zo niet, gebruik --token."
|
||||||
$SUDO cp -r "$tijdelijk/." "$DOEL/"
|
$SUDO cp -r "$tijdelijk/." "$DOEL/"
|
||||||
rm -rf "$tijdelijk"
|
rm -rf "$tijdelijk"
|
||||||
fi
|
fi
|
||||||
|
opruimen
|
||||||
goed "Uitgepakt naar $DOEL"
|
goed "Uitgepakt naar $DOEL"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -540,7 +610,7 @@ lees_env_terug() {
|
||||||
case "$_gid" in ''|*[!0-9]*) SU_GID="$_uid" ;; *) SU_GID="$_gid" ;; esac ;;
|
case "$_gid" in ''|*[!0-9]*) SU_GID="$_uid" ;; *) SU_GID="$_gid" ;; esac ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
if [ -z "$BASE_DIR" ]; then
|
if [ -z "$BASE_DIR" ] && [ "$BASE_DIR_EXPLICIET" -eq 0 ]; then
|
||||||
_basis="$(printf '%s\n' "$_inhoud" | sed -n 's/^BASE_DIR=//p' | tail -1)"
|
_basis="$(printf '%s\n' "$_inhoud" | sed -n 's/^BASE_DIR=//p' | tail -1)"
|
||||||
case "$_basis" in /*) BASE_DIR="$_basis" ;; esac
|
case "$_basis" in /*) BASE_DIR="$_basis" ;; esac
|
||||||
fi
|
fi
|
||||||
|
|
@ -592,6 +662,18 @@ schrijf_env() {
|
||||||
fi
|
fi
|
||||||
gewijzigd=1
|
gewijzigd=1
|
||||||
fi
|
fi
|
||||||
|
# Alleen als je er expliciet om vroeg: anders zou een --update de
|
||||||
|
# hoofdmap terugzetten terwijl je data ergens anders staat.
|
||||||
|
if [ "$BASE_DIR_EXPLICIET" -eq 1 ] && [ -n "$BASE_DIR" ]; then
|
||||||
|
if [ "$DROOG" -eq 1 ]; then
|
||||||
|
printf ' [droog] BASE_DIR=%s in bestaande .env zetten\n' "$BASE_DIR"
|
||||||
|
else
|
||||||
|
zet_env_regel "$env_bestand" BASE_DIR "$BASE_DIR"
|
||||||
|
fi
|
||||||
|
waarschuw "BASE_DIR gewijzigd; bestaande data verhuist hier niet mee."
|
||||||
|
log "Verplaatsen doe je vanuit Instellingen → Paden."
|
||||||
|
gewijzigd=1
|
||||||
|
fi
|
||||||
if [ "$gewijzigd" -eq 1 ]; then
|
if [ "$gewijzigd" -eq 1 ]; then
|
||||||
goed "Bestaande .env bijgewerkt (BIND=$BIND, PORT=$POORT)"
|
goed "Bestaande .env bijgewerkt (BIND=$BIND, PORT=$POORT)"
|
||||||
else
|
else
|
||||||
|
|
@ -602,8 +684,8 @@ schrijf_env() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$DROOG" -eq 1 ]; then
|
if [ "$DROOG" -eq 1 ]; then
|
||||||
printf ' [droog] .env aanmaken met BIND=%s PORT=%s, draaiend als %s\n' \
|
printf ' [droog] .env aanmaken met BIND=%s PORT=%s BASE_DIR=%s, draaiend als %s\n' \
|
||||||
"$BIND" "$POORT" "${SU_NAAM:-root}"
|
"$BIND" "$POORT" "${BASE_DIR:-/opt/serverup}" "${SU_NAAM:-root}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -618,6 +700,7 @@ schrijf_env() {
|
||||||
zet_env_regel "$env_bestand" SU_UID "$SU_UID"
|
zet_env_regel "$env_bestand" SU_UID "$SU_UID"
|
||||||
zet_env_regel "$env_bestand" SU_GID "$SU_GID"
|
zet_env_regel "$env_bestand" SU_GID "$SU_GID"
|
||||||
fi
|
fi
|
||||||
|
[ -n "$BASE_DIR" ] && zet_env_regel "$env_bestand" BASE_DIR "$BASE_DIR"
|
||||||
goed "Aangemaakt: $env_bestand (BIND=$BIND, PORT=$POORT)"
|
goed "Aangemaakt: $env_bestand (BIND=$BIND, PORT=$POORT)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -861,6 +944,152 @@ afsluiting() {
|
||||||
printf ' Documentatie: %s/README.md\n\n' "$DOEL"
|
printf ' Documentatie: %s/README.md\n\n' "$DOEL"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Doorlichten ──────────────────────────────────────────────────────────────
|
||||||
|
# De fouten die dit script had, waren allemaal van dezelfde soort: iets klopte
|
||||||
|
# niet en niemand merkte het, omdat er nergens werd nagekeken. Dit is het
|
||||||
|
# tegengif — het controleert de aannames in plaats van ze te doen.
|
||||||
|
DOK_FOUTEN=0
|
||||||
|
DOK_WAARSCHUWINGEN=0
|
||||||
|
|
||||||
|
dok_ok() { goed "$1"; }
|
||||||
|
dok_waar() { waarschuw "$1"; DOK_WAARSCHUWINGEN=$((DOK_WAARSCHUWINGEN + 1)); }
|
||||||
|
dok_fout() { printf ' %s✖%s %s\n' "$C_FOUT" "$C_UIT" "$1"; DOK_FOUTEN=$((DOK_FOUTEN + 1)); }
|
||||||
|
|
||||||
|
_dok_env() {
|
||||||
|
# _dok_env SLEUTEL — de waarde uit .env, of leeg.
|
||||||
|
[ -f "$DOEL/.env" ] || return 0
|
||||||
|
sed -n "s/^$1=//p" "$DOEL/.env" 2>/dev/null | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
_dok_inspect() {
|
||||||
|
# _dok_inspect FORMAAT — docker inspect op de container, of leeg.
|
||||||
|
$SUDO docker inspect server-up --format "$1" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
doorlichten() {
|
||||||
|
stap "Installatie"
|
||||||
|
if [ -d "$DOEL" ] && [ -f "$DOEL/docker-compose.yml" ]; then
|
||||||
|
dok_ok "Gevonden in $DOEL"
|
||||||
|
else
|
||||||
|
dok_fout "Geen installatie in $DOEL (geen docker-compose.yml)"
|
||||||
|
printf '\n'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ -f "$DOEL/.env" ]; then
|
||||||
|
_rechten="$(stat -c '%a' "$DOEL/.env" 2>/dev/null || echo '?')"
|
||||||
|
case "$_rechten" in
|
||||||
|
600|400) dok_ok ".env staat op $_rechten" ;;
|
||||||
|
'?') dok_waar "Rechten van .env niet te bepalen" ;;
|
||||||
|
*) dok_waar ".env staat op $_rechten; er staan tokens en wachtwoorden in. Zet hem op 600." ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
dok_waar "Geen .env in $DOEL; alles draait op de standaardwaarden"
|
||||||
|
fi
|
||||||
|
[ -f "$DOEL/VERSION" ] && log "Versie in de map: $(cat "$DOEL/VERSION" 2>/dev/null)"
|
||||||
|
|
||||||
|
stap "Docker"
|
||||||
|
if $SUDO docker info >/dev/null 2>&1; then
|
||||||
|
dok_ok "Daemon reageert ($($SUDO docker version --format '{{.Server.Version}}' 2>/dev/null || echo '?'))"
|
||||||
|
else
|
||||||
|
dok_fout "De Docker-daemon reageert niet"
|
||||||
|
printf '\n'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
stap "Container"
|
||||||
|
_staat="$(_dok_inspect '{{.State.Status}}')"
|
||||||
|
if [ -z "$_staat" ]; then
|
||||||
|
dok_fout "Er is geen container 'server-up'. Start hem met: $SUDO docker compose -f $DOEL/docker-compose.yml up -d"
|
||||||
|
elif [ "$_staat" = "running" ]; then
|
||||||
|
_gezond="$(_dok_inspect '{{if .State.Health}}{{.State.Health.Status}}{{end}}')"
|
||||||
|
dok_ok "Draait${_gezond:+ (}${_gezond:-}${_gezond:+)}"
|
||||||
|
[ "${_gezond:-healthy}" = "healthy" ] || dok_waar "De healthcheck staat op '$_gezond'"
|
||||||
|
else
|
||||||
|
dok_fout "De container staat op '$_staat'"
|
||||||
|
fi
|
||||||
|
_img="$(_dok_inspect '{{.Config.Image}}')"
|
||||||
|
[ -n "$_img" ] && log "Image: $_img"
|
||||||
|
|
||||||
|
stap "Bereikbaarheid"
|
||||||
|
_poort="$(_dok_env PORT)"; _poort="${_poort:-$POORT}"
|
||||||
|
_bind="$(_dok_env BIND)"
|
||||||
|
[ -n "$_bind" ] && geldig_bind "$_bind" && BIND="$_bind"
|
||||||
|
POORT="$_poort"
|
||||||
|
_adres="http://$(probe_adres):${POORT}"
|
||||||
|
if curl -fsS --connect-timeout 3 --max-time 8 "${_adres}/healthz" >/dev/null 2>&1; then
|
||||||
|
dok_ok "Antwoordt op $_adres"
|
||||||
|
else
|
||||||
|
dok_fout "Geen antwoord op $_adres"
|
||||||
|
log "Dit is het adres waarop compose publiceert (BIND=$BIND)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
stap "Account"
|
||||||
|
_uid_env="$(_dok_env SU_UID)"
|
||||||
|
_uid_echt="$($SUDO docker exec server-up id -u 2>/dev/null || echo '')"
|
||||||
|
if [ -z "$_uid_echt" ]; then
|
||||||
|
dok_waar "Kon niet in de container kijken onder welk account hij draait"
|
||||||
|
elif [ "$_uid_echt" = "0" ]; then
|
||||||
|
if [ -n "$_uid_env" ] && [ "$_uid_env" != "0" ]; then
|
||||||
|
dok_fout "SU_UID=$_uid_env staat in .env maar hij draait als root. Hercreëer de container: $SUDO docker compose -f $DOEL/docker-compose.yml up -d"
|
||||||
|
else
|
||||||
|
dok_waar "Draait als root. Zie docs/beveiliging.md; sh $DOEL/install.sh --update laat je kiezen."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
dok_ok "Draait als uid $_uid_echt"
|
||||||
|
[ -n "$_uid_env" ] && [ "$_uid_env" != "$_uid_echt" ] \
|
||||||
|
&& dok_waar "In .env staat SU_UID=$_uid_env; hercreëer de container om dat door te voeren."
|
||||||
|
fi
|
||||||
|
|
||||||
|
stap "Mappen"
|
||||||
|
_basis="$(_dok_env BASE_DIR)"; _basis="${_basis:-/opt/serverup}"
|
||||||
|
log "BASE_DIR: $_basis"
|
||||||
|
if $SUDO docker exec server-up test -d "$_basis" 2>/dev/null; then
|
||||||
|
# Bestaan is niet genoeg: zonder mount schrijft hij in zijn eigen
|
||||||
|
# overlay en zie je op de host niets terug.
|
||||||
|
if $SUDO docker exec server-up sh -c "grep -q ' $_basis ' /proc/self/mountinfo" 2>/dev/null; then
|
||||||
|
dok_ok "$_basis is in de container gekoppeld"
|
||||||
|
else
|
||||||
|
dok_fout "$_basis bestaat in de container maar is niet gekoppeld; wat hij daar schrijft komt niet op de host terecht"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
dok_waar "Kon $_basis niet in de container nakijken"
|
||||||
|
fi
|
||||||
|
for _sub in stacks appdata backups; do
|
||||||
|
if [ -d "$_basis/$_sub" ]; then
|
||||||
|
log " $_sub: $(find "$_basis/$_sub" -maxdepth 1 -mindepth 1 2>/dev/null | wc -l) items, eigenaar $(stat -c '%U:%G' "$_basis/$_sub" 2>/dev/null || echo '?')"
|
||||||
|
else
|
||||||
|
dok_waar " $_sub ontbreekt nog ($_basis/$_sub)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if heeft df; then
|
||||||
|
_vrij="$(df -h "$_basis" 2>/dev/null | awk 'NR>1 {print $4; exit}')"
|
||||||
|
[ -n "$_vrij" ] && log "Vrije ruimte: $_vrij"
|
||||||
|
fi
|
||||||
|
|
||||||
|
stap "Bijwerken vanuit de interface"
|
||||||
|
_werkmap="$(_dok_inspect '{{index .Config.Labels "com.docker.compose.project.working_dir"}}')"
|
||||||
|
if [ -z "$_werkmap" ]; then
|
||||||
|
dok_waar "Geen compose-labels; bijwerken vanuit de interface kan niet"
|
||||||
|
else
|
||||||
|
log "Werkmap volgens de container: $_werkmap"
|
||||||
|
case "$_img" in
|
||||||
|
*/*) dok_ok "Registry-image, dus de knop in de interface werkt" ;;
|
||||||
|
*) log "Zelfgebouwd image; bijwerken loopt via $DOEL/install.sh --update" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n%s────────────────────────────────────────────────────────%s\n' "$C_KOP" "$C_UIT"
|
||||||
|
if [ "$DOK_FOUTEN" -gt 0 ]; then
|
||||||
|
printf '%s %s fout(en), %s waarschuwing(en)%s\n' "$C_FOUT" "$DOK_FOUTEN" "$DOK_WAARSCHUWINGEN" "$C_UIT"
|
||||||
|
elif [ "$DOK_WAARSCHUWINGEN" -gt 0 ]; then
|
||||||
|
printf '%s Geen fouten, %s waarschuwing(en)%s\n' "$C_WAARSCHUW" "$DOK_WAARSCHUWINGEN" "$C_UIT"
|
||||||
|
else
|
||||||
|
printf '%s Alles in orde%s\n' "$C_OK" "$C_UIT"
|
||||||
|
fi
|
||||||
|
printf '%s────────────────────────────────────────────────────────%s\n\n' "$C_KOP" "$C_UIT"
|
||||||
|
[ "$DOK_FOUTEN" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
verwijderen() {
|
verwijderen() {
|
||||||
stap "Server Up verwijderen"
|
stap "Server Up verwijderen"
|
||||||
[ -d "$DOEL" ] || fout "Niets gevonden in $DOEL"
|
[ -d "$DOEL" ] || fout "Niets gevonden in $DOEL"
|
||||||
|
|
@ -895,6 +1124,14 @@ case "$ACTIE" in
|
||||||
bepaal_rechten
|
bepaal_rechten
|
||||||
verwijderen
|
verwijderen
|
||||||
;;
|
;;
|
||||||
|
doctor)
|
||||||
|
# Alleen kijken, nooit wijzigen. Daarom geen bepaal_rechten: die kan
|
||||||
|
# afbreken omdat je niet kunt schrijven, terwijl lezen prima gaat.
|
||||||
|
if [ "$(id -u)" -ne 0 ] && ! docker info >/dev/null 2>&1 && heeft sudo; then
|
||||||
|
SUDO="sudo"
|
||||||
|
fi
|
||||||
|
doorlichten
|
||||||
|
;;
|
||||||
update)
|
update)
|
||||||
[ -d "$DOEL" ] || fout "Geen installatie gevonden in $DOEL. Installeer eerst zonder --update."
|
[ -d "$DOEL" ] || fout "Geen installatie gevonden in $DOEL. Installeer eerst zonder --update."
|
||||||
controleer_systeem
|
controleer_systeem
|
||||||
|
|
|
||||||
|
|
@ -179,25 +179,35 @@ def pull(tag: str, log_fn=None) -> tuple[bool, str]:
|
||||||
|
|
||||||
# ── Toepassen ────────────────────────────────────────────────────────────────
|
# ── Toepassen ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _write_tag(working_dir: str, tag: str, log_fn=None):
|
def _tag_script(tag: str) -> str:
|
||||||
"""Zet SU_TAG in het .env van de deploy-map, zodat compose het nieuwe image
|
"""Sh-regels die SU_TAG in het .env van de werkmap zetten.
|
||||||
gebruikt en de wijziging een herstart overleeft."""
|
|
||||||
env_file = Path(working_dir) / ".env"
|
Dit gebeurt in de helper-container en niet hier, en dat is geen detail: de
|
||||||
regels: list[str] = []
|
installatiemap (`/opt/server-up`) is nérgens in deze container gemount. Er
|
||||||
if env_file.exists():
|
zijn maar drie mounts — de docker-socket, het `su-data`-volume en `BASE_DIR`
|
||||||
regels = env_file.read_text(encoding="utf-8").splitlines()
|
— en de installatiemap zit daar niet bij. Zelf schrijven belandde dus in de
|
||||||
nieuw, gezet = [], False
|
overlay van de container of, waarschijnlijker, mislukte omdat de map hier
|
||||||
for r in regels:
|
niet bestaat. Bijwerken vanuit de interface kon daardoor nooit werken.
|
||||||
if r.strip().startswith("SU_TAG="):
|
|
||||||
nieuw.append(f"SU_TAG={tag}")
|
De helper mount `working_dir` wél, op hetzelfde pad, en draait als root.
|
||||||
gezet = True
|
"""
|
||||||
else:
|
q = shlex.quote(f"SU_TAG={tag}")
|
||||||
nieuw.append(r)
|
return (
|
||||||
if not gezet:
|
"touch .env; "
|
||||||
nieuw.append(f"SU_TAG={tag}")
|
"sed -i '/^SU_TAG=/d' .env; "
|
||||||
env_file.write_text("\n".join(nieuw) + "\n", encoding="utf-8")
|
f"printf '%s\\n' {q} >> .env; "
|
||||||
if log_fn:
|
)
|
||||||
log_fn(f"SU_TAG={tag} vastgelegd in {env_file}")
|
|
||||||
|
|
||||||
|
def _tag_uit_env(working_dir: str) -> str:
|
||||||
|
"""Wat staat er nú als SU_TAG in de werkmap? Leeg als we er niet bij kunnen."""
|
||||||
|
try:
|
||||||
|
for regel in (Path(working_dir) / ".env").read_text("utf-8").splitlines():
|
||||||
|
if regel.strip().startswith("SU_TAG="):
|
||||||
|
return regel.split("=", 1)[1].strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def apply(tag: str, log_fn=None) -> tuple[bool, str]:
|
def apply(tag: str, log_fn=None) -> tuple[bool, str]:
|
||||||
|
|
@ -227,31 +237,30 @@ def apply(tag: str, log_fn=None) -> tuple[bool, str]:
|
||||||
"UPDATE_LAST_APPLIED": {"tag": tag, "ts": time.time(),
|
"UPDATE_LAST_APPLIED": {"tag": tag, "ts": time.time(),
|
||||||
"from": vorige_tag}})
|
"from": vorige_tag}})
|
||||||
|
|
||||||
try:
|
|
||||||
_write_tag(werkmap, tag, log_fn)
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"Kan {werkmap}/.env niet schrijven: {e}"
|
|
||||||
|
|
||||||
if log_fn:
|
if log_fn:
|
||||||
log_fn(f"Vorige versie: {vorige_tag} (terugrollen kan vanuit Instellingen)")
|
log_fn(f"Vorige versie: {vorige_tag} (terugrollen kan vanuit Instellingen)")
|
||||||
|
log_fn(f"SU_TAG={tag} wordt door de helper in {werkmap}/.env gezet")
|
||||||
log_fn("Helper starten die de container hercreëert…")
|
log_fn("Helper starten die de container hercreëert…")
|
||||||
|
|
||||||
ok, melding = _spawn_helper(image, werkmap, service, log_fn)
|
ok, melding = _spawn_helper(image, werkmap, service, tag, log_fn)
|
||||||
if not ok:
|
if not ok:
|
||||||
return False, melding
|
return False, melding
|
||||||
return True, (f"Bijwerken naar {tag} gestart. Server Up herstart nu; "
|
return True, (f"Bijwerken naar {tag} gestart. Server Up herstart nu; "
|
||||||
"de interface komt over enkele seconden terug.")
|
"de interface komt over enkele seconden terug.")
|
||||||
|
|
||||||
|
|
||||||
def _spawn_helper(image: str, working_dir: str, service: str,
|
def _spawn_helper(image: str, working_dir: str, service: str, tag: str,
|
||||||
log_fn=None) -> tuple[bool, str]:
|
log_fn=None) -> tuple[bool, str]:
|
||||||
"""Start een korte container die `docker compose up -d <service>` uitvoert.
|
"""Start een korte container die `docker compose up -d <service>` uitvoert.
|
||||||
|
|
||||||
Draait op hetzelfde (nieuwe) image, dat de docker- en compose-CLI al bevat.
|
Draait op hetzelfde (nieuwe) image, dat de docker- en compose-CLI al bevat.
|
||||||
`sleep` geeft dit proces de tijd om zijn HTTP-antwoord af te maken voordat
|
`sleep` geeft dit proces de tijd om zijn HTTP-antwoord af te maken voordat
|
||||||
het onder zijn eigen voeten wordt weggehaald.
|
het onder zijn eigen voeten wordt weggehaald.
|
||||||
|
|
||||||
|
De helper zet ook SU_TAG: hij mount de werkmap, deze container niet.
|
||||||
"""
|
"""
|
||||||
script = (f"sleep 5; "
|
script = (f"sleep 5; "
|
||||||
|
f"{_tag_script(tag)}"
|
||||||
f"docker compose up -d --remove-orphans {shlex.quote(service)}")
|
f"docker compose up -d --remove-orphans {shlex.quote(service)}")
|
||||||
cmd = [docker._bin(), "run", "--rm", "--detach",
|
cmd = [docker._bin(), "run", "--rm", "--detach",
|
||||||
"--name", "server-up-updater",
|
"--name", "server-up-updater",
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,10 @@ def test_geen_bashismen():
|
||||||
(r"\[\[", "[[ ]] is bash-only"),
|
(r"\[\[", "[[ ]] is bash-only"),
|
||||||
(r"^\s*declare ", "declare is bash-only"),
|
(r"^\s*declare ", "declare is bash-only"),
|
||||||
(r"\$\{[A-Za-z_]+\[", "arrays zijn bash-only"),
|
(r"\$\{[A-Za-z_]+\[", "arrays zijn bash-only"),
|
||||||
(r"==", "== in test is bash-only"),
|
# Botweg overal verboden, ook in een awk-programma waar het prima mag.
|
||||||
|
# Dat is één regel die je omschrijft, tegenover een controle die precies
|
||||||
|
# moet weten wanneer je binnen [ ] zit.
|
||||||
|
(r"==", "== in test is bash-only (ook in awk: schrijf het anders)"),
|
||||||
]:
|
]:
|
||||||
treffers = [l for l in tekst.splitlines()
|
treffers = [l for l in tekst.splitlines()
|
||||||
if re.search(patroon, l) and not l.strip().startswith("#")]
|
if re.search(patroon, l) and not l.strip().startswith("#")]
|
||||||
|
|
@ -787,3 +790,220 @@ def test_zonder_terminal_valt_het_script_niet_om(tmp_path):
|
||||||
'else echo "UITKOMST=geen-tty"; fi\necho "EINDE"\n', tmp_path)
|
'else echo "UITKOMST=geen-tty"; fi\necho "EINDE"\n', tmp_path)
|
||||||
assert "UITKOMST=geen-tty" in uit, uit
|
assert "UITKOMST=geen-tty" in uit, uit
|
||||||
assert "EINDE" in uit, "het script stopte halverwege"
|
assert "EINDE" in uit, "het script stopte halverwege"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Bijwerken van een vastgepinde versie ─────────────────────────────────────
|
||||||
|
|
||||||
|
def _repo_met_tag(tmp_path):
|
||||||
|
"""Een lokale repo met een tag, en een kloon daarvan zoals install.sh die maakt."""
|
||||||
|
import subprocess as sp
|
||||||
|
bron = tmp_path / "bron"
|
||||||
|
bron.mkdir()
|
||||||
|
def g(*a, cwd=bron):
|
||||||
|
return sp.run(["git", *a], cwd=str(cwd), capture_output=True, text=True, check=True)
|
||||||
|
g("init", "-q", ".")
|
||||||
|
g("config", "user.email", "t@t"); g("config", "user.name", "t")
|
||||||
|
(bron / "f").write_text("eerst")
|
||||||
|
g("add", "f"); g("commit", "-qm", "eerst"); g("tag", "v1.0.0")
|
||||||
|
(bron / "f").write_text("later")
|
||||||
|
g("commit", "-qam", "later"); g("tag", "v1.1.0")
|
||||||
|
return bron
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("naar_tag", [True, False])
|
||||||
|
def test_bijwerken_werkt_voor_een_tag_en_voor_een_tak(naar_tag, tmp_path):
|
||||||
|
"""`git reset --hard origin/<tag>` bestaat niet: tags krijgen geen
|
||||||
|
origin/-ref. Installeren op een vaste versie was daardoor een
|
||||||
|
eenrichtingsstraat — bijwerken faalde met "ambiguous argument".
|
||||||
|
|
||||||
|
Toetsen op de exitcode zou niets zeggen: `haal_broncode && …` zet errexit
|
||||||
|
uit, dus de functie geeft ook na een mislukte git-opdracht 0 terug. Daarom
|
||||||
|
kijken we naar wat er in de werkmap staat.
|
||||||
|
"""
|
||||||
|
import subprocess as sp
|
||||||
|
if not shutil.which("git"):
|
||||||
|
pytest.skip("git niet aanwezig")
|
||||||
|
bron = _repo_met_tag(tmp_path)
|
||||||
|
doel = tmp_path / "installatie"
|
||||||
|
tak = sp.run(["git", "-C", str(bron), "rev-parse", "--abbrev-ref", "HEAD"],
|
||||||
|
capture_output=True, text=True).stdout.strip()
|
||||||
|
# Installeren op de oude versie, daarna bijwerken naar de nieuwe.
|
||||||
|
sp.run(["git", "clone", "-q", "--depth", "1", "--branch", "v1.0.0",
|
||||||
|
f"file://{bron}", str(doel)], check=True, capture_output=True)
|
||||||
|
assert (doel / "f").read_text() == "eerst"
|
||||||
|
|
||||||
|
doel_ref = "v1.1.0" if naar_tag else tak
|
||||||
|
uit = _sh(f'DROOG=0; SUDO=""; DOEL="{doel}"; BRANCH="{doel_ref}"\n'
|
||||||
|
'haal_broncode\n', tmp_path)
|
||||||
|
|
||||||
|
assert "ambiguous argument" not in uit, uit
|
||||||
|
assert (doel / "f").read_text() == "later", \
|
||||||
|
f"de installatie is niet bijgewerkt naar {doel_ref}: {uit}"
|
||||||
|
|
||||||
|
|
||||||
|
# ── De token mag nergens blijven hangen ──────────────────────────────────────
|
||||||
|
# In de URL schrijft git hem verbatim in .git/config, waar hij blijft staan. Als
|
||||||
|
# argument staat hij in /proc/<pid>/cmdline en leest elke gebruiker op de server
|
||||||
|
# hem met 'ps'. Dat laatste is precies waarom dit script --admin-password
|
||||||
|
# weigert; voor --token gold hetzelfde.
|
||||||
|
|
||||||
|
def test_de_bron_url_bevat_geen_token(tmp_path):
|
||||||
|
uit = _sh('TOKEN="geheimtoken123"\nbron_url\nprintf "\\n"\n', tmp_path)
|
||||||
|
assert "geheimtoken123" not in uit, uit
|
||||||
|
assert uit.strip().endswith(".git")
|
||||||
|
|
||||||
|
|
||||||
|
def test_de_token_staat_niet_in_de_git_argumenten(tmp_path):
|
||||||
|
"""Het pad naar het bestand mag op de opdrachtregel, de inhoud niet."""
|
||||||
|
uit = _sh('TOKEN="geheimtoken123"; SUDO=""; DROOG=1\n'
|
||||||
|
'zet_token_klaar\n'
|
||||||
|
'git_doe clone --depth 1 https://voorbeeld/x.git /tmp/x\n', tmp_path)
|
||||||
|
assert "geheimtoken123" not in uit, f"de token staat in het commando: {uit}"
|
||||||
|
assert "credential.helper=store" in uit, uit
|
||||||
|
|
||||||
|
|
||||||
|
def test_het_tokenbestand_is_afgeschermd_en_verdwijnt(tmp_path):
|
||||||
|
uit = _sh('TOKEN="geheimtoken123"\n'
|
||||||
|
'zet_token_klaar\n'
|
||||||
|
'bewaard="$TOKEN_BESTAND"\n'
|
||||||
|
'ls -l "$bewaard" | cut -c1-10\n'
|
||||||
|
'grep -q geheimtoken123 "$bewaard" && echo "TOKEN-STAAT-ERIN"\n'
|
||||||
|
'opruimen\n'
|
||||||
|
'[ -e "$bewaard" ] && echo "BLIJFT-STAAN" || echo "OPGERUIMD"\n',
|
||||||
|
tmp_path)
|
||||||
|
assert "-rw-------" in uit, f"het tokenbestand is te ruim: {uit}"
|
||||||
|
assert "TOKEN-STAAT-ERIN" in uit, uit
|
||||||
|
assert "OPGERUIMD" in uit, f"het tokenbestand blijft achter: {uit}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_de_token_komt_daadwerkelijk_bij_git_aan(tmp_path):
|
||||||
|
"""Niet lekken is de helft; hij moet ook nog wérken.
|
||||||
|
|
||||||
|
Een git-server die 401 antwoordt tot er inloggegevens komen. Lukt het
|
||||||
|
uitlezen van de refs, dan heeft de credential-helper de token geleverd —
|
||||||
|
zonder dat hij in de opdrachtregel of in .git/config stond.
|
||||||
|
"""
|
||||||
|
import http.server
|
||||||
|
import subprocess as sp
|
||||||
|
import threading
|
||||||
|
if not shutil.which("git"):
|
||||||
|
pytest.skip("git niet aanwezig")
|
||||||
|
|
||||||
|
bron = _repo_met_tag(tmp_path)
|
||||||
|
sp.run(["git", "-C", str(bron), "update-server-info"], check=True,
|
||||||
|
capture_output=True)
|
||||||
|
gezien = []
|
||||||
|
|
||||||
|
class H(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def __init__(self, *a, **kw):
|
||||||
|
super().__init__(*a, directory=str(bron / ".git"), **kw)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
kop = self.headers.get("Authorization")
|
||||||
|
gezien.append(kop)
|
||||||
|
if not kop:
|
||||||
|
self.send_response(401)
|
||||||
|
self.send_header("WWW-Authenticate", 'Basic realm="prive"')
|
||||||
|
self.send_header("Content-Length", "0")
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
super().do_GET()
|
||||||
|
|
||||||
|
def log_message(self, *a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
srv = http.server.HTTPServer(("127.0.0.1", 0), H)
|
||||||
|
poort = srv.server_address[1]
|
||||||
|
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
||||||
|
try:
|
||||||
|
uit = _sh(f'TOKEN="geheimtoken123"; SUDO=""; DROOG=0\n'
|
||||||
|
f'bron_url() {{ printf "http://127.0.0.1:{poort}/"; }}\n'
|
||||||
|
'zet_token_klaar\n'
|
||||||
|
f'git_doe ls-remote "http://127.0.0.1:{poort}/"\n', tmp_path)
|
||||||
|
finally:
|
||||||
|
srv.shutdown()
|
||||||
|
|
||||||
|
assert "geheimtoken123" not in uit, f"de token staat in de uitvoer: {uit}"
|
||||||
|
assert any(a and a.startswith("Basic") for a in gezien), \
|
||||||
|
f"de server kreeg nooit inloggegevens: {gezien}"
|
||||||
|
assert "refs/tags/v1.0.0" in uit, f"de refs zijn niet opgehaald: {uit}"
|
||||||
|
|
||||||
|
|
||||||
|
# ── --base-dir ───────────────────────────────────────────────────────────────
|
||||||
|
# BASE_DIR is sinds v0.9 de hoofdknop voor je paden, maar je kon hem alleen
|
||||||
|
# achteraf in .env zetten — en dan moet je de data alsnog verhuizen.
|
||||||
|
|
||||||
|
def test_base_dir_belandt_in_env_en_in_de_mappen(tmp_path):
|
||||||
|
r = _draai("--dry-run", "--dir", str(tmp_path / "su"), "--yes",
|
||||||
|
"--base-dir", "/srv/serverup", "--user", "root")
|
||||||
|
assert r.returncode == 0, r.stderr
|
||||||
|
assert "BASE_DIR=/srv/serverup" in r.stdout, r.stdout
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("pad", ["relatief/pad", "srv", "."])
|
||||||
|
def test_base_dir_moet_een_volledig_pad_zijn(pad):
|
||||||
|
"""Die map wordt in de container op zichzelf gemount; een relatief pad komt
|
||||||
|
daar ergens anders uit dan op de host."""
|
||||||
|
r = _draai("--base-dir", pad, "--dry-run", "--yes")
|
||||||
|
assert r.returncode != 0
|
||||||
|
assert "volledig pad" in r.stderr
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("pad", ["/", "/etc", "/usr", "/var", "/root"])
|
||||||
|
def test_base_dir_weigert_systeemmappen(pad):
|
||||||
|
r = _draai("--base-dir", pad, "--dry-run", "--yes")
|
||||||
|
assert r.returncode != 0
|
||||||
|
assert "mag niet" in r.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def test_afsluitende_schuine_streep_verdwijnt(tmp_path):
|
||||||
|
r = _draai("--dry-run", "--dir", str(tmp_path / "su"), "--yes",
|
||||||
|
"--base-dir", "/srv/serverup/", "--user", "root")
|
||||||
|
assert "BASE_DIR=/srv/serverup," in r.stdout or "BASE_DIR=/srv/serverup " in r.stdout, r.stdout
|
||||||
|
assert "/srv/serverup//" not in r.stdout
|
||||||
|
|
||||||
|
|
||||||
|
# ── --doctor ─────────────────────────────────────────────────────────────────
|
||||||
|
# De fouten die dit script had waren allemaal van dezelfde soort: iets klopte
|
||||||
|
# niet en niemand keek na. Dit is het tegengif.
|
||||||
|
|
||||||
|
def test_doctor_meldt_een_ontbrekende_installatie(tmp_path):
|
||||||
|
r = _draai("--doctor", "--dir", str(tmp_path / "bestaatniet"))
|
||||||
|
assert r.returncode != 0, "een ontbrekende installatie is geen succes"
|
||||||
|
assert "Geen installatie" in r.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_kijkt_naar_de_rechten_van_env(tmp_path):
|
||||||
|
"""In .env staan tokens en wachtwoordhashes."""
|
||||||
|
doel = tmp_path / "su"
|
||||||
|
doel.mkdir()
|
||||||
|
(doel / "docker-compose.yml").write_text("services: {}\n")
|
||||||
|
env = doel / ".env"
|
||||||
|
env.write_text("BIND=127.0.0.1\nPORT=5000\n")
|
||||||
|
env.chmod(0o644)
|
||||||
|
r = _draai("--doctor", "--dir", str(doel))
|
||||||
|
assert "644" in r.stdout and "600" in r.stdout, r.stdout
|
||||||
|
|
||||||
|
env.chmod(0o600)
|
||||||
|
r = _draai("--doctor", "--dir", str(doel))
|
||||||
|
assert ".env staat op 600" in r.stdout, r.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_wijzigt_niets(tmp_path):
|
||||||
|
"""Doorlichten is kijken. Draait iemand dit op een productieserver, dan mag
|
||||||
|
er niets veranderen — ook geen map aangemaakt."""
|
||||||
|
doel = tmp_path / "su"
|
||||||
|
doel.mkdir()
|
||||||
|
(doel / "docker-compose.yml").write_text("services: {}\n")
|
||||||
|
(doel / ".env").write_text("BASE_DIR=/tmp/su-doctor-mag-dit-niet-maken\n")
|
||||||
|
(doel / ".env").chmod(0o600)
|
||||||
|
voor = sorted(p.name for p in doel.iterdir())
|
||||||
|
_draai("--doctor", "--dir", str(doel))
|
||||||
|
assert sorted(p.name for p in doel.iterdir()) == voor
|
||||||
|
assert not Path("/tmp/su-doctor-mag-dit-niet-maken").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_staat_in_de_hulptekst():
|
||||||
|
r = _draai("--help")
|
||||||
|
assert "--doctor" in r.stdout
|
||||||
|
assert "--base-dir" in r.stdout
|
||||||
|
|
|
||||||
|
|
@ -106,27 +106,66 @@ def test_pull_weigert_ongeldige_tag(su, monkeypatch, tag):
|
||||||
assert "Ongeldige tag" in msg
|
assert "Ongeldige tag" in msg
|
||||||
|
|
||||||
|
|
||||||
def test_write_tag_maakt_env_aan(su, tmp_path):
|
# ── SU_TAG wegschrijven ──────────────────────────────────────────────────────
|
||||||
su._write_tag(str(tmp_path), "0.5.10")
|
# Dit gebeurde eerder in Python, hier in de container. Maar de installatiemap is
|
||||||
assert (tmp_path / ".env").read_text().strip() == "SU_TAG=0.5.10"
|
# hier nergens gemount — er zijn maar drie mounts: de docker-socket, su-data en
|
||||||
|
# BASE_DIR. Bijwerken vanuit de interface kon daardoor nooit werken. Het gebeurt
|
||||||
|
# nu in de helper, die de werkmap wél mount, dus toetsen we het shellfragment.
|
||||||
|
|
||||||
|
def _draai_tagscript(su, map_: Path, tag: str):
|
||||||
|
import subprocess
|
||||||
|
r = subprocess.run(["sh", "-c", su._tag_script(tag)], cwd=str(map_),
|
||||||
|
capture_output=True, text=True, timeout=30)
|
||||||
|
assert r.returncode == 0, r.stderr
|
||||||
|
return (map_ / ".env").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def test_write_tag_vervangt_bestaande_regel(su, tmp_path):
|
def test_tagscript_maakt_env_aan(su, tmp_path):
|
||||||
|
assert _draai_tagscript(su, tmp_path, "0.5.10").strip() == "SU_TAG=0.5.10"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tagscript_vervangt_bestaande_regel(su, tmp_path):
|
||||||
(tmp_path / ".env").write_text("BIND=0.0.0.0\nSU_TAG=0.5.00\nPORT=5000\n")
|
(tmp_path / ".env").write_text("BIND=0.0.0.0\nSU_TAG=0.5.00\nPORT=5000\n")
|
||||||
su._write_tag(str(tmp_path), "0.5.10")
|
regels = _draai_tagscript(su, tmp_path, "0.5.10").splitlines()
|
||||||
regels = (tmp_path / ".env").read_text().splitlines()
|
assert regels == ["BIND=0.0.0.0", "PORT=5000", "SU_TAG=0.5.10"]
|
||||||
assert regels == ["BIND=0.0.0.0", "SU_TAG=0.5.10", "PORT=5000"]
|
|
||||||
assert sum(1 for r in regels if r.startswith("SU_TAG=")) == 1
|
assert sum(1 for r in regels if r.startswith("SU_TAG=")) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_write_tag_behoudt_andere_instellingen(su, tmp_path):
|
def test_tagscript_behoudt_andere_instellingen(su, tmp_path):
|
||||||
(tmp_path / ".env").write_text("SU_IMAGE=git.example.com/bes-r/server-up\n")
|
(tmp_path / ".env").write_text("SU_IMAGE=git.example.com/bes-r/server-up\n")
|
||||||
su._write_tag(str(tmp_path), "0.5.10")
|
inhoud = _draai_tagscript(su, tmp_path, "0.5.10")
|
||||||
inhoud = (tmp_path / ".env").read_text()
|
|
||||||
assert "SU_IMAGE=git.example.com/bes-r/server-up" in inhoud
|
assert "SU_IMAGE=git.example.com/bes-r/server-up" in inhoud
|
||||||
assert "SU_TAG=0.5.10" in inhoud
|
assert "SU_TAG=0.5.10" in inhoud
|
||||||
|
|
||||||
|
|
||||||
|
def test_tagscript_laat_zich_niet_uitbreiden(su, tmp_path):
|
||||||
|
"""De tag wordt al door _TAG_RE gefilterd, maar dit fragment belandt in een
|
||||||
|
`sh -c` in een container met de docker-socket. Twee sloten op de deur."""
|
||||||
|
inhoud = _draai_tagscript(su, tmp_path, "1.0.0'; touch /tmp/su-inbraak; echo '")
|
||||||
|
assert not Path("/tmp/su-inbraak").exists(), "de tag brak uit het commando"
|
||||||
|
assert inhoud.count("SU_TAG=") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_de_helper_zet_de_tag_en_mount_de_werkmap(su, monkeypatch):
|
||||||
|
"""Zonder de mount schrijft de helper in zijn eigen overlay, en zonder het
|
||||||
|
tag-fragment start compose gewoon het oude image opnieuw."""
|
||||||
|
aanroepen = []
|
||||||
|
|
||||||
|
def nep_run(cmd, **kw):
|
||||||
|
aanroepen.append(cmd)
|
||||||
|
return type("R", (), {"returncode": 0, "stdout": "", "stderr": ""})()
|
||||||
|
|
||||||
|
monkeypatch.setattr(su.docker, "_run", nep_run)
|
||||||
|
su._spawn_helper("git.example.com/bes-r/server-up:0.5.10",
|
||||||
|
"/opt/server-up", "server-up", "0.5.10")
|
||||||
|
# De eerste aanroep ruimt een blijven hangen helper op; de tweede is de start.
|
||||||
|
cmd = next(c for c in aanroepen if "run" in c)
|
||||||
|
assert "-v" in cmd and "/opt/server-up:/opt/server-up" in cmd
|
||||||
|
script = cmd[-1]
|
||||||
|
assert "SU_TAG=0.5.10" in script, "de helper zet de tag niet"
|
||||||
|
assert "docker compose up -d" in script
|
||||||
|
|
||||||
|
|
||||||
def test_rollback_zonder_vorige_versie(su):
|
def test_rollback_zonder_vorige_versie(su):
|
||||||
ok, msg = su.rollback()
|
ok, msg = su.rollback()
|
||||||
assert ok is False
|
assert ok is False
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue