Compare commits

..

8 Commits

Author SHA1 Message Date
033e74adda KI-AGENT: Matrix-Raumdaten nach Tenant-Import zurücksetzen
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 19s
Build and Push Docker Images / build-frontend (push) Successful in 11s
Build and Push Docker Images / build-website (push) Successful in 11s
Build and Push Docker Images / build-docs (push) Successful in 10s
2026-06-03 15:06:33 +02:00
ccc66ebd0f KI-AGENT: Matrix-Daten beim Tenant-Import neu provisionieren 2026-06-03 10:43:40 +02:00
c660f62120 KI-AGENT: Veraltete Matrix-Raumreferenzen bereinigen 2026-06-03 10:40:00 +02:00
ad74825781 KI-AGENT: Matrix-Kommunikation im Selfhost-Bootstrap provisionieren
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 19s
Build and Push Docker Images / build-frontend (push) Successful in 10s
Build and Push Docker Images / build-website (push) Successful in 11s
Build and Push Docker Images / build-docs (push) Successful in 11s
2026-06-03 10:22:30 +02:00
f1e0f36cca KI-AGENT: Datenbank-URL im Selfhost-Setup absichern 2026-06-03 10:09:43 +02:00
526ad966c4 KI-AGENT: Selfhost-Setup nutzt passende Compose-Datei
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 11s
Build and Push Docker Images / build-frontend (push) Successful in 10s
Build and Push Docker Images / build-website (push) Successful in 11s
Build and Push Docker Images / build-docs (push) Successful in 11s
2026-06-03 10:02:34 +02:00
99501fb924 KI-AGENT: Uninstall für Selfhost-Setup ergänzen
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 12s
Build and Push Docker Images / build-frontend (push) Successful in 11s
Build and Push Docker Images / build-docs (push) Successful in 11s
Build and Push Docker Images / build-website (push) Successful in 11s
2026-06-03 09:55:40 +02:00
2fdc89565c KI-AGENT: Encryption-Key im Selfhost-Setup als Hex erzeugen 2026-06-03 09:53:23 +02:00
7 changed files with 286 additions and 8 deletions

View File

@@ -117,6 +117,7 @@ FEDEO_BOOTSTRAP_ADMIN_FIRST_NAME=Admin
FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=Benutzer FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=Benutzer
FEDEO_BOOTSTRAP_TENANT_NAME=Mein Unternehmen FEDEO_BOOTSTRAP_TENANT_NAME=Mein Unternehmen
FEDEO_BOOTSTRAP_TENANT_SHORT=MEIN FEDEO_BOOTSTRAP_TENANT_SHORT=MEIN
FEDEO_BOOTSTRAP_MATRIX=true
# FEDEO Matrix-Kommunikation # FEDEO Matrix-Kommunikation
# #

View File

