Files
mesh-catalog/modules/mailu/automx.ts
T
jschoubben 454c5662dd
mesh/merge-gate head 454c5662ddcb merged into trunk 671ab9a3ba7b — pass: builds mailu, sent nowhere; no bus step; 1 wait(s) for a person; every machine com…
mesh/repo-check head 454c5662ddcb merged into trunk 671ab9a3ba7b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivering: 0 machine step(s) passed
Add automx_rows, a read-only tool for the rows automx answers from (issue 1630)
2026-10-21 14:02:37 +02:00

60 lines
2.9 KiB
TypeScript

// What the autoconfiguration module (automx2) holds, as stored: its provider, server, domain and server_domain
// rows, read from its SQLite file inside its container with the sqlite3 program the image carries, opened
// read-only. Nothing is written and nothing is interpreted: the rows are the evidence for what automx answers
// to mail clients (novox/hq issue 1630, ADR 0480), and the reason the answer may be empty.
import type { Exec } from "./health.js";
export const AUTOMX_DATABASE = "/data/db.sqlite";
/** The tables read, each with the one query that reads it. Fixed text: the tool takes no query from its caller. */
export const AUTOMX_TABLES: Record<string, string> = {
provider: "SELECT id, name, short_name FROM provider ORDER BY id",
server: "SELECT id, prio, name, port, type, socket_type, user_name, authentication FROM server ORDER BY id",
domain: "SELECT id, name, provider_id, ldapserver_id FROM domain ORDER BY id",
server_domain: "SELECT server_id, domain_id FROM server_domain ORDER BY domain_id, server_id",
};
export type Row = Record<string, string | number | null>;
export type AutomxRows = {
database: string;
tables: Record<string, Row[]>;
/** servers whose name is empty: the client is told an empty host name for them */
empty_names: { id: number; type: string }[];
/** tables that could not be read, each with what sqlite3 or docker said on one line */
unreadable: Record<string, string>;
};
/** sqlite3 -json prints one JSON array, or nothing at all for a table without rows. */
export function parseRows(output: string): Row[] {
const text = output.trim();
if (text === "") return [];
const rows = JSON.parse(text) as unknown;
if (!Array.isArray(rows)) throw new Error("sqlite3 did not print a list of rows");
return rows as Row[];
}
/** The command that reads one table: read-only, JSON, one fixed statement. */
export function readCommand(sql: string): string[] {
return ["sqlite3", "-readonly", "-json", AUTOMX_DATABASE, sql];
}
/** Read every table; one that fails is said in `unreadable` and does not hide the others. */
export async function readAutomx(exec: Exec, container: string): Promise<AutomxRows> {
const answer: AutomxRows = { database: AUTOMX_DATABASE, tables: {}, empty_names: [], unreadable: {} };
for (const [table, sql] of Object.entries(AUTOMX_TABLES)) {
try {
answer.tables[table] = parseRows(await exec(container, readCommand(sql)));
} catch (e) {
const err = e as { stderr?: string; message?: string };
const text = (err?.stderr ?? "").trim() || (err?.message ?? String(e)).trim();
answer.unreadable[table] = text.split("\n").map((l) => l.trim()).filter(Boolean)[0] ?? "it failed and said nothing";
}
}
for (const s of answer.tables.server ?? []) {
if (String(s.name ?? "").trim() === "") answer.empty_names.push({ id: Number(s.id), type: String(s.type) });
}
return answer;
}