fix(unifi): herstel MongoDB-gebruiker
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 5m44s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 5m44s
This commit is contained in:
parent
da9981378b
commit
6f549357b1
7 changed files with 131 additions and 17 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -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
|
# v0.8.19-beta — Mobiele appacties reageren weer
|
||||||
|
|
||||||
In het mobiele actiemenu kregen normale acties zonder expliciete
|
In het mobiele actiemenu kregen normale acties zonder expliciete
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.8.19-beta
|
0.8.20-beta
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
<< service_name >>-mongo:
|
<< service_name >>-mongo:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
<< service_name >>-mongo-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
<< service_name >>-mongo:
|
<< service_name >>-mongo:
|
||||||
|
|
@ -47,5 +49,25 @@ services:
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
restart: unless-stopped
|
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:
|
volumes:
|
||||||
<< service_name >>_mongodata:
|
<< service_name >>_mongodata:
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,49 @@
|
||||||
<% raw %>
|
<% raw %>
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
if which mongosh > /dev/null 2>&1; then
|
if which mongosh > /dev/null 2>&1; then
|
||||||
mongo_init_bin="mongosh"
|
mongo_init_bin="mongosh"
|
||||||
else
|
else
|
||||||
mongo_init_bin="mongo"
|
mongo_init_bin="mongo"
|
||||||
fi
|
fi
|
||||||
"${mongo_init_bin}" <<EOF
|
|
||||||
use ${MONGO_AUTHSOURCE}
|
mongo_args=()
|
||||||
db.auth("${MONGO_INITDB_ROOT_USERNAME}", "${MONGO_INITDB_ROOT_PASSWORD}")
|
if [[ -n "${MONGO_HOST:-}" ]]; then
|
||||||
db.createUser({
|
mongo_args=(
|
||||||
user: "${MONGO_USER}",
|
--host "${MONGO_HOST}"
|
||||||
pwd: "${MONGO_PASS}",
|
--port "${MONGO_PORT:-27017}"
|
||||||
roles: [
|
)
|
||||||
"clusterMonitor",
|
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}", role: "dbOwner" },
|
||||||
{ db: "${MONGO_DBNAME}_stat", role: "dbOwner" },
|
{ db: "${MONGO_DBNAME}_stat", role: "dbOwner" },
|
||||||
{ db: "${MONGO_DBNAME}_audit", role: "dbOwner" },
|
{ db: "${MONGO_DBNAME}_audit", role: "dbOwner" },
|
||||||
{ db: "${MONGO_DBNAME}_restore", 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
|
EOF
|
||||||
<% endraw %>
|
<% endraw %>
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,26 @@ def has_compose(d: Path) -> bool:
|
||||||
return find_compose(d) is not None
|
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]:
|
def compose_ps(d: Path, name=None) -> list[dict]:
|
||||||
f = find_compose(d)
|
f = find_compose(d)
|
||||||
if not f:
|
if not f:
|
||||||
|
|
@ -249,7 +269,7 @@ def compose_ps(d: Path, name=None) -> list[dict]:
|
||||||
"service": c.get("Service", ""),
|
"service": c.get("Service", ""),
|
||||||
"state": c.get("State", ""),
|
"state": c.get("State", ""),
|
||||||
"status": c.get("Status", ""),
|
"status": c.get("Status", ""),
|
||||||
"ports": c.get("Ports", ""),
|
"ports": _published_ports(c),
|
||||||
"running": c.get("State", "").lower() in ("running", "up"),
|
"running": c.get("State", "").lower() in ("running", "up"),
|
||||||
})
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
Deze suite draait over de echte templates, niet over voorbeelden. Zo valt een
|
Deze suite draait over de echte templates, niet over voorbeelden. Zo valt een
|
||||||
kapot template op vóórdat iemand het probeert te installeren.
|
kapot template op vóórdat iemand het probeert te installeren.
|
||||||
"""
|
"""
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
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")
|
script = (tmp / "init-mongo.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
mongo = doc["services"]["unifi-mongo"]
|
mongo = doc["services"]["unifi-mongo"]
|
||||||
|
reparatie = doc["services"]["unifi-mongo-init"]
|
||||||
unifi = doc["services"]["unifi"]
|
unifi = doc["services"]["unifi"]
|
||||||
assert mongo["image"] == "mongo:4.4.29"
|
assert mongo["image"] == "mongo:4.4.29"
|
||||||
assert "./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh:ro" in mongo["volumes"]
|
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 unifi["environment"])
|
||||||
assert any(v == "MONGO_AUTHSOURCE=admin" for v in mongo["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"]["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 mongo["healthcheck"]["test"][0] == "CMD-SHELL"
|
||||||
assert isinstance(mongo["healthcheck"]["test"][1], str)
|
assert isinstance(mongo["healthcheck"]["test"][1], str)
|
||||||
assert "$${MONGO_INITDB_ROOT_PASSWORD}" in mongo["healthcheck"]["test"][1]
|
assert "$${MONGO_INITDB_ROOT_PASSWORD}" in mongo["healthcheck"]["test"][1]
|
||||||
assert "db.createUser" in script
|
assert "createUser" in script
|
||||||
assert "clusterMonitor" in script
|
assert "clusterMonitor" in script
|
||||||
assert 'role: "dbOwner"' 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)["architectures"] == ["amd64", "arm64"]
|
||||||
assert bp.metadata(app)["web"] == {"scheme": "https", "port": 8443}
|
assert bp.metadata(app)["web"] == {"scheme": "https", "port": 8443}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
r = client.post(f"/api/containers/{app_mod.CONTAINER_NAME}/remove",
|
||||||
headers={"X-CSRF-Token": csrf})
|
headers={"X-CSRF-Token": csrf})
|
||||||
assert r.status_code == 400
|
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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue