Adds a once-per-day heartbeat that lets the project see how many instances run Bulwark, on what platforms, with what features enabled, and roughly how many accounts they have. No email addresses, hostnames, IPs, or any end-user data are ever sent. - lib/telemetry: state file, payload builder, jittered scheduler, instance_id persistence at <data-dir>/.telemetry-id (delete to reset) - app/api/admin/telemetry: admin API for status / set-consent / set-endpoint / send-now (all audit-logged) - app/admin/telemetry: settings page with status, JSON payload preview, endpoint editor, send-now button, link to the privacy page - instrumentation.node.ts: starts the scheduler on boot Default state is enabled. The first heartbeat fires 1 hour after boot so an admin who installs and immediately disables produces zero pings. Disable via the settings UI, BULWARK_TELEMETRY=off (or BULWARK_TELEMETRY_DISABLED=1), or by clearing the endpoint. Account counts are bucketed (1, 2-5, 6-10, 11-50, 51-200, 201+) so a small instance can't be re-identified by exact size. The /.telemetry-id file can be deleted to mint a fresh instance_id. Receiving collector is open source at bulwarkmail/dashboard. Self-host your own and point at it via BULWARK_TELEMETRY_URL. Full schema, retention (90d raw → aggregates), and lawful basis are documented at bulwarkmail.org/docs/legal/privacy/telemetry.
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import { readFileSync } from "fs";
|
|
import { configManager } from "./lib/admin/config-manager";
|
|
import { initAdminPassword } from "./lib/admin/password";
|
|
|
|
const VERSION_CHECK_URL =
|
|
"https://raw.githubusercontent.com/bulwarkmail/webmail/main/VERSION";
|
|
|
|
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
|
|
function compareVersions(current: string, remote: string): number {
|
|
const a = current.split(".").map(Number);
|
|
const b = remote.split(".").map(Number);
|
|
for (let i = 0; i < 3; i++) {
|
|
if ((b[i] ?? 0) > (a[i] ?? 0)) return 1;
|
|
if ((b[i] ?? 0) < (a[i] ?? 0)) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const pkg = JSON.parse(
|
|
readFileSync(`${process.cwd()}/package.json`, "utf-8")
|
|
);
|
|
const current: string = pkg.version ?? "0.0.0";
|
|
console.info(`Bulwark Webmail v${current}`);
|
|
|
|
if (process.env.NODE_ENV === "production") {
|
|
fetch(VERSION_CHECK_URL, {
|
|
cache: "no-store",
|
|
signal: AbortSignal.timeout(5000),
|
|
})
|
|
.then((res) => {
|
|
if (!res.ok) return;
|
|
return res.text();
|
|
})
|
|
.then((text) => {
|
|
if (!text) return;
|
|
const remote = text.trim();
|
|
if (!SEMVER_RE.test(remote)) return;
|
|
if (compareVersions(current, remote) > 0) {
|
|
console.info(
|
|
`Update available: v${remote} - https://github.com/bulwarkmail/webmail`
|
|
);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
// Initialize admin config and password bootstrap
|
|
configManager.load()
|
|
.then(() => initAdminPassword())
|
|
.then(() => {
|
|
console.info("Admin dashboard initialized");
|
|
})
|
|
.then(async () => {
|
|
// Anonymous telemetry - opt-in, off until admin consents.
|
|
// See https://bulwarkmail.org/docs/legal/privacy/telemetry
|
|
const { startScheduler, markProcessStart } = await import("./lib/telemetry");
|
|
markProcessStart();
|
|
await startScheduler();
|
|
})
|
|
.catch((err) => {
|
|
console.warn("Admin dashboard init skipped:", err instanceof Error ? err.message : err);
|
|
});
|