1// Runs inside each Fly container via `node --input-type=module-typescript -e`;
2// Node strips the types, so only erasable TypeScript syntax is allowed here.
3import * as http from "node:http";
4
5const port = Number(process.env.PORT);
6const name = process.env.CONTAINER_NAME;
7const version = process.env.VERSION;
8const machine = process.env.FLY_MACHINE_ID;
9const held = new Set<http.ServerResponse>();
10const receipt = (signal: string) =>
11 JSON.stringify({ machine, name, version, signal });
12const server = http.createServer((request, response) => {
13 response.setHeader("cache-control", "no-store");
14 if (request.url === "/health") {
15 const ready = process.env.BAD_HEALTH !== "true";
16 response.writeHead(ready ? 200 : 503).end();
17 } else if (request.url === "/sidecar/hold") {
18 const proxy = http.get("http://127.0.0.1:3001/hold", (upstream) => {
19 response.writeHead(upstream.statusCode ?? 502);
20 upstream.pipe(response);
21 });
22 proxy.on("error", () => response.destroy());
23 response.on("close", () => proxy.destroy());
24 } else if (request.url === "/hold") {
25 response.writeHead(200, { "content-type": "text/plain" });
26 response.write("waiting\n");
27 held.add(response);
28 response.on("close", () => held.delete(response));
29 // A broken shutdown cannot leave a request alive indefinitely.
30 const deadline = setTimeout(() => response.destroy(), 90_000);
31 response.on("close", () => clearTimeout(deadline));
32 } else if (request.url === "/") {
33 response.end(version);
34 } else {
35 response.writeHead(404).end();
36 }
37});
38server.listen(port, "::");
39process.once("SIGTERM", () => {
40 server.close(() => process.exit(0));
41 setTimeout(() => {
42 for (const response of held) response.end(receipt("SIGTERM"));
43 }, 1000);
44});