refactor(ariadne): split nextcloud mail and maintenance helpers
This commit is contained in:
parent
2477ca3899
commit
d999b4ff8c
@ -1,8 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -15,96 +13,17 @@ from ..settings import settings
|
|||||||
from ..utils.logging import get_logger
|
from ..utils.logging import get_logger
|
||||||
from ..utils.passwords import random_password
|
from ..utils.passwords import random_password
|
||||||
from .keycloak_admin import keycloak_admin
|
from .keycloak_admin import keycloak_admin
|
||||||
|
from .nextcloud_maintenance import run_maintenance as run_nextcloud_maintenance
|
||||||
|
from .nextcloud_mail_models import MailSyncCounters
|
||||||
|
from .nextcloud_mail_models import display_name as _display_name
|
||||||
|
from .nextcloud_mail_models import _extract_attr
|
||||||
|
from .nextcloud_mail_models import _parse_mail_export
|
||||||
|
from .nextcloud_mail_models import _resolve_mailu_email
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _extract_attr(attrs: Any, key: str) -> str:
|
|
||||||
if not isinstance(attrs, dict):
|
|
||||||
return ""
|
|
||||||
raw = attrs.get(key)
|
|
||||||
if isinstance(raw, list):
|
|
||||||
for item in raw:
|
|
||||||
if isinstance(item, str) and item.strip():
|
|
||||||
return item.strip()
|
|
||||||
return ""
|
|
||||||
if isinstance(raw, str) and raw.strip():
|
|
||||||
return raw.strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_mailu_email(username: str, user: dict[str, Any]) -> str:
|
|
||||||
attrs = user.get("attributes")
|
|
||||||
mailu_email = _extract_attr(attrs, "mailu_email")
|
|
||||||
if mailu_email:
|
|
||||||
return mailu_email
|
|
||||||
email = user.get("email")
|
|
||||||
if isinstance(email, str) and email.strip():
|
|
||||||
email = email.strip()
|
|
||||||
if email.lower().endswith(f"@{settings.mailu_domain.lower()}"):
|
|
||||||
return email
|
|
||||||
return f"{username}@{settings.mailu_domain}"
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_mail_export(output: str) -> list[tuple[str, str]]:
|
|
||||||
accounts: list[tuple[str, str]] = []
|
|
||||||
account_id = ""
|
|
||||||
for line in output.splitlines():
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
match = re.match(r"^Account\s+(\d+):", line, flags=re.IGNORECASE)
|
|
||||||
if match:
|
|
||||||
account_id = match.group(1)
|
|
||||||
continue
|
|
||||||
match = re.match(r"^-\s*E-?mail:\s*(\S+)", line, flags=re.IGNORECASE)
|
|
||||||
if match and account_id:
|
|
||||||
accounts.append((account_id, match.group(1)))
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class NextcloudMailSyncSummary:
|
|
||||||
processed: int
|
|
||||||
created: int
|
|
||||||
updated: int
|
|
||||||
deleted: int
|
|
||||||
skipped: int
|
|
||||||
failures: int
|
|
||||||
detail: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MailSyncCounters:
|
|
||||||
processed: int = 0
|
|
||||||
created: int = 0
|
|
||||||
updated: int = 0
|
|
||||||
deleted: int = 0
|
|
||||||
skipped: int = 0
|
|
||||||
failures: int = 0
|
|
||||||
last_error: str = ""
|
|
||||||
|
|
||||||
def summary(self) -> NextcloudMailSyncSummary:
|
|
||||||
return NextcloudMailSyncSummary(
|
|
||||||
processed=self.processed,
|
|
||||||
created=self.created,
|
|
||||||
updated=self.updated,
|
|
||||||
deleted=self.deleted,
|
|
||||||
skipped=self.skipped,
|
|
||||||
failures=self.failures,
|
|
||||||
detail=self.last_error,
|
|
||||||
)
|
|
||||||
|
|
||||||
def status(self) -> str:
|
|
||||||
return "ok" if self.failures == 0 else "error"
|
|
||||||
|
|
||||||
def record_failure(self, detail: str) -> None:
|
|
||||||
self.failures += 1
|
|
||||||
if detail and not self.last_error:
|
|
||||||
self.last_error = detail
|
|
||||||
|
|
||||||
|
|
||||||
class NextcloudService:
|
class NextcloudService:
|
||||||
"""Synchronize user mail configuration inside the Nextcloud pod."""
|
"""Synchronize user mail configuration inside the Nextcloud pod."""
|
||||||
|
|
||||||
@ -162,15 +81,6 @@ class NextcloudService:
|
|||||||
result = self._occ_exec(args, check=True)
|
result = self._occ_exec(args, check=True)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
def _display_name(self, user: dict[str, Any]) -> str:
|
|
||||||
first = user.get("firstName") if isinstance(user.get("firstName"), str) else ""
|
|
||||||
last = user.get("lastName") if isinstance(user.get("lastName"), str) else ""
|
|
||||||
first = first.strip()
|
|
||||||
last = last.strip()
|
|
||||||
if first and last:
|
|
||||||
return f"{first} {last}"
|
|
||||||
return last or first
|
|
||||||
|
|
||||||
def _ensure_nextcloud_user(
|
def _ensure_nextcloud_user(
|
||||||
self,
|
self,
|
||||||
username: str,
|
username: str,
|
||||||
@ -493,7 +403,7 @@ class NextcloudService:
|
|||||||
username, user_id, mailu_email, app_pw, full_user = context
|
username, user_id, mailu_email, app_pw, full_user = context
|
||||||
|
|
||||||
try:
|
try:
|
||||||
display_name = self._display_name(full_user)
|
display_name = _display_name(full_user)
|
||||||
self._ensure_nextcloud_user(username, mailu_email, display_name)
|
self._ensure_nextcloud_user(username, mailu_email, display_name)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
counters.record_failure(f"nextcloud user ensure failed: {exc}")
|
counters.record_failure(f"nextcloud user ensure failed: {exc}")
|
||||||
@ -560,13 +470,6 @@ class NextcloudService:
|
|||||||
|
|
||||||
return {"status": counters.status(), "summary": summary_payload, "detail": summary.detail}
|
return {"status": counters.status(), "summary": summary_payload, "detail": summary.detail}
|
||||||
|
|
||||||
def _run_shell(self, script: str, check: bool = True) -> None:
|
|
||||||
self._executor.exec(
|
|
||||||
script,
|
|
||||||
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
|
||||||
check=check,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _external_api(self, method: str, path: str, data: dict[str, Any] | None = None) -> dict[str, Any]:
|
def _external_api(self, method: str, path: str, data: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||||
if not settings.nextcloud_url:
|
if not settings.nextcloud_url:
|
||||||
raise RuntimeError("nextcloud url not configured")
|
raise RuntimeError("nextcloud url not configured")
|
||||||
@ -589,113 +492,7 @@ class NextcloudService:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def run_maintenance(self) -> dict[str, Any]:
|
def run_maintenance(self) -> dict[str, Any]:
|
||||||
if not settings.nextcloud_namespace:
|
return run_nextcloud_maintenance(self)
|
||||||
raise RuntimeError("nextcloud maintenance not configured")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._run_shell(
|
|
||||||
"""
|
|
||||||
set -euo pipefail
|
|
||||||
if [ ! -d /var/www/html/lib ] && [ -d /usr/src/nextcloud/lib ]; then
|
|
||||||
if command -v rsync >/dev/null 2>&1; then
|
|
||||||
rsync -a --delete --exclude config --exclude data /usr/src/nextcloud/ /var/www/html/
|
|
||||||
else
|
|
||||||
cp -a /usr/src/nextcloud/. /var/www/html/
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
mkdir -p /var/www/html/data
|
|
||||||
chown 33:33 /var/www/html || true
|
|
||||||
chmod 775 /var/www/html || true
|
|
||||||
chown -R 33:33 /var/www/html/apps /var/www/html/custom_apps /var/www/html/data /var/www/html/config 2>/dev/null || true
|
|
||||||
""",
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._occ(["config:app:set", "theming", "name", "--value", "Atlas Cloud"])
|
|
||||||
self._occ(["config:app:set", "theming", "slogan", "--value", "Unified access to Atlas services"])
|
|
||||||
theming_url = settings.nextcloud_url or "https://cloud.bstein.dev"
|
|
||||||
self._occ(["config:app:set", "theming", "url", "--value", theming_url])
|
|
||||||
self._occ(["config:app:set", "theming", "color", "--value", "#0f172a"])
|
|
||||||
self._occ(["config:app:set", "theming", "disable-user-theming", "--value", "yes"])
|
|
||||||
|
|
||||||
self._executor.exec(
|
|
||||||
["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", "app:install", "customcss"],
|
|
||||||
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
self._executor.exec(
|
|
||||||
["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", "app:enable", "customcss"],
|
|
||||||
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
mail_css = (
|
|
||||||
".mail-message-body, .mail-message-body pre, .mail-message-body code, .mail-message-body table {\n"
|
|
||||||
" font-family: \"Inter\", \"Source Sans 3\", \"Helvetica Neue\", Arial, sans-serif;\n"
|
|
||||||
" font-size: 14px;\n"
|
|
||||||
" line-height: 1.6;\n"
|
|
||||||
" color: var(--color-main-text);\n"
|
|
||||||
"}\n"
|
|
||||||
".mail-message-body pre {\n"
|
|
||||||
" background: rgba(15, 23, 42, 0.06);\n"
|
|
||||||
" padding: 12px;\n"
|
|
||||||
" border-radius: 8px;\n"
|
|
||||||
"}\n"
|
|
||||||
".mail-message-body blockquote {\n"
|
|
||||||
" border-left: 3px solid var(--color-border);\n"
|
|
||||||
" padding-left: 12px;\n"
|
|
||||||
" margin: 8px 0;\n"
|
|
||||||
" color: var(--color-text-lighter);\n"
|
|
||||||
"}\n"
|
|
||||||
".mail-message-body img {\n"
|
|
||||||
" max-width: 100%;\n"
|
|
||||||
" border-radius: 6px;\n"
|
|
||||||
"}\n"
|
|
||||||
)
|
|
||||||
self._occ(["config:app:set", "customcss", "css", "--value", mail_css])
|
|
||||||
self._occ(["config:app:set", "files", "default_quota", "--value", "250 GB"])
|
|
||||||
|
|
||||||
payload = self._external_api("GET", "?format=json")
|
|
||||||
links = payload.get("ocs", {}).get("data", []) if isinstance(payload, dict) else []
|
|
||||||
for link in links:
|
|
||||||
link_id = link.get("id") if isinstance(link, dict) else None
|
|
||||||
if link_id is not None:
|
|
||||||
self._external_api("DELETE", f"/sites/{link_id}?format=json")
|
|
||||||
|
|
||||||
sites = [
|
|
||||||
("Vaultwarden", "https://vault.bstein.dev"),
|
|
||||||
("Jellyfin", "https://stream.bstein.dev"),
|
|
||||||
("Gitea", "https://scm.bstein.dev"),
|
|
||||||
("Jenkins", "https://ci.bstein.dev"),
|
|
||||||
("Harbor", "https://registry.bstein.dev"),
|
|
||||||
("Vault", "https://secret.bstein.dev"),
|
|
||||||
("Jitsi", "https://meet.bstein.dev"),
|
|
||||||
("Grafana", "https://metrics.bstein.dev"),
|
|
||||||
("Chat LLM", "https://chat.ai.bstein.dev"),
|
|
||||||
("Vision", "https://draw.ai.bstein.dev"),
|
|
||||||
("STT/TTS", "https://talk.ai.bstein.dev"),
|
|
||||||
]
|
|
||||||
for name, url in sites:
|
|
||||||
self._external_api(
|
|
||||||
"POST",
|
|
||||||
"/sites?format=json",
|
|
||||||
data={
|
|
||||||
"name": name,
|
|
||||||
"url": url,
|
|
||||||
"lang": "",
|
|
||||||
"type": "link",
|
|
||||||
"device": "",
|
|
||||||
"icon": "",
|
|
||||||
"groups[]": "",
|
|
||||||
"redirect": "1",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except (ExecError, PodSelectionError, TimeoutError) as exc:
|
|
||||||
return {"status": "error", "detail": str(exc)}
|
|
||||||
except Exception as exc: # noqa: BLE001
|
|
||||||
return {"status": "error", "detail": str(exc)}
|
|
||||||
|
|
||||||
return {"status": "ok", "detail": "maintenance complete"}
|
|
||||||
|
|
||||||
|
|
||||||
nextcloud = NextcloudService()
|
nextcloud = NextcloudService()
|
||||||
|
|||||||
106
ariadne/services/nextcloud_mail_models.py
Normal file
106
ariadne/services/nextcloud_mail_models.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""Mail synchronization helpers for Nextcloud account management."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from ..settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_attr(attrs: Any, key: str) -> str:
|
||||||
|
if not isinstance(attrs, dict):
|
||||||
|
return ""
|
||||||
|
raw = attrs.get(key)
|
||||||
|
if isinstance(raw, list):
|
||||||
|
for item in raw:
|
||||||
|
if isinstance(item, str) and item.strip():
|
||||||
|
return item.strip()
|
||||||
|
return ""
|
||||||
|
if isinstance(raw, str) and raw.strip():
|
||||||
|
return raw.strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_mailu_email(username: str, user: dict[str, Any]) -> str:
|
||||||
|
attrs = user.get("attributes")
|
||||||
|
mailu_email = _extract_attr(attrs, "mailu_email")
|
||||||
|
if mailu_email:
|
||||||
|
return mailu_email
|
||||||
|
email = user.get("email")
|
||||||
|
if isinstance(email, str) and email.strip():
|
||||||
|
email = email.strip()
|
||||||
|
if email.lower().endswith(f"@{settings.mailu_domain.lower()}"):
|
||||||
|
return email
|
||||||
|
return f"{username}@{settings.mailu_domain}"
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_mail_export(output: str) -> list[tuple[str, str]]:
|
||||||
|
accounts: list[tuple[str, str]] = []
|
||||||
|
account_id = ""
|
||||||
|
for line in output.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
match = re.match(r"^Account\s+(\d+):", line, flags=re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
account_id = match.group(1)
|
||||||
|
continue
|
||||||
|
match = re.match(r"^-\s*E-?mail:\s*(\S+)", line, flags=re.IGNORECASE)
|
||||||
|
if match and account_id:
|
||||||
|
accounts.append((account_id, match.group(1)))
|
||||||
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
|
def display_name(user: dict[str, Any]) -> str:
|
||||||
|
"""Return a human display name from Keycloak first/last name fields."""
|
||||||
|
|
||||||
|
first = user.get("firstName") if isinstance(user.get("firstName"), str) else ""
|
||||||
|
last = user.get("lastName") if isinstance(user.get("lastName"), str) else ""
|
||||||
|
first = first.strip()
|
||||||
|
last = last.strip()
|
||||||
|
if first and last:
|
||||||
|
return f"{first} {last}"
|
||||||
|
return last or first
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NextcloudMailSyncSummary:
|
||||||
|
processed: int
|
||||||
|
created: int
|
||||||
|
updated: int
|
||||||
|
deleted: int
|
||||||
|
skipped: int
|
||||||
|
failures: int
|
||||||
|
detail: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MailSyncCounters:
|
||||||
|
processed: int = 0
|
||||||
|
created: int = 0
|
||||||
|
updated: int = 0
|
||||||
|
deleted: int = 0
|
||||||
|
skipped: int = 0
|
||||||
|
failures: int = 0
|
||||||
|
last_error: str = ""
|
||||||
|
|
||||||
|
def summary(self) -> NextcloudMailSyncSummary:
|
||||||
|
return NextcloudMailSyncSummary(
|
||||||
|
processed=self.processed,
|
||||||
|
created=self.created,
|
||||||
|
updated=self.updated,
|
||||||
|
deleted=self.deleted,
|
||||||
|
skipped=self.skipped,
|
||||||
|
failures=self.failures,
|
||||||
|
detail=self.last_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
def status(self) -> str:
|
||||||
|
return "ok" if self.failures == 0 else "error"
|
||||||
|
|
||||||
|
def record_failure(self, detail: str) -> None:
|
||||||
|
self.failures += 1
|
||||||
|
if detail and not self.last_error:
|
||||||
|
self.last_error = detail
|
||||||
130
ariadne/services/nextcloud_maintenance.py
Normal file
130
ariadne/services/nextcloud_maintenance.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"""Nextcloud maintenance task implementation."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from ..k8s.exec import ExecError
|
||||||
|
from ..k8s.pods import PodSelectionError
|
||||||
|
from ..settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
def _run_shell(service: Any, script: str, check: bool = True) -> None:
|
||||||
|
service._executor.exec(
|
||||||
|
script,
|
||||||
|
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
||||||
|
check=check,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_maintenance(service: Any) -> dict[str, Any]:
|
||||||
|
"""Run theming, app-link, quota, and filesystem maintenance for Nextcloud."""
|
||||||
|
|
||||||
|
if not settings.nextcloud_namespace:
|
||||||
|
raise RuntimeError("nextcloud maintenance not configured")
|
||||||
|
|
||||||
|
try:
|
||||||
|
_run_shell(
|
||||||
|
service,
|
||||||
|
"""
|
||||||
|
set -euo pipefail
|
||||||
|
if [ ! -d /var/www/html/lib ] && [ -d /usr/src/nextcloud/lib ]; then
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
rsync -a --delete --exclude config --exclude data /usr/src/nextcloud/ /var/www/html/
|
||||||
|
else
|
||||||
|
cp -a /usr/src/nextcloud/. /var/www/html/
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
mkdir -p /var/www/html/data
|
||||||
|
chown 33:33 /var/www/html || true
|
||||||
|
chmod 775 /var/www/html || true
|
||||||
|
chown -R 33:33 /var/www/html/apps /var/www/html/custom_apps /var/www/html/data /var/www/html/config 2>/dev/null || true
|
||||||
|
""",
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
service._occ(["config:app:set", "theming", "name", "--value", "Atlas Cloud"])
|
||||||
|
service._occ(["config:app:set", "theming", "slogan", "--value", "Unified access to Atlas services"])
|
||||||
|
theming_url = settings.nextcloud_url or "https://cloud.bstein.dev"
|
||||||
|
service._occ(["config:app:set", "theming", "url", "--value", theming_url])
|
||||||
|
service._occ(["config:app:set", "theming", "color", "--value", "#0f172a"])
|
||||||
|
service._occ(["config:app:set", "theming", "disable-user-theming", "--value", "yes"])
|
||||||
|
|
||||||
|
service._executor.exec(
|
||||||
|
["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", "app:install", "customcss"],
|
||||||
|
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
service._executor.exec(
|
||||||
|
["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", "app:enable", "customcss"],
|
||||||
|
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mail_css = (
|
||||||
|
".mail-message-body, .mail-message-body pre, .mail-message-body code, .mail-message-body table {\n"
|
||||||
|
" font-family: \"Inter\", \"Source Sans 3\", \"Helvetica Neue\", Arial, sans-serif;\n"
|
||||||
|
" font-size: 14px;\n"
|
||||||
|
" line-height: 1.6;\n"
|
||||||
|
" color: var(--color-main-text);\n"
|
||||||
|
"}\n"
|
||||||
|
".mail-message-body pre {\n"
|
||||||
|
" background: rgba(15, 23, 42, 0.06);\n"
|
||||||
|
" padding: 12px;\n"
|
||||||
|
" border-radius: 8px;\n"
|
||||||
|
"}\n"
|
||||||
|
".mail-message-body blockquote {\n"
|
||||||
|
" border-left: 3px solid var(--color-border);\n"
|
||||||
|
" padding-left: 12px;\n"
|
||||||
|
" margin: 8px 0;\n"
|
||||||
|
" color: var(--color-text-lighter);\n"
|
||||||
|
"}\n"
|
||||||
|
".mail-message-body img {\n"
|
||||||
|
" max-width: 100%;\n"
|
||||||
|
" border-radius: 6px;\n"
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
service._occ(["config:app:set", "customcss", "css", "--value", mail_css])
|
||||||
|
service._occ(["config:app:set", "files", "default_quota", "--value", "250 GB"])
|
||||||
|
|
||||||
|
payload = service._external_api("GET", "?format=json")
|
||||||
|
links = payload.get("ocs", {}).get("data", []) if isinstance(payload, dict) else []
|
||||||
|
for link in links:
|
||||||
|
link_id = link.get("id") if isinstance(link, dict) else None
|
||||||
|
if link_id is not None:
|
||||||
|
service._external_api("DELETE", f"/sites/{link_id}?format=json")
|
||||||
|
|
||||||
|
sites = [
|
||||||
|
("Vaultwarden", "https://vault.bstein.dev"),
|
||||||
|
("Jellyfin", "https://stream.bstein.dev"),
|
||||||
|
("Gitea", "https://scm.bstein.dev"),
|
||||||
|
("Jenkins", "https://ci.bstein.dev"),
|
||||||
|
("Harbor", "https://registry.bstein.dev"),
|
||||||
|
("Vault", "https://secret.bstein.dev"),
|
||||||
|
("Jitsi", "https://meet.bstein.dev"),
|
||||||
|
("Grafana", "https://metrics.bstein.dev"),
|
||||||
|
("Chat LLM", "https://chat.ai.bstein.dev"),
|
||||||
|
("Vision", "https://draw.ai.bstein.dev"),
|
||||||
|
("STT/TTS", "https://talk.ai.bstein.dev"),
|
||||||
|
]
|
||||||
|
for name, url in sites:
|
||||||
|
service._external_api(
|
||||||
|
"POST",
|
||||||
|
"/sites?format=json",
|
||||||
|
data={
|
||||||
|
"name": name,
|
||||||
|
"url": url,
|
||||||
|
"lang": "",
|
||||||
|
"type": "link",
|
||||||
|
"device": "",
|
||||||
|
"icon": "",
|
||||||
|
"groups[]": "",
|
||||||
|
"redirect": "1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except (ExecError, PodSelectionError, TimeoutError) as exc:
|
||||||
|
return {"status": "error", "detail": str(exc)}
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
return {"status": "error", "detail": str(exc)}
|
||||||
|
|
||||||
|
return {"status": "ok", "detail": "maintenance complete"}
|
||||||
@ -3,7 +3,6 @@ ariadne/services/cluster_state.py split planned; service orchestration decomposi
|
|||||||
ariadne/app.py split planned; Flask app bootstrap/routes currently co-located
|
ariadne/app.py split planned; Flask app bootstrap/routes currently co-located
|
||||||
ariadne/services/comms.py split planned; comms adapters still consolidated
|
ariadne/services/comms.py split planned; comms adapters still consolidated
|
||||||
ariadne/manager/provisioning.py split planned; provisioning flow modules pending extraction
|
ariadne/manager/provisioning.py split planned; provisioning flow modules pending extraction
|
||||||
ariadne/services/nextcloud.py split planned; provider methods pending partition
|
|
||||||
ariadne/settings.py split planned; settings schema + helpers pending split
|
ariadne/settings.py split planned; settings schema + helpers pending split
|
||||||
ariadne/services/jenkins_workspace_cleanup.py split planned; job orchestration pending extraction
|
ariadne/services/jenkins_workspace_cleanup.py split planned; job orchestration pending extraction
|
||||||
tests/test_provisioning.py test module split planned; broad provisioning coverage retained meanwhile
|
tests/test_provisioning.py test module split planned; broad provisioning coverage retained meanwhile
|
||||||
|
|||||||
|
Loading…
x
Reference in New Issue
Block a user