update 2025-10-26 04:24:05

This commit is contained in:
kenzok8
2025-10-26 04:24:05 +08:00
parent 3e32cf7bdf
commit e98c9610f8
6 changed files with 514 additions and 74 deletions

View File

@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ddns-go
PKG_VERSION:=1.5.4
PKG_RELEASE:=20250601
PKG_VERSION:=1.6.3
PKG_RELEASE:=20251025
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
PKG_CONFIG_DEPENDS:=

View File

@@ -6,9 +6,21 @@
'require uci';
'require form';
'require poll';
'require rpc';
const getDDNSGoInfo = rpc.declare({
object: 'luci.ddns-go',
method: 'get_ver',
expect: { 'ver': {} }
});
async function checkProcess() {
const getUpdateInfo = rpc.declare({
object: 'luci.ddns-go',
method: 'last_update',
expect: { 'update': {} }
});
async function checkProcess() {
// 先尝试用 pidof
try {
const pidofRes = await fs.exec('/bin/pidof', ['ddns-go']);
@@ -21,8 +33,6 @@
} catch (err) {
// pidof 失败,继续尝试 ps
}
// 回退到 ps
try {
const psRes = await fs.exec('/bin/ps', ['-C', 'ddns-go', '-o', 'pid=']);
const pid = psRes.stdout.trim();
@@ -33,29 +43,70 @@
} catch (err) {
return { running: false, pid: null };
}
}
function renderStatus(isRunning, listen_port, noweb) {
}
function getVersionInfo() {
return L.resolveDefault(getDDNSGoInfo(), {}).then(function(result) {
//console.log('getVersionInfo result:', result);
return result || {};
}).catch(function(error) {
console.error('Failed to get version:', error);
return {};
});
}
function checkUpdateStatus() {
return L.resolveDefault(getUpdateInfo(), {}).then(function(result) {
//console.log('checkUpdateStatus result:', result);
return result || {};
}).catch(function(error) {
console.error('Failed to get update info:', error);
return {};
});
}
function renderStatus(isRunning, listen_port, noweb, version) {
var statusText = isRunning ? _('RUNNING') : _('NOT RUNNING');
var color = isRunning ? 'green' : 'red';
var icon = isRunning ? '✓' : '✗';
var versionText = version ? `v${version}` : '';
var html = String.format(
'<em><span style="color:%s">%s <strong>%s %s</strong></span></em>',
color, icon, _('DDNS-Go'), statusText
'<em><span style="color:%s">%s <strong>%s %s - %s</strong></span></em>',
color, icon, _('DDNS-Go'), versionText, statusText
);
if (isRunning && res.pid) {
html += ' <small>(PID: ' + res.pid + ')</small>';
if (isRunning) {
html += String.format('&#160;<a class="btn cbi-button" href="%s//%s:%s" target="_blank">%s</a>',
window.location.protocol, window.location.hostname, listen_port, _('Open Web Interface'));
}
if (isRunning && noweb !== '1') {
const baseUrl = `${window.location.protocol}//${window.location.hostname}`;
const fullUrl = `${baseUrl}:${listen_port}`;
html += String.format('&#160;<a class="btn cbi-button" href="%s" target="_blank">%s</a>', fullUrl, _('Open Web Interface'));
}
return html;
}
function renderUpdateStatus(updateInfo) {
if (!updateInfo || !updateInfo.status) {
return '<span style="color:orange"> ⚠ ' + _('Update status unknown') + '</span>';
}
var status = updateInfo.status;
var message = updateInfo.message || '';
switch(status) {
case 'updated':
return String.format('<span style="color:green">✓ %s</span>', message);
case 'update_available':
return String.format('<span style="color:blue">↻ %s</span>', message);
case 'latest':
return String.format('<span style="color:green">✓ %s</span>', message);
case 'download_failed':
case 'check_failed':
return String.format('<span style="color:red">✗ %s</span>', message);
default:
return String.format('<span style="color:orange">? %s</span>', message);
}
}
return view.extend({
load: function() {
return Promise.all([
@@ -63,6 +114,118 @@ return view.extend({
]);
},
handleResetPassword: async function () {
try {
ui.showModal(_('Resetting Password'), [
E('p', { 'class': 'spinning' }, _('Resetting admin password, please wait...'))
]);
const result = await fs.exec('/usr/bin/ddns-go', ['-resetPassword', 'admin12345', '-c', '/etc/ddns-go/ddns-go-config.yaml']);
ui.hideModal();
const output = (result.stdout + result.stderr).trim();
let success = false;
let message = '';
if (result.code === 0) {
message = _('Password reset successfully to admin12345');
ui.showModal(_('Password Reset Successful'), [
E('p', _('Admin password has been reset to: admin12345')),
E('p', _('You need to restart DDNS-Go service for the changes to take effect.')),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button cbi-button-positive',
'click': ui.createHandlerFn(this, function() {
ui.hideModal();
this.handleRestartService();
})
}, _('Restart Service Now')),
' ',
E('button', {
'class': 'btn cbi-button cbi-button-neutral',
'click': ui.hideModal
}, _('Restart Later'))
])
]);
} else {
alert(_('Reset may have failed:') + '\n' + output);
}
} catch (error) {
ui.hideModal();
console.error('Reset password failed:', error);
alert(_('ERROR:') + '\n' + _('Reset password failed:') + '\n' + error.message);
}
},
handleRestartService: async function() {
try {
await fs.exec('/etc/init.d/ddns-go', ['stop']);
await new Promise(resolve => setTimeout(resolve, 1000));
await fs.exec('/etc/init.d/ddns-go', ['start']);
alert(_('SUCCESS:') + '\n' + _('DDNS-Go service restarted successfully'));
if (window.statusPoll) {
window.statusPoll();
}
} catch (error) {
alert(_('ERROR:') + '\n' + _('Failed to restart service:') + '\n' + error.message);
}
},
handleUpdate: async function () {
try {
var updateView = document.getElementById('update_status');
if (updateView) {
updateView.innerHTML = '<span class="spinning"></span> ' + _('Updating, please wait...');
}
const updateInfo = await checkUpdateStatus();
if (updateView) {
updateView.innerHTML = renderUpdateStatus(updateInfo);
}
if (updateInfo.update_successful || updateInfo.status === 'updated') {
if (window.statusPoll) {
window.statusPoll();
}
// 3秒后恢复显示版本信息
setTimeout(() => {
var updateView = document.getElementById('update_status');
if (updateView) {
getVersionInfo().then(function(versionInfo) {
var version = versionInfo.version || '';
updateView.innerHTML = String.format('<span style="color:green">✓ %s v%s</span>',
_('Current Version:'), version);
});
}
}, 3000);
}
} catch (error) {
console.error('Update failed:', error);
var updateView = document.getElementById('update_status');
if (updateView) {
updateView.innerHTML = '<span style="color:red">✗ ' + _('Update failed') + '</span>';
// 5秒后恢复显示版本信息
setTimeout(() => {
getVersionInfo().then(function(versionInfo) {
var version = versionInfo.version || '';
updateView.innerHTML = String.format('<span>%s v%s</span>',
_('Current Version:'), version);
});
}, 5000);
}
}
},
render: function(data) {
var m, s, o;
var listen_port = (uci.get('ddns-go', 'config', 'port') || '[::]:9876').split(':').slice(-1)[0];
@@ -74,20 +237,27 @@ return view.extend({
// 状态显示部分
s = m.section(form.TypedSection);
s.anonymous = true;
s.render = function() {
var statusView = E('p', { id: 'control_status' },
'<span class="spinning"></span> ' + _('Checking status...'));
var pollInterval = poll.add(function() {
return checkProcess()
.then(function(res) {
statusView.innerHTML = renderStatus(res.running, listen_port, noweb);
})
.catch(function(err) {
console.error('Status check failed:', err);
statusView.innerHTML = '<span style="color:orange">⚠ ' + _('Status check error') + '</span>';
});
}, 5); // 每5秒检查一次
window.statusPoll = function() {
return Promise.all([
checkProcess(),
getVersionInfo()
]).then(function(results) {
var [processInfo, versionInfo] = results;
var version = versionInfo.version || '';
statusView.innerHTML = renderStatus(processInfo.running, listen_port, noweb, version);
}).catch(function(err) {
console.error('Status check failed:', err);
statusView.innerHTML = '<span style="color:orange">⚠ ' + _('Status check error') + '</span>';
});
};
var pollInterval = poll.add(window.statusPoll, 5); // 每5秒检查一次
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
statusView,
@@ -102,44 +272,73 @@ return view.extend({
])
])
]);
}
};
s = m.section(form.NamedSection, 'config', 'basic');
s = m.section(form.NamedSection, 'config', 'basic');
o = s.option(form.Flag, 'enabled', _('Enable'));
o.default = o.disabled;
o.rmempty = false;
o = s.option(form.Flag, 'enabled', _('Enable'));
o.default = o.disabled;
o.rmempty = false;
o = s.option(form.Value, 'port', _('Listen port'));
o.default = '[::]:9876';
o.rmempty = false;
o = s.option(form.Value, 'port', _('Listen port'));
o.default = '[::]:9876';
o.rmempty = false;
o = s.option(form.Value, 'time', _('Update interval'));
o.default = '300';
o = s.option(form.Value, 'time', _('Update interval'));
o.default = '300';
o = s.option(form.Value, 'ctimes', _('Compare with service provider N times intervals'));
o.default = '5';
o = s.option(form.Value, 'ctimes', _('Compare with service provider N times intervals'));
o.default = '5';
o = s.option(form.Value, 'skipverify', _('Skip verifying certificates'));
o.default = '0';
o = s.option(form.Value, 'skipverify', _('Skip verifying certificates'));
o.default = '0';
o = s.option(form.Value, 'dns', _('Specify DNS resolution server'));
o.value('223.5.5.5', _('Ali DNS 223.5.5.5'));
o.value('223.6.6.6', _('Ali DNS 223.6.6.6'));
o.value('119.29.29.29', _('Tencent DNS 119.29.29.29'));
o.value('1.1.1.1', _('CloudFlare DNS 1.1.1.1'));
o.value('8.8.8.8', _('Google DNS 8.8.8.8'));
o.value('8.8.4.4', _('Google DNS 8.8.4.4'));
o.datatype = 'ipaddr';
o = s.option(form.Value, 'dns', _('Specify DNS resolution server'));
o.value('223.5.5.5', _('Ali DNS 223.5.5.5'));
o.value('223.6.6.6', _('Ali DNS 223.6.6.6'));
o.value('119.29.29.29', _('Tencent DNS 119.29.29.29'));
o.value('1.1.1.1', _('CloudFlare DNS 1.1.1.1'));
o.value('8.8.8.8', _('Google DNS 8.8.8.8'));
o.value('8.8.4.4', _('Google DNS 8.8.4.4'));
o.datatype = 'ipaddr';
o = s.option(form.Flag, 'noweb', _('Do not start web services'));
o.default = '0';
o.rmempty = false;
o = s.option(form.Flag, 'noweb', _('Do not start web services'));
o.default = '0';
o.rmempty = false;
o = s.option(form.Value, 'delay', _('Delayed Start (seconds)'));
o.default = '60';
o = s.option(form.Button, '_newpassword', _('Reset account password'));
o.inputtitle = _('ResetPassword');
o.inputstyle = 'apply';
o.onclick = L.bind(this.handleResetPassword, this, data);
o = s.option(form.DummyValue, '_update_status', _('Current Version'));
o.rawhtml = true;
var currentVersion = '';
getVersionInfo().then(function(versionInfo) {
currentVersion = versionInfo.version || '';
var updateView = document.getElementById('update_status');
if (updateView) {
updateView.innerHTML = String.format('<span>v%s</span>', currentVersion);
}
});
o.cfgvalue = function() {
return E('div', { style: 'margin: 5px 0;' }, [
E('span', { id: 'update_status' },
currentVersion ? String.format('v%s', currentVersion) : _('Loading...'))
]);
};
o = s.option(form.Value, 'delay', _('Delayed Start (seconds)'));
o.default = '60';
return m.render();
}
});
o = s.option(form.Button, '_update', _('Update kernel'),
_('Check and update DDNS-Go to the latest version'));
o.inputtitle = _('Check Update');
o.inputstyle = 'apply';
o.onclick = L.bind(this.handleUpdate, this, data);
return m.render();
}
});

