fix: make telemetry opt-in
This commit is contained in:
+8
-4
@@ -110,12 +110,16 @@ JMAP_SERVER_URL=https://your-jmap-server.com
|
|||||||
# Anonymous Telemetry
|
# Anonymous Telemetry
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Anonymous instance telemetry is enabled by default. Heartbeats contain no PII:
|
# Anonymous instance telemetry is OPT-IN and disabled by default. Enabling it
|
||||||
# version, platform, bucketed account counts, and feature toggles only. See
|
# 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.
|
# https://bulwarkmail.org/docs/legal/privacy/telemetry for the full schema.
|
||||||
#
|
#
|
||||||
# Disable telemetry entirely (overrides the admin UI):
|
# Enable telemetry (also toggleable in the admin UI):
|
||||||
# BULWARK_TELEMETRY=off
|
# 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
|
# Directory for telemetry state: instance id, consent, login HMACs
|
||||||
# (default: ./data/telemetry). For Docker, the default resolves to
|
# (default: ./data/telemetry). For Docker, the default resolves to
|
||||||
|
|||||||
+1
-1
@@ -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
|
- 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
|
- 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
|
- 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
|
- Release (`main`) and development (`dev`) Docker images on GHCR
|
||||||
- Subpath deployment via `NEXT_PUBLIC_BASE_PATH` for mounting behind a reverse proxy
|
- Subpath deployment via `NEXT_PUBLIC_BASE_PATH` for mounting behind a reverse proxy
|
||||||
- Demo mode with fixture data – no mail server required
|
- Demo mode with fixture data – no mail server required
|
||||||
|
|||||||
@@ -118,9 +118,10 @@ export function TelemetryTab() {
|
|||||||
<header className="space-y-2">
|
<header className="space-y-2">
|
||||||
<h1 className="text-2xl font-semibold">Anonymous Usage Stats</h1>
|
<h1 className="text-2xl font-semibold">Anonymous Usage Stats</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Bulwark sends one anonymous heartbeat per day so we can see how many instances are
|
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. <strong>Enabled by default</strong>;
|
running, on what platforms, and which features they use. It's <strong>off by
|
||||||
one click below disables it. No email addresses, no hostnames, no IPs are sent.{' '}
|
default</strong>; one click below enables it and helps us make the product better. No
|
||||||
|
email addresses, no hostnames, no IPs are sent.{' '}
|
||||||
<a
|
<a
|
||||||
href="https://bulwarkmail.org/docs/legal/privacy/telemetry"
|
href="https://bulwarkmail.org/docs/legal/privacy/telemetry"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -138,8 +139,8 @@ export function TelemetryTab() {
|
|||||||
<div className="font-medium">Status</div>
|
<div className="font-medium">Status</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{status.consent === 'pending' && 'Initialising - no heartbeats sent yet.'}
|
{status.consent === 'pending' && 'Initialising - no heartbeats sent yet.'}
|
||||||
{status.consent === 'on' && 'Heartbeats are enabled (default).'}
|
{status.consent === 'on' && 'Heartbeats are enabled. Thanks for helping us improve!'}
|
||||||
{status.consent === 'off' && 'Heartbeats are off.'}
|
{status.consent === 'off' && 'Heartbeats are off (default).'}
|
||||||
{envOverridden && (
|
{envOverridden && (
|
||||||
<> Locked by <code>BULWARK_TELEMETRY</code> env var.</>
|
<> Locked by <code>BULWARK_TELEMETRY</code> env var.</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+11
-11
@@ -17,6 +17,7 @@ function idPath(): string { return path.join(getDir(), '.telemetry-id'); }
|
|||||||
function envOverride(): ConsentState | null {
|
function envOverride(): ConsentState | null {
|
||||||
const v = (process.env.BULWARK_TELEMETRY ?? '').toLowerCase();
|
const v = (process.env.BULWARK_TELEMETRY ?? '').toLowerCase();
|
||||||
if (v === 'off' || v === 'false' || v === '0' || v === 'no') return 'off';
|
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) {
|
if (process.env.BULWARK_TELEMETRY_DISABLED) {
|
||||||
const d = process.env.BULWARK_TELEMETRY_DISABLED.toLowerCase();
|
const d = process.env.BULWARK_TELEMETRY_DISABLED.toLowerCase();
|
||||||
if (d === '1' || d === 'true' || d === 'yes') return 'off';
|
if (d === '1' || d === 'true' || d === 'yes') return 'off';
|
||||||
@@ -41,11 +42,13 @@ export async function getInstanceId(): Promise<string> {
|
|||||||
return fresh;
|
return fresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default consent is 'on' - telemetry is anonymous and enabled by default.
|
// Default consent is 'off' - telemetry is opt-in. Admins can enable it during
|
||||||
// Admins can disable via the UI, the BULWARK_TELEMETRY env var, or by clearing
|
// install (BULWARK_TELEMETRY=on in .env.local), via the BULWARK_TELEMETRY env
|
||||||
// the endpoint. See https://bulwarkmail.org/docs/legal/privacy/telemetry.
|
// 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 = {
|
const DEFAULTS: TelemetryStateFile = {
|
||||||
consent: 'on',
|
consent: 'off',
|
||||||
endpoint: DEFAULT_ENDPOINT,
|
endpoint: DEFAULT_ENDPOINT,
|
||||||
consentedAt: null,
|
consentedAt: null,
|
||||||
lastSentAt: null,
|
lastSentAt: null,
|
||||||
@@ -64,13 +67,10 @@ export async function loadState(): Promise<TelemetryStateFile> {
|
|||||||
error: err instanceof Error ? err.message : String(err),
|
error: err instanceof Error ? err.message : String(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// First-ever load on a fresh install: persist the default-on state with
|
// First-ever load on a fresh install: persist the default-off state so the
|
||||||
// an autoEnabledAt stamp so the admin UI can show "telemetry was
|
// instance id and (lack of) consent are stable across restarts. The admin
|
||||||
// auto-enabled at <time>; disable here" without re-arming on restart.
|
// can opt in later via the UI or the BULWARK_TELEMETRY env var.
|
||||||
const fresh: TelemetryStateFile = {
|
const fresh: TelemetryStateFile = { ...DEFAULTS };
|
||||||
...DEFAULTS,
|
|
||||||
consentedAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
await saveState(fresh);
|
await saveState(fresh);
|
||||||
return fresh;
|
return fresh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ CFG_OAUTH_ISSUER_URL=""
|
|||||||
CFG_SESSION_SECRET=""
|
CFG_SESSION_SECRET=""
|
||||||
CFG_SETTINGS_SYNC_ENABLED="false"
|
CFG_SETTINGS_SYNC_ENABLED="false"
|
||||||
CFG_SETTINGS_DATA_DIR="./data/settings"
|
CFG_SETTINGS_DATA_DIR="./data/settings"
|
||||||
|
CFG_TELEMETRY="false"
|
||||||
CFG_LOG_FORMAT="text"
|
CFG_LOG_FORMAT="text"
|
||||||
CFG_LOG_LEVEL="info"
|
CFG_LOG_LEVEL="info"
|
||||||
CFG_APP_SHORT_NAME=""
|
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}"
|
echo -e " ${DIM}You can add a SESSION_SECRET to .env.local at any time.${RESET}"
|
||||||
fi
|
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
|
draw_footer
|
||||||
read -r
|
read -r
|
||||||
}
|
}
|
||||||
@@ -948,6 +973,11 @@ screen_summary() {
|
|||||||
else
|
else
|
||||||
echo -e " Session Secret ........ ${DIM}Not set${RESET}"
|
echo -e " Session Secret ........ ${DIM}Not set${RESET}"
|
||||||
fi
|
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 ""
|
echo ""
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
@@ -1093,6 +1123,20 @@ ENVEOF
|
|||||||
|
|
||||||
cat >> "$ENV_FILE" << 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 -------------------------------------------------------------------
|
# -- Logging -------------------------------------------------------------------
|
||||||
LOG_FORMAT=${CFG_LOG_FORMAT}
|
LOG_FORMAT=${CFG_LOG_FORMAT}
|
||||||
LOG_LEVEL=${CFG_LOG_LEVEL}
|
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 "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_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 "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_FORMAT"; [[ -n "$val" ]] && CFG_LOG_FORMAT="$val"
|
||||||
get_env_val "LOG_LEVEL"; [[ -n "$val" ]] && CFG_LOG_LEVEL="$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"
|
get_env_val "LOGIN_COMPANY_NAME"; [[ -n "$val" ]] && CFG_LOGIN_COMPANY_NAME="$val"
|
||||||
|
|||||||
Reference in New Issue
Block a user