gcb-host 0.0.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/lib/broker.d.ts +9 -2
  3. package/lib/broker.d.ts.map +1 -1
  4. package/lib/broker.js +63 -28
  5. package/lib/broker.js.map +1 -1
  6. package/lib/commands/compose.d.ts.map +1 -1
  7. package/lib/commands/compose.js +42 -15
  8. package/lib/commands/compose.js.map +1 -1
  9. package/lib/commands/config.d.ts +1 -1
  10. package/lib/commands/config.d.ts.map +1 -1
  11. package/lib/commands/config.js +27 -5
  12. package/lib/commands/config.js.map +1 -1
  13. package/lib/commands/host-opener.d.ts +11 -1
  14. package/lib/commands/host-opener.d.ts.map +1 -1
  15. package/lib/commands/host-opener.js +137 -38
  16. package/lib/commands/host-opener.js.map +1 -1
  17. package/lib/config.d.ts +5 -1
  18. package/lib/config.d.ts.map +1 -1
  19. package/lib/config.js +14 -8
  20. package/lib/config.js.map +1 -1
  21. package/lib/daemon.d.ts +0 -9
  22. package/lib/daemon.d.ts.map +1 -1
  23. package/lib/daemon.js +57 -7
  24. package/lib/daemon.js.map +1 -1
  25. package/lib/device-flow.d.ts +1 -0
  26. package/lib/device-flow.d.ts.map +1 -1
  27. package/lib/device-flow.js +1 -0
  28. package/lib/device-flow.js.map +1 -1
  29. package/lib/index.d.ts +1 -1
  30. package/lib/index.d.ts.map +1 -1
  31. package/lib/index.js +1 -1
  32. package/lib/policy.d.ts.map +1 -1
  33. package/lib/policy.js +5 -3
  34. package/lib/policy.js.map +1 -1
  35. package/lib/providers/github-app.d.ts.map +1 -1
  36. package/lib/providers/github-app.js +233 -165
  37. package/lib/providers/github-app.js.map +1 -1
  38. package/lib/reload.d.ts +45 -0
  39. package/lib/reload.d.ts.map +1 -0
  40. package/lib/reload.js +149 -0
  41. package/lib/reload.js.map +1 -0
  42. package/package.json +2 -2
