Clean up manual Watchtower containers
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s
This commit is contained in:
@@ -81,6 +81,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/admin.js?v=7" defer></script>
|
<script src="/admin.js?v=8" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
3
admin.js
3
admin.js
@@ -469,8 +469,7 @@ async function runWatchtowerOnce() {
|
|||||||
|
|
||||||
async function pollWatchtowerRun(runId, attempt = 0) {
|
async function pollWatchtowerRun(runId, attempt = 0) {
|
||||||
try {
|
try {
|
||||||
const cleanup = attempt > 0 ? '&cleanup=1' : '';
|
const response = await fetch(`/api/watchtower/runs/${encodeURIComponent(runId)}`, {
|
||||||
const response = await fetch(`/api/watchtower/runs/${encodeURIComponent(runId)}?${cleanup}`, {
|
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${tokenSet.id_token}`
|
Authorization: `Bearer ${tokenSet.id_token}`
|
||||||
|
|||||||
81
server.js
81
server.js
@@ -62,6 +62,7 @@ const serviceUrls = [
|
|||||||
|
|
||||||
let jwksCache = null;
|
let jwksCache = null;
|
||||||
let jwksCacheUntil = 0;
|
let jwksCacheUntil = 0;
|
||||||
|
const manualWatchtowerRuns = new Map();
|
||||||
|
|
||||||
function json(res, status, payload) {
|
function json(res, status, payload) {
|
||||||
res.writeHead(status, {
|
res.writeHead(status, {
|
||||||
@@ -288,6 +289,8 @@ async function restartContainer(containerName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runWatchtowerOnce() {
|
async function runWatchtowerOnce() {
|
||||||
|
cleanupManualWatchtowerContainers().catch(() => {});
|
||||||
|
|
||||||
const name = `mischlabs-watchtower-run-once-${Date.now()}`;
|
const name = `mischlabs-watchtower-run-once-${Date.now()}`;
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
Image: WATCHTOWER_RUN_IMAGE,
|
Image: WATCHTOWER_RUN_IMAGE,
|
||||||
@@ -315,20 +318,54 @@ async function runWatchtowerOnce() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await dockerRequest(`/containers/${created.Id}/start`, { method: 'POST' });
|
await dockerRequest(`/containers/${created.Id}/start`, { method: 'POST' });
|
||||||
return {
|
const payload = {
|
||||||
id: created.Id.slice(0, 12),
|
id: created.Id.slice(0, 12),
|
||||||
fullId: created.Id,
|
fullId: created.Id,
|
||||||
name,
|
name,
|
||||||
image: WATCHTOWER_RUN_IMAGE,
|
image: WATCHTOWER_RUN_IMAGE,
|
||||||
startedAt: new Date().toISOString()
|
state: 'running',
|
||||||
|
running: true,
|
||||||
|
exitCode: null,
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
finishedAt: null,
|
||||||
|
session: null,
|
||||||
|
tail: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
manualWatchtowerRuns.set(created.Id, payload);
|
||||||
|
monitorWatchtowerRun(created.Id).catch((error) => {
|
||||||
|
manualWatchtowerRuns.set(created.Id, {
|
||||||
|
...payload,
|
||||||
|
state: 'error',
|
||||||
|
running: false,
|
||||||
|
exitCode: 1,
|
||||||
|
finishedAt: new Date().toISOString(),
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeContainer(id) {
|
async function removeContainer(id) {
|
||||||
await dockerRequest(`/containers/${encodeURIComponent(id)}?force=1`, { method: 'DELETE' });
|
await dockerRequest(`/containers/${encodeURIComponent(id)}?force=1`, { method: 'DELETE' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWatchtowerRun(runId, cleanup = false) {
|
async function waitContainer(id) {
|
||||||
|
return dockerRequest(`/containers/${encodeURIComponent(id)}/wait`, { method: 'POST' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupManualWatchtowerContainers() {
|
||||||
|
const containers = await dockerRequest('/containers/json?all=1');
|
||||||
|
await Promise.all((containers || [])
|
||||||
|
.filter((container) => (
|
||||||
|
container.Labels?.['com.mischlabs.role'] === 'manual-watchtower-run'
|
||||||
|
&& container.State !== 'running'
|
||||||
|
))
|
||||||
|
.map((container) => removeContainer(container.Id).catch(() => {})));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readWatchtowerRunContainer(runId) {
|
||||||
const id = normalizeContainerName(runId);
|
const id = normalizeContainerName(runId);
|
||||||
if (!id || !/^[a-fA-F0-9]{12,64}$/.test(id)) {
|
if (!id || !/^[a-fA-F0-9]{12,64}$/.test(id)) {
|
||||||
const error = new Error('Ungueltige Watchtower-Run-ID.');
|
const error = new Error('Ungueltige Watchtower-Run-ID.');
|
||||||
@@ -363,13 +400,34 @@ async function getWatchtowerRun(runId, cleanup = false) {
|
|||||||
session: parsed,
|
session: parsed,
|
||||||
tail: Array.isArray(logs) ? logs.slice(-20) : logs
|
tail: Array.isArray(logs) ? logs.slice(-20) : logs
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cleanup && !payload.running) {
|
|
||||||
removeContainer(inspect.Id).catch(() => {});
|
|
||||||
payload.cleanedUp = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return payload;
|
async function monitorWatchtowerRun(runId) {
|
||||||
|
await waitContainer(runId).catch(() => null);
|
||||||
|
|
||||||
|
const payload = await readWatchtowerRunContainer(runId);
|
||||||
|
manualWatchtowerRuns.set(runId, payload);
|
||||||
|
await removeContainer(runId).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWatchtowerRun(runId) {
|
||||||
|
const id = normalizeContainerName(runId);
|
||||||
|
if (!id || !/^[a-fA-F0-9]{12,64}$/.test(id)) {
|
||||||
|
const error = new Error('Ungueltige Watchtower-Run-ID.');
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manualWatchtowerRuns.has(id)) {
|
||||||
|
return manualWatchtowerRuns.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await readWatchtowerRunContainer(id);
|
||||||
|
} catch (error) {
|
||||||
|
error.statusCode = error.statusCode || 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDisks() {
|
async function getDisks() {
|
||||||
@@ -607,9 +665,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await verifyAdminRequest(req);
|
await verifyAdminRequest(req);
|
||||||
const requestUrl = new URL(req.url, `http://${req.headers.host}`);
|
json(res, 200, await getWatchtowerRun(decodeURIComponent(watchtowerRunMatch[1])));
|
||||||
const cleanup = requestUrl.searchParams.get('cleanup') === '1';
|
|
||||||
json(res, 200, await getWatchtowerRun(decodeURIComponent(watchtowerRunMatch[1]), cleanup));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
json(res, error.statusCode || 500, { error: error.message });
|
json(res, error.statusCode || 500, { error: error.message });
|
||||||
}
|
}
|
||||||
@@ -621,4 +677,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`MischLabs dashboard listening on ${PORT}`);
|
console.log(`MischLabs dashboard listening on ${PORT}`);
|
||||||
|
cleanupManualWatchtowerContainers().catch((error) => {
|
||||||
|
console.warn(`Manual Watchtower cleanup failed: ${error.message}`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user