fix: make telemetry opt-in

This commit is contained in:
Linus Rath
2026-06-14 14:31:26 +02:00
parent 8df483d8c7
commit 3e12fca517
5 changed files with 75 additions and 21 deletions
+8 -4
View File
@@ -110,12 +110,16 @@ JMAP_SERVER_URL=https://your-jmap-server.com
# Anonymous Telemetry
# =============================================================================
# Anonymous instance telemetry is enabled by default. Heartbeats contain no PII:
# version, platform, bucketed account counts, and feature toggles only. See
# Anonymous instance telemetry is OPT-IN and disabled by default. Enabling it
# helps us understand how Bulwark is used so we can make the product better.
# Heartbeats contain no PII: version, platform, bucketed account counts, and
# feature toggles only - never email addresses, hostnames, or IPs. See
# https://bulwarkmail.org/docs/legal/privacy/telemetry for the full schema.
#
# Disable telemetry entirely (overrides the admin UI):
# BULWARK_TELEMETRY=off
# Enable telemetry (also toggleable in the admin UI):
# BULWARK_TELEMETRY=on
#
# Setting this (on or off) locks the choice and disables the admin UI toggle.
# Directory for telemetry state: instance id, consent, login HMACs
# (default: ./data/telemetry). For Docker, the default resolves to
+1 -1
View File
@@ -136,7 +136,7 @@ Automatic browser detection with persistent preference. Configurable locale URL
- Progressive Web App with service worker, install prompt, web push notifications for inbox mail, dynamic manifest, and configurable (per-domain) install screenshots
- Automatic update check with server-side logging of new releases and a non-dismissible update notice
- Structured logging (`text` or `json`) with category-based levels
- Anonymous instance telemetry (opt-out via admin UI or `BULWARK_TELEMETRY=off`) version, platform, bucketed account counts, feature toggles only
- Anonymous instance telemetry (opt-in via admin UI, the installer, or `BULWARK_TELEMETRY=on`; off by default) version, platform, bucketed account counts, feature toggles only
- Release (`main`) and development (`dev`) Docker images on GHCR
- Subpath deployment via `NEXT_PUBLIC_BASE_PATH` for mounting behind a reverse proxy
- Demo mode with fixture data no mail server required
+6 -5
View File
@@ -118,9 +118,10 @@ export function TelemetryTab() {
<header className="space-y-2">
<h1 className="text-2xl font-semibold">Anonymous Usage Stats</h1>
<p className="text-sm text-muted-foreground">
Bulwark sends one anonymous heartbeat per day so we can see how many instances are
running, on what platforms, and which features they use. <strong>Enabled by default</strong>;
one click below disables it. No email addresses, no hostnames, no IPs are sent.{' '}
Bulwark can send one anonymous heartbeat per day so we can see how many instances are
running, on what platforms, and which features they use. It&apos;s <strong>off by
default</strong>; one click below enables it and helps us make the product better. No
email addresses, no hostnames, no IPs are sent.{' '}
<a
href="https://bulwarkmail.org/docs/legal/privacy/telemetry"
target="_blank"
@@ -138,8 +139,8 @@ export function TelemetryTab() {
<div className="font-medium">Status</div>
<div className="text-sm text-muted-foreground">
{status.consent === 'pending' && 'Initialising - no heartbeats sent yet.'}
{status.consent === 'on' && 'Heartbeats are enabled (default).'}
{status.consent === 'off' && 'Heartbeats are off.'}
{status.consent === 'on' && 'Heartbeats are enabled. Thanks for helping us improve!'}
{status.consent === 'off' && 'Heartbeats are off (default).'}
{envOverridden && (
<> Locked by <code>BULWARK_TELEMETRY</code> env var.</>
)}
+11 -11
View File
@@ -17,6 +17,7 @@ function idPath(): string { return path.join(getDir(), '.telemetry-id'); }
function envOverride(): ConsentState | null {
const v = (process.env.BULWARK_TELEMETRY ?? '').toLowerCase();
if (v === 'off' || v === 'false' || v === '0' || v === 'no') return 'off';
if (v === 'on' || v === 'true' || v === '1' || v === 'yes') return 'on';
if (process.env.BULWARK_TELEMETRY_DISABLED) {
const d = process.env.BULWARK_TELEMETRY_DISABLED.toLowerCase();
if (d === '1' || d === 'true' || d === 'yes') return 'off';
@@ -41,11 +42,13 @@ export async function getInstanceId(): Promise<string> {
return fresh;
}
// Default consent is 'on' - telemetry is anonymous and enabled by default.
// Admins can disable via the UI, the BULWARK_TELEMETRY env var, or by clearing
// the endpoint. See https://bulwarkmail.org/docs/legal/privacy/telemetry.
// Default consent is 'off' - telemetry is opt-in. Admins can enable it during
// install (BULWARK_TELEMETRY=on in .env.local), via the BULWARK_TELEMETRY env
// var, or with one click in the admin UI. Heartbeats are anonymous: no PII,
// just version/platform/feature toggles. Enabling helps us improve the product.
// See https://bulwarkmail.org/docs/legal/privacy/telemetry.
const DEFAULTS: TelemetryStateFile = {
consent: 'on',
consent: 'off',
endpoint: DEFAULT_ENDPOINT,
consentedAt: null,
lastSentAt: null,
@@ -64,13 +67,10 @@ export async function loadState(): Promise<TelemetryStateFile> {
error: err instanceof Error ? err.message : String(err),
});
}
// First-ever load on a fresh install: persist the default-on state with
// an autoEnabledAt stamp so the admin UI can show "telemetry was
// auto-enabled at <time>; disable here" without re-arming on restart.
const fresh: TelemetryStateFile = {
...DEFAULTS,
consentedAt: new Date().toISOString(),
};
// First-ever load on a fresh install: persist the default-off state so the
// instance id and (lack of) consent are stable across restarts. The admin
// can opt in later via the UI or the BULWARK_TELEMETRY env var.
const fresh: TelemetryStateFile = { ...DEFAULTS };
await saveState(fresh);
return fresh;
}
+49
View File
@@ -63,6 +63,7 @@ CFG_OAUTH_ISSUER_URL=""
CFG_SESSION_SECRET=""
CFG_SETTINGS_SYNC_ENABLED="false"
CFG_SETTINGS_DATA_DIR="./data/settings"
CFG_TELEMETRY="false"
CFG_LOG_FORMAT="text"
CFG_LOG_LEVEL="info"
CFG_APP_SHORT_NAME=""
@@ -733,6 +734,30 @@ screen_security_config() {
echo -e " ${DIM}You can add a SESSION_SECRET to .env.local at any time.${RESET}"
fi
echo ""
hr
echo ""
echo -e " ${BOLD}Anonymous Usage Stats${RESET}"
echo ""
echo -e " Bulwark can send one anonymous heartbeat per day. It helps us see how"
echo -e " many instances run, on what platforms, and which features are enabled"
echo -e " so we can ${BOLD}make the product better${RESET}."
echo ""
echo -e " ${STAR} ${BOLD}No private data${RESET} - no email addresses, hostnames, or IPs"
echo -e " ${STAR} Just version, platform, and which features are turned on"
echo -e " ${STAR} ${BOLD}Off by default${RESET} - you can change it any time in the admin UI"
echo ""
prompt_yesno "Enable anonymous telemetry to help improve Bulwark?" "$CFG_TELEMETRY" "CFG_TELEMETRY"
if [[ "$CFG_TELEMETRY" == "true" ]]; then
echo ""
echo -e " ${OK} ${GREEN}Thanks! Telemetry will be enabled. We appreciate it.${RESET}"
else
echo ""
note "Telemetry stays off. No heartbeats will be sent."
fi
draw_footer
read -r
}
@@ -948,6 +973,11 @@ screen_summary() {
else
echo -e " Session Secret ........ ${DIM}Not set${RESET}"
fi
if [[ "$CFG_TELEMETRY" == "true" ]]; then
echo -e " Anonymous Telemetry ... ${GREEN}${BOLD}Enabled${RESET} ${DIM}(thank you!)${RESET}"
else
echo -e " Anonymous Telemetry ... ${DIM}Off${RESET}"
fi
echo ""
# Logging
@@ -1093,6 +1123,20 @@ ENVEOF
cat >> "$ENV_FILE" << ENVEOF
# -- Anonymous Telemetry -------------------------------------------------------
# Opt-in, anonymous heartbeats (no PII). Off by default; helps improve Bulwark.
# Toggleable later in the admin UI unless this is set. See
# https://bulwarkmail.org/docs/legal/privacy/telemetry
ENVEOF
if [[ "$CFG_TELEMETRY" == "true" ]]; then
echo "BULWARK_TELEMETRY=on" >> "$ENV_FILE"
else
echo "# BULWARK_TELEMETRY=on" >> "$ENV_FILE"
fi
cat >> "$ENV_FILE" << ENVEOF
# -- Logging -------------------------------------------------------------------
LOG_FORMAT=${CFG_LOG_FORMAT}
LOG_LEVEL=${CFG_LOG_LEVEL}
@@ -1340,6 +1384,11 @@ load_existing_config() {
get_env_val "SESSION_SECRET"; [[ -n "$val" ]] && CFG_SESSION_SECRET="$val"
get_env_val "SETTINGS_SYNC_ENABLED"; [[ -n "$val" ]] && CFG_SETTINGS_SYNC_ENABLED="$val"
get_env_val "SETTINGS_DATA_DIR"; [[ -n "$val" ]] && CFG_SETTINGS_DATA_DIR="$val"
get_env_val "BULWARK_TELEMETRY"
case "$(echo "$val" | tr '[:upper:]' '[:lower:]')" in
on|true|1|yes) CFG_TELEMETRY="true" ;;
off|false|0|no) CFG_TELEMETRY="false" ;;
esac
get_env_val "LOG_FORMAT"; [[ -n "$val" ]] && CFG_LOG_FORMAT="$val"
get_env_val "LOG_LEVEL"; [[ -n "$val" ]] && CFG_LOG_LEVEL="$val"
get_env_val "LOGIN_COMPANY_NAME"; [[ -n "$val" ]] && CFG_LOGIN_COMPANY_NAME="$val"