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 {initS3} from "./utils/s3";
|
||||||
import { runBootstrap } from "./modules/bootstrap.service";
|
import { runBootstrap } from "./modules/bootstrap.service";
|
||||||
import { startMatrixPushWorker } from "./modules/matrix-push-worker.service";
|
import { startMatrixPushWorker } from "./modules/matrix-push-worker.service";
|
||||||
|
import { startCentralServicesHeartbeat } from "./modules/central-services-heartbeat.service";
|
||||||
|
|
||||||
|
|
||||||
//Services
|
//Services
|
||||||
@@ -87,6 +88,7 @@ async function main() {
|
|||||||
await app.register(servicesPlugin);
|
await app.register(servicesPlugin);
|
||||||
await runBootstrap(app);
|
await runBootstrap(app);
|
||||||
startMatrixPushWorker(app);
|
startMatrixPushWorker(app);
|
||||||
|
startCentralServicesHeartbeat(app);
|
||||||
|
|
||||||
app.addHook('preHandler', (req, reply, done) => {
|
app.addHook('preHandler', (req, reply, done) => {
|
||||||
console.log(req.method)
|
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>
|
data?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CentralServicesHeartbeatInput = {
|
||||||
|
fedeoVersion?: string
|
||||||
|
baseUrl?: string
|
||||||
|
capabilities?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
function configured() {
|
function configured() {
|
||||||
return Boolean(secrets.PUSH_SERVER_URL && secrets.PUSH_SERVER_INSTANCE_ID && secrets.PUSH_SERVER_SECRET)
|
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 = {
|
export const centralServicesClient = {
|
||||||
configured,
|
configured,
|
||||||
|
|
||||||
|
heartbeat(input: CentralServicesHeartbeatInput) {
|
||||||
|
return requestPushServer<{
|
||||||
|
status: string
|
||||||
|
instanceId: string
|
||||||
|
payloadMode: string
|
||||||
|
capabilities: string[]
|
||||||
|
}>("POST", "/v1/instances/heartbeat", input)
|
||||||
|
},
|
||||||
|
|
||||||
aiChatCompletions(payload: Record<string, unknown>) {
|
aiChatCompletions(payload: Record<string, unknown>) {
|
||||||
return requestPushServer<any>("POST", "/v1/services/ai/chat-completions", payload)
|
return requestPushServer<any>("POST", "/v1/services/ai/chat-completions", payload)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ export let secrets = {
|
|||||||
PUSH_SERVER_INSTANCE_ID?: string
|
PUSH_SERVER_INSTANCE_ID?: string
|
||||||
PUSH_SERVER_SECRET?: string
|
PUSH_SERVER_SECRET?: string
|
||||||
FEDEO_CENTRAL_SERVICES_ENABLED?: boolean
|
FEDEO_CENTRAL_SERVICES_ENABLED?: boolean
|
||||||
|
FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS?: number
|
||||||
|
FEDEO_VERSION?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretKeys = [
|
const secretKeys = [
|
||||||
@@ -102,9 +104,11 @@ const secretKeys = [
|
|||||||
"PUSH_SERVER_INSTANCE_ID",
|
"PUSH_SERVER_INSTANCE_ID",
|
||||||
"PUSH_SERVER_SECRET",
|
"PUSH_SERVER_SECRET",
|
||||||
"FEDEO_CENTRAL_SERVICES_ENABLED",
|
"FEDEO_CENTRAL_SERVICES_ENABLED",
|
||||||
|
"FEDEO_CENTRAL_HEARTBEAT_INTERVAL_MS",
|
||||||
|
"FEDEO_VERSION",
|
||||||
] as const
|
] 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"])
|
const booleanKeys = new Set(["DOKUBOX_IMAP_SECURE", "FEDEO_CENTRAL_SERVICES_ENABLED"])
|
||||||
|
|
||||||
function normalizeEnvValue(key: string, value: string) {
|
function normalizeEnvValue(key: string, value: string) {
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ services:
|
|||||||
PUSH_SERVER_URL: ${PUSH_SERVER_URL:-}
|
PUSH_SERVER_URL: ${PUSH_SERVER_URL:-}
|
||||||
PUSH_SERVER_INSTANCE_ID: ${PUSH_SERVER_INSTANCE_ID:-}
|
PUSH_SERVER_INSTANCE_ID: ${PUSH_SERVER_INSTANCE_ID:-}
|
||||||
PUSH_SERVER_SECRET: ${PUSH_SERVER_SECRET:-}
|
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}
|
STIRLING_API_KEY: ${STIRLING_API_KEY}
|
||||||
FEDEO_BOOTSTRAP_ADMIN_EMAIL: ${FEDEO_BOOTSTRAP_ADMIN_EMAIL:-}
|
FEDEO_BOOTSTRAP_ADMIN_EMAIL: ${FEDEO_BOOTSTRAP_ADMIN_EMAIL:-}
|
||||||
FEDEO_BOOTSTRAP_ADMIN_PASSWORD: ${FEDEO_BOOTSTRAP_ADMIN_PASSWORD:-}
|
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_URL=https://services.fedeo.de
|
||||||
PUSH_SERVER_INSTANCE_ID=inst_...
|
PUSH_SERVER_INSTANCE_ID=inst_...
|
||||||
PUSH_SERVER_SECRET=fps_...
|
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.
|
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
|
## Minimaler Push-Auftrag
|
||||||
|
|||||||
Reference in New Issue
Block a user