Duidelijker maken waarom de updateknop uit staat bij een zelfgebouwd image
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 41s

Op een server die zelf bouwt (SU_IMAGE leeg, zoals de dev-server na een
deploy-run) draait het image als 'server-up:<versie>'. Zonder registry-pad valt
er niets op te halen, dus de knop staat uit. De melding verwees alleen naar
UPDATE_IMAGE, terwijl het juiste antwoord daar is: die server werkt al bij via
Git.

- status() geeft nu een 'mode' terug (registry / local-build / no-compose /
  no-container) en de melding bij local-build noemt beide routes: pushen naar
  de branch of de deploy-workflow starten, en als alternatief SU_IMAGE.
- De UI toont dat geval als informatie in plaats van als waarschuwing; het is
  de normale opzet, geen storing.
- docs/updates.md: tabel met de twee manieren van bijwerken en de waarschuwing
  om ze niet door elkaar te gebruiken op dezelfde server, omdat de
  deploy-workflow en de updateknop allebei SU_TAG in .env schrijven.

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-26 23:23:30 +02:00
parent a1fb5f5a8f
commit 576ee17a61
5 changed files with 70 additions and 8 deletions

View file

@ -18,6 +18,28 @@ Er zijn twee losse onderdelen:
Je hebt ze allebei nodig voor de updateknop. Alleen detectie werkt ook prima — Je hebt ze allebei nodig voor de updateknop. Alleen detectie werkt ook prima —
dan zie je een melding en werk je bij met een git-tag, zoals voorheen. dan zie je een melding en werk je bij met een git-tag, zoals voorheen.
### Twee manieren van bijwerken — kies er één per server
| | Hoe je bijwerkt | Knop in de interface |
|---|---|---|
| **Git-gestuurd** (`SU_IMAGE` leeg) | Push naar de branch, de deploy-workflow bouwt en herstart | Uit, met uitleg |
| **Registry** (`SU_IMAGE` gezet) | Knop "Nu bijwerken", of een tag pushen | Aan |
Een server die zelf bouwt kán niet uit een registry bijwerken: het image
`server-up:0.5.40-beta` heeft geen registry-pad, dus er is niets om op te halen.
De interface meldt dat en verwijst naar de Git-route. **Dat is geen storing**
zo is een dev-server die na elke push opnieuw bouwt precies bedoeld.
> **Gebruik ze niet door elkaar op dezelfde server.** De deploy-workflow schrijft
> `SU_TAG` in `.env`, en de updateknop doet dat ook. Werk je in de interface bij
> naar 0.5.50 en pusht daarna iemand naar die branch, dan zet de workflow
> `SU_TAG` terug naar wat de branch bevat. Voor een server die aan een branch
> hangt is dat juist goed — git is daar de bron — maar verwacht dan geen effect
> van de knop.
>
> In de praktijk: **dev-server op Git**, en de registry-route voor productie of
> voor installaties die geen toegang tot je repository hebben.
### Kanalen ### Kanalen
| Kanaal | Ziet | Voorbeeld | | Kanaal | Ziet | Voorbeeld |

View file

