querysub 0.200.0 ā 0.201.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.
package/package.json
CHANGED
package/src/-0-hooks/hooks.ts
CHANGED
|
@@ -12,13 +12,13 @@ function createHookFunction<Fnc extends (...args: any[]) => void>(debugName: str
|
|
|
12
12
|
} {
|
|
13
13
|
let queuedCalls = [] as Parameters<Fnc>[];
|
|
14
14
|
let declaration: Fnc | undefined;
|
|
15
|
-
|
|
15
|
+
setTimeout(() => {
|
|
16
16
|
if (!declaration) {
|
|
17
17
|
setTimeout(flushQueued, 1000);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
flushQueued();
|
|
21
|
-
});
|
|
21
|
+
}, 1000);
|
|
22
22
|
function flushQueued() {
|
|
23
23
|
if (!declaration) {
|
|
24
24
|
throw new Error(`Hook function ${debugName} not declared`);
|
|
@@ -1,10 +1,158 @@
|
|
|
1
1
|
import { getBackblazePath } from "../-a-archives/archivesBackBlaze";
|
|
2
|
-
import {
|
|
2
|
+
import { getGitURLLive, getGitRefLive } from "../4-deploy/git";
|
|
3
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
3
4
|
import { runPromise } from "../functional/runCommand";
|
|
4
5
|
import fs from "fs";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import readline from "readline";
|
|
8
|
+
import open from "open";
|
|
9
|
+
// Import querysub, to fix missing dependencies
|
|
10
|
+
Querysub;
|
|
5
11
|
|
|
6
12
|
const pinnedNodeVersion = 22;
|
|
7
13
|
|
|
14
|
+
async function getGitHubApiKey(repoUrl: string, sshRemote: string): Promise<string> {
|
|
15
|
+
// Parse repository info from URL
|
|
16
|
+
let repoOwner = "";
|
|
17
|
+
let repoName = "";
|
|
18
|
+
// Handle both SSH and HTTPS formats
|
|
19
|
+
// SSH: git@github.com:owner/repo.git
|
|
20
|
+
// HTTPS: https://github.com/owner/repo.git
|
|
21
|
+
const sshMatch = repoUrl.match(/git@github\.com:([^/]+)\/(.+)\.git$/);
|
|
22
|
+
const httpsMatch = repoUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+)\.git$/);
|
|
23
|
+
|
|
24
|
+
if (sshMatch) {
|
|
25
|
+
repoOwner = sshMatch[1];
|
|
26
|
+
repoName = sshMatch[2];
|
|
27
|
+
} else if (httpsMatch) {
|
|
28
|
+
repoOwner = httpsMatch[1];
|
|
29
|
+
repoName = httpsMatch[2];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cacheFile = os.homedir() + `/githubkey_${repoOwner}_${repoName}.json`;
|
|
33
|
+
|
|
34
|
+
// Check if we have a cached key
|
|
35
|
+
if (fs.existsSync(cacheFile)) {
|
|
36
|
+
try {
|
|
37
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf8"));
|
|
38
|
+
if (cached.apiKey) {
|
|
39
|
+
console.log(`ā
Using cached GitHub API key from ${cacheFile}`);
|
|
40
|
+
return cached.apiKey;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Invalid cache file, we'll ask for a new key
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Need to get a new API key from user
|
|
48
|
+
console.log("\nš GitHub API key required for private repository access");
|
|
49
|
+
console.log("Opening GitHub token creation page...");
|
|
50
|
+
|
|
51
|
+
// Construct URL for classic token (fine-grained tokens don't support deploy keys)
|
|
52
|
+
const repoInfo = repoOwner && repoName ? ` for repository ${repoOwner}/${repoName}` : "";
|
|
53
|
+
const instructions = `yarn setup-machine ${sshRemote}${repoInfo}
|
|
54
|
+
1) Set expiration to 'No expiration'
|
|
55
|
+
2) Check the 'repo' scope (full repository access)
|
|
56
|
+
3) Click 'Generate token'`;
|
|
57
|
+
|
|
58
|
+
let tokenUrl = `https://github.com/settings/tokens/new?description=${encodeURIComponent(instructions)}&scopes=repo`;
|
|
59
|
+
if (repoOwner && repoName) {
|
|
60
|
+
console.log(`Setting up access for repository: ${repoOwner}/${repoName}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await open(tokenUrl);
|
|
64
|
+
|
|
65
|
+
const rl = readline.createInterface({
|
|
66
|
+
input: process.stdin,
|
|
67
|
+
output: process.stdout
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const apiKey = await new Promise<string>((resolve) => {
|
|
71
|
+
const repoInfo = repoOwner && repoName ? ` for repository ${repoOwner}/${repoName}` : "";
|
|
72
|
+
rl.question(`Please paste your GitHub API token (${repoInfo}): `, (answer) => {
|
|
73
|
+
rl.close();
|
|
74
|
+
resolve(answer.trim());
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Cache the key
|
|
79
|
+
fs.writeFileSync(cacheFile, JSON.stringify({ apiKey }));
|
|
80
|
+
console.log(`ā
Caching GitHub API key to ${cacheFile}`);
|
|
81
|
+
|
|
82
|
+
return apiKey;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function addDeployKeyToGitHub(sshPublicKey: string, keyTitle: string, repoUrl: string, sshRemote: string): Promise<void> {
|
|
86
|
+
const apiKey = await getGitHubApiKey(repoUrl, sshRemote);
|
|
87
|
+
|
|
88
|
+
// Parse repository info from URL to get owner/repo for the API endpoint
|
|
89
|
+
let repoOwner = "";
|
|
90
|
+
let repoName = "";
|
|
91
|
+
const sshMatch = repoUrl.match(/git@github\.com:([^/]+)\/(.+)\.git$/);
|
|
92
|
+
const httpsMatch = repoUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+)\.git$/);
|
|
93
|
+
|
|
94
|
+
if (sshMatch) {
|
|
95
|
+
repoOwner = sshMatch[1];
|
|
96
|
+
repoName = sshMatch[2];
|
|
97
|
+
} else if (httpsMatch) {
|
|
98
|
+
repoOwner = httpsMatch[1];
|
|
99
|
+
repoName = httpsMatch[2];
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error(`Could not parse GitHub repository from URL: ${repoUrl}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let url = `https://api.github.com/repos/${repoOwner}/${repoName}/keys`;
|
|
105
|
+
console.log(url);
|
|
106
|
+
const response = await fetch(url, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: {
|
|
109
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
110
|
+
"Accept": "application/vnd.github+json",
|
|
111
|
+
"Content-Type": "application/json"
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
title: keyTitle,
|
|
115
|
+
key: sshPublicKey,
|
|
116
|
+
read_only: true
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const errorText = await response.text();
|
|
122
|
+
throw new Error(`Failed to add deploy key to GitHub repository: ${response.status} ${errorText}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log("ā
Deploy key added to GitHub repository");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function setupRepositoryOnRemote(sshRemote: string, gitURLLive: string, gitRefLive: string): Promise<void> {
|
|
129
|
+
// Create git folder on remote
|
|
130
|
+
await runPromise(`ssh ${sshRemote} "mkdir -p ~/machine-alwaysup"`);
|
|
131
|
+
|
|
132
|
+
// Add bitbucket.org host key to remote machine's known_hosts
|
|
133
|
+
await runPromise(`ssh ${sshRemote} "mkdir -p ~/.ssh"`);
|
|
134
|
+
await runPromise(`ssh ${sshRemote} "ssh-keyscan -t rsa bitbucket.org >> ~/.ssh/known_hosts 2>/dev/null || true"`);
|
|
135
|
+
await runPromise(`ssh ${sshRemote} "ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 2>/dev/null || true"`);
|
|
136
|
+
|
|
137
|
+
// Check if repo already exists, if not clone it
|
|
138
|
+
try {
|
|
139
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git status"`);
|
|
140
|
+
console.log("Repository already exists, updating...");
|
|
141
|
+
// Repository exists, update it
|
|
142
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git remote update"`);
|
|
143
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git add --all"`);
|
|
144
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git stash"`);
|
|
145
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git fetch --all"`);
|
|
146
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git reset --hard ${gitRefLive}"`);
|
|
147
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git prune"`);
|
|
148
|
+
} catch {
|
|
149
|
+
console.log("Cloning repository...");
|
|
150
|
+
// Repository doesn't exist, clone it
|
|
151
|
+
await runPromise(`ssh ${sshRemote} "git clone ${gitURLLive} ~/machine-alwaysup"`);
|
|
152
|
+
await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git reset --hard ${gitRefLive}"`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
8
156
|
async function main() {
|
|
9
157
|
let sshRemote = process.argv.slice(2).join(" ");
|
|
10
158
|
if (!sshRemote) {
|
|
@@ -39,6 +187,17 @@ async function main() {
|
|
|
39
187
|
console.log("ā
Git installed");
|
|
40
188
|
}
|
|
41
189
|
|
|
190
|
+
// 3. Ensure build tools are installed (needed for native modules)
|
|
191
|
+
console.log("Ensuring build tools are installed...");
|
|
192
|
+
try {
|
|
193
|
+
await runPromise(`ssh ${sshRemote} "which make"`);
|
|
194
|
+
console.log("ā
Build tools already installed");
|
|
195
|
+
} catch {
|
|
196
|
+
console.log("Installing build tools...");
|
|
197
|
+
await runPromise(`ssh ${sshRemote} "sudo apt install -y build-essential"`);
|
|
198
|
+
console.log("ā
Build tools installed");
|
|
199
|
+
}
|
|
200
|
+
|
|
42
201
|
// 3. Ensure nodejs is installed on remote server
|
|
43
202
|
console.log("Ensuring Node.js is installed...");
|
|
44
203
|
try {
|
|
@@ -63,12 +222,47 @@ async function main() {
|
|
|
63
222
|
console.log("ā
Yarn installed");
|
|
64
223
|
}
|
|
65
224
|
|
|
66
|
-
// 5. Clone current repo into ~/machine-alwaysup with
|
|
225
|
+
// 5. Clone current repo into ~/machine-alwaysup with SSH key handling for private repos
|
|
67
226
|
console.log("Setting up repository...");
|
|
68
227
|
let gitURLLive = await getGitURLLive();
|
|
69
228
|
let gitRefLive = await getGitRefLive();
|
|
70
|
-
|
|
71
|
-
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
await setupRepositoryOnRemote(sshRemote, gitURLLive, gitRefLive);
|
|
232
|
+
console.log("ā
Repository cloned and set to correct reference");
|
|
233
|
+
} catch (error) {
|
|
234
|
+
// Check if this is a private repository access issue
|
|
235
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
236
|
+
if (errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found") || errorMessage.includes("fatal: Could not read from remote repository")) {
|
|
237
|
+
console.log("ā ļø Repository appears to be private, setting up SSH key access...");
|
|
238
|
+
|
|
239
|
+
// Ensure SSH key exists on remote machine
|
|
240
|
+
await runPromise(`ssh ${sshRemote} "mkdir -p ~/.ssh"`);
|
|
241
|
+
try {
|
|
242
|
+
await runPromise(`ssh ${sshRemote} "test -f ~/.ssh/id_rsa"`);
|
|
243
|
+
console.log("ā
SSH key already exists on remote machine");
|
|
244
|
+
} catch {
|
|
245
|
+
console.log("Generating SSH key on remote machine...");
|
|
246
|
+
await runPromise(`ssh ${sshRemote} "ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ''"`);
|
|
247
|
+
console.log("ā
SSH key generated on remote machine");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Get the public key from remote machine
|
|
251
|
+
const sshPublicKey = await runPromise(`ssh ${sshRemote} "cat ~/.ssh/id_rsa.pub"`);
|
|
252
|
+
const keyTitle = `Machine Setup - ${sshRemote} - ${new Date().toISOString()}`;
|
|
253
|
+
|
|
254
|
+
// Add the deploy key to GitHub repository
|
|
255
|
+
await addDeployKeyToGitHub(sshPublicKey.trim(), keyTitle, gitURLLive, sshRemote);
|
|
256
|
+
|
|
257
|
+
// Retry repository setup
|
|
258
|
+
console.log("Retrying repository setup with SSH key...");
|
|
259
|
+
await setupRepositoryOnRemote(sshRemote, gitURLLive, gitRefLive);
|
|
260
|
+
console.log("ā
Repository cloned and set to correct reference");
|
|
261
|
+
} else {
|
|
262
|
+
// Re-throw other errors
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
72
266
|
|
|
73
267
|
// Install dependencies
|
|
74
268
|
console.log("Installing dependencies...");
|
|
@@ -79,11 +273,9 @@ async function main() {
|
|
|
79
273
|
console.log("Setting up cron job...");
|
|
80
274
|
|
|
81
275
|
// Create startup script directly on remote machine
|
|
82
|
-
await runPromise(`ssh ${sshRemote} "
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
bash machine.sh
|
|
86
|
-
EOF"`);
|
|
276
|
+
await runPromise(`ssh ${sshRemote} "echo '#!/bin/bash' > ~/machine-startup.sh"`);
|
|
277
|
+
await runPromise(`ssh ${sshRemote} "echo 'cd ~/machine-alwaysup/node_modules/querysub/src/deployManager' >> ~/machine-startup.sh"`);
|
|
278
|
+
await runPromise(`ssh ${sshRemote} "echo 'bash machine.sh' >> ~/machine-startup.sh"`);
|
|
87
279
|
await runPromise(`ssh ${sshRemote} "chmod +x ~/machine-startup.sh"`);
|
|
88
280
|
|
|
89
281
|
// 7. Setup crontab to run ~/machine-startup.sh on startup
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
4) Test on a new server, that we run temporarily with some test scripts
|
|
21
21
|
|
|
22
|
+
5) OH! Also setup swap usage on the machine, as it isn't by default often?
|
|
23
|
+
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
5) Show command for our setup script in the web UI, in case we forget it (basically just instructions)
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { canHaveChildren } from "socket-function/src/types";
|
|
2
|
-
import { logDisk } from "./diskLogger";
|
|
3
2
|
import { isNode } from "typesafecss";
|
|
4
3
|
import { red, yellow } from "socket-function/src/formatting/logColors";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
let addLocalLogModule: (typeof addLocalLogPromise extends Promise<infer T> ? T : never) | undefined;
|
|
6
|
+
const addLocalLogPromise = import("../errorLogs/ErrorLogController").then(x => {
|
|
7
|
+
(addLocalLogModule as any) = x;
|
|
8
|
+
return x;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
let diskLoggerModule: typeof diskLoggerPromise extends Promise<infer T> ? T : never;
|
|
12
|
+
const diskLoggerPromise = import("./diskLogger").then(x => {
|
|
13
|
+
(diskLoggerModule as any) = x;
|
|
14
|
+
return x;
|
|
15
|
+
});
|
|
16
|
+
|
|
7
17
|
|
|
8
18
|
let shimmed = false;
|
|
9
19
|
export function shimConsoleLogs() {
|
|
@@ -26,10 +36,10 @@ export function shimConsoleLogs() {
|
|
|
26
36
|
args.length > 0
|
|
27
37
|
&& String(args[0]).trim().length > 0
|
|
28
38
|
) {
|
|
29
|
-
if (typeof logDisk === "function") {
|
|
39
|
+
if (typeof diskLoggerModule?.logDisk === "function") {
|
|
30
40
|
// Don't call it directly, so we don't get extra line debug context added to this call
|
|
31
41
|
// (as it wouldn't be useful, as we really want the caller)
|
|
32
|
-
let stopDoubleShim = logDisk;
|
|
42
|
+
let stopDoubleShim = diskLoggerModule.logDisk;
|
|
33
43
|
stopDoubleShim(...args, { type: fncName });
|
|
34
44
|
}
|
|
35
45
|
}
|
|
@@ -39,10 +49,10 @@ export function shimConsoleLogs() {
|
|
|
39
49
|
// Filter out objects added by injectFileLocationToConsole
|
|
40
50
|
args = args.filter(x => !(canHaveChildren(x) && x["__FILE__"]));
|
|
41
51
|
|
|
42
|
-
if (typeof addLocalLog === "function") {
|
|
52
|
+
if (typeof addLocalLogModule?.addLocalLog === "function") {
|
|
43
53
|
if (fncName === "error" || fncName === "warn") {
|
|
44
54
|
// ALSO, track the logs in a file, for error notifications, etc
|
|
45
|
-
addLocalLog({ message: args.join(" | ") + " | " + fileObj?.["__FILE__"], time: Date.now() }, fncName);
|
|
55
|
+
addLocalLogModule.addLocalLog({ message: args.join(" | ") + " | " + fileObj?.["__FILE__"], time: Date.now() }, fncName);
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
|