View File

@@ -43,7 +43,7 @@ msgstr ""
msgid "DDNS-GO Service Not Running"
msgstr ""
msgid "Please enable the DDNS-GO service"
msgid "DDNS-GO Web Interface Disabled"
msgstr ""
msgid "Open Web Interface"
@@ -72,3 +72,51 @@ msgstr ""
msgid "Delayed Start (seconds)"
msgstr ""
msgid "Update kernel"
msgstr ""
msgid "Check and update DDNS-Go to the latest version"
msgstr ""
msgid "Check Update"
msgstr ""
msgid "Updating, please wait..."
msgstr ""
msgid "Update failed"
msgstr ""
msgid "Update status unknown"
msgstr ""
msgid "Reset account password"
msgstr ""
msgid "ResetPassword"
msgstr ""
msgid "SUCCESS:"
msgstr ""
msgid "Password reset successfully to admin12345"
msgstr ""
msgid "Password Reset Successful"
msgstr ""
msgid "User password has been reset to: admin12345"
msgstr ""
msgid "You need to restart DDNS-Go service for the changes to take effect."
msgstr ""
msgid "Restart Service Now"
msgstr ""
msgid "Restart Later"
msgstr ""
msgid ""
msgstr ""

View File

@@ -20,7 +20,7 @@ msgid "DDNS-GO Control panel"
msgstr "DDNS-GO操作台"
msgid "Open Web Interface"
msgstr "打开 Web 界面"
msgstr "打开Web界面"
msgid "Log"
msgstr "日志"
@@ -72,3 +72,51 @@ msgstr "不启动web服务"
msgid "Delayed Start (seconds)"
msgstr "开机延时启动(秒)"
msgid "Update kernel"
msgstr "更新内核"
msgid "Check and update DDNS-Go to the latest version"
msgstr "更新DDNS-Go到最新版本"
msgid "Check Update"
msgstr "检查更新"
msgid "Updating, please wait..."
msgstr "更新中,请稍等..."
msgid "Update failed"
msgstr "更新失败"
msgid "Update status unknown"
msgstr "更新状态未知"
msgid "Reset account password"
msgstr "重置登录密码"
msgid "ResetPassword"
msgstr "重置密码"
msgid "SUCCESS:"
msgstr "完成执行:"
msgid "Password reset successfully to admin12345"
msgstr "成功重置密码admin12345"
msgid "Password Reset Successful"
msgstr "重置密码成功"
msgid "User password has been reset to: admin12345"
msgstr "用户密码重置为admin12345"
msgid "You need to restart DDNS-Go service for the changes to take effect."
msgstr "需要重启DDNS-GO服务更改才生效"
msgid "Restart Service Now"
msgstr "立刻重启服务"
msgid "Restart Later"
msgstr "稍后重启"
msgid ""
msgstr ""

