diff --git a/echolot-app/app/src/main/AndroidManifest.xml b/echolot-app/app/src/main/AndroidManifest.xml
index 0030503..a63d39f 100644
--- a/echolot-app/app/src/main/AndroidManifest.xml
+++ b/echolot-app/app/src/main/AndroidManifest.xml
@@ -21,6 +21,8 @@
+
+
+
+
+
+
+
+
+
+
= Build.VERSION_CODES.O) ctx.startForegroundService(i)
diff --git a/echolot-app/app/src/main/kotlin/app/echo_lot/app/RelayBootReceiver.kt b/echolot-app/app/src/main/kotlin/app/echo_lot/app/RelayBootReceiver.kt
new file mode 100644
index 0000000..bb6c582
--- /dev/null
+++ b/echolot-app/app/src/main/kotlin/app/echo_lot/app/RelayBootReceiver.kt
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2026 Echolot contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package app.echo_lot.app
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+/**
+ * Brings the relay back after a reboot or an app update, without anyone opening the app.
+ *
+ * The relay's whole purpose is to keep answering "where is this device" while the device sits on a
+ * shelf unattended. Starting it only from [MainActivity] meant it silently did not come back from
+ * either event — and a relay that has quietly stopped is worse than one that was never switched
+ * on, because the endpoint it last published keeps looking authoritative while pointing at a port
+ * nothing is listening on.
+ *
+ * `MY_PACKAGE_REPLACED` matters as much as boot here: installing a new build is the single most
+ * common way this service dies during development, which is exactly when it is being relied on.
+ */
+class RelayBootReceiver : BroadcastReceiver() {
+ override fun onReceive(ctx: Context, intent: Intent) {
+ if (!BuildConfig.DEBUG) return
+ when (intent.action) {
+ Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> {
+ if (Settings(ctx).adbRelayEnabled) {
+ runCatching { AdbRelayService.start(ctx) }
+ }
+ }
+ }
+ }
+}