@rubytech/create-sitedesk-code 0.1.517 → 0.1.519
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__/edge-bind-template-wiring.test.js +37 -0
- package/dist/__tests__/samba-provision.test.js +103 -1
- package/dist/__tests__/tier-flag.test.js +10 -6
- package/dist/index.js +20 -5
- package/dist/samba-provision.js +72 -0
- package/dist/tier-flag.js +10 -5
- package/package.json +1 -1
- package/payload/platform/lib/entitlement/dist/index.d.ts +24 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/entitlement/dist/index.js +40 -2
- package/payload/platform/lib/entitlement/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +5 -9
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +261 -8
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +44 -6
- package/payload/platform/plugins/docs/references/samba.md +2 -0
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -3
- package/payload/platform/scripts/check-no-esm-require.mjs +5 -3
- package/payload/platform/scripts/generate-entitlement-fixture.mjs +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +16 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +15 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +5 -3
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.js +13 -10
- package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts +6 -2
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.js +66 -32
- package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/notification.d.ts +2 -1
- package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/notification.js +11 -4
- package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.js +30 -14
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.js +11 -5
- package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +18 -4
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +19 -7
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +23 -3
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.js +8 -2
- package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
- package/payload/platform/templates/systemd/edge.service.template +1 -1
- package/payload/server/chunk-PXNY2TAP.js +1980 -0
- package/payload/server/chunk-TIIWMF5G.js +4837 -0
- package/payload/server/{chunk-S6HYTIU3.js → chunk-UX2FRNO7.js} +72 -2031
- package/payload/server/manager-B6OFOXSW.js +61 -0
- package/payload/server/maxy-edge.js +5 -3
- package/payload/server/package.json +2 -1
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-B5qKb3YK.js → AdminLoginScreens-BZPbCk8g.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-Bpo_wgxm.js +2 -0
- package/payload/server/public/assets/AdminShell-Bpo_wgxm.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Bpo_wgxm.js.gz +0 -0
- package/payload/server/public/assets/{activity-DmnVUDYO.js → activity-DOsfKzT3.js} +1 -1
- package/payload/server/public/assets/activity-DOsfKzT3.js.br +0 -0
- package/payload/server/public/assets/activity-DOsfKzT3.js.gz +0 -0
- package/payload/server/public/assets/admin-COgD1z5a.js +1 -0
- package/payload/server/public/assets/admin-COgD1z5a.js.br +0 -0
- package/payload/server/public/assets/admin-COgD1z5a.js.gz +0 -0
- package/payload/server/public/assets/{agents-Bmq9X7P3.js → agents-DqDnZTH_.js} +1 -1
- package/payload/server/public/assets/agents-DqDnZTH_.js.br +0 -0
- package/payload/server/public/assets/agents-DqDnZTH_.js.gz +0 -0
- package/payload/server/public/assets/{browser-B1G-W80B.js → browser-FL17Dfp0.js} +1 -1
- package/payload/server/public/assets/browser-FL17Dfp0.js.br +0 -0
- package/payload/server/public/assets/browser-FL17Dfp0.js.gz +0 -0
- package/payload/server/public/assets/calendar-CLfLvsnV.js +1 -0
- package/payload/server/public/assets/calendar-CLfLvsnV.js.br +0 -0
- package/payload/server/public/assets/calendar-CLfLvsnV.js.gz +0 -0
- package/payload/server/public/assets/chat-DYq4FxMG.js +1 -0
- package/payload/server/public/assets/chat-DYq4FxMG.js.br +0 -0
- package/payload/server/public/assets/chat-DYq4FxMG.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-DWt5cdUo.js +1 -0
- package/payload/server/public/assets/chevron-right-B9suVrYy.js +1 -0
- package/payload/server/public/assets/chevron-right-B9suVrYy.js.br +0 -0
- package/payload/server/public/assets/clock-DW6qVXtv.js +1 -0
- package/payload/server/public/assets/clock-DW6qVXtv.js.br +0 -0
- package/payload/server/public/assets/clock-DW6qVXtv.js.gz +0 -0
- package/payload/server/public/assets/data-C8rbOgds.js +1 -0
- package/payload/server/public/assets/data-C8rbOgds.js.br +0 -0
- package/payload/server/public/assets/data-C8rbOgds.js.gz +0 -0
- package/payload/server/public/assets/{file-text-BVqqaDun.js → file-text-DNm0AzUz.js} +1 -1
- package/payload/server/public/assets/file-text-DNm0AzUz.js.br +0 -0
- package/payload/server/public/assets/file-text-DNm0AzUz.js.gz +0 -0
- package/payload/server/public/assets/{graph-CEcME0lp.js → graph-fAvmlVzh.js} +3 -3
- package/payload/server/public/assets/graph-fAvmlVzh.js.br +0 -0
- package/payload/server/public/assets/graph-fAvmlVzh.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-Zl32_AOH.js → graph-labels-CwdkCxfs.js} +1 -1
- package/payload/server/public/assets/graph-labels-CwdkCxfs.js.br +0 -0
- package/payload/server/public/assets/graph-labels-CwdkCxfs.js.gz +0 -0
- package/payload/server/public/assets/{maximize-2-rkkMyAhf.js → maximize-2-B6tAPhC2.js} +1 -1
- package/payload/server/public/assets/maximize-2-B6tAPhC2.js.br +0 -0
- package/payload/server/public/assets/maximize-2-B6tAPhC2.js.gz +0 -0
- package/payload/server/public/assets/operator-CTKvBVjk.js +1 -0
- package/payload/server/public/assets/operator-CTKvBVjk.js.br +0 -0
- package/payload/server/public/assets/operator-CTKvBVjk.js.gz +0 -0
- package/payload/server/public/assets/page-BWoNW3sa.js +32 -0
- package/payload/server/public/assets/page-BWoNW3sa.js.br +0 -0
- package/payload/server/public/assets/page-BWoNW3sa.js.gz +0 -0
- package/payload/server/public/assets/page-O5HkHQw4.js +1 -0
- package/payload/server/public/assets/page-O5HkHQw4.js.br +0 -0
- package/payload/server/public/assets/page-O5HkHQw4.js.gz +0 -0
- package/payload/server/public/assets/{public-Cqgfmo1D.js → public-BF1rnVPa.js} +1 -1
- package/payload/server/public/assets/public-BF1rnVPa.js.br +0 -0
- package/payload/server/public/assets/public-BF1rnVPa.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-O3RmqoTy.js → rotate-ccw-B8TqfCy4.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-B8TqfCy4.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-B8TqfCy4.js.gz +0 -0
- package/payload/server/public/assets/{routines-DMYObMUy.js → routines-4R7BLZyH.js} +1 -1
- package/payload/server/public/assets/routines-4R7BLZyH.js.br +0 -0
- package/payload/server/public/assets/routines-4R7BLZyH.js.gz +0 -0
- package/payload/server/public/assets/{skills-DON1BKu5.js → skills-CQ9Qs2tg.js} +1 -1
- package/payload/server/public/assets/skills-CQ9Qs2tg.js.br +0 -0
- package/payload/server/public/assets/skills-CQ9Qs2tg.js.gz +0 -0
- package/payload/server/public/assets/tasks-DePyOrRk.js +1 -0
- package/payload/server/public/assets/tasks-DePyOrRk.js.br +0 -0
- package/payload/server/public/assets/tasks-DePyOrRk.js.gz +0 -0
- package/payload/server/public/assets/{time-entry-format-4zgmuQI0.js → time-entry-format-D9gtnG01.js} +1 -1
- package/payload/server/public/assets/time-entry-format-D9gtnG01.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-D9gtnG01.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-By5D7wuI.js → triangle-alert-DAtm2mLL.js} +1 -1
- package/payload/server/public/assets/triangle-alert-DAtm2mLL.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-DAtm2mLL.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-Dx0vtm9s.js → useCopyFeedback-uMJT-p6i.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-uMJT-p6i.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-uMJT-p6i.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BufCPDDH.js +14 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BufCPDDH.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BufCPDDH.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js +2 -0
- package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js.gz +0 -0
- package/payload/server/public/assets/{wrench-CHfMwLza.js → wrench-CYgaN7K9.js} +1 -1
- package/payload/server/public/assets/wrench-CYgaN7K9.js.br +1 -0
- package/payload/server/public/assets/wrench-CYgaN7K9.js.gz +0 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +7 -7
- package/payload/server/public/chat.html +13 -13
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +13 -13
- package/payload/server/public/routines.html +6 -6
- package/payload/server/public/skills.html +5 -5
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +8156 -11969
- package/payload/server/public/assets/AdminLoginScreens-B5qKb3YK.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-B5qKb3YK.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-BgJEq2zJ.js +0 -2
- package/payload/server/public/assets/AdminShell-BgJEq2zJ.js.br +0 -0
- package/payload/server/public/assets/AdminShell-BgJEq2zJ.js.gz +0 -0
- package/payload/server/public/assets/activity-DmnVUDYO.js.br +0 -0
- package/payload/server/public/assets/activity-DmnVUDYO.js.gz +0 -0
- package/payload/server/public/assets/admin-Dpge4OG9.js +0 -1
- package/payload/server/public/assets/admin-Dpge4OG9.js.br +0 -0
- package/payload/server/public/assets/admin-Dpge4OG9.js.gz +0 -0
- package/payload/server/public/assets/agents-Bmq9X7P3.js.br +0 -0
- package/payload/server/public/assets/agents-Bmq9X7P3.js.gz +0 -0
- package/payload/server/public/assets/browser-B1G-W80B.js.br +0 -0
- package/payload/server/public/assets/browser-B1G-W80B.js.gz +0 -0
- package/payload/server/public/assets/calendar-CHgrOFIN.js +0 -1
- package/payload/server/public/assets/calendar-CHgrOFIN.js.br +0 -0
- package/payload/server/public/assets/calendar-CHgrOFIN.js.gz +0 -0
- package/payload/server/public/assets/chat-CIwmmtwP.js +0 -1
- package/payload/server/public/assets/chat-CIwmmtwP.js.br +0 -0
- package/payload/server/public/assets/chat-CIwmmtwP.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-EQjfaQ-n.js +0 -1
- package/payload/server/public/assets/chevron-left-EQjfaQ-n.js.br +0 -0
- package/payload/server/public/assets/chevron-right-Ct8jZUGP.js +0 -1
- package/payload/server/public/assets/chevron-right-Ct8jZUGP.js.br +0 -0
- package/payload/server/public/assets/clock-Cxz5ybck.js +0 -1
- package/payload/server/public/assets/clock-Cxz5ybck.js.br +0 -0
- package/payload/server/public/assets/clock-Cxz5ybck.js.gz +0 -0
- package/payload/server/public/assets/data-BQVXKVsJ.js +0 -1
- package/payload/server/public/assets/data-BQVXKVsJ.js.br +0 -1
- package/payload/server/public/assets/data-BQVXKVsJ.js.gz +0 -0
- package/payload/server/public/assets/file-text-BVqqaDun.js.br +0 -0
- package/payload/server/public/assets/file-text-BVqqaDun.js.gz +0 -0
- package/payload/server/public/assets/graph-CEcME0lp.js.br +0 -0
- package/payload/server/public/assets/graph-CEcME0lp.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-Zl32_AOH.js.br +0 -0
- package/payload/server/public/assets/graph-labels-Zl32_AOH.js.gz +0 -0
- package/payload/server/public/assets/maximize-2-rkkMyAhf.js.br +0 -0
- package/payload/server/public/assets/maximize-2-rkkMyAhf.js.gz +0 -0
- package/payload/server/public/assets/operator-DlL023ch.js +0 -1
- package/payload/server/public/assets/operator-DlL023ch.js.br +0 -0
- package/payload/server/public/assets/operator-DlL023ch.js.gz +0 -0
- package/payload/server/public/assets/page-BAmYYqxp.js +0 -1
- package/payload/server/public/assets/page-BAmYYqxp.js.br +0 -0
- package/payload/server/public/assets/page-BAmYYqxp.js.gz +0 -0
- package/payload/server/public/assets/page-k0BhBkA2.js +0 -32
- package/payload/server/public/assets/page-k0BhBkA2.js.br +0 -0
- package/payload/server/public/assets/page-k0BhBkA2.js.gz +0 -0
- package/payload/server/public/assets/public-Cqgfmo1D.js.br +0 -0
- package/payload/server/public/assets/public-Cqgfmo1D.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-O3RmqoTy.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-O3RmqoTy.js.gz +0 -0
- package/payload/server/public/assets/routines-DMYObMUy.js.br +0 -0
- package/payload/server/public/assets/routines-DMYObMUy.js.gz +0 -0
- package/payload/server/public/assets/skills-DON1BKu5.js.br +0 -0
- package/payload/server/public/assets/skills-DON1BKu5.js.gz +0 -0
- package/payload/server/public/assets/tasks-C2PhUSrf.js +0 -1
- package/payload/server/public/assets/tasks-C2PhUSrf.js.br +0 -0
- package/payload/server/public/assets/tasks-C2PhUSrf.js.gz +0 -0
- package/payload/server/public/assets/time-entry-format-4zgmuQI0.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-4zgmuQI0.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-By5D7wuI.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-By5D7wuI.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-Dx0vtm9s.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-Dx0vtm9s.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CRWoQXuU.js +0 -9
- package/payload/server/public/assets/useSubAccountSwitcher-CRWoQXuU.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CRWoQXuU.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-UQQDcj0B.css +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-UQQDcj0B.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-UQQDcj0B.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BgwOCCwj.js +0 -2
- package/payload/server/public/assets/useVoiceRecorder-BgwOCCwj.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BgwOCCwj.js.gz +0 -0
- package/payload/server/public/assets/wrench-CHfMwLza.js.br +0 -0
- package/payload/server/public/assets/wrench-CHfMwLza.js.gz +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Nothing else in the repo ties the edge unit template's placeholders to the
|
|
2
|
+
// substitutions in index.ts, so either half can be edited alone and the whole
|
|
3
|
+
// suite stays green. Two silent failures this closes:
|
|
4
|
+
//
|
|
5
|
+
// - substitution added, template left at a literal value → the replace is a
|
|
6
|
+
// no-op, the unit still binds 0.0.0.0, and the Task 2082 security fix ships
|
|
7
|
+
// inert with every pickEdgeBindDecision test passing;
|
|
8
|
+
// - template placeholder added, substitution forgotten → the unit ships a
|
|
9
|
+
// literal `Environment=EDGE_HOSTNAME=__EDGE_HOSTNAME__`, which edge.ts's
|
|
10
|
+
// `process.env.EDGE_HOSTNAME ?? '0.0.0.0'` does NOT catch, because the
|
|
11
|
+
// variable is present, just not an address, so server.listen() gets a
|
|
12
|
+
// non-address and the edge restart-loops.
|
|
13
|
+
//
|
|
14
|
+
// Both files are read from disk at runtime, the same pattern as
|
|
15
|
+
// macos-darwin-branch.test.ts and base-toolchain-deps.test.ts.
|
|
16
|
+
import test from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { dirname, resolve } from "node:path";
|
|
21
|
+
// dist/__tests__/edge-bind-template-wiring.test.js → ../../src/index.ts
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const INDEX_TS = resolve(here, "../../src/index.ts");
|
|
24
|
+
// dist/__tests__ → up four is the maxy-code root that holds platform/.
|
|
25
|
+
const EDGE_TEMPLATE = resolve(here, "../../../../platform/templates/systemd/edge.service.template");
|
|
26
|
+
const SRC = readFileSync(INDEX_TS, "utf-8");
|
|
27
|
+
const TEMPLATE = readFileSync(EDGE_TEMPLATE, "utf-8");
|
|
28
|
+
test("edge template: EDGE_HOSTNAME is a placeholder, not a hardcoded address", () => {
|
|
29
|
+
assert.match(TEMPLATE, /^Environment=EDGE_HOSTNAME=__EDGE_HOSTNAME__$/m, "edge.service.template must take EDGE_HOSTNAME from the installer's bind decision (Task 2082), not hardcode it");
|
|
30
|
+
});
|
|
31
|
+
test("edge template: every placeholder has a matching substitution in index.ts", () => {
|
|
32
|
+
const placeholders = [...new Set(TEMPLATE.match(/__[A-Z0-9_]+__/g) ?? [])].sort();
|
|
33
|
+
assert.ok(placeholders.includes("__EDGE_HOSTNAME__"), "template must carry __EDGE_HOSTNAME__");
|
|
34
|
+
for (const p of placeholders) {
|
|
35
|
+
assert.ok(SRC.includes(`.replace(/${p}/g,`), `${p} appears in edge.service.template but index.ts has no .replace(/${p}/g, ...) — the rendered unit would ship the literal placeholder`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// style and runs under `node --test dist/__tests__/*.test.js` after build.
|
|
7
7
|
import test from "node:test";
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
|
-
import { renderBrandStanza, renderGlobalSection, renderFullSmbConf, isPrivateIPv4, pickBindDecision, mergeSmbConf, removeBrandStanza, hasAnyBrandStanza, formatSambaMarker, SAMBA_STEPS, SAMBA_ENABLE_UNITS, } from "../samba-provision.js";
|
|
9
|
+
import { renderBrandStanza, renderGlobalSection, renderFullSmbConf, isPrivateIPv4, pickBindDecision, pickEdgeBindDecision, formatEdgeBindDecision, mergeSmbConf, removeBrandStanza, hasAnyBrandStanza, formatSambaMarker, SAMBA_STEPS, SAMBA_ENABLE_UNITS, } from "../samba-provision.js";
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// Fixtures
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
@@ -49,6 +49,42 @@ const MIXED_PUBLIC_AND_PRIVATE = {
|
|
|
49
49
|
eth0: [{ address: "178.105.251.82", netmask: "255.255.255.255", family: "IPv4", mac: "96:00:00:00:00:02", internal: false, cidr: "178.105.251.82/32" }],
|
|
50
50
|
wg0: [{ address: "10.8.0.3", netmask: "255.255.255.0", family: "IPv4", mac: "00:00:00:00:00:00", internal: false, cidr: "10.8.0.3/24" }],
|
|
51
51
|
};
|
|
52
|
+
// Hetzner Cloud shape: public eth0 plus a provider private network on ens10.
|
|
53
|
+
// pickBindDecision calls this `lan`; the edge must still bind loopback.
|
|
54
|
+
const HETZNER_CLOUD_PUBLIC_PLUS_PRIVNET = {
|
|
55
|
+
lo: [{ address: "127.0.0.1", netmask: "255.0.0.0", family: "IPv4", mac: "00:00:00:00:00:00", internal: true, cidr: "127.0.0.1/8" }],
|
|
56
|
+
eth0: [{ address: "167.233.21.246", netmask: "255.255.255.255", family: "IPv4", mac: "96:00:00:00:00:03", internal: false, cidr: "167.233.21.246/32" }],
|
|
57
|
+
ens10: [{ address: "10.0.0.2", netmask: "255.255.0.0", family: "IPv4", mac: "96:00:00:00:00:04", internal: false, cidr: "10.0.0.2/16" }],
|
|
58
|
+
};
|
|
59
|
+
// Private wlan0 plus a public address elsewhere. Reaches pickBindDecision's
|
|
60
|
+
// wlan0 early return, which MIXED_PUBLIC_AND_PRIVATE never does because its
|
|
61
|
+
// private address is on wg0.
|
|
62
|
+
const PRIVATE_WLAN0_PLUS_PUBLIC = {
|
|
63
|
+
lo: [{ address: "127.0.0.1", netmask: "255.0.0.0", family: "IPv4", mac: "00:00:00:00:00:00", internal: true, cidr: "127.0.0.1/8" }],
|
|
64
|
+
wlan0: [{ address: "192.168.1.50", netmask: "255.255.255.0", family: "IPv4", mac: "aa:bb:cc:dd:ee:ff", internal: false, cidr: "192.168.1.50/24" }],
|
|
65
|
+
eth0: [{ address: "203.0.113.7", netmask: "255.255.255.255", family: "IPv4", mac: "96:00:00:00:00:05", internal: false, cidr: "203.0.113.7/32" }],
|
|
66
|
+
};
|
|
67
|
+
// Private eth0 plus a public address elsewhere. Reaches the eth0 early return.
|
|
68
|
+
const PRIVATE_ETH0_PLUS_PUBLIC = {
|
|
69
|
+
lo: [{ address: "127.0.0.1", netmask: "255.0.0.0", family: "IPv4", mac: "00:00:00:00:00:00", internal: true, cidr: "127.0.0.1/8" }],
|
|
70
|
+
eth0: [{ address: "10.0.0.5", netmask: "255.255.255.0", family: "IPv4", mac: "11:11:11:11:11:11", internal: false, cidr: "10.0.0.5/24" }],
|
|
71
|
+
ens5: [{ address: "198.51.100.9", netmask: "255.255.255.255", family: "IPv4", mac: "96:00:00:00:00:06", internal: false, cidr: "198.51.100.9/32" }],
|
|
72
|
+
};
|
|
73
|
+
// One interface carrying both a public and a private IPv4. No other fixture in
|
|
74
|
+
// this file has a multi-address interface.
|
|
75
|
+
const ONE_IFACE_PUBLIC_AND_PRIVATE = {
|
|
76
|
+
lo: [{ address: "127.0.0.1", netmask: "255.0.0.0", family: "IPv4", mac: "00:00:00:00:00:00", internal: true, cidr: "127.0.0.1/8" }],
|
|
77
|
+
eth0: [
|
|
78
|
+
{ address: "192.0.2.44", netmask: "255.255.255.255", family: "IPv4", mac: "96:00:00:00:00:07", internal: false, cidr: "192.0.2.44/32" },
|
|
79
|
+
{ address: "172.16.4.9", netmask: "255.255.0.0", family: "IPv4", mac: "96:00:00:00:00:07", internal: false, cidr: "172.16.4.9/16" },
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
// CGNAT-only host. isPrivateIPv4 counts 100.64/10 as private, so this binds
|
|
83
|
+
// 0.0.0.0. Pinned so the outcome is declared rather than incidental.
|
|
84
|
+
const CGNAT_ONLY = {
|
|
85
|
+
lo: [{ address: "127.0.0.1", netmask: "255.0.0.0", family: "IPv4", mac: "00:00:00:00:00:00", internal: true, cidr: "127.0.0.1/8" }],
|
|
86
|
+
eth0: [{ address: "100.100.3.14", netmask: "255.192.0.0", family: "IPv4", mac: "96:00:00:00:00:08", internal: false, cidr: "100.100.3.14/10" }],
|
|
87
|
+
};
|
|
52
88
|
// ---------------------------------------------------------------------------
|
|
53
89
|
// isPrivateIPv4 — RFC1918 / CGNAT / link-local classification
|
|
54
90
|
// ---------------------------------------------------------------------------
|
|
@@ -107,6 +143,72 @@ test("pickBindDecision: none when the only non-loopback iface has no IPv4", () =
|
|
|
107
143
|
assert.deepEqual(pickBindDecision(PI_IPV6_ONLY_LAN), { kind: "none" });
|
|
108
144
|
});
|
|
109
145
|
// ---------------------------------------------------------------------------
|
|
146
|
+
// pickEdgeBindDecision — 0.0.0.0 only when private exists and public does not
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
test("pickEdgeBindDecision: private-only hosts keep the public bind", () => {
|
|
149
|
+
for (const [name, ifaces] of [
|
|
150
|
+
["PI_WLAN0_ONLY", PI_WLAN0_ONLY],
|
|
151
|
+
["PI_BOTH_IFACES", PI_BOTH_IFACES],
|
|
152
|
+
["PI_ETH0_ONLY", PI_ETH0_ONLY],
|
|
153
|
+
["PI_USB0_FALLBACK", PI_USB0_FALLBACK],
|
|
154
|
+
["CGNAT_ONLY", CGNAT_ONLY],
|
|
155
|
+
]) {
|
|
156
|
+
assert.equal(pickEdgeBindDecision(ifaces).hostname, "0.0.0.0", name);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// The fail-open this task exists to close: pickBindDecision calls every one of
|
|
160
|
+
// these `lan`, so keeping its verdict verbatim would leave the edge public on a
|
|
161
|
+
// cloud box that happens to carry a provider private network.
|
|
162
|
+
test("pickEdgeBindDecision: a public IPv4 forces loopback even when a private one exists", () => {
|
|
163
|
+
for (const [name, ifaces] of [
|
|
164
|
+
["MIXED_PUBLIC_AND_PRIVATE", MIXED_PUBLIC_AND_PRIVATE],
|
|
165
|
+
["HETZNER_CLOUD_PUBLIC_PLUS_PRIVNET", HETZNER_CLOUD_PUBLIC_PLUS_PRIVNET],
|
|
166
|
+
["PRIVATE_WLAN0_PLUS_PUBLIC", PRIVATE_WLAN0_PLUS_PUBLIC],
|
|
167
|
+
["PRIVATE_ETH0_PLUS_PUBLIC", PRIVATE_ETH0_PLUS_PUBLIC],
|
|
168
|
+
["ONE_IFACE_PUBLIC_AND_PRIVATE", ONE_IFACE_PUBLIC_AND_PRIVATE],
|
|
169
|
+
]) {
|
|
170
|
+
const d = pickEdgeBindDecision(ifaces);
|
|
171
|
+
assert.equal(d.hostname, "127.0.0.1", name);
|
|
172
|
+
assert.equal(d.kind, "lan", `${name} still classifies as lan for smbd`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
test("pickEdgeBindDecision: public-only host binds loopback", () => {
|
|
176
|
+
const d = pickEdgeBindDecision(HETZNER_PUBLIC_ETH0_ONLY);
|
|
177
|
+
assert.equal(d.hostname, "127.0.0.1");
|
|
178
|
+
assert.equal(d.kind, "loopback-only");
|
|
179
|
+
});
|
|
180
|
+
// Unlike the Samba call site, which throws on `none`, the edge binds loopback
|
|
181
|
+
// and lets the install finish: a loopback edge still serves the tunnel.
|
|
182
|
+
test("pickEdgeBindDecision: no non-loopback IPv4 binds loopback rather than throwing", () => {
|
|
183
|
+
for (const ifaces of [PI_LO_ONLY, PI_IPV6_ONLY_LAN]) {
|
|
184
|
+
const d = pickEdgeBindDecision(ifaces);
|
|
185
|
+
assert.equal(d.hostname, "127.0.0.1");
|
|
186
|
+
assert.equal(d.kind, "none");
|
|
187
|
+
assert.deepEqual(d.privateAddresses, []);
|
|
188
|
+
assert.deepEqual(d.publicAddresses, []);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
test("pickEdgeBindDecision: reports the interface and addresses it decided from", () => {
|
|
192
|
+
assert.deepEqual(pickEdgeBindDecision(MIXED_PUBLIC_AND_PRIVATE), {
|
|
193
|
+
hostname: "127.0.0.1",
|
|
194
|
+
kind: "lan",
|
|
195
|
+
iface: "wg0",
|
|
196
|
+
publicAddresses: ["178.105.251.82"],
|
|
197
|
+
privateAddresses: ["10.8.0.3"],
|
|
198
|
+
});
|
|
199
|
+
assert.deepEqual(pickEdgeBindDecision(PI_WLAN0_ONLY), {
|
|
200
|
+
hostname: "0.0.0.0",
|
|
201
|
+
kind: "lan",
|
|
202
|
+
iface: "wlan0",
|
|
203
|
+
publicAddresses: [],
|
|
204
|
+
privateAddresses: ["192.168.1.50"],
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
test("formatEdgeBindDecision: carries both grep tokens and never leaves an empty field", () => {
|
|
208
|
+
assert.equal(formatEdgeBindDecision(pickEdgeBindDecision(PI_WLAN0_ONLY)), "[edge-bind-decision] kind=lan iface=wlan0 private=192.168.1.50 public=none EDGE_HOSTNAME=0.0.0.0");
|
|
209
|
+
assert.equal(formatEdgeBindDecision(pickEdgeBindDecision(HETZNER_PUBLIC_ETH0_ONLY)), "[edge-bind-decision] kind=loopback-only iface=none private=none public=167.233.21.246 EDGE_HOSTNAME=127.0.0.1");
|
|
210
|
+
});
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
110
212
|
// renderBrandStanza — exact spec match
|
|
111
213
|
// ---------------------------------------------------------------------------
|
|
112
214
|
test("renderBrandStanza emits the spec directives in order", () => {
|
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { validateTierFlag, applyTierToAccountConfig, isValidTier, } from "../tier-flag.js";
|
|
7
|
-
test("validateTierFlag accepts solo|family|pro when commercialMode is false", () => {
|
|
7
|
+
test("validateTierFlag accepts solo|family|pro|max when commercialMode is false", () => {
|
|
8
8
|
assert.equal(validateTierFlag("solo", false), "solo");
|
|
9
9
|
assert.equal(validateTierFlag("family", false), "family");
|
|
10
10
|
assert.equal(validateTierFlag("pro", false), "pro");
|
|
11
|
+
assert.equal(validateTierFlag("max", false), "max");
|
|
11
12
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Task 2063 — both messages are joined from VALID_TIERS, never written out.
|
|
14
|
+
// The stale-payload failure mode is diagnosed from exactly this text, so a
|
|
15
|
+
// hardcoded list would let a stale build print a plausible one.
|
|
16
|
+
test("validateTierFlag rejects values outside the enum, naming every valid tier", () => {
|
|
17
|
+
assert.throws(() => validateTierFlag("enterprise", false), /--tier value must be one of: solo, family, pro, max \(got: enterprise\)/);
|
|
15
18
|
});
|
|
16
|
-
test("validateTierFlag rejects empty or flag-shaped value", () => {
|
|
17
|
-
assert.throws(() => validateTierFlag(undefined, false), /--tier requires a value/);
|
|
19
|
+
test("validateTierFlag rejects empty or flag-shaped value, naming every valid tier", () => {
|
|
20
|
+
assert.throws(() => validateTierFlag(undefined, false), /--tier requires a value \(one of: solo, family, pro, max\)/);
|
|
18
21
|
assert.throws(() => validateTierFlag("", false), /--tier requires a value/);
|
|
19
22
|
assert.throws(() => validateTierFlag("--hostname", false), /--tier requires a value/);
|
|
20
23
|
});
|
|
@@ -48,6 +51,7 @@ test("isValidTier narrows correctly", () => {
|
|
|
48
51
|
assert.equal(isValidTier("solo"), true);
|
|
49
52
|
assert.equal(isValidTier("family"), true);
|
|
50
53
|
assert.equal(isValidTier("pro"), true);
|
|
54
|
+
assert.equal(isValidTier("max"), true);
|
|
51
55
|
assert.equal(isValidTier("enterprise"), false);
|
|
52
56
|
assert.equal(isValidTier(""), false);
|
|
53
57
|
});
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import { classifyPortHolder } from "./preflight-port-classifier.js";
|
|
|
29
29
|
import { memoryControllerMissing, cmdlineAlreadyPatched, patchCmdline } from "./cgroup-memory-controller.js";
|
|
30
30
|
import { parsePluginList, computeInstallActions, parseExternalPlugins, findUnregisteredResyncs, discoverMarketplaceCandidateDirs, } from "./lib/plugin-install.js";
|
|
31
31
|
import { findPremiumMcpDirs } from "./lib/premium-mcp-discover.js";
|
|
32
|
-
import { pickBindDecision, mergeSmbConf, formatSambaMarker, SAMBA_ENABLE_UNITS, } from "./samba-provision.js";
|
|
32
|
+
import { pickBindDecision, pickEdgeBindDecision, formatEdgeBindDecision, mergeSmbConf, formatSambaMarker, SAMBA_ENABLE_UNITS, } from "./samba-provision.js";
|
|
33
33
|
import { networkInterfaces, userInfo, cpus } from "node:os";
|
|
34
34
|
const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
|
|
35
35
|
// Brand manifest — read from payload to derive all brand-specific installation values.
|
|
@@ -4493,11 +4493,20 @@ WantedBy=default.target
|
|
|
4493
4493
|
// VNC_DISPLAY (defined above for the main brand unit) is also stamped into
|
|
4494
4494
|
// the edge unit so both services agree on the X display. Per-brand display
|
|
4495
4495
|
// closes the cross-brand cookie leak documented.
|
|
4496
|
+
// Task 2082 — the edge binds 0.0.0.0 only on a host with a private IPv4 and
|
|
4497
|
+
// no public one; anything else binds loopback so a rented server never puts
|
|
4498
|
+
// the admin edge on the internet. Logged with its inputs because a wrong
|
|
4499
|
+
// classification is otherwise invisible until an external probe finds it.
|
|
4500
|
+
// Linux-only by construction: this function returns at the darwin branch
|
|
4501
|
+
// above, and darwin runs no edge process at all.
|
|
4502
|
+
const edgeBind = pickEdgeBindDecision(networkInterfaces());
|
|
4503
|
+
logFile(` ${formatEdgeBindDecision(edgeBind)}`);
|
|
4496
4504
|
const edgeTemplatePath = resolve(INSTALL_DIR, "platform/templates/systemd/edge.service.template");
|
|
4497
4505
|
if (existsSync(edgeTemplatePath)) {
|
|
4498
4506
|
const edgeServiceContent = readFileSync(edgeTemplatePath, "utf-8")
|
|
4499
4507
|
.replace(/__INSTALL_DIR__/g, INSTALL_DIR)
|
|
4500
4508
|
.replace(/__EDGE_PORT__/g, String(PORT))
|
|
4509
|
+
.replace(/__EDGE_HOSTNAME__/g, edgeBind.hostname)
|
|
4501
4510
|
.replace(/__MAXY_UI_PORT__/g, String(MAXY_UI_INTERNAL_PORT))
|
|
4502
4511
|
.replace(/__PERSIST_DIR__/g, persistDir)
|
|
4503
4512
|
.replace(/__VNC_DISPLAY__/g, String(VNC_DISPLAY))
|
|
@@ -4505,7 +4514,7 @@ WantedBy=default.target
|
|
|
4505
4514
|
.replace(/__CDP_PORT__/g, String(CDP_PORT_BRAND))
|
|
4506
4515
|
.replace(/__RFB_PORT__/g, String(RFB_PORT));
|
|
4507
4516
|
writeFileSync(join(serviceDir, edgeUnitName), edgeServiceContent);
|
|
4508
|
-
logFile(` ${edgeUnitName}: EDGE_PORT=${PORT} MAXY_UI_PORT=${MAXY_UI_INTERNAL_PORT} VNC_DISPLAY=:${VNC_DISPLAY} RFB_PORT=${RFB_PORT} WEBSOCKIFY_PORT=${WEBSOCKIFY_PORT_BRAND} CDP_PORT=${CDP_PORT_BRAND}`);
|
|
4517
|
+
logFile(` ${edgeUnitName}: EDGE_PORT=${PORT} EDGE_HOSTNAME=${edgeBind.hostname} MAXY_UI_PORT=${MAXY_UI_INTERNAL_PORT} VNC_DISPLAY=:${VNC_DISPLAY} RFB_PORT=${RFB_PORT} WEBSOCKIFY_PORT=${WEBSOCKIFY_PORT_BRAND} CDP_PORT=${CDP_PORT_BRAND}`);
|
|
4509
4518
|
}
|
|
4510
4519
|
else {
|
|
4511
4520
|
console.error(` WARNING: edge.service.template missing at ${edgeTemplatePath} — VNC transport unavailable`);
|
|
@@ -4998,7 +5007,7 @@ if (hostnameIdx !== -1) {
|
|
|
4998
5007
|
// ---------------------------------------------------------------------------
|
|
4999
5008
|
// Founder-side tier-set flags (Task 240).
|
|
5000
5009
|
//
|
|
5001
|
-
// `--tier <solo|family|pro>` stamps account.json.tier directly when the brand
|
|
5010
|
+
// `--tier <solo|family|pro|max>` stamps account.json.tier directly when the brand
|
|
5002
5011
|
// is personal-mode (today's default for both maxy-code and realagent-code).
|
|
5003
5012
|
// `--entitlement-base64 <b64>` decodes a signed payload and writes it to
|
|
5004
5013
|
// ~/<BRAND.configDir>/entitlement.json when the brand is commercial-mode.
|
|
@@ -5389,8 +5398,14 @@ try {
|
|
|
5389
5398
|
console.log("================================================================");
|
|
5390
5399
|
console.log("");
|
|
5391
5400
|
if (isLinux()) {
|
|
5392
|
-
|
|
5393
|
-
|
|
5401
|
+
// Only true when the edge binds 0.0.0.0. On a host classified to loopback
|
|
5402
|
+
// (Task 2082) nothing answers on the LAN address, so printing this line
|
|
5403
|
+
// there would be a false instruction; the SSH-forward recipe below is the
|
|
5404
|
+
// whole story for that host class.
|
|
5405
|
+
if (pickEdgeBindDecision(networkInterfaces()).hostname === "0.0.0.0") {
|
|
5406
|
+
console.log(` Same network (Pi / LAN): http://${DEVICE_HOSTNAME}.local:${PORT}`);
|
|
5407
|
+
console.log("");
|
|
5408
|
+
}
|
|
5394
5409
|
console.log(` Remote access (Hetzner / cloud) — on your local machine run:`);
|
|
5395
5410
|
console.log(` ssh -L ${PORT}:localhost:${PORT} -L ${BRAND.websockifyPort}:localhost:${BRAND.websockifyPort} admin@<server-ipv4>`);
|
|
5396
5411
|
console.log(` Then open:`);
|
package/dist/samba-provision.js
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
// stanza intact (peer-brand isolation is the structural invariant).
|
|
15
15
|
// 5. Report whether any brand stanza remains so the uninstall step can
|
|
16
16
|
// decide whether to apt-purge samba.
|
|
17
|
+
//
|
|
18
|
+
// Despite the file name, the interface classification here serves two
|
|
19
|
+
// consumers, not one: `isPrivateIPv4` and `pickBindDecision` answer where smbd
|
|
20
|
+
// binds, and `pickEdgeBindDecision` (Task 2082) answers where the edge binds.
|
|
21
|
+
// The two deliberately disagree on a dual-homed host; see that function.
|
|
17
22
|
// ---------------------------------------------------------------------------
|
|
18
23
|
// Brand-scoped Samba stanza
|
|
19
24
|
// ---------------------------------------------------------------------------
|
|
@@ -152,6 +157,73 @@ export function pickBindDecision(ifaces) {
|
|
|
152
157
|
const hasNonLoopbackIPv4 = Object.keys(ifaces).some((name) => name !== "lo" && ipv4Addrs(name).length > 0);
|
|
153
158
|
return hasNonLoopbackIPv4 ? { kind: "loopback-only" } : { kind: "none" };
|
|
154
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Decide how the edge should bind, from the same interface table
|
|
162
|
+
* `pickBindDecision` reads. The edge binds `0.0.0.0` only when the host has at
|
|
163
|
+
* least one private IPv4 AND zero public IPv4; every other shape binds
|
|
164
|
+
* `127.0.0.1`. Task 2082.
|
|
165
|
+
*
|
|
166
|
+
* Three properties are deliberate, not oversights:
|
|
167
|
+
*
|
|
168
|
+
* 1. This DISAGREES with `pickBindDecision` on a dual-homed host. For SMB, a
|
|
169
|
+
* private interface alongside a public one keeps the LAN bind (operator
|
|
170
|
+
* confirmation, Task 755 — see the MIXED_PUBLIC_AND_PRIVATE fixture). For
|
|
171
|
+
* the edge the operator decided the opposite: a cloud server carrying a
|
|
172
|
+
* provider private network (Hetzner Cloud's public `eth0` plus private
|
|
173
|
+
* `ens10`) must not silently keep a public bind. Both functions are right
|
|
174
|
+
* for their own consumer; this is not a rename of the other.
|
|
175
|
+
* 2. IPv6 is ignored. `pickBindDecision` already filters to IPv4, and
|
|
176
|
+
* `0.0.0.0` binds IPv4 only, so a public IPv6 address cannot be exposed by
|
|
177
|
+
* this bind under either outcome.
|
|
178
|
+
* 3. A cloud host presenting its public address by 1:1 NAT is NOT covered.
|
|
179
|
+
* Its guest NIC carries only a private address, so this reads a
|
|
180
|
+
* private-only host and binds `0.0.0.0` while the box is publicly
|
|
181
|
+
* reachable. The scoped signal is a public IPv4 alongside the private one;
|
|
182
|
+
* no host in `.docs/install/` behaves that way, and catching it would need
|
|
183
|
+
* an outbound probe rather than the interface table.
|
|
184
|
+
*
|
|
185
|
+
* `kind: "none"` binds `127.0.0.1` rather than throwing, unlike the Samba call
|
|
186
|
+
* site, which hard-fails: a share bound to nothing is useless, whereas a
|
|
187
|
+
* loopback edge still serves the Cloudflare tunnel, which dials
|
|
188
|
+
* `http://localhost:<port>`. Cost: a host that gains an interface later stays
|
|
189
|
+
* loopback-only until the installer is re-run, which the decision line makes
|
|
190
|
+
* visible in the install log.
|
|
191
|
+
*/
|
|
192
|
+
export function pickEdgeBindDecision(ifaces) {
|
|
193
|
+
const bind = pickBindDecision(ifaces);
|
|
194
|
+
const publicAddresses = [];
|
|
195
|
+
const privateAddresses = [];
|
|
196
|
+
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
197
|
+
if (name === "lo" || !addrs)
|
|
198
|
+
continue;
|
|
199
|
+
for (const a of addrs) {
|
|
200
|
+
if (a.family !== "IPv4" || a.internal)
|
|
201
|
+
continue;
|
|
202
|
+
if (isPrivateIPv4(a.address))
|
|
203
|
+
privateAddresses.push(a.address);
|
|
204
|
+
else
|
|
205
|
+
publicAddresses.push(a.address);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
hostname: privateAddresses.length > 0 && publicAddresses.length === 0 ? "0.0.0.0" : "127.0.0.1",
|
|
210
|
+
kind: bind.kind,
|
|
211
|
+
iface: bind.kind === "lan" ? bind.lanInterface : null,
|
|
212
|
+
publicAddresses,
|
|
213
|
+
privateAddresses,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The install-log line for an edge bind decision. The shape is load-bearing:
|
|
218
|
+
* the task's diagnostic path greps `bind-decision` and `EDGE_HOSTNAME`, so both
|
|
219
|
+
* tokens must survive any edit. Empty lists render as the literal `none` so a
|
|
220
|
+
* host with no addresses is distinguishable from a truncated line.
|
|
221
|
+
*/
|
|
222
|
+
export function formatEdgeBindDecision(d) {
|
|
223
|
+
const list = (xs) => (xs.length === 0 ? "none" : xs.join(","));
|
|
224
|
+
return `[edge-bind-decision] kind=${d.kind} iface=${d.iface ?? "none"} ` +
|
|
225
|
+
`private=${list(d.privateAddresses)} public=${list(d.publicAddresses)} EDGE_HOSTNAME=${d.hostname}`;
|
|
226
|
+
}
|
|
155
227
|
// ---------------------------------------------------------------------------
|
|
156
228
|
// smb.conf merge / remove
|
|
157
229
|
// ---------------------------------------------------------------------------
|
package/dist/tier-flag.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
// Pure validation + path helpers for the founder-side tier-set installer flags
|
|
2
2
|
// (Task 240). Two flags wired into the installer at src/index.ts:
|
|
3
|
-
// --tier <solo|family|pro>
|
|
3
|
+
// --tier <solo|family|pro|max> personal-mode (brand.commercialMode === false)
|
|
4
4
|
// --entitlement-base64 <b64> commercial-mode (brand.commercialMode === true)
|
|
5
5
|
//
|
|
6
6
|
// Both helpers throw on invalid input; the caller exits non-zero and surfaces the
|
|
7
7
|
// thrown message. No filesystem or console side effects — index.ts owns those.
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
|
-
const VALID_TIERS = new Set(["solo", "family", "pro"]);
|
|
9
|
+
const VALID_TIERS = new Set(["solo", "family", "pro", "max"]);
|
|
10
|
+
// Task 2063 — the list in both thrown messages is JOINED FROM VALID_TIERS,
|
|
11
|
+
// never written out. A device that rejects a valid `--tier` because it carries
|
|
12
|
+
// a stale bundled build is diagnosed from exactly this text, so a hardcoded
|
|
13
|
+
// list would let a stale build print a plausible one and destroy the signal.
|
|
14
|
+
const TIER_LIST = [...VALID_TIERS].join(", ");
|
|
10
15
|
export function isValidTier(value) {
|
|
11
16
|
return VALID_TIERS.has(value);
|
|
12
17
|
}
|
|
@@ -15,7 +20,7 @@ export function isValidTier(value) {
|
|
|
15
20
|
*
|
|
16
21
|
* Throws when:
|
|
17
22
|
* - raw is missing / starts with `--` (no value supplied after the flag)
|
|
18
|
-
* - raw is not in {solo, family, pro}
|
|
23
|
+
* - raw is not in {solo, family, pro, max}
|
|
19
24
|
* - brand is commercial-mode (operator must use --entitlement-base64 instead)
|
|
20
25
|
*/
|
|
21
26
|
export function validateTierFlag(raw, commercialMode) {
|
|
@@ -23,10 +28,10 @@ export function validateTierFlag(raw, commercialMode) {
|
|
|
23
28
|
throw new Error("--tier is for personal mode. Commercial mode — use --entitlement-base64 to deliver a signed payload.");
|
|
24
29
|
}
|
|
25
30
|
if (!raw || raw.startsWith("--")) {
|
|
26
|
-
throw new Error(
|
|
31
|
+
throw new Error(`--tier requires a value (one of: ${TIER_LIST}).`);
|
|
27
32
|
}
|
|
28
33
|
if (!isValidTier(raw)) {
|
|
29
|
-
throw new Error(`--tier value must be one of:
|
|
34
|
+
throw new Error(`--tier value must be one of: ${TIER_LIST} (got: ${raw}).`);
|
|
30
35
|
}
|
|
31
36
|
return raw;
|
|
32
37
|
}
|
package/package.json
CHANGED
|
@@ -62,6 +62,30 @@ export interface BrandConfigSubset {
|
|
|
62
62
|
/** True only when this brand has flipped to commercial entitlement enforcement. */
|
|
63
63
|
commercialMode?: boolean;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Admin seats per tier. Lives beside VALID_TIERS deliberately (Task 2063).
|
|
67
|
+
* The admin MCP used to hold its own copy and resolve through a silent
|
|
68
|
+
* `?? MAX_ADMINS_DEFAULT`, so a tier added to the signed-payload registry
|
|
69
|
+
* but not to that table refused the 6th admin with a message reading as a
|
|
70
|
+
* limit rather than a bug — no event, and re-running reproduced nothing.
|
|
71
|
+
* Adding a tier now puts both edits in one file, and the unmapped case is
|
|
72
|
+
* an event rather than a fallback.
|
|
73
|
+
*/
|
|
74
|
+
export declare const MAX_ADMINS_BY_TIER: Readonly<Record<string, number>>;
|
|
75
|
+
export declare const MAX_ADMINS_DEFAULT = 5;
|
|
76
|
+
/**
|
|
77
|
+
* Admin seat cap for an effective tier. Emits `op=tier-cap-unmapped` when a
|
|
78
|
+
* tier that IS SET carries no entry — the standing signal for a tier that
|
|
79
|
+
* verifies but has no seat count, which is otherwise indistinguishable from a
|
|
80
|
+
* real limit.
|
|
81
|
+
*
|
|
82
|
+
* An EMPTY tier is not that fault and must stay silent. The installer stamps
|
|
83
|
+
* account.json.tier only when `--tier` is passed, so every default personal
|
|
84
|
+
* install reaches implicitTrust with `tier: ""` — the commonest shape there
|
|
85
|
+
* is. Alerting on it would fire this signal on installs where nothing is
|
|
86
|
+
* wrong, which is the same loss as having no signal at all.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveMaxAdmins(tier: string): number;
|
|
65
89
|
/**
|
|
66
90
|
* Resolve effective entitlement for the current install. Sync — Ed25519
|
|
67
91
|
* verify is a CPU-bound primitive on the order of 0.3ms; no async surface.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAoBH,eAAO,MAAM,aAAa,qEAAqE,CAAC;AAMhG,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,cAAc,GACd,gBAAgB,GAChB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,2GAA2G;IAC3G,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA0BD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,mBAAmB,GAC3B,mBAAmB,CAuBrB;AAED,sEAAsE;AACtE,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAoBH,eAAO,MAAM,aAAa,qEAAqE,CAAC;AAMhG,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,cAAc,GACd,gBAAgB,GAChB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,2GAA2G;IAC3G,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA0BD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK/D,CAAC;AACF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASrD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,mBAAmB,GAC3B,mBAAmB,CAuBrB;AAED,sEAAsE;AACtE,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
* is explicitly out of scope.
|
|
42
42
|
*/
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.PUBKEY_SHA256 = void 0;
|
|
44
|
+
exports.MAX_ADMINS_DEFAULT = exports.MAX_ADMINS_BY_TIER = exports.PUBKEY_SHA256 = void 0;
|
|
45
|
+
exports.resolveMaxAdmins = resolveMaxAdmins;
|
|
45
46
|
exports.resolveEntitlement = resolveEntitlement;
|
|
46
47
|
exports._clearMemo = _clearMemo;
|
|
47
48
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -78,7 +79,44 @@ function memoKey(mtimeMs, account) {
|
|
|
78
79
|
const bucket = Math.floor(Date.now() / 60000);
|
|
79
80
|
return `${mtimeMs}:${bucket}:${account.accountId}:${account.customerEmail ?? ""}`;
|
|
80
81
|
}
|
|
81
|
-
const VALID_TIERS = new Set(["solo", "family", "pro"]);
|
|
82
|
+
const VALID_TIERS = new Set(["solo", "family", "pro", "max"]);
|
|
83
|
+
/**
|
|
84
|
+
* Admin seats per tier. Lives beside VALID_TIERS deliberately (Task 2063).
|
|
85
|
+
* The admin MCP used to hold its own copy and resolve through a silent
|
|
86
|
+
* `?? MAX_ADMINS_DEFAULT`, so a tier added to the signed-payload registry
|
|
87
|
+
* but not to that table refused the 6th admin with a message reading as a
|
|
88
|
+
* limit rather than a bug — no event, and re-running reproduced nothing.
|
|
89
|
+
* Adding a tier now puts both edits in one file, and the unmapped case is
|
|
90
|
+
* an event rather than a fallback.
|
|
91
|
+
*/
|
|
92
|
+
exports.MAX_ADMINS_BY_TIER = {
|
|
93
|
+
solo: 1,
|
|
94
|
+
family: 5,
|
|
95
|
+
pro: 5,
|
|
96
|
+
max: 20,
|
|
97
|
+
};
|
|
98
|
+
exports.MAX_ADMINS_DEFAULT = 5;
|
|
99
|
+
/**
|
|
100
|
+
* Admin seat cap for an effective tier. Emits `op=tier-cap-unmapped` when a
|
|
101
|
+
* tier that IS SET carries no entry — the standing signal for a tier that
|
|
102
|
+
* verifies but has no seat count, which is otherwise indistinguishable from a
|
|
103
|
+
* real limit.
|
|
104
|
+
*
|
|
105
|
+
* An EMPTY tier is not that fault and must stay silent. The installer stamps
|
|
106
|
+
* account.json.tier only when `--tier` is passed, so every default personal
|
|
107
|
+
* install reaches implicitTrust with `tier: ""` — the commonest shape there
|
|
108
|
+
* is. Alerting on it would fire this signal on installs where nothing is
|
|
109
|
+
* wrong, which is the same loss as having no signal at all.
|
|
110
|
+
*/
|
|
111
|
+
function resolveMaxAdmins(tier) {
|
|
112
|
+
const mapped = exports.MAX_ADMINS_BY_TIER[tier];
|
|
113
|
+
if (typeof mapped === "number")
|
|
114
|
+
return mapped;
|
|
115
|
+
if (tier !== "") {
|
|
116
|
+
console.error(`[entitlement] op=tier-cap-unmapped tier=${tier} fallback=${exports.MAX_ADMINS_DEFAULT}`);
|
|
117
|
+
}
|
|
118
|
+
return exports.MAX_ADMINS_DEFAULT;
|
|
119
|
+
}
|
|
82
120
|
/**
|
|
83
121
|
* Resolve effective entitlement for the current install. Sync — Ed25519
|
|
84
122
|
* verify is a CPU-bound primitive on the order of 0.3ms; no async surface.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import { embed } from "../../../../lib/embed-client/dist/index.js";
|
|
|
13
13
|
import { requirePortEnv } from "../../../../lib/require-port-env/dist/index.js";
|
|
14
14
|
import { deviceUrlBlock } from "../../../../lib/device-url/dist/index.js";
|
|
15
15
|
import { substituteBrandPlaceholders } from "../../../../lib/brand-templating/dist/index.js";
|
|
16
|
-
import { resolveEntitlement } from "../../../../lib/entitlement/dist/index.js";
|
|
16
|
+
import { resolveEntitlement, resolveMaxAdmins } from "../../../../lib/entitlement/dist/index.js";
|
|
17
17
|
import { createHash, randomInt, randomUUID } from "node:crypto";
|
|
18
18
|
import { createConnection } from "node:net";
|
|
19
19
|
import { homedir, hostname as osHostname } from "node:os";
|
|
@@ -223,13 +223,6 @@ const USERS_FILE = resolve(CONFIG_DIR, "users.json");
|
|
|
223
223
|
// Append-only credential-store audit log — same path the admin server resolves
|
|
224
224
|
// from app/lib/paths (CONFIG_DIR == MAXY_DIR for both processes).
|
|
225
225
|
const USERS_AUDIT_LOG = resolve(CONFIG_DIR, "logs", "users-audit.log");
|
|
226
|
-
/** Maximum number of admin users allowed per tier. */
|
|
227
|
-
const MAX_ADMINS_BY_TIER = {
|
|
228
|
-
solo: 1,
|
|
229
|
-
family: 5,
|
|
230
|
-
pro: 5,
|
|
231
|
-
};
|
|
232
|
-
const MAX_ADMINS_DEFAULT = 5;
|
|
233
226
|
function hashPin(pin) {
|
|
234
227
|
return createHash("sha256").update(pin).digest("hex");
|
|
235
228
|
}
|
|
@@ -905,7 +898,10 @@ eagerTool(server, "admin-add", "Add a new admin user to this account. Creates a
|
|
|
905
898
|
const currentAdmins = (config.admins ?? []);
|
|
906
899
|
const entitlement = await currentEntitlement();
|
|
907
900
|
const tier = entitlement.tier;
|
|
908
|
-
|
|
901
|
+
// Task 2063 — the seat table and the tier list live together in the
|
|
902
|
+
// entitlement lib. A tier with no entry emits op=tier-cap-unmapped there
|
|
903
|
+
// rather than silently resolving to 5 here, which read as a limit.
|
|
904
|
+
const maxAdmins = resolveMaxAdmins(tier);
|
|
909
905
|
if (currentAdmins.length >= maxAdmins) {
|
|
910
906
|
return { content: [{ type: "text", text: `${TAG} Admin limit reached (${maxAdmins} for ${tier || "this"} tier). Remove an existing admin before adding a new one.` }], isError: true };
|
|
911
907
|
}
|