mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 09:24:09 +00:00
23bbee4f45
* feat(mesh): replace host-mode with shared sencho_mesh Docker network Phase D of the mesh redesign: drop the operator's `network_mode: host` requirement and the `host-gateway` extra_hosts pattern that did not work on cloud iptables-restrictive distros (OCI, etc.) or Docker Desktop. Each Sencho creates a shared `sencho_mesh` Docker bridge network on boot (default subnet 172.30.0.0/24, override via SENCHO_MESH_SUBNET), pins itself at `<network>+2`, and attaches every meshed user service to the same bridge. Compose overrides now emit IP-based `extra_hosts` plus a top-level `networks` block declaring `sencho_mesh` external. Override delivery: central renders for local stacks; for remote stacks it sends the fleet alias list to the remote's new `PUT /api/mesh/local- override/:stackName` endpoint, which renders against the remote's OWN local senchoIp and writes under its OWN DATA_DIR. Each node may use a different subnet without coordination beyond the env var. Opt-in / opt-out now trigger an automatic redeploy of the affected stack via the existing deploy code path (local: ComposeService; remote: HTTP POST through proxyFetch). The frontend opt-in sheet shows a confirmation modal (ConfirmModal) before the mutation. Failed redeploys emit both a mesh activity event and a durable audit-log row. Hardening: - Reserve port 1852 at opt-in (prevents user containers from racing the Sencho API listener). - ensureMeshNetwork refuses to continue if `sencho_mesh` exists with a mismatched subnet rather than silently routing to the wrong IP. - Idempotent network connect/disconnect helpers in DockerController. - optInStack rolls back the DB row if the just-inserted stack's override push fails (no half-states surviving across calls). - regenerateOverridesForNode runs in parallel and skips the just- pushed stack on opt-in. Operator template: drop `network_mode: host`, restore `ports: ["1852:1852"]`. Mesh now works identically on Linux LAN, OCI, and Docker Desktop without firewall changes. Docs: rewrite docs/features/sencho-mesh.mdx around the shared bridge network, document SENCHO_MESH_SUBNET, surface the host-network-service opt-in restriction, and cross-link with the Pilot Agent docs. BREAKING CHANGE: the operator's `docker-compose.yml` no longer uses `network_mode: host`. After upgrading, redeploy any meshed stacks once so they pick up the new IP-based override and join `sencho_mesh`. * fix(mesh): wrap stackName with path.basename in local-override fs ops CodeQL flagged js/path-injection on the new applyLocalOverride and removeLocalOverride methods because they are publicly reachable and its data-flow model does not recognize isValidStackName / isPathWithinBase as sanitizers. The validation IS sufficient (the allowlist regex blocks path separators, the path-prefix check blocks escape), but path.basename is a model CodeQL recognizes and is purely defensive: for any input that already passes isValidStackName, basename is the identity.
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
import * as YAML from 'yaml';
|
|
|
|
/**
|
|
* Sencho Mesh Compose override generator.
|
|
*
|
|
* Produces a YAML override applied with `docker compose -f compose.yml -f
|
|
* mesh.override.yml up` that:
|
|
* - injects cross-node alias entries into each opted-in service's
|
|
* `/etc/hosts`, resolving every alias to the Sencho container's
|
|
* static IP on the internal `sencho_mesh` Docker network;
|
|
* - attaches each service to `sencho_mesh` so that IP is reachable
|
|
* from inside the user's container.
|
|
*
|
|
* The user's source compose file is never mutated; the override lives in
|
|
* Sencho's data dir and is regenerated whenever the alias set changes.
|
|
*/
|
|
|
|
export const SENCHO_MESH_NETWORK = 'sencho_mesh';
|
|
|
|
export interface MeshAlias {
|
|
/** `<service>.<stack>.<nodeName>.sencho` */
|
|
host: string;
|
|
}
|
|
|
|
export interface MeshOverrideInput {
|
|
/** Service names from the user's compose file (the override echoes them). */
|
|
services: string[];
|
|
/** Aliases this stack should be able to resolve. Order is normalized in output. */
|
|
aliases: MeshAlias[];
|
|
/** Sencho's static IP on the `sencho_mesh` Docker network. */
|
|
senchoIp: string;
|
|
}
|
|
|
|
/**
|
|
* Returns a YAML string suitable for `-f mesh.override.yml`. Stable output
|
|
* ordering so file content does not churn between deploys.
|
|
*/
|
|
export function generateOverrideYaml(input: MeshOverrideInput): string {
|
|
const sortedServices = [...input.services].sort();
|
|
const sortedAliases = [...input.aliases].sort((a, b) => a.host.localeCompare(b.host));
|
|
|
|
const services: Record<string, unknown> = {};
|
|
for (const svc of sortedServices) {
|
|
const entry: Record<string, unknown> = {
|
|
networks: [SENCHO_MESH_NETWORK],
|
|
};
|
|
if (sortedAliases.length > 0) {
|
|
entry.extra_hosts = sortedAliases.map((a) => `${a.host}:${input.senchoIp}`);
|
|
}
|
|
services[svc] = entry;
|
|
}
|
|
|
|
const doc: Record<string, unknown> = {
|
|
services,
|
|
networks: { [SENCHO_MESH_NETWORK]: { external: true } },
|
|
};
|
|
|
|
return YAML.stringify(doc, { lineWidth: 0 });
|
|
}
|
|
|
|
/**
|
|
* Build alias hostnames for every opted-in service across the fleet. Pure
|
|
* helper consumed by MeshService and the override generator.
|
|
*/
|
|
export function buildAliasHosts(opts: {
|
|
nodeName: string;
|
|
stackName: string;
|
|
services: Array<{ service: string; ports: number[] }>;
|
|
}): string[] {
|
|
return opts.services.map((s) => `${s.service}.${opts.stackName}.${opts.nodeName}.sencho`);
|
|
}
|