feat: initial Go/JS/Rust Origin check-in client SDKs
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s

Standalone clients that resolve a machine unique ID (OS machine ID first,
falling back to a generated UUID persisted to disk), detect hostname/IP/
container/orchestrator, and register+re-ping origin.warky.dev's public
service-checkin endpoint on a daily interval. Ported from icy2's internal
origincheckin package but extended to the full check-in contract and made
dependency-light for external reuse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 15:25:30 +02:00
co-authored by Claude Sonnet 5
parent 282f76a021
commit 2590e404f5
40 changed files with 4105 additions and 181 deletions
+160
View File
@@ -0,0 +1,160 @@
import { containerId as detectContainerId, hostname as detectHostname, orchestrator as detectOrchestrator } from "./hostInfo.js";
import type { Logger } from "./machineId.js";
import { resolveMachineId } from "./machineId.js";
import type { CheckinResponse, Payload } from "./payload.js";
export const DEFAULT_URL = "https://origin.warky.dev/api/public/service-checkin";
export const DEFAULT_INTERVAL_HOURS = 24;
const REQUEST_TIMEOUT_MS = 10_000;
export interface Config {
/** Check-in endpoint. Defaults to DEFAULT_URL. */
url?: string;
/** Sent as the X-Origin-Service-Key header. Required. */
serviceKey: string;
/** Origin service type, which must already be registered in Origin (e.g. "icy2"). Required. */
type: string;
/** Caller's own version string. Required. */
version: string;
/** Identifies this deployment/instance to Origin, e.g. "icy2-nova". Required. */
name: string;
site?: string;
environment?: string;
databaseType?: string;
databaseName?: string;
/** Defaults to "javascript" when unset. */
appType?: string;
/** Defaults to the OS hostname when unset. */
hostname?: string;
port?: number;
baseUrl?: string;
internalUrl?: string;
/** Defaults to best-effort Docker/cgroup detection when unset. */
containerId?: string;
/** Defaults to best-effort Kubernetes/Docker detection when unset. */
orchestrator?: string;
description?: string;
/** Overrides machine-ID resolution entirely when set. */
installId?: string;
/** Where a generated fallback install ID is persisted when no real OS machine ID is available. */
installIdPath?: string;
/** How often start() repeats. Defaults to DEFAULT_INTERVAL_HOURS. */
intervalHours?: number;
/** Receives non-fatal warnings. Defaults to discarding them. */
logger?: Logger;
}
type ResolvedConfig = Config & { url: string; appType: string; intervalHours: number; logger: Logger };
/** OriginClient checks a service in with Origin. */
export class OriginClient {
private readonly cfg: ResolvedConfig;
private readonly installId: string;
private timer?: ReturnType<typeof setInterval>;
/**
* Validates config, resolves the machine/install ID, and returns a ready
* client. Resolution happens once, at construction, so every check-in
* made by this client reports the same unique_install_id.
*/
constructor(config: Config) {
if (!config.serviceKey || !config.type || !config.version || !config.name) {
throw new Error("originclient: serviceKey, type, version, and name are required");
}
const logger = config.logger ?? { warn: () => {} };
this.cfg = {
...config,
url: config.url || DEFAULT_URL,
appType: config.appType || "javascript",
intervalHours: config.intervalHours && config.intervalHours > 0 ? config.intervalHours : DEFAULT_INTERVAL_HOURS,
logger,
};
this.installId = resolveMachineId(config.installId, config.installIdPath, logger);
}
private buildPayload(): Payload {
const hostname = this.cfg.hostname || detectHostname();
const containerId = this.cfg.containerId || detectContainerId();
const orchestrator = this.cfg.orchestrator || detectOrchestrator();
return {
type: this.cfg.type,
version: this.cfg.version,
name: this.cfg.name,
unique_install_id: this.installId,
site: this.cfg.site,
environment: this.cfg.environment,
database_type: this.cfg.databaseType,
database_name: this.cfg.databaseName,
app_type: this.cfg.appType,
hostname: hostname || undefined,
port: this.cfg.port,
base_url: this.cfg.baseUrl,
internal_url: this.cfg.internalUrl,
container_id: containerId || undefined,
orchestrator: orchestrator || undefined,
description: this.cfg.description,
};
}
/**
* Sends a single check-in request and returns Origin's response. Throws
* on any encode/network/non-2xx failure, after logging it via
* config.logger.
*/
async checkinOnce(): Promise<CheckinResponse> {
const payload = this.buildPayload();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
try {
const res = await fetch(this.cfg.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Origin-Service-Key": this.cfg.serviceKey,
},
body: JSON.stringify(payload),
signal: controller.signal,
});
if (!res.ok) {
throw new Error(`originclient: non-2xx response: ${res.status}`);
}
return (await res.json()) as CheckinResponse;
} catch (err) {
this.cfg.logger.warn("origin checkin: request failed", err);
throw err;
} finally {
clearTimeout(timeout);
}
}
/**
* Performs an immediate check-in, then repeats every intervalHours until
* stop() is called. Check-in failures are logged via config.logger and
* otherwise ignored; call checkinOnce() directly to observe them.
*/
start(): void {
void this.checkinOnce().catch(() => {});
const intervalMs = this.cfg.intervalHours * 60 * 60 * 1000;
this.timer = setInterval(() => {
void this.checkinOnce().catch(() => {});
}, intervalMs);
this.timer.unref?.();
}
/** Stops the interval started by start(). Safe to call even if start() was never called. */
stop(): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = undefined;
}
}
}
+85
View File
@@ -0,0 +1,85 @@
import { existsSync, readFileSync } from "node:fs";
import { hostname as osHostname, networkInterfaces } from "node:os";
/** hostname returns the OS hostname, or "" if it cannot be determined. */
export function hostname(): string {
try {
return osHostname();
} catch {
return "";
}
}
/**
* outboundIp returns the first non-loopback IPv4 address bound to this
* host. It is a LAN-facing address, not the host's public internet
* address, and is not part of Origin's wire contract - it exists so
* callers can populate base_url/internal_url/description themselves.
*/
export function outboundIp(): string {
const interfaces = networkInterfaces();
for (const name of Object.keys(interfaces)) {
for (const info of interfaces[name] ?? []) {
if (info.family === "IPv4" && !info.internal) {
return info.address;
}
}
}
return "";
}
/**
* containerId best-effort detects the Docker/Kubernetes container ID this
* process runs in. It returns "" outside a container or when detection
* fails; an empty container ID is treated as "not containerized" rather
* than an error.
*/
export function containerId(): string {
try {
if (existsSync("/proc/self/cgroup")) {
const id = parseCgroupContainerId(readFileSync("/proc/self/cgroup", "utf8"));
if (id) return id;
}
} catch {
// fall through to dockerenv check
}
if (existsSync("/.dockerenv")) {
const h = hostname();
if (isContainerIdLike(h)) return h;
}
return "";
}
/**
* orchestrator best-effort detects the orchestration environment:
* "kubernetes" under Kubernetes, "docker" in a plain Docker container, ""
* otherwise.
*/
export function orchestrator(): string {
if (process.env.KUBERNETES_SERVICE_HOST) return "kubernetes";
if (existsSync("/.dockerenv")) return "docker";
return "";
}
/**
* parseCgroupContainerId extracts a container ID from /proc/self/cgroup
* content, handling both cgroup v1 (".../docker/<id>") and cgroup v2
* systemd-scope (".../docker-<id>.scope") layouts.
*/
export function parseCgroupContainerId(data: string): string {
for (const rawLine of data.split("\n")) {
const line = rawLine.trim();
const idx = line.lastIndexOf("/");
if (idx === -1) continue;
let segment = line.slice(idx + 1);
segment = segment.replace(/\.scope$/, "");
segment = segment.replace(/^docker-/, "");
if (isContainerIdLike(segment)) return segment;
}
return "";
}
export function isContainerIdLike(s: string): boolean {
if (s.length < 12 || s.length > 64) return false;
return /^[0-9a-f]+$/.test(s);
}
+6
View File
@@ -0,0 +1,6 @@
export { DEFAULT_INTERVAL_HOURS, DEFAULT_URL, OriginClient } from "./client.js";
export type { Config } from "./client.js";
export { containerId, hostname, orchestrator, outboundIp } from "./hostInfo.js";
export { defaultInstallIdPath, detectOsMachineId, resolveMachineId } from "./machineId.js";
export type { Logger } from "./machineId.js";
export type { CheckinResponse, Payload } from "./payload.js";
+124
View File
@@ -0,0 +1,124 @@
import { execFileSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir, platform, tmpdir } from "node:os";
import { dirname, join } from "node:path";
export interface Logger {
warn(msg: string, err?: unknown): void;
}
const noopLogger: Logger = { warn: () => {} };
function readLinuxMachineId(): string | undefined {
for (const path of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
try {
const id = readFileSync(path, "utf8").trim();
if (id) return id;
} catch {
// try the next candidate path
}
}
return undefined;
}
function readDarwinMachineId(): string | undefined {
try {
const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
encoding: "utf8",
timeout: 2000,
});
return out.match(/"IOPlatformUUID"\s*=\s*"([0-9A-Fa-f-]+)"/)?.[1];
} catch {
return undefined;
}
}
function readWindowsMachineId(): string | undefined {
try {
const out = execFileSync(
"reg",
["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
{ encoding: "utf8", timeout: 2000 },
);
return out.match(/MachineGuid\s+REG_SZ\s+([0-9A-Fa-f-]+)/)?.[1];
} catch {
return undefined;
}
}
/** detectOsMachineId reads the real OS machine ID, or undefined if unavailable/unsupported. */
export function detectOsMachineId(): string | undefined {
switch (platform()) {
case "linux":
return readLinuxMachineId();
case "darwin":
return readDarwinMachineId();
case "win32":
return readWindowsMachineId();
default:
return undefined;
}
}
/**
* machineIdDetector is a mutable indirection over detectOsMachineId so
* tests can stub OS machine-id lookup without depending on the actual
* host's state. It's a settable-property holder rather than a plain
* exported `let` because ES module namespace bindings can't be reassigned
* from outside the module.
*/
export const machineIdDetector: { current: () => string | undefined } = {
current: detectOsMachineId,
};
/** defaultInstallIdPath returns the fallback-UUID persistence path used when no explicit path is given. */
export function defaultInstallIdPath(): string {
const base = process.env.XDG_CONFIG_HOME || join(homedir() || tmpdir(), ".config");
return join(base, "origin-client", "install-id");
}
/**
* resolveMachineId returns the unique install ID to report to Origin, in
* priority order:
*
* 1. explicit, if non-empty - a caller-supplied override.
* 2. the real OS machine ID (/etc/machine-id on Linux, IOPlatformUUID on
* macOS, MachineGuid on Windows), if readable.
* 3. the ID already persisted at path, if present.
* 4. a newly generated UUID, which resolveMachineId attempts to persist
* to path for future runs.
*
* OS-ID and persistence failures are logged as warnings, not thrown:
* resolveMachineId always returns a usable ID.
*/
export function resolveMachineId(explicit?: string, path?: string, logger: Logger = noopLogger): string {
const trimmedExplicit = explicit?.trim();
if (trimmedExplicit) return trimmedExplicit;
const osId = machineIdDetector.current();
if (osId) {
const trimmed = osId.trim();
if (trimmed) return trimmed;
} else {
logger.warn("machine id: OS machine id unavailable, falling back to a generated id");
}
const resolvedPath = path && path.length > 0 ? path : defaultInstallIdPath();
try {
const existing = readFileSync(resolvedPath, "utf8").trim();
if (existing) return existing;
} catch {
// no persisted id yet
}
const id = randomUUID();
try {
mkdirSync(dirname(resolvedPath), { recursive: true, mode: 0o750 });
writeFileSync(resolvedPath, id + "\n", { mode: 0o600 });
} catch (err) {
logger.warn("machine id: failed to persist generated id, using an ephemeral id for this run", err);
}
return id;
}
+35
View File
@@ -0,0 +1,35 @@
/**
* Payload is the JSON body posted to Origin's public service-checkin
* endpoint. Field names and requiredness mirror
* POST /api/public/service-checkin as documented in
* origin/doc/service-checkin.md: type, version, name, and
* unique_install_id are required; everything else is optional and, when
* omitted, does not overwrite existing instance data on Origin's side.
*/
export interface Payload {
type: string;
version: string;
name: string;
unique_install_id: string;
site?: string;
environment?: string;
database_type?: string;
database_name?: string;
app_type?: string;
hostname?: string;
port?: number;
base_url?: string;
internal_url?: string;
container_id?: string;
orchestrator?: string;
description?: string;
}
/** CheckinResponse is returned by Origin after a successful check-in. */
export interface CheckinResponse {
success: boolean;
service_instance_id: number;
unique_install_id: string;
status: string;
pinged_at: string;
}