View File

@@ -1,23 +1,33 @@
{
"luci-app-ddns-go": {
"luci-app-ddns-go": {
"description": "Grant UCI access for luci-app-ddns-go",
"read": {
"file": {
"read": {
"uci": [ "ddns-go" ],
"file": {
"/etc/init.d/ddns-go": [ "exec" ],
"/usr/libexec/ddns-go-call": [ "exec" ],
"/usr/share/rpcd/ucode/luci.ddns-go": [ "exec" ],
"/bin/pidof": [ "exec" ],
"/bin/ps": [ "exec" ],
"/bin/ash": [ "exec" ],
"/etc/ddns-go/ddns-go-config.yaml": [ "read" ],
"/var/log/ddns-go.log": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "ddns-go" ,"ddns-go" ]
},
"write": {
"uci": [ "ddns-go" ,"ddns-go" ]
}
}
}
},
"ubus": {
"rc": [ "*" ],
"service": ["list"],
"luci.ddns-go": [ "*" ]
}
},
"write": {
"ubus": {
"luci.ddns-go": [ "*" ]
},
"file": {
"/etc/ddns-go/ddns-go-config.yaml": ["write"]
},
"uci": ["ddns-go"]
}
}
}

View File

@@ -0,0 +1,135 @@
#!/usr/bin/ucode
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2021-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-ddns-go
*/
'use strict';
import { access, error, lstat, popen, readfile, writefile } from 'fs';
/* Kanged from ucode/luci */
function shellquote(s) {
return `'${replace(s, "'", "'\\''")}'`;
}
function get_current_version() {
if (!access('/usr/bin/ddns-go'))
return null;
const fd = popen('/usr/bin/ddns-go -v');
if (fd) {
let version_output = fd.read('all');
fd.close();
if (!version_output || length(version_output) === 0)
return null;
try {
// 去除开头的 'v' 字符
version_output = replace(trim(version_output), /^v/, '');
return version_output;
} catch(e) {
return null;
}
}
return null;
}
const methods = {
get_ver: {
call: function() {
let current_version = get_current_version();
if (!current_version)
return { ver: {}, error: 'ddns-go not found or version check failed' };
return { ver: { version: current_version } };
}
},
last_update: {
call: function() {
if (!access('/usr/bin/ddns-go'))
return { update: {}, error: 'ddns-go not found' };
let version_before = get_current_version();
const fd = popen('/usr/bin/ddns-go -u');
if (fd) {
let output = fd.read('all');
fd.close();
if (!output || length(output) === 0)
return { update: {}, error: 'empty response' };
try {
output = trim(output);
let update_info = {
raw_output: output,
version_before: version_before,
version_after: null,
has_update: false,
update_successful: false,
current_version: '',
latest_version: '',
status: 'unknown',
message: output
};
update_info.version_after = get_current_version();
if (version_before && update_info.version_after && version_before !== update_info.version_after) {
update_info.update_successful = true;
update_info.has_update = false; // 已经更新完成,所以没有待更新了
update_info.status = 'updated';
update_info.message = `更新成功: ${version_before} → ${update_info.version_after}`;
}
else if (match(output, /Current version.*is the latest/)) {
update_info.status = 'latest';
update_info.has_update = false;
let version_match = match(output, /v[\d.]+/);
if (version_match) {
update_info.current_version = replace(version_match[0], /^v/, '');
update_info.latest_version = update_info.current_version;
}
update_info.message = '已是最新版本 ' + (update_info.current_version || '');
} else if (match(output, /new version.*available/)) {
update_info.status = 'update_available';
update_info.has_update = true;
let versions = match(output, /v[\d.]+/, 'g');
if (versions && length(versions) >= 2) {
update_info.current_version = replace(versions[0], /^v/, '');
update_info.latest_version = replace(versions[1], /^v/, '');
} else if (version_before) {
update_info.current_version = version_before;
}
update_info.message = '有新版本可用: ' + (update_info.latest_version || '');
} else if (match(output, /download.*failed/)) {
update_info.status = 'download_failed';
update_info.has_update = false;
update_info.message = '下载更新失败';
} else if (match(output, /check.*failed/)) {
update_info.status = 'check_failed';
update_info.has_update = false;
update_info.message = '检查更新失败';
} else {
update_info.status = 'unknown';
update_info.message = output;
}
return { update: update_info };
} catch(e) {
return { update: {}, error: 'Parse error: ' + e };
}
} else {
return { update: {}, error: 'failed to execute ddns-go command' };
}
}
}
};
return { 'luci.ddns-go': methods };