@camstack/system 1.2.115 → 1.2.116
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-hostname.d.ts +15 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +31 -7
- package/dist/builtins/local-network/local-network.addon.js +177 -28
- package/dist/builtins/local-network/local-network.addon.mjs +177 -28
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-Drb54SZD.mjs → dist-BpzbLPel.mjs} +139 -5
- package/dist/{dist-iAwSA2_f.js → dist-Cri60Jd0.js} +139 -5
- package/dist/http/lan-http-bind.d.ts +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -472
- package/dist/index.mjs +11 -468
- package/dist/lan-http-bind-BDJm4d-K.mjs +716 -0
- package/dist/lan-http-bind-CZ4EBeD_.js +848 -0
- package/dist/{retired-settings-keys-OfhqQio4.js → retired-settings-keys-BVAxT5XL.js} +1 -1
- package/dist/{retired-settings-keys-Dz5-7gjt.mjs → retired-settings-keys-SrxMH3OT.mjs} +1 -1
- package/dist/tls/cert-manager.d.ts +11 -0
- package/dist/tls/index.d.ts +3 -1
- package/dist/tls/tls-status.d.ts +16 -0
- package/dist/tls/uploaded-cert.d.ts +14 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-BVAxT5XL.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Ct as DeviceFeature, E as buildStreamParamsConfigSchema, F as deviceStatusCapability, Gt as EventCategory, Lt as isDeviceConfigCap, N as deviceManagerCapability, Ot as WELL_KNOWN_TAB_MAP, P as deviceStateCapability, R as enumerateItemArrayFields, Tt as DeviceType, Wt as sleep, a as CAP_NAMES_WITH_STATUS, at as runtimeStatePolicyFor, et as normalizeUnit, nt as parseStreamParamsFormPatch, p as STREAM_PROFILE_META, t as ALL_CAPABILITY_DEFINITIONS, u as DeviceStatusSchema, vt as errMsg, wt as DeviceRole, yt as BaseAddon, z as enumerateSchemaFields } from "../../dist-
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { Ct as DeviceFeature, E as buildStreamParamsConfigSchema, F as deviceStatusCapability, Gt as EventCategory, Lt as isDeviceConfigCap, N as deviceManagerCapability, Ot as WELL_KNOWN_TAB_MAP, P as deviceStateCapability, R as enumerateItemArrayFields, Tt as DeviceType, Wt as sleep, a as CAP_NAMES_WITH_STATUS, at as runtimeStatePolicyFor, et as normalizeUnit, nt as parseStreamParamsFormPatch, p as STREAM_PROFILE_META, t as ALL_CAPABILITY_DEFINITIONS, u as DeviceStatusSchema, vt as errMsg, wt as DeviceRole, yt as BaseAddon, z as enumerateSchemaFields } from "../../dist-BpzbLPel.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-SrxMH3OT.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
8
8
|
/**
|
|
9
9
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as isSameAddonId, Gt as EventCategory, I as doorbellCapability, Mt as createEvent, Tt as DeviceType, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { G as isSameAddonId, Gt as EventCategory, I as doorbellCapability, Mt as createEvent, Tt as DeviceType, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
9
9
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
4
4
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
4
4
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
5
5
|
var NO_DEVICES = "liveness:no-devices";
|
|
6
6
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
2
2
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
3
3
|
var NO_DEVICES = "liveness:no-devices";
|
|
4
4
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
9
9
|
let crypto$1 = require("crypto");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-
|
|
2
|
+
import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-BpzbLPel.mjs";
|
|
3
3
|
import * as crypto$2 from "node:crypto";
|
|
4
4
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
5
5
|
import nodeCrypto from "crypto";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ParseLocalHostnameResult = {
|
|
2
|
+
readonly ok: true;
|
|
3
|
+
readonly value: string | null;
|
|
4
|
+
} | {
|
|
5
|
+
readonly ok: false;
|
|
6
|
+
readonly error: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Parse the operator-typed local hostname.
|
|
10
|
+
*
|
|
11
|
+
* Empty / whitespace → `null` (advertise the raw LAN IP). A DNS name is
|
|
12
|
+
* lowercased. A URL, port suffix, path, or bare IP is rejected so it cannot
|
|
13
|
+
* leak into a SAN or a candidate URL.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseLocalHostname(raw: string): ParseLocalHostnameResult;
|
|
@@ -37,6 +37,12 @@ interface LocalNetworkConfig {
|
|
|
37
37
|
* different address after a reboot.
|
|
38
38
|
*/
|
|
39
39
|
readonly notificationBaseUrl: string;
|
|
40
|
+
/** LAN HTTP listener. Default true — native apps skip the private cert. */
|
|
41
|
+
readonly httpEnabled: boolean;
|
|
42
|
+
/** Dedicated HTTP port. Must not equal the HTTPS listen port. */
|
|
43
|
+
readonly httpPort: number;
|
|
44
|
+
/** Operator-declared DNS name advertised instead of the raw LAN IP. Empty = none. */
|
|
45
|
+
readonly localHostname: string;
|
|
40
46
|
}
|
|
41
47
|
export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
|
|
42
48
|
private pollTimer;
|
|
@@ -61,6 +67,8 @@ export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
|
|
|
61
67
|
*/
|
|
62
68
|
private resolveNotificationEndpoint;
|
|
63
69
|
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
70
|
+
protected globalSettingsSchema(): import('@camstack/types').ConfigUISchema;
|
|
71
|
+
protected onConfigChanged(): Promise<void>;
|
|
64
72
|
protected onShutdown(): Promise<void>;
|
|
65
73
|
/**
|
|
66
74
|
* Other hub addons (e.g. cloudflare-tunnel) signal the active public
|
|
@@ -86,6 +94,15 @@ export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
|
|
|
86
94
|
* not joined simply contributes nothing.
|
|
87
95
|
*/
|
|
88
96
|
private reconcileMeshEndpoint;
|
|
97
|
+
private resolvedHttpPort;
|
|
98
|
+
private tlsDataDir;
|
|
99
|
+
private snapshotTls;
|
|
100
|
+
/**
|
|
101
|
+
* Bind or unbind the LAN HTTP sibling from the addon's own settings, and
|
|
102
|
+
* keep the leaf SAN in sync with `localHostname`. This is the settings
|
|
103
|
+
* side-effect path — there is no second persist API.
|
|
104
|
+
*/
|
|
105
|
+
private applyLanAccessFromSettings;
|
|
89
106
|
/**
|
|
90
107
|
* The port this hub is actually listening on, plus the provenance of the
|
|
91
108
|
* number. The boot loader merges the `CAMSTACK_PORT` env override into the
|
|
@@ -152,14 +169,21 @@ export declare function pickPreferred(interfaces: readonly LocalInterface[]): Lo
|
|
|
152
169
|
* but above the IPv6 / loopback fallbacks — giving remote, mesh-joined
|
|
153
170
|
* clients a working candidate when no public tunnel is up.
|
|
154
171
|
*
|
|
155
|
-
* `scheme` controls
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
172
|
+
* `scheme` controls loopback URLs and, when LAN HTTP is off, LAN URLs.
|
|
173
|
+
* When {@link LanHttpAdvertisement} is enabled and listening, LAN (and
|
|
174
|
+
* the local-hostname alias) switch to `http://…:httpPort` so a native
|
|
175
|
+
* client can skip the private certificate. Public tunnel + mesh always
|
|
176
|
+
* emit `https://`. Loopback stays on `scheme`/`port` — it is not a LAN
|
|
177
|
+
* candidate.
|
|
161
178
|
*/
|
|
162
|
-
export
|
|
179
|
+
export interface LanHttpAdvertisement {
|
|
180
|
+
readonly enabled: boolean;
|
|
181
|
+
readonly listening: boolean;
|
|
182
|
+
readonly httpPort: number;
|
|
183
|
+
readonly httpsPort: number;
|
|
184
|
+
readonly localHostname: string | null;
|
|
185
|
+
}
|
|
186
|
+
export declare function buildEndpoints(interfaces: readonly LocalInterface[], port: number, includeLoopback: boolean, ipv4Only: boolean, publicHostname: string, scheme?: 'http' | 'https', meshEndpoint?: MeshEndpointCache | null, lanHttp?: LanHttpAdvertisement | null): ConnectionEndpoint[];
|
|
163
187
|
/**
|
|
164
188
|
* First-boot heuristic: which addresses should the allowlist start
|
|
165
189
|
* with? Includes LAN + Wi-Fi IPv4 addresses + plausible IPv6:
|
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-Cri60Jd0.js");
|
|
4
|
+
const require_lan_http_bind = require("../../lan-http-bind-CZ4EBeD_.js");
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
let node_path = require("node:path");
|
|
4
7
|
let node_os = require("node:os");
|
|
5
8
|
node_os = require_chunk.__toESM(node_os);
|
|
9
|
+
let node_net = require("node:net");
|
|
10
|
+
//#region src/builtins/local-network/local-hostname.ts
|
|
11
|
+
var DNS_NAME = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])$/;
|
|
12
|
+
/**
|
|
13
|
+
* Parse the operator-typed local hostname.
|
|
14
|
+
*
|
|
15
|
+
* Empty / whitespace → `null` (advertise the raw LAN IP). A DNS name is
|
|
16
|
+
* lowercased. A URL, port suffix, path, or bare IP is rejected so it cannot
|
|
17
|
+
* leak into a SAN or a candidate URL.
|
|
18
|
+
*/
|
|
19
|
+
function parseLocalHostname(raw) {
|
|
20
|
+
const trimmed = raw.trim();
|
|
21
|
+
if (trimmed === "") return {
|
|
22
|
+
ok: true,
|
|
23
|
+
value: null
|
|
24
|
+
};
|
|
25
|
+
if (/[\s/:?#]/.test(trimmed) || trimmed.includes("\\")) return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: "Enter a DNS name such as camstack.lan, without a scheme or port."
|
|
28
|
+
};
|
|
29
|
+
const lowered = trimmed.toLowerCase();
|
|
30
|
+
if ((0, node_net.isIP)(lowered) !== 0) return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: "Use the address allowlist for IPs — this field is a DNS name."
|
|
33
|
+
};
|
|
34
|
+
if (!DNS_NAME.test(lowered)) return {
|
|
35
|
+
ok: false,
|
|
36
|
+
error: "Enter a DNS name such as camstack.lan, without a scheme or port."
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
value: lowered
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
6
44
|
//#region src/builtins/local-network/local-network.addon.ts
|
|
7
45
|
/**
|
|
8
46
|
* local-network — hub-only system cap implementation.
|
|
@@ -51,7 +89,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
51
89
|
super({
|
|
52
90
|
allowedAddresses: [],
|
|
53
91
|
bootSeeded: false,
|
|
54
|
-
notificationBaseUrl: ""
|
|
92
|
+
notificationBaseUrl: "",
|
|
93
|
+
httpEnabled: true,
|
|
94
|
+
httpPort: require_lan_http_bind.DEFAULT_LAN_HTTP_PORT,
|
|
95
|
+
localHostname: ""
|
|
55
96
|
});
|
|
56
97
|
}
|
|
57
98
|
/**
|
|
@@ -97,8 +138,15 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
97
138
|
source: localPort.source
|
|
98
139
|
} });
|
|
99
140
|
await this.reconcileMeshEndpoint();
|
|
141
|
+
const httpState = require_lan_http_bind.readLanHttpState();
|
|
100
142
|
return {
|
|
101
|
-
endpoints: buildEndpoints(interfaces, localPort.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint
|
|
143
|
+
endpoints: buildEndpoints(interfaces, localPort.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint, {
|
|
144
|
+
enabled: this.config.httpEnabled !== false,
|
|
145
|
+
listening: httpState.listening,
|
|
146
|
+
httpPort: this.resolvedHttpPort(),
|
|
147
|
+
httpsPort: localPort.port,
|
|
148
|
+
localHostname: this.config.localHostname || null
|
|
149
|
+
}),
|
|
102
150
|
localPort
|
|
103
151
|
};
|
|
104
152
|
},
|
|
@@ -133,6 +181,33 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
133
181
|
dropped: addresses.length - cleaned.length
|
|
134
182
|
} });
|
|
135
183
|
return { success: true };
|
|
184
|
+
},
|
|
185
|
+
getTlsStatus: async () => this.snapshotTls(false),
|
|
186
|
+
regenerateCertificate: async (_input) => {
|
|
187
|
+
if (require_lan_http_bind.readTlsMode(this.tlsDataDir()) === "uploaded") throw new Error("Cannot regenerate an uploaded certificate — revert to generated first.");
|
|
188
|
+
const extraSans = [...require_lan_http_bind.readExtraSans(this.tlsDataDir())];
|
|
189
|
+
await require_lan_http_bind.reissueTlsLeaf(this.tlsDataDir(), { extraSans });
|
|
190
|
+
return this.snapshotTls(true);
|
|
191
|
+
},
|
|
192
|
+
uploadCertificate: async ({ certPem, keyPem, caPem }) => {
|
|
193
|
+
const valid = require_lan_http_bind.validateUploadedTls(certPem, keyPem);
|
|
194
|
+
if (!valid.ok) throw new Error(valid.error);
|
|
195
|
+
const dir = (0, node_path.join)(this.tlsDataDir(), "tls");
|
|
196
|
+
await (0, node_fs_promises.mkdir)(dir, { recursive: true });
|
|
197
|
+
await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "camstack.crt"), certPem.endsWith("\n") ? certPem : `${certPem}\n`);
|
|
198
|
+
await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "camstack.key"), keyPem.endsWith("\n") ? keyPem : `${keyPem}\n`, { mode: 384 });
|
|
199
|
+
if (caPem && caPem.trim() !== "") await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "camstack-ca.crt"), caPem.endsWith("\n") ? caPem : `${caPem}\n`);
|
|
200
|
+
require_lan_http_bind.writeTlsMode(this.tlsDataDir(), "uploaded");
|
|
201
|
+
return this.snapshotTls(true);
|
|
202
|
+
},
|
|
203
|
+
downloadCa: async () => {
|
|
204
|
+
return { pem: this.snapshotTls(false).caCertPem ?? "" };
|
|
205
|
+
},
|
|
206
|
+
revertToGeneratedCertificate: async () => {
|
|
207
|
+
require_lan_http_bind.writeTlsMode(this.tlsDataDir(), "generated");
|
|
208
|
+
const extraSans = [...require_lan_http_bind.readExtraSans(this.tlsDataDir())];
|
|
209
|
+
await require_lan_http_bind.ensureTlsCert(this.tlsDataDir(), { extraSans });
|
|
210
|
+
return this.snapshotTls(true);
|
|
136
211
|
}
|
|
137
212
|
};
|
|
138
213
|
this.lastSnapshotKey = snapshotKey(this.enumerate());
|
|
@@ -156,11 +231,58 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
156
231
|
} : null);
|
|
157
232
|
});
|
|
158
233
|
this.ctx.logger.info("local-network initialized", { meta: { interfaceCount: this.enumerate().length } });
|
|
234
|
+
try {
|
|
235
|
+
await this.applyLanAccessFromSettings();
|
|
236
|
+
} catch (err) {
|
|
237
|
+
this.ctx.logger.warn("local-network: LAN HTTP not applied at boot", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
238
|
+
}
|
|
159
239
|
return [{
|
|
160
240
|
capability: require_dist.localNetworkCapability,
|
|
161
241
|
provider
|
|
162
242
|
}];
|
|
163
243
|
}
|
|
244
|
+
globalSettingsSchema() {
|
|
245
|
+
return this.schema({ sections: [{
|
|
246
|
+
id: "local-access",
|
|
247
|
+
title: "LAN HTTP",
|
|
248
|
+
description: "Native apps on this Wi-Fi use http:// and skip the private certificate. The tunnel and this admin UI stay on HTTPS. Turn off once a publicly-trusted certificate covers the LAN name.",
|
|
249
|
+
columns: 1,
|
|
250
|
+
fields: [
|
|
251
|
+
this.field({
|
|
252
|
+
type: "boolean",
|
|
253
|
+
key: "httpEnabled",
|
|
254
|
+
label: "Enable HTTP on the LAN",
|
|
255
|
+
style: "switch",
|
|
256
|
+
default: true
|
|
257
|
+
}),
|
|
258
|
+
this.field({
|
|
259
|
+
type: "number",
|
|
260
|
+
key: "httpPort",
|
|
261
|
+
label: "HTTP port",
|
|
262
|
+
description: "Dedicated listener. Must not equal the HTTPS port (4443 by default).",
|
|
263
|
+
min: 1,
|
|
264
|
+
max: 65535,
|
|
265
|
+
step: 1,
|
|
266
|
+
default: require_lan_http_bind.DEFAULT_LAN_HTTP_PORT,
|
|
267
|
+
showWhen: {
|
|
268
|
+
field: "httpEnabled",
|
|
269
|
+
equals: true
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
this.field({
|
|
273
|
+
type: "text",
|
|
274
|
+
key: "localHostname",
|
|
275
|
+
label: "Local hostname",
|
|
276
|
+
description: "Advertised to the viewer instead of the raw LAN IP, and added to the certificate SAN. Requires this name to resolve on the LAN (router DNS, mDNS, or a hosts file). Leave empty to keep using the IP.",
|
|
277
|
+
maxLength: 253,
|
|
278
|
+
placeholder: "camstack.lan"
|
|
279
|
+
})
|
|
280
|
+
]
|
|
281
|
+
}] });
|
|
282
|
+
}
|
|
283
|
+
async onConfigChanged() {
|
|
284
|
+
await this.applyLanAccessFromSettings();
|
|
285
|
+
}
|
|
164
286
|
async onShutdown() {
|
|
165
287
|
if (this.pollTimer) {
|
|
166
288
|
clearInterval(this.pollTimer);
|
|
@@ -229,6 +351,42 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
229
351
|
}
|
|
230
352
|
}
|
|
231
353
|
}
|
|
354
|
+
resolvedHttpPort() {
|
|
355
|
+
const port = this.config.httpPort;
|
|
356
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : require_lan_http_bind.DEFAULT_LAN_HTTP_PORT;
|
|
357
|
+
}
|
|
358
|
+
tlsDataDir() {
|
|
359
|
+
return this.ctx.nodeDataDir ?? this.ctx.dataDir;
|
|
360
|
+
}
|
|
361
|
+
snapshotTls(restartRequired) {
|
|
362
|
+
const status = require_lan_http_bind.readTlsAccessStatus(this.tlsDataDir(), restartRequired);
|
|
363
|
+
return {
|
|
364
|
+
...status,
|
|
365
|
+
sans: [...status.sans]
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Bind or unbind the LAN HTTP sibling from the addon's own settings, and
|
|
370
|
+
* keep the leaf SAN in sync with `localHostname`. This is the settings
|
|
371
|
+
* side-effect path — there is no second persist API.
|
|
372
|
+
*/
|
|
373
|
+
async applyLanAccessFromSettings() {
|
|
374
|
+
const parsed = parseLocalHostname(this.config.localHostname ?? "");
|
|
375
|
+
if (!parsed.ok) throw new Error(parsed.error);
|
|
376
|
+
const hostname = parsed.value ?? "";
|
|
377
|
+
const httpsPort = (await this.resolveAdvertisedListenPort(null)).port;
|
|
378
|
+
const httpPort = this.resolvedHttpPort();
|
|
379
|
+
if (this.config.httpEnabled !== false && httpPort === httpsPort) throw new Error(`HTTP port ${httpPort} cannot equal the HTTPS listen port`);
|
|
380
|
+
await require_lan_http_bind.applyLanHttp({
|
|
381
|
+
enabled: this.config.httpEnabled !== false,
|
|
382
|
+
port: httpPort,
|
|
383
|
+
host: "0.0.0.0"
|
|
384
|
+
});
|
|
385
|
+
if (hostname !== "") {
|
|
386
|
+
require_lan_http_bind.writeExtraSans(this.tlsDataDir(), [hostname]);
|
|
387
|
+
if (require_lan_http_bind.readTlsMode(this.tlsDataDir()) === "generated") await require_lan_http_bind.ensureTlsCert(this.tlsDataDir(), { extraSans: [hostname] });
|
|
388
|
+
} else require_lan_http_bind.writeExtraSans(this.tlsDataDir(), []);
|
|
389
|
+
}
|
|
232
390
|
/**
|
|
233
391
|
* The port this hub is actually listening on, plus the provenance of the
|
|
234
392
|
* number. The boot loader merges the `CAMSTACK_PORT` env override into the
|
|
@@ -402,31 +560,13 @@ function pickPreferred(interfaces) {
|
|
|
402
560
|
return prefixLen(b.netmask) - prefixLen(a.netmask);
|
|
403
561
|
})[0] ?? null;
|
|
404
562
|
}
|
|
405
|
-
|
|
406
|
-
* Build the ordered candidate URL list. Priority schema:
|
|
407
|
-
* 0 — preferred LAN IPv4
|
|
408
|
-
* 10+ — other LAN IPv4
|
|
409
|
-
* 100 — public tunnel hostname (always HTTPS)
|
|
410
|
-
* 150 — mesh endpoint (Tailscale MagicDNS / 100.x, always HTTPS)
|
|
411
|
-
* 200+ — LAN IPv6
|
|
412
|
-
* 1000 — loopback (last resort)
|
|
413
|
-
*
|
|
414
|
-
* The mesh endpoint ranks just below a true public funnel (a Funnel is
|
|
415
|
-
* reachable by ANY client; the mesh needs the peer on the same tailnet)
|
|
416
|
-
* but above the IPv6 / loopback fallbacks — giving remote, mesh-joined
|
|
417
|
-
* clients a working candidate when no public tunnel is up.
|
|
418
|
-
*
|
|
419
|
-
* `scheme` controls LAN + loopback URLs. Browsers running over HTTPS
|
|
420
|
-
* block `http://` candidates as mixed content, so callers loaded over
|
|
421
|
-
* HTTPS should pass `scheme: 'https'` even when probing a LAN IP — the
|
|
422
|
-
* hub's cert manager already issues a SAN-multi cert covering local
|
|
423
|
-
* interfaces. The public tunnel + mesh endpoint always emit `https://`
|
|
424
|
-
* (the tunnel edge / the hub itself terminates TLS).
|
|
425
|
-
*/
|
|
426
|
-
function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostname, scheme = "http", meshEndpoint = null) {
|
|
563
|
+
function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostname, scheme = "http", meshEndpoint = null, lanHttp = null) {
|
|
427
564
|
const out = [];
|
|
428
565
|
let priority = 0;
|
|
429
566
|
const preferred = pickPreferred(interfaces);
|
|
567
|
+
const httpLan = lanHttp?.enabled === true && lanHttp.listening === true;
|
|
568
|
+
const lanScheme = httpLan ? "http" : scheme;
|
|
569
|
+
const lanPort = httpLan ? lanHttp.httpPort : port;
|
|
430
570
|
const emit = (iface, kind, label, baseUrl, pri) => {
|
|
431
571
|
out.push({
|
|
432
572
|
label,
|
|
@@ -438,13 +578,22 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
438
578
|
priority: pri
|
|
439
579
|
});
|
|
440
580
|
};
|
|
441
|
-
if (
|
|
581
|
+
if (httpLan && lanHttp.localHostname) out.push({
|
|
582
|
+
label: "Local name",
|
|
583
|
+
baseUrl: `${lanScheme}://${lanHttp.localHostname}:${lanPort}`,
|
|
584
|
+
kind: "lan-ipv4",
|
|
585
|
+
interfaceKind: "lan",
|
|
586
|
+
plausible: true,
|
|
587
|
+
plausibleReason: "",
|
|
588
|
+
priority: priority++
|
|
589
|
+
});
|
|
590
|
+
if (preferred) emit(preferred, "lan-ipv4", `${formatKind(preferred.kind)} — ${preferred.name}`, `${lanScheme}://${preferred.address}:${lanPort}`, priority++);
|
|
442
591
|
for (const iface of interfaces) {
|
|
443
592
|
if (iface.internal || iface.family !== "IPv4") continue;
|
|
444
593
|
if (iface.kind === "loopback") continue;
|
|
445
594
|
if (preferred && iface.name === preferred.name && iface.address === preferred.address) continue;
|
|
446
595
|
if (iface.address.startsWith("169.254.")) continue;
|
|
447
|
-
emit(iface, "lan-ipv4", `${formatKind(iface.kind)} — ${iface.name}`, `${
|
|
596
|
+
emit(iface, "lan-ipv4", `${formatKind(iface.kind)} — ${iface.name}`, `${lanScheme}://${iface.address}:${lanPort}`, 10 + priority++);
|
|
448
597
|
}
|
|
449
598
|
if (publicHostname) out.push({
|
|
450
599
|
label: "Public tunnel",
|
|
@@ -470,7 +619,7 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
470
619
|
if (iface.internal || iface.family !== "IPv6") continue;
|
|
471
620
|
if (iface.kind === "loopback") continue;
|
|
472
621
|
if (iface.address.startsWith("fe80:")) continue;
|
|
473
|
-
emit(iface, "lan-ipv6", `${formatKind(iface.kind)} — ${iface.name} (IPv6)`, `${
|
|
622
|
+
emit(iface, "lan-ipv6", `${formatKind(iface.kind)} — ${iface.name} (IPv6)`, `${lanScheme}://[${iface.address}]:${lanPort}`, v6prio++);
|
|
474
623
|
}
|
|
475
624
|
}
|
|
476
625
|
if (includeLoopback) out.push({
|