feat: central services heartbeat senden
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 26s
Build and Push Docker Images / build-frontend (push) Successful in 16s
Build and Push Docker Images / build-website (push) Successful in 16s
Build and Push Docker Images / build-central-services-api (push) Successful in 21s
Build and Push Docker Images / build-central-services-admin (push) Successful in 41s
Build and Push Docker Images / build-docs (push) Successful in 16s
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 26s
Build and Push Docker Images / build-frontend (push) Successful in 16s
Build and Push Docker Images / build-website (push) Successful in 16s
Build and Push Docker Images / build-central-services-api (push) Successful in 21s
Build and Push Docker Images / build-central-services-admin (push) Successful in 41s
Build and Push Docker Images / build-docs (push) Successful in 16s
This commit is contained in:
@@ -61,6 +61,7 @@ import {initMailer} from "./utils/mailer"
|
||||
import {initS3} from "./utils/s3";
|
||||
import { runBootstrap } from "./modules/bootstrap.service";
|
||||
import { startMatrixPushWorker } from "./modules/matrix-push-worker.service";
|
||||
import { startCentralServicesHeartbeat } from "./modules/central-services-heartbeat.service";
|
||||
|
||||
|
||||
//Services
|
||||
@@ -87,6 +88,7 @@ async function main() {
|
||||
await app.register(servicesPlugin);
|
||||
await runBootstrap(app);
|
||||
startMatrixPushWorker(app);
|
||||
startCentralServicesHeartbeat(app);
|
||||
|
||||
app.addHook('preHandler', (req, reply, done) => {
|
||||
console.log(req.method)
|
||||
|
||||
55
backend/src/modules/central-services-heartbeat.service.ts
Normal file
55
backend/src/modules/central-services-heartbeat.service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { FastifyInstance } from "fastify"
|
||||
|
||||
import { centralServicesClient } from "./push-server.client"
|
||||
import { secrets } from "../utils/secrets"
|
||||
|
||||
const DEFAULT_HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000
|
||||
|
||||
function instanceBaseUrl() {
|
||||
if (!secrets.API_BASE_URL) return undefined
|
||||
try {
|
||||
return new URL(secrets.API_BASE_URL).origin
|
||||
} catch {
|
||||
console.warn("⚠️ Central Services Heartbeat: API_BASE_URL ist keine gültige URL")
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function startCentralServicesHeartbeat(server: FastifyInstance) {
|
||||
if (!centralServicesClient.configured()) {
|
||||
console.log("ℹ️ Central Services Heartbeat deaktiviert: Instanzzugangsdaten fehlen")
|
||||
return
|
||||
}
|
||||
|
||||
const configuredInterval = Number(secrets.FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS)
|
||||
const intervalMs = Number.isFinite(configuredInterval) && configuredInterval >= 30_000
|
||||
? configuredInterval
|
||||
: DEFAULT_HEARTBEAT_INTERVAL_MS
|
||||
let timer: NodeJS.Timeout | undefined
|
||||
let stopped = false
|
||||
|
||||
const sendHeartbeat = async () => {
|
||||
try {
|
||||
const response = await centralServicesClient.heartbeat({
|
||||
fedeoVersion: secrets.FEDEO_VERSION || "selfhost",
|
||||
baseUrl: instanceBaseUrl(),
|
||||
})
|
||||
console.log(`✅ Central Services Heartbeat: ${response.instanceId} (${response.status})`)
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Central Services Heartbeat fehlgeschlagen: ${error?.message || error}`)
|
||||
} finally {
|
||||
if (!stopped) {
|
||||
timer = setTimeout(() => void sendHeartbeat(), intervalMs)
|
||||
timer.unref()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server.addHook("onClose", async () => {
|
||||
stopped = true
|
||||
if (timer) clearTimeout(timer)
|
||||
})
|
||||
|
||||
console.log(`ℹ️ Central Services Heartbeat gestartet (${Math.round(intervalMs / 1000)} Sekunden)`)
|
||||
void sendHeartbeat()
|
||||
}
|
||||
@@ -30,6 +30,12 @@ export type SendPushServerMessageInput = {
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type CentralServicesHeartbeatInput = {
|
||||
fedeoVersion?: string
|
||||
baseUrl?: string
|
||||
capabilities?: string[]
|
||||
}
|
||||
|
||||
function configured() {
|
||||
return Boolean(secrets.PUSH_SERVER_URL && secrets.PUSH_SERVER_INSTANCE_ID && secrets.PUSH_SERVER_SECRET)
|
||||
}
|
||||
@@ -105,6 +111,15 @@ export const pushServerClient = {
|
||||
export const centralServicesClient = {
|
||||
configured,
|
||||
|
||||
heartbeat(input: CentralServicesHeartbeatInput) {
|
||||
return requestPushServer<{
|
||||
status: string
|
||||
instanceId: string
|
||||
payloadMode: string
|
||||
capabilities: string[]
|
||||
}>("POST", "/v1/instances/heartbeat", input)
|
||||
},
|
||||
|
||||
aiChatCompletions(payload: Record<string, unknown>) {
|
||||
return requestPushServer<any>("POST", "/v1/services/ai/chat-completions", payload)
|
||||
},
|
||||
|
||||
@@ -54,6 +54,8 @@ export let secrets = {
|
||||
PUSH_SERVER_INSTANCE_ID?: string
|
||||
PUSH_SERVER_SECRET?: string
|
||||
FEDEO_CENTRAL_SERVICES_ENABLED?: boolean
|
||||
FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS?: number
|
||||
FEDEO_VERSION?: string
|
||||
}
|
||||
|
||||
const secretKeys = [
|
||||
@@ -102,9 +104,11 @@ const secretKeys = [
|
||||
"PUSH_SERVER_INSTANCE_ID",
|
||||
"PUSH_SERVER_SECRET",
|
||||
"FEDEO_CENTRAL_SERVICES_ENABLED",
|
||||
"FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS",
|
||||
"FEDEO_VERSION",
|
||||
] as const
|
||||
|
||||
const numberKeys = new Set(["PORT", "MAILER_SMTP_PORT", "DOKUBOX_IMAP_PORT"])
|
||||
const numberKeys = new Set(["PORT", "MAILER_SMTP_PORT", "DOKUBOX_IMAP_PORT", "FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS"])
|
||||
const booleanKeys = new Set(["DOKUBOX_IMAP_SECURE", "FEDEO_CENTRAL_SERVICES_ENABLED"])
|
||||
|
||||
function normalizeEnvValue(key: string, value: string) {
|
||||
|
||||
@@ -133,6 +133,8 @@ services:
|
||||
PUSH_SERVER_URL: ${PUSH_SERVER_URL:-}
|
||||
PUSH_SERVER_INSTANCE_ID: ${PUSH_SERVER_INSTANCE_ID:-}
|
||||
PUSH_SERVER_SECRET: ${PUSH_SERVER_SECRET:-}
|
||||
FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS: ${FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS:-300000}
|
||||
FEDEO_VERSION: ${FEDEO_VERSION:-selfhost-dev}
|
||||
STIRLING_API_KEY: ${STIRLING_API_KEY}
|
||||
FEDEO_BOOTSTRAP_ADMIN_EMAIL: ${FEDEO_BOOTSTRAP_ADMIN_EMAIL:-}
|
||||
FEDEO_BOOTSTRAP_ADMIN_PASSWORD: ${FEDEO_BOOTSTRAP_ADMIN_PASSWORD:-}
|
||||
|
||||
@@ -13,8 +13,11 @@ FEDEO_CENTRAL_SERVICES_ENABLED=true
|
||||
PUSH_SERVER_URL=https://services.fedeo.de
|
||||
PUSH_SERVER_INSTANCE_ID=inst_...
|
||||
PUSH_SERVER_SECRET=fps_...
|
||||
FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS=300000
|
||||
```
|
||||
|
||||
Das Selfhost-Backend sendet direkt beim Start und anschließend standardmäßig alle fünf Minuten einen signierten Heartbeat. Im Backend-Log erscheinen erfolgreiche Meldungen als `Central Services Heartbeat: inst_... (active)`; Verbindungs-, Signatur- oder Freischaltungsfehler werden mit ihrer zentralen Fehlermeldung protokolliert. Werte unter 30 Sekunden werden aus Sicherheitsgründen ignoriert.
|
||||
|
||||
Wenn `FEDEO_CENTRAL_SERVICES_ENABLED=false` bleibt, verwendet FEDEO wie bisher direkt konfigurierte OpenAI- und GoCardless-Zugangsdaten. Damit bleibt ein vollständig unabhängiger Betrieb möglich.
|
||||
|
||||
## Minimaler Push-Auftrag
|
||||
|
||||
Reference in New Issue
Block a user