From f6009d3070fc7b009d212bcd3fac71068b7f5906 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 30 Oct 2026 22:23:15 +0100 Subject: [PATCH] Persist optional Android monitoring endpoints into native configuration --- .../java/com/utun/chat/data/ConfigProvider.kt | 27 ++++++++---- .../com/utun/chat/data/MonitoringSettings.kt | 42 +++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 tools/chatgui-android/app/src/main/java/com/utun/chat/data/MonitoringSettings.kt diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index 2b5bda7bb..39c1ae749 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -63,8 +63,6 @@ class ConfigProvider(private val context: Context) { private val nodePrivateKey = stringPreferencesKey("node_private_key") private val nodePublicKey = stringPreferencesKey("node_public_key") private val nodeName = stringPreferencesKey("node_name") - private val controlPort = intPreferencesKey("control_port") - private val controlBindIp = stringPreferencesKey("control_bind_ip") private val dbPath = stringPreferencesKey("db_path") private val debugConsoleLevel = stringPreferencesKey("debug_console_level") private val debugLogFile = stringPreferencesKey("debug_log_file") @@ -191,7 +189,7 @@ class ConfigProvider(private val context: Context) { "node.public_key" to (prefs[nodePublicKey] ?: ""), "node.name" to (prefs[nodeName] ?: getDeviceName()), "gui.utun_enabled" to (prefs[guiUtunEnabled]?.toString() ?: "0"), - "control.port" to (prefs[controlPort]?.toString() ?: "9999"), + "control.port" to MonitoringSettings.read(prefs).port.toString(), "db.path" to (prefs[dbPath] ?: context.filesDir.absolutePath), "debug.console_level" to (prefs[debugConsoleLevel] ?: "info"), "debug.log_file" to (prefs[debugLogFile] ?: "utun.log"), @@ -209,6 +207,21 @@ class ConfigProvider(private val context: Context) { udpLogSettings(context.dataStore.data.first()) }.await() + suspend fun getMonitoringSettings(): MonitoringSettings = configFiles.submit("load monitoring settings") { + MonitoringSettings.read(context.dataStore.data.first()) + }.await() + + /** Атомарно сохраняет enabled/endpoint и обновляет INI также при остановленном ядре. + * Runtime применяет общий restart настроек при выходе с экрана. */ + fun saveMonitoringSettings(settings: MonitoringSettings): Deferred = configFiles.submit("save monitoring settings") { + context.dataStore.edit { settings.writeTo(it) } + val snapshot = configSnapshot() + val servers = loadServers(File(snapshot.directory, "utun.cfg")) + saveConfig(snapshot, servers.active, servers.inactive) + LogManager.addLog("INFO", "CFG", "Monitoring saved enabled=${settings.enabled} " + + if (settings.enabled) "endpoint=${settings.ip}:${settings.port}" else "endpoint removed") + } + fun saveUdpLogSettings(settings: UdpLogSettings): Deferred = configFiles.submit("save UDP log settings") { context.dataStore.edit { prefs -> prefs[logUdpEnabled] = settings.enabled; prefs[logUdpIp] = settings.ip; prefs[logUdpPort] = settings.port @@ -346,7 +359,7 @@ class ConfigProvider(private val context: Context) { "node.public_key" -> keys?.first ?: prefs[nodePublicKey] "node.name" -> prefs[nodeName] ?: getDeviceName() "db.path" -> prefs[dbPath] ?: context.filesDir.absolutePath - "control.bind_ip" -> prefs[controlBindIp] ?: "127.0.0.1" + "control.bind_ip" -> MonitoringSettings.read(prefs).ip "debug.console_level" -> prefs[debugConsoleLevel] ?: "info" "debug.log_file" -> prefs[debugLogFile] ?: "utun.log" "debug.categories" -> prefs[debugCategories] ?: "" @@ -361,7 +374,8 @@ class ConfigProvider(private val context: Context) { private fun intValue(key: String, prefs: Preferences): Int = when (key) { "gui.utun_enabled" -> prefs[guiUtunEnabled] ?: 0 - "control.port" -> prefs[controlPort] ?: 9999 + "control.enabled" -> if (MonitoringSettings.read(prefs).enabled) 1 else 0 + "control.port" -> MonitoringSettings.read(prefs).port "log_udp.enabled" -> if (prefs[logUdpEnabled] ?: false) 1 else 0 "log_udp.port" -> prefs[logUdpPort] ?: UdpLogSettings.DEFAULT_PORT "chat.storage_autoload" -> prefs[chatStorageAutoload] ?: 1 @@ -407,8 +421,6 @@ class ConfigProvider(private val context: Context) { "node.private_key" -> prefs[nodePrivateKey] = value "node.public_key" -> prefs[nodePublicKey] = value "node.name" -> prefs[nodeName] = value - "control.port" -> prefs[controlPort] = value.toIntOrNull() ?: 9999 - "control.bind_ip" -> prefs[controlBindIp] = value "db.path" -> prefs[dbPath] = value "debug.console_level" -> prefs[debugConsoleLevel] = value "debug.log_file" -> prefs[debugLogFile] = value @@ -889,6 +901,7 @@ class ConfigProvider(private val context: Context) { appendLine() for (srv in servers) appendServerSection(this, srv, false) for (srv in commented) appendServerSection(this, srv, true) + MonitoringSettings.read(prefs).appendConfig(this) appendLine("[allowed_keys]") appendLine("allow_all=yes") appendLine() diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/MonitoringSettings.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/MonitoringSettings.kt new file mode 100644 index 000000000..fcd98cb35 --- /dev/null +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/MonitoringSettings.kt @@ -0,0 +1,42 @@ +package com.utun.chat.data + +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.MutablePreferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey + +/** TCP-монитор ядра: только числовой IPv4 (ACL control server не поддерживает IPv6). + * Выключение удаляет endpoint из DataStore; значения по умолчанию — только для следующего включения. */ +data class MonitoringSettings(val enabled: Boolean = false, val ip: String = "127.0.0.1", val port: Int = 9999) { + val valid: Boolean get() = port in 1..65535 && ip.split('.').let { parts -> + parts.size == 4 && parts.all { part -> part.toIntOrNull()?.let { it in 0..255 && part == it.toString() } == true } + } + + fun writeTo(prefs: MutablePreferences) { + require(!enabled || valid) { "Monitoring requires a numeric IPv4 address and port 1..65535" } + prefs[enabledKey] = enabled + if (enabled) { prefs[ipKey] = ip; prefs[portKey] = port } + else { prefs.remove(ipKey); prefs.remove(portKey) } + } + + /** Слушаем только выбранный IP; при включении разрешаем TCP-клиентам подключаться к этому endpoint. */ + fun appendConfig(config: StringBuilder) { + if (!enabled) return + require(valid) { "Invalid monitoring endpoint" } + config.appendLine("[control]") + config.appendLine("ip=$ip") + config.appendLine("port=$port") + config.appendLine("allow=0.0.0.0/0") + config.appendLine() + } + + companion object { + private val enabledKey = booleanPreferencesKey("control_enabled") + private val ipKey = stringPreferencesKey("control_bind_ip") + private val portKey = intPreferencesKey("control_port") + + fun read(prefs: Preferences): MonitoringSettings = if (prefs[enabledKey] == true) + MonitoringSettings(true, prefs[ipKey] ?: "127.0.0.1", prefs[portKey] ?: 9999) else MonitoringSettings() + } +}