@@ -0,0 +1,45 @@
1
+ /** What one file read looked like. */
2
+ export interface ConfigFingerprint {
3
+ readonly digest: string;
4
+ }
5
+ /**
6
+ * Read the file and hash it.
7
+ *
8
+ * @param file - the configuration path.
9
+ * @returns the digest, or undefined when the file is momentarily absent (an atomic rename).
10
+ */
11
+ export declare function fingerprintConfig(file: string): ConfigFingerprint | undefined;
12
+ /** Options for {@link ConfigWatcher}. */
13
+ export interface ConfigWatcherOptions {
14
+ readonly file: string;
15
+ /** Safety poll interval, used alongside the watch and as the fallback when it is unavailable. */
16
+ readonly intervalMs?: number;
17
+ /** How long to coalesce a burst of events. */
18
+ readonly debounceMs?: number;
19
+ /** Reload the file; throws when invalid. Returns the digest of the bytes it loaded. */
20
+ readonly reload: () => string;
21
+ /** Digest already loaded at start-up; avoids a race between load and watcher construction. */
22
+ readonly initialDigest?: string;
23
+ /** Report a failed reload; the last good snapshot stays active. */
24
+ readonly onError?: (error: Error) => void;
25
+ }
26
+ /** Poll and watch one configuration file. */
27
+ export declare class ConfigWatcher {
28
+ #private;
29
+ constructor(options: ConfigWatcherOptions);
30
+ /**
31
+ * Start watching. A failure to establish the fast path is not fatal: polling still runs.
32
+ *
33
+ * @returns this watcher, for chaining.
34
+ */
35
+ start(): this;
36
+ /**
37
+ * Compare the file with the last accepted digest and reload when it changed.
38
+ *
39
+ * @param force - reload even when the digest is unchanged (SIGHUP).
40
+ */
41
+ check(force: boolean): void;
42
+ /** Stop both triggers. */
43
+ stop(): void;
44
+ }
45
+ //# sourceMappingURL=reload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reload.d.ts","sourceRoot":"","sources":["../src/reload.ts"],"names":[],"mappings":"AAWA,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAS7E;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iGAAiG;IACjG,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC3C;AAED,6CAA6C;AAC7C,qBAAa,aAAa;;IAkBxB,YAAY,OAAO,EAAE,oBAAoB,EAYxC;IAED;;;;OAIG;IACH,KAAK,IAAI,IAAI,CAiBZ;IASD;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAiC1B;IAED,0BAA0B;IAC1B,IAAI,IAAI,IAAI,CAKX;CACF"}
package/lib/reload.js ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Watch the configuration file for a change, with a poll as the safety net.
3
+ *
4
+ * `fs.watch` is the low-latency trigger. Events can be missed (network filesystems and bind mounts
5
+ * have been observed dropping them), so a 5-second digest poll runs alongside it; a digest that has
6
+ * not changed costs one read and no reload.
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ /**
12
+ * Read the file and hash it.
13
+ *
14
+ * @param file - the configuration path.
15
+ * @returns the digest, or undefined when the file is momentarily absent (an atomic rename).
16
+ */
17
+ export function fingerprintConfig(file) {
18
+ let text;
19
+ try {
20
+ text = fs.readFileSync(file, 'utf8');
21
+ }
22
+ catch (error) {
23
+ if (error.code === 'ENOENT')
24
+ return undefined;
25
+ throw error;
26
+ }
27
+ return { digest: createHash('sha256').update(text).digest('hex') };
28
+ }
29
+ /** Poll and watch one configuration file. */
30
+ export class ConfigWatcher {
31
+ #file;
32
+ #intervalMs;
33
+ #debounceMs;
34
+ #reload;
35
+ #onError;
36
+ #last;
37
+ /** A SIGHUP arrived while the file was absent; honour it when the file returns. */
38
+ #forcePending = false;
39
+ /** The (digest, reason) already reported, so a deterministic failure is not logged every tick. */
40
+ #failedReport;
41
+ /** The read error already reported, so a permanently unreadable file is not logged every tick. */
42
+ #readError;
43
+ #timer;
44
+ #watch;
45
+ #debounce;
46
+ #stopped = false;
47
+ constructor(options) {
48
+ this.#file = options.file;
49
+ this.#intervalMs = options.intervalMs ?? 5_000;
50
+ this.#debounceMs = options.debounceMs ?? 300;
51
+ this.#reload = options.reload;
52
+ this.#onError = options.onError ?? (() => { });
53
+ try {
54
+ this.#last = options.initialDigest ?? fingerprintConfig(this.#file)?.digest;
55
+ }
56
+ catch {
57
+ // An unreadable file at start-up is reported by the first check, not thrown from the constructor.
58
+ this.#last = options.initialDigest;
59
+ }
60
+ }
61
+ /**
62
+ * Start watching. A failure to establish the fast path is not fatal: polling still runs.
63
+ *
64
+ * @returns this watcher, for chaining.
65
+ */
66
+ start() {
67
+ this.#startPolling();
68
+ try {
69
+ this.#watch = fs.watch(path.dirname(this.#file), { persistent: false }, (_event, name) => {
70
+ if (name !== null && name !== path.basename(this.#file))
71
+ return;
72
+ if (this.#debounce !== undefined)
73
+ clearTimeout(this.#debounce);
74
+ this.#debounce = setTimeout(() => this.check(false), this.#debounceMs);
75
+ this.#debounce.unref();
76
+ });
77
+ this.#watch.on('error', () => {
78
+ this.#watch?.close();
79
+ this.#watch = undefined;
80
+ });
81
+ }
82
+ catch {
83
+ // The safety poll is already running; an unavailable watch must not stop the daemon.
84
+ }
85
+ return this;
86
+ }
87
+ /** Start the safety poll once; the watch is the primary, low-latency trigger. */
88
+ #startPolling() {
89
+ if (this.#stopped || this.#timer !== undefined)
90
+ return;
91
+ this.#timer = setInterval(() => this.check(false), this.#intervalMs);
92
+ this.#timer.unref();
93
+ }
94
+ /**
95
+ * Compare the file with the last accepted digest and reload when it changed.
96
+ *
97
+ * @param force - reload even when the digest is unchanged (SIGHUP).
98
+ */
99
+ check(force) {
100
+ if (this.#stopped)
101
+ return;
102
+ if (force)
103
+ this.#forcePending = true;
104
+ let next;
105
+ try {
106
+ next = fingerprintConfig(this.#file);
107
+ }
108
+ catch (error) {
109
+ const message = error.message;
110
+ if (message !== this.#readError) {
111
+ this.#readError = message;
112
+ this.#onError(error);
113
+ }
114
+ return;
115
+ }
116
+ this.#readError = undefined;
117
+ if (next === undefined)
118
+ return;
119
+ if (!this.#forcePending && next.digest === this.#last)
120
+ return;
121
+ try {
122
+ this.#last = this.#reload();
123
+ this.#failedReport = undefined;
124
+ this.#forcePending = false;
125
+ }
126
+ catch (error) {
127
+ // Keep the last good snapshot. Clearing the digest retries the same bytes on the next tick, for
128
+ // a transient cause; report only once per distinct bad content so a typo does not flood the
129
+ // audit log and the container log every five seconds.
130
+ this.#last = undefined;
131
+ this.#forcePending = false;
132
+ const report = `${next.digest}\0${error.message}`;
133
+ if (report !== this.#failedReport) {
134
+ this.#failedReport = report;
135
+ this.#onError(error);
136
+ }
137
+ }
138
+ }
139
+ /** Stop both triggers. */
140
+ stop() {
141
+ this.#stopped = true;
142
+ if (this.#timer !== undefined)
143
+ clearInterval(this.#timer);
144
+ if (this.#debounce !== undefined)
145
+ clearTimeout(this.#debounce);
146
+ this.#watch?.close();
147
+ }
148
+ }
149
+ //# sourceMappingURL=reload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reload.js","sourceRoot":"","sources":["../src/reload.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gcb-host",
3
- "version": "0.0.3",
3
+ "version": "0.2.0",
4
4
  "description": "Host-side gcb broker, configuration, providers and administration commands.",
5
5
  "license": "MIT",
6
6
  "author": "JounQin <admin@1stg.me>",
@@ -27,7 +27,7 @@
27
27
  }
28
28
  },
29
29
  "dependencies": {
30
- "gcb-core": "0.0.3",
30
+ "gcb-core": "0.2.0",
31
31
  "node-fetch-native": "^1.6.7",
32
32
  "x-fetch": "^0.3.0",
33
33
  "yaml": "^2.9.1"