querysub 0.557.0 → 0.558.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/appSecrets.ts +1 -52
- package/package.json +1 -1
- package/src/-a-archives/archivesBackBlaze.ts +1 -1
- package/src/4-querysub/Querysub.ts +0 -1
- package/src/deployManager/components/RouteConfigView.tsx +28 -2
- package/src/deployManager/setupMachineMain.ts +1 -1
- package/src/library-components/URLParam.ts +6 -0
- package/src/misc/appPaths.ts +56 -0
package/appSecrets.ts
CHANGED
|
@@ -1,62 +1,11 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import { isNode } from "socket-function/src/misc";
|
|
4
2
|
import { lazy } from "socket-function/src/caching";
|
|
5
3
|
import { ArchivesBackblaze } from "sliftutils/storage/backblaze";
|
|
6
|
-
import {
|
|
4
|
+
import { STORAGE_DIR, getDomain, getBackblazePath } from "./src/misc/appPaths";
|
|
7
5
|
import { Querysub } from "./src/4-querysub/Querysub";
|
|
8
6
|
|
|
9
|
-
// getSecret always imports us relative to the process cwd, so the cwd is the repo root and this matches what getStorageDir would resolve
|
|
10
|
-
const STORAGE_DIR = "./database-storage/";
|
|
11
|
-
|
|
12
|
-
// querysubConfig and getDomain are copied verbatim from src/config.ts, so this file has no dependencies on our own application (parseArgsFactory is fine to import, as it has no dependencies of its own)
|
|
13
|
-
|
|
14
|
-
let yargObj = parseArgsFactory()
|
|
15
|
-
.option("domain", { type: "string", desc: `Sets the domain` })
|
|
16
|
-
.argv
|
|
17
|
-
;
|
|
18
|
-
|
|
19
|
-
type QuerysubConfig = {
|
|
20
|
-
domain?: string;
|
|
21
|
-
emaildomain?: string;
|
|
22
|
-
notifyemails?: string[];
|
|
23
|
-
};
|
|
24
|
-
let querysubConfig = lazy((): QuerysubConfig => {
|
|
25
|
-
if (!isNode()) throw new Error("querysubConfig is only available on the server");
|
|
26
|
-
const path = "./querysub.json";
|
|
27
|
-
if (!fs.existsSync(path)) {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(fs.readFileSync(path, "utf8"));
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error("Error parsing querysub.json", e);
|
|
34
|
-
return {};
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export function getDomain() {
|
|
39
|
-
if (!isNode()) {
|
|
40
|
-
return location.hostname.split(".").slice(-2).join(".");
|
|
41
|
-
}
|
|
42
|
-
return yargObj.domain || querysubConfig().domain || "querysub.com";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
7
|
const keysArchives = lazy(() => new ArchivesBackblaze({ bucketName: getDomain() }));
|
|
46
8
|
|
|
47
|
-
export function getBackblazePath() {
|
|
48
|
-
let testPaths = [
|
|
49
|
-
STORAGE_DIR + "backblaze.json",
|
|
50
|
-
os.homedir() + "/backblaze.json",
|
|
51
|
-
];
|
|
52
|
-
for (let path of testPaths) {
|
|
53
|
-
if (fs.existsSync(path)) {
|
|
54
|
-
return path;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return testPaths[0];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
9
|
export const getCloudflareCreds = lazy(async (): Promise<{ key: string; email: string }> => {
|
|
61
10
|
let archives = keysArchives();
|
|
62
11
|
let credsJSON = await archives.get("keys/cloudflare.json");
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import { Archives } from "./archives";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import { isNode, timeInMinute } from "socket-function/src/misc";
|
|
5
5
|
import { isLogBackblaze } from "../config";
|
|
6
|
-
import { getBackblazePath } from "
|
|
6
|
+
import { getBackblazePath } from "../misc/appPaths";
|
|
7
7
|
import { ArchivesBackblaze as ArchivesBackblazeBase } from "sliftutils/storage/backblaze";
|
|
8
8
|
import type { IArchives, ArchivesConfig } from "sliftutils/storage/IArchives";
|
|
9
9
|
|
|
@@ -50,7 +50,6 @@ import yargs, { check } from "yargs";
|
|
|
50
50
|
import { parseArgsFactory } from "../misc/rawParams";
|
|
51
51
|
|
|
52
52
|
import * as typesafecss from "typesafecss";
|
|
53
|
-
import "../library-components/urlResetGroups";
|
|
54
53
|
import { createLocalSchema } from "./schemaHelpers";
|
|
55
54
|
|
|
56
55
|
setTimeout(() => import("./FunctionRunnerTracking"));
|
|
@@ -5,6 +5,7 @@ import { css } from "typesafecss";
|
|
|
5
5
|
import { sort } from "socket-function/src/misc";
|
|
6
6
|
import { formatNumber, formatDateTime, formatTime, formatDateTimeDetailed } from "socket-function/src/formatting/format";
|
|
7
7
|
import { parseHostedUrl, parseBackblazeUrl } from "sliftutils/storage/remoteStorage/remoteConfig";
|
|
8
|
+
import { resolveIntermediateSources, getIntermediateSources } from "sliftutils/storage/remoteStorage/intermediateSources";
|
|
8
9
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
9
10
|
import { FULL_VALID_WINDOW, FULL_ROUTE } from "sliftutils/storage/IArchives";
|
|
10
11
|
import type { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } from "sliftutils/storage/IArchives";
|
|
@@ -20,6 +21,7 @@ type Source = HostedConfig | BackblazeConfig;
|
|
|
20
21
|
const DEFAULT_HTTPS_PORT = 443;
|
|
21
22
|
const TAG_COLOR = { h: 210, s: 45, l: 88 };
|
|
22
23
|
const WARNING_COLOR = { h: 35, s: 90, l: 85 };
|
|
24
|
+
const NOTICE_COLOR = { h: 205, s: 70, l: 88 };
|
|
23
25
|
const SOURCE_BORDER_COLOR = { h: 0, s: 0, l: 75 };
|
|
24
26
|
const ACTIVE_COLOR = { h: 130, s: 55, l: 85 };
|
|
25
27
|
const PAST_COLOR = { h: 0, s: 0, l: 90 };
|
|
@@ -454,10 +456,29 @@ class WindowRanges extends qreact.Component<{ sources: Source[]; ownBucketName:
|
|
|
454
456
|
}
|
|
455
457
|
}
|
|
456
458
|
|
|
459
|
+
/** Two configs that differ only by switchover windows are not really in conflict - resolving those away is exactly what a client does before using the config, so if the remainder matches, every server is working from the same underlying configuration. The version is ignored: a config gains a version purely by having an intermediate written into it. */
|
|
460
|
+
function getIntermediateOnlyDifference(consensus: RemoteConfig, other: RemoteConfig, now: number): {
|
|
461
|
+
intermediateOnly: boolean;
|
|
462
|
+
/** Undefined when nothing is pending; otherwise when the last switchover window still in the future ends */
|
|
463
|
+
expiresAt?: number;
|
|
464
|
+
} {
|
|
465
|
+
let sourcesOf = (config: RemoteConfig) => JSON.stringify(resolveIntermediateSources(config).sources);
|
|
466
|
+
let intermediateOnly = sourcesOf(consensus) === sourcesOf(other);
|
|
467
|
+
let ends = [...getIntermediateSources(consensus), ...getIntermediateSources(other)].map(x => x.validWindow[1]);
|
|
468
|
+
let pending = ends.filter(end => end > now);
|
|
469
|
+
return {
|
|
470
|
+
intermediateOnly,
|
|
471
|
+
expiresAt: pending.length && Math.max(...pending) || undefined,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
457
475
|
class ConflictWarning extends qreact.Component<{ consensus: ConfigVariant; conflict: ConfigConflict }> {
|
|
458
476
|
render() {
|
|
459
477
|
let { consensus, conflict } = this.props;
|
|
460
478
|
let { bucketName, variant } = conflict;
|
|
479
|
+
let now = Querysub.timeDelayed(TIME_REFRESH_INTERVAL);
|
|
480
|
+
let { intermediateOnly, expiresAt } = getIntermediateOnlyDifference(consensus.rawConfig, variant.rawConfig, now);
|
|
481
|
+
let color = intermediateOnly && NOTICE_COLOR || WARNING_COLOR;
|
|
461
482
|
let consensusKeys = new Set(consensus.sources.map(x => getSourceKey(x, bucketName)));
|
|
462
483
|
let conflictKeys = new Set(variant.sources.map(x => getSourceKey(x, bucketName)));
|
|
463
484
|
let extra = variant.sources.filter(x => !consensusKeys.has(getSourceKey(x, bucketName)));
|
|
@@ -469,10 +490,15 @@ class ConflictWarning extends qreact.Component<{ consensus: ConfigVariant; confl
|
|
|
469
490
|
route {(source.route || FULL_ROUTE).join(" – ")}, valid {formatWindowTime(source.validWindow[0])} → {formatWindowTime(source.validWindow[1])}
|
|
470
491
|
</div>
|
|
471
492
|
</div>;
|
|
472
|
-
return <div className={css.vbox(6).pad2(10, 8).hsl(
|
|
493
|
+
return <div className={css.vbox(6).pad2(10, 8).hsl(color.h, color.s, color.l).bord2(color.h, color.s, color.l - 25)}>
|
|
473
494
|
<div className={css.boldStyle}>
|
|
474
|
-
⚠ {bucketName}: {variant.servers.length} server{variant.servers.length === 1 && "" || "s"} report a different routing config (version {String(variant.version ?? "none")}, consensus is version {String(consensus.version ?? "none")})
|
|
495
|
+
{intermediateOnly && "ℹ" || "⚠"} {bucketName}: {variant.servers.length} server{variant.servers.length === 1 && "" || "s"} report a different routing config (version {String(variant.version ?? "none")}, consensus is version {String(consensus.version ?? "none")})
|
|
475
496
|
</div>
|
|
497
|
+
{intermediateOnly && <div>
|
|
498
|
+
Only the switchover windows differ - with those resolved away, every server is working from the same configuration.
|
|
499
|
+
{expiresAt === undefined && " They are all in the past, so this shouldn't break anything."}
|
|
500
|
+
{expiresAt !== undefined && ` The sources become consistent again once the last one expires, ${formatTime(expiresAt - now)} from now (${formatWindowTimeDetailed(expiresAt)}).`}
|
|
501
|
+
</div>}
|
|
476
502
|
<div className={css.vbox(2).fontSize(TAG_FONT_SIZE)}>
|
|
477
503
|
{variant.servers.map(server => <div key={server}>{server}</div>)}
|
|
478
504
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBackblazePath } from "
|
|
1
|
+
import { getBackblazePath } from "../misc/appPaths";
|
|
2
2
|
import { getGitURLLive, getGitRefLive } from "../4-deploy/git";
|
|
3
3
|
import { Querysub } from "../4-querysub/Querysub";
|
|
4
4
|
import { runPromise } from "../functional/runCommand";
|
|
@@ -170,6 +170,12 @@ export function createURLSync<T>(urlKey: string, defaultValue: T, config?: URLPa
|
|
|
170
170
|
return param;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
if (!syncSchema) {
|
|
174
|
+
debugger;
|
|
175
|
+
require("debugbreak")(2);
|
|
176
|
+
debugger;
|
|
177
|
+
}
|
|
178
|
+
|
|
173
179
|
// TODO: Support pathname keys (ex /name-value)
|
|
174
180
|
const { data } = syncSchema<{
|
|
175
181
|
params: {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import { isNode } from "socket-function/src/misc";
|
|
4
|
+
import { lazy } from "socket-function/src/caching";
|
|
5
|
+
import { parseArgsFactory } from "./rawParams";
|
|
6
|
+
|
|
7
|
+
// Where the machine's own configuration and credentials live on disk. Deliberately dependency-free (beyond rawParams, which has no dependencies of its own): the storage layer needs these paths, and it sits far below the application - routing this through appSecrets.ts made the lowest-level archives code depend on Querysub.
|
|
8
|
+
|
|
9
|
+
// getSecret always imports appSecrets relative to the process cwd, so the cwd is the repo root and this matches what getStorageDir would resolve
|
|
10
|
+
export const STORAGE_DIR = "./database-storage/";
|
|
11
|
+
|
|
12
|
+
// querysubConfig and getDomain are copied verbatim from src/config.ts, so this file has no dependencies on our own application
|
|
13
|
+
|
|
14
|
+
let yargObj = parseArgsFactory()
|
|
15
|
+
.option("domain", { type: "string", desc: `Sets the domain` })
|
|
16
|
+
.argv
|
|
17
|
+
;
|
|
18
|
+
|
|
19
|
+
type QuerysubConfig = {
|
|
20
|
+
domain?: string;
|
|
21
|
+
emaildomain?: string;
|
|
22
|
+
notifyemails?: string[];
|
|
23
|
+
};
|
|
24
|
+
let querysubConfig = lazy((): QuerysubConfig => {
|
|
25
|
+
if (!isNode()) throw new Error("querysubConfig is only available on the server");
|
|
26
|
+
const path = "./querysub.json";
|
|
27
|
+
if (!fs.existsSync(path)) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(path, "utf8"));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error("Error parsing querysub.json", e);
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export function getDomain() {
|
|
39
|
+
if (!isNode()) {
|
|
40
|
+
return location.hostname.split(".").slice(-2).join(".");
|
|
41
|
+
}
|
|
42
|
+
return yargObj.domain || querysubConfig().domain || "querysub.com";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getBackblazePath() {
|
|
46
|
+
let testPaths = [
|
|
47
|
+
STORAGE_DIR + "backblaze.json",
|
|
48
|
+
os.homedir() + "/backblaze.json",
|
|
49
|
+
];
|
|
50
|
+
for (let path of testPaths) {
|
|
51
|
+
if (fs.existsSync(path)) {
|
|
52
|
+
return path;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return testPaths[0];
|
|
56
|
+
}
|