fix(selfhost): pin MinIO images and add application health checks
This commit is contained in:
@@ -98,10 +98,6 @@ async function main() {
|
|||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get('/health', async (req, res) => {
|
|
||||||
return res.send({ status: 'ok' })
|
|
||||||
})
|
|
||||||
|
|
||||||
//Plugin nur auf bestimmten Routes
|
//Plugin nur auf bestimmten Routes
|
||||||
await app.register(queryConfigPlugin, {
|
await app.register(queryConfigPlugin, {
|
||||||
routes: ['/api/resource/:resource/paginated']
|
routes: ['/api/resource/:resource/paginated']
|
||||||
|
|||||||
@@ -2,6 +2,16 @@ import { FastifyInstance } from "fastify";
|
|||||||
import { getBuildInfo } from "../utils/buildInfo";
|
import { getBuildInfo } from "../utils/buildInfo";
|
||||||
|
|
||||||
export default async function routes(server: FastifyInstance) {
|
export default async function routes(server: FastifyInstance) {
|
||||||
|
server.get("/health", async (_request, reply) => {
|
||||||
|
reply.header("Cache-Control", "no-store");
|
||||||
|
try {
|
||||||
|
await server.db.execute("SELECT 1");
|
||||||
|
return { status: "ok", ...getBuildInfo() };
|
||||||
|
} catch {
|
||||||
|
return reply.code(503).send({ status: "unavailable" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.get("/version", async (_request, reply) => {
|
server.get("/version", async (_request, reply) => {
|
||||||
reply.header("Cache-Control", "no-store");
|
reply.header("Cache-Control", "no-store");
|
||||||
return getBuildInfo();
|
return getBuildInfo();
|
||||||
|
|||||||
@@ -3,6 +3,28 @@ import { test } from 'node:test';
|
|||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import healthRoutes from '../src/routes/health';
|
import healthRoutes from '../src/routes/health';
|
||||||
|
|
||||||
|
test('health checks the database and reports a failure without leaking connection details', async () => {
|
||||||
|
const app = Fastify();
|
||||||
|
let available = true;
|
||||||
|
app.decorate('db', { execute: async () => {
|
||||||
|
if (!available) throw new Error('postgres://secret@database');
|
||||||
|
return { rows: [{ result: 1 }] };
|
||||||
|
} } as any);
|
||||||
|
try {
|
||||||
|
await app.register(healthRoutes);
|
||||||
|
const healthy = await app.inject('/health');
|
||||||
|
assert.equal(healthy.statusCode, 200);
|
||||||
|
assert.equal(healthy.json().status, 'ok');
|
||||||
|
assert.equal(healthy.headers['cache-control'], 'no-store');
|
||||||
|
available = false;
|
||||||
|
const unhealthy = await app.inject('/health');
|
||||||
|
assert.equal(unhealthy.statusCode, 503);
|
||||||
|
assert.deepEqual(unhealthy.json(), { status: 'unavailable' });
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('version endpoint reports image metadata without a database or authentication', async () => {
|
test('version endpoint reports image metadata without a database or authentication', async () => {
|
||||||
const previousVersion = process.env.FEDEO_VERSION;
|
const previousVersion = process.env.FEDEO_VERSION;
|
||||||
const previousRevision = process.env.FEDEO_REVISION;
|
const previousRevision = process.env.FEDEO_REVISION;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ services:
|
|||||||
- internal
|
- internal
|
||||||
|
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:latest
|
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
|
||||||
container_name: fedeo-minio
|
container_name: fedeo-minio
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
@@ -66,13 +66,13 @@ services:
|
|||||||
- internal
|
- internal
|
||||||
|
|
||||||
createbuckets:
|
createbuckets:
|
||||||
image: minio/mc:latest
|
image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727
|
||||||
container_name: fedeo-minio-init
|
container_name: fedeo-minio-init
|
||||||
depends_on:
|
depends_on:
|
||||||
minio:
|
minio:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
entrypoint: >
|
entrypoint: >
|
||||||
/bin/sh -c "
|
/bin/sh -ec "
|
||||||
mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD};
|
mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD};
|
||||||
mc mb --ignore-existing local/${MINIO_BUCKET};
|
mc mb --ignore-existing local/${MINIO_BUCKET};
|
||||||
mc anonymous set private local/${MINIO_BUCKET};
|
mc anonymous set private local/${MINIO_BUCKET};
|
||||||
@@ -143,6 +143,12 @@ services:
|
|||||||
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}
|
||||||
NODE_EXPORTER_URL: ${NODE_EXPORTER_URL:-http://node-exporter:9100}
|
NODE_EXPORTER_URL: ${NODE_EXPORTER_URL:-http://node-exporter:9100}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:' + (process.env.PORT || '3100') + '/health', {signal: AbortSignal.timeout(4000)}).then(r => {if (!r.ok) process.exit(1)}).catch(() => process.exit(1))"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 6
|
||||||
|
start_period: 120s
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.http.routers.fedeo-backend.rule=Host(`${DOMAIN}`) && PathPrefix(`/backend`)
|
- traefik.http.routers.fedeo-backend.rule=Host(`${DOMAIN}`) && PathPrefix(`/backend`)
|
||||||
@@ -178,11 +184,18 @@ services:
|
|||||||
container_name: fedeo-frontend
|
container_name: fedeo-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
NUXT_PUBLIC_API_BASE: https://${DOMAIN}/backend
|
NUXT_PUBLIC_API_BASE: https://${DOMAIN}/backend
|
||||||
NUXT_PUBLIC_PDF_LICENSE: ${NUXT_PUBLIC_PDF_LICENSE}
|
NUXT_PUBLIC_PDF_LICENSE: ${NUXT_PUBLIC_PDF_LICENSE}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/health', {signal: AbortSignal.timeout(4000)}).then(r => {if (!r.ok) process.exit(1)}).catch(() => process.exit(1))"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 6
|
||||||
|
start_period: 30s
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.http.routers.fedeo-frontend.rule=Host(`${DOMAIN}`)
|
- traefik.http.routers.fedeo-frontend.rule=Host(`${DOMAIN}`)
|
||||||
|
|||||||
10
frontend/server/api/health.get.ts
Normal file
10
frontend/server/api/health.get.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default defineEventHandler((event) => {
|
||||||
|
setHeader(event, 'Cache-Control', 'no-store')
|
||||||
|
const config = useRuntimeConfig(event)
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
product: 'FEDEO',
|
||||||
|
version: config.public.fedeoVersion,
|
||||||
|
revision: config.public.fedeoRevision,
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user