Adds Firebase CLI install + appdistribution:distribute step to both
F-Droid and Play release workflows. Also adds a new build-play-release.yml
workflow for the Play (Firebase) flavor.
Required secrets (store in Forgejo repo → Settings → Secrets):
FIREBASE_APP_ID — from Firebase project settings
FIREBASE_CI_TOKEN — from Firebase CLI login:ci
ANDROID_KEYSTORE_BASE64 — base64-encoded release keystore
ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_ALIAS
ANDROID_KEY_PASSWORD
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds OpsPushWorker, a WorkManager-based native push client that
connects to the centralcloud-ops push endpoints (SSE stream +
messages catch-up) instead of relying solely on FCM/ntfy.
## New module
- OpsPushWorker — polls /api/push/:topic/messages for catch-up,
then opens an SSE stream on /api/push/:topic/stream. Messages
are normalized into the existing ntfy Message shape so all
existing notification plumbing (channels, DND bypass, actions)
works unchanged.
## Changes to existing modules
- Application.kt: calls SmsRelayInit.start() on cold boot
- RemoteConfigFetcher: parses native_push block from
/api/android/config response and schedules/cancels the worker
- SmsRelayInit: restarts the worker on cold boot if enabled
- SmsRelayPreferences: adds native_push_* persisted config keys
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Full namespace + token rename across the entire fork. Greenfield, no
backwards-compatibility aliasing.
Package rename
io.heckel.ntfy.* → com.centralcloud.oncall.*
- 71 .kt + 3 .java + 2 .xml + 1 .gradle file contents rewritten
- source tree moved under app/src/{main,play,fdroid}/java/com/centralcloud/oncall/
- namespace updated in app/build.gradle
- Room schema dir app/schemas/io.heckel.ntfy.* removed (regenerates)
Token sweep
Ntfy/NTFY/ntfy → Oncall/ONCALL/oncall in all code/resource files,
including Japanese translations.
Affected: log-tag constants (NtfyApplication, NtfyMainActivity, ...),
BroadcastService SEND_MESSAGE / CONNECTION_ALERT_* action names,
translated string resources mentioning ntfy.
URL hostnames
ntfy.sh → oncall.hugo.dk
docs.ntfy.sh → docs.oncall.hugo.dk
ntfy.hugo.dk → oncall.hugo.dk
ntfy.example.com, ntfy.ejemplo.es, ntfy.exemple.cat, ntfy.exemplo.com.br,
ntfy.exemplo.pt → oncall.* equivalents (i18n example URLs)
Deep-link scheme
ntfy:// → oncall://
Backup format
FILE_MAGIC "ntfy2586" → "oncall26" (greenfield, no existing backups to read)
Verified: zero matches for /ntfy/i across the entire source tree, gradle
files, resources, and AGENTS.md.
DNS reminder: app_base_url default is now https://oncall.hugo.dk — point
that DNS record at the same server as ntfy.hugo.dk (or update the
default in BuildConfig if you prefer a different hostname).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a SmsRelaySettingsActivity that edits the four config knobs
(enabled, base URL, device ID, sender whitelist) directly against the
`sms_relay_prefs` SharedPreferences file via a PreferenceFragmentCompat.
Also shows last-forward time and 24h failure count read-only.
Standalone (its own Activity), not folded into the existing
SettingsActivity, so this lands without touching the 1000-line monolith.
Launch with adb during bring-up:
adb shell am start -n io.heckel.ntfy/.sms.SmsRelaySettingsActivity
A future pass can link it from main_preferences.xml with
app:fragment="io.heckel.ntfy.sms.SmsRelaySettingsActivity$Fragment"
SmsRelayPreferences.whitelist
Storage format moved from StringSet to a single comma-separated
string so an EditTextPreference can edit it directly. External API
is unchanged — getter still returns Set<String>, whitespace and
empty entries stripped on read.
res/xml/sms_relay_preferences.xml
SwitchPreferenceCompat (enabled) + three EditTextPreferences
(base_url, device_id, whitelist) + two read-only summary entries
(last forward, failure count).
SmsRelaySettingsActivity / Fragment
Activity hosts the fragment; fragment loads the XML, points the
PreferenceManager at the `sms_relay_prefs` file, and refreshes the
read-only status entries on resume.
Manifest <activity> declaration goes with the broader pending manifest
commit alongside the SmsRelayReceiver registration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
So centralcloud-ops can SEE that this phone is alive and the SMS relay
is working. Server side (POST /api/devices/heartbeat + /devices LiveView)
landed in centralcloud_ops commit 6a7f22e.
DeviceHeartbeatWorker
Periodic CoroutineWorker (15min, the WorkManager floor) that POSTs
sms_relay state + device_state to /api/devices/heartbeat.
sms_relay summary: enabled, whitelist_size, last_forward_at,
failure_count_24h.
device_state: battery_level, is_charging, network_type,
low_power_mode, dnd_active, ringer_mode.
DND read via NotificationManager.currentInterruptionFilter — works
on API 23+ without notification policy access permission.
Skips (Result.success) when relay isn't configured yet — no point
spamming the server, but the schedule stays alive so it picks up
automatically as soon as config lands.
SmsRelayInit
One-liner bootstrap. Call from Application.onCreate when your
managed-config / branding WIP merges:
SmsRelayInit.start(this)
Idempotent (WorkManager KEEP policy) so safe across cold starts.
SmsRelayPreferences (extended)
Tracks lastForwardAtMs (epoch-ms, set by SmsForwardWorker on 200)
and failureCount24h (rolling, reset by heartbeat on success).
SmsForwardWorker (extended)
On 200 → record lastForwardAtMs. On 4xx/5xx/exception → bump
failureCount24h. Heartbeat then reports both up to the server,
which renders them on the /devices LiveView.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Whitelisted incoming SMS are forwarded to the centralcloud-ops
phone-relay endpoint as a redundant inbound path alongside Twilio.
io.heckel.ntfy.sms.SmsRelayPreferences
SharedPreferences-backed config — enabled flag, base URL, device id,
sender whitelist (Set<String>). Strict opt-in: forward nothing unless
explicitly enabled AND a non-empty whitelist matches the sender.
io.heckel.ntfy.sms.SmsRelayReceiver
BroadcastReceiver bound to SMS_RECEIVED_ACTION. Extracts the sender +
concatenated body of multi-part SMS, applies the whitelist, and
enqueues a OneTimeWorkRequest for forwarding. Wraps everything in
try/catch so a failure on this hot path can never crash the device's
SMS handling.
io.heckel.ntfy.sms.SmsForwardWorker
CoroutineWorker that POSTs to {baseUrl}/api/sms/inbound/phone-relay
with X-Device-Id auth. Uses HttpUtil.defaultClient and JSONObject
(no new deps). 4xx -> permanent drop, 5xx/network -> WorkManager
exponential backoff retry. WorkManager constraints require a
connected network so retries don't fire while offline.
Manifest registration (uses-permission RECEIVE_SMS / READ_SMS, receiver
declaration) is left as WIP in the working tree pending the broader
managed-config / branding changes also in progress on the manifest.
Commit those together (or in a follow-up) to make the receiver actually
bind.
No settings UI yet — set values via SharedPreferences directly or wait
for the Phase 2.1 settings screen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>