diff --git a/CHANGELOG.md b/CHANGELOG.md index bbdc44b..78b5ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +# v0.3.01 — Boilerplates fixes + git-driven versioning + +## Fixes bovenop v0.3.0 + +### 🔁 Auto-migratie van default-repos voor upgraders +Bestaande installaties (vanaf v0.2.x) hadden de Boilerplates-repo niet zichtbaar +in de App Store omdat hun `config.json` in de `su-data` volume al bestond en de +nieuwe `DEFAULTS["APP_REPOS"]` daardoor werd overschreven. + +`core/__init__.py` `load()` doet nu een eenmalige migratie: ontbrekende +default-repos worden bij opstart aangevuld op basis van id, en gemarkeerd in +`MIGRATIONS_DONE: ["v0.3.0_default_repos"]`. Wordt direct gepersisteerd. +Verwijdert een gebruiker de Boilerplates-repo expliciet, dan komt-ie niet +automatisch terug. + +### 🧹 YAML-poetsstap na boilerplate-render +Bij stacks waar de meeste optionele groepen uit staan (Authentik, Nextcloud, +etc.) liet de Jinja-render verlaten mapping-sleutels achter (`volumes:`, +`networks:`, etc. zonder kinderen). Docker-compose faalt daarop met *"block +sequence entries are not allowed in this context"*. + +`core/boilerplates.py` `_tidy()` heeft nu een `_drop_empty_mappings()` substep +die in meerdere passes mapping-sleutels verwijdert die alleen worden gevolgd +door whitespace/commentaar of een sibling op gelijke/lagere indent. Werkt in +cascade. + +### 🏷️ Versie komt nu uit git +`Dockerfile` accepteert `ARG SU_VERSION=dev` en bakt die in `ENV SU_VERSION` + +OCI image-label. Met `docker build --build-arg SU_VERSION=$(git describe ...)` +weet het image zijn eigen versie. De Server Up UI toont automatisch de juiste +waarde, ongeacht wat de productie-compose meegeeft. + +Forgejo Actions kan een tag-push automatisch verwerken — zie +`server-up-deploy/README.md`. + +## Migratie vanaf v0.3.0 +```bash +docker compose build --no-cache +docker compose up -d +``` + +--- + # v0.3.0 — UI rebuild + Boilerplates support ## Hoogtepunten diff --git a/Dockerfile b/Dockerfile index b270d9e..e5c8738 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,16 @@ FROM python:3.12-slim AS build WORKDIR /app -COPY app/requirements.txt . +COPY server-up/requirements.txt . RUN pip install --no-cache-dir --prefix=/inst -r requirements.txt FROM python:3.12-slim -LABEL org.opencontainers.image.title="Server Up" org.opencontainers.image.version="0.3.0" +# Build-arg: zet bij build naar de git-tag, zodat de image z'n eigen versie kent. +# docker build --build-arg SU_VERSION=$(git describe --tags --always) ... +# Forgejo Actions doet dit automatisch (zie server-up-deploy/README.md). +ARG SU_VERSION=dev +LABEL org.opencontainers.image.title="Server Up" \ + org.opencontainers.image.version="${SU_VERSION}" \ + org.opencontainers.image.source="https://git.example.com/bes-r/server-up" RUN apt-get update && apt-get install -y --no-install-recommends \ git openssh-client curl tar gzip ca-certificates \ && rm -rf /var/lib/apt/lists/* @@ -29,7 +35,7 @@ RUN DPKG_ARCH=$(dpkg --print-architecture) \ COPY --from=build /inst /usr/local WORKDIR /app -COPY app/ ./ +COPY server-up/ ./ COPY modules/ ./modules-bundled/ RUN mkdir -p static/fonts \ && curl -fsSL "https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" -o static/fonts/mdi.min.css \ @@ -41,7 +47,7 @@ RUN mkdir -p /data/stacks /data/appdata /data/backups /data/git modules /root \ && printf '[safe]\n\tdirectory = *\n' > /root/.gitconfig ENV PYTHONUNBUFFERED=1 PORT=5000 HOME=/root \ - SU_VERSION=0.3.0 \ + SU_VERSION=${SU_VERSION} \ SU_CONFIG=/data/config.json SU_AUDIT=/data/audit.db SU_GIT_CACHE=/data/git \ GIT_SSH_COMMAND="ssh -F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" EXPOSE 5000 diff --git a/server-up/core/__init__.py b/server-up/core/__init__.py index b1b54dd..62c829a 100644 --- a/server-up/core/__init__.py +++ b/server-up/core/__init__.py @@ -70,6 +70,29 @@ def load() -> dict: for key in ("APP_REPOS", "MODULE_REPOS"): if not cfg.get(key) and DEFAULTS.get(key): cfg[key] = list(DEFAULTS[key]) + # Migratie: vul ontbrekende default-repos eenmalig aan (bij upgrade van oudere + # versie). Markeer welke migraties al gedraaid zijn — zo komt een door de + # gebruiker verwijderde repo niet automatisch terug. + done = set(cfg.get("MIGRATIONS_DONE") or []) + migrated = False + if "v0.3.0_default_repos" not in done: + for key in ("APP_REPOS", "MODULE_REPOS"): + existing = {r.get("id") for r in (cfg.get(key) or []) if isinstance(r, dict)} + for d in DEFAULTS.get(key, []) or []: + if d.get("id") and d["id"] not in existing: + cfg[key].append(dict(d)) + migrated = True + done.add("v0.3.0_default_repos") + cfg["MIGRATIONS_DONE"] = sorted(done) + if migrated and _path.exists(): + try: + with _lock: + fd, tmp = tempfile.mkstemp(dir=str(_path.parent), suffix=".json") + with os.fdopen(fd, "w") as f: + json.dump(cfg, f, indent=2) + Path(tmp).replace(_path) + except Exception: + pass # Fix foute URLs for key in ("APP_REPOS", "MODULE_REPOS"): if cfg.get(key): diff --git a/server-up/core/boilerplates.py b/server-up/core/boilerplates.py index 2bed0e1..ac9a91f 100644 --- a/server-up/core/boilerplates.py +++ b/server-up/core/boilerplates.py @@ -233,10 +233,56 @@ def _looks_textual(p: Path) -> bool: def _tidy(text: str) -> str: - # Collapse 3+ blank lines into 2 — block conditionals leave gaps behind. + """Repareer artefacten die conditional-blocks achterlaten in YAML output: + + 1. Verwijder mapping-sleutels die geen inhoud hebben (bv. `volumes:` met alleen + een whitespace-blok eronder dat door een uitgeschakelde `<%- if %>` ontstond). + Een sleutel `key:` is "verlaten" als de eerstvolgende regel met content óf + op gelijk-of-minder indent staat (volgende sibling) óf het einde is. + 2. Vouw 3+ lege regels in tot 2. + """ + text = _drop_empty_mappings(text) return re.sub(r"\n{3,}", "\n\n", text) +_KEY_RE = re.compile(r"^(\s*)([A-Za-z_][\w\-]*)\s*:\s*$") + + +def _drop_empty_mappings(text: str) -> str: + """Verwijder ´keys´ die op een lege regel of een gelijk/minder-indent sibling + worden gevolgd — meerdere passes voor cascade-effect (parent kan leeg worden + nadat een child is opgeruimd).""" + for _ in range(5): + lines = text.splitlines() + keep = [True] * len(lines) + for i, line in enumerate(lines): + m = _KEY_RE.match(line) + if not m: + continue + indent = len(m.group(1)) + # Zoek de eerste niet-lege, niet-commentaar regel hierna + j = i + 1 + while j < len(lines) and (lines[j].strip() == "" or lines[j].lstrip().startswith("#")): + j += 1 + if j >= len(lines): + # Sleutel aan het einde van het bestand zonder inhoud + keep[i] = False + continue + next_line = lines[j] + stripped = next_line.lstrip() + next_indent = len(next_line) - len(stripped) + # Geen kinderen → meer indent zou dat zijn. Sibling/uncle = leeg. + if next_indent <= indent: + keep[i] = False + new_text = "\n".join(l for l, k in zip(lines, keep) if k) + if new_text == text: + break + text = new_text + if not text.endswith("\n"): + text += "\n" + return text + + def _normalize_compose_name(dest: Path): """Ensure there's a docker-compose.yml at root; rename compose.yaml if not.""" if (dest / "docker-compose.yml").exists() or (dest / "compose.yml").exists():