Repository navigation
Expand file tree
/
Copy pathmain.ts
More file actions
100 lines (80 loc) · 3.13 KB
/
Copy pathmain.ts
File metadata and controls
100 lines (80 loc) · 3.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import * as Persistency from "./src/persist.ts";
import QueueManager from "./src/manager.ts";
import { createHandler } from "./src/handler.ts";
import { parseConfig } from "./src/config.ts";
import type { Payload } from "./src/payload.ts";
const LOG_ENCODER = new TextEncoder();
function writeLog(message: string): void {
Deno.stdout.writeSync(LOG_ENCODER.encode(`${message}\n`));
}
// Read each config var by name rather than Deno.env.toObject(), which
// enumerates the entire process environment and therefore requires
// unrestricted env access. A compiled binary with a scoped --allow-env
// allowlist (see Dockerfile/CI) can only grant per-name access.
const ENV_VAR_NAMES = [
"HOST",
"PORT",
"PERSIST",
"QUEUE_API_TOKEN",
"QUEUE_DEPTH_LIMIT",
"QUEUE_COUNT_LIMIT",
"RATE_LIMIT_REQUESTS",
] as const;
function readEnv(): Record<string, string | undefined> {
const env: Record<string, string | undefined> = {};
for (const name of ENV_VAR_NAMES) {
env[name] = Deno.env.get(name);
}
return env;
}
const CONFIG = parseConfig(readEnv(), Deno.args);
// Set up our persistency manager
const PERSIST_ENGINE = CONFIG.persistEnabled
? new Persistency.FileStore<Payload>()
: new Persistency.MemoryStore<Payload>();
PERSIST_ENGINE.dir(CONFIG.persistDir);
// Set up the manager, which will handle our queues for us
const MANAGER = new QueueManager<Payload>(PERSIST_ENGINE, CONFIG.queueDepthLimit, CONFIG.queueCountLimit, CONFIG.persistEnabled);
// Load up any existing queue data, if we're persisting
if (PERSIST_ENGINE instanceof Persistency.FileStore) {
try {
PERSIST_ENGINE.claimDirectory();
} catch (error) {
if (!(error instanceof Persistency.PersistDirectoryInUseError)) {
throw error;
}
console.error(`${error.message}; refusing to start`);
Deno.exit(1);
}
writeLog("Loading in data from persist.dat...\n");
MANAGER.load();
}
const HANDLER = createHandler(MANAGER, CONFIG.apiToken, CONFIG.rateLimitRequests);
// Start up the application
const SERVER = Deno.serve({
hostname: CONFIG.host,
port: CONFIG.port,
onListen: ({ hostname, port }) => writeLog(`Listening on ${hostname}:${port}`),
}, HANDLER);
async function shutdown(signal: string): Promise<void> {
writeLog(`Received ${signal}, shutting down gracefully...`);
// Stop accepting new connections
await SERVER.shutdown();
// If we're using file persistency, save all current state to persistant storage
if (PERSIST_ENGINE instanceof Persistency.FileStore) {
writeLog("Flushing data to persist.dat...\n");
try {
MANAGER.save();
} catch (error) {
// The previous persist.dat is left in place; it still holds every logged item.
writeLog(`Failed to flush data to persist.dat: ${error instanceof Error ? error.message : error}`);
PERSIST_ENGINE.close();
Deno.exit(1);
}
}
PERSIST_ENGINE.close();
writeLog("Goodbye!");
Deno.exit(0);
}
Deno.addSignalListener("SIGINT", () => shutdown("SIGINT"));
Deno.addSignalListener("SIGTERM", () => shutdown("SIGTERM"));