phoenix-deploy-api: OpenAPI, server, systemd install, env example
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Made-with: Cursor
This commit is contained in:
@@ -28,3 +28,8 @@ PHOENIX_ALERT_WEBHOOK_SECRET=
|
||||
|
||||
# Optional: comma-separated API keys for /api/v1/* (X-API-Key or Bearer)
|
||||
PHOENIX_PARTNER_KEYS=
|
||||
|
||||
# Optional: override path for GET /api/v1/public-sector/programs (else bundled copy, repo config/, or ../config/)
|
||||
PUBLIC_SECTOR_MANIFEST_PATH=
|
||||
# Optional: proxmox repo root on host (manifest = $PHOENIX_REPO_ROOT/config/public-sector-program-manifest.json)
|
||||
PHOENIX_REPO_ROOT=
|
||||
|
||||
@@ -16,9 +16,10 @@ Gitea webhook receiver and deploy endpoint stub for Gitea → Phoenix deployment
|
||||
| GET | /api/v1/health/metrics | Prometheus query proxy (`?query=<PromQL>`) |
|
||||
| GET | /api/v1/health/alerts | Active alerts (optional PROMETHEUS_ALERTS_URL) |
|
||||
| GET | /api/v1/health/summary | Aggregated health for Portal |
|
||||
| GET | /api/v1/public-sector/programs | Public-sector / eIDAS program manifest (JSON; **no API key**) |
|
||||
| GET | /health | Health check |
|
||||
|
||||
All `/api/v1/*` routes accept optional partner API key when `PHOENIX_PARTNER_KEYS` is set (`X-API-Key` or `Authorization: Bearer <key>`).
|
||||
All `/api/v1/*` routes except **`GET /api/v1/public-sector/programs`** accept optional partner API key when `PHOENIX_PARTNER_KEYS` is set (`X-API-Key` or `Authorization: Bearer <key>`).
|
||||
|
||||
## Environment
|
||||
|
||||
@@ -42,6 +43,10 @@ Copy `.env.example` to `.env` and set `GITEA_TOKEN` (and optionally `PHOENIX_DEP
|
||||
| PHOENIX_WEBHOOK_URL | | Outbound webhook URL; POST deploy events with X-Phoenix-Signature |
|
||||
| PHOENIX_WEBHOOK_SECRET | | Secret to sign webhook payloads (HMAC-SHA256) |
|
||||
| PHOENIX_PARTNER_KEYS | | Comma-separated API keys for /api/v1/* (optional) |
|
||||
| PUBLIC_SECTOR_MANIFEST_PATH | | Override JSON path for `/api/v1/public-sector/programs` |
|
||||
| PHOENIX_REPO_ROOT | | Proxmox repo root; loads `config/public-sector-program-manifest.json` if present |
|
||||
|
||||
**Program manifest:** From a full repo checkout, the file is `config/public-sector-program-manifest.json`. `scripts/install-systemd.sh` copies it next to `server.js` on `/opt/phoenix-deploy-api` so the endpoint works without a full tree.
|
||||
|
||||
## Gitea Webhook Configuration
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ tags:
|
||||
- name: Infra
|
||||
- name: VE
|
||||
- name: Health
|
||||
- name: PublicSector
|
||||
- name: System
|
||||
|
||||
paths:
|
||||
@@ -66,6 +67,27 @@ paths:
|
||||
'202': { description: Accepted }
|
||||
'401': { description: Unauthorized }
|
||||
|
||||
/api/v1/public-sector/programs:
|
||||
get:
|
||||
tags: [PublicSector]
|
||||
summary: Public-sector and eIDAS program manifest (JSON)
|
||||
description: |
|
||||
Serves `config/public-sector-program-manifest.json` from the proxmox repo (or `PUBLIC_SECTOR_MANIFEST_PATH`).
|
||||
No API key required. Returns 503 if the file is missing on the host.
|
||||
responses:
|
||||
'200':
|
||||
description: Manifest JSON
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
schemaVersion: { type: string }
|
||||
updated: { type: string }
|
||||
programs: { type: array, items: { type: object } }
|
||||
'500': { description: Invalid JSON or read error }
|
||||
'503': { description: Manifest file not found }
|
||||
|
||||
/api/v1/infra/nodes:
|
||||
get:
|
||||
tags: [Infra]
|
||||
|
||||
@@ -18,6 +18,13 @@ fi
|
||||
echo "Installing Phoenix Deploy API to $TARGET ..."
|
||||
mkdir -p "$TARGET"
|
||||
cp -a "$APP_DIR/server.js" "$APP_DIR/package.json" "$APP_DIR/package-lock.json" "$TARGET/" 2>/dev/null || cp -a "$APP_DIR/server.js" "$APP_DIR/package.json" "$TARGET/"
|
||||
# Program manifest for GET /api/v1/public-sector/programs (server loads from cwd-adjacent copy on /opt)
|
||||
if [[ -f "$REPO_ROOT/config/public-sector-program-manifest.json" ]]; then
|
||||
cp -a "$REPO_ROOT/config/public-sector-program-manifest.json" "$TARGET/public-sector-program-manifest.json"
|
||||
echo "Installed public-sector-program-manifest.json"
|
||||
else
|
||||
echo "WARN: $REPO_ROOT/config/public-sector-program-manifest.json missing — set PUBLIC_SECTOR_MANIFEST_PATH in .env"
|
||||
fi
|
||||
[ -f "$APP_DIR/.env" ] && cp "$APP_DIR/.env" "$TARGET/.env" || [ -f "$APP_DIR/.env.example" ] && cp "$APP_DIR/.env.example" "$TARGET/.env" || true
|
||||
chown -R root:root "$TARGET"
|
||||
cd "$TARGET" && npm install --omit=dev
|
||||
|
||||
@@ -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'})`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user