🔥 Sync 2026-10-24 23:19:23

This commit is contained in:
github-actions[bot] committed 2026-10-24 23:19:23 +08:00
1 parent 9886197604
commit 37e1475e87
59 files changed
+5167 -1483

No files matched your search

@@ -149,7 +149,6 @@ methods.do_login = {
if (status.status != 'logout') {
return { error: 'Tailscale is already logged in and running.' };
}
// --- 1. Prepare and Run Login Command (Once) ---
const loginserver = trim(form_data.loginserver) || '';
const loginserver_authkey = trim(form_data.loginserver_authkey) || '';
@@ -177,7 +176,7 @@ methods.do_login = {
let parts = split(trline, ' ');
for (let part in parts) {
if (index(part, 'http') != -1) {
return { url: part };
return { success: true, url: part };
}
}
}
@@ -199,7 +198,7 @@ methods.do_logout = {
if (logout_result.code != 0) {
return { error: 'Failed to logout: ' + logout_result.stderr };
}
return { success: true };
return { success: true, message: 'Logged out.' };
}
};
@@ -241,6 +240,7 @@ methods.setup_firewall = {
let changed_network = false;
let changed_firewall = false;
let run_id = 0;
// 1. config Network Interface
let net_ts = uci.get('network', 'tailscale');
@@ -335,24 +335,57 @@ methods.setup_firewall = {
changed_firewall = true;
}
// 4. save
// 4. save and hand the reload to a background worker
//
// Reloading network/firewall synchronously here was the reason the
// button failed through a reverse proxy: the reload tears down the
// connection carrying this very request. The worker records its
// progress in /var/run/tailscale-fwsetup.status and the UI polls it.
if (changed_network) {
uci.save('network');
uci.commit('network');
exec('/etc/init.d/network reload');
}
if (changed_firewall) {
uci.save('firewall');
uci.commit('firewall');
exec('/etc/init.d/firewall reload');
}
if (changed_network || changed_firewall) {
if (!access('/usr/libexec/tailscale-setup-firewall')) {
return { error: 'Firewall worker /usr/libexec/tailscale-setup-firewall is missing. Reinstall the package.' };
}
// The run id ties the polled result to this request, and the
// queued state replaces any previous result before the worker
// gets a chance to overwrite it. Without both, a poll arriving
// while the worker is still starting would read the previous
// run's "done" and report success for work that has not run.
run_id = time();
let st = writefile('/var/run/tailscale-fwsetup.status.tmp',
'queued\nstart\n\n' + run_id + '\n');
if (st == null || rename('/var/run/tailscale-fwsetup.status.tmp', '/var/run/tailscale-fwsetup.status') == null) {
// Without a writable status file the poll can never see a
// result, so say so instead of letting the UI wait it out.
return { error: 'Cannot write the firewall status file in /var/run. Configuration was not changed.' };
}
// The background shell exits immediately, so its status only
// tells us whether sh itself started. Anything the worker
// reports afterwards lands in the status file.
let spawn = exec('/bin/sh -c ' + shell_quote('/usr/libexec/tailscale-setup-firewall ' + run_id + ' >/dev/null 2>&1 &'));
if (spawn.code != 0) {
return { error: 'Failed to start the firewall worker: ' + join('\n', spawn.stdout) };
}
}
return {
success: true,
changed_network: changed_network,
changed_firewall: changed_firewall,
message: (changed_network || changed_firewall) ? 'Tailscale firewall/interface configuration applied.' : 'Tailscale firewall/interface already configured.'
job: (changed_network || changed_firewall) ? 'running' : 'idle',
run_id: run_id,
message: (changed_network || changed_firewall) ? 'Configuration committed, reloading network and firewall in the background.' : 'Tailscale firewall/interface already configured.'
};
} catch (e) {
@@ -361,6 +394,75 @@ methods.setup_firewall = {
}
};
// Record that the user asked to disable the service.
//
// The actual stop happens in /etc/init.d/tailscale-settings on the reload
// trigger, which runs right after rpcd commits and *before* it arms the LuCI
// rollback timer. Stopping here would not survive a rollback: rpcd restores
// the config files but never restarts a daemon it did not stop, so the user
// would end up with service_enabled=1 and a dead tunnel. The request file is
// dropped by the reload handler as soon as the config says enabled again.
methods.set_service_enabled = {
args: { enabled: '' },
call: function(request) {
let enabled = request?.args?.enabled;
if (enabled != '0' && enabled != '1') {
return { error: 'Invalid enabled value, expected 0 or 1.' };
}
if (enabled == '1') {
let r = exec('rm -f /var/run/tailscale-settings/disable-requested');
if (r.code != 0) {
return { error: 'Failed to clear the disable request: ' + join('\n', r.stdout) };
}
return { success: true, armed: false };
}
// No daemon probe here: get_status shells out to 'tailscale status'
// and would delay the apply for nothing. A request armed on a box
// without the tailscale package is simply ignored by the reload
// handler, which bails out when the init script is missing.
let r = exec('mkdir -p /var/run/tailscale-settings && touch /var/run/tailscale-settings/disable-requested');
if (r.code != 0) {
return { error: 'Failed to arm the disable request: ' + join('\n', r.stdout) };
}
return { success: true, armed: true };
}
};
// Result of the last background firewall setup job.
//
// Read here rather than in the browser so the ACL needs no file rights: the
// ucode plugin runs as root and rpcd only enforces path ACLs on the file
// ubus object.
methods.get_firewall_status = {
call: function() {
let path = '/var/run/tailscale-fwsetup.status';
if (!access(path)) {
return { success: true, state: 'idle', step: '', message: '', run_id: 0 };
}
try {
let raw = readfile(path);
if (raw == null) {
return { success: true, state: 'idle', step: '', message: '', run_id: 0 };
}
let lines = split(rtrim(raw, '\n'), '\n');
return {
success: true,
state: lines[0] || 'unknown',
step: lines[1] || '',
message: lines[2] || '',
// the run id is always the last line, so a message that somehow
// still contains a newline cannot shift it into line 3
run_id: int(lines[length(lines) - 1]) || 0
};
} catch (e) {
return { success: true, state: 'unknown', step: '', message: 'Unreadable job state: ' + e, run_id: 0 };
}
}
};
methods.get_logs = {
args: { lines: 200 },
call: function(request) {
@@ -368,9 +470,9 @@ methods.get_logs = {
let cmd = 'logread -l ' + lines + ' 2>/dev/null | grep -i -E "tailscale" || true';
let result = exec(cmd);
if (result.code == 0) {
return { logs: result.stdout };
return { success: true, logs: result.stdout };
}
return { logs: [], error: join(' ', result.stderr) };
return { success: false, logs: [], error: join(' ', result.stderr) };
}
};