diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx
index c92e2a9..3047ba8 100644
--- a/frontend/src/lib/i18n.tsx
+++ b/frontend/src/lib/i18n.tsx
@@ -98,8 +98,6 @@ const en: Dict = {
'offline.synced': '{n} QSO(s) added to the logbook',
'offline.stillDown': 'Database still unreachable — your QSOs are safe',
'settings.theme': 'Theme', 'settings.themeHint': 'Interface colour theme.',
- 'settings.telemetry': 'Send anonymous usage statistics',
- 'settings.telemetryHint': 'install ID + version + OS, once a day — no callsign or QSO data',
'settings.mainView': 'Main view',
'settings.mainViewHint': 'Choose what the Main tab shows on each side (per profile).',
'settings.leftPane': 'Left pane', 'settings.rightPane': 'Right pane',
@@ -467,8 +465,6 @@ const fr: Dict = {
'offline.synced': '{n} QSO ajoutés au journal',
'offline.stillDown': 'Base toujours injoignable — tes QSO sont en sécurité',
'settings.theme': 'Thème', 'settings.themeHint': "Thème de couleur de l'interface.",
- 'settings.telemetry': "Envoyer des statistiques d'usage anonymes",
- 'settings.telemetryHint': "ID d'installation + version + OS, une fois par jour — aucun indicatif ni donnée QSO",
'settings.mainView': 'Vue principale',
'settings.mainViewHint': "Choisis ce que l'onglet Principal affiche de chaque côté (par profil).",
'settings.leftPane': 'Volet gauche', 'settings.rightPane': 'Volet droit',
diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts
index da2d1b8..f76e19a 100644
--- a/frontend/wailsjs/go/main/App.d.ts
+++ b/frontend/wailsjs/go/main/App.d.ts
@@ -472,8 +472,6 @@ export function GetStationSettings():Promise
;
export function GetStationStatus():Promise>;
-export function GetTelemetryEnabled():Promise;
-
export function GetUIPref(arg1:string):Promise;
export function GetUltrabeamSettings():Promise;
@@ -878,8 +876,6 @@ export function SetDVKLabel(arg1:number,arg2:string):Promise;
export function SetPassphrase(arg1:string):Promise;
-export function SetTelemetryEnabled(arg1:boolean):Promise;
-
export function SetUIPref(arg1:string,arg2:string):Promise;
export function SetUltrabeamDirection(arg1:number):Promise;
diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js
index 77a100c..95c38a9 100644
--- a/frontend/wailsjs/go/main/App.js
+++ b/frontend/wailsjs/go/main/App.js
@@ -898,10 +898,6 @@ export function GetStationStatus() {
return window['go']['main']['App']['GetStationStatus']();
}
-export function GetTelemetryEnabled() {
- return window['go']['main']['App']['GetTelemetryEnabled']();
-}
-
export function GetUIPref(arg1) {
return window['go']['main']['App']['GetUIPref'](arg1);
}
@@ -1710,10 +1706,6 @@ export function SetPassphrase(arg1) {
return window['go']['main']['App']['SetPassphrase'](arg1);
}
-export function SetTelemetryEnabled(arg1) {
- return window['go']['main']['App']['SetTelemetryEnabled'](arg1);
-}
-
export function SetUIPref(arg1, arg2) {
return window['go']['main']['App']['SetUIPref'](arg1, arg2);
}
diff --git a/telemetry.go b/telemetry.go
deleted file mode 100644
index ec0d5e6..0000000
--- a/telemetry.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package main
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/json"
- "fmt"
- "net/http"
- "runtime"
- "strings"
- "time"
-
- "hamlog/internal/applog"
-)
-
-// Anonymous usage telemetry - a once-a-day "app_opened" heartbeat to PostHog so
-// the OpsLog author can see how many people actively use it. Privacy by design:
-// only a random install ID + app version + OS are sent (no callsign, no QSO
-// data, no IP beyond what any HTTP request reveals). Users can disable it in
-// Preferences -> General. See [[user-analytics-posthog]] notes in MEMORY.
-
-const (
- // appVersion is stamped on every heartbeat (and could feed the About box).
- appVersion = "0.20.11"
-
- // posthogHost is the PostHog ingestion endpoint. EU cloud by default; change
- // to https://us.i.posthog.com for a US project.
- posthogHost = "https://eu.i.posthog.com"
-
- // posthogAPIKey is the PostHog PROJECT API key ("phc_..."). It's a public,
- // write-only ingestion key (safe to ship in the binary, like the ClubLog app
- // key). Until it's filled in, telemetry is a no-op.
- posthogAPIKey = "phc_vumvN7XTERNhmRzMZHNgY5DncZfFibTbomiE9epZvUJ4"
-
- keyTelemetryEnabled = "telemetry.enabled" // "0" disables; default on
- keyTelemetryInstallID = "telemetry.install_id" // random, stable per install
- keyTelemetryLastSent = "telemetry.last_sent" // YYYY-MM-DD of last heartbeat
-)
-
-// GetTelemetryEnabled reports whether anonymous usage stats are on (default on).
-func (a *App) GetTelemetryEnabled() bool {
- if a.settings == nil {
- return true
- }
- v, _ := a.settings.GetGlobal(a.ctx, keyTelemetryEnabled)
- return strings.TrimSpace(v) != "0"
-}
-
-// SetTelemetryEnabled turns anonymous usage stats on or off.
-func (a *App) SetTelemetryEnabled(on bool) error {
- if a.settings == nil {
- return fmt.Errorf("db not initialized")
- }
- val := "1"
- if !on {
- val = "0"
- }
- return a.settings.SetGlobal(a.ctx, keyTelemetryEnabled, val)
-}
-
-// telemetryInstallID returns this install's stable anonymous ID, generating and
-// persisting one on first use.
-func (a *App) telemetryInstallID() string {
- id, _ := a.settings.GetGlobal(a.ctx, keyTelemetryInstallID)
- if id = strings.TrimSpace(id); id != "" {
- return id
- }
- id = randomUUID()
- _ = a.settings.SetGlobal(a.ctx, keyTelemetryInstallID, id)
- return id
-}
-
-// sendTelemetryHeartbeat sends at most one "app_opened" event per calendar day
-// (UTC). Best effort: any failure is logged and ignored. Runs in a goroutine at
-// startup.
-func (a *App) sendTelemetryHeartbeat() {
- if a.settings == nil || !a.GetTelemetryEnabled() {
- return
- }
- if strings.Contains(posthogAPIKey, "REPLACE") || strings.TrimSpace(posthogAPIKey) == "" {
- return // not configured yet
- }
- today := time.Now().UTC().Format("2006-01-02")
- if last, _ := a.settings.GetGlobal(a.ctx, keyTelemetryLastSent); strings.TrimSpace(last) == today {
- return // already counted today
- }
-
- payload := map[string]any{
- "api_key": posthogAPIKey,
- "event": "app_opened",
- "distinct_id": a.telemetryInstallID(),
- "properties": map[string]any{
- "version": appVersion,
- "os": runtime.GOOS,
- "arch": runtime.GOARCH,
- "$lib": "opslog",
- },
- "timestamp": time.Now().UTC().Format(time.RFC3339),
- }
- body, err := json.Marshal(payload)
- if err != nil {
- return
- }
- client := &http.Client{Timeout: 15 * time.Second}
- resp, err := client.Post(posthogHost+"/capture/", "application/json", bytes.NewReader(body))
- if err != nil {
- applog.Printf("telemetry: heartbeat failed: %v", err)
- return
- }
- resp.Body.Close()
- if resp.StatusCode >= 200 && resp.StatusCode < 300 {
- _ = a.settings.SetGlobal(a.ctx, keyTelemetryLastSent, today)
- applog.Printf("telemetry: heartbeat ok (%s)", today)
- } else {
- applog.Printf("telemetry: heartbeat HTTP %d", resp.StatusCode)
- }
-}
-
-// randomUUID returns a random RFC-4122 v4 UUID string.
-func randomUUID() string {
- var b [16]byte
- if _, err := rand.Read(b[:]); err != nil {
- // Extremely unlikely; fall back to a time-based id so we still get one.
- return fmt.Sprintf("ts-%d", time.Now().UnixNano())
- }
- b[6] = (b[6] & 0x0f) | 0x40 // version 4
- b[8] = (b[8] & 0x3f) | 0x80 // variant 10
- return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
-}
diff --git a/version.go b/version.go
new file mode 100644
index 0000000..9866c30
--- /dev/null
+++ b/version.go
@@ -0,0 +1,6 @@
+package main
+
+// appVersion is this build's version. It's shown in the About/update flow, the
+// diagnostic-log e-mail and the multi-op live-status heartbeat. The release
+// script (.vscode/release.ps1) rewrites this line alongside frontend/version.ts.
+const appVersion = "0.20.11"
diff --git a/wiki/Security.md b/wiki/Security.md
index 8f64968..a2a6473 100644
--- a/wiki/Security.md
+++ b/wiki/Security.md
@@ -18,7 +18,5 @@ them.
- **Callsign lookups** go to QRZ.com / HamQTH with your credentials.
- **QSL uploads/downloads** go to the services you configure (LoTW via TQSL, QRZ,
eQSL, ClubLog, HRDLog).
-- **Telemetry** is a once-a-day anonymous heartbeat (random install ID + version
- + OS — no callsign, no QSO data). Opt out in Preferences.
- Everything else stays local (or on your own MySQL / web server for the
multi-op live status).
diff --git a/wiki/Settings-and-Data.md b/wiki/Settings-and-Data.md
index e371aed..8ded65c 100644
--- a/wiki/Settings-and-Data.md
+++ b/wiki/Settings-and-Data.md
@@ -47,5 +47,3 @@ format, or an **ADIF record on each logged QSO** — so external tools stay in s
- **Autostart** external programs (WSJT-X, JTAlert, rotator control…) at launch,
skipping any already running.
- **Update check** at startup (toggleable).
-- **Anonymous usage telemetry** — a once-a-day heartbeat (random install ID +
- version + OS; no callsign or QSO data). Opt out in Preferences.