@rubytech/create-maxy-code 0.1.441 → 0.1.443
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/dist/__tests__/converge-client-admins.test.js +50 -0
- package/dist/converge-client-admins.js +67 -0
- package/dist/index.js +107 -1
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +547 -0
- package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +458 -0
- package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +178 -0
- package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +119 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +7 -1
- package/payload/platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh +19 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +126 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +30 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +78 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -2
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +35 -0
- package/payload/platform/plugins/memory/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/resolve-active-vertical.test.js +12 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/resolve-active-vertical.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.d.ts +9 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.js +19 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +8 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +37 -4
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +27 -3
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +52 -11
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +5 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +32 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +20 -22
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
- package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +58 -0
- package/payload/platform/scripts/__tests__/provision-role-stamp.test.sh +6 -0
- package/payload/platform/scripts/lib/account-schema-owned-dirs.py +143 -10
- package/payload/platform/scripts/lib/provision-account-dir.sh +5 -0
- package/payload/platform/scripts/logs-read.sh +24 -6
- package/payload/platform/scripts/logs-read.test.sh +42 -0
- package/payload/platform/scripts/name-glsmith-owner.sh +239 -0
- package/payload/platform/scripts/rss-sampler.sh +107 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-CukwQufW.js → AdminLoginScreens-BJVwtpLH.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BwEBH6tT.js → AdminShell-CMu8_Xwn.js} +1 -1
- package/payload/server/public/assets/{Checkbox-B629mOPp.js → Checkbox-CzD0ORqA.js} +1 -1
- package/payload/server/public/assets/{admin-CdFRCUUZ.js → admin-D0yk2LMq.js} +1 -1
- package/payload/server/public/assets/{browser-BWR59IkB.js → browser-CvfbgznO.js} +1 -1
- package/payload/server/public/assets/{calendar-kuTcxX1t.js → calendar-fw4WethJ.js} +1 -1
- package/payload/server/public/assets/chat-DhD-NES_.js +1 -0
- package/payload/server/public/assets/chevron-left-B3xSeEDR.js +1 -0
- package/payload/server/public/assets/data-CUnDzDox.js +1 -0
- package/payload/server/public/assets/{file-download-4mKJxUi-.js → file-download-Px4s0Bhp.js} +1 -1
- package/payload/server/public/assets/{graph-CboWM2_-.js → graph-DbT8nA6R.js} +1 -1
- package/payload/server/public/assets/{graph-labels-B8YAdo3Z.js → graph-labels-C1YIyLiA.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CxstTgoK.js → maximize-2-BwphzI9S.js} +1 -1
- package/payload/server/public/assets/{operator-oSaB1mya.js → operator-zcIfeB14.js} +1 -1
- package/payload/server/public/assets/{page-DMeJHVfA.js → page-6fCnIb45.js} +2 -2
- package/payload/server/public/assets/page-CfQFAJia.js +1 -0
- package/payload/server/public/assets/{public-C54tl6V4.js → public-C0ehZCdE.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-COGQTi7n.js → rotate-ccw-C7BqkMec.js} +1 -1
- package/payload/server/public/assets/{tasks-B97VpwEJ.js → tasks-DlgiCafL.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-C09rDHo9.js → time-entry-format-CyNYVDkq.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-DHoYHiK9.js → useCopyFeedback-ByGUMN94.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-1izQvPiD.css +1 -0
- package/payload/server/public/assets/useVoiceRecorder-XgpWvXpk.js +2 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +742 -321
- package/payload/platform/plugins/memory/references/schema-trades.md +0 -34
- package/payload/server/public/assets/chat-ejnq2rck.js +0 -1
- package/payload/server/public/assets/chevron-left-u4tjq7fI.js +0 -1
- package/payload/server/public/assets/data-dZbOyLdT.js +0 -1
- package/payload/server/public/assets/page-DEkI-Gl1.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-Dzmd7Aip.css +0 -1
- package/payload/server/public/assets/useVoiceRecorder-b7HzTFs0.js +0 -2
- /package/payload/server/public/assets/{useSubAccountSwitcher-DLJFISqf.js → useSubAccountSwitcher-s6WlRn52.js} +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Task 1623 — acceptance for the installer convergence that drops the vestigial
|
|
2
|
+
// admins[] key from existing client account.json. Filesystem fixtures under a
|
|
3
|
+
// tmp dir; no installer side effects. Runs under `node --test dist/__tests__/*`.
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { removeClientAdminsKey } from "../converge-client-admins.js";
|
|
10
|
+
function makeRoot() {
|
|
11
|
+
return mkdtempSync(join(tmpdir(), "conv-1623-"));
|
|
12
|
+
}
|
|
13
|
+
function acct(root, id, cfg) {
|
|
14
|
+
mkdirSync(join(root, id), { recursive: true });
|
|
15
|
+
writeFileSync(join(root, id, "account.json"), JSON.stringify(cfg, null, 2));
|
|
16
|
+
}
|
|
17
|
+
function hasAdmins(root, id) {
|
|
18
|
+
return "admins" in JSON.parse(readFileSync(join(root, id, "account.json"), "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
test("removes admins from a client, keeps it on the house, is idempotent", () => {
|
|
21
|
+
const root = makeRoot();
|
|
22
|
+
try {
|
|
23
|
+
acct(root, "c1", { accountId: "c1", role: "client", admins: [] });
|
|
24
|
+
acct(root, "h1", { accountId: "h1", role: "house", admins: [{ userId: "u1", role: "owner" }] });
|
|
25
|
+
acct(root, "c2", { accountId: "c2", role: "client" }); // already stripped
|
|
26
|
+
const first = removeClientAdminsKey(root);
|
|
27
|
+
assert.equal(first.find((r) => r.accountId === "c1")?.action, "removed");
|
|
28
|
+
assert.equal(first.find((r) => r.accountId === "h1")?.action, "house-kept");
|
|
29
|
+
assert.equal(first.find((r) => r.accountId === "c2")?.action, "absent");
|
|
30
|
+
assert.equal(hasAdmins(root, "c1"), false);
|
|
31
|
+
assert.equal(hasAdmins(root, "h1"), true);
|
|
32
|
+
const second = removeClientAdminsKey(root);
|
|
33
|
+
assert.equal(second.find((r) => r.accountId === "c1")?.action, "absent");
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
rmSync(root, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test("unreadable account.json is reported, not thrown", () => {
|
|
40
|
+
const root = makeRoot();
|
|
41
|
+
try {
|
|
42
|
+
mkdirSync(join(root, "bad"), { recursive: true });
|
|
43
|
+
writeFileSync(join(root, "bad", "account.json"), "{ not json");
|
|
44
|
+
const res = removeClientAdminsKey(root);
|
|
45
|
+
assert.equal(res.find((r) => r.accountId === "bad")?.action, "unreadable");
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
rmSync(root, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Task 1623 — installer convergence. A client sub-account never carries admins[]
|
|
2
|
+
// (Task 999 retired the seats, Task 1596 the inbound read). This removes the
|
|
3
|
+
// vestigial key from every existing role:'client' account.json on each install,
|
|
4
|
+
// reaching all installs and brands without a per-box script. The house keeps its
|
|
5
|
+
// admins[] (the real install-wide access surface). Atomic temp-write + rename,
|
|
6
|
+
// idempotent, one auditable line per file; contents are never echoed.
|
|
7
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync, renameSync, statSync, rmSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
export function removeClientAdminsKey(accountsDir) {
|
|
10
|
+
const out = [];
|
|
11
|
+
let entries = [];
|
|
12
|
+
try {
|
|
13
|
+
entries = readdirSync(accountsDir);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
if (entry.startsWith("."))
|
|
20
|
+
continue;
|
|
21
|
+
const dir = join(accountsDir, entry);
|
|
22
|
+
try {
|
|
23
|
+
if (!statSync(dir).isDirectory())
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const cfgPath = join(dir, "account.json");
|
|
30
|
+
if (!existsSync(cfgPath))
|
|
31
|
+
continue;
|
|
32
|
+
let cfg;
|
|
33
|
+
try {
|
|
34
|
+
cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
out.push({ accountId: entry, action: "unreadable" });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (cfg.role === "house") {
|
|
41
|
+
out.push({ accountId: entry, action: "house-kept" });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (cfg.role !== "client" || !("admins" in cfg)) {
|
|
45
|
+
out.push({ accountId: entry, action: "absent" });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
delete cfg.admins;
|
|
49
|
+
const tmp = `${cfgPath}.1623.tmp`;
|
|
50
|
+
try {
|
|
51
|
+
writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n");
|
|
52
|
+
renameSync(tmp, cfgPath);
|
|
53
|
+
out.push({ accountId: entry, action: "removed" });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// A write/rename failure (full disk, permissions, file changed under us):
|
|
57
|
+
// remove any half-written temp so no .1623.tmp is orphaned, and label it
|
|
58
|
+
// write-failed (the file was readable — "unreadable" would misdescribe it).
|
|
59
|
+
try {
|
|
60
|
+
rmSync(tmp, { force: true });
|
|
61
|
+
}
|
|
62
|
+
catch { /* best-effort cleanup */ }
|
|
63
|
+
out.push({ accountId: entry, action: "write-failed" });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { buildHeartbeatCronBlock, mergeHeartbeatBlock } from "./cron-registratio
|
|
|
10
10
|
import { validateTierFlag, validateEntitlementBase64, applyTierToAccountConfig, entitlementPath } from "./tier-flag.js";
|
|
11
11
|
import { parseOsRelease, isUbuntuLike as isUbuntuLikePure, parseAptCacheCandidate, decideAptResolution, } from "./apt-resolve.js";
|
|
12
12
|
import { findPeerBrandOnDefaultNeo4jPort } from "./peer-brand-detect.js";
|
|
13
|
+
import { removeClientAdminsKey } from "./converge-client-admins.js";
|
|
13
14
|
import { resolveNeo4jPidfileScrub } from "./neo4j-pidfile-scrub.js";
|
|
14
15
|
import { runInitLogging } from "./init-logging.js";
|
|
15
16
|
import { requireSupportedPlatform, detectPlatform } from "./platform-detect.js";
|
|
@@ -856,6 +857,89 @@ function installSystemDeps() {
|
|
|
856
857
|
catch (err) {
|
|
857
858
|
console.error(` WARNING: failed to provision swap: ${err instanceof Error ? err.message : String(err)}`);
|
|
858
859
|
}
|
|
860
|
+
// Task 1593 — zram compressed-RAM swap ahead of the SD dphys swapfile. On a
|
|
861
|
+
// Pi, swapping to the SD card thrashes I/O and turns an approach-to-OOM into
|
|
862
|
+
// an apparent hard-freeze (the muvin 2026-07-13 incident). zram swaps to
|
|
863
|
+
// compressed RAM first (default priority 100 vs dphys -2), removing the
|
|
864
|
+
// thrash and adding effective headroom; dphys stays as the low-priority
|
|
865
|
+
// last-resort overflow tier. Best-effort: a failure warns but never aborts.
|
|
866
|
+
try {
|
|
867
|
+
console.log(" [privileged] apt-get install zram-tools");
|
|
868
|
+
shell("apt-get", ["install", "-y", "zram-tools"], { sudo: true, bestEffort: true });
|
|
869
|
+
const desiredZram = "ALGO=zstd\nPERCENT=50\n";
|
|
870
|
+
const zramConfPath = "/etc/default/zramswap";
|
|
871
|
+
let zramCurrent = "";
|
|
872
|
+
if (existsSync(zramConfPath)) {
|
|
873
|
+
try {
|
|
874
|
+
zramCurrent = readFileSync(zramConfPath, "utf-8");
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
zramCurrent = "";
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (zramCurrent.includes("ALGO=zstd") && zramCurrent.includes("PERCENT=50")) {
|
|
881
|
+
logFile(" zram: already configured (ALGO=zstd PERCENT=50)");
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
const zramTmp = `/tmp/zramswap.${process.pid}`;
|
|
885
|
+
writeFileSync(zramTmp, desiredZram);
|
|
886
|
+
console.log(" [privileged] install /etc/default/zramswap (ALGO=zstd PERCENT=50)");
|
|
887
|
+
shell("cp", [zramTmp, zramConfPath], { sudo: true });
|
|
888
|
+
spawnSync("rm", ["-f", zramTmp]);
|
|
889
|
+
console.log(" [privileged] systemctl restart zramswap");
|
|
890
|
+
shell("systemctl", ["restart", "zramswap"], { sudo: true, bestEffort: true });
|
|
891
|
+
logFile(" zram: configured ALGO=zstd PERCENT=50 and restarted zramswap");
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
catch (err) {
|
|
895
|
+
console.error(` WARNING: failed to provision zram: ${err instanceof Error ? err.message : String(err)}`);
|
|
896
|
+
}
|
|
897
|
+
// Task 1593 — earlyoom userspace killer. Under sustained pressure it SIGTERMs
|
|
898
|
+
// the largest process at 10% MemAvailable (SIGKILL at 5%) BEFORE the kernel
|
|
899
|
+
// oom-killer wedges the box; systemd's Restart=on-failure then restarts the
|
|
900
|
+
// service, so a leak becomes "one process dies and comes back" instead of a
|
|
901
|
+
// hard freeze. --avoid protects sshd (keeps the box reachable, so SSH never
|
|
902
|
+
// drops during a kill) and systemd (init). Neo4j runs as comm=java and is
|
|
903
|
+
// crash-safe (recovers from its transaction log on restart), so it is
|
|
904
|
+
// intentionally not protected. Best-effort. The 10%/5% thresholds are the
|
|
905
|
+
// calibration target of the on-device induced-pressure test.
|
|
906
|
+
try {
|
|
907
|
+
console.log(" [privileged] apt-get install earlyoom");
|
|
908
|
+
shell("apt-get", ["install", "-y", "earlyoom"], { sudo: true, bestEffort: true });
|
|
909
|
+
// No inner quotes around the --avoid regex: the Debian earlyoom.service
|
|
910
|
+
// runs `ExecStart=/usr/bin/earlyoom $EARLYOOM_ARGS`, and systemd expands the
|
|
911
|
+
// variable by whitespace-splitting WITHOUT shell quote removal, so inner
|
|
912
|
+
// single-quotes would survive into the regex and never match sshd/systemd.
|
|
913
|
+
// The regex `^(sshd|systemd)$` contains no whitespace, so it stays one token.
|
|
914
|
+
const desiredEarlyoom = "EARLYOOM_ARGS=\"-m 10,5 -r 3600 --avoid ^(sshd|systemd)$\"\n";
|
|
915
|
+
const earlyoomConfPath = "/etc/default/earlyoom";
|
|
916
|
+
let earlyoomCurrent = "";
|
|
917
|
+
if (existsSync(earlyoomConfPath)) {
|
|
918
|
+
try {
|
|
919
|
+
earlyoomCurrent = readFileSync(earlyoomConfPath, "utf-8");
|
|
920
|
+
}
|
|
921
|
+
catch {
|
|
922
|
+
earlyoomCurrent = "";
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (earlyoomCurrent.includes("-m 10,5") && earlyoomCurrent.includes("--avoid ^(sshd|systemd)$")) {
|
|
926
|
+
logFile(" earlyoom: already configured (-m 10,5 --avoid sshd|systemd)");
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
const eoTmp = `/tmp/earlyoom.${process.pid}`;
|
|
930
|
+
writeFileSync(eoTmp, desiredEarlyoom);
|
|
931
|
+
console.log(" [privileged] install /etc/default/earlyoom (-m 10,5 -r 3600)");
|
|
932
|
+
shell("cp", [eoTmp, earlyoomConfPath], { sudo: true });
|
|
933
|
+
spawnSync("rm", ["-f", eoTmp]);
|
|
934
|
+
console.log(" [privileged] systemctl enable --now earlyoom");
|
|
935
|
+
shell("systemctl", ["enable", "--now", "earlyoom"], { sudo: true, bestEffort: true });
|
|
936
|
+
shell("systemctl", ["restart", "earlyoom"], { sudo: true, bestEffort: true });
|
|
937
|
+
logFile(" earlyoom: configured -m 10,5 -r 3600 --avoid sshd|systemd and enabled");
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
catch (err) {
|
|
941
|
+
console.error(` WARNING: failed to provision earlyoom: ${err instanceof Error ? err.message : String(err)}`);
|
|
942
|
+
}
|
|
859
943
|
// Hostname resolution — four sources, in priority order:
|
|
860
944
|
// 1. --hostname flag (unconditional — the caller is the authority)
|
|
861
945
|
// 2. OS detection on same-brand upgrade (service exists → keep whatever is currently set)
|
|
@@ -2491,6 +2575,13 @@ function deployPayload() {
|
|
|
2491
2575
|
// installer is its own npm package and doesn't bundle the platform lib.
|
|
2492
2576
|
// Mirrors checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts;
|
|
2493
2577
|
// future divergence between the two should be caught by the test suite.
|
|
2578
|
+
// Task 1623 — converge existing client account.json to drop the vestigial
|
|
2579
|
+
// admins[] key before the invariant check reads them. House untouched.
|
|
2580
|
+
for (const r of removeClientAdminsKey(join(INSTALL_DIR, "data", "accounts"))) {
|
|
2581
|
+
if (r.action === "removed" || r.action === "unreadable" || r.action === "write-failed") {
|
|
2582
|
+
console.log(` [converge-1623] account=${r.accountId.slice(0, 8)} admins=${r.action}`);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2494
2585
|
runInstallInvariantCheck(persistentUsersFile, join(INSTALL_DIR, "data", "accounts"));
|
|
2495
2586
|
// The version marker is NOT written here. It is stamped at the very end of a
|
|
2496
2587
|
// fully-successful install (after plugin registration and every throwing
|
|
@@ -4218,6 +4309,17 @@ function installService() {
|
|
|
4218
4309
|
const rssSamplerUnitName = `${BRAND.hostname}-rss-sampler.service`;
|
|
4219
4310
|
const rssSamplerScriptPath = resolve(INSTALL_DIR, "platform/scripts/rss-sampler.sh");
|
|
4220
4311
|
const rssSamplerLogPath = join(persistDir, "rss-sampler.log");
|
|
4312
|
+
// Task 1593 — the per-service unit names the sampler reads for its
|
|
4313
|
+
// op=mem-sample line, kept brand-agnostic by passing them as env. Neo4j runs
|
|
4314
|
+
// as a dedicated per-brand unit when it is off the default port, else the
|
|
4315
|
+
// shared system unit. MEM_PRESSURE_PCT sets the approach-to-OOM warning line
|
|
4316
|
+
// as a percentage of MemTotal; 20% sits above earlyoom's 10% SIGTERM line on
|
|
4317
|
+
// every fleet RAM size (8-16 GB), so the warning is logged before the kill
|
|
4318
|
+
// (a fixed MB value cannot stay above a percentage kill line across both).
|
|
4319
|
+
const memBrandService = `${BRAND.hostname}.service`;
|
|
4320
|
+
const memEdgeService = edgeUnitName;
|
|
4321
|
+
const memNeo4jUnit = NEO4J_DEDICATED ? `neo4j-${BRAND.hostname}.service` : "neo4j.service";
|
|
4322
|
+
const memPressurePct = "20";
|
|
4221
4323
|
const rssSamplerUnit = `[Unit]
|
|
4222
4324
|
Description=${BRAND.productName} RSS Sampler
|
|
4223
4325
|
|
|
@@ -4225,6 +4327,10 @@ Description=${BRAND.productName} RSS Sampler
|
|
|
4225
4327
|
Type=simple
|
|
4226
4328
|
ExecStart=/bin/bash ${rssSamplerScriptPath}
|
|
4227
4329
|
Environment=LOG_PATH=${rssSamplerLogPath}
|
|
4330
|
+
Environment=BRAND_SERVICE=${memBrandService}
|
|
4331
|
+
Environment=EDGE_SERVICE=${memEdgeService}
|
|
4332
|
+
Environment=NEO4J_UNIT=${memNeo4jUnit}
|
|
4333
|
+
Environment=MEM_PRESSURE_PCT=${memPressurePct}
|
|
4228
4334
|
Restart=on-failure
|
|
4229
4335
|
RestartSec=10
|
|
4230
4336
|
|
|
@@ -4232,7 +4338,7 @@ RestartSec=10
|
|
|
4232
4338
|
WantedBy=default.target
|
|
4233
4339
|
`;
|
|
4234
4340
|
writeFileSync(join(serviceDir, rssSamplerUnitName), rssSamplerUnit);
|
|
4235
|
-
logFile(` rss-sampler installed: ${rssSamplerUnitName} LOG_PATH=${rssSamplerLogPath}`);
|
|
4341
|
+
logFile(` rss-sampler installed: ${rssSamplerUnitName} LOG_PATH=${rssSamplerLogPath} BRAND_SERVICE=${memBrandService} EDGE_SERVICE=${memEdgeService} NEO4J_UNIT=${memNeo4jUnit} MEM_PRESSURE_PCT=${memPressurePct}`);
|
|
4236
4342
|
// the edge service: always-on front door that owns the public
|
|
4237
4343
|
// port (PORT) and the VNC stack (Xtigervnc + websockify). Its lifecycle is
|
|
4238
4344
|
// independent of the main brand service, so an in-place upgrade triggered
|