@ -388,6 +388,7 @@ def api_update():
res.update({ res.update({
"can_apply": st["can_apply"], "can_apply": st["can_apply"],
"apply_reason": st["reason"], "apply_reason": st["reason"],
"apply_mode": st.get("mode", ""),
"image": st["image"], "image": st["image"],
"previous_tag": st["previous_tag"], "previous_tag": st["previous_tag"],
"channels": list(updater.CHANNELS), "channels": list(updater.CHANNELS),

View file

@ -98,28 +98,40 @@ def status() -> dict:
"""Kan deze installatie zichzelf bijwerken, en zo niet: waarom niet?""" """Kan deze installatie zichzelf bijwerken, en zo niet: waarom niet?"""
info = inspect_self() info = inspect_self()
repo = image_repo(info) repo = image_repo(info)
res = {"can_apply": False, "reason": "", "image": repo, res = {"can_apply": False, "reason": "", "mode": "", "image": repo,
"current_image": info.get("image", ""), "current_image": info.get("image", ""),
"working_dir": info.get("working_dir", ""), "working_dir": info.get("working_dir", ""),
"service": info.get("service", ""), "service": info.get("service", ""),
"previous_tag": (cfg.load().get("UPDATE_PREVIOUS_TAG") or "")} "previous_tag": (cfg.load().get("UPDATE_PREVIOUS_TAG") or "")}
if not info: if not info:
res["mode"] = "no-container"
res["reason"] = ("De eigen container is niet te vinden via de " res["reason"] = ("De eigen container is niet te vinden via de "
"Docker-socket. Draait Server Up wel in Docker?") "Docker-socket. Draait Server Up wel in Docker?")
return res return res
if not info.get("working_dir"): if not info.get("working_dir"):
res["mode"] = "no-compose"
res["reason"] = ("Deze container is niet met docker compose gestart " res["reason"] = ("Deze container is niet met docker compose gestart "
"(geen compose-labels), dus bijwerken vanuit de " "(geen compose-labels), dus bijwerken vanuit de "
"interface kan niet.") "interface kan niet.")
return res return res
if not repo: if not repo:
res["mode"] = "unknown"
res["reason"] = "Geen image bekend om bij te werken." res["reason"] = "Geen image bekend om bij te werken."
return res return res
if "/" not in repo: if "/" not in repo:
res["reason"] = (f"Het image '{repo}' komt niet uit een registry. Stel " # Lokaal gebouwd image. Meestal is dat geen probleem maar juist de
"UPDATE_IMAGE in op bv. git.example.com/bes-r/server-up " # opzet: de server bouwt zelf na een push naar Git. De knop is bedoeld
"en draai vanaf een registry-image.") # voor installaties die een kant-en-klaar image uit een registry halen.
res["mode"] = "local-build"
res["reason"] = (
f"Deze installatie draait op een zelfgebouwd image ('{repo}') en "
"werkt dus bij via je eigen build- of deploy-proces — bij een "
"Forgejo-opzet: pushen naar de branch, of de deploy-workflow "
"handmatig starten. Wil je hier tóch de knop gebruiken, laat de "
"server dan vanaf een registry-image draaien (SU_IMAGE in .env, "
"zie docs/updates.md).")
return res return res
res["mode"] = "registry"
res["can_apply"] = True res["can_apply"] = True
return res return res

View file

@ -670,10 +670,14 @@ tailwind.config = {
</div> </div>
<!-- Waarom de knop niet bruikbaar is (geen registry-image, geen <!-- Waarom de knop niet bruikbaar is (geen registry-image, geen
compose-labels, …) — anders staat er alleen een dode knop. --> compose-labels, …) — anders staat er alleen een dode knop. -->
<!-- Bij een zelfgebouwd image is dit geen storing maar de opzet:
dan is Git de weg om bij te werken, niet deze knop. -->
<p x-show="update.update_available && !update.can_apply" <p x-show="update.update_available && !update.can_apply"
class="text-xs text-wr flex items-start gap-1.5"> class="text-xs flex items-start gap-1.5"
<span class="mdi mdi-alert-outline shrink-0"></span> :class="update.apply_mode === 'local-build' ? 'text-t2 dark:text-t2-dark' : 'text-wr'">
<span><span x-text="update.apply_reason"></span> <span x-text="t('update_cannot_apply')"></span></span> <span class="mdi shrink-0"
:class="update.apply_mode === 'local-build' ? 'mdi-information-outline' : 'mdi-alert-outline'"></span>
<span x-text="update.apply_reason"></span>
</p> </p>
<p x-show="update.error" class="text-xs text-er" x-text="update.error"></p> <p x-show="update.error" class="text-xs text-er" x-text="update.error"></p>
<p x-show="update.checked_at" class="text-[11px] text-dm dark:text-dm-dark"> <p x-show="update.checked_at" class="text-[11px] text-dm dark:text-dm-dark">

View file

@ -53,10 +53,33 @@ def test_status_ok_bij_registry_image(su, monkeypatch):
def test_status_weigert_lokaal_gebouwd_image(su, monkeypatch): def test_status_weigert_lokaal_gebouwd_image(su, monkeypatch):
"""Een zelfgebouwd image is geen storing maar de normale opzet bij een
server die na een push zelf bouwt; de melding moet dat ook zeggen."""
_fake_inspect(su, monkeypatch, image="server-up:latest") _fake_inspect(su, monkeypatch, image="server-up:latest")
st = su.status() st = su.status()
assert st["can_apply"] is False assert st["can_apply"] is False
assert "registry" in st["reason"] assert st["mode"] == "local-build"
assert "zelfgebouwd" in st["reason"]
assert "deploy" in st["reason"] # wijst naar de Git-route
assert "SU_IMAGE" in st["reason"] # én naar het alternatief
def test_status_modus_bij_registry_image(su, monkeypatch):
_fake_inspect(su, monkeypatch)
assert su.status()["mode"] == "registry"
@pytest.mark.parametrize("overrides,modus", [
({"working_dir": ""}, "no-compose"),
])
def test_status_modi(su, monkeypatch, overrides, modus):
_fake_inspect(su, monkeypatch, **overrides)
assert su.status()["mode"] == modus
def test_status_modus_zonder_container(su, monkeypatch):
monkeypatch.setattr(su, "inspect_self", lambda: {})
assert su.status()["mode"] == "no-container"
def test_status_weigert_zonder_compose_labels(su, monkeypatch): def test_status_weigert_zonder_compose_labels(su, monkeypatch):