gcb-host 0.8.1 → 0.10.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.
@@ -0,0 +1,34 @@
1
+ /** The directory holding the token's lock. It persists so a bind mount can provide it. */
2
+ export declare function tokenLockPath(path: string): string;
3
+ /**
4
+ * Run `fn` while holding the token's lock.
5
+ *
6
+ * @param path - The token path.
7
+ * @param fn - The work to do under the lock.
8
+ * @returns Whatever `fn` returns.
9
+ */
10
+ export declare function withTokenLock<T>(path: string, fn: () => Promise<T>): Promise<T>;
11
+ /**
12
+ * Open the token for an in-place write through a validated descriptor.
13
+ *
14
+ * @param path - The token path.
15
+ * @returns An open writable descriptor.
16
+ */
17
+ export declare function openTokenForWrite(path: string): number;
18
+ /**
19
+ * Open, replace and close the token while its caller holds the lock.
20
+ *
21
+ * @param path - The token path.
22
+ * @param token - The new refresh token.
23
+ */
24
+ export declare function writeTokenLocked(path: string, token: string): void;
25
+ /**
26
+ * Replace the token contents through an already-open descriptor.
27
+ *
28
+ * Callers hold the token lock; the descriptor prevents a path swap from redirecting the write.
29
+ *
30
+ * @param fd - An open token descriptor.
31
+ * @param token - The new refresh token.
32
+ */
33
+ export declare function writeTokenThrough(fd: number, token: string): void;
34
+ //# sourceMappingURL=token-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-lock.d.ts","sourceRoot":"","sources":["../src/token-lock.ts"],"names":[],"mappings":"AA2BA,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAmBD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA2CrF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOlE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIjE"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Cross-process coordination for the one writable secret: the authorized person's refresh token.
3
+ *
4
+ * The daemon renews it and `gcb authorize` replaces it. Both hold the same lock for the whole read
5
+ * → exchange → write sequence, so a reader never sees a token another process is rewriting. The
6
+ * lock directory is mounted beside the token in the sidecar; it must exist on both sides.
7
+ */
8
+ import { closeSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, constants as fsConstants, } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ const LOCK_TIMEOUT_MS = 120_000;
11
+ function sleep(ms) {
12
+ return new Promise(resolve => setTimeout(resolve, ms));
13
+ }
14
+ /** The directory holding the token's lock. It persists so a bind mount can provide it. */
15
+ export function tokenLockPath(path) {
16
+ return `${path}.lock`;
17
+ }
18
+ /** Create the persistent lock directory, or reject a path that is not a real directory. */
19
+ function ensureLockDirectory(path) {
20
+ const lock = tokenLockPath(path);
21
+ try {
22
+ const stat = lstatSync(lock);
23
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
24
+ throw new Error(`${lock} is not a real directory`);
25
+ }
26
+ }
27
+ catch (error) {
28
+ if (error.code !== 'ENOENT') {
29
+ throw error;
30
+ }
31
+ mkdirSync(lock, { recursive: true, mode: 0o700 });
32
+ }
33
+ return lock;
34
+ }
35
+ /**
36
+ * Run `fn` while holding the token's lock.
37
+ *
38
+ * @param path - The token path.
39
+ * @param fn - The work to do under the lock.
40
+ * @returns Whatever `fn` returns.
41
+ */
42
+ export async function withTokenLock(path, fn) {
43
+ const lock = ensureLockDirectory(path);
44
+ const held = join(lock, 'held');
45
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
46
+ let fd;
47
+ for (;;) {
48
+ try {
49
+ fd = openSync(held, 'wx', 0o600);
50
+ try {
51
+ writeFileSync(fd, `${process.pid}\n`);
52
+ }
53
+ catch (error) {
54
+ closeSync(fd);
55
+ unlinkSync(held);
56
+ throw new Error(`could not initialize the lock on ${path}: ${error.message}`, {
57
+ cause: error,
58
+ });
59
+ }
60
+ break;
61
+ }
62
+ catch (error) {
63
+ if (error.code !== 'EEXIST') {
64
+ throw new Error(`could not lock ${path}: ${error.message}`, { cause: error });
65
+ }
66
+ if (Date.now() >= deadline) {
67
+ throw new Error(`timed out waiting for the lock on ${path}; if no authorize or broker is running, remove ${held} and retry`, { cause: error });
68
+ }
69
+ await sleep(25);
70
+ }
71
+ }
72
+ try {
73
+ return await fn();
74
+ }
75
+ finally {
76
+ closeSync(fd);
77
+ try {
78
+ if (readFileSync(held, 'utf8').trim() === String(process.pid)) {
79
+ unlinkSync(held);
80
+ }
81
+ }
82
+ catch {
83
+ // Another process already removed a stale lock; nothing to release.
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Open the token for an in-place write through a validated descriptor.
89
+ *
90
+ * @param path - The token path.
91
+ * @returns An open writable descriptor.
92
+ */
93
+ export function openTokenForWrite(path) {
94
+ const fd = openSync(path, fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0) | (fsConstants.O_NONBLOCK ?? 0));
95
+ try {
96
+ if (!fstatSync(fd).isFile()) {
97
+ throw new Error(`${path} is not a regular file`);
98
+ }
99
+ fchmodSync(fd, 0o600);
100
+ return fd;
101
+ }
102
+ catch (error) {
103
+ closeSync(fd);
104
+ throw error;
105
+ }
106
+ }
107
+ /**
108
+ * Open, replace and close the token while its caller holds the lock.
109
+ *
110
+ * @param path - The token path.
111
+ * @param token - The new refresh token.
112
+ */
113
+ export function writeTokenLocked(path, token) {
114
+ const fd = openTokenForWrite(path);
115
+ try {
116
+ writeTokenThrough(fd, token);
117
+ }
118
+ finally {
119
+ closeSync(fd);
120
+ }
121
+ }
122
+ /**
123
+ * Replace the token contents through an already-open descriptor.
124
+ *
125
+ * Callers hold the token lock; the descriptor prevents a path swap from redirecting the write.
126
+ *
127
+ * @param fd - An open token descriptor.
128
+ * @param token - The new refresh token.
129
+ */
130
+ export function writeTokenThrough(fd, token) {
131
+ ftruncateSync(fd, 0);
132
+ writeFileSync(fd, `${token}\n`);
133
+ fchmodSync(fd, 0o600);
134
+ }
135
+ //# sourceMappingURL=token-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-lock.js","sourceRoot":"","sources":["../src/token-lock.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.8.1",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "Host-side gcb broker, configuration, providers and administration commands.",
6
6
  "repository": {
@@ -27,7 +27,7 @@
27
27
  "lib"
28
28
  ],
29
29
  "dependencies": {
30
- "gcb-core": "0.8.1",
30
+ "gcb-core": "0.10.0",
31
31
  "node-fetch-native": "^1.6.7",
32
32
  "x-fetch": "^0.3.0",
33
33
  "yaml": "^2.9.1"