Manifestbasierter Production-Updater 3.3, Modulnavigation, optionales Produktbild Studio, Listing-Studio-Aktionsleiste sowie plattformübergreifende Windows-/Linux-Release-Gates.
133 lines
8.1 KiB
JavaScript
133 lines
8.1 KiB
JavaScript
import { objectSchema } from '../../kernel/input-schema.mjs';
|
|
import { PlatformError } from '../../kernel/errors.mjs';
|
|
import { getDeploymentService } from './index.mjs';
|
|
|
|
const settingsSchema = objectSchema({
|
|
provider: { type: 'string', enum: ['none', 'coolify'] },
|
|
coolify_url: { type: 'string', maxLength: 1000 },
|
|
resource_uuid: { type: 'string', maxLength: 120 },
|
|
trigger_mode: { type: 'string', enum: ['webhook', 'api'] },
|
|
deploy_method: { type: 'string', enum: ['GET', 'POST'] },
|
|
deploy_endpoint: { type: 'string', maxLength: 500 },
|
|
deployment_status_endpoint: { type: 'string', maxLength: 500 },
|
|
deployment_timeout_seconds: { type: 'number', integer: true, min: 60, max: 7200 },
|
|
health_poll_seconds: { type: 'number', integer: true, min: 5, max: 300 },
|
|
github_repository: { type: 'string', maxLength: 200 },
|
|
github_branch: { type: 'string', maxLength: 120 },
|
|
ghcr_image: { type: 'string', maxLength: 300 },
|
|
update_channel: { type: 'string', enum: ['stable', 'beta'] },
|
|
backup_before_deploy: { type: 'boolean' },
|
|
backup_uploads: { type: 'boolean' },
|
|
api_token: { type: 'string', maxLength: 16384, trim: false },
|
|
deploy_webhook_url: { type: 'string', maxLength: 2000, trim: false },
|
|
});
|
|
|
|
const triggerSchema = objectSchema({
|
|
force: { type: 'boolean' },
|
|
confirmation: { type: 'string', required: true, maxLength: 20 },
|
|
reason: { type: 'string', maxLength: 120 },
|
|
idempotency_key: { type: 'string', maxLength: 160 },
|
|
target_version: { type: 'string', maxLength: 40 },
|
|
});
|
|
|
|
const confirmSchema = objectSchema({
|
|
confirmation: { type: 'string', required: true, maxLength: 20 },
|
|
outcome: { type: 'string', required: true, enum: ['running', 'succeeded', 'failed'] },
|
|
message: { type: 'string', maxLength: 500 },
|
|
});
|
|
|
|
function requireRun(run) {
|
|
if (!run) throw new PlatformError('Deployment-Auftrag wurde nicht gefunden.', {
|
|
code: 'DEPLOYMENT_RUN_NOT_FOUND', status: 404, expose: true,
|
|
});
|
|
return run;
|
|
}
|
|
|
|
export function registerDeploymentRoutes({ app, routes, deps }) {
|
|
routes.register(app, {
|
|
id: 'deployments.status.read', method: 'GET', path: '/api/admin/deployment', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.status.read', rateLimit: 'administration', csrf: false, stability: 'stable',
|
|
handler: async (_req, res) => res.json(await getDeploymentService().status()),
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.settings.update', method: 'PUT', path: '/api/admin/deployment', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.settings.updated', rateLimit: 'administration', csrf: true, stability: 'stable', input: settingsSchema,
|
|
handler: async (req, res) => {
|
|
const result = getDeploymentService().update(req.validatedBody || {});
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.settings.updated', 'deployment', 'coolify', null, deps.getClientIp(req));
|
|
res.json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.connection.test', method: 'POST', path: '/api/admin/deployment/test', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.connection.tested', rateLimit: 'administration', csrf: true, stability: 'stable',
|
|
handler: async (req, res) => {
|
|
const result = await getDeploymentService().testConnection();
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.connection.tested', 'deployment', 'coolify', JSON.stringify({ ok: result.ok }), deps.getClientIp(req));
|
|
res.json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.updates.check', method: 'POST', path: '/api/admin/deployment/updates/check', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.updates.checked', rateLimit: 'administration', csrf: true, stability: 'stable',
|
|
handler: async (req, res) => {
|
|
const result = await getDeploymentService().checkUpdates();
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.updates.checked', 'deployment', 'manifest', JSON.stringify({ update_available: result.update_available || false }), deps.getClientIp(req));
|
|
res.json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.trigger', method: 'POST', path: '/api/admin/deployment/trigger', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.triggered', rateLimit: 'administration', csrf: true, stability: 'stable', input: triggerSchema,
|
|
handler: async (req, res) => {
|
|
const idempotencyKey = req.get('Idempotency-Key') || req.validatedBody?.idempotency_key || '';
|
|
const result = await getDeploymentService().trigger(req.validatedBody || {}, { userId: req.user.id, idempotencyKey });
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.triggered', 'deployment', result.id, JSON.stringify({ backup_id: result.backup_id, branch: result.github_branch, target_version: result.target_version, reused: result.reused }), deps.getClientIp(req));
|
|
res.status(result.reused ? 200 : 202).json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.runs.list', method: 'GET', path: '/api/admin/deployment/runs', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.runs.read', rateLimit: 'administration', csrf: false, stability: 'stable',
|
|
handler: async (req, res) => res.json({ runs: getDeploymentService().listRuns(Number(req.query.limit) || 20) }),
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.runs.read', method: 'GET', path: '/api/admin/deployment/runs/:id', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.run.read', rateLimit: 'administration', csrf: false, stability: 'stable',
|
|
handler: async (req, res) => res.json(requireRun(getDeploymentService().getRun(req.params.id))),
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.runs.refresh', method: 'POST', path: '/api/admin/deployment/runs/:id/refresh', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.run.refreshed', rateLimit: 'administration', csrf: true, stability: 'stable',
|
|
handler: async (req, res) => {
|
|
requireRun(getDeploymentService().getRun(req.params.id));
|
|
const result = await getDeploymentService().refreshRun(req.params.id);
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.run.refreshed', 'deployment', req.params.id, JSON.stringify({ state: result.state }), deps.getClientIp(req));
|
|
res.json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.runs.confirm', method: 'POST', path: '/api/admin/deployment/runs/:id/confirm', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.run.confirmed', rateLimit: 'administration', csrf: true, stability: 'stable', input: confirmSchema,
|
|
handler: async (req, res) => {
|
|
const result = await getDeploymentService().confirmExternal(req.params.id, req.validatedBody || {});
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.run.confirmed', 'deployment', req.params.id, JSON.stringify({ outcome: req.validatedBody?.outcome, state: result.state }), deps.getClientIp(req));
|
|
res.json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.production.check.run', method: 'POST', path: '/api/admin/deployment/production-check', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.production.checked', rateLimit: 'administration', csrf: true, stability: 'stable',
|
|
handler: async (req, res) => {
|
|
const result = await getDeploymentService().runProductionCheck({ userId: req.user.id });
|
|
deps.auditLog(req.user.id, req.user.email, 'deployment.production.checked', 'deployment', result.id, JSON.stringify({ overall_status: result.overall_status, failed_count: result.failed_count }), deps.getClientIp(req));
|
|
res.json(result);
|
|
},
|
|
});
|
|
routes.register(app, {
|
|
id: 'deployments.production.check.read', method: 'GET', path: '/api/admin/deployment/production-check', owner: 'vendoo.deployments',
|
|
policy: 'updates.manage', audit: 'deployments.production.check.read', rateLimit: 'administration', csrf: false, stability: 'stable',
|
|
handler: async (_req, res) => res.json({ check: getDeploymentService().latestProductionCheck() }),
|
|
});
|
|
}
|