Skip to content

Commit 84502cf

Browse files
authored
feat: Stopping and restart of same container (#61)
<img width="375" height="89" alt="image" src="https://github.com/user-attachments/assets/7b09f247-aea9-4644-8385-312c6f9aa6b4" />
2 parents 437c1d8 + f4bba32 commit 84502cf

9 files changed

Lines changed: 342 additions & 51 deletions

File tree

src/backend/auth/github.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async function authenticateAndCreateJWT(
7777

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

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

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

9696
async function handleJwtAuthentication(ctx: Context) {
97-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
9897
if (!ctx.request.hasBody) {
9998
ctx.throw(415);
10099
}

src/backend/health-api.ts

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ import {
66
type TimeStep,
77
type TimeRange,
88
} from "./utils/container-health.ts";
9-
import { restartContainer, getRestartCount } from "./utils/auto-restart.ts";
9+
import { restartContainer, stopContainer, getRestartCount, getStopCount, validateContainerName } from "./utils/auto-restart.ts";
1010
import { getMonitorStatus, triggerHealthCheck } from "./health-monitor.ts";
1111
import { checkJWT } from "./utils/jwt.ts";
1212

1313
const TIME_RANGE_PRESETS: Record<TimeStep, TimeRange> = {
14-
'1s': { step: '1s', duration: '5m' },
15-
'15s': { step: '15s', duration: '15m' },
16-
'1m': { step: '1m', duration: '1h' },
17-
'5m': { step: '5m', duration: '6h' },
18-
'1h': { step: '1h', duration: '24h' },
19-
'1d': { step: '1d', duration: '7d' },
14+
'1s': { step: '1s', duration: '5m' },
15+
'15s': { step: '15s', duration: '15m' },
16+
'1m': { step: '1m', duration: '1h' },
17+
'5m': { step: '5m', duration: '6h' },
18+
'1h': { step: '1h', duration: '24h' },
19+
'1d': { step: '1d', duration: '7d' },
2020
};
2121

2222

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

3232
const summary = await getHealthSummary();
3333

34-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
34+
3535
ctx.response.body = {
3636
total: summary.total,
3737
healthy: summary.healthy,
@@ -44,6 +44,7 @@ export async function getContainerHealth(ctx: Context): Promise<void> {
4444
memoryPercent: Math.round(c.memoryPercent * 100) / 100,
4545
memoryUsageMB: Math.round(c.memoryUsage / (1024 * 1024)),
4646
restartCount: getRestartCount(c.name),
47+
stopCount: getStopCount(c.name),
4748
isHealthy: !isUnhealthy(c),
4849
lastUpdated: c.lastUpdated.toISOString(),
4950
})),
@@ -67,7 +68,7 @@ export async function getContainerMetrics(ctx: Context): Promise<void> {
6768

6869
const history = await getContainerHistory(subdomain, range);
6970

70-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
71+
7172
ctx.response.body = {
7273
subdomain,
7374
step: range.step,
@@ -97,7 +98,7 @@ export async function getHealthDashboard(ctx: Context): Promise<void> {
9798
const summary = await getHealthSummary();
9899
const monitorStatus = getMonitorStatus();
99100

100-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
101+
101102
ctx.response.body = {
102103
overview: {
103104
total: summary.total,
@@ -126,6 +127,55 @@ export async function getHealthDashboard(ctx: Context): Promise<void> {
126127

127128
export async function restartContainerHandler(ctx: Context): Promise<void> {
128129
const subdomain = ctx.params.subdomain;
130+
let safeSubdomain = "";
131+
try {
132+
safeSubdomain = validateContainerName(subdomain);
133+
} catch {
134+
ctx.throw(400, "Invalid container identifier");
135+
}
136+
137+
const body = await ctx.request.body().value;
138+
let document;
139+
try {
140+
document = typeof body === 'string' ? JSON.parse(body) : body;
141+
} catch {
142+
document = body;
143+
}
144+
145+
const author = document?.author;
146+
const token = document?.token;
147+
const provider = document?.provider;
148+
149+
if (author !== await checkJWT(provider, token)) {
150+
ctx.throw(401);
151+
}
152+
153+
try {
154+
await restartContainer(safeSubdomain);
155+
156+
157+
ctx.response.body = {
158+
status: "success",
159+
message: `Container ${safeSubdomain} restart initiated`,
160+
};
161+
} catch (error) {
162+
console.error(`Failed to restart container ${safeSubdomain}`, error);
163+
ctx.response.status = 500;
164+
ctx.response.body = {
165+
status: "error",
166+
message: `Failed to restart ${safeSubdomain}`,
167+
};
168+
}
169+
}
170+
171+
export async function stopContainerHandler(ctx: Context): Promise<void> {
172+
const subdomain = ctx.params.subdomain;
173+
let safeSubdomain = "";
174+
try {
175+
safeSubdomain = validateContainerName(subdomain);
176+
} catch {
177+
ctx.throw(400, "Invalid container identifier");
178+
}
129179

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

146196
try {
147-
await restartContainer(subdomain);
197+
await stopContainer(safeSubdomain);
198+
148199

149-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
150200
ctx.response.body = {
151201
status: "success",
152-
message: `Container ${subdomain} restart initiated`,
202+
message: `Container ${safeSubdomain} stop initiated`,
153203
};
154204
} catch (error) {
205+
console.error(`Failed to stop container ${safeSubdomain}`, error);
155206
ctx.response.status = 500;
156207
ctx.response.body = {
157208
status: "error",
158-
message: `Failed to restart ${subdomain}: ${error}`,
209+
message: `Failed to stop ${safeSubdomain}`,
159210
};
160211
}
161212
}
@@ -181,7 +232,6 @@ export async function triggerHealthCheckHandler(ctx: Context): Promise<void> {
181232

182233
await triggerHealthCheck();
183234

184-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
185235
ctx.response.body = {
186236
status: "success",
187237
message: "Health check triggered",

src/backend/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ async function getSubdomains(ctx: Context) {
1313
ctx.throw(401);
1414
}
1515
const data = await getMaps(author, ADMIN_LIST!);
16-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
16+
1717
ctx.response.body = data.documents;
1818
}
1919

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

4949
if (success) {
5050
await addScript(
@@ -93,7 +93,7 @@ async function deleteSubdomain(ctx: Context) {
9393
"info",
9494
);
9595
}
96-
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
96+
9797
ctx.response.body = data;
9898
}
9999

src/backend/server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getContainerMetrics,
2020
getHealthDashboard,
2121
restartContainerHandler,
22+
stopContainerHandler,
2223
triggerHealthCheckHandler,
2324
} from "./health-api.ts";
2425
import { startHealthMonitor } from "./health-monitor.ts";
@@ -76,9 +77,10 @@ router
7677
.get("/health/summary", (ctx) => getHealthDashboard(ctx))
7778
.get("/health/:subdomain/metrics", (ctx) => getContainerMetrics(ctx))
7879
.post("/health/:subdomain/restart", (ctx) => restartContainerHandler(ctx))
80+
.post("/health/:subdomain/stop", (ctx) => stopContainerHandler(ctx))
7981
.post("/health/check", (ctx) => triggerHealthCheckHandler(ctx));
8082

81-
app.use(oakCors());
83+
app.use(oakCors({ origin: frontend }));
8284
app.use(router.routes());
8385
app.use(router.allowedMethods());
8486

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
3+
# This script takes in 1 command line argument (the container name)
4+
5+
id -u
6+
if [ "$#" -ne 1 ]; then
7+
echo "Usage: $0 container_name"
8+
exit 1
9+
fi
10+
11+
arg1=$1
12+
13+
if [[ ! "$arg1" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
14+
echo "Error: Invalid container name '$arg1'. Allowed characters: letters, digits, '.', '-', '_'." >&2
15+
exit 1
16+
fi
17+
18+
echo "Restarting... $arg1"
19+
20+
sudo docker restart -- "$arg1"
21+
22+
# Re-enable nginx routing (check for .conf suffix)
23+
if [ ! -L "/etc/nginx/sites-enabled/$arg1.conf" ] && [ ! -f "/etc/nginx/sites-enabled/$arg1.conf" ] && [ -f "/etc/nginx/sites-available/$arg1.conf" ]; then
24+
sudo ln -s -- "/etc/nginx/sites-available/$arg1.conf" "/etc/nginx/sites-enabled/$arg1.conf"
25+
fi
26+
27+
# Re-enable nginx routing (check for no suffix)
28+
if [ ! -L "/etc/nginx/sites-enabled/$arg1" ] && [ ! -f "/etc/nginx/sites-enabled/$arg1" ] && [ -f "/etc/nginx/sites-available/$arg1" ]; then
29+
sudo ln -s -- "/etc/nginx/sites-available/$arg1" "/etc/nginx/sites-enabled/$arg1"
30+
fi
31+
32+
sudo systemctl reload nginx

src/backend/shell_scripts/stop.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
# This script takes in 1 command line argument (the container name)
4+
5+
id -u
6+
if [ "$#" -ne 1 ]; then
7+
echo "Usage: $0 container_name"
8+
exit 1
9+
fi
10+
11+
arg1=$1
12+
13+
if [[ ! "$arg1" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
14+
echo "Error: Invalid container name '$arg1'. Allowed characters: letters, digits, '.', '-', '_'." >&2
15+
exit 1
16+
fi
17+
18+
echo "Stopping... $arg1"
19+
20+
sudo docker stop -- "$arg1"
21+
22+
# Disable nginx routing for this domain so it doesn't return 502
23+
if [ -L "/etc/nginx/sites-enabled/$arg1.conf" ] || [ -f "/etc/nginx/sites-enabled/$arg1.conf" ]; then
24+
sudo rm -- "/etc/nginx/sites-enabled/$arg1.conf"
25+
fi
26+
27+
if [ -L "/etc/nginx/sites-enabled/$arg1" ] || [ -f "/etc/nginx/sites-enabled/$arg1" ]; then
28+
sudo rm -- "/etc/nginx/sites-enabled/$arg1"
29+
fi
30+
31+
sudo systemctl reload nginx

0 commit comments

Comments
 (0)