mailu: full multi-container stack from the live novox deployment
Rebuild the mailu manifest from the running production deployment as the
source of truth, and finish wiring its tool runtime.
- Full 11-container topology: front, smtp, imap, admin, antispam, antivirus,
webmail, webdav, fetchmail, resolver, redis — real ghcr.io/mailu images
pinned by digest at tag 1.9 (clamav/radicale/fetchmail digests newly fetched).
- Admin DB now consumes the mesh postgres-database provider (requires +
contributes + binds + secrets, DB_* templated from ${bound}/${secret}),
replacing the bundled postgres:13 admindb the live stack still runs.
- Full config env from the live containers as a plain env-file; SECRET_KEY,
the admin API token and the initial-admin password become own-secrets;
DB password comes from the provider secret. No secret values hardcoded.
- Enable the Mailu admin REST API (API=true, WEB_API, API_TOKEN own-secret) so
the ported tools can reach it — the live deployment runs this API OFF.
- mesh-mailu tool runtime on the mailu network: admin API over the module
network, token from the mounted own-secret, docker.sock for the doveadm mail
reads, broker + mergeable config with restart-on.
- client.ts fromEnv reads the API token from its mounted own-secret file
(MESH_MAILU_API_KEY_FILE), matching the cloudflare-dns/umami pattern.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
+13
-4
@@ -52,6 +52,13 @@ function meshConfig(file?: string): Record<string, string> {
|
||||
catch { return {}; }
|
||||
}
|
||||
|
||||
/** Read a secret from the file the mesh mounted it at (an own-secret), if the pointing env is set. */
|
||||
function readSecret(path: string | undefined): string | undefined {
|
||||
if (!path) return undefined;
|
||||
try { return readFileSync(path, "utf8").trim() || undefined; }
|
||||
catch { return undefined; }
|
||||
}
|
||||
|
||||
export class MailuClient {
|
||||
readonly baseUrl: string;
|
||||
|
||||
@@ -66,15 +73,17 @@ export class MailuClient {
|
||||
|
||||
/**
|
||||
* Build from the module's resolved environment. MESH_MAILU_URL points at the admin API (e.g. the
|
||||
* admin container's /api/v1), MESH_MAILU_API_KEY authenticates against it. Both are required — a
|
||||
* client with neither would only fail later, one call at a time, so it fails here instead.
|
||||
* admin container's /api/v1); the token authenticates against it. The token is an own-secret,
|
||||
* so it arrives as a file the mesh mounts (MESH_MAILU_API_KEY_FILE) — the deployed path — with a
|
||||
* bare MESH_MAILU_API_KEY honoured only as a fallback for a hand-run instance. URL and token are
|
||||
* both required: a client with neither would only fail later, one call at a time, so it fails here.
|
||||
*/
|
||||
static fromEnv(env: NodeJS.ProcessEnv = process.env): MailuClient {
|
||||
const cfg = meshConfig(env.MESH_MAILU_CONFIG_FILE);
|
||||
const url = cfg.url ?? env.MESH_MAILU_URL;
|
||||
const apiKey = cfg.apiKey ?? env.MESH_MAILU_API_KEY;
|
||||
const apiKey = cfg.apiKey ?? readSecret(env.MESH_MAILU_API_KEY_FILE) ?? env.MESH_MAILU_API_KEY;
|
||||
if (!url || !apiKey) {
|
||||
throw new Error("Mailu is not configured — set MESH_MAILU_URL and MESH_MAILU_API_KEY");
|
||||
throw new Error("Mailu is not configured — set MESH_MAILU_URL and the API token own-secret");
|
||||
}
|
||||
const imapContainer = cfg.container ?? env.MESH_MAILU_IMAP_CONTAINER ?? "mailu-imap";
|
||||
return new MailuClient(url, apiKey, imapContainer);
|
||||
|
||||
Reference in New Issue
Block a user