feat(reset): кнопка сброса настроек в LuCI и дефолтное состояние при чистой установке
- новый модуль service/reset.uc (режим reset-settings, флаг no-start): остановка сервиса, чистка runtime/кэшей, сохранение tailscale, запись дефолтного конфига
This commit is contained in:
@@ -20,6 +20,8 @@ function stripIllegalReturn(code) {
|
|||||||
const files = await glob([
|
const files = await glob([
|
||||||
'src/**/*.ts',
|
'src/**/*.ts',
|
||||||
'src/**/*.tsx',
|
'src/**/*.tsx',
|
||||||
|
'../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js',
|
||||||
|
'../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js',
|
||||||
], {
|
], {
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
ignore: [
|
ignore: [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -60,15 +60,13 @@ function parsePo(content) {
|
|||||||
try {
|
try {
|
||||||
msgid = JSON.parse(line.slice(6));
|
msgid = JSON.parse(line.slice(6));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse msgid on line:', line);
|
msgid = parseUnquotedPoValue(line, 'msgid');
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
} else if (line.startsWith('msgstr ') && msgid !== null) {
|
} else if (line.startsWith('msgstr ') && msgid !== null) {
|
||||||
try {
|
try {
|
||||||
msgstr = JSON.parse(line.slice(7));
|
msgstr = JSON.parse(line.slice(7));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse msgstr on line:', line);
|
msgstr = parseUnquotedPoValue(line, 'msgstr');
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
translations.set(msgid, msgstr);
|
translations.set(msgid, msgstr);
|
||||||
msgid = null;
|
msgid = null;
|
||||||
@@ -78,6 +76,19 @@ function parsePo(content) {
|
|||||||
return translations;
|
return translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseUnquotedPoValue(line, prefix) {
|
||||||
|
const match = line.match(new RegExp(`^${prefix} "(.+)"$`));
|
||||||
|
if (!match) {
|
||||||
|
console.error('Failed to parse line:', line);
|
||||||
|
throw new Error('Failed to parse PO line');
|
||||||
|
}
|
||||||
|
return match[1]
|
||||||
|
.replace(/\\n/g, '\n')
|
||||||
|
.replace(/\\t/g, '\t')
|
||||||
|
.replace(/\\"/g, '"')
|
||||||
|
.replace(/\\\\/g, '\\');
|
||||||
|
}
|
||||||
|
|
||||||
function escapePoString(str) {
|
function escapePoString(str) {
|
||||||
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||||
}
|
}
|
||||||
|
|||||||
+4686
-116
File diff suppressed because it is too large
Load Diff
+2762
-79
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -58,23 +58,23 @@ function formatSectionName(name: string): string {
|
|||||||
'Базовая связность': 'Базовая связность',
|
'Базовая связность': 'Базовая связность',
|
||||||
'Заблокированные в РФ': 'Заблокированные ресурсы',
|
'Заблокированные в РФ': 'Заблокированные ресурсы',
|
||||||
'Свои домены': 'Собственные домены',
|
'Свои домены': 'Собственные домены',
|
||||||
'Custom': 'Пользовательский',
|
Custom: 'Пользовательский',
|
||||||
'ChatGPT / OpenAI': 'ChatGPT / OpenAI',
|
'ChatGPT / OpenAI': 'ChatGPT / OpenAI',
|
||||||
'Gemini / Google AI': 'Gemini / Google AI',
|
'Gemini / Google AI': 'Gemini / Google AI',
|
||||||
'X (Twitter)': 'Twitter (X)',
|
'X (Twitter)': 'Twitter (X)',
|
||||||
'UDP / QUIC': 'UDP / QUIC протоколы',
|
'UDP / QUIC': 'UDP / QUIC протоколы',
|
||||||
'Telegram': 'Telegram',
|
Telegram: 'Telegram',
|
||||||
'Instagram': 'Instagram',
|
Instagram: 'Instagram',
|
||||||
'YouTube': 'YouTube',
|
YouTube: 'YouTube',
|
||||||
'TikTok': 'TikTok',
|
TikTok: 'TikTok',
|
||||||
'Discord': 'Discord',
|
Discord: 'Discord',
|
||||||
'Facebook': 'Facebook',
|
Facebook: 'Facebook',
|
||||||
'WhatsApp': 'WhatsApp',
|
WhatsApp: 'WhatsApp',
|
||||||
'Netflix': 'Netflix',
|
Netflix: 'Netflix',
|
||||||
'Spotify': 'Spotify',
|
Spotify: 'Spotify',
|
||||||
'Twitch': 'Twitch',
|
Twitch: 'Twitch',
|
||||||
'Steam': 'Steam',
|
Steam: 'Steam',
|
||||||
'GitHub': 'GitHub',
|
GitHub: 'GitHub',
|
||||||
};
|
};
|
||||||
|
|
||||||
return sectionMap[clean] || clean;
|
return sectionMap[clean] || clean;
|
||||||
@@ -375,11 +375,7 @@ export function renderServiceCheckModal() {
|
|||||||
'padding: 4px 8px; font-size: 12px; border-radius: 4px; max-width: 240px;',
|
'padding: 4px 8px; font-size: 12px; border-radius: 4px; max-width: 240px;',
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
E(
|
E('option', { value: 'ALL' }, `Все сервисы (${targets.length})`),
|
||||||
'option',
|
|
||||||
{ value: 'ALL' },
|
|
||||||
`Все сервисы (${targets.length})`,
|
|
||||||
),
|
|
||||||
...sectionNames.map((sec) => {
|
...sectionNames.map((sec) => {
|
||||||
const count = targets.filter((t) => t.section === sec).length;
|
const count = targets.filter((t) => t.section === sec).length;
|
||||||
return E(
|
return E(
|
||||||
@@ -524,19 +520,24 @@ export function renderServiceCheckModal() {
|
|||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
targets.forEach((item, index) => {
|
targets.forEach((item, index) => {
|
||||||
const rowClass = index % 2 === 0 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2';
|
const rowClass =
|
||||||
|
index % 2 === 0 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2';
|
||||||
const badge = renderStatusBadge('', false, true);
|
const badge = renderStatusBadge('', false, true);
|
||||||
|
|
||||||
const tr = E('tr', { class: `tr ${rowClass}` }, [
|
const tr = E('tr', { class: `tr ${rowClass}` }, [
|
||||||
E('td', { class: 'td', style: 'word-break: break-all; padding: 8px;' }, [
|
E(
|
||||||
E(
|
'td',
|
||||||
'span',
|
{ class: 'td', style: 'word-break: break-all; padding: 8px;' },
|
||||||
{ style: 'font-weight: 600; font-size: 12px;' },
|
[
|
||||||
formatSectionName(item.section),
|
E(
|
||||||
),
|
'span',
|
||||||
E('br'),
|
{ style: 'font-weight: 600; font-size: 12px;' },
|
||||||
E('small', { style: 'opacity: 0.8;' }, item.domain),
|
formatSectionName(item.section),
|
||||||
]),
|
),
|
||||||
|
E('br'),
|
||||||
|
E('small', { style: 'opacity: 0.8;' }, item.domain),
|
||||||
|
],
|
||||||
|
),
|
||||||
E('td', { class: 'td', style: 'font-size: 12px; padding: 8px;' }, [
|
E('td', { class: 'td', style: 'font-size: 12px; padding: 8px;' }, [
|
||||||
E(
|
E(
|
||||||
'span',
|
'span',
|
||||||
@@ -554,20 +555,33 @@ export function renderServiceCheckModal() {
|
|||||||
]),
|
]),
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: right; font-size: 12px; padding: 8px 4px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: right; font-size: 12px; padding: 8px 4px;',
|
||||||
|
},
|
||||||
'-',
|
'-',
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: right; font-size: 12px; padding: 8px 4px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: right; font-size: 12px; padding: 8px 4px;',
|
||||||
|
},
|
||||||
'-',
|
'-',
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: right; font-size: 12px; padding: 8px 4px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: right; font-size: 12px; padding: 8px 4px;',
|
||||||
|
},
|
||||||
'-',
|
'-',
|
||||||
),
|
),
|
||||||
E('td', { class: 'td', style: 'text-align: center; padding: 8px 6px;' }, badge),
|
E(
|
||||||
|
'td',
|
||||||
|
{ class: 'td', style: 'text-align: center; padding: 8px 6px;' },
|
||||||
|
badge,
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
rowMap.push({ target: item, tr });
|
rowMap.push({ target: item, tr });
|
||||||
@@ -582,7 +596,9 @@ export function renderServiceCheckModal() {
|
|||||||
!searchQuery ||
|
!searchQuery ||
|
||||||
target.domain.toLowerCase().includes(searchQuery) ||
|
target.domain.toLowerCase().includes(searchQuery) ||
|
||||||
target.section.toLowerCase().includes(searchQuery) ||
|
target.section.toLowerCase().includes(searchQuery) ||
|
||||||
formatSectionName(target.section).toLowerCase().includes(searchQuery);
|
formatSectionName(target.section)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchQuery);
|
||||||
tr.style.display = matchesSection && matchesSearch ? '' : 'none';
|
tr.style.display = matchesSection && matchesSearch ? '' : 'none';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -663,7 +679,10 @@ export function renderServiceCheckModal() {
|
|||||||
const customTr = E('tr', { class: 'tr cbi-rowstyle-1' }, [
|
const customTr = E('tr', { class: 'tr cbi-rowstyle-1' }, [
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'word-break: break-all; padding: 8px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'word-break: break-all; padding: 8px;',
|
||||||
|
},
|
||||||
[
|
[
|
||||||
E(
|
E(
|
||||||
'span',
|
'span',
|
||||||
@@ -674,39 +693,65 @@ export function renderServiceCheckModal() {
|
|||||||
E('small', {}, cItem.domain),
|
E('small', {}, cItem.domain),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
E('td', { class: 'td', style: 'font-size: 12px; padding: 8px;' }, [
|
|
||||||
E(
|
|
||||||
'span',
|
|
||||||
{
|
|
||||||
class: 'badge cbi-value-title',
|
|
||||||
style: 'font-size: 11px; padding: 2px 6px;',
|
|
||||||
},
|
|
||||||
formatRouteType(cItem.route_type),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
E('td', { class: 'td', style: 'font-size: 12px; padding: 8px;' }, [
|
|
||||||
cItem.ip || '?',
|
|
||||||
E('br'),
|
|
||||||
E('small', {}, `${cItem.dns_ms}ms`),
|
|
||||||
]),
|
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: right; padding: 8px 4px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'font-size: 12px; padding: 8px;',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
E(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
class: 'badge cbi-value-title',
|
||||||
|
style: 'font-size: 11px; padding: 2px 6px;',
|
||||||
|
},
|
||||||
|
formatRouteType(cItem.route_type),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
E(
|
||||||
|
'td',
|
||||||
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'font-size: 12px; padding: 8px;',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
cItem.ip || '?',
|
||||||
|
E('br'),
|
||||||
|
E('small', {}, `${cItem.dns_ms}ms`),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
E(
|
||||||
|
'td',
|
||||||
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: right; padding: 8px 4px;',
|
||||||
|
},
|
||||||
cItem.tcp_ms > 0 ? `${cItem.tcp_ms}` : '-',
|
cItem.tcp_ms > 0 ? `${cItem.tcp_ms}` : '-',
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: right; padding: 8px 4px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: right; padding: 8px 4px;',
|
||||||
|
},
|
||||||
cItem.tls_ms > 0 ? `${cItem.tls_ms}` : '-',
|
cItem.tls_ms > 0 ? `${cItem.tls_ms}` : '-',
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: right; padding: 8px 4px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: right; padding: 8px 4px;',
|
||||||
|
},
|
||||||
cItem.http_ms > 0 ? `${cItem.http_ms}` : '-',
|
cItem.http_ms > 0 ? `${cItem.http_ms}` : '-',
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
'td',
|
'td',
|
||||||
{ class: 'td', style: 'text-align: center; padding: 8px 6px;' },
|
{
|
||||||
|
class: 'td',
|
||||||
|
style: 'text-align: center; padding: 8px 6px;',
|
||||||
|
},
|
||||||
cBadge,
|
cBadge,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|||||||
+13
@@ -331,6 +331,7 @@ LOG_FILE="/tmp/tachyon-install.log"
|
|||||||
START_TIME=""
|
START_TIME=""
|
||||||
TACHYON_WAS_ENABLED=0
|
TACHYON_WAS_ENABLED=0
|
||||||
TACHYON_WAS_RUNNING=0
|
TACHYON_WAS_RUNNING=0
|
||||||
|
TACHYON_WAS_INSTALLED=0
|
||||||
TACHYON_LEGACY_DETECTED=0
|
TACHYON_LEGACY_DETECTED=0
|
||||||
TACHYON_FORKOP_MIGRATION=0
|
TACHYON_FORKOP_MIGRATION=0
|
||||||
TACHYON_I18N_REQUESTED=0
|
TACHYON_I18N_REQUESTED=0
|
||||||
@@ -1337,6 +1338,7 @@ function installer_cleanup_legacy() {
|
|||||||
|
|
||||||
print("TACHYON_WAS_ENABLED=", was_enabled ? "1" : "0", "\n");
|
print("TACHYON_WAS_ENABLED=", was_enabled ? "1" : "0", "\n");
|
||||||
print("TACHYON_WAS_RUNNING=", was_running ? "1" : "0", "\n");
|
print("TACHYON_WAS_RUNNING=", was_running ? "1" : "0", "\n");
|
||||||
|
print("TACHYON_WAS_INSTALLED=", tachyon_installed ? "1" : "0", "\n");
|
||||||
print("TACHYON_LEGACY_DETECTED=", legacy_installed ? "1" : "0", "\n");
|
print("TACHYON_LEGACY_DETECTED=", legacy_installed ? "1" : "0", "\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1447,6 +1449,16 @@ function installer_post_install() {
|
|||||||
warn("Failed to start Tachyon after upgrade.\n");
|
warn("Failed to start Tachyon after upgrade.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fresh installs must end up in a pristine default state: wipe leftover
|
||||||
|
// caches and runtime state from previous installations and rewrite the
|
||||||
|
// config from the bundled default. Upgrades and legacy migrations keep
|
||||||
|
// the user configuration untouched.
|
||||||
|
if (env("TACHYON_WAS_INSTALLED", "0") != "1" &&
|
||||||
|
env("TACHYON_LEGACY_DETECTED", "0") != "1") {
|
||||||
|
if (!run_args([ "timeout", "60", INSTALLER_TACHYON_BIN, "reset_settings", "no-start" ]))
|
||||||
|
warn("Failed to reset Tachyon settings to defaults after a fresh install.\n");
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2576,6 +2588,7 @@ post_install() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
TACHYON_WAS_ENABLED="$TACHYON_WAS_ENABLED" TACHYON_WAS_RUNNING="$TACHYON_WAS_RUNNING" \
|
TACHYON_WAS_ENABLED="$TACHYON_WAS_ENABLED" TACHYON_WAS_RUNNING="$TACHYON_WAS_RUNNING" \
|
||||||
|
TACHYON_WAS_INSTALLED="$TACHYON_WAS_INSTALLED" TACHYON_LEGACY_DETECTED="$TACHYON_LEGACY_DETECTED" \
|
||||||
install_json_ucode installer-post-install ||
|
install_json_ucode installer-post-install ||
|
||||||
fail "Failed to complete Tachyon post-install actions"
|
fail "Failed to complete Tachyon post-install actions"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7937,13 +7937,10 @@ var styles = `
|
|||||||
|
|
||||||
.tachyon_dashboard-page__subscription-meta__title {
|
.tachyon_dashboard-page__subscription-meta__title {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
width: max-content;
|
|
||||||
max-width: min(28ch, 30%);
|
|
||||||
min-width: min-content;
|
|
||||||
color: var(--text-color-high);
|
color: var(--text-color-high);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
overflow-wrap: anywhere;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tachyon_dashboard-page__subscription-meta__facts {
|
.tachyon_dashboard-page__subscription-meta__facts {
|
||||||
@@ -10066,23 +10063,23 @@ function formatSectionName(name) {
|
|||||||
"\u0411\u0430\u0437\u043E\u0432\u0430\u044F \u0441\u0432\u044F\u0437\u043D\u043E\u0441\u0442\u044C": "\u0411\u0430\u0437\u043E\u0432\u0430\u044F \u0441\u0432\u044F\u0437\u043D\u043E\u0441\u0442\u044C",
|
"\u0411\u0430\u0437\u043E\u0432\u0430\u044F \u0441\u0432\u044F\u0437\u043D\u043E\u0441\u0442\u044C": "\u0411\u0430\u0437\u043E\u0432\u0430\u044F \u0441\u0432\u044F\u0437\u043D\u043E\u0441\u0442\u044C",
|
||||||
"\u0417\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0432 \u0420\u0424": "\u0417\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044B",
|
"\u0417\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0432 \u0420\u0424": "\u0417\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044B",
|
||||||
"\u0421\u0432\u043E\u0438 \u0434\u043E\u043C\u0435\u043D\u044B": "\u0421\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435 \u0434\u043E\u043C\u0435\u043D\u044B",
|
"\u0421\u0432\u043E\u0438 \u0434\u043E\u043C\u0435\u043D\u044B": "\u0421\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435 \u0434\u043E\u043C\u0435\u043D\u044B",
|
||||||
"Custom": "\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439",
|
Custom: "\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439",
|
||||||
"ChatGPT / OpenAI": "ChatGPT / OpenAI",
|
"ChatGPT / OpenAI": "ChatGPT / OpenAI",
|
||||||
"Gemini / Google AI": "Gemini / Google AI",
|
"Gemini / Google AI": "Gemini / Google AI",
|
||||||
"X (Twitter)": "Twitter (X)",
|
"X (Twitter)": "Twitter (X)",
|
||||||
"UDP / QUIC": "UDP / QUIC \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B\u044B",
|
"UDP / QUIC": "UDP / QUIC \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B\u044B",
|
||||||
"Telegram": "Telegram",
|
Telegram: "Telegram",
|
||||||
"Instagram": "Instagram",
|
Instagram: "Instagram",
|
||||||
"YouTube": "YouTube",
|
YouTube: "YouTube",
|
||||||
"TikTok": "TikTok",
|
TikTok: "TikTok",
|
||||||
"Discord": "Discord",
|
Discord: "Discord",
|
||||||
"Facebook": "Facebook",
|
Facebook: "Facebook",
|
||||||
"WhatsApp": "WhatsApp",
|
WhatsApp: "WhatsApp",
|
||||||
"Netflix": "Netflix",
|
Netflix: "Netflix",
|
||||||
"Spotify": "Spotify",
|
Spotify: "Spotify",
|
||||||
"Twitch": "Twitch",
|
Twitch: "Twitch",
|
||||||
"Steam": "Steam",
|
Steam: "Steam",
|
||||||
"GitHub": "GitHub"
|
GitHub: "GitHub"
|
||||||
};
|
};
|
||||||
return sectionMap[clean] || clean;
|
return sectionMap[clean] || clean;
|
||||||
}
|
}
|
||||||
@@ -10328,11 +10325,7 @@ function renderServiceCheckModal() {
|
|||||||
style: "padding: 4px 8px; font-size: 12px; border-radius: 4px; max-width: 240px;"
|
style: "padding: 4px 8px; font-size: 12px; border-radius: 4px; max-width: 240px;"
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
E(
|
E("option", { value: "ALL" }, `\u0412\u0441\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044B (${targets.length})`),
|
||||||
"option",
|
|
||||||
{ value: "ALL" },
|
|
||||||
`\u0412\u0441\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044B (${targets.length})`
|
|
||||||
),
|
|
||||||
...sectionNames.map((sec) => {
|
...sectionNames.map((sec) => {
|
||||||
const count = targets.filter((t) => t.section === sec).length;
|
const count = targets.filter((t) => t.section === sec).length;
|
||||||
return E(
|
return E(
|
||||||
@@ -10460,15 +10453,19 @@ function renderServiceCheckModal() {
|
|||||||
const rowClass = index % 2 === 0 ? "cbi-rowstyle-1" : "cbi-rowstyle-2";
|
const rowClass = index % 2 === 0 ? "cbi-rowstyle-1" : "cbi-rowstyle-2";
|
||||||
const badge = renderStatusBadge("", false, true);
|
const badge = renderStatusBadge("", false, true);
|
||||||
const tr = E("tr", { class: `tr ${rowClass}` }, [
|
const tr = E("tr", { class: `tr ${rowClass}` }, [
|
||||||
E("td", { class: "td", style: "word-break: break-all; padding: 8px;" }, [
|
E(
|
||||||
E(
|
"td",
|
||||||
"span",
|
{ class: "td", style: "word-break: break-all; padding: 8px;" },
|
||||||
{ style: "font-weight: 600; font-size: 12px;" },
|
[
|
||||||
formatSectionName(item.section)
|
E(
|
||||||
),
|
"span",
|
||||||
E("br"),
|
{ style: "font-weight: 600; font-size: 12px;" },
|
||||||
E("small", { style: "opacity: 0.8;" }, item.domain)
|
formatSectionName(item.section)
|
||||||
]),
|
),
|
||||||
|
E("br"),
|
||||||
|
E("small", { style: "opacity: 0.8;" }, item.domain)
|
||||||
|
]
|
||||||
|
),
|
||||||
E("td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [
|
E("td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [
|
||||||
E(
|
E(
|
||||||
"span",
|
"span",
|
||||||
@@ -10486,20 +10483,33 @@ function renderServiceCheckModal() {
|
|||||||
]),
|
]),
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: right; font-size: 12px; padding: 8px 4px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: right; font-size: 12px; padding: 8px 4px;"
|
||||||
|
},
|
||||||
"-"
|
"-"
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: right; font-size: 12px; padding: 8px 4px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: right; font-size: 12px; padding: 8px 4px;"
|
||||||
|
},
|
||||||
"-"
|
"-"
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: right; font-size: 12px; padding: 8px 4px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: right; font-size: 12px; padding: 8px 4px;"
|
||||||
|
},
|
||||||
"-"
|
"-"
|
||||||
),
|
),
|
||||||
E("td", { class: "td", style: "text-align: center; padding: 8px 6px;" }, badge)
|
E(
|
||||||
|
"td",
|
||||||
|
{ class: "td", style: "text-align: center; padding: 8px 6px;" },
|
||||||
|
badge
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
rowMap.push({ target: item, tr });
|
rowMap.push({ target: item, tr });
|
||||||
tableBody.appendChild(tr);
|
tableBody.appendChild(tr);
|
||||||
@@ -10567,7 +10577,10 @@ function renderServiceCheckModal() {
|
|||||||
const customTr = E("tr", { class: "tr cbi-rowstyle-1" }, [
|
const customTr = E("tr", { class: "tr cbi-rowstyle-1" }, [
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "word-break: break-all; padding: 8px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "word-break: break-all; padding: 8px;"
|
||||||
|
},
|
||||||
[
|
[
|
||||||
E(
|
E(
|
||||||
"span",
|
"span",
|
||||||
@@ -10578,39 +10591,65 @@ function renderServiceCheckModal() {
|
|||||||
E("small", {}, cItem.domain)
|
E("small", {}, cItem.domain)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
E("td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [
|
|
||||||
E(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "badge cbi-value-title",
|
|
||||||
style: "font-size: 11px; padding: 2px 6px;"
|
|
||||||
},
|
|
||||||
formatRouteType(cItem.route_type)
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
E("td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [
|
|
||||||
cItem.ip || "?",
|
|
||||||
E("br"),
|
|
||||||
E("small", {}, `${cItem.dns_ms}ms`)
|
|
||||||
]),
|
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: right; padding: 8px 4px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "font-size: 12px; padding: 8px;"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
E(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "badge cbi-value-title",
|
||||||
|
style: "font-size: 11px; padding: 2px 6px;"
|
||||||
|
},
|
||||||
|
formatRouteType(cItem.route_type)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
E(
|
||||||
|
"td",
|
||||||
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "font-size: 12px; padding: 8px;"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
cItem.ip || "?",
|
||||||
|
E("br"),
|
||||||
|
E("small", {}, `${cItem.dns_ms}ms`)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
E(
|
||||||
|
"td",
|
||||||
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: right; padding: 8px 4px;"
|
||||||
|
},
|
||||||
cItem.tcp_ms > 0 ? `${cItem.tcp_ms}` : "-"
|
cItem.tcp_ms > 0 ? `${cItem.tcp_ms}` : "-"
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: right; padding: 8px 4px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: right; padding: 8px 4px;"
|
||||||
|
},
|
||||||
cItem.tls_ms > 0 ? `${cItem.tls_ms}` : "-"
|
cItem.tls_ms > 0 ? `${cItem.tls_ms}` : "-"
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: right; padding: 8px 4px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: right; padding: 8px 4px;"
|
||||||
|
},
|
||||||
cItem.http_ms > 0 ? `${cItem.http_ms}` : "-"
|
cItem.http_ms > 0 ? `${cItem.http_ms}` : "-"
|
||||||
),
|
),
|
||||||
E(
|
E(
|
||||||
"td",
|
"td",
|
||||||
{ class: "td", style: "text-align: center; padding: 8px 6px;" },
|
{
|
||||||
|
class: "td",
|
||||||
|
style: "text-align: center; padding: 8px 6px;"
|
||||||
|
},
|
||||||
cBadge
|
cBadge
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -376,6 +376,82 @@ function createWatchdogStatusWidget() {
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createResetSettingsWidget() {
|
||||||
|
const wrapper = E("div", {
|
||||||
|
id: "tachyon-reset-settings-widget",
|
||||||
|
style: "display:flex;align-items:center;gap:12px;padding:4px 0;flex-wrap:wrap;",
|
||||||
|
});
|
||||||
|
|
||||||
|
const btnReset = E("button", {
|
||||||
|
class: "btn cbi-button cbi-button-negative",
|
||||||
|
type: "button",
|
||||||
|
});
|
||||||
|
btnReset.textContent = _("Reset Settings");
|
||||||
|
|
||||||
|
const msgEl = E("span", {
|
||||||
|
style: "font-size:12px;color:var(--text-color-medium,#888);",
|
||||||
|
});
|
||||||
|
|
||||||
|
function performReset() {
|
||||||
|
if (btnReset.disabled)
|
||||||
|
return;
|
||||||
|
btnReset.disabled = true;
|
||||||
|
msgEl.textContent = _("Resetting\u2026");
|
||||||
|
fs.exec("/usr/bin/tachyon", ["reset_settings"])
|
||||||
|
.then(function (res) {
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
success = JSON.parse((res && res.stdout) || "").success !== false;
|
||||||
|
} catch (e) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
if (!success)
|
||||||
|
throw new Error("reset failed");
|
||||||
|
msgEl.textContent = "";
|
||||||
|
ui.addNotification(null, E("p", {}, _("Settings have been reset to defaults. The page will reload.")), "info");
|
||||||
|
setTimeout(function () {
|
||||||
|
location.reload();
|
||||||
|
}, 1500);
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
msgEl.textContent = _("Failed to reset settings");
|
||||||
|
btnReset.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
btnReset.addEventListener("click", function () {
|
||||||
|
ui.showModal(
|
||||||
|
_("Reset Settings"),
|
||||||
|
[
|
||||||
|
E("p", {}, _("This will erase all Tachyon settings and restore the factory defaults. The service will restart. This action cannot be undone.")),
|
||||||
|
E("div", { class: "button-row" }, [
|
||||||
|
E("button", {
|
||||||
|
class: "btn cbi-button cbi-button-neutral",
|
||||||
|
type: "button",
|
||||||
|
click: function () {
|
||||||
|
ui.hideModal();
|
||||||
|
},
|
||||||
|
}, _("Cancel")),
|
||||||
|
E("button", {
|
||||||
|
class: "btn cbi-button cbi-button-negative",
|
||||||
|
type: "button",
|
||||||
|
click: function () {
|
||||||
|
ui.hideModal();
|
||||||
|
performReset();
|
||||||
|
},
|
||||||
|
}, _("Reset")),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
"cbi-modal",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.appendChild(btnReset);
|
||||||
|
wrapper.appendChild(msgEl);
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
function createSmartDetectSectionsWidget(section_id) {
|
function createSmartDetectSectionsWidget(section_id) {
|
||||||
const TESTABLE_ACTIONS = ["connection", "proxy", "outbound", "vpn", "zapret", "zapret2", "byedpi"];
|
const TESTABLE_ACTIONS = ["connection", "proxy", "outbound", "vpn", "zapret", "zapret2", "byedpi"];
|
||||||
const allSections = (uci.sections(UCI_PACKAGE, "section") || [])
|
const allSections = (uci.sections(UCI_PACKAGE, "section") || [])
|
||||||
@@ -1217,6 +1293,20 @@ function createSettingsContent(section, capabilities) {
|
|||||||
o.default = "0";
|
o.default = "0";
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
|
const resetOpt = section.taboption(
|
||||||
|
"advanced",
|
||||||
|
form.DummyValue,
|
||||||
|
"_reset_settings",
|
||||||
|
_("Reset Settings"),
|
||||||
|
_(
|
||||||
|
"Restores the Tachyon factory defaults: all rules, subscriptions, custom DNS servers and other options will be lost.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
resetOpt.rawhtml = true;
|
||||||
|
resetOpt.cfgvalue = function () {
|
||||||
|
return createResetSettingsWidget();
|
||||||
|
};
|
||||||
|
|
||||||
o = section.taboption(
|
o = section.taboption(
|
||||||
"ai",
|
"ai",
|
||||||
form.Flag,
|
form.Flag,
|
||||||
|
|||||||
+2374
-88
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -148,6 +148,9 @@ define Package/tachyon/install
|
|||||||
$(INSTALL_DIR) $(1)/usr/lib/tachyon
|
$(INSTALL_DIR) $(1)/usr/lib/tachyon
|
||||||
$(CP) ./files/usr/lib/. $(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_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
|
$(INSTALL_BIN) ./files/etc/hotplug.d/iface/99-tachyon-wan-monitor $(1)/etc/hotplug.d/iface/99-tachyon-wan-monitor
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function restore_dnsmasq_failsafe_on_loader_failure(command) {
|
|||||||
command = as_string(command);
|
command = as_string(command);
|
||||||
if (command == "start" || command == "stop" || command == "restart" ||
|
if (command == "start" || command == "stop" || command == "restart" ||
|
||||||
command == "reload" || command == "uninstall" ||
|
command == "reload" || command == "uninstall" ||
|
||||||
|
command == "reset_settings" ||
|
||||||
command == "dnsmasq_restore" || command == "restore_dnsmasq")
|
command == "dnsmasq_restore" || command == "restore_dnsmasq")
|
||||||
restore_dnsmasq_failsafe();
|
restore_dnsmasq_failsafe();
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,8 @@ function show_help() {
|
|||||||
print(" restart Restart Tachyon service\n");
|
print(" restart Restart Tachyon service\n");
|
||||||
print(" enable Enable Tachyon autostart\n");
|
print(" enable Enable Tachyon autostart\n");
|
||||||
print(" disable Disable 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(" uninstall Remove Tachyon files installed outside opkg/apk\n");
|
||||||
print(" main Run main Tachyon process\n");
|
print(" main Run main Tachyon process\n");
|
||||||
print(" list_update Update remote lists\n");
|
print(" list_update Update remote lists\n");
|
||||||
@@ -160,6 +163,7 @@ function command_spec(command) {
|
|||||||
enable: [ "service/lifecycle.uc", "enable", 0 ],
|
enable: [ "service/lifecycle.uc", "enable", 0 ],
|
||||||
disable: [ "service/lifecycle.uc", "disable", 0 ],
|
disable: [ "service/lifecycle.uc", "disable", 0 ],
|
||||||
uninstall: [ "service/uninstall.uc", "uninstall", 1 ],
|
uninstall: [ "service/uninstall.uc", "uninstall", 1 ],
|
||||||
|
reset_settings: [ "service/reset.uc", "reset-settings", 1 ],
|
||||||
dnsmasq_restore: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
|
dnsmasq_restore: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
|
||||||
restore_dnsmasq: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
|
restore_dnsmasq: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
|
||||||
main: [ "service/lifecycle.uc", "main", 0 ],
|
main: [ "service/lifecycle.uc", "main", 0 ],
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||||
|
RESET_UC="$TACHYON_LIB/service/reset.uc"
|
||||||
|
TACHYON_BIN="$ROOT_DIR/tachyon/files/usr/bin/tachyon"
|
||||||
|
INSTALLER="$ROOT_DIR/install.sh"
|
||||||
|
WORK_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -r "$RESET_UC" ] || fail "service/reset.uc must own reset-to-defaults logic"
|
||||||
|
[ -r "$TACHYON_BIN" ] || fail "tachyon entrypoint is missing"
|
||||||
|
|
||||||
|
grep -Fq 'reset_settings: [ "service/reset.uc", "reset-settings", 1 ]' "$TACHYON_BIN" ||
|
||||||
|
fail "tachyon entrypoint must dispatch reset_settings to service/reset.uc"
|
||||||
|
grep -Fq 'reset_settings' "$TACHYON_BIN" ||
|
||||||
|
fail "tachyon entrypoint must list reset_settings in the command table"
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR/lib/defaults"
|
||||||
|
cat >"$WORK_DIR/default-config" <<'EOF'
|
||||||
|
config settings 'settings'
|
||||||
|
option config_version '1.0.5'
|
||||||
|
option dns_type 'udp'
|
||||||
|
option shutdown_correctly '0'
|
||||||
|
EOF
|
||||||
|
cp "$WORK_DIR/default-config" "$WORK_DIR/lib/defaults/config"
|
||||||
|
|
||||||
|
mkdir -p \
|
||||||
|
"$WORK_DIR/persistent/subscription-cache" \
|
||||||
|
"$WORK_DIR/persistent/rulesets" \
|
||||||
|
"$WORK_DIR/persistent/tailscale/server-main"
|
||||||
|
printf '%s\n' 'stale-cache' >"$WORK_DIR/persistent/subscription-cache/cache.json"
|
||||||
|
printf '%s\n' 'stale-ruleset' >"$WORK_DIR/persistent/rulesets/community.srs"
|
||||||
|
printf '%s\n' 'tailscale-identity' >"$WORK_DIR/persistent/tailscale/server-main/node.key"
|
||||||
|
mkdir -p "$WORK_DIR/runtime" "$WORK_DIR/sing-box-tmp"
|
||||||
|
printf '%s\n' '{"log":{}}' >"$WORK_DIR/sing-box-tmp/config.json"
|
||||||
|
mkdir -p "$(dirname "$WORK_DIR/sing-box-config")"
|
||||||
|
printf '%s\n' '{"log":{}}' >"$WORK_DIR/sing-box-config"
|
||||||
|
printf '%s\n' 'custom-user-config' >"$WORK_DIR/config"
|
||||||
|
|
||||||
|
: >"$WORK_DIR/bin.log"
|
||||||
|
: >"$WORK_DIR/init.log"
|
||||||
|
cat >"$WORK_DIR/fake-bin" <<'SH'
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
printf '%s\n' "$*" >> "$TACHYON_RESET_BIN_LOG"
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod 0755 "$WORK_DIR/fake-bin"
|
||||||
|
cat >"$WORK_DIR/fake-init" <<'SH'
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
printf '%s\n' "$*" >> "$TACHYON_RESET_INIT_LOG"
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod 0755 "$WORK_DIR/fake-init"
|
||||||
|
|
||||||
|
TACHYON_LIB="$WORK_DIR/lib" \
|
||||||
|
TACHYON_BIN="$WORK_DIR/fake-bin" \
|
||||||
|
TACHYON_SERVICE_INIT="$WORK_DIR/fake-init" \
|
||||||
|
TACHYON_CONFIG_PATH="$WORK_DIR/config" \
|
||||||
|
TACHYON_PERSISTENT_DIR="$WORK_DIR/persistent" \
|
||||||
|
TACHYON_RUNTIME_STATE_DIR="$WORK_DIR/runtime" \
|
||||||
|
TACHYON_SING_BOX_TMP_DIR="$WORK_DIR/sing-box-tmp" \
|
||||||
|
TACHYON_SING_BOX_CONFIG_PATH="$WORK_DIR/sing-box-config" \
|
||||||
|
TACHYON_RESET_BIN_LOG="$WORK_DIR/bin.log" \
|
||||||
|
TACHYON_RESET_INIT_LOG="$WORK_DIR/init.log" \
|
||||||
|
ucode -L "$WORK_DIR/lib" "$RESET_UC" reset-settings no-start >"$WORK_DIR/result.json"
|
||||||
|
grep -Fq '"success":true' "$WORK_DIR/result.json" ||
|
||||||
|
fail "reset-settings must report success"
|
||||||
|
|
||||||
|
grep -Fxq 'stop' "$WORK_DIR/bin.log" ||
|
||||||
|
fail "reset must stop the service through the backend entrypoint"
|
||||||
|
grep -Fxq 'stop' "$WORK_DIR/init.log" ||
|
||||||
|
fail "reset must stop the service through init.d"
|
||||||
|
if grep -Fxq 'start' "$WORK_DIR/init.log"; then
|
||||||
|
fail "reset no-start must not start the service"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmp -s "$WORK_DIR/config" "$WORK_DIR/default-config" ||
|
||||||
|
fail "reset must restore the config file to factory defaults"
|
||||||
|
[ "$(stat -c %a "$WORK_DIR/config")" = "600" ] ||
|
||||||
|
fail "reset config must be chmod 600"
|
||||||
|
|
||||||
|
[ ! -e "$WORK_DIR/persistent/subscription-cache" ] ||
|
||||||
|
fail "reset must remove stale subscription caches"
|
||||||
|
[ ! -e "$WORK_DIR/persistent/rulesets" ] ||
|
||||||
|
fail "reset must remove stale ruleset caches"
|
||||||
|
[ "$(cat "$WORK_DIR/persistent/tailscale/server-main/node.key")" = 'tailscale-identity' ] ||
|
||||||
|
fail "reset must preserve the Tailscale identity"
|
||||||
|
[ ! -e "$WORK_DIR/runtime" ] ||
|
||||||
|
fail "reset must remove the runtime state directory"
|
||||||
|
[ ! -e "$WORK_DIR/sing-box-tmp" ] ||
|
||||||
|
fail "reset must remove the sing-box temporary directory"
|
||||||
|
[ ! -e "$WORK_DIR/sing-box-config" ] ||
|
||||||
|
fail "reset must remove the sing-box config file"
|
||||||
|
|
||||||
|
TACHYON_LIB="$WORK_DIR/lib" \
|
||||||
|
TACHYON_BIN="$WORK_DIR/fake-bin" \
|
||||||
|
TACHYON_SERVICE_INIT="$WORK_DIR/fake-init" \
|
||||||
|
TACHYON_CONFIG_PATH="$WORK_DIR/config" \
|
||||||
|
TACHYON_PERSISTENT_DIR="$WORK_DIR/persistent" \
|
||||||
|
TACHYON_RUNTIME_STATE_DIR="$WORK_DIR/runtime" \
|
||||||
|
TACHYON_SING_BOX_TMP_DIR="$WORK_DIR/sing-box-tmp" \
|
||||||
|
TACHYON_SING_BOX_CONFIG_PATH="$WORK_DIR/sing-box-config" \
|
||||||
|
TACHYON_RESET_BIN_LOG="$WORK_DIR/bin.log" \
|
||||||
|
TACHYON_RESET_INIT_LOG="$WORK_DIR/init.log" \
|
||||||
|
ucode -L "$WORK_DIR/lib" "$RESET_UC" reset-settings >"$WORK_DIR/result.json"
|
||||||
|
grep -Fxq 'start' "$WORK_DIR/init.log" ||
|
||||||
|
fail "reset without no-start must start the service"
|
||||||
|
|
||||||
|
TACHYON_LIB="$WORK_DIR/lib" \
|
||||||
|
TACHYON_BIN="$WORK_DIR/fake-bin" \
|
||||||
|
TACHYON_SERVICE_INIT="$WORK_DIR/fake-init" \
|
||||||
|
TACHYON_CONFIG_PATH="$WORK_DIR/config" \
|
||||||
|
TACHYON_PERSISTENT_DIR="$WORK_DIR/persistent" \
|
||||||
|
TACHYON_RUNTIME_STATE_DIR="$WORK_DIR/runtime" \
|
||||||
|
TACHYON_SING_BOX_TMP_DIR="$WORK_DIR/sing-box-tmp" \
|
||||||
|
TACHYON_SING_BOX_CONFIG_PATH="$WORK_DIR/sing-box-config" \
|
||||||
|
TACHYON_RESET_BIN_LOG="$WORK_DIR/bin.log" \
|
||||||
|
TACHYON_RESET_INIT_LOG="$WORK_DIR/init.log" \
|
||||||
|
ucode -L "$WORK_DIR/lib" "$RESET_UC" bogus-mode 2>"$WORK_DIR/usage.err" &&
|
||||||
|
fail "reset module must reject unknown modes"
|
||||||
|
grep -Fq 'Usage' "$WORK_DIR/usage.err" ||
|
||||||
|
fail "reset module must print usage for unknown modes"
|
||||||
|
|
||||||
|
grep -Fq 'reset_settings [no-start]' "$TACHYON_BIN" ||
|
||||||
|
fail "tachyon help must document reset_settings"
|
||||||
|
grep -Fq 'reset_settings' "$TACHYON_BIN" ||
|
||||||
|
fail "tachyon failsafe dnsmasq restore must cover reset_settings"
|
||||||
|
|
||||||
|
# The installer must wipe leftover state on a fresh install but keep user
|
||||||
|
# configuration on upgrades and legacy migrations.
|
||||||
|
grep -Fq 'TACHYON_WAS_INSTALLED' "$INSTALLER" ||
|
||||||
|
fail "installer must remember whether Tachyon was installed before"
|
||||||
|
grep -Fq 'reset_settings", "no-start"' "$INSTALLER" ||
|
||||||
|
fail "installer post-install must delegate fresh-install defaults to reset_settings"
|
||||||
|
if grep -n -E 'reset_settings|TACHYON_WAS_INSTALLED' "$INSTALLER" | grep -qE 'rm -f |uci |sed -i '; then
|
||||||
|
fail "installer must not own reset/state shell logic"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'reset settings contract checks passed\n'
|
||||||
Reference in New Issue
Block a user