fix(unifi): herstel MongoDB-gebruiker
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 5m44s

This commit is contained in:
Ramon 2026-08-06 09:36:37 +02:00
parent da9981378b
commit 6f549357b1
7 changed files with 131 additions and 17 deletions

View file

@ -1,3 +1,18 @@
# v0.8.20-beta — UniFi herstelt zijn MongoDB-gebruiker
Een eerder geïnitialiseerd Mongo-volume voerde het init-script niet opnieuw uit.
Daardoor bleef UniFi herstarten met `UserNotFound: unifi@unifi` en kwam de
webinterface nooit online.
- **Idempotente databasereparatie.** Een korte init-container maakt de
UniFi-gebruiker aan of werkt hem bij, ook bij een bestaand Mongo-volume.
- **Oude installaties worden ondersteund.** Zowel de officiële authsource
`admin` als de eerder opgeslagen authsource `unifi` wordt gerepareerd.
- **UniFi wacht op de reparatie.** De applicatie start pas wanneer Mongo gezond
is en de gebruikerscontrole succesvol is afgerond.
- **Geen misleidende poort 8843.** De appkaart toont alleen werkelijk op de host
gepubliceerde poorten; UniFi's ongepubliceerde gastportaalpoort verdwijnt.
# v0.8.19-beta — Mobiele appacties reageren weer
In het mobiele actiemenu kregen normale acties zonder expliciete

View file

@ -1 +1 @@
0.8.19-beta
0.8.20-beta

View file

@ -22,6 +22,8 @@ services:
depends_on:
<< service_name >>-mongo:
condition: service_healthy
<< service_name >>-mongo-init:
condition: service_completed_successfully
restart: unless-stopped
<< service_name >>-mongo:
@ -47,5 +49,25 @@ services:
start_period: 30s
restart: unless-stopped
<< service_name >>-mongo-init:
image: mongo:4.4.29
container_name: << service_name >>-mongo-init
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=<< mongo_password >>
- MONGO_USER=unifi
- MONGO_PASS=<< mongo_password >>
- MONGO_HOST=<< service_name >>-mongo
- MONGO_PORT=27017
- MONGO_DBNAME=unifi
- MONGO_AUTHSOURCE=admin
volumes:
- ./init-mongo.sh:/init-mongo.sh:ro
entrypoint: ["/bin/bash", "/init-mongo.sh"]
depends_on:
<< service_name >>-mongo:
condition: service_healthy
restart: "no"
volumes:
<< service_name >>_mongodata:

View file

@ -1,24 +1,49 @@
<% raw %>
#!/bin/bash
set -e
if which mongosh > /dev/null 2>&1; then
mongo_init_bin="mongosh"
else
mongo_init_bin="mongo"
fi
"${mongo_init_bin}" <<EOF
use ${MONGO_AUTHSOURCE}
db.auth("${MONGO_INITDB_ROOT_USERNAME}", "${MONGO_INITDB_ROOT_PASSWORD}")
db.createUser({
user: "${MONGO_USER}",
pwd: "${MONGO_PASS}",
roles: [
"clusterMonitor",
{ db: "${MONGO_DBNAME}", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_stat", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_audit", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_restore", role: "dbOwner" }
]
})
mongo_args=()
if [[ -n "${MONGO_HOST:-}" ]]; then
mongo_args=(
--host "${MONGO_HOST}"
--port "${MONGO_PORT:-27017}"
)
fi
"${mongo_init_bin}" "${mongo_args[@]}" <<EOF
const authDb = db.getSiblingDB("${MONGO_AUTHSOURCE}");
if (!authDb.auth("${MONGO_INITDB_ROOT_USERNAME}", "${MONGO_INITDB_ROOT_PASSWORD}")) {
quit(18);
}
const roles = [
{ db: "admin", role: "clusterMonitor" },
{ db: "${MONGO_DBNAME}", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_stat", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_audit", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_restore", role: "dbOwner" }
];
function ensureUser(database) {
const userDb = db.getSiblingDB(database);
const existing = userDb.getUser("${MONGO_USER}");
if (existing) {
userDb.updateUser("${MONGO_USER}", { pwd: "${MONGO_PASS}", roles: roles });
} else {
userDb.createUser({ user: "${MONGO_USER}", pwd: "${MONGO_PASS}", roles: roles });
}
}
ensureUser("${MONGO_AUTHSOURCE}");
if ("${MONGO_AUTHSOURCE}" !== "${MONGO_DBNAME}") {
ensureUser("${MONGO_DBNAME}");
}
EOF
<% endraw %>

View file

@ -229,6 +229,26 @@ def has_compose(d: Path) -> bool:
return find_compose(d) is not None
def _published_ports(container: dict) -> str:
"""Toon alleen echte hostmappings, niet alle EXPOSE-poorten van het image."""
if "Publishers" not in container:
# Oudere Compose-versies leveren alleen de reeds geformatteerde tekst.
return str(container.get("Ports") or "")
uit = []
for mapping in container.get("Publishers") or []:
if not isinstance(mapping, dict):
continue
host = mapping.get("PublishedPort")
doel = mapping.get("TargetPort")
if not host or not doel:
continue
protocol = str(mapping.get("Protocol") or "tcp")
tekst = f"{host}->{doel}/{protocol}"
if tekst not in uit:
uit.append(tekst)
return ", ".join(uit)
def compose_ps(d: Path, name=None) -> list[dict]:
f = find_compose(d)
if not f:
@ -249,7 +269,7 @@ def compose_ps(d: Path, name=None) -> list[dict]:
"service": c.get("Service", ""),
"state": c.get("State", ""),
"status": c.get("Status", ""),
"ports": c.get("Ports", ""),
"ports": _published_ports(c),
"running": c.get("State", "").lower() in ("running", "up"),
})
except Exception:

