Add temporary danger restart mode
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 12s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 12s
This commit is contained in:
54
server.js
54
server.js
@@ -63,6 +63,7 @@ const serviceUrls = [
|
||||
let jwksCache = null;
|
||||
let jwksCacheUntil = 0;
|
||||
const manualWatchtowerRuns = new Map();
|
||||
const dangerTokens = new Map();
|
||||
|
||||
function json(res, status, payload) {
|
||||
res.writeHead(status, {
|
||||
@@ -198,9 +199,11 @@ function normalizeContainerName(name) {
|
||||
return String(name || '').replace(/^\//, '').trim();
|
||||
}
|
||||
|
||||
function canRestartContainer(container) {
|
||||
function canRestartContainer(container, danger = false) {
|
||||
const name = normalizeContainerName(container.name || container.Names?.[0]);
|
||||
if (!name || RESTART_BLOCKLIST.has(name)) return false;
|
||||
if (!name) return false;
|
||||
if (danger) return container.state === 'running' || container.State === 'running';
|
||||
if (RESTART_BLOCKLIST.has(name)) return false;
|
||||
if (name.endsWith('_db') || name.includes('-db')) return false;
|
||||
return container.state === 'running' || container.State === 'running';
|
||||
}
|
||||
@@ -214,6 +217,32 @@ function restartBlockReason(container) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function pruneDangerTokens() {
|
||||
const now = Date.now();
|
||||
for (const [token, expiresAt] of dangerTokens.entries()) {
|
||||
if (expiresAt <= now) dangerTokens.delete(token);
|
||||
}
|
||||
}
|
||||
|
||||
function createDangerToken() {
|
||||
pruneDangerTokens();
|
||||
const token = crypto.randomBytes(32).toString('base64url');
|
||||
const expiresAt = Date.now() + 2 * 60 * 1000;
|
||||
dangerTokens.set(token, expiresAt);
|
||||
return {
|
||||
token,
|
||||
expiresAt: new Date(expiresAt).toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
function verifyDangerToken(req) {
|
||||
pruneDangerTokens();
|
||||
const token = req.headers['x-mischlabs-danger-token'];
|
||||
if (!token || Array.isArray(token)) return false;
|
||||
const expiresAt = dangerTokens.get(token);
|
||||
return Boolean(expiresAt && expiresAt > Date.now());
|
||||
}
|
||||
|
||||
function dockerLogs(container, tail = 160) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const endpoint = `/containers/${encodeURIComponent(container)}/logs?stdout=1&stderr=1&tail=${tail}`;
|
||||
@@ -271,7 +300,7 @@ async function getContainers() {
|
||||
})).sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
async function restartContainer(containerName) {
|
||||
async function restartContainer(containerName, danger = false) {
|
||||
const name = normalizeContainerName(containerName);
|
||||
if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) {
|
||||
const error = new Error('Ungueltiger Containername.');
|
||||
@@ -285,7 +314,7 @@ async function restartContainer(containerName) {
|
||||
state: inspect.State?.Status
|
||||
};
|
||||
|
||||
if (!canRestartContainer(container)) {
|
||||
if (!canRestartContainer(container, danger)) {
|
||||
const error = new Error(`Container ${name} ist fuer Neustarts ueber das Dashboard gesperrt.`);
|
||||
error.statusCode = 403;
|
||||
throw error;
|
||||
@@ -645,7 +674,7 @@ const server = http.createServer(async (req, res) => {
|
||||
|
||||
try {
|
||||
await verifyAdminRequest(req);
|
||||
json(res, 200, await restartContainer(decodeURIComponent(restartMatch[1])));
|
||||
json(res, 200, await restartContainer(decodeURIComponent(restartMatch[1]), verifyDangerToken(req)));
|
||||
} catch (error) {
|
||||
const status = error.statusCode || (['missing_token', 'invalid_token', 'expired_token', 'not_allowed'].includes(error.message) ? 401 : 500);
|
||||
json(res, status, { error: error.message });
|
||||
@@ -653,6 +682,21 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url?.match(/^\/api\/danger\/unlock(?:[?#].*)?$/)) {
|
||||
if (req.method !== 'POST') {
|
||||
json(res, 405, { error: 'Method not allowed' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyAdminRequest(req);
|
||||
json(res, 200, createDangerToken());
|
||||
} catch (error) {
|
||||
json(res, error.statusCode || 500, { error: error.message });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url?.match(/^\/api\/watchtower\/run-once(?:[?#].*)?$/)) {
|
||||
if (req.method !== 'POST') {
|
||||
json(res, 405, { error: 'Method not allowed' });
|
||||
|
||||
Reference in New Issue
Block a user