v0.3.01 — boilerplate fixes + git-driven versioning
Some checks failed
Deploy server-up / deploy (push) Has been cancelled

This commit is contained in:
bes-r 2026-05-19 23:44:56 +02:00
parent e34c5ebff6
commit 0b8e3dffb8
4 changed files with 123 additions and 5 deletions

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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():