Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/backend/auth/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async function authenticateAndCreateJWT(

console.log("DB Status for User:", userId, status);

ctx.response.headers.set("Access-Control-Allow-Origin", "*");


if (status.matchedCount == 1 || status.upsertedId !== undefined) {
const id_jwt = await createJWT(provider, userId);
Expand All @@ -94,7 +94,6 @@ async function authenticateAndCreateJWT(
}

async function handleJwtAuthentication(ctx: Context) {
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
if (!ctx.request.hasBody) {
ctx.throw(415);
}
Expand Down
80 changes: 65 additions & 15 deletions src/backend/health-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import {
type TimeStep,
type TimeRange,
} from "./utils/container-health.ts";
import { restartContainer, getRestartCount } from "./utils/auto-restart.ts";
import { restartContainer, stopContainer, getRestartCount, getStopCount, validateContainerName } from "./utils/auto-restart.ts";
import { getMonitorStatus, triggerHealthCheck } from "./health-monitor.ts";
import { checkJWT } from "./utils/jwt.ts";

const TIME_RANGE_PRESETS: Record<TimeStep, TimeRange> = {
'1s': { step: '1s', duration: '5m' },
'15s': { step: '15s', duration: '15m' },
'1m': { step: '1m', duration: '1h' },
'5m': { step: '5m', duration: '6h' },
'1h': { step: '1h', duration: '24h' },
'1d': { step: '1d', duration: '7d' },
'1s': { step: '1s', duration: '5m' },
'15s': { step: '15s', duration: '15m' },
'1m': { step: '1m', duration: '1h' },
'5m': { step: '5m', duration: '6h' },
'1h': { step: '1h', duration: '24h' },
'1d': { step: '1d', duration: '7d' },
};


Expand All @@ -31,7 +31,7 @@ export async function getContainerHealth(ctx: Context): Promise<void> {

const summary = await getHealthSummary();

ctx.response.headers.set("Access-Control-Allow-Origin", "*");

ctx.response.body = {
total: summary.total,
healthy: summary.healthy,
Expand All @@ -44,6 +44,7 @@ export async function getContainerHealth(ctx: Context): Promise<void> {
memoryPercent: Math.round(c.memoryPercent * 100) / 100,
memoryUsageMB: Math.round(c.memoryUsage / (1024 * 1024)),
restartCount: getRestartCount(c.name),
stopCount: getStopCount(c.name),
isHealthy: !isUnhealthy(c),
lastUpdated: c.lastUpdated.toISOString(),
})),
Expand All @@ -67,7 +68,7 @@ export async function getContainerMetrics(ctx: Context): Promise<void> {

const history = await getContainerHistory(subdomain, range);

ctx.response.headers.set("Access-Control-Allow-Origin", "*");

ctx.response.body = {
subdomain,
step: range.step,
Expand Down Expand Up @@ -97,7 +98,7 @@ export async function getHealthDashboard(ctx: Context): Promise<void> {
const summary = await getHealthSummary();
const monitorStatus = getMonitorStatus();

ctx.response.headers.set("Access-Control-Allow-Origin", "*");

ctx.response.body = {
overview: {
total: summary.total,
Expand Down Expand Up @@ -126,6 +127,55 @@ export async function getHealthDashboard(ctx: Context): Promise<void> {

export async function restartContainerHandler(ctx: Context): Promise<void> {
const subdomain = ctx.params.subdomain;
let safeSubdomain = "";
try {
safeSubdomain = validateContainerName(subdomain);
} catch {
ctx.throw(400, "Invalid container identifier");
}

const body = await ctx.request.body().value;
let document;
try {
document = typeof body === 'string' ? JSON.parse(body) : body;
} catch {
document = body;
}

const author = document?.author;
const token = document?.token;
const provider = document?.provider;

if (author !== await checkJWT(provider, token)) {
ctx.throw(401);
}

try {
await restartContainer(safeSubdomain);


ctx.response.body = {
status: "success",
message: `Container ${safeSubdomain} restart initiated`,
};
} catch (error) {
console.error(`Failed to restart container ${safeSubdomain}`, error);
ctx.response.status = 500;
ctx.response.body = {
status: "error",
message: `Failed to restart ${safeSubdomain}`,
};
}
}

export async function stopContainerHandler(ctx: Context): Promise<void> {
const subdomain = ctx.params.subdomain;
let safeSubdomain = "";
try {
safeSubdomain = validateContainerName(subdomain);
} catch {
ctx.throw(400, "Invalid container identifier");
}

const body = await ctx.request.body().value;
let document;
Expand All @@ -144,18 +194,19 @@ export async function restartContainerHandler(ctx: Context): Promise<void> {
}

try {
await restartContainer(subdomain);
await stopContainer(safeSubdomain);

Comment thread
Annonnymmousss marked this conversation as resolved.

ctx.response.headers.set("Access-Control-Allow-Origin", "*");
ctx.response.body = {
status: "success",
message: `Container ${subdomain} restart initiated`,
message: `Container ${safeSubdomain} stop initiated`,
};
} catch (error) {
console.error(`Failed to stop container ${safeSubdomain}`, error);
ctx.response.status = 500;
ctx.response.body = {
status: "error",
message: `Failed to restart ${subdomain}: ${error}`,
message: `Failed to stop ${safeSubdomain}`,
};
}
}
Expand All @@ -181,7 +232,6 @@ export async function triggerHealthCheckHandler(ctx: Context): Promise<void> {

await triggerHealthCheck();

ctx.response.headers.set("Access-Control-Allow-Origin", "*");
ctx.response.body = {
status: "success",
message: "Health check triggered",
Expand Down
6 changes: 3 additions & 3 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function getSubdomains(ctx: Context) {
ctx.throw(401);
}
const data = await getMaps(author, ADMIN_LIST!);
ctx.response.headers.set("Access-Control-Allow-Origin", "*");

ctx.response.body = data.documents;
}

Expand Down Expand Up @@ -44,7 +44,7 @@ async function addSubdomain(ctx: Context) {
ctx.throw(401);
}
const success: boolean = await addMaps(document);
ctx.response.headers.set("Access-Control-Allow-Origin", "*");


if (success) {
await addScript(
Expand Down Expand Up @@ -93,7 +93,7 @@ async function deleteSubdomain(ctx: Context) {
"info",
);
}
ctx.response.headers.set("Access-Control-Allow-Origin", "*");

ctx.response.body = data;
}

Expand Down
4 changes: 3 additions & 1 deletion src/backend/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getContainerMetrics,
getHealthDashboard,
restartContainerHandler,
stopContainerHandler,
triggerHealthCheckHandler,
} from "./health-api.ts";
import { startHealthMonitor } from "./health-monitor.ts";
Expand Down Expand Up @@ -76,9 +77,10 @@ router
.get("/health/summary", (ctx) => getHealthDashboard(ctx))
.get("/health/:subdomain/metrics", (ctx) => getContainerMetrics(ctx))
.post("/health/:subdomain/restart", (ctx) => restartContainerHandler(ctx))
.post("/health/:subdomain/stop", (ctx) => stopContainerHandler(ctx))
.post("/health/check", (ctx) => triggerHealthCheckHandler(ctx));

app.use(oakCors());
app.use(oakCors({ origin: frontend }));
app.use(router.routes());
app.use(router.allowedMethods());

Expand Down
32 changes: 32 additions & 0 deletions src/backend/shell_scripts/restart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash

# This script takes in 1 command line argument (the container name)

id -u
if [ "$#" -ne 1 ]; then
echo "Usage: $0 container_name"
exit 1
fi

arg1=$1

if [[ ! "$arg1" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
echo "Error: Invalid container name '$arg1'. Allowed characters: letters, digits, '.', '-', '_'." >&2
exit 1
fi

echo "Restarting... $arg1"

sudo docker restart -- "$arg1"

# Re-enable nginx routing (check for .conf suffix)
if [ ! -L "/etc/nginx/sites-enabled/$arg1.conf" ] && [ ! -f "/etc/nginx/sites-enabled/$arg1.conf" ] && [ -f "/etc/nginx/sites-available/$arg1.conf" ]; then
sudo ln -s -- "/etc/nginx/sites-available/$arg1.conf" "/etc/nginx/sites-enabled/$arg1.conf"
fi

# Re-enable nginx routing (check for no suffix)
if [ ! -L "/etc/nginx/sites-enabled/$arg1" ] && [ ! -f "/etc/nginx/sites-enabled/$arg1" ] && [ -f "/etc/nginx/sites-available/$arg1" ]; then
sudo ln -s -- "/etc/nginx/sites-available/$arg1" "/etc/nginx/sites-enabled/$arg1"
fi

sudo systemctl reload nginx
31 changes: 31 additions & 0 deletions src/backend/shell_scripts/stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash

# This script takes in 1 command line argument (the container name)

id -u
if [ "$#" -ne 1 ]; then
echo "Usage: $0 container_name"
exit 1
fi

arg1=$1

if [[ ! "$arg1" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
echo "Error: Invalid container name '$arg1'. Allowed characters: letters, digits, '.', '-', '_'." >&2
exit 1
fi

echo "Stopping... $arg1"

sudo docker stop -- "$arg1"

# Disable nginx routing for this domain so it doesn't return 502
if [ -L "/etc/nginx/sites-enabled/$arg1.conf" ] || [ -f "/etc/nginx/sites-enabled/$arg1.conf" ]; then
sudo rm -- "/etc/nginx/sites-enabled/$arg1.conf"
fi

if [ -L "/etc/nginx/sites-enabled/$arg1" ] || [ -f "/etc/nginx/sites-enabled/$arg1" ]; then
sudo rm -- "/etc/nginx/sites-enabled/$arg1"
fi

sudo systemctl reload nginx
Loading