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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.200.0",
3
+ "version": "0.201.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -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
- setImmediate(() => {
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,6 +1,3 @@
1
1
  #! /bin/bash
2
- cd ~/machine-alwaysup
3
- yarn machine-alwaysup
4
-
5
2
  tmux new -s machine-alwaysup -d
6
3
  tmux send-keys -t machine-alwaysup "cd ~/machine-alwaysup && yarn machine-alwaysup" Enter
@@ -1,10 +1,158 @@
1
1
  import { getBackblazePath } from "../-a-archives/archivesBackBlaze";
2
- import { setGitRef, getGitURLLive, getGitRefLive } from "../4-deploy/git";
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 setGitRef
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
- await setGitRef({ gitFolder: "~/machine-alwaysup", repoUrl: gitURLLive, gitRef: gitRefLive });
71
- console.log("āœ… Repository cloned and set to correct reference");
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} "cat > ~/machine-startup.sh << 'EOF'
83
- #!/bin/bash
84
- cd ~/machine-alwaysup/src/deployManager
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
- import { addLocalLog } from "../errorLogs/ErrorLogController";
6
- import { LogType } from "../errorLogs/ErrorLogCore";
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