@@ -21,6 +21,7 @@ import {
texttemplates, texttemplates,
units, units,
} from "../../db/schema" } from "../../db/schema"
import { matrixService } from "./matrix.service"
const adminPermissions = [ const adminPermissions = [
"mcp.tokens.write", "mcp.tokens.write",
@@ -487,4 +488,19 @@ export async function runBootstrap(server: FastifyInstance) {
await ensureTenantBaseData(server, tenant.id, adminUser.id) await ensureTenantBaseData(server, tenant.id, adminUser.id)
console.log("✅ Bootstrap-Grunddaten geprüft") console.log("✅ Bootstrap-Grunddaten geprüft")
if (process.env.FEDEO_BOOTSTRAP_MATRIX === "true") {
try {
const matrix = matrixService(server)
await matrix.provisionTenantRoom(adminUser.id, tenant.id, {
key: "allgemein",
name: "Allgemeiner Chat",
type: "general",
})
console.log("✅ Bootstrap-Matrix-Kommunikation geprüft")
} catch (err) {
console.error("❌ Bootstrap-Matrix-Kommunikation fehlgeschlagen:", err)
throw err
}
}
} }

View File

@@ -291,6 +291,16 @@ export function matrixService(server: FastifyInstance) {
roomKey: string roomKey: string
) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${serverName()}` ) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${serverName()}`
const matrixIdentifierServerName = (value?: string | null) => {
const match = value?.match(/^[!#@][^:]+:(.+)$/)
return match?.[1] || null
}
const belongsToCurrentMatrixServer = (value?: string | null) => {
const identifierServerName = matrixIdentifierServerName(value)
return !identifierServerName || identifierServerName === serverName()
}
const normalizeTenantRoomOptions = (options: MatrixTenantRoomOptions = {}) => { const normalizeTenantRoomOptions = (options: MatrixTenantRoomOptions = {}) => {
const fallbackName = options.key || options.name || "Allgemeiner Chat" const fallbackName = options.key || options.name || "Allgemeiner Chat"
const key = normalizeMatrixAliasSeed(options.key || fallbackName) const key = normalizeMatrixAliasSeed(options.key || fallbackName)
@@ -836,9 +846,14 @@ export function matrixService(server: FastifyInstance) {
) => { ) => {
const normalizedOptions = normalizeTenantRoomOptions(options) const normalizedOptions = normalizeTenantRoomOptions(options)
const existing = await findTenantRoomMetadata(tenant.id, normalizedOptions.key) const existing = await findTenantRoomMetadata(tenant.id, normalizedOptions.key)
const expectedAlias = tenantRoomAlias(tenant, normalizedOptions.key)
if (existing) { if (existing) {
const hasStaleMatrixRoomId = !belongsToCurrentMatrixServer(existing.matrixRoomId)
const hasStaleMatrixAlias = !belongsToCurrentMatrixServer(existing.matrixAlias)
const shouldUpdate = const shouldUpdate =
hasStaleMatrixRoomId ||
hasStaleMatrixAlias ||
(options.name !== undefined && existing.name !== normalizedOptions.name) || (options.name !== undefined && existing.name !== normalizedOptions.name) ||
(options.topic !== undefined && existing.topic !== normalizedOptions.topic) || (options.topic !== undefined && existing.topic !== normalizedOptions.topic) ||
(options.type !== undefined && existing.type !== normalizedOptions.type) || (options.type !== undefined && existing.type !== normalizedOptions.type) ||
@@ -857,6 +872,9 @@ export function matrixService(server: FastifyInstance) {
entityType: options.entityType !== undefined ? normalizedOptions.entityType : existing.entityType, entityType: options.entityType !== undefined ? normalizedOptions.entityType : existing.entityType,
entityId: options.entityId !== undefined ? normalizedOptions.entityId : existing.entityId, entityId: options.entityId !== undefined ? normalizedOptions.entityId : existing.entityId,
entityUuid: options.entityUuid !== undefined ? normalizedOptions.entityUuid : existing.entityUuid, entityUuid: options.entityUuid !== undefined ? normalizedOptions.entityUuid : existing.entityUuid,
matrixRoomId: hasStaleMatrixRoomId ? null : existing.matrixRoomId,
matrixAlias: hasStaleMatrixAlias ? expectedAlias : existing.matrixAlias,
parentSpaceRoomId: hasStaleMatrixRoomId ? null : existing.parentSpaceRoomId,
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(communicationRooms.id, existing.id)) .where(eq(communicationRooms.id, existing.id))
@@ -876,7 +894,7 @@ export function matrixService(server: FastifyInstance) {
entityType: normalizedOptions.entityType, entityType: normalizedOptions.entityType,
entityId: normalizedOptions.entityId, entityId: normalizedOptions.entityId,
entityUuid: normalizedOptions.entityUuid, entityUuid: normalizedOptions.entityUuid,
matrixAlias: tenantRoomAlias(tenant, normalizedOptions.key), matrixAlias: expectedAlias,
}) })
.returning() .returning()
@@ -960,8 +978,8 @@ export function matrixService(server: FastifyInstance) {
entityUuid: metadata.entityUuid, entityUuid: metadata.entityUuid,
alias, alias,
exists: false, exists: false,
roomId: metadata.matrixRoomId, roomId: belongsToCurrentMatrixServer(metadata.matrixRoomId) ? metadata.matrixRoomId : null,
parentSpaceRoomId: metadata.parentSpaceRoomId, parentSpaceRoomId: belongsToCurrentMatrixServer(metadata.parentSpaceRoomId) ? metadata.parentSpaceRoomId : null,
servers: [], servers: [],
} }
} }
@@ -1141,6 +1159,9 @@ export function matrixService(server: FastifyInstance) {
const ensureServiceUserJoinedRoom = async (room: { roomId?: string | null; alias?: string | null }) => { const ensureServiceUserJoinedRoom = async (room: { roomId?: string | null; alias?: string | null }) => {
const target = room.roomId || room.alias const target = room.roomId || room.alias
if (!target) return { ok: false, status: "missing_room" } if (!target) return { ok: false, status: "missing_room" }
if (!belongsToCurrentMatrixServer(target)) {
return { ok: false, status: "stale_room", roomId: target }
}
const serviceLogin = await ensureServiceAccessToken() const serviceLogin = await ensureServiceAccessToken()

View File

@@ -18,6 +18,7 @@ import { ensureTenantBaseData } from "../modules/bootstrap.service";
import { buildTenantFullExport, importTenantFullExport } from "../utils/tenantFullExport"; import { buildTenantFullExport, importTenantFullExport } from "../utils/tenantFullExport";
import type { TenantFullExport } from "../utils/tenantFullExport"; import type { TenantFullExport } from "../utils/tenantFullExport";
import { buildSystemStatus } from "../modules/system-status.service"; import { buildSystemStatus } from "../modules/system-status.service";
import { matrixService } from "../modules/matrix.service";
export default async function adminRoutes(server: FastifyInstance) { export default async function adminRoutes(server: FastifyInstance) {
const deriveNameFromEmail = (email: string) => { const deriveNameFromEmail = (email: string) => {
@@ -1034,8 +1035,27 @@ export default async function adminRoutes(server: FastifyInstance) {
}); });
} }
let matrixProvisioned = false;
let matrixProvisioningError: string | null = null;
if (process.env.MATRIX_REGISTRATION_SHARED_SECRET) {
try {
const matrix = matrixService(server);
await matrix.provisionTenantRoom(currentUser.id, result.tenantId, {
key: "allgemein",
name: "Allgemeiner Chat",
type: "general",
});
matrixProvisioned = true;
} catch (err: any) {
matrixProvisioningError = err?.message || String(err);
req.log.warn({ err }, "Matrix-Räume konnten nach Tenant-Import nicht neu provisioniert werden");
}
}
return { return {
success: true, success: true,
matrixProvisioned,
matrixProvisioningError,
...result, ...result,
}; };
} catch (err: any) { } catch (err: any) {

View File

@@ -48,6 +48,46 @@ const ENTITY_BANKACCOUNT_PLAIN_FIELDS = {
} }
const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"` const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"`
const matrixServerName = () =>
process.env.MATRIX_SERVER_NAME ||
secrets.MATRIX_SERVER_NAME ||
process.env.DOMAIN ||
"localhost"
const normalizeMatrixLocalpartSeed = (value: string) => {
const normalized = value
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/ä/g, "a")
.replace(/ö/g, "o")
.replace(/ü/g, "u")
.replace(/ß/g, "ss")
.replace(/[^a-z0-9._=-]+/g, "_")
.replace(/_+/g, "_")
.replace(/^[._=-]+|[._=-]+$/g, "")
return normalized || "user"
}
const normalizeMatrixAliasSeed = (value: string) =>
normalizeMatrixLocalpartSeed(value)
.replace(/[.=]/g, "_")
.replace(/_+/g, "_")
const tenantRoomAliasLocalpart = (
tenant: { id: number, short?: string | null, name?: string | null },
roomKey: string
) => {
const tenantSeed = normalizeMatrixAliasSeed(tenant.short || tenant.name || `tenant_${tenant.id}`)
const roomSeed = normalizeMatrixAliasSeed(roomKey)
return `fedeo_${tenantSeed}_${tenant.id}_${roomSeed}`
}
const tenantRoomAlias = (
tenant: { id: number, short?: string | null, name?: string | null },
roomKey: string
) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${matrixServerName()}`
const tableColumns = async (client: any) => { const tableColumns = async (client: any) => {
const result = await client.query(` const result = await client.query(`
@@ -346,6 +386,73 @@ const encryptEntityBankAccountRowsForImport = (exportData: TenantFullExport) =>
} }
} }
const prepareCommunicationRoomsForImport = (exportData: TenantFullExport) => {
const rows = exportData.tables.communication_rooms || []
if (!rows.length) return
const tenantById = new Map((exportData.tables.tenants || []).map((tenant) => [
Number(tenant.id),
{
id: Number(tenant.id),
name: tenant.name,
short: tenant.short,
},
]))
for (const row of rows) {
const tenantId = Number(row.tenant_id)
const tenant = tenantById.get(tenantId)
row.matrix_room_id = null
row.parent_space_room_id = null
if (tenant && row.key) {
row.matrix_alias = tenantRoomAlias(tenant, String(row.key))
} else {
row.matrix_alias = null
}
}
}
const cleanupImportedCommunicationRooms = async (client: any, exportData: TenantFullExport) => {
const rows = exportData.tables.communication_rooms || []
if (!rows.length) return 0
const tenantById = new Map((exportData.tables.tenants || []).map((tenant) => [
Number(tenant.id),
{
id: Number(tenant.id),
name: tenant.name,
short: tenant.short,
},
]))
let cleaned = 0
for (const row of rows) {
const tenantId = Number(row.tenant_id)
const key = String(row.key || "")
const tenant = tenantById.get(tenantId)
if (!tenantId || !key || !tenant) continue
const alias = tenantRoomAlias(tenant, key)
const result = await client.query(
`
update communication_rooms
set matrix_room_id = null,
parent_space_room_id = null,
matrix_alias = $3,
updated_at = now()
where tenant_id = $1 and key = $2
`,
[tenantId, key, alias]
)
cleaned += result.rowCount || 0
}
return cleaned
}
const prepareColumnValue = (value: any, isJsonColumn: boolean) => { const prepareColumnValue = (value: any, isJsonColumn: boolean) => {
if (!isJsonColumn || value === null || typeof value === "undefined") return value if (!isJsonColumn || value === null || typeof value === "undefined") return value
if (typeof value === "string") return value if (typeof value === "string") return value
@@ -465,6 +572,7 @@ export const importTenantFullExport = async (
const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId) const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId)
encryptEntityBankAccountRowsForImport(exportData) encryptEntityBankAccountRowsForImport(exportData)
prepareCommunicationRoomsForImport(exportData)
const client = await pool.connect() const client = await pool.connect()
const importOrder = [ const importOrder = [
"tenants", "tenants",
@@ -516,6 +624,11 @@ export const importTenantFullExport = async (
importedTables.push({ table, rows: count }) importedTables.push({ table, rows: count })
} }
const cleanedCommunicationRooms = await cleanupImportedCommunicationRooms(client, exportData)
if (cleanedCommunicationRooms) {
importedTables.push({ table: "communication_rooms_matrix_reset", rows: cleanedCommunicationRooms })
}
await refreshSequences(client, columnsByTable) await refreshSequences(client, columnsByTable)
await client.query("commit") await client.query("commit")

View File

@@ -91,6 +91,8 @@ services:
condition: service_healthy condition: service_healthy
createbuckets: createbuckets:
condition: service_completed_successfully condition: service_completed_successfully
matrix-synapse:
condition: service_healthy
environment: environment:
NODE_ENV: production NODE_ENV: production
FEDEO_RUN_MIGRATIONS: ${FEDEO_RUN_MIGRATIONS:-true} FEDEO_RUN_MIGRATIONS: ${FEDEO_RUN_MIGRATIONS:-true}
@@ -99,7 +101,7 @@ services:
COOKIE_SECRET: ${COOKIE_SECRET} COOKIE_SECRET: ${COOKIE_SECRET}
JWT_SECRET: ${JWT_SECRET} JWT_SECRET: ${JWT_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY} ENCRYPTION_KEY: ${ENCRYPTION_KEY}
DATABASE_URL: ${DATABASE_URL} DATABASE_URL: ${DATABASE_URL:-postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}}
MAILER_SMTP_HOST: ${MAILER_SMTP_HOST} MAILER_SMTP_HOST: ${MAILER_SMTP_HOST}
MAILER_SMTP_PORT: ${MAILER_SMTP_PORT} MAILER_SMTP_PORT: ${MAILER_SMTP_PORT}
MAILER_SMTP_SSL: ${MAILER_SMTP_SSL} MAILER_SMTP_SSL: ${MAILER_SMTP_SSL}
@@ -134,6 +136,7 @@ services:
FEDEO_BOOTSTRAP_ADMIN_LAST_NAME: ${FEDEO_BOOTSTRAP_ADMIN_LAST_NAME:-Benutzer} FEDEO_BOOTSTRAP_ADMIN_LAST_NAME: ${FEDEO_BOOTSTRAP_ADMIN_LAST_NAME:-Benutzer}
FEDEO_BOOTSTRAP_TENANT_NAME: ${FEDEO_BOOTSTRAP_TENANT_NAME:-FEDEO} FEDEO_BOOTSTRAP_TENANT_NAME: ${FEDEO_BOOTSTRAP_TENANT_NAME:-FEDEO}
FEDEO_BOOTSTRAP_TENANT_SHORT: ${FEDEO_BOOTSTRAP_TENANT_SHORT:-FEDEO} FEDEO_BOOTSTRAP_TENANT_SHORT: ${FEDEO_BOOTSTRAP_TENANT_SHORT:-FEDEO}
FEDEO_BOOTSTRAP_MATRIX: ${FEDEO_BOOTSTRAP_MATRIX:-true}
MATRIX_HOMESERVER_URL: ${MATRIX_HOMESERVER_URL:-http://matrix-synapse:8008} MATRIX_HOMESERVER_URL: ${MATRIX_HOMESERVER_URL:-http://matrix-synapse:8008}
MATRIX_SERVER_NAME: ${MATRIX_SERVER_NAME:-${DOMAIN}} MATRIX_SERVER_NAME: ${MATRIX_SERVER_NAME:-${DOMAIN}}
MATRIX_RTC_HOST: ${MATRIX_RTC_HOST:-${DOMAIN}} MATRIX_RTC_HOST: ${MATRIX_RTC_HOST:-${DOMAIN}}
@@ -301,6 +304,12 @@ services:
exec /start.py exec /start.py
volumes: volumes:
- ./matrix/synapse:/data - ./matrix/synapse:/data
healthcheck:
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8008/_matrix/client/versions', timeout=2)\""]
interval: 10s
timeout: 5s
retries: 30
start_period: 20s
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.fedeo-matrix.rule=Host(`${DOMAIN}`) && PathPrefix(`/_matrix`) - traefik.http.routers.fedeo-matrix.rule=Host(`${DOMAIN}`) && PathPrefix(`/_matrix`)

View File

@@ -4,11 +4,17 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env" ENV_FILE="$ROOT_DIR/.env"
ENV_EXAMPLE="$ROOT_DIR/.env.example" ENV_EXAMPLE="$ROOT_DIR/.env.example"
COMPOSE_FILE="${FEDEO_COMPOSE_FILE:-$ROOT_DIR/docker-compose.yml}" DEFAULT_COMPOSE_FILE="$ROOT_DIR/docker-compose.selfhost.yml"
if [[ ! -f "$DEFAULT_COMPOSE_FILE" ]]; then
DEFAULT_COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
fi
COMPOSE_FILE="${FEDEO_COMPOSE_FILE:-$DEFAULT_COMPOSE_FILE}"
MODE="" MODE=""
START_STACK="ask" START_STACK="ask"
FORCE="false" FORCE="false"
UNINSTALL="false"
PURGE="false"
TTY_INPUT="${FEDEO_TTY_INPUT:-/dev/tty}" TTY_INPUT="${FEDEO_TTY_INPUT:-/dev/tty}"
TTY_FD="" TTY_FD=""
@@ -53,6 +59,8 @@ Nutzung:
bash scripts/selfhost-setup.sh --simple bash scripts/selfhost-setup.sh --simple
bash scripts/selfhost-setup.sh --advanced bash scripts/selfhost-setup.sh --advanced
bash scripts/selfhost-setup.sh --simple --start bash scripts/selfhost-setup.sh --simple --start
bash scripts/selfhost-setup.sh --uninstall
bash scripts/selfhost-setup.sh --uninstall --purge
Optionen: Optionen:
--simple Kurzer Assistent mit lokalen Diensten und sicheren Defaults --simple Kurzer Assistent mit lokalen Diensten und sicheren Defaults
@@ -60,6 +68,8 @@ Optionen:
--start Startet den Stack nach dem Schreiben der Konfiguration --start Startet den Stack nach dem Schreiben der Konfiguration
--no-start Schreibt nur Konfiguration und Verzeichnisse --no-start Schreibt nur Konfiguration und Verzeichnisse
--force Überschreibt eine vorhandene .env ohne Rückfrage --force Überschreibt eine vorhandene .env ohne Rückfrage
--uninstall Stoppt und entfernt den Docker-Compose-Stack
--purge Entfernt beim Uninstall zusätzlich .env und lokale Datenverzeichnisse
USAGE USAGE
} }
@@ -80,6 +90,12 @@ while [[ $# -gt 0 ]]; do
--force) --force)
FORCE="true" FORCE="true"
;; ;;
--uninstall)
UNINSTALL="true"
;;
--purge)
PURGE="true"
;;
-h|--help) -h|--help)
usage usage
exit 0 exit 0
@@ -108,6 +124,14 @@ random_secret() {
fi fi
} }
random_hex_secret() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 32 | tr -d '\n'
else
LC_ALL=C tr -dc 'a-f0-9' </dev/urandom | head -c 64
fi
}
env_quote() { env_quote() {
local value="$1" local value="$1"
value="${value//\'/\'\\\'\'}" value="${value//\'/\'\\\'\'}"
@@ -219,7 +243,7 @@ FEDEO Selfhost Setup
Dieses Script führt dich durch die lokale Betriebsstruktur: Dieses Script führt dich durch die lokale Betriebsstruktur:
$ROOT_DIR/ $ROOT_DIR/
docker-compose.yml Docker Stack für FEDEO, Traefik, PostgreSQL, MinIO, Matrix und Monitoring $(basename "$COMPOSE_FILE") Docker Stack für FEDEO, Traefik, PostgreSQL, MinIO, Matrix und Monitoring
.env Zielkonfiguration, wird von diesem Script geschrieben .env Zielkonfiguration, wird von diesem Script geschrieben
postgres/ persistente FEDEO-Datenbank postgres/ persistente FEDEO-Datenbank
minio/ lokaler S3-kompatibler Dateispeicher minio/ lokaler S3-kompatibler Dateispeicher
@@ -346,6 +370,7 @@ FEDEO_BOOTSTRAP_ADMIN_FIRST_NAME=$(env_quote "$admin_first_name")
FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=$(env_quote "$admin_last_name") FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=$(env_quote "$admin_last_name")
FEDEO_BOOTSTRAP_TENANT_NAME=$(env_quote "$tenant_name") FEDEO_BOOTSTRAP_TENANT_NAME=$(env_quote "$tenant_name")
FEDEO_BOOTSTRAP_TENANT_SHORT=$(env_quote "$tenant_short") FEDEO_BOOTSTRAP_TENANT_SHORT=$(env_quote "$tenant_short")
FEDEO_BOOTSTRAP_MATRIX=$(env_quote "true")
MATRIX_SERVER_NAME=$(env_quote "$domain") MATRIX_SERVER_NAME=$(env_quote "$domain")
MATRIX_POSTGRES_DB=$(env_quote "synapse") MATRIX_POSTGRES_DB=$(env_quote "synapse")
@@ -377,7 +402,80 @@ prepare_directories() {
chmod 600 "$ROOT_DIR/traefik/letsencrypt/acme.json" chmod 600 "$ROOT_DIR/traefik/letsencrypt/acme.json"
} }
uninstall_stack() {
need_file "$COMPOSE_FILE"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Keine .env gefunden. Docker Compose wird mit Standardwerten ausgeführt."
fi
echo
echo "FEDEO Selfhost Uninstall"
echo "Compose-Datei: $COMPOSE_FILE"
echo
if [[ "$FORCE" != "true" ]]; then
if ! yes_no "Docker-Compose-Stack stoppen und Container/Netzwerke entfernen?" "n"; then
echo "Abgebrochen."
exit 0
fi
fi
if [[ -f "$ENV_FILE" ]]; then
compose_stack down --remove-orphans
else
compose -f "$COMPOSE_FILE" down --remove-orphans
fi
if [[ "$PURGE" != "true" ]]; then
echo
echo "Uninstall abgeschlossen. Lokale Daten und .env wurden behalten."
echo "Für vollständiges Entfernen erneut mit --uninstall --purge ausführen."
return
fi
echo
echo "Achtung: --purge entfernt lokale Datenbanken, Dateien, Matrix-Daten, Zertifikate und .env."
echo "Betroffene Pfade:"
echo " $ENV_FILE"
echo " $ROOT_DIR/postgres"
echo " $ROOT_DIR/minio"
echo " $ROOT_DIR/matrix/postgres"
echo " $ROOT_DIR/matrix/synapse"
echo " $ROOT_DIR/traefik/letsencrypt"
echo " $ROOT_DIR/traefik/logs"
if [[ "$FORCE" != "true" ]]; then
if ! yes_no "Diese lokalen Daten endgültig löschen?" "n"; then
echo "Daten wurden behalten."
return
fi
fi
rm -rf \
"$ENV_FILE" \
"$ROOT_DIR/postgres" \
"$ROOT_DIR/minio" \
"$ROOT_DIR/matrix/postgres" \
"$ROOT_DIR/matrix/synapse" \
"$ROOT_DIR/traefik/letsencrypt" \
"$ROOT_DIR/traefik/logs"
echo
echo "Uninstall inklusive lokaler Daten abgeschlossen."
}
main() { main() {
if [[ "$UNINSTALL" == "true" ]]; then
uninstall_stack
return
fi
if [[ "$PURGE" == "true" ]]; then
echo "--purge kann nur zusammen mit --uninstall verwendet werden." >&2
exit 1
fi
need_file "$COMPOSE_FILE" need_file "$COMPOSE_FILE"
need_file "$ENV_EXAMPLE" need_file "$ENV_EXAMPLE"
@@ -409,11 +507,11 @@ main() {
echo "Secrets werden automatisch erzeugt." echo "Secrets werden automatisch erzeugt."
local db_password minio_password cookie_secret jwt_secret encryption_key m2m_key local db_password minio_password cookie_secret jwt_secret encryption_key m2m_key
local matrix_db_password matrix_turn_secret matrix_registration_secret livekit_secret local matrix_db_password matrix_turn_secret matrix_registration_secret livekit_secret
db_password="$(random_secret)" db_password="$(random_hex_secret)"
minio_password="$(random_secret)" minio_password="$(random_secret)"
cookie_secret="$(random_secret)" cookie_secret="$(random_secret)"
jwt_secret="$(random_secret)" jwt_secret="$(random_secret)"
encryption_key="$(random_secret)" encryption_key="$(random_hex_secret)"
m2m_key="$(random_secret)" m2m_key="$(random_secret)"
matrix_db_password="$(random_secret)" matrix_db_password="$(random_secret)"
matrix_turn_secret="$(random_secret)" matrix_turn_secret="$(random_secret)"