gcb-host 0.7.1 → 0.8.1
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.
- package/CHANGELOG.md +22 -1
- package/bin/gcb.js +3 -3
- package/lib/audit.d.ts +4 -4
- package/lib/audit.d.ts.map +1 -1
- package/lib/audit.js +15 -14
- package/lib/audit.js.map +1 -1
- package/lib/broker.d.ts +12 -13
- package/lib/broker.d.ts.map +1 -1
- package/lib/broker.js +77 -53
- package/lib/broker.js.map +1 -1
- package/lib/commands/authorize.d.ts +43 -13
- package/lib/commands/authorize.d.ts.map +1 -1
- package/lib/commands/authorize.js +146 -53
- package/lib/commands/authorize.js.map +1 -1
- package/lib/commands/compose.d.ts +8 -10
- package/lib/commands/compose.d.ts.map +1 -1
- package/lib/commands/compose.js +33 -44
- package/lib/commands/compose.js.map +1 -1
- package/lib/commands/config.d.ts.map +1 -1
- package/lib/commands/config.js +59 -41
- package/lib/commands/config.js.map +1 -1
- package/lib/commands/deployment.d.ts +11 -11
- package/lib/commands/deployment.d.ts.map +1 -1
- package/lib/commands/deployment.js +19 -18
- package/lib/commands/deployment.js.map +1 -1
- package/lib/commands/diagnose.d.ts.map +1 -1
- package/lib/commands/diagnose.js +51 -33
- package/lib/commands/diagnose.js.map +1 -1
- package/lib/commands/doctor.d.ts +5 -5
- package/lib/commands/doctor.d.ts.map +1 -1
- package/lib/commands/doctor.js +53 -32
- package/lib/commands/doctor.js.map +1 -1
- package/lib/commands/host.d.ts +7 -7
- package/lib/commands/host.d.ts.map +1 -1
- package/lib/commands/host.js +144 -93
- package/lib/commands/host.js.map +1 -1
- package/lib/commands/init.d.ts +33 -33
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +93 -75
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/stage.d.ts +6 -6
- package/lib/commands/stage.d.ts.map +1 -1
- package/lib/commands/stage.js +21 -15
- package/lib/commands/stage.js.map +1 -1
- package/lib/config.d.ts +29 -28
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +100 -84
- package/lib/config.js.map +1 -1
- package/lib/daemon.d.ts +9 -9
- package/lib/daemon.d.ts.map +1 -1
- package/lib/daemon.js +21 -18
- package/lib/daemon.js.map +1 -1
- package/lib/device-flow.d.ts +23 -22
- package/lib/device-flow.d.ts.map +1 -1
- package/lib/device-flow.js +33 -28
- package/lib/device-flow.js.map +1 -1
- package/lib/gh.d.ts +2 -2
- package/lib/gh.d.ts.map +1 -1
- package/lib/gh.js +74 -46
- package/lib/gh.js.map +1 -1
- package/lib/http.d.ts.map +1 -1
- package/lib/http.js +35 -18
- package/lib/http.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/policy.d.ts +25 -25
- package/lib/policy.d.ts.map +1 -1
- package/lib/policy.js +57 -36
- package/lib/policy.js.map +1 -1
- package/lib/providers/github-app.d.ts +12 -12
- package/lib/providers/github-app.d.ts.map +1 -1
- package/lib/providers/github-app.js +194 -125
- package/lib/providers/github-app.js.map +1 -1
- package/lib/providers/index.d.ts +8 -9
- package/lib/providers/index.d.ts.map +1 -1
- package/lib/providers/index.js +5 -5
- package/lib/providers/index.js.map +1 -1
- package/lib/providers/static.d.ts +3 -3
- package/lib/providers/static.d.ts.map +1 -1
- package/lib/providers/static.js +14 -12
- package/lib/providers/static.js.map +1 -1
- package/lib/proxy.d.ts +10 -10
- package/lib/proxy.d.ts.map +1 -1
- package/lib/proxy.js +18 -16
- package/lib/proxy.js.map +1 -1
- package/lib/public-repo.d.ts +10 -10
- package/lib/public-repo.d.ts.map +1 -1
- package/lib/public-repo.js +17 -14
- package/lib/public-repo.js.map +1 -1
- package/lib/reload.d.ts +4 -4
- package/lib/reload.d.ts.map +1 -1
- package/lib/reload.js +24 -14
- package/lib/reload.js.map +1 -1
- package/package.json +10 -10
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* `authorize` — get a user token for this app, once, from whoever runs it.
|
|
3
3
|
*
|
|
4
4
|
* Run on the host, beside the configuration. It prints a code, waits for that person to type it at
|
|
5
|
-
* GitHub on whatever device has a browser, and stores the refresh token next to the configuration
|
|
6
|
-
* its key — outside every mount, never in the repository. From then on the broker renews the
|
|
7
|
-
* itself and nothing here has to be run again.
|
|
5
|
+
* GitHub on whatever device has a browser, and stores the refresh token next to the configuration
|
|
6
|
+
* and its key — outside every mount, never in the repository. From then on the broker renews the
|
|
7
|
+
* token by itself and nothing here has to be run again.
|
|
8
8
|
*
|
|
9
9
|
* The token is what makes a pull request authored by a person rather than by the app. Only a person
|
|
10
10
|
* can be the author of one that reviewers will not skip, and only the device flow reaches a person
|
|
@@ -13,26 +13,56 @@
|
|
|
13
13
|
* Exit codes: 0 stored, 1 refused.
|
|
14
14
|
*/
|
|
15
15
|
/** Usage text for `authorize`. */
|
|
16
|
-
export declare const AUTHORIZE_USAGE = "Usage: gcb authorize [options]\n\nGet a user token for this app, by authorizing it once from a browser somewhere else. Run it on the host,\nbeside the configuration. It stores a refresh token and prints where; nothing else needs running after\nthat, because the broker renews the token itself.\n\n --config <path>
|
|
16
|
+
export declare const AUTHORIZE_USAGE = "Usage: gcb authorize [options]\n\nGet a user token for this app, by authorizing it once from a browser somewhere else. Run it on the host,\nbeside the configuration. It stores a refresh token and prints where; nothing else needs running after\nthat, because the broker renews the token itself.\n\n --config <path> The configuration (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)\n --host <host> The host block to authorize for (default: github.com)\n --to <path> Where to write the refresh token (default: beside the configuration)\n --broker-container Allow a target inside the broker container's mounts; only when it is not\n shared with the agent container\n --proxy <url> Use this proxy for this command\n --no-proxy Reach GitHub directly for this command\n -h, --help Show this help\n\nThe app needs its client id configured (the App ID will not do), and \"Enable Device Flow\" selected in\nits settings. GitHub returns a refresh token only while \"Expire user authorization tokens\" is selected,\nwhich is its default and what this wants.\n";
|
|
17
17
|
/**
|
|
18
18
|
* Where this command writes the refresh token.
|
|
19
19
|
*
|
|
20
|
-
* Beside the configuration, because the configuration is the path this process was
|
|
21
|
-
* inside it belong to whichever process runs the broker, and for a sidecar those are
|
|
22
|
-
* that do not exist out here. In the usual sidecar layout the configuration and the
|
|
23
|
-
* directory, so this lands in the same file the broker reads; where they differ, the
|
|
24
|
-
* printed rather than discovered later.
|
|
20
|
+
* Beside the configuration, because the configuration is the path this process was _given_: the
|
|
21
|
+
* paths inside it belong to whichever process runs the broker, and for a sidecar those are
|
|
22
|
+
* container paths that do not exist out here. In the usual sidecar layout the configuration and the
|
|
23
|
+
* key sit in one directory, so this lands in the same file the broker reads; where they differ, the
|
|
24
|
+
* difference is printed rather than discovered later.
|
|
25
25
|
*
|
|
26
|
-
* @param configPath -
|
|
27
|
-
* @returns
|
|
26
|
+
* @param configPath - The resolved configuration path.
|
|
27
|
+
* @returns The path to write the refresh token to.
|
|
28
28
|
*/
|
|
29
29
|
export declare function refreshTokenPath(configPath: string): string;
|
|
30
30
|
/**
|
|
31
31
|
* Where the broker will look for it, which is beside the key — from the key's own point of view.
|
|
32
32
|
*
|
|
33
|
-
* @param privateKeyPath -
|
|
34
|
-
* @returns
|
|
33
|
+
* @param privateKeyPath - The path as the configuration records it.
|
|
34
|
+
* @returns The path the broker reads, or `undefined` when the key is not a file.
|
|
35
35
|
*/
|
|
36
36
|
export declare function brokerReadsPath(privateKeyPath: string | undefined): string | undefined;
|
|
37
|
+
/** Inputs for the credential-placement decision. */
|
|
38
|
+
export interface AuthorizationPlacementInput {
|
|
39
|
+
readonly configPath: string;
|
|
40
|
+
readonly to?: string;
|
|
41
|
+
readonly brokerContainer: boolean;
|
|
42
|
+
readonly isInsideContainer?: () => boolean;
|
|
43
|
+
readonly mountedPath?: (target: string) => string | null;
|
|
44
|
+
}
|
|
45
|
+
/** The writable target and the warning to print before starting the device flow. */
|
|
46
|
+
export interface AuthorizationPlacement {
|
|
47
|
+
readonly target: string;
|
|
48
|
+
readonly warning?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Open and secure the token target before authorization, so a later path swap cannot redirect the
|
|
52
|
+
* write and a permission failure surfaces before the device flow.
|
|
53
|
+
*
|
|
54
|
+
* @param target - The token path.
|
|
55
|
+
* @returns The open descriptor, already mode 0600.
|
|
56
|
+
*/
|
|
57
|
+
export declare function openTokenTarget(target: string): number;
|
|
58
|
+
/**
|
|
59
|
+
* Decide where the refresh token goes, and whether that placement is allowed.
|
|
60
|
+
*
|
|
61
|
+
* @param input - The configuration path, target override and injectable guards.
|
|
62
|
+
* @returns The target and an optional warning.
|
|
63
|
+
* @throws {Error} When the placement would put the token inside a container mount without the
|
|
64
|
+
* explicit broker-container acknowledgement.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveAuthorizationPlacement(input: AuthorizationPlacementInput): AuthorizationPlacement;
|
|
37
67
|
export declare function runAuthorize(argv: readonly string[]): Promise<number>;
|
|
38
68
|
//# sourceMappingURL=authorize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAoBH,kCAAkC;AAClC,eAAO,MAAM,eAAe,mnCAkB3B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEtF;AAED,oDAAoD;AACpD,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAA;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;CACzD;AAED,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBtD;AAUD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,2BAA2B,GACjC,sBAAsB,CAsBxB;AAUD,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4G3E"}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* `authorize` — get a user token for this app, once, from whoever runs it.
|
|
3
3
|
*
|
|
4
4
|
* Run on the host, beside the configuration. It prints a code, waits for that person to type it at
|
|
5
|
-
* GitHub on whatever device has a browser, and stores the refresh token next to the configuration
|
|
6
|
-
* its key — outside every mount, never in the repository. From then on the broker renews the
|
|
7
|
-
* itself and nothing here has to be run again.
|
|
5
|
+
* GitHub on whatever device has a browser, and stores the refresh token next to the configuration
|
|
6
|
+
* and its key — outside every mount, never in the repository. From then on the broker renews the
|
|
7
|
+
* token by itself and nothing here has to be run again.
|
|
8
8
|
*
|
|
9
9
|
* The token is what makes a pull request authored by a person rather than by the app. Only a person
|
|
10
10
|
* can be the author of one that reviewers will not skip, and only the device flow reaches a person
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Exit codes: 0 stored, 1 refused.
|
|
14
14
|
*/
|
|
15
|
+
import { closeSync, constants, fchmodSync, fstatSync, ftruncateSync, mkdirSync, openSync, writeFileSync, } from 'node:fs';
|
|
15
16
|
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
17
|
+
import { fail, insideMountedPath, isInsideContainer, parseArgs, say, warn } from 'gcb-core';
|
|
16
18
|
import { USER_TOKEN_FILE, userTokenPath, loadConfig, resolveConfigPath } from '../config.js';
|
|
17
19
|
import { collectUserToken, startDeviceFlow } from '../device-flow.js';
|
|
18
20
|
import { fetcherFor, parseProxy } from '../http.js';
|
|
19
|
-
import { fail, insideMountedPath, isInsideContainer, parseArgs, say, writeSecretFile } from 'gcb-core';
|
|
20
21
|
/** Usage text for `authorize`. */
|
|
21
22
|
export const AUTHORIZE_USAGE = `Usage: gcb authorize [options]
|
|
22
23
|
|
|
@@ -24,12 +25,14 @@ Get a user token for this app, by authorizing it once from a browser somewhere e
|
|
|
24
25
|
beside the configuration. It stores a refresh token and prints where; nothing else needs running after
|
|
25
26
|
that, because the broker renews the token itself.
|
|
26
27
|
|
|
27
|
-
--config <path>
|
|
28
|
-
--host <host>
|
|
29
|
-
--to <path>
|
|
30
|
-
--
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
--config <path> The configuration (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)
|
|
29
|
+
--host <host> The host block to authorize for (default: github.com)
|
|
30
|
+
--to <path> Where to write the refresh token (default: beside the configuration)
|
|
31
|
+
--broker-container Allow a target inside the broker container's mounts; only when it is not
|
|
32
|
+
shared with the agent container
|
|
33
|
+
--proxy <url> Use this proxy for this command
|
|
34
|
+
--no-proxy Reach GitHub directly for this command
|
|
35
|
+
-h, --help Show this help
|
|
33
36
|
|
|
34
37
|
The app needs its client id configured (the App ID will not do), and "Enable Device Flow" selected in
|
|
35
38
|
its settings. GitHub returns a refresh token only while "Expire user authorization tokens" is selected,
|
|
@@ -38,14 +41,14 @@ which is its default and what this wants.
|
|
|
38
41
|
/**
|
|
39
42
|
* Where this command writes the refresh token.
|
|
40
43
|
*
|
|
41
|
-
* Beside the configuration, because the configuration is the path this process was
|
|
42
|
-
* inside it belong to whichever process runs the broker, and for a sidecar those are
|
|
43
|
-
* that do not exist out here. In the usual sidecar layout the configuration and the
|
|
44
|
-
* directory, so this lands in the same file the broker reads; where they differ, the
|
|
45
|
-
* printed rather than discovered later.
|
|
44
|
+
* Beside the configuration, because the configuration is the path this process was _given_: the
|
|
45
|
+
* paths inside it belong to whichever process runs the broker, and for a sidecar those are
|
|
46
|
+
* container paths that do not exist out here. In the usual sidecar layout the configuration and the
|
|
47
|
+
* key sit in one directory, so this lands in the same file the broker reads; where they differ, the
|
|
48
|
+
* difference is printed rather than discovered later.
|
|
46
49
|
*
|
|
47
|
-
* @param configPath -
|
|
48
|
-
* @returns
|
|
50
|
+
* @param configPath - The resolved configuration path.
|
|
51
|
+
* @returns The path to write the refresh token to.
|
|
49
52
|
*/
|
|
50
53
|
export function refreshTokenPath(configPath) {
|
|
51
54
|
return join(dirname(resolvePath(configPath)), USER_TOKEN_FILE);
|
|
@@ -53,67 +56,157 @@ export function refreshTokenPath(configPath) {
|
|
|
53
56
|
/**
|
|
54
57
|
* Where the broker will look for it, which is beside the key — from the key's own point of view.
|
|
55
58
|
*
|
|
56
|
-
* @param privateKeyPath -
|
|
57
|
-
* @returns
|
|
59
|
+
* @param privateKeyPath - The path as the configuration records it.
|
|
60
|
+
* @returns The path the broker reads, or `undefined` when the key is not a file.
|
|
58
61
|
*/
|
|
59
62
|
export function brokerReadsPath(privateKeyPath) {
|
|
60
63
|
return privateKeyPath === undefined ? undefined : userTokenPath(privateKeyPath);
|
|
61
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Open and secure the token target before authorization, so a later path swap cannot redirect the
|
|
67
|
+
* write and a permission failure surfaces before the device flow.
|
|
68
|
+
*
|
|
69
|
+
* @param target - The token path.
|
|
70
|
+
* @returns The open descriptor, already mode 0600.
|
|
71
|
+
*/
|
|
72
|
+
export function openTokenTarget(target) {
|
|
73
|
+
mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
|
|
74
|
+
const flags = constants.O_WRONLY | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);
|
|
75
|
+
const fd = openSync(target, flags, 0o600);
|
|
76
|
+
try {
|
|
77
|
+
const info = fstatSync(fd);
|
|
78
|
+
if (!info.isFile()) {
|
|
79
|
+
throw new Error(`${target} is not a regular file`);
|
|
80
|
+
}
|
|
81
|
+
const uid = process.geteuid?.();
|
|
82
|
+
if (uid !== undefined && info.uid !== uid) {
|
|
83
|
+
throw new Error(`${target} is not owned by the current user`);
|
|
84
|
+
}
|
|
85
|
+
fchmodSync(fd, 0o600);
|
|
86
|
+
return fd;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
closeSync(fd);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Write the refresh token through the descriptor opened before the device flow. */
|
|
94
|
+
function writeToken(fd, token) {
|
|
95
|
+
ftruncateSync(fd, 0);
|
|
96
|
+
writeFileSync(fd, `${token}\n`);
|
|
97
|
+
// ACL-based shares can ignore the create/open mode; set it again after the write.
|
|
98
|
+
fchmodSync(fd, 0o600);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Decide where the refresh token goes, and whether that placement is allowed.
|
|
102
|
+
*
|
|
103
|
+
* @param input - The configuration path, target override and injectable guards.
|
|
104
|
+
* @returns The target and an optional warning.
|
|
105
|
+
* @throws {Error} When the placement would put the token inside a container mount without the
|
|
106
|
+
* explicit broker-container acknowledgement.
|
|
107
|
+
*/
|
|
108
|
+
export function resolveAuthorizationPlacement(input) {
|
|
109
|
+
const target = input.to ?? refreshTokenPath(input.configPath);
|
|
110
|
+
if ((input.isInsideContainer ?? isInsideContainer)()) {
|
|
111
|
+
throw new Error('run this on the host, not inside the container: the token must not land anywhere the container can read');
|
|
112
|
+
}
|
|
113
|
+
if (input.brokerContainer) {
|
|
114
|
+
return {
|
|
115
|
+
target,
|
|
116
|
+
warning: "writing inside the broker container's mounts; this is only safe when the target is not shared with the agent container",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const mounted = (input.mountedPath ?? insideMountedPath)(target);
|
|
120
|
+
if (mounted !== null) {
|
|
121
|
+
throw new Error(`${target} is inside ${mounted}, which is mounted into the container; the refresh token must live\n` +
|
|
122
|
+
' outside every mount — the deployment directory beside the configuration is the usual place');
|
|
123
|
+
}
|
|
124
|
+
return { target };
|
|
125
|
+
}
|
|
62
126
|
/**
|
|
63
127
|
* Run the `authorize` command.
|
|
64
128
|
*
|
|
65
|
-
* @param argv -
|
|
66
|
-
* @returns
|
|
129
|
+
* @param argv - Arguments after the command name.
|
|
130
|
+
* @returns The process exit code: 0 stored, 1 refused.
|
|
67
131
|
*/
|
|
68
132
|
import { assertProxyEnabled } from '../proxy.js';
|
|
69
133
|
export async function runAuthorize(argv) {
|
|
70
|
-
const args = parseArgs(argv, { booleans: ['help', 'no-proxy'] });
|
|
71
|
-
if (args.has('no-proxy') && args.value('proxy') !== undefined)
|
|
134
|
+
const args = parseArgs(argv, { booleans: ['broker-container', 'help', 'no-proxy'] });
|
|
135
|
+
if (args.has('no-proxy') && args.value('proxy') !== undefined) {
|
|
72
136
|
fail('--proxy and --no-proxy are mutually exclusive');
|
|
73
|
-
|
|
137
|
+
}
|
|
138
|
+
if (args.has('proxy') && args.value('proxy') === undefined) {
|
|
74
139
|
fail('--proxy needs a value');
|
|
140
|
+
}
|
|
75
141
|
const proxy = args.has('no-proxy') ? null : args.value('proxy');
|
|
76
|
-
if (proxy !== undefined && parseProxy(proxy) === false)
|
|
142
|
+
if (proxy !== undefined && parseProxy(proxy) === false) {
|
|
77
143
|
fail('--proxy must be an http(s) URL');
|
|
144
|
+
}
|
|
78
145
|
if (args.has('help') || args.has('h')) {
|
|
79
146
|
say(AUTHORIZE_USAGE);
|
|
80
147
|
return 0;
|
|
81
148
|
}
|
|
82
|
-
if (proxy === undefined)
|
|
149
|
+
if (proxy === undefined) {
|
|
83
150
|
assertProxyEnabled();
|
|
151
|
+
}
|
|
84
152
|
const configPath = resolveConfigPath(args.value('config'));
|
|
85
153
|
const host = args.value('host') ?? 'github.com';
|
|
86
154
|
const config = loadConfig(configPath);
|
|
87
155
|
const block = config.hosts[host];
|
|
88
|
-
if (block === undefined)
|
|
156
|
+
if (block === undefined) {
|
|
89
157
|
fail(`no host ${host} in ${configPath}`);
|
|
90
|
-
|
|
158
|
+
}
|
|
159
|
+
if (block.provider !== 'github-app') {
|
|
91
160
|
fail(`${host} is not a github-app host`);
|
|
161
|
+
}
|
|
92
162
|
if (block.clientId === undefined) {
|
|
93
163
|
fail(`${host} has no clientId — a device flow needs the app's client id, and the App ID will not do`);
|
|
94
164
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
165
|
+
let placement;
|
|
166
|
+
try {
|
|
167
|
+
placement = resolveAuthorizationPlacement({
|
|
168
|
+
configPath,
|
|
169
|
+
to: args.value('to'),
|
|
170
|
+
brokerContainer: args.has('broker-container'),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
fail(error.message);
|
|
175
|
+
}
|
|
176
|
+
if (placement.warning !== undefined) {
|
|
177
|
+
warn(placement.warning);
|
|
178
|
+
}
|
|
179
|
+
say(`the refresh token will be written to ${placement.target}`);
|
|
180
|
+
let tokenFd;
|
|
181
|
+
try {
|
|
182
|
+
tokenFd = openTokenTarget(placement.target);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
fail(`could not open ${placement.target}: ${error.message}`);
|
|
186
|
+
}
|
|
187
|
+
let failure;
|
|
188
|
+
try {
|
|
189
|
+
const fetcher = fetcherFor(proxy);
|
|
190
|
+
const code = await startDeviceFlow(block.clientId, fetcher);
|
|
191
|
+
say(`Open ${code.verificationUri} and enter: ${code.userCode}`);
|
|
192
|
+
say(`Waiting up to ${Math.round(code.expiresIn / 60)} minutes for that to be authorized…`);
|
|
193
|
+
const token = await collectUserToken(block.clientId, code, fetcher, ms => new Promise(done => setTimeout(done, ms)));
|
|
194
|
+
if (token.refreshToken === undefined) {
|
|
195
|
+
throw new Error('GitHub returned no refresh token, so the token would expire in eight hours with no way to renew.\n' +
|
|
196
|
+
' Select "Expire user authorization tokens" in the app settings and authorize again.');
|
|
197
|
+
}
|
|
198
|
+
writeToken(tokenFd, token.refreshToken);
|
|
114
199
|
}
|
|
115
|
-
|
|
116
|
-
|
|
200
|
+
catch (error) {
|
|
201
|
+
failure = error;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
closeSync(tokenFd);
|
|
205
|
+
}
|
|
206
|
+
if (failure !== undefined) {
|
|
207
|
+
fail(failure.message);
|
|
208
|
+
}
|
|
209
|
+
say(`stored the refresh token in ${placement.target} (mode 600)`);
|
|
117
210
|
const readByBroker = brokerReadsPath(block.privateKeyPath);
|
|
118
211
|
if (readByBroker === undefined) {
|
|
119
212
|
// An inline privateKeyPem deployment: the broker has no directory to derive a path from, so it never
|
|
@@ -121,12 +214,12 @@ export async function runAuthorize(argv) {
|
|
|
121
214
|
say('note: this host block keeps its key inline, so the broker has nowhere to look for this file:');
|
|
122
215
|
say(' pull requests will stay app-authored until the key is a file (privateKeyPath)');
|
|
123
216
|
}
|
|
124
|
-
else if (resolvePath(readByBroker)
|
|
125
|
-
say(
|
|
126
|
-
say(' writable, because GitHub rotates the token on every exchange');
|
|
217
|
+
else if (resolvePath(readByBroker) === resolvePath(placement.target)) {
|
|
218
|
+
say('which is where the broker looks for it — keep it writable, since the token is rotated on every use');
|
|
127
219
|
}
|
|
128
220
|
else {
|
|
129
|
-
say(
|
|
221
|
+
say(`note: the broker will look in ${readByBroker} — make sure that is this file, and that it is`);
|
|
222
|
+
say(' writable, because GitHub rotates the token on every exchange');
|
|
130
223
|
}
|
|
131
224
|
say('the broker will use it for pull requests authored by that person; nothing else needs running');
|
|
132
225
|
return 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,16 +8,14 @@ export interface ComposeInput {
|
|
|
8
8
|
readonly socketDir: string;
|
|
9
9
|
/** `uid:gid` the sidecar runs as; must match the container that needs the socket. */
|
|
10
10
|
readonly user: string;
|
|
11
|
-
/** Base image. */
|
|
11
|
+
/** Base image; the released host image contains the `gcb` binary. */
|
|
12
12
|
readonly image: string;
|
|
13
|
-
/** npm spec to run, e.g. `gcb@0.0.0`. */
|
|
14
|
-
readonly packageSpec: string;
|
|
15
13
|
/** Compose service and container name. */
|
|
16
14
|
readonly name: string;
|
|
17
15
|
/**
|
|
18
16
|
* Host directory holding the broker's code, as `stage` exports it. When given, the sidecar runs
|
|
19
|
-
* that code with the image's Node instead of
|
|
20
|
-
* at boot, and the commit that runs is the one you reviewed.
|
|
17
|
+
* that code with the image's Node instead of running the image's installed binary: no registry
|
|
18
|
+
* access at boot, and the commit that runs is the one you reviewed.
|
|
21
19
|
*/
|
|
22
20
|
readonly code?: string;
|
|
23
21
|
}
|
|
@@ -26,17 +24,17 @@ export declare const CODE_DIR = "/opt/gcb";
|
|
|
26
24
|
/**
|
|
27
25
|
* Render the compose file.
|
|
28
26
|
*
|
|
29
|
-
* @param input -
|
|
30
|
-
* @returns
|
|
27
|
+
* @param input - The resolved options.
|
|
28
|
+
* @returns The YAML, newline-terminated.
|
|
31
29
|
*/
|
|
32
30
|
export declare function renderCompose(input: ComposeInput): string;
|
|
33
31
|
/** Usage text for `compose`. */
|
|
34
|
-
export declare const COMPOSE_USAGE = "Usage: gcb compose [options]\n\nPrint a Docker sidecar definition for the broker (to stdout). Pair it with a config written by\n`init --mode sidecar`, whose paths this file fixes.\n\n --dir <path> Host directory holding broker.config.yml, app.pem and log/ (default: the config's directory)\n --config <path> The sidecar's config, checked before anything is printed\n (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)\n --socket-dir <path> Host directory the pushing container already mounts\n --code <path> Run code mounted from this host directory instead of
|
|
32
|
+
export declare const COMPOSE_USAGE = "Usage: gcb compose [options]\n\nPrint a Docker sidecar definition for the broker (to stdout). Pair it with a config written by\n`init --mode sidecar`, whose paths this file fixes.\n\n --dir <path> Host directory holding broker.config.yml, app.pem and log/ (default: the config's directory)\n --config <path> The sidecar's config, checked before anything is printed\n (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)\n --socket-dir <path> Host directory the pushing container already mounts\n --code <path> Run code mounted from this host directory instead of the installed binary\n (as `stage` exports it); nothing is fetched or built at boot\n --user <uid:gid> UID the sidecar runs as (default: this user)\n --image <name> Base image (default: ghcr.io/code-vaults/gcb-host:<version>)\n --name <name> Service and container name (default: gcb)\n -h, --help Show this help\n";
|
|
35
33
|
/**
|
|
36
34
|
* Run the `compose` command.
|
|
37
35
|
*
|
|
38
|
-
* @param argv -
|
|
39
|
-
* @returns
|
|
36
|
+
* @param argv - Arguments after the command name.
|
|
37
|
+
* @returns The process exit code.
|
|
40
38
|
*/
|
|
41
39
|
export declare function runCompose(argv: readonly string[]): number;
|
|
42
40
|
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/commands/compose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/commands/compose.ts"],"names":[],"mappings":"AAyBA,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,uDAAuD;AACvD,eAAO,MAAM,QAAQ,aAAa,CAAA;AAsBlC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAmHzD;AAED,gCAAgC;AAChC,eAAO,MAAM,aAAa,88BAezB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CA6D1D"}
|
package/lib/commands/compose.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `compose` — print a Docker sidecar definition for the broker.
|
|
3
3
|
*
|
|
4
|
-
* The broker runs in its own container with the private key mounted read-only, no published
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The broker runs in its own container with the private key mounted read-only, no published ports,
|
|
5
|
+
* no capabilities and a read-only root filesystem; the only surface it shares with anything else is
|
|
6
|
+
* the socket directory. The released image contains the package, so there is no checkout to copy and
|
|
7
|
+
* nothing to build on the host.
|
|
8
8
|
*
|
|
9
|
-
* The mount targets are constants from `deployment.ts`, shared with `init --mode sidecar`, so
|
|
10
|
-
*
|
|
9
|
+
* The mount targets are constants from `deployment.ts`, shared with `init --mode sidecar`, so the
|
|
10
|
+
* paths recorded in `broker.config.yml` and the paths this file mounts cannot drift apart.
|
|
11
11
|
*/
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { fail, parseArgs, say, warn } from 'gcb-core';
|
|
17
|
-
import { USER_TOKEN_FILE } from '../config.js';
|
|
14
|
+
import { PACKAGE_VERSION, fail, parseArgs, say, warn } from 'gcb-core';
|
|
15
|
+
import { inferMode, loadConfig, resolveConfigPath, deploymentDir, SIDECAR, USER_TOKEN_FILE, } from '../config.js';
|
|
18
16
|
/** Where a code-mounted sidecar expects the broker. */
|
|
19
17
|
export const CODE_DIR = '/opt/gcb';
|
|
20
18
|
/** A volume scalar as YAML, with compose's dollar escaping applied first. */
|
|
@@ -30,16 +28,17 @@ function selectedConfigFile(configPath) {
|
|
|
30
28
|
return fs.realpathSync(configPath);
|
|
31
29
|
}
|
|
32
30
|
catch (error) {
|
|
33
|
-
if (error.code === 'ENOENT')
|
|
31
|
+
if (error.code === 'ENOENT') {
|
|
34
32
|
return configPath;
|
|
33
|
+
}
|
|
35
34
|
throw error;
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
39
38
|
* Render the compose file.
|
|
40
39
|
*
|
|
41
|
-
* @param input -
|
|
42
|
-
* @returns
|
|
40
|
+
* @param input - The resolved options.
|
|
41
|
+
* @returns The YAML, newline-terminated.
|
|
43
42
|
*/
|
|
44
43
|
export function renderCompose(input) {
|
|
45
44
|
const socketDir = input.socketDir.replace(/\/+$/, '');
|
|
@@ -54,14 +53,8 @@ export function renderCompose(input) {
|
|
|
54
53
|
? `# It runs the code mounted read-only from ${input.code}, which is whatever \`stage\` put there —
|
|
55
54
|
# see the STAGED.json beside it for the commit. stage does not install dependencies or build:
|
|
56
55
|
# run \`corepack yarn install\` in that directory first, then the source entry point runs here.`
|
|
57
|
-
: `# The image
|
|
58
|
-
#
|
|
59
|
-
# npx --yes --package '${input.packageSpec}' gcb daemon
|
|
60
|
-
#
|
|
61
|
-
# To avoid depending on the registry at boot, either render this file with --code <directory> to
|
|
62
|
-
# run staged code from a mount, or point --image at an image of your own that already has the
|
|
63
|
-
# package installed, and change \`command\` to the binary in it:
|
|
64
|
-
# ["gcb", "daemon", "--config", "${mountedConfigPath}"]`;
|
|
56
|
+
: `# The image contains gcb-host and the command below runs its installed \`gcb\` binary.
|
|
57
|
+
# To pin a reviewed commit instead, render this file with --code <directory>.`;
|
|
65
58
|
// The egress lines belong to both renders: a --code deployment is a sidecar too, and without them it
|
|
66
59
|
// would carry no proxy variables at all, so the daemon would go direct and hang rather than refuse.
|
|
67
60
|
const egress = `
|
|
@@ -73,13 +66,8 @@ export function renderCompose(input) {
|
|
|
73
66
|
- NODE_USE_ENV_PROXY=1
|
|
74
67
|
# Runtime override: a URL pins a proxy, an empty value goes direct, unset keeps the above.
|
|
75
68
|
- GCB_PROXY=\${GCB_PROXY-default}`;
|
|
76
|
-
const environment =
|
|
77
|
-
|
|
78
|
-
environment:${egress}`
|
|
79
|
-
: `
|
|
80
|
-
environment:
|
|
81
|
-
# npx needs somewhere writable for its cache; /tmp is a tmpfs above.
|
|
82
|
-
- npm_config_cache=/tmp/npm-cache${egress}`;
|
|
69
|
+
const environment = `
|
|
70
|
+
environment:${egress}`;
|
|
83
71
|
// A bind mount for a path that does not exist becomes a directory, and the broker would then read a
|
|
84
72
|
// directory instead of a token — for every action, not only the personal ones. Emit it once `authorize`
|
|
85
73
|
// has written the file, and say what to run until then.
|
|
@@ -89,14 +77,14 @@ export function renderCompose(input) {
|
|
|
89
77
|
'\n # for a path that does not exist would become a directory, and the broker would read that.';
|
|
90
78
|
const start = fromCode
|
|
91
79
|
? `exec node --conditions=development ${CODE_DIR}/packages/host/bin/gcb.js daemon --config ${mountedConfigArg}`
|
|
92
|
-
: `exec
|
|
80
|
+
: `exec gcb daemon --config ${mountedConfigArg}`;
|
|
93
81
|
const command = ` - sh
|
|
94
82
|
- -c
|
|
95
83
|
- |
|
|
96
|
-
case "
|
|
84
|
+
case "$\${GCB_PROXY-default}" in
|
|
97
85
|
default) ;;
|
|
98
86
|
"") unset http_proxy https_proxy all_proxy no_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY NODE_USE_ENV_PROXY ;;
|
|
99
|
-
*) export http_proxy="
|
|
87
|
+
*) export http_proxy="$$GCB_PROXY" https_proxy="$$GCB_PROXY" no_proxy=localhost,127.0.0.1 NODE_USE_ENV_PROXY=1 ;;
|
|
100
88
|
esac
|
|
101
89
|
${start}`;
|
|
102
90
|
const codeMount = fromCode
|
|
@@ -166,19 +154,18 @@ Print a Docker sidecar definition for the broker (to stdout). Pair it with a con
|
|
|
166
154
|
--config <path> The sidecar's config, checked before anything is printed
|
|
167
155
|
(default: $GIT_BROKER_CONFIG, else ./broker.config.yml)
|
|
168
156
|
--socket-dir <path> Host directory the pushing container already mounts
|
|
169
|
-
--code <path> Run code mounted from this host directory instead of
|
|
170
|
-
|
|
157
|
+
--code <path> Run code mounted from this host directory instead of the installed binary
|
|
158
|
+
(as \`stage\` exports it); nothing is fetched or built at boot
|
|
171
159
|
--user <uid:gid> UID the sidecar runs as (default: this user)
|
|
172
|
-
--image <name> Base image (default:
|
|
173
|
-
--package <spec> npm spec to run (default: gcb-host@<version>)
|
|
160
|
+
--image <name> Base image (default: ghcr.io/code-vaults/gcb-host:<version>)
|
|
174
161
|
--name <name> Service and container name (default: gcb)
|
|
175
162
|
-h, --help Show this help
|
|
176
163
|
`;
|
|
177
164
|
/**
|
|
178
165
|
* Run the `compose` command.
|
|
179
166
|
*
|
|
180
|
-
* @param argv -
|
|
181
|
-
* @returns
|
|
167
|
+
* @param argv - Arguments after the command name.
|
|
168
|
+
* @returns The process exit code.
|
|
182
169
|
*/
|
|
183
170
|
export function runCompose(argv) {
|
|
184
171
|
const args = parseArgs(argv, { booleans: ['help'] });
|
|
@@ -186,13 +173,16 @@ export function runCompose(argv) {
|
|
|
186
173
|
say(COMPOSE_USAGE);
|
|
187
174
|
return 0;
|
|
188
175
|
}
|
|
176
|
+
if (argv.some(argument => argument === '--package' || argument.startsWith('--package='))) {
|
|
177
|
+
fail('--package is not supported; pin the released image with --image instead');
|
|
178
|
+
}
|
|
189
179
|
const explicitConfig = args.value('config');
|
|
190
180
|
const dirArg = args.value('dir');
|
|
191
|
-
const configPath = path.resolve(explicitConfig
|
|
192
|
-
?
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
181
|
+
const configPath = path.resolve(explicitConfig === undefined
|
|
182
|
+
? dirArg === undefined
|
|
183
|
+
? resolveConfigPath()
|
|
184
|
+
: path.join(dirArg, 'broker.config.yml')
|
|
185
|
+
: resolveConfigPath(explicitConfig));
|
|
196
186
|
const configFile = selectedConfigFile(configPath);
|
|
197
187
|
const dir = path.resolve(dirArg ?? deploymentDir(configFile));
|
|
198
188
|
const socketDir = args.value('socket-dir');
|
|
@@ -225,8 +215,7 @@ export function runCompose(argv) {
|
|
|
225
215
|
configPath: configFile,
|
|
226
216
|
socketDir,
|
|
227
217
|
user,
|
|
228
|
-
image: args.value('image') ??
|
|
229
|
-
packageSpec: args.value('package') ?? `gcb-host@${PACKAGE_VERSION}`,
|
|
218
|
+
image: args.value('image') ?? `ghcr.io/code-vaults/gcb-host:${PACKAGE_VERSION}`,
|
|
230
219
|
name: args.value('name') ?? 'gcb',
|
|
231
220
|
code: args.value('code'),
|
|
232
221
|
}));
|