feat: establish standalone Device Core repository

This commit is contained in:
DCCONSTRUCTIONS
2026-08-21 11:51:21 +03:00
commit e0bac205d0
244 changed files with 51962 additions and 0 deletions
+255
View File
@@ -0,0 +1,255 @@
import {
createPrivateKey,
createPublicKey,
timingSafeEqual,
X509Certificate,
} from "node:crypto";
import { lstat, readFile } from "node:fs/promises";
import { createControlCoreApp } from "./app.mjs";
import { resolveDeviceDatabaseUrl } from "./database-config.mjs";
import {
createDeviceEdgeChannelSupervisor,
} from "./edge-channel-supervisor.mjs";
import { createDeviceGatewayIngest } from "./gateway-ingest.mjs";
import { PostgresDeviceRepository } from "./postgres-repository.mjs";
import { createTypedCommandRuntime } from "./typed-command-runtime.mjs";
const config = await readConfig();
const repository = new PostgresDeviceRepository({
databaseUrl: config.databaseUrl,
poolSize: config.databasePoolSize,
});
await repository.migrate();
const typedCommandRuntime = config.managementApiEnabled && config.edgeChannelEnabled
? createTypedCommandRuntime({ repository })
: null;
const gatewayIngest = config.discoveryIngestEnabled || config.edgeChannelEnabled
? createDeviceGatewayIngest({
repository,
identifierPepper: config.identifierPepper,
})
: null;
const edgeChannelSupervisor = config.edgeChannelEnabled
? createDeviceEdgeChannelSupervisor({
repository,
gatewayIngest,
coreIdentity: config.edgeChannelCoreIdentity,
trustRoot: config.edgeChannelTrustRoot,
maxEdges: config.edgeChannelMaxEdges,
reconcileIntervalMs: config.edgeChannelReconcileIntervalMs,
typedCommandRuntime,
})
: null;
await edgeChannelSupervisor?.start();
const server = createControlCoreApp({
repository,
gatewayToken: config.gatewayToken,
identifierPepper: config.identifierPepper,
discoveryIngestEnabled: config.discoveryIngestEnabled,
managementApiEnabled: config.managementApiEnabled,
managementToken: config.managementToken,
gatewayIngest,
edgeChannelStatusProvider: edgeChannelSupervisor
? () => edgeChannelSupervisor.status()
: null,
typedCommandRuntime,
});
server.listen(config.port, config.host, () => {
console.log(JSON.stringify({
event: "device_control_core_started",
host: config.host,
port: config.port,
discoveryIngest: config.discoveryIngestEnabled,
managementApi: config.managementApiEnabled,
edgeChannels: config.edgeChannelEnabled,
commandTransport: typedCommandRuntime ? "typed-service-ping-v1" : "disabled",
}));
});
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
async function shutdown() {
server.close(async () => {
await edgeChannelSupervisor?.stop();
await repository.close();
process.exit(0);
});
}
async function readConfig() {
const discoveryIngestEnabled = parseBoolean(
process.env.DEVICE_DISCOVERY_INGEST_ENABLED,
false,
);
const managementApiEnabled = parseBoolean(
process.env.DEVICE_MANAGEMENT_API_ENABLED,
false,
);
const edgeChannelEnabled = parseBoolean(
process.env.DEVICE_EDGE_CHANNEL_ENABLED,
false,
);
const edgeChannelCoreIdentity = edgeChannelEnabled
? await readCoreIdentity(process.env)
: null;
return {
host: String(process.env.HOST || "127.0.0.1").trim(),
port: parsePort(process.env.PORT, 18120),
databaseUrl: await resolveDeviceDatabaseUrl(process.env),
databasePoolSize: parsePositiveInt(
process.env.DEVICE_DATABASE_POOL_SIZE,
10,
),
discoveryIngestEnabled,
managementApiEnabled,
edgeChannelEnabled,
gatewayToken: discoveryIngestEnabled
? await readRequiredSecretFile(
process.env.DEVICE_GATEWAY_CORE_TOKEN_FILE,
"device_gateway_core_token_file_required",
)
: "",
identifierPepper:
discoveryIngestEnabled || managementApiEnabled || edgeChannelEnabled
? await readRequiredSecretFile(
process.env.DEVICE_IDENTIFIER_PEPPER_FILE,
"device_identifier_pepper_file_required",
)
: "",
managementToken: managementApiEnabled
? await readRequiredSecretFile(
process.env.DEVICE_MANAGEMENT_CORE_TOKEN_FILE,
"device_management_core_token_file_required",
)
: "",
edgeChannelCoreIdentity,
edgeChannelTrustRoot: edgeChannelEnabled
? await readRequiredDirectory(
process.env.DEVICE_EDGE_CHANNEL_TRUST_ROOT,
"device_edge_channel_trust_root_required",
)
: "",
edgeChannelMaxEdges: parseBoundedInt(
process.env.DEVICE_EDGE_CHANNEL_MAX_EDGES,
32,
1,
64,
),
edgeChannelReconcileIntervalMs: parseBoundedInt(
process.env.DEVICE_EDGE_CHANNEL_RECONCILE_INTERVAL_MS,
15_000,
1_000,
300_000,
),
};
}
async function readCoreIdentity(environment) {
const keyPath = requiredValue(
environment.DEVICE_EDGE_CHANNEL_CORE_KEY_FILE,
"device_edge_channel_core_key_file_required",
);
const certificatePath = requiredValue(
environment.DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE,
"device_edge_channel_core_certificate_file_required",
);
const [key, cert] = await Promise.all([
readBoundedRegularFile(keyPath, 32 * 1024),
readBoundedRegularFile(certificatePath, 32 * 1024),
]);
const privatePublic = createPublicKey(createPrivateKey(key)).export({
type: "spki",
format: "der",
});
const certificatePublic = new X509Certificate(cert).publicKey.export({
type: "spki",
format: "der",
});
if (
privatePublic.length !== certificatePublic.length
|| !timingSafeEqual(privatePublic, certificatePublic)
) {
throw new Error("device_edge_channel_core_identity_mismatch");
}
return Object.freeze({
identityRef: "workload:device-control-core",
key,
cert,
});
}
async function readBoundedRegularFile(path, maximumBytes) {
const state = await lstat(path);
if (
state.isSymbolicLink()
|| !state.isFile()
|| state.size < 1
|| state.size > maximumBytes
) {
throw new Error("device_edge_channel_core_identity_file_invalid");
}
return readFile(path);
}
async function readRequiredDirectory(path, errorCode) {
const normalized = requiredValue(path, errorCode);
const state = await lstat(normalized);
if (state.isSymbolicLink() || !state.isDirectory()) throw new Error(errorCode);
return normalized;
}
async function readRequiredSecretFile(path, errorCode) {
const normalized = requiredValue(path, errorCode);
const value = (await readFile(normalized, "utf8")).trim();
if (value.length < 32) throw new Error(errorCode);
return value;
}
function requiredValue(value, errorCode) {
if (typeof value !== "string" || value.trim() === "") {
throw new Error(errorCode);
}
return value.trim();
}
function parsePort(value, fallback) {
const parsed = Number(value || fallback);
if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 65535) {
throw new Error("device_control_port_invalid");
}
return parsed;
}
function parsePositiveInt(value, fallback) {
const parsed = Number(value || fallback);
if (!Number.isSafeInteger(parsed) || parsed < 1) {
throw new Error("device_positive_integer_invalid");
}
return parsed;
}
function parseBoundedInt(value, fallback, minimum, maximum) {
const parsed = Number(value ?? fallback);
if (
!Number.isSafeInteger(parsed)
|| parsed < minimum
|| parsed > maximum
) {
throw new Error("device_bounded_integer_invalid");
}
return parsed;
}
function parseBoolean(value, fallback) {
if (value === undefined || value === null || value === "") return fallback;
const normalized = String(value).trim().toLowerCase();
if (["1", "true", "yes", "on"].includes(normalized)) return true;
if (["0", "false", "no", "off"].includes(normalized)) return false;
throw new Error("device_boolean_invalid");
}