Compare commits
8 Commits
427c0580c4
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 033e74adda | |||
| ccc66ebd0f | |||
| c660f62120 | |||
| ad74825781 | |||
| f1e0f36cca | |||
| 526ad966c4 | |||
| 99501fb924 | |||
| 2fdc89565c |
@@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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`)
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
Reference in New Issue
Block a user