phoenix-deploy-api: OpenAPI, server, systemd install, env example
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-27 18:50:54 -07:00
parent d38581f04a
commit 92d854a31c
5 changed files with 87 additions and 3 deletions

View File

@@ -9,6 +9,7 @@
* GET /api/v1/infra/storage — Storage pools (Proxmox or stub)
* GET /api/v1/ve/vms — List VMs/CTs (Proxmox or stub)
* GET /api/v1/ve/vms/:node/:vmid/status — VM/CT status
* GET /api/v1/public-sector/programs — Public-sector / eIDAS program manifest (JSON)
* GET /health — Health check
*
* Env: PORT, GITEA_URL, GITEA_TOKEN, PHOENIX_DEPLOY_SECRET
@@ -18,7 +19,7 @@
import crypto from 'crypto';
import https from 'https';
import path from 'path';
import { readFileSync } from 'fs';
import { readFileSync, existsSync } from 'fs';
import { fileURLToPath } from 'url';
import express from 'express';
@@ -42,6 +43,26 @@ const PHOENIX_WEBHOOK_URL = process.env.PHOENIX_WEBHOOK_URL || '';
const PHOENIX_WEBHOOK_SECRET = process.env.PHOENIX_WEBHOOK_SECRET || '';
const PARTNER_KEYS = (process.env.PHOENIX_PARTNER_KEYS || '').split(',').map((k) => k.trim()).filter(Boolean);
/**
* Manifest resolution order:
* 1) PUBLIC_SECTOR_MANIFEST_PATH (explicit file)
* 2) public-sector-program-manifest.json next to server.js (systemd install copies this)
* 3) PHOENIX_REPO_ROOT or PROXMOX_REPO_PATH + config/public-sector-program-manifest.json
* 4) ../config/... (running from phoenix-deploy-api inside full proxmox clone)
*/
function resolvePublicSectorManifestPath() {
const override = (process.env.PUBLIC_SECTOR_MANIFEST_PATH || '').trim();
if (override && existsSync(override)) return override;
const bundled = path.join(__dirname, 'public-sector-program-manifest.json');
if (existsSync(bundled)) return bundled;
const repoRoot = (process.env.PHOENIX_REPO_ROOT || process.env.PROXMOX_REPO_PATH || '').trim().replace(/\/$/, '');
if (repoRoot) {
const fromRepo = path.join(repoRoot, 'config', 'public-sector-program-manifest.json');
if (existsSync(fromRepo)) return fromRepo;
}
return path.join(__dirname, '..', 'config', 'public-sector-program-manifest.json');
}
const httpsAgent = new https.Agent({ rejectUnauthorized: process.env.PROXMOX_TLS_VERIFY !== '0' });
async function proxmoxRequest(endpoint, method = 'GET', body = null) {
@@ -207,6 +228,28 @@ app.post('/api/deploy', async (req, res) => {
}
});
/**
* GET /api/v1/public-sector/programs — Program registry for operators / Phoenix UI (non-secret).
* Registered before partnerKeyMiddleware so callers do not need X-API-Key.
*/
app.get('/api/v1/public-sector/programs', (req, res) => {
const manifestPath = resolvePublicSectorManifestPath();
try {
if (!existsSync(manifestPath)) {
return res.status(503).json({
error: 'Manifest not found',
path: manifestPath,
hint: 'Set PUBLIC_SECTOR_MANIFEST_PATH or deploy repo with config/public-sector-program-manifest.json next to phoenix-deploy-api/',
});
}
const raw = readFileSync(manifestPath, 'utf8');
const data = JSON.parse(raw);
res.type('application/json').json(data);
} catch (err) {
res.status(500).json({ error: err.message, path: manifestPath });
}
});
app.use('/api/v1', partnerKeyMiddleware);
/**
@@ -431,5 +474,7 @@ app.listen(PORT, () => {
if (!GITEA_TOKEN) console.warn('GITEA_TOKEN not set — commit status updates disabled');
if (!hasProxmox) console.warn('PROXMOX_* not set — Infra/VE API returns stub data');
if (PHOENIX_WEBHOOK_URL) console.log('Outbound webhook enabled:', PHOENIX_WEBHOOK_URL);
if (PARTNER_KEYS.length > 0) console.log('Partner API key auth enabled for /api/v1/*');
if (PARTNER_KEYS.length > 0) console.log('Partner API key auth enabled for /api/v1/* (except GET /api/v1/public-sector/programs)');
const mpath = resolvePublicSectorManifestPath();
console.log(`Public-sector manifest: ${mpath} (${existsSync(mpath) ? 'ok' : 'missing'})`);
});