feat(reset): кнопка сброса настроек в LuCI и дефолтное состояние при чистой установке

- новый модуль service/reset.uc (режим reset-settings, флаг no-start): остановка сервиса, чистка runtime/кэшей, сохранение tailscale, запись дефолтного конфига
This commit is contained in:
Dushnilin
2026-08-13 21:06:39 +03:00
parent 9b4767a5af
commit 305e14d44e
16 changed files with 16626 additions and 570 deletions
+3
View File
@@ -148,6 +148,9 @@ define Package/tachyon/install
$(INSTALL_DIR) $(1)/usr/lib/tachyon
$(CP) ./files/usr/lib/. $(1)/usr/lib/tachyon/
$(INSTALL_DIR) $(1)/usr/lib/tachyon/defaults
$(INSTALL_CONF) ./files/etc/config/tachyon $(1)/usr/lib/tachyon/defaults/config
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
$(INSTALL_BIN) ./files/etc/hotplug.d/iface/99-tachyon-wan-monitor $(1)/etc/hotplug.d/iface/99-tachyon-wan-monitor
+4
View File
@@ -51,6 +51,7 @@ function restore_dnsmasq_failsafe_on_loader_failure(command) {
command = as_string(command);
if (command == "start" || command == "stop" || command == "restart" ||
command == "reload" || command == "uninstall" ||
command == "reset_settings" ||
command == "dnsmasq_restore" || command == "restore_dnsmasq")
restore_dnsmasq_failsafe();
}
@@ -64,6 +65,8 @@ function show_help() {
print(" restart Restart Tachyon service\n");
print(" enable Enable Tachyon autostart\n");
print(" disable Disable Tachyon autostart\n");
print(" reset_settings [no-start]\n");
print(" Reset Tachyon settings to factory defaults\n");
print(" uninstall Remove Tachyon files installed outside opkg/apk\n");
print(" main Run main Tachyon process\n");
print(" list_update Update remote lists\n");
@@ -160,6 +163,7 @@ function command_spec(command) {
enable: [ "service/lifecycle.uc", "enable", 0 ],
disable: [ "service/lifecycle.uc", "disable", 0 ],
uninstall: [ "service/uninstall.uc", "uninstall", 1 ],
reset_settings: [ "service/reset.uc", "reset-settings", 1 ],
dnsmasq_restore: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
restore_dnsmasq: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
main: [ "service/lifecycle.uc", "main", 0 ],
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env ucode
let fs = require("fs");
function as_string(value) {
return value == null ? "" : "" + value;
}
function env(name, fallback) {
let value = getenv(name);
return value == null ? as_string(fallback) : as_string(value);
}
function shell_quote(value) {
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
}
function command_from_args(args) {
let parts = [];
for (let arg in args)
push(parts, shell_quote(arg));
return join(" ", parts);
}
function normalize_status(status) {
status = int(status);
if (status == -1)
return 255;
let signal = status & 127;
if (signal != 0)
return 128 + signal;
return (status >> 8) & 255;
}
function command_success_from_args(args) {
return normalize_status(system(command_from_args(args) + " >/dev/null 2>&1")) == 0;
}
function log_message(message) {
system(command_from_args([ "logger", "-t", CONFIG_NAME, "[info] " + message ]) + " >/dev/null 2>&1");
}
const CONFIG_NAME = env("TACHYON_CONFIG_NAME", "tachyon");
const LIB_DIR = env("TACHYON_LIB", "/usr/lib/tachyon");
const BIN_PATH = env("TACHYON_BIN", "/usr/bin/tachyon");
const INIT_PATH = env("TACHYON_SERVICE_INIT", "/etc/init.d/" + CONFIG_NAME);
const CONFIG_PATH = env("TACHYON_CONFIG_PATH", "/etc/config/" + CONFIG_NAME);
const PERSISTENT_DIR = env("TACHYON_PERSISTENT_DIR", "/etc/" + CONFIG_NAME);
const RUNTIME_STATE_DIR = env("TACHYON_RUNTIME_STATE_DIR", "/var/run/" + CONFIG_NAME);
const SING_BOX_TMP_DIR = env("TACHYON_SING_BOX_TMP_DIR", "/tmp/sing-box");
const SING_BOX_CONFIG_PATH = env("TACHYON_SING_BOX_CONFIG_PATH", "/etc/sing-box/config.json");
const PRESERVED_PERSISTENT_ENTRIES = {
tailscale: 1
};
function default_config_source() {
for (let candidate in [
"/rom/etc/config/" + CONFIG_NAME,
LIB_DIR + "/defaults/" + CONFIG_NAME + ".conf",
LIB_DIR + "/defaults/config"
]) {
candidate = as_string(candidate);
if (fs.stat(candidate) != null)
return candidate;
}
return null;
}
function remove_persistent_state() {
if (fs.stat(PERSISTENT_DIR) == null)
return true;
let entries = fs.lsdir(PERSISTENT_DIR);
if (type(entries) != "array")
return false;
for (let entry in entries) {
entry = as_string(entry);
if (PRESERVED_PERSISTENT_ENTRIES[entry])
continue;
if (!command_success_from_args([ "rm", "-rf", PERSISTENT_DIR + "/" + entry ]))
return false;
}
return true;
}
function reset_settings(no_start) {
command_success_from_args([ BIN_PATH, "stop" ]);
command_success_from_args([ INIT_PATH, "stop" ]);
command_success_from_args([ "rm", "-rf",
RUNTIME_STATE_DIR,
SING_BOX_TMP_DIR,
SING_BOX_CONFIG_PATH
]);
if (!remove_persistent_state()) {
log_message("Reset settings failed: unable to clean " + PERSISTENT_DIR);
print("{\"success\":false,\"message\":\"unable to clean persistent state\"}\n");
return 1;
}
let source = default_config_source();
if (source == null) {
log_message("Reset settings failed: default config source not found");
print("{\"success\":false,\"message\":\"default config source not found\"}\n");
return 1;
}
if (!command_success_from_args([ "cp", "-f", source, CONFIG_PATH ])) {
log_message("Reset settings failed: unable to write default config");
print("{\"success\":false,\"message\":\"unable to write default config\"}\n");
return 1;
}
command_success_from_args([ "chmod", "600", CONFIG_PATH ]);
log_message("Settings have been reset to defaults");
print("{\"success\":true}\n");
if (no_start == "1")
return 0;
let start_status = command_success_from_args([ INIT_PATH, "start" ]) ? 0 : 1;
if (start_status != 0) {
log_message("Reset settings: service failed to start after reset");
print("{\"success\":true,\"started\":false}\n");
}
return start_status;
}
let mode = as_string(ARGV[0]);
let no_start = "0";
for (let i = 1; i < length(ARGV); i++) {
let arg = as_string(ARGV[i]);
if (arg == "no-start" || arg == "--no-start")
no_start = "1";
}
if (mode == "reset-settings")
exit(reset_settings(no_start));
warn("Usage: service/reset.uc reset-settings [no-start]\n");
exit(1);