💋 Sync 2026-03-21 18:21:15

This commit is contained in:
github-actions[bot] committed 2026-03-21 18:21:15 +08:00
1 parent 7147204921
commit 5168976bf5
36 files changed
+24624 -9

No files matched your search

+2 -2
View File
@@ -6,8 +6,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gowebdav
PKG_VERSION:=5.11.2
PKG_RELEASE:=2
PKG_VERSION:=5.11.3
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
+11
View File
@@ -0,0 +1,11 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.1.0
PKG_RELEASE:=1
LUCI_TITLE:=LuCI Support for momo
LUCI_DEPENDS:=+luci-base +momo
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1,152 @@
'use strict';
'require baseclass';
'require uci';
'require fs';
'require rpc';
'require request';
const callRCList = rpc.declare({
object: 'rc',
method: 'list',
params: ['name'],
expect: { '': {} }
});
const callRCInit = rpc.declare({
object: 'rc',
method: 'init',
params: ['name', 'action'],
expect: { '': {} }
});
const callMomoGetPaths = rpc.declare({
object: 'luci.momo',
method: 'get_paths',
expect: { '': {} }
});
const callMomoVersion = rpc.declare({
object: 'luci.momo',
method: 'version',
expect: { '': {} }
});
const callMomoProfile = rpc.declare({
object: 'luci.momo',
method: 'profile',
params: ['defaults'],
expect: { '': {} }
});
const callMomoUpdateSubscription = rpc.declare({
object: 'luci.momo',
method: 'update_subscription',
params: ['section_id'],
expect: { '': {} }
});
const callMomoAPI = rpc.declare({
object: 'luci.momo',
method: 'api',
params: ['method', 'path', 'query', 'body'],
expect: { '': {} }
});
const callMomoGetIdentifiers = rpc.declare({
object: 'luci.momo',
method: 'get_identifiers',
expect: { '': {} }
});
const callMomoDebug = rpc.declare({
object: 'luci.momo',
method: 'debug',
expect: { '': {} }
});
return baseclass.extend({
getPaths: async function () {
return callMomoGetPaths();
},
status: async function () {
return (await callRCList('momo'))?.momo?.running;
},
reload: function () {
return callRCInit('momo', 'reload');
},
restart: function () {
return callRCInit('momo', 'restart');
},
version: function () {
return callMomoVersion();
},
profile: function (defaults) {
return callMomoProfile(defaults);
},
updateSubscription: function (section_id) {
return callMomoUpdateSubscription(section_id);
},
updateDashboard: function () {
return callMomoAPI('POST', '/upgrade/ui');
},
openDashboard: async function () {
const profile = await callMomoProfile({ 'experimental': { 'clash_api': { 'external_controller': null, 'secret': null } } });
const apiListen = profile?.['experimental']?.['clash_api']?.['external_controller'];
const apiSecret = profile?.['experimental']?.['clash_api']?.['secret'] ?? '';
if (!apiListen) {
return Promise.reject('Clash API has not been configured');
}
const apiPort = apiListen.substring(apiListen.lastIndexOf(':') + 1);
const params = {
host: window.location.hostname,
hostname: window.location.hostname,
port: apiPort,
secret: apiSecret
};
const query = new URLSearchParams(params).toString();
const url = `http://${window.location.hostname}:${apiPort}/ui/?${query}`;
setTimeout(function () { window.open(url, '_blank') }, 0);
return Promise.resolve();
},
getIdentifiers: function () {
return callMomoGetIdentifiers();
},
listProfiles: async function () {
const paths = await this.getPaths();
return L.resolveDefault(fs.list(paths.profiles_dir), []);
},
getAppLog: async function () {
const paths = await this.getPaths();
return L.resolveDefault(fs.read_direct(paths.app_log_path));
},
getCoreLog: async function () {
const paths = await this.getPaths();
return L.resolveDefault(fs.read_direct(paths.core_log_path));
},
clearAppLog: async function () {
const paths = await this.getPaths();
return fs.write(paths.app_log_path);
},
clearCoreLog: async function () {
const paths = await this.getPaths();
return fs.write(paths.core_log_path);
},
debug: function () {
return callMomoDebug();
},
})
@@ -0,0 +1,169 @@
'use strict';
'require form';
'require view';
'require uci';
'require poll';
'require tools.momo as momo';
function renderStatus(running) {
return updateStatus(E('input', { id: 'core_status', style: 'border: unset; font-style: italic; font-weight: bold;', readonly: '' }), running);
}
function updateStatus(element, running) {
if (element) {
element.style.color = running ? 'green' : 'red';
element.value = running ? _('Running') : _('Not Running');
}
return element;
}
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.version(),
momo.status(),
momo.listProfiles()
]);
},
render: function (data) {
const subscriptions = uci.sections('momo', 'subscription');
const appVersion = data[1].app ?? '';
const coreVersion = data[1].core ?? '';
const running = data[2];
const profiles = data[3];
let m, s, o;
m = new form.Map('momo', _('Momo'), `${_('Transparent Proxy with sing-box on OpenWrt.')} <a href="https://github.com/nikkinikki-org/OpenWrt-momo/wiki" target="_blank">${_('How To Use')}</a>`);
s = m.section(form.TableSection, 'placeholder', _('Status'));
s.anonymous = true;
o = s.option(form.Value, '_app_version', _('App Version'));
o.readonly = true;
o.load = function () {
return appVersion;
};
o.write = function () { };
o = s.option(form.Value, '_core_version', _('Core Version'));
o.readonly = true;
o.load = function () {
return coreVersion;
};
o.write = function () { };
o = s.option(form.DummyValue, '_core_status', _('Core Status'));
o.cfgvalue = function () {
return renderStatus(running);
};
poll.add(function () {
return L.resolveDefault(momo.status()).then(function (running) {
updateStatus(document.getElementById('core_status'), running);
});
});
o = s.option(form.Button, 'reload');
o.inputstyle = 'action';
o.inputtitle = _('Reload Service');
o.onclick = function () {
return momo.reload();
};
o = s.option(form.Button, 'restart');
o.inputstyle = 'negative';
o.inputtitle = _('Restart Service');
o.onclick = function () {
return momo.restart();
};
o = s.option(form.Button, 'update_dashboard');
o.inputstyle = 'positive';
o.inputtitle = _('Update Dashboard');
o.onclick = function () {
return momo.updateDashboard();
};
o = s.option(form.Button, 'open_dashboard');
o.inputtitle = _('Open Dashboard');
o.onclick = function () {
return momo.openDashboard();
};
s = m.section(form.NamedSection, 'config', 'config', _('App Config'));
o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.option(form.ListValue, 'profile', _('Choose Profile'));
o.optional = true;
for (const profile of profiles) {
o.value('file:' + profile.name, _('File:') + profile.name);
};
for (const subscription of subscriptions) {
o.value('subscription:' + subscription['.name'], _('Subscription:') + subscription.name);
};
o = s.option(form.Value, 'start_delay', _('Start Delay'));
o.datatype = 'uinteger';
o.placeholder = _('Start Immidiately');
o = s.option(form.Flag, 'scheduled_restart', _('Scheduled Restart'));
o.rmempty = false;
o = s.option(form.Value, 'cron_expression', _('Cron Expression'));
o.retain = true;
o.rmempty = false;
o.depends('scheduled_restart', '1');
o = s.option(form.Flag, 'test_profile', _('Test Profile'));
o.rmempty = false;
o = s.option(form.Flag, 'core_only', _('Core Only'));
o.rmempty = false;
s = m.section(form.NamedSection, 'procd', 'procd', _('procd Config'));
s.tab('general', _('General Config'));
o = s.taboption('general', form.Flag, 'fast_reload', _('Fast Reload'));
o.rmempty = false;
s.tab('rlimit', _('RLIMIT Config'));
o = s.taboption('rlimit', form.Value, 'rlimit_address_space_soft', _('Address Space Size Soft Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_address_space_hard', _('Address Space Size Hard Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_data_soft', _('Heap Size Soft Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_data_hard', _('Heap Size Hard Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_stack_soft', _('Stack Size Soft Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_stack_hard', _('Stack Size Hard Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_nofile_soft', _('Number of Open Files Soft Limit'));
o.datatype = 'uinteger';
o = s.taboption('rlimit', form.Value, 'rlimit_nofile_hard', _('Number of Open Files Hard Limit'));
o.datatype = 'uinteger';
return m.render();
}
});
@@ -0,0 +1,69 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require tools.momo as momo'
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.getPaths(),
momo.listProfiles(),
]);
},
render: function (data) {
const subscriptions = uci.sections('momo', 'subscription');
const paths = data[1];
const profiles = data[2];
let m, s, o;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'placeholder', 'placeholder', _('Editor'));
o = s.option(form.ListValue, '_file', _('Choose File'));
o.optional = true;
for (const profile of profiles) {
o.value(paths.profiles_dir + '/' + profile.name, _('File:') + profile.name);
};
for (const subscription of subscriptions) {
o.value(paths.subscriptions_dir + '/' + subscription['.name'] + '.json', _('Subscription:') + subscription.name);
};
o.value(paths.run_profile_path, _('Profile for Startup'));
o.write = function (section_id, formvalue) {
return true;
};
o.onchange = function (event, section_id, value) {
return L.resolveDefault(fs.read_direct(value), '').then(function (content) {
m.lookupOption('_file_content', section_id)[0].getUIElement(section_id).setValue(content);
});
};
o = s.option(form.TextValue, '_file_content',);
o.rows = 25;
o.wrap = false;
o.write = function (section_id, formvalue) {
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
return fs.write(path, formvalue);
};
o.remove = function (section_id) {
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
return fs.write(path);
};
return m.render();
},
handleSaveApply: function (ev, mode) {
return this.handleSave(ev).finally(function () {
return mode === '0' ? momo.reload() : momo.restart();
});
},
handleReset: null
});
@@ -0,0 +1,126 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require poll';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.getPaths(),
momo.getAppLog(),
momo.getCoreLog()
]);
},
render: function (data) {
const paths = data[1];
const appLog = data[2];
const coreLog = data[3];
let m, s, o;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'placeholder', 'placeholder', _('Log'));
s.tab('app_log', _('App Log'));
o = s.taboption('app_log', form.Button, 'clear_app_log');
o.inputstyle = 'negative';
o.inputtitle = _('Clear Log');
o.onclick = function (_, section_id) {
m.lookupOption('_app_log', section_id)[0].getUIElement(section_id).setValue('');
return momo.clearAppLog();
};
o = s.taboption('app_log', form.TextValue, '_app_log');
o.rows = 25;
o.wrap = false;
o.load = function (section_id) {
return appLog;
};
o.write = function (section_id, formvalue) {
return true;
};
poll.add(L.bind(function () {
const option = this;
return L.resolveDefault(momo.getAppLog()).then(function (log) {
option.getUIElement('log').setValue(log);
});
}, o));
o = s.taboption('app_log', form.Button, 'scroll_app_log_to_bottom');
o.inputtitle = _('Scroll To Bottom');
o.onclick = function (_, section_id) {
const element = m.lookupOption('_app_log', section_id)[0].getUIElement(section_id).node.firstChild;
element.scrollTop = element.scrollHeight;
};
s.tab('core_log', _('Core Log'));
o = s.taboption('core_log', form.Button, 'clear_core_log');
o.inputstyle = 'negative';
o.inputtitle = _('Clear Log');
o.onclick = function (_, section_id) {
m.lookupOption('_core_log', section_id)[0].getUIElement(section_id).setValue('');
return momo.clearCoreLog();
};
o = s.taboption('core_log', form.TextValue, '_core_log');
o.rows = 25;
o.wrap = false;
o.load = function (section_id) {
return coreLog;
};
o.write = function (section_id, formvalue) {
return true;
};
poll.add(L.bind(function () {
const option = this;
return L.resolveDefault(momo.getCoreLog()).then(function (log) {
option.getUIElement('log').setValue(log);
});
}, o));
o = s.taboption('core_log', form.Button, 'scroll_core_log_to_bottom');
o.inputtitle = _('Scroll To Bottom');
o.onclick = function (_, section_id) {
const element = m.lookupOption('_core_log', section_id)[0].getUIElement(section_id).node.firstChild;
element.scrollTop = element.scrollHeight;
};
s.tab('debug_log', _('Debug Log'));
o = s.taboption('debug_log', form.Button, '_generate_download_debug_log');
o.inputstyle = 'negative';
o.inputtitle = _('Generate & Download');
o.onclick = function () {
return momo.debug().then(function () {
fs.read_direct(paths.debug_log_path, 'blob').then(function (data) {
// create url
const url = window.URL.createObjectURL(data, { type: 'text/markdown' });
// create link
const link = document.createElement('a');
link.href = url;
link.download = 'debug.log';
// append to body
document.body.appendChild(link);
// download
link.click();
// remove from body
document.body.removeChild(link);
// revoke url
window.URL.revokeObjectURL(url);
});
});
};
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});
@@ -0,0 +1,90 @@
'use strict';
'require form';
'require view';
'require uci';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.getPaths(),
]);
},
render: function (data) {
const paths = data[1];
let m, s, o, so;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'config', 'config', _('Profile'));
o = s.option(form.FileUpload, '_upload_profile', _('Upload Profile'));
o.browser = true;
o.enable_download = true;
o.root_directory = paths.profiles_dir;
o.write = function (section_id, formvalue) {
return true;
};
s = m.section(form.GridSection, 'subscription', _('Subscription'));
s.addremove = true;
s.anonymous = true;
s.sortable = true;
s.modaltitle = _('Edit Subscription');
o = s.option(form.Value, 'name', _('Subscription Name'));
o.rmempty = false;
o = s.option(form.Value, 'used', _('Used'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Value, 'total', _('Total'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Value, 'expire', _('Expire At'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Value, 'update', _('Update At'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Button, 'update_subscription');
o.editable = true;
o.inputstyle = 'positive';
o.inputtitle = _('Update');
o.modalonly = false;
o.onclick = function (_, section_id) {
return momo.updateSubscription(section_id);
};
o = s.option(form.Value, 'info_url', _('Subscription Info Url'));
o.modalonly = true;
o = s.option(form.Value, 'url', _('Subscription Url'));
o.modalonly = true;
o.rmempty = false;
o = s.option(form.Value, 'user_agent', _('User Agent'));
o.default = 'sing-box';
o.modalonly = true;
o.rmempty = false;
o.value('sing-box');
o = s.option(form.ListValue, 'prefer', _('Prefer'));
o.default = 'remote';
o.modalonly = true;
o.value('remote', _('Remote'));
o.value('local', _('Local'));
return m.render();
}
});
@@ -0,0 +1,212 @@
'use strict';
'require form';
'require view';
'require uci';
'require network';
'require tools.widgets as widgets';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
network.getHostHints(),
network.getNetworks(),
momo.getIdentifiers(),
]);
},
render: function (data) {
const hosts = data[1].hosts;
const networks = data[2];
const users = data[3]?.users ?? [];
const groups = data[3]?.groups ?? [];
const cgroups = data[3]?.cgroups ?? [];
let m, s, o, so;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'proxy', 'proxy', _('Proxy Config'));
s.tab('proxy', _('Proxy Config'));
o = s.taboption('proxy', form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv4_dns_hijack', _('IPv4 DNS Hijack'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv6_dns_hijack', _('IPv6 DNS Hijack'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv4_proxy', _('IPv4 Proxy'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv6_proxy', _('IPv6 Proxy'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'fake_ip_ping_hijack', _('Fake-IP Ping Hijack'));
o.rmempty = false;
o = s.taboption('proxy', form.ListValue, 'tcp_mode', _('TCP Mode'));
o.optional = true;
o.placeholder = _('Disable');
o.value('redirect', _('Redirect Mode'));
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
o = s.taboption('proxy', form.ListValue, 'udp_mode', _('UDP Mode'));
o.optional = true;
o.placeholder = _('Disable');
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
s.tab('router', _('Router Proxy'));
o = s.taboption('router', form.Flag, 'router_proxy', _('Enable'));
o.rmempty = false;
o = s.taboption('router', form.SectionValue, '_router_access_control', form.TableSection, 'router_access_control', _('Access Control'));
o.retain = true;
o.depends('router_proxy', '1');
o.subsection.addremove = true;
o.subsection.anonymous = true;
o.subsection.sortable = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.default = '1';
so.rmempty = false;
so = o.subsection.option(form.DynamicList, 'user', _('User'));
for (const user of users) {
so.value(user);
};
so = o.subsection.option(form.DynamicList, 'group', _('Group'));
for (const group of groups) {
so.value(group);
};
so = o.subsection.option(form.DynamicList, 'cgroup', _('CGroup'));
for (const cgroup of cgroups) {
so.value(cgroup);
};
so = o.subsection.option(form.Flag, 'dns', _('DNS'));
so.rmempty = false;
so = o.subsection.option(form.Flag, 'proxy', _('Proxy'));
so.rmempty = false;
s.tab('lan', _('LAN Proxy'));
o = s.taboption('lan', form.Flag, 'lan_proxy', _('Enable'));
o.rmempty = false;
o = s.taboption('lan', form.DynamicList, 'lan_inbound_interface', _('Inbound Interface'));
o.retain = true;
o.rmempty = false;
o.depends('lan_proxy', '1');
for (const network of networks) {
if (network.getName() === 'loopback') {
continue;
}
o.value(network.getName());
}
o = s.taboption('lan', form.SectionValue, '_lan_access_control', form.TableSection, 'lan_access_control', _('Access Control'));
o.retain = true;
o.depends('lan_proxy', '1');
o.subsection.addremove = true;
o.subsection.anonymous = true;
o.subsection.sortable = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.default = '1';
so.rmempty = false;
so = o.subsection.option(form.DynamicList, 'ip', 'IP');
so.datatype = 'ip4addr';
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ipaddrs) {
const hint = host.name ?? mac;
so.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
};
};
so = o.subsection.option(form.DynamicList, 'ip6', 'IP6');
so.datatype = 'ip6addr';
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ip6addrs) {
const hint = host.name ?? mac;
so.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
};
};
so = o.subsection.option(form.DynamicList, 'mac', 'MAC');
so.datatype = 'macaddr';
for (const mac in hosts) {
const host = hosts[mac];
const hint = host.name ?? host.ipaddrs[0];
so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
};
so = o.subsection.option(form.Flag, 'dns', _('DNS'));
so.rmempty = false;
so = o.subsection.option(form.Flag, 'proxy', _('Proxy'));
so.rmempty = false;
s.tab('bypass', _('Bypass'));
o = s.taboption('bypass', form.Flag, 'bypass_china_mainland_ip', _('Bypass China Mainland IP'));
o.rmempty = false;
o = s.taboption('bypass', form.Flag, 'bypass_china_mainland_ip6', _('Bypass China Mainland IP6'));
o.rmempty = false;
o = s.taboption('bypass', form.Value, 'proxy_tcp_dport', _('Destination TCP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('21 22 80 110 143 194 443 465 853 993 995 8080 8443', _('Commonly Used Port'));
o = s.taboption('bypass', form.Value, 'proxy_udp_dport', _('Destination UDP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('123 443 8443', _('Commonly Used Port'));
o = s.taboption('bypass', form.DynamicList, 'bypass_dscp', _('Bypass DSCP'));
o.datatype = 'range(0, 63)';
o = s.taboption('bypass', form.DynamicList, 'bypass_fwmark', _('Bypass FWMark'));
s.tab('misc', _('Misc'));
o = s.taboption('misc', form.DynamicList, 'reserved_ip', _('Reserved IP'));
o.datatype = 'ip4addr';
o = s.taboption('misc', form.DynamicList, 'reserved_ip6', _('Reserved IP6'));
o.datatype = 'ip6addr';
o = s.taboption('misc', form.Value, 'tun_timeout', _('TUN Timeout'));
o.datatype = 'uinteger';
o.rmempty = false;
o = s.taboption('misc', form.Value, 'tun_interval', _('TUN Interval'));
o.datatype = 'uinteger';
o.rmempty = false;
return m.render();
}
});
+427
View File
@@ -0,0 +1,427 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:69
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:122
msgid "Access Control"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:141
msgid "Address Space Size Hard Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:137
msgid "Address Space Size Soft Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:181
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:186
msgid "All Port"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:94
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:13
msgid "App Config"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:29
msgid "App Log"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:43
msgid "App Version"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:171
msgid "Bypass"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:173
msgid "Bypass China Mainland IP"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:176
msgid "Bypass China Mainland IP6"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:189
msgid "Bypass DSCP"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:192
msgid "Bypass FWMark"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:93
msgid "CGroup"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:27
msgid "Choose File"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:99
msgid "Choose Profile"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:33
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:66
msgid "Clear Log"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:182
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:187
msgid "Commonly Used Port"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:62
msgid "Core Log"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:125
msgid "Core Only"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:57
msgid "Core Status"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:50
msgid "Core Version"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:117
msgid "Cron Expression"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:99
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:165
msgid "DNS"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:95
msgid "Debug Log"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:179
msgid "Destination TCP Port to Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:184
msgid "Destination UDP Port to Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:53
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:60
msgid "Disable"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:35
msgid "Edit Subscription"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:25
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:37
msgid "Editor"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:96
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:33
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:66
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:77
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:107
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:130
msgid "Enable"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:50
msgid "Expire At"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:48
msgid "Fake-IP Ping Hijack"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:132
msgid "Fast Reload"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:103
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:31
msgid "File:"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:130
msgid "General Config"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:99
msgid "Generate & Download"
msgstr ""
#: applications/luci-app-momo/root/usr/share/rpcd/acl.d/luci-app-momo.json:3
msgid "Grant access to momo procedures"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:87
msgid "Group"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:149
msgid "Heap Size Hard Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:145
msgid "Heap Size Soft Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:38
msgid "How To Use"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:36
msgid "IPv4 DNS Hijack"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:42
msgid "IPv4 Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:39
msgid "IPv6 DNS Hijack"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:45
msgid "IPv6 Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:110
msgid "Inbound Interface"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:105
msgid "LAN Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:86
msgid "Local"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:27
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:45
msgid "Log"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:194
msgid "Misc"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:38
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:3
msgid "Momo"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:15
msgid "Not Running"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:164
msgid "Number of Open Files Hard Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:161
msgid "Number of Open Files Soft Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:89
msgid "Open Dashboard"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:82
msgid "Prefer"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:21
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:21
msgid "Profile"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:38
msgid "Profile for Startup"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:102
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:168
msgid "Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:29
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:31
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:29
msgid "Proxy Config"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:135
msgid "RLIMIT Config"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:54
msgid "Redirect Mode"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:69
msgid "Reload Service"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:85
msgid "Remote"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:196
msgid "Reserved IP"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:199
msgid "Reserved IP6"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:76
msgid "Restart Service"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:64
msgid "Router Proxy"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:15
msgid "Running"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:114
msgid "Scheduled Restart"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:56
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:89
msgid "Scroll To Bottom"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:157
msgid "Stack Size Hard Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:153
msgid "Stack Size Soft Limit"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:110
msgid "Start Delay"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:112
msgid "Start Immidiately"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:40
msgid "Status"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:31
msgid "Subscription"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:69
msgid "Subscription Info Url"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:37
msgid "Subscription Name"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:72
msgid "Subscription Url"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:107
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:35
msgid "Subscription:"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:51
msgid "TCP Mode"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:55
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:61
msgid "TPROXY Mode"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:206
msgid "TUN Interval"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:56
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:62
msgid "TUN Mode"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:202
msgid "TUN Timeout"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:122
msgid "Test Profile"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:45
msgid "Total"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:38
msgid "Transparent Proxy with sing-box on OpenWrt."
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:58
msgid "UDP Mode"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:139
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:143
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:147
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:151
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:155
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:159
msgid "Unlimited"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:63
msgid "Update"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:55
msgid "Update At"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:83
msgid "Update Dashboard"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:23
msgid "Upload Profile"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:40
msgid "Used"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:81
msgid "User"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:76
msgid "User Agent"
msgstr ""
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:128
msgid "procd Config"
msgstr ""
+1
View File
@@ -0,0 +1 @@
zh_Hans
+434
View File
@@ -0,0 +1,434 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:69
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:122
msgid "Access Control"
msgstr "访问控制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:141
msgid "Address Space Size Hard Limit"
msgstr "地址空间大小硬限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:137
msgid "Address Space Size Soft Limit"
msgstr "地址空间大小软限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:181
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:186
msgid "All Port"
msgstr "全部端口"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:94
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:13
msgid "App Config"
msgstr "插件配置"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:29
msgid "App Log"
msgstr "插件日志"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:43
msgid "App Version"
msgstr "插件版本"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:171
msgid "Bypass"
msgstr "绕过"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:173
msgid "Bypass China Mainland IP"
msgstr "绕过中国大陆 IP"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:176
msgid "Bypass China Mainland IP6"
msgstr "绕过中国大陆 IP6"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:189
msgid "Bypass DSCP"
msgstr "绕过 DSCP"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:192
msgid "Bypass FWMark"
msgstr "绕过 FWMark"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:93
msgid "CGroup"
msgstr "控制组"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:27
msgid "Choose File"
msgstr "选择文件"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:99
msgid "Choose Profile"
msgstr "选择配置文件"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:33
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:66
msgid "Clear Log"
msgstr "清空日志"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:182
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:187
msgid "Commonly Used Port"
msgstr "常用端口"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:62
msgid "Core Log"
msgstr "核心日志"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:125
msgid "Core Only"
msgstr "仅核心"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:57
msgid "Core Status"
msgstr "核心状态"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:50
msgid "Core Version"
msgstr "核心版本"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:117
msgid "Cron Expression"
msgstr "Cron 表达式"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:99
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:165
msgid "DNS"
msgstr "DNS"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:95
msgid "Debug Log"
msgstr "调试日志"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:179
msgid "Destination TCP Port to Proxy"
msgstr "要代理的 TCP 目标端口"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:184
msgid "Destination UDP Port to Proxy"
msgstr "要代理的 UDP 目标端口"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:53
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:60
msgid "Disable"
msgstr "禁用"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:35
msgid "Edit Subscription"
msgstr "编辑订阅"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:25
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:37
msgid "Editor"
msgstr "编辑器"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:96
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:33
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:66
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:77
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:107
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:130
msgid "Enable"
msgstr "启用"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:50
msgid "Expire At"
msgstr "到期时间"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:48
msgid "Fake-IP Ping Hijack"
msgstr "Fake-IP Ping 劫持"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:132
msgid "Fast Reload"
msgstr "快速重载"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:103
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:31
msgid "File:"
msgstr "文件:"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:130
msgid "General Config"
msgstr "全局配置"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:99
msgid "Generate & Download"
msgstr "生成并下载"
#: applications/luci-app-momo/root/usr/share/rpcd/acl.d/luci-app-momo.json:3
msgid "Grant access to momo procedures"
msgstr "授予访问 momo 程序的权限"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:87
msgid "Group"
msgstr "用户组"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:149
msgid "Heap Size Hard Limit"
msgstr "堆大小硬限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:145
msgid "Heap Size Soft Limit"
msgstr "堆大小软限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:38
msgid "How To Use"
msgstr "使用说明"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:36
msgid "IPv4 DNS Hijack"
msgstr "IPv4 DNS 劫持"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:42
msgid "IPv4 Proxy"
msgstr "IPv4 代理"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:39
msgid "IPv6 DNS Hijack"
msgstr "IPv6 DNS 劫持"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:45
msgid "IPv6 Proxy"
msgstr "IPv6 代理"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:110
msgid "Inbound Interface"
msgstr "入站接口"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:105
msgid "LAN Proxy"
msgstr "局域网代理"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:86
msgid "Local"
msgstr "本地"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:27
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:45
msgid "Log"
msgstr "日志"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:194
msgid "Misc"
msgstr "杂项"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:38
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:3
msgid "Momo"
msgstr "Momo"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:15
msgid "Not Running"
msgstr "未在运行"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:164
msgid "Number of Open Files Hard Limit"
msgstr "打开文件数量硬限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:161
msgid "Number of Open Files Soft Limit"
msgstr "打开文件数量软限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:89
msgid "Open Dashboard"
msgstr "打开面板"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:82
msgid "Prefer"
msgstr "优先"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:21
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:21
msgid "Profile"
msgstr "配置文件"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:38
msgid "Profile for Startup"
msgstr "用于启动的配置文件"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:102
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:168
msgid "Proxy"
msgstr "代理"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:29
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:31
#: applications/luci-app-momo/root/usr/share/luci/menu.d/luci-app-momo.json:29
msgid "Proxy Config"
msgstr "代理配置"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:135
msgid "RLIMIT Config"
msgstr "资源限制配置"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:54
msgid "Redirect Mode"
msgstr "Redirect 模式"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:69
msgid "Reload Service"
msgstr "重载服务"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:85
msgid "Remote"
msgstr "远程"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:196
msgid "Reserved IP"
msgstr "IPv4 保留地址"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:199
msgid "Reserved IP6"
msgstr "IPv6 保留地址"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:76
msgid "Restart Service"
msgstr "重启服务"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:64
msgid "Router Proxy"
msgstr "路由器代理"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:15
msgid "Running"
msgstr "运行中"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:114
msgid "Scheduled Restart"
msgstr "定时重启"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:56
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/log.js:89
msgid "Scroll To Bottom"
msgstr "滚动到底部"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:157
msgid "Stack Size Hard Limit"
msgstr "栈大小硬限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:153
msgid "Stack Size Soft Limit"
msgstr "栈大小软限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:110
msgid "Start Delay"
msgstr "启动延迟"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:112
msgid "Start Immidiately"
msgstr "立即启动"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:40
msgid "Status"
msgstr "状态"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:31
msgid "Subscription"
msgstr "订阅"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:69
msgid "Subscription Info Url"
msgstr "订阅信息链接"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:37
msgid "Subscription Name"
msgstr "订阅名称"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:72
msgid "Subscription Url"
msgstr "订阅链接"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:107
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js:35
msgid "Subscription:"
msgstr "订阅:"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:51
msgid "TCP Mode"
msgstr "TCP 模式"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:55
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:61
msgid "TPROXY Mode"
msgstr "TPROXY 模式"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:206
msgid "TUN Interval"
msgstr "TUN 等待间隔"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:56
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:62
msgid "TUN Mode"
msgstr "TUN 模式"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:202
msgid "TUN Timeout"
msgstr "TUN 等待超时"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:122
msgid "Test Profile"
msgstr "检查配置文件"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:45
msgid "Total"
msgstr "总量"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:38
msgid "Transparent Proxy with sing-box on OpenWrt."
msgstr "在 OpenWrt 上使用 sing-box 进行透明代理"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:58
msgid "UDP Mode"
msgstr "UDP 模式"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:139
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:143
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:147
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:151
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:155
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:159
msgid "Unlimited"
msgstr "无限制"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:63
msgid "Update"
msgstr "更新"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:55
msgid "Update At"
msgstr "更新时间"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:83
msgid "Update Dashboard"
msgstr "更新面板"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:23
msgid "Upload Profile"
msgstr "上传配置文件"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:40
msgid "Used"
msgstr "已使用"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/proxy.js:81
msgid "User"
msgstr "用户"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/profile.js:76
msgid "User Agent"
msgstr "用户代理(UA)"
#: applications/luci-app-momo/htdocs/luci-static/resources/view/momo/app.js:128
msgid "procd Config"
msgstr "procd 配置"
@@ -0,0 +1,52 @@
{
"admin/services/momo": {
"title": "Momo",
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-momo" ],
"uci": { "momo": true }
}
},
"admin/services/momo/config": {
"title": "App Config",
"order": 10,
"action": {
"type": "view",
"path": "momo/app"
}
},
"admin/services/momo/profile": {
"title": "Profile",
"order": 20,
"action": {
"type": "view",
"path": "momo/profile"
}
},
"admin/services/momo/proxy": {
"title": "Proxy Config",
"order": 30,
"action": {
"type": "view",
"path": "momo/proxy"
}
},
"admin/services/momo/editor": {
"title": "Editor",
"order": 40,
"action": {
"type": "view",
"path": "momo/editor"
}
},
"admin/services/momo/log": {
"title": "Log",
"order": 50,
"action": {
"type": "view",
"path": "momo/log"
}
}
}
@@ -0,0 +1,27 @@
{
"luci-app-momo": {
"description": "Grant access to momo procedures",
"read": {
"uci": [ "momo" ],
"ubus": {
"rc": [ "*" ],
"luci.momo": [ "*" ]
},
"file": {
"/etc/momo/profiles/*.json": ["read"],
"/etc/momo/subscriptions/*.json": ["read"],
"/etc/momo/run/config.json": ["read"],
"/var/log/momo/*.log": ["read"]
}
},
"write": {
"uci": [ "momo" ],
"file": {
"/etc/momo/profiles/*.json": ["write"],
"/etc/momo/subscriptions/*.json": ["write"],
"/etc/momo/run/config.json": ["write"],
"/var/log/momo/*.log": ["write"]
}
}
}
}
@@ -0,0 +1,107 @@
#!/usr/bin/ucode
'use strict';
import { access, popen, readfile, writefile } from 'fs';
import { get_paths, merge_exists, get_users, get_groups, get_cgroups } from '/etc/momo/ucode/include.uc';
const methods = {
get_paths: {
call: function() {
return get_paths();
}
},
version: {
call: function() {
let process;
let app = '';
if (system('command -v opkg') == 0) {
process = popen(`opkg list-installed luci-app-momo | cut -d ' ' -f 3`);
if (process) {
app = trim(process.read('all'));
process.close();
}
} else if (system('command -v apk') == 0) {
process = popen(`apk list -I luci-app-momo | cut -d ' ' -f 1 | cut -d '-' -f 4`);
if (process) {
app = trim(process.read('all'));
process.close();
}
}
let core = '';
process = popen(`sing-box version | grep sing-box | cut -d ' ' -f 3`);
if (process) {
core = trim(process.read('all'));
process.close();
}
return { app: app, core: core };
}
},
profile: {
args: { defaults: {} },
call: function(req) {
const paths = get_paths();
const defaults = req.args?.defaults ?? {};
const profile = json(readfile(paths.run_profile_path));
return merge_exists(defaults, profile);
}
},
update_subscription: {
args: { section_id: 'section_id' },
call: function(req) {
let success = false;
const section_id = req.args?.section_id;
if (section_id) {
success = system(['service', 'momo', 'update_subscription', section_id]) == 0;
}
return { success: success };
}
},
api: {
args: { method: 'method', path: 'path', query: 'query', body: 'body' },
call: function(req) {
let result = {};
const method = req.args?.method;
const path = req.args?.path;
const query = req.args?.query;
const body = req.args?.body;
const paths = get_paths();
const profile = json(readfile(paths.run_profile_path));
const api_listen = profile['experimental']['clash_api']['external_controller'];
const api_secret = profile['experimental']['clash_api']['secret'];
if (!api_listen) {
return result;
}
const url = api_listen + path;
const process = popen(`curl --request '${method}' --oauth2-bearer '${api_secret}' --url-query '${query}' --data '${body}' '${url}'`);
if (process) {
result = json(process);
process.close();
}
return result;
}
},
get_identifiers: {
call: function() {
const users = filter(get_users(), (x) => x != '');
const groups = filter(get_groups(), (x) => x != '');
const cgroups = filter(get_cgroups(), (x) => x != '' && index(x, 'services/momo') < 0);
return { users: users, groups: groups, cgroups: cgroups };
}
},
debug: {
call: function() {
const paths = get_paths();
const success = system(`${paths.debug_sh} > ${paths.debug_log_path}`) == 0;
return { success: success };
}
}
};
return { 'luci.momo': methods };
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-mosdns
PKG_VERSION:=1.7.0
PKG_RELEASE:=3
PKG_RELEASE:=4
LUCI_TITLE:=LuCI Support for mosdns
LUCI_PKGARCH:=all
+1 -1
View File
@@ -749,7 +749,7 @@ restore_dhcp_setting() {
}
reload_dnsmasq() {
/etc/init.d/dnsmasq reload
[ "$(/etc/init.d/dnsmasq status)" = "running" ] && /etc/init.d/dnsmasq reload
}
reload_service() {
+3 -3
View File
@@ -1,12 +1,12 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=0.1.2
PKG_RELEASE:=4
PKG_VERSION:=0.1.3
PKG_RELEASE:=5
LUCI_TITLE:=LuCI support for OpenClaw Launcher
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+luci-base +luci-compat +luci-lib-taskd +curl +ca-bundle +tar +xz-utils +jsonfilter +git +git-http
LUCI_DEPENDS:=+luci-base +luci-compat +luci-lib-taskd +curl +ca-bundle +tar +xz-utils +jsonfilter +git +git-http +ttyd
define Package/luci-app-openclawmgr/conffiles
/etc/config/openclawmgr
@@ -10,12 +10,17 @@ function index()
local page = entry({"admin", "services", "openclawmgr", "config"}, call("action_app"), _("Config"), 10)
page.leaf = true
entry({"admin", "services", "openclawmgr", "cli"}, form("openclawmgr/cli"), _("命令行"), 20).leaf = true
entry({"admin", "services", "openclawmgr", "logs"}, template("openclawmgr/logs"), _("日志"), 30).leaf = true
entry({"admin", "services", "openclawmgr", "status"}, call("action_status")).leaf = true
entry({"admin", "services", "openclawmgr", "ready"}, call("action_ready")).leaf = true
entry({"admin", "services", "openclawmgr", "op"}, call("action_op")).leaf = true
entry({"admin", "services", "openclawmgr", "config_data"}, call("action_config_data")).leaf = true
entry({"admin", "services", "openclawmgr", "apply_config"}, call("action_apply_config")).leaf = true
entry({"admin", "services", "openclawmgr", "logs_api"}, call("action_logs")).leaf = true
entry({"admin", "services", "openclawmgr", "diag_info"}, call("action_diag_info")).leaf = true
entry({"admin", "services", "openclawmgr", "diag_run"}, call("action_diag_run")).leaf = true
entry({"admin", "services", "openclawmgr", "diag_poll"}, call("action_diag_poll")).leaf = true
@@ -136,6 +141,22 @@ local function get_task_state(task_id)
}
end
local function safe_int(v, def, minv, maxv)
v = tostring(v or "")
local n = tonumber(v)
if not n then
return def
end
n = math.floor(n)
if minv and n < minv then
return minv
end
if maxv and n > maxv then
return maxv
end
return n
end
local function require_csrf()
local http = require "luci.http"
local disp = require "luci.dispatcher"
@@ -179,6 +200,36 @@ local function get_host()
return host
end
function action_logs()
local http = require "luci.http"
local sys = require "luci.sys"
local fs = require "nixio.fs"
local util = require "luci.util"
local limit = safe_int(http.formvalue("limit"), 200, 50, 2000)
local kind = (http.formvalue("kind") or "openclaw"):lower()
local cmd = ""
if kind == "tasks" then
local f = "/var/log/tasks/openclawmgr.log"
if fs.access(f) then
cmd = "tail -n " .. limit .. " " .. util.shellquote(f) .. " 2>/dev/null"
else
cmd = "echo '(task log not found: " .. f .. ")'"
end
elseif kind == "openclawmgr" then
cmd = "logread 2>/dev/null | grep -i openclawmgr | tail -n " .. limit
elseif kind == "all" then
cmd = "logread 2>/dev/null | tail -n " .. limit
else
-- default: openclaw gateway logs
cmd = "logread 2>/dev/null | grep -i openclaw | tail -n " .. limit
end
local log = sys.exec(cmd) or ""
write_json({ ok = true, kind = kind, limit = limit, server_time = os.time(), log = log })
end
local function configured_base_path(base_dir)
local jsonc = require "luci.jsonc"
if type(base_dir) ~= "string" or base_dir == "" then
@@ -0,0 +1,99 @@
--[[
LuCI - Lua Configuration Interface
]]--
require "luci.util"
local uci = require "luci.model.uci".cursor()
local m, s, o
local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil
local base_dir = uci:get("openclawmgr", "main", "base_dir") or ""
m = SimpleForm("OpenClawCLI", "", translate("Only works in LAN"))
m.submit = false
m.reset = false
if not cmd_ttyd or cmd_ttyd:match("^%s*$") then
s = m:section(SimpleSection, nil, translate("ttyd not found"))
return m
end
if base_dir == "" then
s = m:section(SimpleSection, nil, translate("base_dir is not configured"))
return m
end
local node_dir = base_dir .. "/node"
local global_dir = base_dir .. "/global"
local data_dir = base_dir .. "/data"
local cmdline = "sh /usr/share/openclawmgr/oc-config.sh"
s = m:section(SimpleSection)
o = s:option(Value, "mode", translate("Mode"))
o:value("menu", translate("Interactive menu"))
o.default = "menu"
o.forcewrite = true
o.write = function(self, section, value)
if value == "menu" then
cmdline = "sh /usr/share/openclawmgr/oc-config.sh"
end
end
o = s:option(DummyValue, "_tip", translate("CLI Config"))
o.rawhtml = true
o.cfgvalue = function()
return translate("Starts an interactive OpenClaw configuration menu in a web terminal (LAN only).")
end
o = s:option(Button, "connect")
o.render = function(self, section, scope)
self.inputstyle = "add"
self.title = " "
self.inputtitle = translate("Connect")
Button.render(self, section, scope)
end
o.write = function(self, section)
local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil
if not cmd_ttyd or cmd_ttyd:match("^%s*$") then
return
end
local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1"))
if pid and pid ~= "" then
luci.util.exec("kill -9 " .. pid)
end
local current_path = os.getenv("PATH") or "/usr/sbin:/usr/bin:/sbin:/bin"
local env_path = node_dir .. "/bin:" .. global_dir .. "/bin:" .. current_path
local env_prefix = table.concat({
"BASE_DIR=" .. luci.util.shellquote(base_dir),
"NODE_DIR=" .. luci.util.shellquote(node_dir),
"GLOBAL_DIR=" .. luci.util.shellquote(global_dir),
"DATA_DIR=" .. luci.util.shellquote(data_dir),
"HOME=" .. luci.util.shellquote(data_dir),
"OPENCLAW_HOME=" .. luci.util.shellquote(data_dir),
"OPENCLAW_STATE_DIR=" .. luci.util.shellquote(data_dir .. "/.openclaw"),
"OPENCLAW_CONFIG_PATH=" .. luci.util.shellquote(data_dir .. "/.openclaw/openclaw.json"),
"PATH=" .. luci.util.shellquote(env_path),
}, " ")
local start_cmd = string.format(
"%s %s -d 2 --once -p 7682 sh -lc %s &",
env_prefix,
cmd_ttyd,
luci.util.shellquote(cmdline)
)
os.execute(start_cmd)
m.children[#m.children] = nil
s = m:section(SimpleSection)
o = s:option(DummyValue, "console")
o.template = "openclawmgr/cli"
end
return m
@@ -0,0 +1,11 @@
<div class="cbi-map">
<iframe id="terminal" style="width: 100%; min-height: 600px; border: none; border-radius: 3px;"></iframe>
</div>
<script type="text/javascript">
const el = document.querySelectorAll(".cbi-value");
for (let i = 0; i < el.length; i++) {
el[i].style.display = "none";
}
document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682";
</script>
@@ -0,0 +1,228 @@
<%+header%>
<%
local logs_url = luci.dispatcher.build_url("admin", "services", "openclawmgr", "logs_api")
%>
<style type="text/css">
.oclm-logbar { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin: 8px 0 10px 0; }
.oclm-logbar label { color:#555; font-size:12px; }
.oclm-logbar input[type="text"] { padding: 6px 10px; border: 1px solid #d0d7de; border-radius: 6px; background:#fff; min-width: 220px; }
.oclm-logbar select { padding: 6px 10px; border: 1px solid #d0d7de; border-radius: 6px; background:#fff; }
.oclm-logbar .chk { display:flex; align-items:center; gap:6px; font-size:12px; color:#555; }
.oclm-logbar button { cursor:pointer; }
.oclm-logbar .meta { margin-left:auto; font-size:12px; color:#666; }
.oclm-logwrap { border:1px solid #d0d7de; border-radius:8px; background:#0b1020; color:#dbe2ff; padding:10px; }
.oclm-logpre { white-space: pre-wrap; word-break: break-word; font-family: monospace; font-size: 12px; line-height: 1.5; margin:0; max-height: 70vh; overflow:auto; }
.oclm-hint { color:#666; font-size:12px; line-height:1.6; margin-top:8px; }
</style>
<h2><%:日志%></h2>
<div class="oclm-logbar">
<label for="oclm-log-kind"><%:来源%></label>
<select id="oclm-log-kind">
<option value="openclaw"><%:OpenClaw%></option>
<option value="openclawmgr"><%:OpenClawMgr%></option>
<option value="tasks"><%:任务日志%></option>
<option value="all"><%:全部%></option>
</select>
<label for="oclm-log-limit"><%:行数%></label>
<select id="oclm-log-limit">
<option value="200">200</option>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="2000">2000</option>
</select>
<label for="oclm-log-range"><%:时间范围%></label>
<select id="oclm-log-range">
<option value="all"><%:全部%></option>
<option value="300"><%:最近 5 分钟%></option>
<option value="900"><%:最近 15 分钟%></option>
<option value="3600"><%:最近 1 小时%></option>
<option value="21600"><%:最近 6 小时%></option>
<option value="86400"><%:最近 24 小时%></option>
</select>
<label for="oclm-log-keyword"><%:关键字%></label>
<input id="oclm-log-keyword" type="text" placeholder="<%:输入关键字过滤...%>">
<span class="chk"><input id="oclm-log-autoscroll" type="checkbox" checked> <%:自动滚动%></span>
<button type="button" class="cbi-button cbi-button-action" id="oclm-log-apply"><%:确定%></button>
<span id="oclm-meta" class="meta">-</span>
</div>
<div class="oclm-logwrap">
<pre class="oclm-logpre" id="oclm-logpre">-</pre>
</div>
<div class="oclm-hint">
<div><%:OpenClaw / OpenClawMgr 日志来自系统 logd,可在 SSH 运行%> <code>logread</code> <%:查看。%></div>
<div><%:任务日志为操作安装/升级/重启等时 taskd 输出,路径通常为%> <code>/var/log/tasks/openclawmgr.log</code>。</div>
</div>
<script type="text/javascript">
//<![CDATA[
(function() {
var url = '<%=logs_url%>';
var pre = document.getElementById('oclm-logpre');
var kindSel = document.getElementById('oclm-log-kind');
var limitSel = document.getElementById('oclm-log-limit');
var rangeSel = document.getElementById('oclm-log-range');
var kwInput = document.getElementById('oclm-log-keyword');
var autoScroll = document.getElementById('oclm-log-autoscroll');
var btn = document.getElementById('oclm-log-apply');
var meta = document.getElementById('oclm-meta');
var rawLog = '';
var serverNowSec = null;
var pollTimer = null;
var reqSeq = 0;
var filtersActive = false;
var activeKeyword = '';
var activeRange = 'all';
function monthToNum(mon) {
var m = {Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11};
return (m[mon] !== undefined) ? m[mon] : null;
}
function parseLogTimeSec(line) {
// Common logread formats:
// - "Mar 21 12:34:56 ..." (ubox)
// - "Sat Mar 21 12:34:56 2026 ..." / "Sat Mar 21 12:34:56 ..." (busybox variants)
var now = serverNowSec ? new Date(serverNowSec * 1000) : new Date();
var year = now.getFullYear();
var m = line.match(/^([A-Z][a-z]{2})\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})\b/);
if (!m) {
m = line.match(/^[A-Z][a-z]{2}\s+([A-Z][a-z]{2})\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})\b/);
if (m) m.splice(1, 0, m[1]); // keep shape
}
if (!m) return null;
var monStr = m[1];
var mon = monthToNum(monStr);
if (mon === null) return null;
var day = parseInt(m[2], 10);
var hh = parseInt(m[3], 10);
var mm = parseInt(m[4], 10);
var ss = parseInt(m[5], 10);
var dt = new Date(year, mon, day, hh, mm, ss);
// If parsed time is > 36h in the future, assume it belongs to last year (year rollover).
var delta = dt.getTime() - now.getTime();
if (delta > 36 * 3600 * 1000) dt = new Date(year - 1, mon, day, hh, mm, ss);
return Math.floor(dt.getTime() / 1000);
}
function applyFilters(raw) {
if (!filtersActive) {
var total0 = (raw || '').split(/\r?\n/).filter(Boolean).length;
meta.textContent = total0 + ' lines';
return raw || '';
}
var kw = (activeKeyword || '').trim();
var range = activeRange || 'all';
var since = null;
if (range !== 'all') {
var sec = parseInt(range, 10);
if (!isNaN(sec) && sec > 0) {
var base = serverNowSec || Math.floor(Date.now() / 1000);
since = base - sec;
}
}
var lines = (raw || '').split(/\r?\n/);
var out = [];
var matched = 0;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (!line) continue;
if (since !== null) {
var ts = parseLogTimeSec(line);
if (ts !== null && ts < since) continue;
}
if (kw) {
if (line.toLowerCase().indexOf(kw.toLowerCase()) === -1) continue;
}
out.push(line);
matched++;
}
var total = (raw || '').split(/\r?\n/).filter(Boolean).length;
meta.textContent = matched + '/' + total + ' lines';
return out.join('\n');
}
function render() {
var text = applyFilters(rawLog);
pre.textContent = text || '';
if (autoScroll.checked) {
pre.scrollTop = pre.scrollHeight;
}
}
function fetchOnce() {
var kind = kindSel.value || 'openclaw';
var limit = limitSel.value || '200';
var cur = ++reqSeq;
(new XHR()).get(url, { kind: kind, limit: limit }, function(x) {
if (cur !== reqSeq) return;
try {
var d = JSON.parse(x.responseText || '{}');
rawLog = d.log || '';
serverNowSec = d.server_time || null;
render();
} catch(e) {
rawLog = x.responseText || '';
serverNowSec = null;
render();
}
});
}
btn.addEventListener('click', function() {
filtersActive = true;
activeKeyword = (kwInput.value || '').trim();
activeRange = rangeSel.value || 'all';
render();
fetchOnce();
});
kindSel.addEventListener('change', fetchOnce);
limitSel.addEventListener('change', fetchOnce);
rangeSel.addEventListener('change', function() {
if (filtersActive) {
activeRange = rangeSel.value || 'all';
render();
}
});
kwInput.addEventListener('keydown', function(ev) {
if (ev.key === 'Enter') {
ev.preventDefault();
btn.click();
}
});
kwInput.addEventListener('compositionend', function() {
// No-op: filter applies on "确定"
});
autoScroll.addEventListener('change', function() { render(); });
function startPoll() {
if (pollTimer) window.clearInterval(pollTimer);
pollTimer = window.setInterval(fetchOnce, 2000);
}
function stopPoll() {
if (pollTimer) window.clearInterval(pollTimer);
pollTimer = null;
}
window.addEventListener('beforeunload', stopPoll);
fetchOnce();
startPoll();
})();
//]]>
</script>
<%+footer%>
@@ -0,0 +1,217 @@
#!/bin/sh
# ============================================================================
# OpenClawMgr CLI Config Helper (LAN ttyd)
# - Provides an interactive menu to run OpenClaw official configure wizard,
# manage config file, and restart gateway.
# ============================================================================
set -e
# Ensure a sensible TERM for ttyd/xterm.js.
export TERM="${TERM:-xterm-256color}"
ESC="$(printf '\033')"
RED="${ESC}[0;31m"; GREEN="${ESC}[0;32m"; YELLOW="${ESC}[1;33m"
CYAN="${ESC}[0;36m"; BOLD="${ESC}[1m"; DIM="${ESC}[2m"; NC="${ESC}[0m"
prompt_with_default() {
local prompt="$1" def="$2" var="$3" v=""
if [ -n "$def" ]; then
printf "%s%s[%s]%s: " "$prompt" "$DIM" "$def" "$NC"
else
printf "%s: " "$prompt"
fi
# shellcheck disable=SC2162
read v
[ -z "$v" ] && v="$def"
eval "$var=\$v"
}
pause_enter() {
printf "\n%s按回车继续...%s" "$DIM" "$NC"
# shellcheck disable=SC2162
read _
}
uci_get() { uci -q get "openclawmgr.main.$1" 2>/dev/null || true; }
BASE_DIR="${BASE_DIR:-$(uci_get base_dir)}"
if [ -z "$BASE_DIR" ]; then
printf '%s\n' "${RED}ERROR: base_dir 未配置。请先在 OpenClaw 启动器页面设置数据目录并保存应用。${NC}"
exit 1
fi
NODE_DIR="${BASE_DIR}/node"
GLOBAL_DIR="${BASE_DIR}/global"
DATA_DIR="${BASE_DIR}/data"
NODE_BIN="${NODE_DIR}/bin/node"
OPENCLAW_BIN="${GLOBAL_DIR}/bin/openclaw"
CONFIG_FILE="${DATA_DIR}/.openclaw/openclaw.json"
BACKUP_DIR="${DATA_DIR}/.openclaw/backups"
export HOME="$DATA_DIR"
export OPENCLAW_HOME="$DATA_DIR"
export OPENCLAW_STATE_DIR="${DATA_DIR}/.openclaw"
export OPENCLAW_CONFIG_PATH="$CONFIG_FILE"
export PATH="${NODE_DIR}/bin:${GLOBAL_DIR}/bin:/usr/sbin:/usr/bin:/sbin:/bin"
find_entry() {
local d="${GLOBAL_DIR}/lib/node_modules/openclaw"
[ -f "${d}/openclaw.mjs" ] && { echo "${d}/openclaw.mjs"; return 0; }
[ -f "${d}/dist/cli.js" ] && { echo "${d}/dist/cli.js"; return 0; }
return 1
}
OPENCLAW_ENTRY="$(find_entry 2>/dev/null || true)"
oc_cmd() {
if [ -x "$OPENCLAW_BIN" ]; then
"$OPENCLAW_BIN" "$@"
return $?
fi
if [ -x "$NODE_BIN" ] && [ -n "$OPENCLAW_ENTRY" ]; then
"$NODE_BIN" "$OPENCLAW_ENTRY" "$@"
return $?
fi
printf '%s\n' "${RED}ERROR: OpenClaw 未安装或运行时不完整。${NC}"
printf '%s\n' "HINT: 在 OpenClaw 启动器页面执行安装/升级。"
return 127
}
restart_gateway() {
printf '\n'
printf '%s\n' "${CYAN}=== 重启 Gateway ===${NC}"
/usr/libexec/istorec/openclawmgr.sh restart 2>&1 || true
}
apply_config() {
printf '\n'
printf '%s\n' "${CYAN}=== 重新生成/应用配置(以 OpenClawMgr 配置为准)===${NC}"
/usr/libexec/istorec/openclawmgr.sh apply_config 2>&1 || true
}
show_config() {
printf '\n'
printf '%s\n' "${CYAN}=== 配置文件 ===${NC}"
printf '%s\n' "路径: ${DIM}${CONFIG_FILE}${NC}"
printf '\n'
if [ ! -f "$CONFIG_FILE" ]; then
printf '%s\n' "${YELLOW}(配置文件不存在)${NC}"
return 0
fi
if [ -x "$NODE_BIN" ]; then
"$NODE_BIN" -e "console.log(JSON.stringify(JSON.parse(require('fs').readFileSync(process.env.OPENCLAW_CONFIG_PATH,'utf8')),null,2))" 2>/dev/null \
|| cat "$CONFIG_FILE"
else
cat "$CONFIG_FILE"
fi
}
edit_config() {
if [ ! -f "$CONFIG_FILE" ]; then
printf '%s\n' "${YELLOW}配置文件不存在:${CONFIG_FILE}${NC}"
return 1
fi
if command -v vi >/dev/null 2>&1; then
vi "$CONFIG_FILE"
elif command -v nano >/dev/null 2>&1; then
nano "$CONFIG_FILE"
else
printf '%s\n' "${YELLOW}未找到编辑器(vi/nano)。${NC}"
return 1
fi
}
tail_logs() {
printf '\n'
printf '%s\n' "${CYAN}=== 最近日志(logread -e openclaw)===${NC}"
printf '\n'
logread -e openclaw 2>/dev/null | tail -200 || printf '%s\n' "${YELLOW}(无法读取日志)${NC}"
}
backup_config() {
mkdir -p "$BACKUP_DIR" 2>/dev/null || true
if [ ! -f "$CONFIG_FILE" ]; then
printf '%s\n' "${YELLOW}配置文件不存在,无法备份。${NC}"
return 1
fi
local ts dst
ts="$(date -u +%Y-%m-%dT%H-%M-%SZ 2>/dev/null || date +%s)"
dst="${BACKUP_DIR}/${ts}-openclaw.json"
cp -f "$CONFIG_FILE" "$dst"
printf '%s\n' "${GREEN}✅ 已备份到: ${dst}${NC}"
}
restore_config() {
mkdir -p "$BACKUP_DIR" 2>/dev/null || true
printf '\n'
printf '%s\n' "${CYAN}=== 可用备份 ===${NC}"
ls -lt "$BACKUP_DIR"/*.json 2>/dev/null | head -10 | awk '{print " " $NF}' || printf '%s\n' "${YELLOW}(无备份)${NC}"
printf '\n'
local fp=""
prompt_with_default "请输入要恢复的备份文件路径" "" fp
[ -n "$fp" ] || return 0
if [ ! -f "$fp" ]; then
printf '%s\n' "${YELLOW}文件不存在:${fp}${NC}"
return 1
fi
cp -f "$fp" "$CONFIG_FILE"
printf '%s\n' "${GREEN}✅ 已恢复配置到: ${CONFIG_FILE}${NC}"
}
main_menu() {
while true; do
printf '\n'
printf '%s\n' "${BOLD}OpenClaw AI Gateway — CLI 配置入口(OpenClawMgr)${NC}"
printf '%s\n' "${DIM}base_dir: ${BASE_DIR}${NC}"
printf '\n'
printf '%s\n' " ${CYAN}1)${NC} 🧭 官方配置向导 ${DIM}(openclaw configure)${NC}"
printf '%s\n' " ${CYAN}2)${NC} ℹ️ OpenClaw 版本/帮助 ${DIM}(openclaw --version / --help)${NC}"
printf '%s\n' " ${CYAN}3)${NC} 📄 查看配置文件 ${DIM}(openclaw.json)${NC}"
printf '%s\n' " ${CYAN}4)${NC} ✍️ 编辑配置文件 ${DIM}(vi/nano)${NC}"
printf '%s\n' " ${CYAN}5)${NC} ♻️ 应用/重建配置 ${DIM}(openclawmgr.sh apply_config)${NC}"
printf '%s\n' " ${CYAN}6)${NC} 🔄 重启 Gateway ${DIM}(openclawmgr.sh restart)${NC}"
printf '%s\n' " ${CYAN}7)${NC} 📋 查看日志 ${DIM}(logread -e openclaw)${NC}"
printf '%s\n' " ${CYAN}8)${NC} 💾 备份配置"
printf '%s\n' " ${CYAN}9)${NC} 📥 恢复配置"
printf '\n'
printf '%s\n' " ${CYAN}0)${NC} 退出"
printf '\n'
local c=""
prompt_with_default "请选择" "1" c
case "$c" in
1)
printf '\n'
printf '%s\n' "${CYAN}=== openclaw configure ===${NC}"
oc_cmd configure || true
pause_enter
;;
2)
printf '\n'
oc_cmd --version 2>/dev/null || true
printf '\n'
oc_cmd --help 2>/dev/null | sed -n '1,120p' || true
pause_enter
;;
3) show_config; pause_enter ;;
4) edit_config || true; pause_enter ;;
5) apply_config; pause_enter ;;
6) restart_gateway; pause_enter ;;
7) tail_logs; pause_enter ;;
8) backup_config || true; pause_enter ;;
9) restore_config || true; pause_enter ;;
0) printf '%s\n' "${GREEN}再见!${NC}"; exit 0 ;;
*) printf '%s\n' "${YELLOW}无效选择${NC}" ;;
esac
done
}
case "${1:-}" in
--help|-h)
echo "Usage: oc-config.sh [--help]"
;;
*)
main_menu
;;
esac
+65
View File
@@ -0,0 +1,65 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=momo
PKG_VERSION:=2025.12.31
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0+
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
include $(INCLUDE_DIR)/package.mk
define Package/momo
SECTION:=net
CATEGORY:=Network
TITLE:=The universal proxy platform.
URL:=https://sing-box.sagernet.org
DEPENDS:=+ca-bundle +curl firewall4 +ip-full +kmod-inet-diag +kmod-nft-socket +kmod-nft-tproxy +kmod-tun +sing-box
endef
define Package/momo/description
The universal proxy platform.
endef
define Package/momo/conffiles
/etc/config/momo
endef
define Package/momo/install
$(INSTALL_DIR) $(1)/etc/momo
$(INSTALL_DIR) $(1)/etc/momo/ucode
$(INSTALL_DIR) $(1)/etc/momo/scripts
$(INSTALL_DIR) $(1)/etc/momo/firewall
$(INSTALL_DIR) $(1)/etc/momo/profiles
$(INSTALL_DIR) $(1)/etc/momo/subscriptions
$(INSTALL_DIR) $(1)/etc/momo/run
$(INSTALL_BIN) $(CURDIR)/files/ucode/include.uc $(1)/etc/momo/ucode/include.uc
$(INSTALL_BIN) $(CURDIR)/files/ucode/hijack.ut $(1)/etc/momo/ucode/hijack.ut
$(INSTALL_BIN) $(CURDIR)/files/scripts/include.sh $(1)/etc/momo/scripts/include.sh
$(INSTALL_BIN) $(CURDIR)/files/scripts/firewall_include.sh $(1)/etc/momo/scripts/firewall_include.sh
$(INSTALL_BIN) $(CURDIR)/files/scripts/debug.sh $(1)/etc/momo/scripts/debug.sh
$(INSTALL_BIN) $(CURDIR)/files/firewall/geoip_cn.nft $(1)/etc/momo/firewall/geoip_cn.nft
$(INSTALL_BIN) $(CURDIR)/files/firewall/geoip6_cn.nft $(1)/etc/momo/firewall/geoip6_cn.nft
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) $(CURDIR)/files/momo.conf $(1)/etc/config/momo
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/momo.init $(1)/etc/init.d/momo
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) $(CURDIR)/files/uci-defaults/firewall.sh $(1)/etc/uci-defaults/99_firewall_momo
$(INSTALL_BIN) $(CURDIR)/files/uci-defaults/migrate.sh $(1)/etc/uci-defaults/99_migrate_momo
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
$(INSTALL_DATA) $(CURDIR)/files/momo.upgrade $(1)/lib/upgrade/keep.d/momo
endef
define Build/Compile
endef
$(eval $(call BuildPackage,momo))
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+110
View File
@@ -0,0 +1,110 @@
config config 'config'
option 'init' '1'
option 'enabled' '0'
option 'profile' 'file:example.json'
option 'start_delay' '0'
option 'scheduled_restart' '0'
option 'cron_expression' '0 3 * * *'
option 'test_profile' '1'
option 'core_only' '0'
config procd 'procd'
option 'fast_reload' '0'
config core 'core'
option 'redirect_inbound_tag' 'redirect-in'
option 'tproxy_inbound_tag' 'tproxy-in'
option 'tun_inbound_tag' 'tun-in'
option 'dns_inbound_tag' 'dns-in'
option 'fake_ip_dns_server_tag' 'fake-ip-dns-server'
config proxy 'proxy'
option 'enabled' '1'
option 'ipv4_dns_hijack' '1'
option 'ipv6_dns_hijack' '1'
option 'ipv4_proxy' '1'
option 'ipv6_proxy' '1'
option 'fake_ip_ping_hijack' '1'
option 'tcp_mode' 'redirect'
option 'udp_mode' 'tun'
option 'router_proxy' '1'
option 'lan_proxy' '1'
list 'lan_inbound_interface' 'lan'
list 'reserved_ip' '0.0.0.0/8'
list 'reserved_ip' '10.0.0.0/8'
list 'reserved_ip' '127.0.0.0/8'
list 'reserved_ip' '100.64.0.0/10'
list 'reserved_ip' '169.254.0.0/16'
list 'reserved_ip' '172.16.0.0/12'
list 'reserved_ip' '192.168.0.0/16'
list 'reserved_ip' '224.0.0.0/4'
list 'reserved_ip' '240.0.0.0/4'
list 'reserved_ip6' '::/128'
list 'reserved_ip6' '::1/128'
list 'reserved_ip6' '::ffff:0:0/96'
list 'reserved_ip6' '100::/64'
list 'reserved_ip6' '64:ff9b::/96'
list 'reserved_ip6' '2001::/32'
list 'reserved_ip6' '2001:10::/28'
list 'reserved_ip6' '2001:20::/28'
list 'reserved_ip6' '2001:db8::/32'
list 'reserved_ip6' '2002::/16'
list 'reserved_ip6' 'fc00::/7'
list 'reserved_ip6' 'fe80::/10'
list 'reserved_ip6' 'ff00::/8'
option 'bypass_china_mainland_ip' '0'
option 'bypass_china_mainland_ip6' '0'
option 'proxy_tcp_dport' '0-65535'
option 'proxy_udp_dport' '0-65535'
list 'bypass_dscp' '4'
option 'tun_timeout' '30'
option 'tun_interval' '1'
config router_access_control
option 'enabled' '1'
list 'user' 'dnsmasq'
list 'user' 'ftp'
list 'user' 'logd'
list 'user' 'nobody'
list 'user' 'ntp'
list 'user' 'ubus'
list 'group' 'dnsmasq'
list 'group' 'ftp'
list 'group' 'logd'
list 'group' 'nogroup'
list 'group' 'ntp'
list 'group' 'ubus'
list 'cgroup' 'services/adguardhome'
list 'cgroup' 'services/aria2'
list 'cgroup' 'services/dnsmasq'
list 'cgroup' 'services/netbird'
list 'cgroup' 'services/qbittorrent'
list 'cgroup' 'services/sysntpd'
list 'cgroup' 'services/tailscale'
list 'cgroup' 'services/zerotier'
option 'dns' '0'
option 'proxy' '0'
config router_access_control
option 'enabled' '1'
option 'dns' '1'
option 'proxy' '1'
config lan_access_control
option 'enabled' '1'
option 'dns' '1'
option 'proxy' '1'
config routing 'routing'
option 'tproxy_fw_mark' '0x80'
option 'tproxy_fw_mask' '0xFF'
option 'tun_fw_mark' '0x81'
option 'tun_fw_mask' '0xFF'
option 'tproxy_rule_pref' '1024'
option 'tun_rule_pref' '1025'
option 'tproxy_route_table' '80'
option 'tun_route_table' '81'
option 'cgroup_id' '0x07250725'
option 'cgroup_name' 'momo'
config placeholder 'placeholder'
+500
View File
@@ -0,0 +1,500 @@
#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1
PROG="/usr/bin/sing-box"
. "$IPKG_INSTROOT/etc/momo/scripts/include.sh"
extra_command 'update_subscription' 'Update subscription by section id'
boot_func() {
# prepare files
prepare_files
# load config
config_load momo
# start delay
local enabled start_delay
config_get_bool enabled "config" "enabled" 0
config_get start_delay "config" "start_delay" 0
if [ "$enabled" = 1 ] && [ "$start_delay" -gt 0 ]; then
log "App" "Start after $start_delay seconds."
sleep "$start_delay"
fi
# start
start
}
boot() {
boot_func &
}
start_service() {
# prepare files
prepare_files
# load config
config_load momo
# check if enabled
local enabled
config_get_bool enabled "config" "enabled" 0
if [ "$enabled" = 0 ]; then
log "App" "Disabled."
log "App" "Exit."
return
fi
# start
log "App" "Enabled."
log "App" "Start."
# check if executable
if [ ! -x $PROG ]; then
log "App" "Core is not executable."
log "App" "Exit."
return
fi
# get config
## app config
local scheduled_restart cron_expression profile test_profile core_only
config_get_bool scheduled_restart "config" "scheduled_restart" 0
config_get cron_expression "config" "cron_expression"
config_get profile "config" "profile"
config_get_bool test_profile "config" "test_profile" 0
config_get_bool core_only "config" "core_only" 0
## procd config
### general
local fast_reload
config_get_bool fast_reload "procd" "fast_reload" 0
### rlimit
local rlimit_address_space_soft rlimit_address_space_hard rlimit_data_soft rlimit_data_hard rlimit_stack_soft rlimit_stack_hard rlimit_nofile_soft rlimit_nofile_hard
config_get rlimit_address_space_soft "procd" "rlimit_address_space_soft"
config_get rlimit_address_space_hard "procd" "rlimit_address_space_hard"
config_get rlimit_data_soft "procd" "rlimit_data_soft"
config_get rlimit_data_hard "procd" "rlimit_data_hard"
config_get rlimit_stack_soft "procd" "rlimit_stack_soft"
config_get rlimit_stack_hard "procd" "rlimit_stack_hard"
config_get rlimit_nofile_soft "procd" "rlimit_nofile_soft" "$(ulimit -Sn)"
config_get rlimit_nofile_hard "procd" "rlimit_nofile_hard" "$(ulimit -Hn)"
## core config
local redirect_inbound_tag tproxy_inbound_tag tun_inbound_tag dns_inbound_tag fake_ip_dns_server_tag
config_get redirect_inbound_tag "core" "redirect_inbound_tag"
config_get tproxy_inbound_tag "core" "tproxy_inbound_tag"
config_get tun_inbound_tag "core" "tun_inbound_tag"
config_get dns_inbound_tag "core" "dns_inbound_tag"
config_get fake_ip_dns_server_tag "core" "fake_ip_dns_server_tag"
## proxy config
local proxy_enabled ipv4_dns_hijack ipv6_dns_hijack tcp_mode udp_mode
config_get_bool proxy_enabled "proxy" "enabled" 0
config_get_bool ipv4_dns_hijack "proxy" "ipv4_dns_hijack" 0
config_get_bool ipv6_dns_hijack "proxy" "ipv6_dns_hijack" 0
config_get tcp_mode "proxy" "tcp_mode"
config_get udp_mode "proxy" "udp_mode"
# get profile
local profile_type; profile_type=$(echo "$profile" | cut -d ':' -f 1)
local profile_id; profile_id=$(echo "$profile" | cut -d ':' -f 2)
if [ "$profile_type" = "file" ]; then
local profile_name; profile_name="$profile_id"
local profile_file; profile_file="$PROFILES_DIR/$profile_name"
log "Profile" "Use file: $profile_name."
if [ ! -f "$profile_file" ]; then
log "Profile" "File not found."
log "App" "Exit."
return
fi
cp -f "$profile_file" "$RUN_PROFILE_PATH"
elif [ "$profile_type" = "subscription" ]; then
local subscription_section; subscription_section="$profile_id"
local subscription_name subscription_prefer
config_get subscription_name "$subscription_section" "name"
config_get subscription_prefer "$subscription_section" "prefer" "remote"
log "Profile" "Use subscription: $subscription_name."
local subscription_file; subscription_file="$SUBSCRIPTIONS_DIR/$subscription_section.json"
if [ "$subscription_prefer" = "remote" ] || [ ! -f "$subscription_file" ]; then
update_subscription "$subscription_section"
fi
if [ ! -f "$subscription_file" ]; then
log "Profile" "Subscription file not found."
log "App" "Exit."
return
fi
cp -f "$subscription_file" "$RUN_PROFILE_PATH"
else
log "Profile" "No profile/subscription selected."
log "App" "Exit."
return
fi
# check profile
if [ "$core_only" = 0 ] && [ "$proxy_enabled" = 1 ]; then
log "Profile" "Checking..."
if [ "$ipv4_dns_hijack" = 1 ] || [ "$ipv6_dns_hijack" = 1 ]; then
local dns_port; dns_port=$(jsonfilter -q -i "$RUN_PROFILE_PATH" -e "@['inbounds'][*]" | jsonfilter -q -a -e "@[@['tag']='$dns_inbound_tag']" | jsonfilter -q -a -e "@[@['type']='direct']" | jsonfilter -q -e "@['listen_port']")
if [ -z "$dns_port" ]; then
log "Profile" "Check failed."
log "Profile" "DNS inbound not found, should be tagged with $dns_inbound_tag and define listen_port."
log "App" "Exit."
return
fi
fi
if [ "$tcp_mode" = "redirect" ]; then
local redirect_port; redirect_port=$(jsonfilter -q -i "$RUN_PROFILE_PATH" -e "@['inbounds'][*]" | jsonfilter -q -a -e "@[@['tag']='$redirect_inbound_tag']" | jsonfilter -q -a -e "@[@['type']='redirect']" | jsonfilter -q -e "@['listen_port']")
if [ -z "$redirect_port" ]; then
log "Profile" "Check failed."
log "Profile" "Redirect inbound not found, should be tagged with $redirect_inbound_tag and define listen_port."
log "App" "Exit."
return
fi
fi
if [ "$tcp_mode" = "tproxy" ] || [ "$udp_mode" = "tproxy" ]; then
local tproxy_port; tproxy_port=$(jsonfilter -q -i "$RUN_PROFILE_PATH" -e "@['inbounds'][*]" | jsonfilter -q -a -e "@[@['tag']='$tproxy_inbound_tag']" | jsonfilter -q -a -e "@[@['type']='tproxy']" | jsonfilter -q -e "@['listen_port']")
if [ -z "$tproxy_port" ]; then
log "Profile" "Check failed."
log "Profile" "TPROXY inbound not found, should be tagged with $tproxy_inbound_tag and define listen_port."
log "App" "Exit."
return
fi
fi
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
local tun_device; tun_device=$(jsonfilter -q -i "$RUN_PROFILE_PATH" -e "@['inbounds'][*]" | jsonfilter -q -a -e "@[@['tag']='$tun_inbound_tag']" | jsonfilter -q -a -e "@[@['type']='tun']" | jsonfilter -q -e "@['interface_name']")
if [ -z "$tun_device" ]; then
log "Profile" "Check failed."
log "Profile" "TUN inbound not found, should be tagged with $tun_inbound_tag and define interface_name."
log "App" "Exit."
return
fi
fi
log "Profile" "Check passed."
fi
# test profile
if [ "$test_profile" = 1 ]; then
log "Profile" "Testing..."
if $PROG -D "$RUN_DIR" check >> "$CORE_LOG_PATH" 2>&1; then
log "Profile" "Test passed."
else
log "Profile" "Test failed."
log "Profile" "Please check the core log to find out the problem."
log "App" "Exit."
return
fi
fi
# start core
log "Core" "Start."
procd_open_instance momo
procd_set_param command /bin/sh -c "$PROG -D $RUN_DIR run --disable-color >> $CORE_LOG_PATH 2>&1"
procd_set_param file "$RUN_PROFILE_PATH"
if [ "$fast_reload" = 1 ]; then
procd_set_param reload_signal HUP
fi
procd_set_param pidfile "$PID_FILE_PATH"
procd_set_param respawn
procd_append_param limits as="${rlimit_address_space_soft:-unlimited} ${rlimit_address_space_hard:-unlimited}"
procd_append_param limits data="${rlimit_data_soft:-unlimited} ${rlimit_data_hard:-unlimited}"
procd_append_param limits stack="${rlimit_stack_soft:-unlimited} ${rlimit_stack_hard:-unlimited}"
procd_append_param limits nofile="${rlimit_nofile_soft} ${rlimit_nofile_hard}"
procd_close_instance
# cron
if [ "$scheduled_restart" = 1 ] && [ -n "$cron_expression" ]; then
log "App" "Set scheduled restart."
echo "$cron_expression /etc/init.d/momo restart #momo" >> "/etc/crontabs/root"
/etc/init.d/cron restart
fi
# set started flag
touch "$STARTED_FLAG_PATH"
}
service_started() {
# check if started
if [ ! -f "$STARTED_FLAG_PATH" ]; then
return
fi
# load config
config_load momo
# check if proxy enabled
local proxy_enabled
config_get_bool proxy_enabled "proxy" "enabled" 0
if [ "$proxy_enabled" = 0 ]; then
log "Proxy" "Disabled."
return
fi
# get config
## app config
local core_only
config_get_bool core_only "config" "core_only" 0
## core
local tun_inbound_tag
config_get tun_inbound_tag "core" "tun_inbound_tag"
## proxy config
### general
local ipv4_proxy ipv6_proxy tcp_mode udp_mode tun_timeout tun_interval
config_get_bool ipv4_proxy "proxy" "ipv4_proxy" 0
config_get_bool ipv6_proxy "proxy" "ipv6_proxy" 0
config_get tcp_mode "proxy" "tcp_mode"
config_get udp_mode "proxy" "udp_mode"
config_get tun_timeout "proxy" "tun_timeout" 30
config_get tun_interval "proxy" "tun_interval" 1
## routing config
local tproxy_fw_mark tproxy_fw_mask tun_fw_mark tun_fw_mask tproxy_rule_pref tun_rule_pref tproxy_route_table tun_route_table cgroup_id cgroup_name
config_get tproxy_fw_mark "routing" "tproxy_fw_mark" "0x80"
config_get tproxy_fw_mask "routing" "tproxy_fw_mask" "0xFF"
config_get tun_fw_mark "routing" "tun_fw_mark" "0x81"
config_get tun_fw_mask "routing" "tun_fw_mask" "0xFF"
config_get tproxy_rule_pref "routing" "tproxy_rule_pref" "1024"
config_get tun_rule_pref "routing" "tun_rule_pref" "1025"
config_get tproxy_route_table "routing" "tproxy_route_table" "80"
config_get tun_route_table "routing" "tun_route_table" "81"
config_get cgroup_id "routing" "cgroup_id" "0x12061206"
config_get cgroup_name "routing" "cgroup_name" "momo"
# prepare config
local tproxy_enable; tproxy_enable=0
if [ "$tcp_mode" = "tproxy" ] || [ "$udp_mode" = "tproxy" ]; then
tproxy_enable=1
fi
local tun_enable; tun_enable=0
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
tun_enable=1
fi
local tun_device; tun_device=$(jsonfilter -q -i "$RUN_PROFILE_PATH" -e "@['inbounds'][*]" | jsonfilter -q -a -e "@[@['tag']='$tun_inbound_tag']" | jsonfilter -q -a -e "@[@['type']='tun']" | jsonfilter -q -e "@['interface_name']")
if [ "$core_only" = 0 ]; then
# proxy
log "Proxy" "Enabled."
# wait for tun device online
if [ "$tun_enable" = 1 ]; then
log "Proxy" "Waiting for tun device online within $tun_timeout seconds..."
while [ "$tun_timeout" -gt 0 ]; do
if ip -j link show dev "$tun_device" 2>/dev/null | jsonfilter -q -e "@[@['flags'][@='UP']]" > /dev/null 2>&1; then
log "Proxy" "TUN device is online."
break
fi
tun_timeout=$((tun_timeout - tun_interval))
sleep "$tun_interval"
done
if [ "$tun_timeout" -le 0 ]; then
log "Proxy" "Timeout, TUN device is not online."
log "App" "Exit."
return
fi
fi
# fix compatible with dockerd
## cgroupfs-mount
### when cgroupfs-mount is installed, cgroupv1 will mounted instead of cgroupv2, we need to create cgroup manually
if mount | grep -q -w "^cgroup"; then
mkdir -p "/sys/fs/cgroup/net_cls/$cgroup_name"
echo "$cgroup_id" > "/sys/fs/cgroup/net_cls/$cgroup_name/net_cls.classid"
cat "$PID_FILE_PATH" > "/sys/fs/cgroup/net_cls/$cgroup_name/cgroup.procs"
fi
## kmod-br-netfilter
### when kmod-br-netfilter is loaded, bridge-nf-call-iptables and bridge-nf-call-ip6tables are set to 1, we need to set them to 0 if tproxy is enabled
if [ "$tproxy_enable" = 1 ] && (lsmod | grep -q br_netfilter); then
if [ "$ipv4_proxy" = 1 ]; then
local bridge_nf_call_iptables; bridge_nf_call_iptables=$(sysctl -e -n net.bridge.bridge-nf-call-iptables)
if [ "$bridge_nf_call_iptables" = 1 ]; then
touch "$BRIDGE_NF_CALL_IPTABLES_FLAG_PATH"
sysctl -q -w net.bridge.bridge-nf-call-iptables=0
fi
fi
if [ "$ipv6_proxy" = 1 ]; then
local bridge_nf_call_ip6tables; bridge_nf_call_ip6tables=$(sysctl -e -n net.bridge.bridge-nf-call-ip6tables)
if [ "$bridge_nf_call_ip6tables" = 1 ]; then
touch "$BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH"
sysctl -q -w net.bridge.bridge-nf-call-ip6tables=0
fi
fi
fi
# ip route and rule
if [ "$tproxy_enable" = 1 ]; then
if [ "$ipv4_proxy" = 1 ]; then
ip -4 route add local default dev lo table "$tproxy_route_table"
ip -4 rule add pref "$tproxy_rule_pref" fwmark "$tproxy_fw_mark/$tproxy_fw_mask" table "$tproxy_route_table"
fi
if [ "$ipv6_proxy" = 1 ]; then
ip -6 route add local default dev lo table "$tproxy_route_table"
ip -6 rule add pref "$tproxy_rule_pref" fwmark "$tproxy_fw_mark/$tproxy_fw_mask" table "$tproxy_route_table"
fi
fi
if [ "$tun_enable" = 1 ]; then
if [ "$ipv4_proxy" = 1 ]; then
ip -4 route add unicast default dev "$tun_device" table "$tun_route_table"
ip -4 rule add pref "$tun_rule_pref" fwmark "$tun_fw_mark/$tun_fw_mask" table "$tun_route_table"
fi
if [ "$ipv6_proxy" = 1 ]; then
ip -6 route add unicast default dev "$tun_device" table "$tun_route_table"
ip -6 rule add pref "$tun_rule_pref" fwmark "$tun_fw_mark/$tun_fw_mask" table "$tun_route_table"
fi
$FIREWALL_INCLUDE_SH
fi
# hijack
utpl -S "$HIJACK_UT" | nft -f -
# check hijack
if nft list tables | grep -q momo; then
log "Proxy" "Hijack successful."
else
log "Proxy" "Hijack failed."
log "App" "Exit."
fi
fi
}
service_stopped() {
cleanup
}
reload_service() {
cleanup
start
}
service_triggers() {
procd_add_reload_trigger "momo"
}
cleanup() {
# clear log
clear_log
# load config
config_load momo
# get config
## routing config
local tproxy_route_table tun_route_table
config_get tproxy_route_table "routing" "tproxy_route_table" "80"
config_get tun_route_table "routing" "tun_route_table" "81"
# delete routing policy
ip -4 rule del table "$tproxy_route_table" > /dev/null 2>&1
ip -4 rule del table "$tun_route_table" > /dev/null 2>&1
ip -6 rule del table "$tproxy_route_table" > /dev/null 2>&1
ip -6 rule del table "$tun_route_table" > /dev/null 2>&1
# delete routing table
ip -4 route flush table "$tproxy_route_table" > /dev/null 2>&1
ip -4 route flush table "$tun_route_table" > /dev/null 2>&1
ip -6 route flush table "$tproxy_route_table" > /dev/null 2>&1
ip -6 route flush table "$tun_route_table" > /dev/null 2>&1
# delete hijack
nft delete table inet momo > /dev/null 2>&1
local handles handle
handles=$(nft --json list table inet fw4 | jsonfilter -q -e "@['nftables'][*]['rule']" | jsonfilter -q -a -e "@[@['chain']='input']" | jsonfilter -q -a -e "@[@['comment']='momo']" | jsonfilter -q -a -e "@[*]['handle']")
for handle in $handles; do
nft delete rule inet fw4 input handle "$handle"
done
handles=$(nft --json list table inet fw4 | jsonfilter -q -e "@['nftables'][*]['rule']" | jsonfilter -q -a -e "@[@['chain']='forward']" | jsonfilter -q -a -e "@[@['comment']='momo']" | jsonfilter -q -a -e "@[*]['handle']")
for handle in $handles; do
nft delete rule inet fw4 forward handle "$handle"
done
# delete started flag
rm "$STARTED_FLAG_PATH" > /dev/null 2>&1
# revert fix compatible with dockerd
## kmod-br-netfilter
if rm "$BRIDGE_NF_CALL_IPTABLES_FLAG_PATH" > /dev/null 2>&1; then
sysctl -q -w net.bridge.bridge-nf-call-iptables=1
fi
if rm "$BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH" > /dev/null 2>&1; then
sysctl -q -w net.bridge.bridge-nf-call-ip6tables=1
fi
# delete cron
sed -i "/#momo/d" "/etc/crontabs/root" > /dev/null 2>&1
/etc/init.d/cron restart
}
update_subscription() {
local subscription_section; subscription_section="$1"
if [ -z "$subscription_section" ]; then
return
fi
# load config
config_load momo
# get subscription config
local subscription_name subscription_info_url subscription_url subscription_user_agent
config_get subscription_name "$subscription_section" "name"
config_get subscription_info_url "$subscription_section" "info_url"
config_get subscription_url "$subscription_section" "url"
config_get subscription_user_agent "$subscription_section" "user_agent"
# reset subscription info
uci_remove "momo" "$subscription_section" "expire" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "upload" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "download" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "total" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "used" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "avaliable" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "update" > /dev/null 2>&1
uci_remove "momo" "$subscription_section" "success" > /dev/null 2>&1
# update subscription
log "Profile" "Update subscription: $subscription_name."
local success
local subscription_info_header_tmpfile; subscription_info_header_tmpfile="$TEMP_DIR/$subscription_section.info.header"
local subscription_header_tmpfile; subscription_header_tmpfile="$TEMP_DIR/$subscription_section.header"
local subscription_tmpfile; subscription_tmpfile="$TEMP_DIR/$subscription_section.json"
local subscription_info_file
local subscription_file; subscription_file="$SUBSCRIPTIONS_DIR/$subscription_section.json"
# fetch subscription info
if [ -n "$subscription_info_url" ]; then
log "Profile" "Fetch subscription info."
if curl -s -f -m 120 --connect-timeout 15 --retry 3 -L -X GET -A "$subscription_user_agent" -D "$subscription_info_header_tmpfile" "$subscription_info_url" > /dev/null 2>&1; then
if [ ! -f "$subscription_info_file" ] && grep -q -i "subscription-userinfo:" "$subscription_info_header_tmpfile"; then
subscription_info_file="$subscription_info_header_tmpfile"
fi
fi
fi
# download subscription
log "Profile" "Download subscription."
if curl -s -f -m 120 --connect-timeout 15 --retry 3 -L -X GET -A "$subscription_user_agent" -D "$subscription_header_tmpfile" -o "$subscription_tmpfile" "$subscription_url" > /dev/null 2>&1; then
log "Profile" "Subscription download successful."
if [ ! -f "$subscription_info_file" ] && grep -q -i "subscription-userinfo:" "$subscription_header_tmpfile"; then
subscription_info_file="$subscription_header_tmpfile"
fi
log "Profile" "Subscription download successful."
success=1
else
log "Profile" "Subscription download failed."
success=0
fi
# check if success
if [ "$success" = 1 ]; then
log "Profile" "Subscription update successful."
if [ -f "$subscription_info_file" ]; then
local subscription_expire subscription_upload subscription_download subscription_total subscription_used subscription_avaliable
subscription_expire=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "expire=[[:digit:]]+" | cut -d '=' -f 2)
subscription_upload=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "upload=[[:digit:]]+" | cut -d '=' -f 2)
subscription_download=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "download=[[:digit:]]+" | cut -d '=' -f 2)
subscription_total=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "total=[[:digit:]]+" | cut -d '=' -f 2)
if [ -n "$subscription_upload" ] && [ -n "$subscription_download" ]; then
subscription_used=$((subscription_upload + subscription_download))
if [ -n "$subscription_total" ]; then
subscription_avaliable=$((subscription_total - subscription_used))
fi
fi
# update subscription info
if [ -n "$subscription_expire" ]; then
uci_set "momo" "$subscription_section" "expire" "$(date "+%Y-%m-%d %H:%M:%S" -d "@$subscription_expire")"
fi
if [ -n "$subscription_upload" ]; then
uci_set "momo" "$subscription_section" "upload" "$(format_filesize "$subscription_upload")"
fi
if [ -n "$subscription_download" ]; then
uci_set "momo" "$subscription_section" "download" "$(format_filesize "$subscription_download")"
fi
if [ -n "$subscription_total" ]; then
uci_set "momo" "$subscription_section" "total" "$(format_filesize "$subscription_total")"
fi
if [ -n "$subscription_used" ]; then
uci_set "momo" "$subscription_section" "used" "$(format_filesize "$subscription_used")"
fi
if [ -n "$subscription_avaliable" ]; then
uci_set "momo" "$subscription_section" "avaliable" "$(format_filesize "$subscription_avaliable")"
fi
fi
uci_set "momo" "$subscription_section" "update" "$(date "+%Y-%m-%d %H:%M:%S")"
uci_set "momo" "$subscription_section" "success" "1"
# update subscription file
rm -f "$subscription_info_header_tmpfile"
rm -f "$subscription_header_tmpfile"
mv -f "$subscription_tmpfile" "$subscription_file"
elif [ "$success" = 0 ]; then
log "Profile" "Subscription update failed."
# update subscription info
uci_set "momo" "$subscription_section" "success" "0"
# remove tmpfile
rm -f "$subscription_info_header_tmpfile"
rm -f "$subscription_header_tmpfile"
rm -f "$subscription_tmpfile"
fi
uci_commit "momo"
}
+2
View File
@@ -0,0 +1,2 @@
/etc/momo/profiles/
/etc/momo/subscriptions/
+343
View File
@@ -0,0 +1,343 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/momo/scripts/include.sh"
enabled=`uci get momo.config.enabled`
if [ "$enabled" == "0" ]; then
uci set momo.config.enabled=1
uci commit momo
/etc/init.d/momo restart
fi
echo \
"
# Momo Debug Info
## system
\`\`\`shell
`
cat /etc/openwrt_release
`
\`\`\`
## kernel
\`\`\`
`
uname -a
`
\`\`\`
## application
\`\`\`
`
if [ -x "/bin/opkg" ]; then
opkg list-installed "momo"
opkg list-installed "luci-app-momo"
elif [ -x "/usr/bin/apk" ]; then
apk list -I "momo"
apk list -I "luci-app-momo"
fi
`
\`\`\`
## config
\`\`\`json
`
ucode -S -e '
import { cursor } from "uci";
const uci = cursor();
const config = uci.get_all("momo");
const result = {};
for (let section_id in config) {
const section = config[section_id];
const section_type = section[".type"];
if (result[section_type] == null) {
result[section_type] = [];
}
push(result[section_type], section);
}
for (let section_type in result) {
for (let section in result[section_type]) {
delete section[".anonymous"];
delete section[".type"];
delete section[".name"];
delete section[".index"];
}
}
if (exists(result, "subscription")) {
for (let x in result["subscription"]) {
if (exists(x, "url")) {
x["url"] = "*";
}
}
}
if (exists(result, "lan_access_control")) {
for (let x in result["lan_access_control"]) {
if (exists(x, "ip")) {
for (let i = 0; i < length(x["ip"]); i++) {
x["ip"][i] = "*";
}
}
if (exists(x, "ip6")) {
for (let i = 0; i < length(x["ip6"]); i++) {
x["ip6"][i] = "*";
}
}
if (exists(x, "mac")) {
for (let i = 0; i < length(x["mac"]); i++) {
x["mac"][i] = "*";
}
}
}
}
delete result["placeholder"];
print(result);
'
`
\`\`\`
## profile
\`\`\`json
`
ucode -S -e '
import { readfile } from "fs";
function desensitize_inbounds(inbounds) {
for (let x in inbounds) {
if (exists(x, "password")) {
x["password"] = "*";
}
if (exists(x, "users")) {
x["users"] = "*";
}
if (exists(x, "obfs")) {
if (exists(x["obfs"], "password")) {
x["obfs"]["password"] = "*";
}
}
if (exists(x, "tls")) {
if (exists(x["tls"], "server_name")) {
x["tls"]["server_name"] = "*";
}
if (exists(x["tls"], "certificate")) {
x["tls"]["certificate"] = "*";
}
if (exists(x["tls"], "key")) {
x["tls"]["key"] = "*";
}
if (exists(x["tls"], "acme")) {
x["tls"]["acme"] = {};
}
if (exists(x["tls"], "reality")) {
if (exists(x["tls"]["reality"], "private_key")) {
x["tls"]["reality"]["private_key"] = "*";
}
if (exists(x["tls"]["reality"], "short_id")) {
x["tls"]["reality"]["short_id"] = "*";
}
}
}
if (exists(x, "transport")) {
if (exists(x["transport"], "host")) {
x["transport"]["host"] = "*";
}
if (exists(x["transport"], "path")) {
x["transport"]["path"] = "*";
}
if (exists(x["transport"], "server_name")) {
x["transport"]["server_name"] = "*";
}
}
}
}
function desensitize_outbounds(outbounds) {
for (let x in outbounds) {
if (exists(x, "server")) {
x["server"] = "*";
}
if (exists(x, "server_port")) {
x["server_port"] = "*";
}
if (exists(x, "server_ports")) {
x["server_port"] = "*";
}
if (exists(x, "sni")) {
x["sni"] = "*";
}
if (exists(x, "ports")) {
x["ports"] = "*";
}
if (exists(x, "port-range")) {
x["port-range"] = "*";
}
if (exists(x, "uuid")) {
x["uuid"] = "*";
}
if (exists(x, "private-key")) {
x["private-key"] = "*";
}
if (exists(x, "public-key")) {
x["public-key"] = "*";
}
if (exists(x, "token")) {
x["token"] = "*";
}
if (exists(x, "user")) {
x["user"] = "*";
}
if (exists(x, "username")) {
x["username"] = "*";
}
if (exists(x, "password")) {
x["password"] = "*";
}
if (exists(x, "obfs")) {
if (exists(x["obfs"], "password")) {
x["obfs"]["password"] = "*";
}
}
if (exists(x, "tls")) {
if (exists(x["tls"], "server_name")) {
x["tls"]["server_name"] = "*";
}
if (exists(x["tls"], "certificate")) {
x["tls"]["certificate"] = "*";
}
if (exists(x["tls"], "reality")) {
if (exists(x["tls"]["reality"], "public_key")) {
x["tls"]["reality"]["public_key"] = "*";
}
if (exists(x["tls"]["reality"], "short_id")) {
x["tls"]["reality"]["short_id"] = "*";
}
}
}
if (exists(x, "transport")) {
if (exists(x["transport"], "host")) {
x["transport"]["host"] = "*";
}
if (exists(x["transport"], "path")) {
x["transport"]["path"] = "*";
}
if (exists(x["transport"], "server_name")) {
x["transport"]["server_name"] = "*";
}
}
}
}
function desensitize_endpoints(endpoints) {
for (let x in endpoints) {
if (exists(x, "private_key")) {
x["private_key"] = "*";
}
if (exists(x, "peers")) {
for (let y in x["peers"]) {
if (exists(y, "address")) {
y["address"] = "*";
}
if (exists(y, "port")) {
y["port"] = "*";
}
if (exists(y, "public_key")) {
y["public_key"] = "*";
}
if (exists(y, "pre_shared_key")) {
y["pre_shared_key"] = "*";
}
}
}
if (exists(x, "auth_key")) {
x["auth_key"] = "*";
}
if (exists(x, "control_url")) {
x["control_url"] = "*";
}
}
}
function desensitize_profile() {
const profile = json(readfile("/etc/momo/run/config.json"));
if (exists(profile, "experimental") && exists(profile["experimental"], "clash_api") && exists(profile["experimental"]["clash_api"], "secret")) {
profile["secret"] = "*";
}
if (exists(profile, "inbounds")) {
desensitize_inbounds(profile["inbounds"]);
}
if (exists(profile, "outbounds")) {
desensitize_outbounds(profile["outbounds"]);
}
if (exists(profile, "endpoints")) {
desensitize_endpoints(profile["endpoints"]);
}
if (exists(profile, "services")) {
profile["services"] = [];
}
return profile;
}
print(desensitize_profile());
'
`
\`\`\`
## ip rule
\`\`\`
`
ip rule list
`
\`\`\`
## ip route
\`\`\`
TPROXY:
`
ip route list table "$(uci get momo.routing.tproxy_route_table)"
`
TUN:
`
ip route list table "$(uci get momo.routing.tun_route_table)"
`
\`\`\`
## ip6 rule
\`\`\`
`
ip -6 rule list
`
\`\`\`
## ip6 route
\`\`\`
TPROXY:
`
ip -6 route list table "$(uci get momo.routing.tproxy_route_table)"
`
TUN:
`
ip -6 route list table "$(uci get momo.routing.tun_route_table)"
`
\`\`\`
## firewall tables
\`\`\`
`
nft list tables
`
\`\`\`
## firewall
\`\`\`
`
nft list table inet momo
`
\`\`\`
## service
\`\`\`json
`
/etc/init.d/momo info
`
\`\`\`
"
if [ "$enabled" == "0" ]; then
uci set momo.config.enabled=0
uci commit momo
/etc/init.d/momo restart
fi
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
. "$IPKG_INSTROOT/lib/functions.sh"
. "$IPKG_INSTROOT/etc/momo/scripts/include.sh"
config_load momo
config_get_bool enabled "config" "enabled" 0
config_get_bool core_only "config" "core_only" 0
config_get_bool proxy_enabled "proxy" "enabled" 0
config_get tcp_mode "proxy" "tcp_mode"
config_get udp_mode "proxy" "udp_mode"
config_get tun_inbound_tag "core" "tun_inbound_tag"
if [ "$enabled" = 1 ] && [ "$core_only" = 0 ] && [ "$proxy_enabled" = 1 ]; then
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
tun_device=$(jsonfilter -q -i "$RUN_PROFILE_PATH" -e "@['inbounds'][*]" | jsonfilter -q -a -e "@[@['tag']='$tun_inbound_tag']" | jsonfilter -q -a -e "@[@['type']='tun']" | jsonfilter -q -e "@['interface_name']")
nft insert rule inet fw4 input iifname "$tun_device" counter accept comment "momo"
nft insert rule inet fw4 forward oifname "$tun_device" counter accept comment "momo"
nft insert rule inet fw4 forward iifname "$tun_device" counter accept comment "momo"
fi
fi
exit 0
+133
View File
@@ -0,0 +1,133 @@
#!/bin/sh
. "$IPKG_INSTROOT/usr/share/libubox/jshn.sh"
# paths
## home
HOME_DIR="/etc/momo"
PROFILES_DIR="$HOME_DIR/profiles"
SUBSCRIPTIONS_DIR="$HOME_DIR/subscriptions"
## run
RUN_DIR="$HOME_DIR/run"
RUN_PROFILE_PATH="$RUN_DIR/config.json"
## ucode
UCODE_DIR="$HOME_DIR/ucode"
INCLUDE_UC="$UCODE_DIR/include.uc"
MIXIN_UC="$UCODE_DIR/mixin.uc"
HIJACK_UT="$UCODE_DIR/hijack.ut"
## scripts
SH_DIR="$HOME_DIR/scripts"
INCLUDE_SH="$SH_DIR/include.sh"
FIREWALL_INCLUDE_SH="$SH_DIR/firewall_include.sh"
DEBUG_SH="$SH_DIR/debug.sh"
## firewall
NFT_DIR="$HOME_DIR/firewall"
GEOIP_CN_NFT="$NFT_DIR/geoip_cn.nft"
GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft"
## log
LOG_DIR="/var/log/momo"
APP_LOG_PATH="$LOG_DIR/app.log"
CORE_LOG_PATH="$LOG_DIR/core.log"
DEBUG_LOG_PATH="$LOG_DIR/debug.log"
## temp
TEMP_DIR="/var/run/momo"
PID_FILE_PATH="$TEMP_DIR/momo.pid"
STARTED_FLAG_PATH="$TEMP_DIR/started.flag"
BRIDGE_NF_CALL_IPTABLES_FLAG_PATH="$TEMP_DIR/bridge_nf_call_iptables.flag"
BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH="$TEMP_DIR/bridge_nf_call_ip6tables.flag"
# functions
get_paths() {
json_init
json_add_string home_dir "$HOME_DIR"
json_add_string profiles_dir "$PROFILES_DIR"
json_add_string subscriptions_dir "$SUBSCRIPTIONS_DIR"
json_add_string run_dir "$RUN_DIR"
json_add_string run_profile_path "$RUN_PROFILE_PATH"
json_add_string ucode_dir "$UCODE_DIR"
json_add_string include_uc "$INCLUDE_UC"
json_add_string hijack_ut "$HIJACK_UT"
json_add_string sh_dir "$SH_DIR"
json_add_string include_sh "$INCLUDE_SH"
json_add_string firewall_include_sh "$FIREWALL_INCLUDE_SH"
json_add_string debug_sh "$DEBUG_SH"
json_add_string nft_dir "$NFT_DIR"
json_add_string geoip_cn_nft "$GEOIP_CN_NFT"
json_add_string geoip6_cn_nft "$GEOIP6_CN_NFT"
json_add_string log_dir "$LOG_DIR"
json_add_string app_log_path "$APP_LOG_PATH"
json_add_string core_log_path "$CORE_LOG_PATH"
json_add_string debug_log_path "$DEBUG_LOG_PATH"
json_add_string temp_dir "$TEMP_DIR"
json_add_string pid_file_path "$PID_FILE_PATH"
json_add_string started_flag_path "$STARTED_FLAG_PATH"
json_add_string bridge_nf_call_iptables_flag_path "$BRIDGE_NF_CALL_IPTABLES_FLAG_PATH"
json_add_string bridge_nf_call_ip6tables_flag_path "$BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH"
json_dump
json_cleanup
}
format_filesize() {
local b; b=1
local kb; kb=$((b * 1024))
local mb; mb=$((kb * 1024))
local gb; gb=$((mb * 1024))
local tb; tb=$((gb * 1024))
local pb; pb=$((tb * 1024))
local size; size=$1
if [ -n "$size" ]; then
if [ "$size" -lt "$kb" ]; then
echo "$(awk "BEGIN {print $size / $b}") B"
elif [ "$size" -lt "$mb" ]; then
echo "$(awk "BEGIN {print $size / $kb}") KB"
elif [ "$size" -lt "$gb" ]; then
echo "$(awk "BEGIN {print $size / $mb}") MB"
elif [ "$size" -lt "$tb" ]; then
echo "$(awk "BEGIN {print $size / $gb}") GB"
elif [ "$size" -lt "$pb" ]; then
echo "$(awk "BEGIN {print $size / $tb}") TB"
else
echo "$(awk "BEGIN {print $size / $pb}") PB"
fi
fi
}
prepare_files() {
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR"
fi
if [ ! -f "$APP_LOG_PATH" ]; then
touch "$APP_LOG_PATH"
fi
if [ ! -f "$CORE_LOG_PATH" ]; then
touch "$CORE_LOG_PATH"
fi
if [ ! -d "$TEMP_DIR" ]; then
mkdir -p "$TEMP_DIR"
fi
}
clear_log() {
echo -n > "$APP_LOG_PATH"
echo -n > "$CORE_LOG_PATH"
}
log() {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] [$1] $2" >> "$APP_LOG_PATH"
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/momo/scripts/include.sh"
uci -q batch <<-EOF > /dev/null
del firewall.momo
set firewall.momo=include
set firewall.momo.type=script
set firewall.momo.path=$FIREWALL_INCLUDE_SH
set firewall.momo.fw4_compatible=1
commit firewall
EOF
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/momo/scripts/include.sh"
# since v1.0.2
section_placeholder=$(uci -q get momo.placeholder); [ -z "$section_placeholder" ] && uci set momo.placeholder="placeholder"
# since v1.1.0
proxy_bypass_china_mainland_ip=$(uci -q get momo.proxy.bypass_china_mainland_ip)
proxy_bypass_china_mainland_ip6=$(uci -q get momo.proxy.bypass_china_mainland_ip6)
[ -z "$proxy_bypass_china_mainland_ip6" ] && uci set momo.proxy.bypass_china_mainland_ip6=$proxy_bypass_china_mainland_ip
routing_tproxy_fw_mask=$(uci -q get momo.routing.tproxy_fw_mask); [ -z "$routing_tproxy_fw_mask" ] && uci set momo.routing.tproxy_fw_mask=0xFF
routing_tun_fw_mask=$(uci -q get momo.routing.tun_fw_mask); [ -z "$routing_tun_fw_mask" ] && uci set momo.routing.tun_fw_mask=0xFF
procd=$(uci -q get momo.procd); [ -z "$procd" ] && {
uci set momo.procd=procd
uci set momo.procd.fast_reload=$(uci -q get momo.config.fast_reload)
uci del momo.config.fast_reload
}
# commit
uci commit momo
# exit with 0
exit 0
+618
View File
@@ -0,0 +1,618 @@
#!/usr/bin/utpl
{%-
'use strict';
import { readfile } from 'fs';
import { cursor } from 'uci';
import { connect } from 'ubus';
import { get_paths, uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups } from '/etc/momo/ucode/include.uc';
const fw4 = require('fw4');
const paths = get_paths();
const cgroups_version = get_cgroups_version();
const users = get_users();
const groups = get_groups();
const cgroups = get_cgroups();
const uci = cursor();
const ubus = connect();
const profile = json(readfile(paths.run_profile_path));
uci.load('momo');
let redir_port;
let tproxy_port;
let tun_device;
let dns_port;
let fake_ip_range;
let fake_ip6_range;
const redirect_inbound_tag = uci.get('momo', 'core', 'redirect_inbound_tag');
const tproxy_inbound_tag = uci.get('momo', 'core', 'tproxy_inbound_tag');
const tun_inbound_tag = uci.get('momo', 'core', 'tun_inbound_tag');
const dns_inbound_tag = uci.get('momo', 'core', 'dns_inbound_tag');
const fake_ip_dns_server_tag = uci.get('momo', 'core', 'fake_ip_dns_server_tag');
let profile_array;
profile_array = filter(profile['inbounds'], (x) => x['tag'] == redirect_inbound_tag);
if (length(profile_array) > 0) {
redir_port = profile_array[0]['listen_port'];
}
profile_array = filter(profile['inbounds'], (x) => x['tag'] == tproxy_inbound_tag);
if (length(profile_array) > 0) {
tproxy_port = profile_array[0]['listen_port'];
}
profile_array = filter(profile['inbounds'], (x) => x['tag'] == tun_inbound_tag);
if (length(profile_array) > 0) {
tun_device = profile_array[0]['interface_name'];
}
profile_array = filter(profile['inbounds'], (x) => x['tag'] == dns_inbound_tag);
if (length(profile_array) > 0) {
dns_port = profile_array[0]['listen_port'];
}
profile_array = filter(profile['dns']['servers'], (x) => x['tag'] == fake_ip_dns_server_tag);
if (length(profile_array) > 0) {
fake_ip_range = profile_array[0]['inet4_range'];
fake_ip6_range = profile_array[0]['inet6_range'];
}
const ipv4_dns_hijack = uci_bool(uci.get('momo', 'proxy', 'ipv4_dns_hijack'));
const ipv6_dns_hijack = uci_bool(uci.get('momo', 'proxy', 'ipv6_dns_hijack'));
const ipv4_proxy = uci_bool(uci.get('momo', 'proxy', 'ipv4_proxy'));
const ipv6_proxy = uci_bool(uci.get('momo', 'proxy', 'ipv6_proxy'));
const fake_ip_ping_hijack = uci_bool(uci.get('momo', 'proxy', 'fake_ip_ping_hijack'));
const tcp_mode = uci.get('momo', 'proxy', 'tcp_mode');
const udp_mode = uci.get('momo', 'proxy', 'udp_mode');
const router_proxy = uci_bool(uci.get('momo', 'proxy', 'router_proxy'));
const router_access_control = [];
uci.foreach('momo', 'router_access_control', (access_control) => {
access_control['enabled'] = uci_bool(access_control['enabled']);
access_control['user'] = filter(uci_array(access_control['user']), (x) => index(users, x) >= 0);
access_control['group'] = filter(uci_array(access_control['group']), (x) => index(groups, x) >= 0);
access_control['cgroup'] = filter(uci_array(access_control['cgroup']), (x) => index(cgroups, x) >= 0);
access_control['proxy'] = uci_bool(access_control['proxy']);
access_control['dns'] = uci_bool(access_control['dns']);
push(router_access_control, access_control);
});
const lan_proxy = uci_bool(uci.get('momo', 'proxy', 'lan_proxy'));
const lan_inbound_interface = uci_array(uci.get('momo', 'proxy', 'lan_inbound_interface'));
const lan_inbound_device = [];
for (let interface in lan_inbound_interface) {
const status = ubus.call('network.interface', 'status', { 'interface': interface });
const device = status?.l3_device ?? status?.device ?? '';
if (device != '') {
push(lan_inbound_device, device);
}
}
const lan_access_control = [];
uci.foreach('momo', 'lan_access_control', (access_control) => {
access_control['enabled'] = uci_bool(access_control['enabled']);
access_control['ip'] = uci_array(access_control['ip']);
access_control['ip6'] = uci_array(access_control['ip6']);
access_control['mac'] = uci_array(access_control['mac']);
access_control['proxy'] = uci_bool(access_control['proxy']);
access_control['dns'] = uci_bool(access_control['dns']);
push(lan_access_control, access_control);
});
const reserved_ip = uci_array(uci.get('momo', 'proxy', 'reserved_ip'));
const reserved_ip6 = uci_array(uci.get('momo', 'proxy', 'reserved_ip6'));
const bypass_china_mainland_ip = uci_bool(uci.get('momo', 'proxy', 'bypass_china_mainland_ip'));
const bypass_china_mainland_ip6 = uci_bool(uci.get('momo', 'proxy', 'bypass_china_mainland_ip6'));
const proxy_tcp_dport = split((uci.get('momo', 'proxy', 'proxy_tcp_dport') ?? '0-65535'), ' ');
const proxy_udp_dport = split((uci.get('momo', 'proxy', 'proxy_udp_dport') ?? '0-65535'), ' ');
const bypass_dscp = uci_array(uci.get('momo', 'proxy', 'bypass_dscp'));
const bypass_fwmark = [];
for (let fwmark in uci_array(uci.get('momo', 'proxy', 'bypass_fwmark'))) {
let mark;
let mask = '0xFFFFFFFF';
if (index(fwmark, '/') >= 0) {
const parts = split(fwmark, '/');
mark = parts[0];
mask = parts[1];
} else {
mark = fwmark;
}
push(bypass_fwmark, { mark: mark, mask: mask });
}
const cgroup_id = uci.get('momo', 'routing', 'cgroup_id') ?? '0x07250725';
const cgroup_name = uci.get('momo', 'routing', 'cgroup_name') ?? 'momo';
const tproxy_fw_mark = uci.get('momo', 'routing', 'tproxy_fw_mark') ?? '0x80';
const tproxy_fw_mask = uci.get('momo', 'routing', 'tproxy_fw_mask') ?? '0xFF';
const tproxy_fw_umask = fw4.hex(~tproxy_fw_mask & 0xFFFFFFFF);
const tun_fw_mark = uci.get('momo', 'routing', 'tun_fw_mark') ?? '0x81';
const tun_fw_mask = uci.get('momo', 'routing', 'tun_fw_mask') ?? '0xFF';
const tun_fw_umask = fw4.hex(~tun_fw_mask & 0xFFFFFFFF);
const dns_hijack_nfproto = [];
if (ipv4_dns_hijack) {
push(dns_hijack_nfproto, 'ipv4');
}
if (ipv6_dns_hijack) {
push(dns_hijack_nfproto, 'ipv6');
}
const proxy_nfproto = [];
if (ipv4_proxy) {
push(proxy_nfproto, 'ipv4');
}
if (ipv6_proxy) {
push(proxy_nfproto, 'ipv6');
}
const proxy_dport = [];
for (let port in proxy_tcp_dport) {
push(proxy_dport, `tcp . ${port}`);
}
for (let port in proxy_udp_dport) {
push(proxy_dport, `udp . ${port}`);
}
-%}
table inet momo {
{% if (length(dns_hijack_nfproto) > 0): %}
set dns_hijack_nfproto {
type nf_proto
flags interval
elements = {
{{ join(', ', dns_hijack_nfproto) }}
}
}
{% endif %}
set proxy_nfproto {
type nf_proto
flags interval
{% if (length(proxy_nfproto) > 0): %}
elements = {
{{ join(', ', proxy_nfproto) }}
}
{% endif %}
}
set reserved_ip {
type ipv4_addr
flags interval
auto-merge
{% if (length(reserved_ip) > 0): %}
elements = {
{{ join(', ', reserved_ip) }}
}
{% endif %}
}
set reserved_ip6 {
type ipv6_addr
flags interval
auto-merge
{% if (length(reserved_ip6) > 0): %}
elements = {
{{ join(', ', reserved_ip6) }}
}
{% endif %}
}
set lan_inbound_device {
type ifname
flags interval
auto-merge
{% if (length(lan_inbound_device) > 0): %}
elements = {
{{ join(', ', map(lan_inbound_device, (x) => `"${x}"`)) }}
}
{% endif %}
}
set china_ip {
type ipv4_addr
flags interval
}
set china_ip6 {
type ipv6_addr
flags interval
}
set proxy_dport {
type inet_proto . inet_service
flags interval
auto-merge
{% if (length(proxy_dport) > 0): %}
elements = {
{{ join(', ', proxy_dport) }}
}
{% endif %}
}
set bypass_dscp {
type dscp
flags interval
auto-merge
{% if (length(bypass_dscp) > 0): %}
elements = {
{{ join(', ', bypass_dscp) }}
}
{% endif %}
}
{% if (router_proxy): %}
{% if (length(dns_hijack_nfproto) > 0): %}
chain router_dns_hijack {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto { tcp, udp } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'redirect'): %}
chain router_redirect {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto tcp counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto tcp meta skuid { {{ join(', ', access_control['user']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto tcp meta skgid { {{ join(', ', access_control['group']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto tcp socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
chain router_tproxy {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
chain router_tun {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endif %}
{% if (lan_proxy): %}
{% if (length(dns_hijack_nfproto) > 0): %}
chain lan_dns_hijack {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto { tcp, udp } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'redirect'): %}
chain lan_redirect {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto tcp counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto tcp ip saddr { {{ join(', ', access_control['ip']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto tcp ip6 saddr { {{ join(', ', access_control['ip6']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto tcp ether saddr { {{ join(', ', access_control['mac']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
chain lan_tproxy {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy ip to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy ip6 to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
chain lan_tun {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endif %}
{% if (router_proxy): %}
chain nat_output {
type nat hook output priority filter; policy accept;
{% if (cgroups_version == 1): %}
meta cgroup {{ cgroup_id }} counter return
{% elif (cgroups_version == 2): %}
socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return
{% endif %}
{% if (length(dns_hijack_nfproto) > 0): %}
meta nfproto @dns_hijack_nfproto jump router_dns_hijack
{% endif %}
{% if (tcp_mode == 'redirect'): %}
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
{% for (let fwmark in bypass_fwmark): %}
meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
{% endfor %}
meta nfproto @proxy_nfproto jump router_redirect
{% endif %}
{% if (fake_ip_ping_hijack): %}
{% if (fake_ip_range): %}
icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect
{% endif %}
{% if (fake_ip6_range): %}
icmpv6 type echo-request ip6 daddr {{ fake_ip6_range }} counter redirect
{% endif %}
{% endif %}
}
chain mangle_output {
type route hook output priority mangle; policy accept;
{% if (cgroups_version == 1): %}
meta cgroup {{ cgroup_id }} counter return
{% elif (cgroups_version == 2): %}
socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return
{% endif %}
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
{% for (let fwmark in bypass_fwmark): %}
meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
{% endfor %}
{% if (length(dns_hijack_nfproto) > 0): %}
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return
{% endif %}
{% if (tcp_mode == 'tproxy'): %}
meta nfproto @proxy_nfproto meta l4proto tcp jump router_tproxy
{% elif (tcp_mode == 'tun'): %}
meta nfproto @proxy_nfproto meta l4proto tcp jump router_tun
{% endif %}
{% if (udp_mode == 'tproxy'): %}
meta nfproto @proxy_nfproto meta l4proto udp jump router_tproxy
{% elif (udp_mode == 'tun'): %}
meta nfproto @proxy_nfproto meta l4proto udp jump router_tun
{% endif %}
}
chain mangle_prerouting_router {
type filter hook prerouting priority mangle - 1; policy accept;
{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
iifname lo meta l4proto { tcp, udp } meta mark & {{ tproxy_fw_mask }} == {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept
{% endif %}
{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
iifname "{{ tun_device }}" meta l4proto { icmp, tcp, udp } counter accept
{% endif %}
}
{% endif %}
{% if (lan_proxy): %}
chain dstnat {
type nat hook prerouting priority dstnat + 1; policy accept;
{% if (length(dns_hijack_nfproto) > 0): %}
iifname @lan_inbound_device meta nfproto @dns_hijack_nfproto jump lan_dns_hijack
{% endif %}
{% if (tcp_mode == 'redirect'): %}
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
{% for (let fwmark in bypass_fwmark): %}
meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
{% endfor %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto jump lan_redirect
{% endif %}
{% if (fake_ip_ping_hijack): %}
{% if (fake_ip_range): %}
icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect
{% endif %}
{% if (fake_ip6_range): %}
icmpv6 type echo-request ip6 daddr {{ fake_ip6_range }} counter redirect
{% endif %}
{% endif %}
}
chain mangle_prerouting_lan {
type filter hook prerouting priority mangle; policy accept;
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
{% for (let fwmark in bypass_fwmark): %}
meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
{% endfor %}
{% if (length(dns_hijack_nfproto) > 0): %}
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return
{% endif %}
{% if (tcp_mode == 'tproxy'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tproxy
{% elif (tcp_mode == 'tun'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tun
{% endif %}
{% if (udp_mode == 'tproxy'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto udp jump lan_tproxy
{% elif (udp_mode == 'tun'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto udp jump lan_tun
{% endif %}
}
{% endif %}
}
{% if (bypass_china_mainland_ip): %}
include "/etc/momo/firewall/geoip_cn.nft"
{% endif %}
{% if (bypass_china_mainland_ip6): %}
include "/etc/momo/firewall/geoip6_cn.nft"
{% endif %}
+123
View File
@@ -0,0 +1,123 @@
import { readfile, popen } from 'fs';
export function get_paths() {
let result = {};
const process = popen('. /etc/momo/scripts/include.sh && get_paths');
if (process) {
result = json(process);
process.close();
}
return result;
};
export function uci_bool(obj) {
return obj == null ? null : obj == '1';
};
export function uci_int(obj) {
return obj == null ? null : int(obj);
};
export function uci_array(obj) {
if (obj == null) {
return [];
}
if (type(obj) == 'array') {
return uniq(obj);
}
return [obj];
};
export function merge(target, ...sources) {
for (let source in sources) {
for (let key in keys(source)) {
const target_value = target[key];
const target_value_type = type(target_value);
const source_value = source[key];
const source_value_type = type(source_value);
if (target_value_type === 'object' && source_value_type === 'object') {
target[key] = merge(target_value, source_value);
} else {
target[key] = source[key];
}
}
}
return target;
};
export function merge_exists(target, ...sources) {
for (let source in sources) {
for (let key in keys(source)) {
if (exists(target, key)) {
const target_value = target[key];
const target_value_type = type(target_value);
const source_value = source[key];
const source_value_type = type(source_value);
if (target_value_type === 'object' && source_value_type === 'object') {
target[key] = merge_exists(target_value, source_value);
} else {
target[key] = source[key];
}
}
}
}
return target;
};
export function trim_all(obj) {
if (obj == null) {
return null;
}
if (type(obj) == 'string') {
if (length(obj) == 0) {
return null;
}
return obj;
}
if (type(obj) == 'array') {
if (length(obj) == 0) {
return null;
}
return obj;
}
if (type(obj) == 'object') {
const obj_keys = keys(obj);
for (let key in obj_keys) {
obj[key] = trim_all(obj[key]);
if (obj[key] == null) {
delete obj[key];
}
}
if (length(keys(obj)) == 0) {
return null;
}
return obj;
}
return obj;
};
export function get_cgroups_version() {
return system('mount | grep -q -w "^cgroup"') == 0 ? 1 : 2;
};
export function get_users() {
return map(split(readfile('/etc/passwd'), '\n'), (x) => split(x, ':')[0]);
};
export function get_groups() {
return map(split(readfile('/etc/group'), '\n'), (x) => split(x, ':')[0]);
};
export function get_cgroups() {
const result = [];
if (get_cgroups_version() == 2) {
const cgroup_path = '/sys/fs/cgroup/';
const process = popen(`find ${cgroup_path} -type d -mindepth 1`);
if (process) {
for (let line = process.read('line'); length(line); line = process.read('line')) {
push(result, substr(trim(line), length(cgroup_path)));
}
}
}
return result;
};
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=nexttrace
PKG_VERSION:=1.5.0
PKG_RELEASE:=1
PKG_VERSION:=1.6.1
PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)?