View file

@ -3,6 +3,7 @@
Deze suite draait over de echte templates, niet over voorbeelden. Zo valt een
kapot template op vóórdat iemand het probeert te installeren.
"""
import subprocess
import sys
import tempfile
from pathlib import Path
@ -384,18 +385,32 @@ def test_unifi_heeft_pi_geschikte_en_correct_geinitialiseerde_mongodb():
script = (tmp / "init-mongo.sh").read_text(encoding="utf-8")
mongo = doc["services"]["unifi-mongo"]
reparatie = doc["services"]["unifi-mongo-init"]
unifi = doc["services"]["unifi"]
assert mongo["image"] == "mongo:4.4.29"
assert "./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh:ro" in mongo["volumes"]
assert any(v == "MONGO_AUTHSOURCE=admin" for v in unifi["environment"])
assert any(v == "MONGO_AUTHSOURCE=admin" for v in mongo["environment"])
assert unifi["depends_on"]["unifi-mongo"]["condition"] == "service_healthy"
assert unifi["depends_on"]["unifi-mongo-init"]["condition"] == \
"service_completed_successfully"
assert reparatie["image"] == "mongo:4.4.29"
assert reparatie["depends_on"]["unifi-mongo"]["condition"] == "service_healthy"
assert reparatie["restart"] == "no"
assert "./init-mongo.sh:/init-mongo.sh:ro" in reparatie["volumes"]
assert any(v == "MONGO_HOST=unifi-mongo" for v in reparatie["environment"])
assert mongo["healthcheck"]["test"][0] == "CMD-SHELL"
assert isinstance(mongo["healthcheck"]["test"][1], str)
assert "$${MONGO_INITDB_ROOT_PASSWORD}" in mongo["healthcheck"]["test"][1]
assert "db.createUser" in script
assert "createUser" in script
assert "clusterMonitor" in script
assert 'role: "dbOwner"' in script
assert "getUser" in script and "updateUser" in script
assert 'ensureUser("${MONGO_AUTHSOURCE}")' in script
assert 'ensureUser("${MONGO_DBNAME}")' in script
syntaxis = subprocess.run(["bash", "-n"], input=script, text=True,
capture_output=True)
assert syntaxis.returncode == 0, syntaxis.stderr
assert bp.metadata(app)["architectures"] == ["amd64", "arm64"]
assert bp.metadata(app)["web"] == {"scheme": "https", "port": 8443}

View file

@ -240,3 +240,20 @@ def test_server_up_verwijdert_zichzelf_niet(client, env, monkeypatch):
r = client.post(f"/api/containers/{app_mod.CONTAINER_NAME}/remove",
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
def test_compose_ps_toont_alleen_werkelijk_gepubliceerde_poorten(host):
container = {
"Ports": "8443/tcp, 8843/tcp",
"Publishers": [
{"TargetPort": 8443, "PublishedPort": 8443, "Protocol": "tcp"},
{"TargetPort": 8443, "PublishedPort": 8443, "Protocol": "tcp"},
{"TargetPort": 8843, "PublishedPort": 0, "Protocol": "tcp"},
],
}
assert host._published_ports(container) == "8443->8443/tcp"
def test_compose_ps_valt_bij_oude_compose_terug_op_ports(host):
assert host._published_ports(
{"Ports": "0.0.0.0:8080->80/tcp"}) == "0.0.0.0:8080->80/tcp"