mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-06 20:14:10 +08:00
🦄 Sync 2026-06-17 03:41:10
This commit is contained in:
1 parent
bf7d8e462c
commit
08963f8537
79 files changed
+1353
-485
No files matched your search
@@ -5,8 +5,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-daede
|
PKG_NAME:=luci-app-daede
|
||||||
PKG_VERSION:=1.12.0
|
PKG_VERSION:=1.13.0
|
||||||
PKG_RELEASE:=11
|
PKG_RELEASE:=12
|
||||||
PKG_MAINTAINER:=kenzok8
|
PKG_MAINTAINER:=kenzok8
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,22 @@ const DATA_PATHS = {
|
|||||||
geosite: '/usr/share/v2ray/geosite.dat'
|
geosite: '/usr/share/v2ray/geosite.dat'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Geo data source presets. `loyalsoldier` keeps both URLs empty so the actual
|
||||||
|
// default lives in one place — update-geo.sh — avoiding UI/script drift.
|
||||||
|
const GEO_PRESETS = {
|
||||||
|
loyalsoldier: { geoip: '', geosite: '' },
|
||||||
|
v2fly: {
|
||||||
|
geoip: 'https://github.com/v2fly/geoip/releases/latest/download/geoip.dat',
|
||||||
|
geosite: 'https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function currentPreset(gi, gs) {
|
||||||
|
if (!gi && !gs) return 'loyalsoldier';
|
||||||
|
if (gi === GEO_PRESETS.v2fly.geoip && gs === GEO_PRESETS.v2fly.geosite) return 'v2fly';
|
||||||
|
return 'custom';
|
||||||
|
}
|
||||||
|
|
||||||
const HEALTH_PATHS = {
|
const HEALTH_PATHS = {
|
||||||
btf: '/sys/kernel/btf/vmlinux',
|
btf: '/sys/kernel/btf/vmlinux',
|
||||||
netns: '/run/netns/daens'
|
netns: '/run/netns/daens'
|
||||||
@@ -36,6 +52,11 @@ const CSS = [
|
|||||||
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
|
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
|
||||||
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
|
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
|
||||||
'.dd-up-btn-primary{border-color:#4aa065;color:#4aa065}',
|
'.dd-up-btn-primary{border-color:#4aa065;color:#4aa065}',
|
||||||
|
'.dd-geo-row{display:grid;grid-template-columns:96px 1fr;gap:10px;align-items:center;font-size:12px;padding:6px 0}',
|
||||||
|
'.dd-geo-row label{opacity:.75;font-weight:600}',
|
||||||
|
'.dd-geo-row input[type=text],.dd-geo-row select{font-size:12px;padding:4px 8px;border:1px solid rgba(128,128,128,.35);border-radius:5px;background:transparent;color:inherit;width:100%}',
|
||||||
|
'.dd-geo-chk{display:flex;align-items:center;gap:6px}',
|
||||||
|
'.dd-geo-actions{margin-top:10px;display:flex;gap:10px;align-items:center}',
|
||||||
'.dd-up-log{margin-top:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px;padding:10px;border:1px solid rgba(128,128,128,.14);border-radius:8px;max-height:200px;overflow:auto;white-space:pre-wrap;word-break:break-all;display:none;background:inherit;color:#4a8c63}',
|
'.dd-up-log{margin-top:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px;padding:10px;border:1px solid rgba(128,128,128,.14);border-radius:8px;max-height:200px;overflow:auto;white-space:pre-wrap;word-break:break-all;display:none;background:inherit;color:#4a8c63}',
|
||||||
'.dd-up-log.show{display:block}',
|
'.dd-up-log.show{display:block}',
|
||||||
'body.dark .dd-up-log,html[data-theme="dark"] .dd-up-log,html[data-bs-theme="dark"] .dd-up-log{color:#a3d9ad}',
|
'body.dark .dd-up-log,html[data-theme="dark"] .dd-up-log,html[data-bs-theme="dark"] .dd-up-log{color:#a3d9ad}',
|
||||||
@@ -102,13 +123,20 @@ function probePkg(pkg) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YYYYMMDD-HHMM stamp for backup filenames
|
||||||
|
function stamp() {
|
||||||
|
const d = new Date(), p = n => (n < 10 ? '0' : '') + n;
|
||||||
|
return '' + d.getFullYear() + p(d.getMonth() + 1) + p(d.getDate()) + '-' + p(d.getHours()) + p(d.getMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
// background apk update so new versions show on next poll
|
// background apk update so new versions show on next poll
|
||||||
fs.exec('/usr/share/luci-app-daede/refresh-index.sh', []).catch(function() {});
|
fs.exec('/usr/share/luci-app-daede/refresh-index.sh', []).catch(function() {});
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
backend.detectBackend(),
|
backend.detectBackend(),
|
||||||
uci.load('daed').catch(function() {})
|
uci.load('daed').catch(function() {}),
|
||||||
|
uci.load('daede').catch(function() {})
|
||||||
]).then(function(r) {
|
]).then(function(r) {
|
||||||
return r[0];
|
return r[0];
|
||||||
});
|
});
|
||||||
@@ -172,6 +200,49 @@ return view.extend({
|
|||||||
return runJob('update-pkg.sh', pkg, btn, '/tmp/luci-app-daede.pkg-' + pkg + '.log');
|
return runJob('update-pkg.sh', pkg, btn, '/tmp/luci-app-daede.pkg-' + pkg + '.log');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// === Config backup (export / import the whole daede config) ===
|
||||||
|
const exportBtn = E('button', { 'class': 'dd-up-btn' }, _('Export'));
|
||||||
|
const importBtn = E('button', { 'class': 'dd-up-btn' }, _('Import'));
|
||||||
|
const fileInput = E('input', { 'type': 'file', 'accept': '.tar.gz,.gz,application/gzip', 'style': 'display:none' });
|
||||||
|
|
||||||
|
exportBtn.addEventListener('click', function() {
|
||||||
|
const orig = exportBtn.textContent;
|
||||||
|
exportBtn.disabled = true; exportBtn.textContent = '...';
|
||||||
|
fs.exec('/usr/share/luci-app-daede/config-backup.sh', ['export']).then(function(res) {
|
||||||
|
if (res.code !== 0 || !res.stdout) {
|
||||||
|
logPane.textContent = String(res.stderr || res.stdout || 'export failed').trim();
|
||||||
|
logPane.classList.add('show');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bin = atob(res.stdout.trim());
|
||||||
|
const arr = new Uint8Array(bin.length);
|
||||||
|
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
||||||
|
const url = URL.createObjectURL(new Blob([arr], { 'type': 'application/gzip' }));
|
||||||
|
const a = E('a', { 'href': url, 'download': 'daede-config-' + stamp() + '.tar.gz' });
|
||||||
|
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||||
|
setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
|
||||||
|
}).catch(function() {}).finally(function() {
|
||||||
|
exportBtn.disabled = false; exportBtn.textContent = orig;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importBtn.addEventListener('click', function() { fileInput.click(); });
|
||||||
|
fileInput.addEventListener('change', function(ev) {
|
||||||
|
const file = ev.target.files && ev.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
if (!confirm(_('Import overwrites the current daede config and restarts the backend. Continue?'))) {
|
||||||
|
fileInput.value = ''; return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
const b64 = String(e.target.result).split(',')[1] || '';
|
||||||
|
fs.write('/tmp/daede-import.b64', b64).then(function() {
|
||||||
|
return runJob('config-backup.sh', 'import', importBtn, '/tmp/luci-app-daede.backup.log');
|
||||||
|
}).catch(function() {}).finally(function() { fileInput.value = ''; });
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
|
||||||
const refresh = function() {
|
const refresh = function() {
|
||||||
const probes = [
|
const probes = [
|
||||||
probeFile(DATA_PATHS.geoip),
|
probeFile(DATA_PATHS.geoip),
|
||||||
@@ -309,16 +380,113 @@ return view.extend({
|
|||||||
poll.add(refresh);
|
poll.add(refresh);
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
|
// === Geo data source (preset / custom URL + auto-update) ===
|
||||||
|
const geoSettings = (function() {
|
||||||
|
const gi0 = uci.get('daede', 'config', 'geoip_url') || '';
|
||||||
|
const gs0 = uci.get('daede', 'config', 'geosite_url') || '';
|
||||||
|
const auto0 = uci.get('daede', 'config', 'geo_auto') === '1';
|
||||||
|
const freq0 = uci.get('daede', 'config', 'geo_auto_freq') || 'daily';
|
||||||
|
const preset0 = currentPreset(gi0, gs0);
|
||||||
|
|
||||||
|
const presetSel = E('select', {}, [
|
||||||
|
E('option', { 'value': 'loyalsoldier' }, 'Loyalsoldier'),
|
||||||
|
E('option', { 'value': 'v2fly' }, 'v2fly'),
|
||||||
|
E('option', { 'value': 'custom' }, _('Custom'))
|
||||||
|
]);
|
||||||
|
presetSel.value = preset0;
|
||||||
|
|
||||||
|
const giInput = E('input', { 'type': 'text', 'placeholder': 'https://…/geoip.dat' });
|
||||||
|
const gsInput = E('input', { 'type': 'text', 'placeholder': 'https://…/geosite.dat' });
|
||||||
|
giInput.value = preset0 === 'custom' ? gi0 : '';
|
||||||
|
gsInput.value = preset0 === 'custom' ? gs0 : '';
|
||||||
|
|
||||||
|
const customRows = E('div', {}, [
|
||||||
|
E('div', { 'class': 'dd-geo-row' }, [ E('label', {}, 'GeoIP URL'), giInput ]),
|
||||||
|
E('div', { 'class': 'dd-geo-row' }, [ E('label', {}, 'GeoSite URL'), gsInput ])
|
||||||
|
]);
|
||||||
|
const syncCustom = function() {
|
||||||
|
customRows.style.display = presetSel.value === 'custom' ? '' : 'none';
|
||||||
|
};
|
||||||
|
presetSel.addEventListener('change', syncCustom);
|
||||||
|
syncCustom();
|
||||||
|
|
||||||
|
const autoCb = E('input', { 'type': 'checkbox' });
|
||||||
|
autoCb.checked = auto0;
|
||||||
|
const freqSel = E('select', {}, [
|
||||||
|
E('option', { 'value': 'daily' }, _('Daily')),
|
||||||
|
E('option', { 'value': 'weekly' }, _('Weekly'))
|
||||||
|
]);
|
||||||
|
freqSel.value = freq0;
|
||||||
|
|
||||||
|
const saveBtn = E('button', { 'class': 'dd-up-btn dd-up-btn-primary' }, _('Save'));
|
||||||
|
saveBtn.addEventListener('click', function() {
|
||||||
|
const p = presetSel.value;
|
||||||
|
let gi = '', gs = '';
|
||||||
|
if (p === 'v2fly') { gi = GEO_PRESETS.v2fly.geoip; gs = GEO_PRESETS.v2fly.geosite; }
|
||||||
|
else if (p === 'custom') { gi = giInput.value.trim(); gs = gsInput.value.trim(); }
|
||||||
|
uci.set('daede', 'config', 'geoip_url', gi);
|
||||||
|
uci.set('daede', 'config', 'geosite_url', gs);
|
||||||
|
uci.set('daede', 'config', 'geo_auto', autoCb.checked ? '1' : '0');
|
||||||
|
uci.set('daede', 'config', 'geo_auto_freq', freqSel.value);
|
||||||
|
const orig = saveBtn.textContent;
|
||||||
|
saveBtn.disabled = true; saveBtn.textContent = '...';
|
||||||
|
uci.save().then(function() { return uci.apply(); }).then(function() {
|
||||||
|
return fs.exec('/usr/share/luci-app-daede/geo-cron.sh', [autoCb.checked ? 'enable' : 'disable']);
|
||||||
|
}).then(function() {
|
||||||
|
logPane.textContent = _('Geo data source saved.');
|
||||||
|
logPane.classList.add('show');
|
||||||
|
ui.changes.init();
|
||||||
|
}).catch(function(e) {
|
||||||
|
logPane.textContent = _('Save failed') + ': ' + (e && e.message ? e.message : e);
|
||||||
|
logPane.classList.add('show');
|
||||||
|
}).finally(function() {
|
||||||
|
saveBtn.disabled = false; saveBtn.textContent = orig;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const adv = E('div', { 'class': 'dd-adv dd-closed' }, [
|
||||||
|
E('div', { 'class': 'dd-adv-bar' }, [
|
||||||
|
E('span', {}, _('Data Source')),
|
||||||
|
E('span', { 'class': 'dd-adv-chevron' }, '›')
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'dd-adv-body' }, [
|
||||||
|
E('div', { 'class': 'dd-geo-row' }, [ E('label', {}, _('Source')), presetSel ]),
|
||||||
|
customRows,
|
||||||
|
E('div', { 'class': 'dd-geo-row' }, [
|
||||||
|
E('label', {}, _('Auto-update')),
|
||||||
|
E('div', { 'class': 'dd-geo-chk' }, [ autoCb, freqSel ])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'dd-geo-actions' }, [ saveBtn ])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
adv.firstChild.addEventListener('click', function() {
|
||||||
|
adv.classList.toggle('dd-closed');
|
||||||
|
});
|
||||||
|
return adv;
|
||||||
|
})();
|
||||||
|
|
||||||
return E('div', { 'class': 'dd-up-wrap' }, [
|
return E('div', { 'class': 'dd-up-wrap' }, [
|
||||||
E('style', {}, CSS),
|
E('style', {}, CSS),
|
||||||
E('div', { 'class': 'dd-card' }, [
|
E('div', { 'class': 'dd-card' }, [
|
||||||
E('h4', { 'class': 'dd-card-title' }, _('Data Updates')),
|
E('h4', { 'class': 'dd-card-title' }, _('Data Updates')),
|
||||||
dataBody
|
dataBody,
|
||||||
|
geoSettings
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'dd-card' }, [
|
E('div', { 'class': 'dd-card' }, [
|
||||||
E('h4', { 'class': 'dd-card-title' }, _('Package Updates')),
|
E('h4', { 'class': 'dd-card-title' }, _('Package Updates')),
|
||||||
pkgBody
|
pkgBody
|
||||||
]),
|
]),
|
||||||
|
E('div', { 'class': 'dd-card' }, [
|
||||||
|
E('h4', { 'class': 'dd-card-title' }, _('Config Backup')),
|
||||||
|
E('div', { 'class': 'dd-up-row' }, [
|
||||||
|
E('span', { 'class': 'dd-up-icon dd-up-new' }, '⤓'),
|
||||||
|
E('span', { 'class': 'dd-up-name' }, _('dae + daed')),
|
||||||
|
E('span', { 'class': 'dd-up-meta' }, _('Back up / restore the whole daede config (kernels excluded)')),
|
||||||
|
exportBtn,
|
||||||
|
importBtn
|
||||||
|
]),
|
||||||
|
fileInput
|
||||||
|
]),
|
||||||
(function() {
|
(function() {
|
||||||
const adv = E('div', { 'class': 'dd-adv dd-closed' }, [
|
const adv = E('div', { 'class': 'dd-adv dd-closed' }, [
|
||||||
E('div', { 'class': 'dd-adv-bar' }, [
|
E('div', { 'class': 'dd-adv-bar' }, [
|
||||||
|
|||||||
@@ -738,3 +738,56 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Node group \"%s\" is available on %s. Select nodes above to update it."
|
msgid "Node group \"%s\" is available on %s. Select nodes above to update it."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Config Backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Export"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Import"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "dae + daed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Back up / restore the whole daede config (kernels excluded)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Import overwrites the current daede config and restarts the backend. Continue?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Data Source"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Auto-update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Daily"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Weekly"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Save"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Geo data source saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Save failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "latest version unknown"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Delete the daens netns?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"%s is running. Deleting the daens netns now will break its networking until "
|
||||||
|
"you restart it. Continue?"
|
||||||
|
msgstr ""
|
||||||
@@ -946,3 +946,53 @@ msgstr "将所选节点作为一个命名节点组导入,避免机场节点较
|
|||||||
|
|
||||||
msgid "Node group \"%s\" is available on %s. Select nodes above to update it."
|
msgid "Node group \"%s\" is available on %s. Select nodes above to update it."
|
||||||
msgstr "节点组“%s”已导入 %s。重新选择上方节点即可更新此组。"
|
msgstr "节点组“%s”已导入 %s。重新选择上方节点即可更新此组。"
|
||||||
|
|
||||||
|
msgid "Config Backup"
|
||||||
|
msgstr "配置备份"
|
||||||
|
|
||||||
|
msgid "Export"
|
||||||
|
msgstr "导出"
|
||||||
|
|
||||||
|
msgid "Import"
|
||||||
|
msgstr "导入"
|
||||||
|
|
||||||
|
msgid "Back up / restore the whole daede config (kernels excluded)"
|
||||||
|
msgstr "备份 / 还原整个 daede 配置(不含内核)"
|
||||||
|
|
||||||
|
msgid "Import overwrites the current daede config and restarts the backend. Continue?"
|
||||||
|
msgstr "导入会覆盖当前 daede 配置并重启后端,确定继续?"
|
||||||
|
|
||||||
|
msgid "Data Source"
|
||||||
|
msgstr "数据源"
|
||||||
|
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "自定义"
|
||||||
|
|
||||||
|
msgid "Auto-update"
|
||||||
|
msgstr "自动更新"
|
||||||
|
|
||||||
|
msgid "Daily"
|
||||||
|
msgstr "每天"
|
||||||
|
|
||||||
|
msgid "Weekly"
|
||||||
|
msgstr "每周"
|
||||||
|
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "保存"
|
||||||
|
|
||||||
|
msgid "Geo data source saved."
|
||||||
|
msgstr "数据源已保存。"
|
||||||
|
|
||||||
|
msgid "Save failed"
|
||||||
|
msgstr "保存失败"
|
||||||
|
|
||||||
|
msgid "latest version unknown"
|
||||||
|
msgstr "最新版本未知"
|
||||||
|
|
||||||
|
msgid "Delete the daens netns?"
|
||||||
|
msgstr "删除 daens netns?"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"%s is running. Deleting the daens netns now will break its networking until "
|
||||||
|
"you restart it. Continue?"
|
||||||
|
msgstr "%s 正在运行。现在删除 daens netns 会中断其网络,直到重启它。确定继续?"
|
||||||
@@ -946,3 +946,53 @@ msgstr "将所选节点作为一个命名节点组导入,避免机场节点较
|
|||||||
|
|
||||||
msgid "Node group \"%s\" is available on %s. Select nodes above to update it."
|
msgid "Node group \"%s\" is available on %s. Select nodes above to update it."
|
||||||
msgstr "节点组“%s”已导入 %s。重新选择上方节点即可更新此组。"
|
msgstr "节点组“%s”已导入 %s。重新选择上方节点即可更新此组。"
|
||||||
|
|
||||||
|
msgid "Config Backup"
|
||||||
|
msgstr "配置备份"
|
||||||
|
|
||||||
|
msgid "Export"
|
||||||
|
msgstr "导出"
|
||||||
|
|
||||||
|
msgid "Import"
|
||||||
|
msgstr "导入"
|
||||||
|
|
||||||
|
msgid "Back up / restore the whole daede config (kernels excluded)"
|
||||||
|
msgstr "备份 / 还原整个 daede 配置(不含内核)"
|
||||||
|
|
||||||
|
msgid "Import overwrites the current daede config and restarts the backend. Continue?"
|
||||||
|
msgstr "导入会覆盖当前 daede 配置并重启后端,确定继续?"
|
||||||
|
|
||||||
|
msgid "Data Source"
|
||||||
|
msgstr "数据源"
|
||||||
|
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "自定义"
|
||||||
|
|
||||||
|
msgid "Auto-update"
|
||||||
|
msgstr "自动更新"
|
||||||
|
|
||||||
|
msgid "Daily"
|
||||||
|
msgstr "每天"
|
||||||
|
|
||||||
|
msgid "Weekly"
|
||||||
|
msgstr "每周"
|
||||||
|
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "保存"
|
||||||
|
|
||||||
|
msgid "Geo data source saved."
|
||||||
|
msgstr "数据源已保存。"
|
||||||
|
|
||||||
|
msgid "Save failed"
|
||||||
|
msgstr "保存失败"
|
||||||
|
|
||||||
|
msgid "latest version unknown"
|
||||||
|
msgstr "最新版本未知"
|
||||||
|
|
||||||
|
msgid "Delete the daens netns?"
|
||||||
|
msgstr "删除 daens netns?"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"%s is running. Deleting the daens netns now will break its networking until "
|
||||||
|
"you restart it. Continue?"
|
||||||
|
msgstr "%s 正在运行。现在删除 daens netns 会中断其网络,直到重启它。确定继续?"
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# config-backup.sh export|import — back up/restore the whole daede config
|
||||||
|
# (dae + daed + active backend). export: prints base64(tar.gz) to stdout.
|
||||||
|
# import: decodes /tmp/daede-import.b64, validates, restores, restarts backend.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ACTION="${1:-}"
|
||||||
|
LOG="/tmp/luci-app-daede.backup.log"
|
||||||
|
IMPORT_B64="/tmp/daede-import.b64"
|
||||||
|
MAX_TAR=184320 # 180 KiB tar.gz -> ~240 KiB base64, under the ubus limit
|
||||||
|
|
||||||
|
# relative paths (no leading /) so tar entries match the extraction whitelist
|
||||||
|
WHITELIST="etc/config/dae etc/config/daed etc/config/daede etc/dae/config.dae etc/daed/wing.db"
|
||||||
|
|
||||||
|
b64enc() { ucode -e 'let f=require("fs"); print(b64enc(f.open(ARGV[0],"r").read("all")));' -- "$1"; }
|
||||||
|
b64dec() { ucode -e 'let f=require("fs"); let r=b64dec(trim(f.open(ARGV[0],"r").read("all"))); if(!r)exit(1); let o=f.open(ARGV[1],"w"); o.write(r); o.close();' -- "$1" "$2"; }
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
export)
|
||||||
|
exist=""
|
||||||
|
for p in $WHITELIST; do [ -e "/$p" ] && exist="$exist $p"; done
|
||||||
|
[ -n "$exist" ] || { echo "no config to back up" >&2; exit 1; }
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
( cd / && tar -czf "$tmp" $exist ) 2>/dev/null || { rm -f "$tmp"; echo "tar failed" >&2; exit 1; }
|
||||||
|
if [ "$(wc -c < "$tmp")" -gt "$MAX_TAR" ]; then
|
||||||
|
rm -f "$tmp"; echo "config too large; use System Backup instead" >&2; exit 2
|
||||||
|
fi
|
||||||
|
b64enc "$tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
;;
|
||||||
|
|
||||||
|
import)
|
||||||
|
[ -f "$IMPORT_B64" ] || { echo "no upload found" >&2; exit 1; }
|
||||||
|
(
|
||||||
|
exec >"$LOG" 2>&1
|
||||||
|
echo "$(date '+%F %T') begin import"
|
||||||
|
rc=0
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if ! b64dec "$IMPORT_B64" "$tmp"; then
|
||||||
|
echo "decode failed"; rc=1
|
||||||
|
elif ! gzip -t "$tmp" 2>/dev/null; then
|
||||||
|
echo "not a valid backup archive"; rc=1
|
||||||
|
else
|
||||||
|
# reject any entry outside the whitelist (path-traversal guard)
|
||||||
|
bad="$(tar -tzf "$tmp" 2>/dev/null | grep -vxE 'etc/config/dae|etc/config/daed|etc/config/daede|etc/dae/config\.dae|etc/daed/wing\.db' | head -1)"
|
||||||
|
if [ -n "$bad" ]; then
|
||||||
|
echo "rejected: unexpected entry '$bad'"; rc=1
|
||||||
|
else
|
||||||
|
ab="$(uci -q get daede.config.active_backend || echo dae)"
|
||||||
|
[ -x "/etc/init.d/$ab" ] && /etc/init.d/"$ab" stop 2>/dev/null || true
|
||||||
|
if ( cd / && tar -xzf "$tmp" ); then
|
||||||
|
echo "restored config"
|
||||||
|
else
|
||||||
|
echo "extract failed"; rc=1
|
||||||
|
fi
|
||||||
|
ab="$(uci -q get daede.config.active_backend || echo "$ab")"
|
||||||
|
[ -x "/etc/init.d/$ab" ] && { /etc/init.d/"$ab" enabled || /etc/init.d/"$ab" enable; /etc/init.d/"$ab" restart 2>/dev/null || /etc/init.d/"$ab" start 2>/dev/null; } || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$tmp" "$IMPORT_B64"
|
||||||
|
if [ "$rc" = 0 ]; then echo "result: config restored, backend restarted"; else echo "result: import failed"; fi
|
||||||
|
echo "$(date '+%F %T') done (rc=$rc)"
|
||||||
|
) </dev/null >/dev/null 2>&1 &
|
||||||
|
echo "started in background, see $LOG"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "usage: $0 export|import" >&2
|
||||||
|
exit 64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# geo-cron.sh enable|disable
|
||||||
|
# Manage the geo auto-update crontab entry. enable reads the schedule from
|
||||||
|
# daede.config.geo_auto_freq (daily|weekly); both actions are idempotent.
|
||||||
|
|
||||||
|
ACTION="$1"
|
||||||
|
CRONTAB="/etc/crontabs/root"
|
||||||
|
TAG="# luci-app-daede geo-update"
|
||||||
|
SCRIPT="/usr/share/luci-app-daede/update-geo.sh"
|
||||||
|
|
||||||
|
# Drop any previous daede geo entry (tag line + its command line).
|
||||||
|
clean() {
|
||||||
|
[ -f "$CRONTAB" ] || return 0
|
||||||
|
grep -vF -e "$TAG" -e "$SCRIPT" "$CRONTAB" > "$CRONTAB.tmp" 2>/dev/null
|
||||||
|
mv "$CRONTAB.tmp" "$CRONTAB"
|
||||||
|
}
|
||||||
|
|
||||||
|
clean
|
||||||
|
|
||||||
|
if [ "$ACTION" = "enable" ]; then
|
||||||
|
case "$(uci -q get daede.config.geo_auto_freq)" in
|
||||||
|
weekly) sched="17 4 * * 0" ;; # Sunday 04:17
|
||||||
|
*) sched="17 4 * * *" ;; # every day 04:17
|
||||||
|
esac
|
||||||
|
mkdir -p /etc/crontabs
|
||||||
|
{
|
||||||
|
echo "$TAG"
|
||||||
|
echo "$sched $SCRIPT geoip; $SCRIPT geosite"
|
||||||
|
} >> "$CRONTAB"
|
||||||
|
/etc/init.d/cron enable >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
/etc/init.d/cron restart >/dev/null 2>&1
|
||||||
|
exit 0
|
||||||
@@ -5,13 +5,18 @@
|
|||||||
# in /tmp prevents concurrent runs.
|
# in /tmp prevents concurrent runs.
|
||||||
|
|
||||||
TYPE="$1"
|
TYPE="$1"
|
||||||
|
# Default source: Loyalsoldier/v2ray-rules-dat — daily-built, China-optimized,
|
||||||
|
# ships both geoip.dat and geosite.dat. Users can override per-type via
|
||||||
|
# daede.config.geoip_url / geosite_url (empty falls back to the default below).
|
||||||
case "$TYPE" in
|
case "$TYPE" in
|
||||||
geoip)
|
geoip)
|
||||||
URL="https://github.com/v2fly/geoip/releases/latest/download/geoip.dat"
|
DEF_URL="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
|
URL="$(uci -q get daede.config.geoip_url)"
|
||||||
DEST="/usr/share/v2ray/geoip.dat"
|
DEST="/usr/share/v2ray/geoip.dat"
|
||||||
;;
|
;;
|
||||||
geosite)
|
geosite)
|
||||||
URL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"
|
DEF_URL="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
|
URL="$(uci -q get daede.config.geosite_url)"
|
||||||
DEST="/usr/share/v2ray/geosite.dat"
|
DEST="/usr/share/v2ray/geosite.dat"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -19,6 +24,7 @@ case "$TYPE" in
|
|||||||
exit 64
|
exit 64
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
[ -n "$URL" ] || URL="$DEF_URL"
|
||||||
|
|
||||||
LOCK="/tmp/luci-app-daede.${TYPE}.lock"
|
LOCK="/tmp/luci-app-daede.${TYPE}.lock"
|
||||||
LOG="/tmp/luci-app-daede.${TYPE}.log"
|
LOG="/tmp/luci-app-daede.${TYPE}.log"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"/tmp/luci-app-daede.pkg-dae.log": [ "read" ],
|
"/tmp/luci-app-daede.pkg-dae.log": [ "read" ],
|
||||||
"/tmp/luci-app-daede.pkg-daed.log": [ "read" ],
|
"/tmp/luci-app-daede.pkg-daed.log": [ "read" ],
|
||||||
"/tmp/luci-app-daede.pkg-luci-app-daede.log": [ "read" ],
|
"/tmp/luci-app-daede.pkg-luci-app-daede.log": [ "read" ],
|
||||||
|
"/tmp/luci-app-daede.backup.log": [ "read" ],
|
||||||
"/bin/pidof dae": [ "exec" ],
|
"/bin/pidof dae": [ "exec" ],
|
||||||
"/bin/pidof daed": [ "exec" ],
|
"/bin/pidof daed": [ "exec" ],
|
||||||
"/usr/bin/dae validate -c /tmp/dae-validate.dae": [ "exec" ],
|
"/usr/bin/dae validate -c /tmp/dae-validate.dae": [ "exec" ],
|
||||||
@@ -28,7 +29,8 @@
|
|||||||
"/usr/share/luci-app-daede/pkg-info.sh daed": [ "exec" ],
|
"/usr/share/luci-app-daede/pkg-info.sh daed": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/pkg-info.sh luci-app-daede": [ "exec" ],
|
"/usr/share/luci-app-daede/pkg-info.sh luci-app-daede": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/refresh-index.sh": [ "exec" ],
|
"/usr/share/luci-app-daede/refresh-index.sh": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/proxy-check.sh": [ "exec" ]
|
"/usr/share/luci-app-daede/proxy-check.sh": [ "exec" ],
|
||||||
|
"/usr/share/luci-app-daede/config-backup.sh export": [ "exec" ]
|
||||||
},
|
},
|
||||||
"ubus": {
|
"ubus": {
|
||||||
"service": [ "list" ]
|
"service": [ "list" ]
|
||||||
@@ -39,6 +41,8 @@
|
|||||||
"file": {
|
"file": {
|
||||||
"/etc/dae/config.dae": [ "write" ],
|
"/etc/dae/config.dae": [ "write" ],
|
||||||
"/tmp/dae-validate.dae": [ "write" ],
|
"/tmp/dae-validate.dae": [ "write" ],
|
||||||
|
"/tmp/daede-import.b64": [ "write" ],
|
||||||
|
"/usr/share/luci-app-daede/config-backup.sh import": [ "exec" ],
|
||||||
"/etc/config/daede": [ "write" ],
|
"/etc/config/daede": [ "write" ],
|
||||||
"/var/log/dae/dae.log": [ "write" ],
|
"/var/log/dae/dae.log": [ "write" ],
|
||||||
"/var/log/daed/daed.log": [ "write" ],
|
"/var/log/daed/daed.log": [ "write" ],
|
||||||
@@ -63,6 +67,8 @@
|
|||||||
"/sbin/ip netns del daens": [ "exec" ],
|
"/sbin/ip netns del daens": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/update-geo.sh geoip": [ "exec" ],
|
"/usr/share/luci-app-daede/update-geo.sh geoip": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/update-geo.sh geosite": [ "exec" ],
|
"/usr/share/luci-app-daede/update-geo.sh geosite": [ "exec" ],
|
||||||
|
"/usr/share/luci-app-daede/geo-cron.sh enable": [ "exec" ],
|
||||||
|
"/usr/share/luci-app-daede/geo-cron.sh disable": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/update-pkg.sh dae": [ "exec" ],
|
"/usr/share/luci-app-daede/update-pkg.sh dae": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/update-pkg.sh daed": [ "exec" ],
|
"/usr/share/luci-app-daede/update-pkg.sh daed": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/update-pkg.sh luci-app-daede": [ "exec" ],
|
"/usr/share/luci-app-daede/update-pkg.sh luci-app-daede": [ "exec" ],
|
||||||
|
|||||||
@@ -893,17 +893,12 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
// @ 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, fingerprint, certificate, private-key, alpn)
|
// @ 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, fingerprint, certificate, private-key, alpn)
|
||||||
|
|
||||||
/* TLS fields */
|
/* TLS fields */
|
||||||
o = s.taboption('field_general', form.Flag, 'allow_insecure', _('Allow insecure connections'),
|
|
||||||
_('Only applicable when %s are used as a frontend.').format('nginx/caddy'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.depends({type: /^(vless|trojan|anytls)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'tls', _('TLS'));
|
o = s.taboption('field_general', form.Flag, 'tls', _('TLS'));
|
||||||
o.default = o.disabled;
|
o.default = o.disabled;
|
||||||
o.validate = function(section_id, value) {
|
o.validate = function(section_id, value) {
|
||||||
const type = this.section.getOption('type').formvalue(section_id);
|
const type = this.section.getOption('type').formvalue(section_id);
|
||||||
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
|
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
|
||||||
|
let allow_insecure = this.section.getUIElement(section_id, 'allow_insecure').node.querySelector('input');
|
||||||
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
|
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
|
||||||
let tls_reality = this.section.getUIElement(section_id, 'tls_reality').node.querySelector('input');
|
let tls_reality = this.section.getUIElement(section_id, 'tls_reality').node.querySelector('input');
|
||||||
|
|
||||||
@@ -935,6 +930,12 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Force disabled
|
// Force disabled
|
||||||
|
if (!['vless', 'trojan', 'anytls'].includes(type)) {
|
||||||
|
allow_insecure.checked = false;
|
||||||
|
} else if (allow_insecure.checked) {
|
||||||
|
tls.checked = false;
|
||||||
|
tls.disabled = true;
|
||||||
|
}
|
||||||
if (['trusttunnel'].includes(type)) {
|
if (['trusttunnel'].includes(type)) {
|
||||||
tls_alpn.node.querySelector('input').disabled = true;
|
tls_alpn.node.querySelector('input').disabled = true;
|
||||||
tls_alpn.setValue('');
|
tls_alpn.setValue('');
|
||||||
@@ -950,7 +951,13 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
o.depends({allow_insecure: '0', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
|
o.depends({type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
|
||||||
|
o.modalonly = true;
|
||||||
|
|
||||||
|
o = s.taboption('field_general', form.Flag, 'allow_insecure', _('Allow insecure connections'),
|
||||||
|
_('Only applicable when %s are used as a frontend.').format('nginx/caddy'));
|
||||||
|
o.default = o.disabled;
|
||||||
|
o.depends({type: /^(vless|trojan|anytls)$/});
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
o = s.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ return view.extend({
|
|||||||
render(data) {
|
render(data) {
|
||||||
let m, s, o, ss, so;
|
let m, s, o, ss, so;
|
||||||
|
|
||||||
m = new form.Map('fchomo', _('Edit node'));
|
m = new form.Map('fchomo', _('Edit outbound'));
|
||||||
|
|
||||||
s = m.section(form.NamedSection, 'global', 'fchomo');
|
s = m.section(form.NamedSection, 'global', 'fchomo');
|
||||||
|
|
||||||
@@ -1050,7 +1050,7 @@ return view.extend({
|
|||||||
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
||||||
_('Used to verify the hostname on the returned certificates.'));
|
_('Used to verify the hostname on the returned certificates.'));
|
||||||
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|trusttunnel)$/});
|
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|trusttunnel)$/});
|
||||||
so.depends({tls: '1', tls_disable_sni: '0', type: /^(tuic)$/});
|
so.depends({tls: '1', type: /^(tuic)$/, tls_disable_sni: '0'});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
so = ss.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
||||||
|
|||||||
@@ -805,6 +805,10 @@ msgstr ""
|
|||||||
msgid "Edit node"
|
msgid "Edit node"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: htdocs/luci-static/resources/view/fchomo/node.js:214
|
||||||
|
msgid "Edit outbound"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
||||||
msgid "Edit ruleset"
|
msgid "Edit ruleset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -798,7 +798,7 @@ msgstr "强制覆盖 ECS"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/client.js:1605
|
#: htdocs/luci-static/resources/view/fchomo/client.js:1605
|
||||||
msgid "EDNS Client Subnet"
|
msgid "EDNS Client Subnet"
|
||||||
msgstr ""
|
msgstr "EDNS 客户端子网"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/global.js:433
|
#: htdocs/luci-static/resources/view/fchomo/global.js:433
|
||||||
msgid "ETag support"
|
msgid "ETag support"
|
||||||
@@ -820,6 +820,10 @@ msgstr "编辑入站"
|
|||||||
msgid "Edit node"
|
msgid "Edit node"
|
||||||
msgstr "编辑节点"
|
msgstr "编辑节点"
|
||||||
|
|
||||||
|
#: htdocs/luci-static/resources/view/fchomo/node.js:214
|
||||||
|
msgid "Edit outbound"
|
||||||
|
msgstr "编辑出站"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
||||||
msgid "Edit ruleset"
|
msgid "Edit ruleset"
|
||||||
msgstr "编辑规则集"
|
msgstr "编辑规则集"
|
||||||
@@ -1104,7 +1108,7 @@ msgstr "兜底 DNS 服务器 (用于已被投毒污染的域名)"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:137
|
#: htdocs/luci-static/resources/fchomo/listeners.js:137
|
||||||
msgid "Firewall"
|
msgid "Firewall"
|
||||||
msgstr ""
|
msgstr "防火墙"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:535
|
#: htdocs/luci-static/resources/fchomo/listeners.js:535
|
||||||
#: htdocs/luci-static/resources/view/fchomo/node.js:660
|
#: htdocs/luci-static/resources/view/fchomo/node.js:660
|
||||||
@@ -1335,7 +1339,7 @@ msgstr "握手超时"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:244
|
#: htdocs/luci-static/resources/fchomo/listeners.js:244
|
||||||
msgid "Header to read real client IP from (e.g. X-Forwarded-For)"
|
msgid "Header to read real client IP from (e.g. X-Forwarded-For)"
|
||||||
msgstr ""
|
msgstr "用于读取客户端真实 IP 的 Header(例如 X-Forwarded-For)"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/node.js:751
|
#: htdocs/luci-static/resources/view/fchomo/node.js:751
|
||||||
msgid "Health check"
|
msgid "Health check"
|
||||||
@@ -1525,7 +1529,7 @@ msgstr "单位为毫秒。留空则使用 <code>%s</code>。"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1350
|
#: htdocs/luci-static/resources/view/fchomo/node.js:1350
|
||||||
msgid "In milliseconds."
|
msgid "In milliseconds."
|
||||||
msgstr ""
|
msgstr "单位为毫秒。"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:418
|
#: htdocs/luci-static/resources/fchomo/listeners.js:418
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:487
|
#: htdocs/luci-static/resources/fchomo/listeners.js:487
|
||||||
@@ -3882,7 +3886,7 @@ msgstr "包含 %d 个字符的有效 base64 密钥"
|
|||||||
#: htdocs/luci-static/resources/fchomo.js:1579
|
#: htdocs/luci-static/resources/fchomo.js:1579
|
||||||
#: htdocs/luci-static/resources/fchomo.js:1585
|
#: htdocs/luci-static/resources/fchomo.js:1585
|
||||||
msgid "valid format: 2x, 2p, 4v"
|
msgid "valid format: 2x, 2p, 4v"
|
||||||
msgstr ""
|
msgstr "有效格式: 2x, 2p, 4v"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo.js:1566
|
#: htdocs/luci-static/resources/fchomo.js:1566
|
||||||
msgid "valid key length with %d characters"
|
msgid "valid key length with %d characters"
|
||||||
|
|||||||
@@ -798,7 +798,7 @@ msgstr "強制覆蓋 ECS"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/client.js:1605
|
#: htdocs/luci-static/resources/view/fchomo/client.js:1605
|
||||||
msgid "EDNS Client Subnet"
|
msgid "EDNS Client Subnet"
|
||||||
msgstr ""
|
msgstr "EDNS 客戶端子網"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/global.js:433
|
#: htdocs/luci-static/resources/view/fchomo/global.js:433
|
||||||
msgid "ETag support"
|
msgid "ETag support"
|
||||||
@@ -820,6 +820,10 @@ msgstr "編輯入站"
|
|||||||
msgid "Edit node"
|
msgid "Edit node"
|
||||||
msgstr "編輯節點"
|
msgstr "編輯節點"
|
||||||
|
|
||||||
|
#: htdocs/luci-static/resources/view/fchomo/node.js:214
|
||||||
|
msgid "Edit outbound"
|
||||||
|
msgstr "編輯出站"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
||||||
msgid "Edit ruleset"
|
msgid "Edit ruleset"
|
||||||
msgstr "編輯規則集"
|
msgstr "編輯規則集"
|
||||||
@@ -1104,7 +1108,7 @@ msgstr "兜底 DNS 伺服器 (用於已被投毒汙染的網域)"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:137
|
#: htdocs/luci-static/resources/fchomo/listeners.js:137
|
||||||
msgid "Firewall"
|
msgid "Firewall"
|
||||||
msgstr ""
|
msgstr "防火牆"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:535
|
#: htdocs/luci-static/resources/fchomo/listeners.js:535
|
||||||
#: htdocs/luci-static/resources/view/fchomo/node.js:660
|
#: htdocs/luci-static/resources/view/fchomo/node.js:660
|
||||||
@@ -1335,7 +1339,7 @@ msgstr "握手超時"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:244
|
#: htdocs/luci-static/resources/fchomo/listeners.js:244
|
||||||
msgid "Header to read real client IP from (e.g. X-Forwarded-For)"
|
msgid "Header to read real client IP from (e.g. X-Forwarded-For)"
|
||||||
msgstr ""
|
msgstr "用於讀取客戶端真實 IP 的 Header(例如 X-Forwarded-For)"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/node.js:751
|
#: htdocs/luci-static/resources/view/fchomo/node.js:751
|
||||||
msgid "Health check"
|
msgid "Health check"
|
||||||
@@ -1525,7 +1529,7 @@ msgstr "單位為毫秒。留空則使用 <code>%s</code>。"
|
|||||||
|
|
||||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1350
|
#: htdocs/luci-static/resources/view/fchomo/node.js:1350
|
||||||
msgid "In milliseconds."
|
msgid "In milliseconds."
|
||||||
msgstr ""
|
msgstr "單位為毫秒。"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:418
|
#: htdocs/luci-static/resources/fchomo/listeners.js:418
|
||||||
#: htdocs/luci-static/resources/fchomo/listeners.js:487
|
#: htdocs/luci-static/resources/fchomo/listeners.js:487
|
||||||
@@ -3882,7 +3886,7 @@ msgstr "包含 %d 個字元的有效 base64 金鑰"
|
|||||||
#: htdocs/luci-static/resources/fchomo.js:1579
|
#: htdocs/luci-static/resources/fchomo.js:1579
|
||||||
#: htdocs/luci-static/resources/fchomo.js:1585
|
#: htdocs/luci-static/resources/fchomo.js:1585
|
||||||
msgid "valid format: 2x, 2p, 4v"
|
msgid "valid format: 2x, 2p, 4v"
|
||||||
msgstr ""
|
msgstr "有效格式: 2x, 2p, 4v"
|
||||||
|
|
||||||
#: htdocs/luci-static/resources/fchomo.js:1566
|
#: htdocs/luci-static/resources/fchomo.js:1566
|
||||||
msgid "valid key length with %d characters"
|
msgid "valid key length with %d characters"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_VERSION:=1.2.1
|
PKG_VERSION:=1.2.1
|
||||||
PKG_RELEASE:=6
|
PKG_RELEASE:=7
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI Support for momo
|
LUCI_TITLE:=LuCI Support for momo
|
||||||
LUCI_DEPENDS:=+luci-base +momo
|
LUCI_DEPENDS:=+luci-base +momo
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ const callRCInit = rpc.declare({
|
|||||||
expect: { '': {} }
|
expect: { '': {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const callFileWrite = rpc.declare({
|
||||||
|
object: 'file',
|
||||||
|
method: 'write',
|
||||||
|
params: ['path', 'data', 'append', 'mode']
|
||||||
|
});
|
||||||
|
|
||||||
const callMomoGetPaths = rpc.declare({
|
const callMomoGetPaths = rpc.declare({
|
||||||
object: 'luci.momo',
|
object: 'luci.momo',
|
||||||
method: 'get_paths',
|
method: 'get_paths',
|
||||||
@@ -81,6 +87,31 @@ return baseclass.extend({
|
|||||||
return callRCInit('momo', 'restart');
|
return callRCInit('momo', 'restart');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
writefile: function (path, data, mode) {
|
||||||
|
data = (data != null) ? String(data) : '';
|
||||||
|
mode = (mode != null) ? mode : 0o644;
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const chunkSize = 8 * 1024;
|
||||||
|
|
||||||
|
const bytes = encoder.encode(data);
|
||||||
|
|
||||||
|
if (bytes.length <= chunkSize) {
|
||||||
|
return callFileWrite(path, data, false, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
for(let offset = 0; offset < bytes.length; offset += chunkSize) {
|
||||||
|
const chunkBytes = bytes.slice(offset, Math.min(offset + chunkSize, bytes.length));
|
||||||
|
const chunk = decoder.decode(chunkBytes);
|
||||||
|
const append = offset > 0;
|
||||||
|
promise = promise.then(() => callFileWrite(path, chunk, append, mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
version: function () {
|
version: function () {
|
||||||
return callMomoVersion();
|
return callMomoVersion();
|
||||||
},
|
},
|
||||||
@@ -138,12 +169,12 @@ return baseclass.extend({
|
|||||||
|
|
||||||
clearAppLog: async function () {
|
clearAppLog: async function () {
|
||||||
const paths = await this.getPaths();
|
const paths = await this.getPaths();
|
||||||
return fs.write(paths.app_log_path);
|
return this.writefile(paths.app_log_path);
|
||||||
},
|
},
|
||||||
|
|
||||||
clearCoreLog: async function () {
|
clearCoreLog: async function () {
|
||||||
const paths = await this.getPaths();
|
const paths = await this.getPaths();
|
||||||
return fs.write(paths.core_log_path);
|
return this.writefile(paths.core_log_path);
|
||||||
},
|
},
|
||||||
|
|
||||||
debug: function () {
|
debug: function () {
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ return view.extend({
|
|||||||
o.wrap = false;
|
o.wrap = false;
|
||||||
o.write = function (section_id, formvalue) {
|
o.write = function (section_id, formvalue) {
|
||||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||||
return fs.write(path, formvalue);
|
return momo.writefile(path, formvalue);
|
||||||
};
|
};
|
||||||
o.remove = function (section_id) {
|
o.remove = function (section_id) {
|
||||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||||
return fs.write(path);
|
return momo.writefile(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
return m.render();
|
return m.render();
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { access, popen, readfile } from 'fs';
|
import { popen } from 'fs';
|
||||||
import { get_paths, merge_exists, get_users, get_groups, get_cgroups, load_profile, save_profile } from '/etc/momo/ucode/include.uc';
|
import { get_paths, merge_exists, get_users, get_groups, get_cgroups, load_profile } from '/etc/momo/ucode/include.uc';
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
get_paths: {
|
get_paths: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_VERSION:=1.26.1
|
PKG_VERSION:=1.26.1
|
||||||
PKG_RELEASE:=5
|
PKG_RELEASE:=6
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI Support for nikki
|
LUCI_TITLE:=LuCI Support for nikki
|
||||||
LUCI_DEPENDS:=+luci-base +nikki
|
LUCI_DEPENDS:=+luci-base +nikki
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ const callRCInit = rpc.declare({
|
|||||||
expect: { '': {} }
|
expect: { '': {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const callFileWrite = rpc.declare({
|
||||||
|
object: 'file',
|
||||||
|
method: 'write',
|
||||||
|
params: ['path', 'data', 'append', 'mode']
|
||||||
|
});
|
||||||
|
|
||||||
const callNikkiVersion = rpc.declare({
|
const callNikkiVersion = rpc.declare({
|
||||||
object: 'luci.nikki',
|
object: 'luci.nikki',
|
||||||
method: 'version',
|
method: 'version',
|
||||||
@@ -98,6 +104,31 @@ return baseclass.extend({
|
|||||||
return callRCInit('nikki', 'restart');
|
return callRCInit('nikki', 'restart');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
writefile: function (path, data, mode) {
|
||||||
|
data = (data != null) ? String(data) : '';
|
||||||
|
mode = (mode != null) ? mode : 0o644;
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const chunkSize = 8 * 1024;
|
||||||
|
|
||||||
|
const bytes = encoder.encode(data);
|
||||||
|
|
||||||
|
if (bytes.length <= chunkSize) {
|
||||||
|
return callFileWrite(path, data, false, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
for(let offset = 0; offset < bytes.length; offset += chunkSize) {
|
||||||
|
const chunkBytes = bytes.slice(offset, Math.min(offset + chunkSize, bytes.length));
|
||||||
|
const chunk = decoder.decode(chunkBytes);
|
||||||
|
const append = offset > 0;
|
||||||
|
promise = promise.then(() => callFileWrite(path, chunk, append, mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
version: function () {
|
version: function () {
|
||||||
return callNikkiVersion();
|
return callNikkiVersion();
|
||||||
},
|
},
|
||||||
@@ -183,11 +214,11 @@ return baseclass.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
clearAppLog: function () {
|
clearAppLog: function () {
|
||||||
return fs.write(this.appLogPath);
|
return this.writefile(this.appLogPath, '');
|
||||||
},
|
},
|
||||||
|
|
||||||
clearCoreLog: function () {
|
clearCoreLog: function () {
|
||||||
return fs.write(this.coreLogPath);
|
return this.writefile(this.coreLogPath, '');
|
||||||
},
|
},
|
||||||
|
|
||||||
debug: function () {
|
debug: function () {
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ return view.extend({
|
|||||||
o.wrap = false;
|
o.wrap = false;
|
||||||
o.write = function (section_id, formvalue) {
|
o.write = function (section_id, formvalue) {
|
||||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||||
return fs.write(path, formvalue);
|
return nikki.writefile(path, formvalue);
|
||||||
};
|
};
|
||||||
o.remove = function (section_id) {
|
o.remove = function (section_id) {
|
||||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||||
return fs.write(path);
|
return nikki.writefile(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
return m.render();
|
return m.render();
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-passwall2
|
PKG_NAME:=luci-app-passwall2
|
||||||
PKG_VERSION:=26.6.3
|
PKG_VERSION:=26.6.16
|
||||||
PKG_RELEASE:=54
|
PKG_RELEASE:=55
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -320,15 +320,6 @@ o.rewrite_option = "method"
|
|||||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||||
|
|
||||||
o = s:option(Flag, _n("iv_check"), translate("IV Check"))
|
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("ss_method")] = "aes-128-gcm" })
|
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("ss_method")] = "aes-256-gcm" })
|
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("ss_method")] = "chacha20-poly1305" })
|
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("ss_method")] = "xchacha20-poly1305" })
|
|
||||||
|
|
||||||
o = s:option(Flag, _n("uot"), translate("UDP over TCP"))
|
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
|
||||||
|
|
||||||
o = s:option(ListValue, _n("flow"), translate("flow"))
|
o = s:option(ListValue, _n("flow"), translate("flow"))
|
||||||
o.default = ""
|
o.default = ""
|
||||||
o:value("", translate("Disable"))
|
o:value("", translate("Disable"))
|
||||||
@@ -420,7 +411,6 @@ o:value("http/1.1")
|
|||||||
o:value("h2,http/1.1")
|
o:value("h2,http/1.1")
|
||||||
o:value("h3,h2,http/1.1")
|
o:value("h3,h2,http/1.1")
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
|
||||||
|
|
||||||
-- o = s:option(Value, _n("minversion"), translate("minversion"))
|
-- o = s:option(Value, _n("minversion"), translate("minversion"))
|
||||||
-- o.default = "1.3"
|
-- o.default = "1.3"
|
||||||
@@ -772,11 +762,6 @@ o.default = 0
|
|||||||
o = s:option(Flag, _n("tcpMptcp"), "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
|
o = s:option(Flag, _n("tcpMptcp"), "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
|
||||||
o.default = 0
|
o.default = 0
|
||||||
|
|
||||||
o = s:option(Value, _n("preconns"), translate("Pre-connections"), translate("Number of early established connections to reduce latency."))
|
|
||||||
o.datatype = "uinteger"
|
|
||||||
o.placeholder = 0
|
|
||||||
o:depends({ [_n("protocol")] = "vless" })
|
|
||||||
|
|
||||||
o = s:option(ListValue, _n("domain_resolver"), translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution."))
|
o = s:option(ListValue, _n("domain_resolver"), translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution."))
|
||||||
o:value("", translate("Auto"))
|
o:value("", translate("Auto"))
|
||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
|
|||||||
@@ -97,9 +97,6 @@ o.rewrite_option = "method"
|
|||||||
for a, t in ipairs(x_ss_method_list) do o:value(t) end
|
for a, t in ipairs(x_ss_method_list) do o:value(t) end
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||||
|
|
||||||
o = s:option(Flag, _n("iv_check"), translate("IV Check"))
|
|
||||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
|
||||||
|
|
||||||
o = s:option(ListValue, _n("ss_network"), translate("Transport"))
|
o = s:option(ListValue, _n("ss_network"), translate("Transport"))
|
||||||
o.default = "tcp,udp"
|
o.default = "tcp,udp"
|
||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
@@ -232,7 +229,6 @@ o:value("http/1.1")
|
|||||||
o:value("h2,http/1.1")
|
o:value("h2,http/1.1")
|
||||||
o:value("h3,h2,http/1.1")
|
o:value("h3,h2,http/1.1")
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
|
||||||
|
|
||||||
o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65"))
|
o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65"))
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
|
|||||||
@@ -1611,27 +1611,23 @@ end
|
|||||||
|
|
||||||
function parse_realm_uri(uri)
|
function parse_realm_uri(uri)
|
||||||
if type(uri) ~= "string" then return nil end
|
if type(uri) ~= "string" then return nil end
|
||||||
-- realm://token@server/realm_id?query
|
-- realm[+http]://token@server/realm_id?query
|
||||||
local token, server_url, realm_id, query = trim(uri):match("^realm://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
local scheme, token, server_url, realm_id, query = trim(uri):match("^(realm%+http|realm)://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||||||
if not token or not server_url or not realm_id then return nil end
|
if not scheme or not token or not server_url or not realm_id then return nil end
|
||||||
realm_id = realm_id:gsub("/+$", "")
|
realm_id = realm_id:gsub("/+$", "")
|
||||||
local realm = {
|
local realm = {
|
||||||
|
scheme = scheme,
|
||||||
token = token,
|
token = token,
|
||||||
server_url = server_url,
|
server_url = server_url,
|
||||||
realm_id = realm_id
|
realm_id = realm_id
|
||||||
}
|
}
|
||||||
-- 解析 query 中的 stun=
|
-- Parse stun= in the query
|
||||||
if query and query ~= "" then
|
local stun_servers
|
||||||
local stun_servers = {}
|
for value in (query or ""):gmatch("[?&]?[Ss][Tt][Uu][Nn]=([^&]+)") do
|
||||||
for key, value in query:gmatch("([^&=?]+)=([^&]+)") do
|
if not stun_servers then stun_servers = {} end
|
||||||
if key == "stun" and value ~= "" then
|
stun_servers[#stun_servers + 1] = value
|
||||||
stun_servers[#stun_servers + 1] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #stun_servers > 0 then
|
|
||||||
realm.stun_servers = stun_servers
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
realm.stun_servers = stun_servers
|
||||||
return realm
|
return realm
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ function gen_config(var)
|
|||||||
server_host = api.get_ipv6_full(server_host)
|
server_host = api.get_ipv6_full(server_host)
|
||||||
end
|
end
|
||||||
|
|
||||||
local server = server_host .. ":" .. ((server_port or "") .. "," .. (node.hysteria2_hop or "")):gsub("^[%s,]+", ""):gsub("[%s,]+$", ""):gsub(":", "-")
|
local port_hop = ((server_port or "") .. "," .. (node.hysteria2_hop or "")):gsub("^[%s,]+", ""):gsub("[%s,]+$", ""):gsub(":", "-")
|
||||||
|
local server = server_host .. ":" .. (port_hop ~= "" and port_hop or "443")
|
||||||
|
|
||||||
local config = {
|
local config = {
|
||||||
server = (function()
|
server = (function()
|
||||||
|
|||||||
@@ -617,8 +617,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
result.server_port = nil
|
result.server_port = nil
|
||||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||||
if realm then
|
if realm then
|
||||||
realm.server_url = realm.server_url and "https://" .. realm.server_url or nil
|
realm.server_url = (realm.scheme == "realm+http" and "http://" or "https://") .. realm.server_url
|
||||||
realm.stun_servers = realm.stun_servers or node.hysteria2_realm_stun
|
realm.stun_servers = realm.stun_servers or node.hysteria2_realm_stun
|
||||||
|
realm.scheme = nil
|
||||||
return realm
|
return realm
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
@@ -959,8 +960,9 @@ function gen_config_server(node)
|
|||||||
realm = node.hysteria2_realms and (function()
|
realm = node.hysteria2_realms and (function()
|
||||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||||
if realm then
|
if realm then
|
||||||
realm.server_url = realm.server_url and "https://" .. realm.server_url or nil
|
realm.server_url = (realm.scheme == "realm+http" and "http://" or "https://") .. realm.server_url
|
||||||
realm.stun_servers = realm.stun_servers or node.hysteria2_realm_stun
|
realm.stun_servers = realm.stun_servers or node.hysteria2_realm_stun
|
||||||
|
realm.scheme = nil
|
||||||
realm.stun_domain_resolver = "direct"
|
realm.stun_domain_resolver = "direct"
|
||||||
return realm
|
return realm
|
||||||
end
|
end
|
||||||
@@ -1797,13 +1799,12 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(route.rules, {
|
|
||||||
action = "route",
|
|
||||||
ip_is_private = true,
|
|
||||||
outbound = "direct"
|
|
||||||
})
|
|
||||||
|
|
||||||
if COMMON.default_outbound_tag then
|
if COMMON.default_outbound_tag then
|
||||||
|
table.insert(route.rules, {
|
||||||
|
action = "route",
|
||||||
|
port_range = { "0:65535" },
|
||||||
|
outbound = COMMON.default_outbound_tag
|
||||||
|
})
|
||||||
route.final = COMMON.default_outbound_tag
|
route.final = COMMON.default_outbound_tag
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -285,21 +285,22 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
local finalmask = {}
|
local finalmask = {}
|
||||||
local TP = node.transport
|
local TP = node.transport
|
||||||
if TP == "mkcp" then
|
if TP == "mkcp" then
|
||||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
local map = {none = "none", srtp = "srtp", utp = "utp", ["wechat-video"] = "wechat",
|
||||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
dtls = "dtls", wireguard = "wireguard", dns = "dns"}
|
||||||
local udp = {}
|
local udp = {}
|
||||||
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
||||||
local g = { type = map[node.mkcp_guise] }
|
local g = { type = "mkcp-legacy" }
|
||||||
|
g.settings = { header = map[node.mkcp_guise] }
|
||||||
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||||
g.settings = { domain = node.mkcp_domain }
|
g.settings.value = node.mkcp_domain
|
||||||
end
|
end
|
||||||
udp[#udp+1] = g
|
udp[#udp+1] = g
|
||||||
end
|
end
|
||||||
local c = { type = (node.mkcp_seed and node.mkcp_seed ~= "") and "mkcp-aes128gcm" or "mkcp-original" }
|
local s = { type = "mkcp-legacy" }
|
||||||
if node.mkcp_seed and node.mkcp_seed ~= "" then
|
if node.mkcp_seed and node.mkcp_seed ~= "" then
|
||||||
c.settings = { password = node.mkcp_seed }
|
s.settings = { value = node.mkcp_seed }
|
||||||
end
|
end
|
||||||
udp[#udp+1] = c
|
udp[#udp+1] = s
|
||||||
finalmask.udp = udp
|
finalmask.udp = udp
|
||||||
elseif TP == "hysteria" then
|
elseif TP == "hysteria" then
|
||||||
local udp = {}
|
local udp = {}
|
||||||
@@ -317,9 +318,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||||
local url, stun
|
local url, stun
|
||||||
if realm then
|
if realm then
|
||||||
if realm.token and realm.server_url and realm.realm_id then
|
url = realm.scheme .. "://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||||
url = "realm://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
|
||||||
end
|
|
||||||
stun = realm.stun_servers or node.hysteria2_realm_stun
|
stun = realm.stun_servers or node.hysteria2_realm_stun
|
||||||
end
|
end
|
||||||
local r = {
|
local r = {
|
||||||
@@ -386,43 +385,18 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
end)()
|
end)()
|
||||||
} or nil,
|
} or nil,
|
||||||
settings = {
|
settings = {
|
||||||
vnext = (node.protocol == "vmess" or node.protocol == "vless") and {
|
address = (node.protocol == "wireguard") and node.wireguard_local_address or node.address,
|
||||||
{
|
port = tonumber(node.port),
|
||||||
address = node.address,
|
id = (node.protocol == "vmess" or node.protocol == "vless") and node.uuid or nil,
|
||||||
port = tonumber(node.port),
|
encryption = (node.protocol == "vless") and ((node.encryption and node.encryption ~= "") and node.encryption or "none") or nil,
|
||||||
users = {
|
flow = (node.protocol == "vless" and (node.tls == "1" or (node.encryption and node.encryption ~= "" and node.encryption ~= "none"))
|
||||||
{
|
and node.flow and node.flow ~= "") and node.flow or nil,
|
||||||
id = node.uuid,
|
security = (node.protocol == "vmess") and node.security or nil,
|
||||||
level = 0,
|
user = (node.protocol == "socks" or node.protocol == "http") and node.username or nil,
|
||||||
security = (node.protocol == "vmess") and node.security or nil,
|
pass = (node.protocol == "socks" or node.protocol == "http") and node.password or nil,
|
||||||
testpre = (node.protocol == "vless") and tonumber(node.preconns) or nil,
|
password = (node.protocol == "shadowsocks" or node.protocol == "trojan") and node.password or nil,
|
||||||
encryption = (node.protocol == "vless") and ((node.encryption and node.encryption ~= "") and node.encryption or "none") or nil,
|
method = (node.protocol == "shadowsocks") and ((node.method == "chacha20-ietf-poly1305" and "chacha20-poly1305") or
|
||||||
flow = (node.protocol == "vless"
|
(node.method == "xchacha20-ietf-poly1305" and "xchacha20-poly1305") or (node.method ~= "" and node.method) or nil) or nil,
|
||||||
and (node.tls == "1" or (node.encryption and node.encryption ~= "" and node.encryption ~= "none"))
|
|
||||||
and node.flow and node.flow ~= "") and node.flow or nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} or nil,
|
|
||||||
servers = (node.protocol == "socks" or node.protocol == "http" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
|
|
||||||
{
|
|
||||||
address = node.address,
|
|
||||||
port = tonumber(node.port),
|
|
||||||
method = (node.method == "chacha20-ietf-poly1305" and "chacha20-poly1305") or
|
|
||||||
(node.method == "xchacha20-ietf-poly1305" and "xchacha20-poly1305") or
|
|
||||||
(node.method ~= "" and node.method) or nil,
|
|
||||||
ivCheck = (node.protocol == "shadowsocks") and node.iv_check == "1" or nil,
|
|
||||||
uot = (node.protocol == "shadowsocks") and node.uot == "1" or nil,
|
|
||||||
password = node.password or "",
|
|
||||||
users = (node.username and node.password) and {
|
|
||||||
{
|
|
||||||
user = node.username,
|
|
||||||
pass = node.password
|
|
||||||
}
|
|
||||||
} or nil
|
|
||||||
}
|
|
||||||
} or nil,
|
|
||||||
address = (node.protocol == "wireguard" and node.wireguard_local_address) or (node.protocol == "hysteria" and node.address) or nil,
|
|
||||||
secretKey = (node.protocol == "wireguard") and node.wireguard_secret_key or nil,
|
secretKey = (node.protocol == "wireguard") and node.wireguard_secret_key or nil,
|
||||||
peers = (node.protocol == "wireguard") and {
|
peers = (node.protocol == "wireguard") and {
|
||||||
{
|
{
|
||||||
@@ -434,13 +408,13 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
} or nil,
|
} or nil,
|
||||||
mtu = (node.protocol == "wireguard" and node.wireguard_mtu) and tonumber(node.wireguard_mtu) or nil,
|
mtu = (node.protocol == "wireguard" and node.wireguard_mtu) and tonumber(node.wireguard_mtu) or nil,
|
||||||
reserved = (node.protocol == "wireguard" and node.wireguard_reserved) and node.wireguard_reserved or nil,
|
reserved = (node.protocol == "wireguard" and node.wireguard_reserved) and node.wireguard_reserved or nil,
|
||||||
port = (node.protocol == "hysteria" and node.port) and tonumber(node.port) or nil,
|
version = (node.protocol == "hysteria") and 2 or nil,
|
||||||
version = node.protocol == "hysteria" and 2 or nil
|
level = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.protocol == "wireguard" then
|
if node.protocol == "wireguard" then
|
||||||
result.settings.kernelMode = false
|
result.settings.noKernelTun = true
|
||||||
if node.finalmask and node.finalmask ~= "" then
|
if node.finalmask and node.finalmask ~= "" then
|
||||||
local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask))
|
local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask))
|
||||||
if ok and type(fm) == "table" then
|
if ok and type(fm) == "table" then
|
||||||
@@ -561,7 +535,6 @@ function gen_config_server(node)
|
|||||||
settings = {
|
settings = {
|
||||||
method = node.method,
|
method = node.method,
|
||||||
password = node.password,
|
password = node.password,
|
||||||
ivCheck = ("1" == node.iv_check) and true or false,
|
|
||||||
network = node.ss_network or "TCP,UDP"
|
network = node.ss_network or "TCP,UDP"
|
||||||
}
|
}
|
||||||
elseif node.protocol == "trojan" then
|
elseif node.protocol == "trojan" then
|
||||||
@@ -740,21 +713,22 @@ function gen_config_server(node)
|
|||||||
finalmask = (function()
|
finalmask = (function()
|
||||||
local finalmask = {}
|
local finalmask = {}
|
||||||
if node.transport == "mkcp" then
|
if node.transport == "mkcp" then
|
||||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
local map = {none = "none", srtp = "srtp", utp = "utp", ["wechat-video"] = "wechat",
|
||||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
dtls = "dtls", wireguard = "wireguard", dns = "dns"}
|
||||||
local udp = {}
|
local udp = {}
|
||||||
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
||||||
local g = { type = map[node.mkcp_guise] }
|
local g = { type = "mkcp-legacy" }
|
||||||
|
g.settings = { header = map[node.mkcp_guise] }
|
||||||
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||||
g.settings = { domain = node.mkcp_domain }
|
g.settings.value = node.mkcp_domain
|
||||||
end
|
end
|
||||||
udp[#udp+1] = g
|
udp[#udp+1] = g
|
||||||
end
|
end
|
||||||
local c = { type = (node.mkcp_seed and node.mkcp_seed ~= "") and "mkcp-aes128gcm" or "mkcp-original" }
|
local s = { type = "mkcp-legacy" }
|
||||||
if node.mkcp_seed and node.mkcp_seed ~= "" then
|
if node.mkcp_seed and node.mkcp_seed ~= "" then
|
||||||
c.settings = { password = node.mkcp_seed }
|
s.settings = { value = node.mkcp_seed }
|
||||||
end
|
end
|
||||||
udp[#udp+1] = c
|
udp[#udp+1] = s
|
||||||
finalmask.udp = udp
|
finalmask.udp = udp
|
||||||
elseif node.transport == "hysteria" then
|
elseif node.transport == "hysteria" then
|
||||||
local udp = {}
|
local udp = {}
|
||||||
@@ -772,9 +746,7 @@ function gen_config_server(node)
|
|||||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||||
local url, stun
|
local url, stun
|
||||||
if realm then
|
if realm then
|
||||||
if realm.token and realm.server_url and realm.realm_id then
|
url = realm.scheme .. "://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||||
url = "realm://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
|
||||||
end
|
|
||||||
stun = realm.stun_servers or node.hysteria2_realm_stun
|
stun = realm.stun_servers or node.hysteria2_realm_stun
|
||||||
end
|
end
|
||||||
local r = {
|
local r = {
|
||||||
@@ -1524,7 +1496,7 @@ function gen_config(var)
|
|||||||
outboundTag = default_outboundTag,
|
outboundTag = default_outboundTag,
|
||||||
}
|
}
|
||||||
if node.domainStrategy == "IPIfNonMatch" then
|
if node.domainStrategy == "IPIfNonMatch" then
|
||||||
rule.ip = { "0.0.0.0/0", "::/0" }
|
rule.port = "1-65535"
|
||||||
else
|
else
|
||||||
rule.network = "tcp,udp"
|
rule.network = "tcp,udp"
|
||||||
end
|
end
|
||||||
@@ -1719,6 +1691,7 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local dns_outbound
|
||||||
if dns_listen_port then
|
if dns_listen_port then
|
||||||
table.insert(inbounds, {
|
table.insert(inbounds, {
|
||||||
listen = "127.0.0.1",
|
listen = "127.0.0.1",
|
||||||
@@ -1775,12 +1748,13 @@ function gen_config(var)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
local type_dns = direct_type_dns
|
local type_dns = direct_type_dns
|
||||||
table.insert(outbounds, {
|
dns_outbound = {
|
||||||
tag = "dns-out",
|
tag = "dns-out",
|
||||||
protocol = "dns",
|
protocol = "dns",
|
||||||
proxySettings = type_dns.proxySettings,
|
proxySettings = type_dns.proxySettings,
|
||||||
settings = type_dns.settings
|
settings = type_dns.settings
|
||||||
})
|
}
|
||||||
|
table.insert(outbounds, dns_outbound)
|
||||||
table.insert(routing.rules, 1, {
|
table.insert(routing.rules, 1, {
|
||||||
inboundTag = {
|
inboundTag = {
|
||||||
"dns-in"
|
"dns-in"
|
||||||
@@ -1815,6 +1789,7 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Shunt rule DNS logic
|
-- Shunt rule DNS logic
|
||||||
|
local dns_out_rules = {}
|
||||||
if dns_domain_rules and #dns_domain_rules > 0 then
|
if dns_domain_rules and #dns_domain_rules > 0 then
|
||||||
for index, value in ipairs(dns_domain_rules) do
|
for index, value in ipairs(dns_domain_rules) do
|
||||||
if value.domain and value.outboundTag then
|
if value.domain and value.outboundTag then
|
||||||
@@ -1834,19 +1809,19 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local dns_block = true -- If you do not want the blackhole rule to use DNS routing, please change it to `nil` or do not assign a value.
|
|
||||||
if dns_outboundTag == "blackhole" then
|
if dns_outboundTag == "blackhole" then
|
||||||
if dns_block then
|
table.insert(dns_out_rules, {
|
||||||
local dns_block_mode = "Hosts" -- use the Hosts mode to block.
|
action = "return",
|
||||||
if dns_block_mode == "Hosts" then
|
rCode = 0,
|
||||||
for d_i, d_k in ipairs(value.domain) do
|
domain = api.clone(value.domain)
|
||||||
dns.hosts[d_k] = "0.0.0.0"
|
})
|
||||||
end
|
dns_server = nil
|
||||||
dns_server = nil
|
else
|
||||||
end
|
table.insert(dns_out_rules, {
|
||||||
else
|
action = "hijack",
|
||||||
dns_server = nil
|
qType = "1,28",
|
||||||
end
|
domain = api.clone(value.domain)
|
||||||
|
})
|
||||||
end
|
end
|
||||||
if dns_server then
|
if dns_server then
|
||||||
dns_server.finalQuery = true
|
dns_server.finalQuery = true
|
||||||
@@ -1861,6 +1836,11 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if dns_outbound and #dns_out_rules > 0 then
|
||||||
|
for i = #dns_out_rules, 1, -1 do
|
||||||
|
table.insert(dns_outbound.settings.rules, 1, dns_out_rules[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1251,9 +1251,6 @@ msgstr "پاک کردن گزارشها"
|
|||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "رمز عبور"
|
msgstr "رمز عبور"
|
||||||
|
|
||||||
msgid "IV Check"
|
|
||||||
msgstr "بررسی IV"
|
|
||||||
|
|
||||||
msgid "UDP over TCP"
|
msgid "UDP over TCP"
|
||||||
msgstr "UDP بر روی TCP"
|
msgstr "UDP بر روی TCP"
|
||||||
|
|
||||||
@@ -1266,12 +1263,6 @@ msgstr "پورت محلی"
|
|||||||
msgid "Fast Open"
|
msgid "Fast Open"
|
||||||
msgstr "بازگشایی سریع (Fast Open)"
|
msgstr "بازگشایی سریع (Fast Open)"
|
||||||
|
|
||||||
msgid "Pre-connections"
|
|
||||||
msgstr "پیشاتصالات"
|
|
||||||
|
|
||||||
msgid "Number of early established connections to reduce latency."
|
|
||||||
msgstr "تعداد اتصالات از پیش برقرار شده برای کاهش تاخیر."
|
|
||||||
|
|
||||||
msgid "plugin"
|
msgid "plugin"
|
||||||
msgstr "افزونه"
|
msgstr "افزونه"
|
||||||
|
|
||||||
|
|||||||
@@ -1252,9 +1252,6 @@ msgstr "Очистить журнал"
|
|||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Пароль"
|
msgstr "Пароль"
|
||||||
|
|
||||||
msgid "IV Check"
|
|
||||||
msgstr "Проверка IV"
|
|
||||||
|
|
||||||
msgid "UDP over TCP"
|
msgid "UDP over TCP"
|
||||||
msgstr "UDP через TCP"
|
msgstr "UDP через TCP"
|
||||||
|
|
||||||
@@ -1267,12 +1264,6 @@ msgstr "Локальный порт"
|
|||||||
msgid "Fast Open"
|
msgid "Fast Open"
|
||||||
msgstr "Fast Open"
|
msgstr "Fast Open"
|
||||||
|
|
||||||
msgid "Pre-connections"
|
|
||||||
msgstr "Предварительные соединения"
|
|
||||||
|
|
||||||
msgid "Number of early established connections to reduce latency."
|
|
||||||
msgstr "Количество заранее установленных соединений для снижения задержки."
|
|
||||||
|
|
||||||
msgid "plugin"
|
msgid "plugin"
|
||||||
msgstr "Плагин"
|
msgstr "Плагин"
|
||||||
|
|
||||||
|
|||||||
@@ -1243,9 +1243,6 @@ msgstr "清空日志"
|
|||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "密码"
|
msgstr "密码"
|
||||||
|
|
||||||
msgid "IV Check"
|
|
||||||
msgstr "IV 检查"
|
|
||||||
|
|
||||||
msgid "UDP over TCP"
|
msgid "UDP over TCP"
|
||||||
msgstr "TCP 封装 UDP"
|
msgstr "TCP 封装 UDP"
|
||||||
|
|
||||||
@@ -1258,12 +1255,6 @@ msgstr "本地端口"
|
|||||||
msgid "Fast Open"
|
msgid "Fast Open"
|
||||||
msgstr "快速打开"
|
msgstr "快速打开"
|
||||||
|
|
||||||
msgid "Pre-connections"
|
|
||||||
msgstr "预连接"
|
|
||||||
|
|
||||||
msgid "Number of early established connections to reduce latency."
|
|
||||||
msgstr "预连接的数量,用于降低延迟。"
|
|
||||||
|
|
||||||
msgid "plugin"
|
msgid "plugin"
|
||||||
msgstr "插件"
|
msgstr "插件"
|
||||||
|
|
||||||
|
|||||||
@@ -1243,9 +1243,6 @@ msgstr "清空日誌"
|
|||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "密碼"
|
msgstr "密碼"
|
||||||
|
|
||||||
msgid "IV Check"
|
|
||||||
msgstr "IV 檢查"
|
|
||||||
|
|
||||||
msgid "UDP over TCP"
|
msgid "UDP over TCP"
|
||||||
msgstr "TCP 封装 UDP"
|
msgstr "TCP 封装 UDP"
|
||||||
|
|
||||||
@@ -1258,12 +1255,6 @@ msgstr "本地端口"
|
|||||||
msgid "Fast Open"
|
msgid "Fast Open"
|
||||||
msgstr "快速打開"
|
msgstr "快速打開"
|
||||||
|
|
||||||
msgid "Pre-connections"
|
|
||||||
msgstr "預連接"
|
|
||||||
|
|
||||||
msgid "Number of early established connections to reduce latency."
|
|
||||||
msgstr "預連接的數量,用於降低延遲。"
|
|
||||||
|
|
||||||
msgid "plugin"
|
msgid "plugin"
|
||||||
msgstr "插件"
|
msgstr "插件"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
START=99
|
START=99
|
||||||
|
|
||||||
|
boot_func() {
|
||||||
|
sleep 90
|
||||||
|
restart
|
||||||
|
}
|
||||||
|
|
||||||
|
boot() {
|
||||||
|
boot_func >/dev/null 2>&1 &
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
lua /usr/lib/lua/luci/passwall2/server_app.lua start
|
lua /usr/lib/lua/luci/passwall2/server_app.lua start
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,23 @@ config shunt_rules 'Russia'
|
|||||||
option ip_list 'geoip:ru
|
option ip_list 'geoip:ru
|
||||||
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geoip/geoip-ru.srs'
|
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geoip/geoip-ru.srs'
|
||||||
|
|
||||||
|
config shunt_rules 'PrivateIP'
|
||||||
|
option remarks 'PrivateIP'
|
||||||
|
option network 'tcp,udp'
|
||||||
|
option ip_list '10.0.0.0/8
|
||||||
|
127.0.0.0/8
|
||||||
|
198.51.100.0/24
|
||||||
|
192.0.0.0/24
|
||||||
|
169.254.0.0/16
|
||||||
|
224.0.0.0/3
|
||||||
|
192.168.0.0/16
|
||||||
|
192.88.99.0/24
|
||||||
|
100.64.0.0/10
|
||||||
|
172.16.0.0/12
|
||||||
|
0.0.0.0/8
|
||||||
|
192.0.2.0/24
|
||||||
|
203.0.113.0/24'
|
||||||
|
|
||||||
config nodes 'examplenode'
|
config nodes 'examplenode'
|
||||||
option remarks 'Example'
|
option remarks 'Example'
|
||||||
option type 'Xray'
|
option type 'Xray'
|
||||||
@@ -104,6 +121,7 @@ config nodes 'rulenode'
|
|||||||
option remarks 'rulenode'
|
option remarks 'rulenode'
|
||||||
option type 'Xray'
|
option type 'Xray'
|
||||||
option protocol '_shunt'
|
option protocol '_shunt'
|
||||||
|
option PrivateIP '_direct'
|
||||||
option default_node 'examplenode'
|
option default_node 'examplenode'
|
||||||
option domainStrategy 'IPOnDemand'
|
option domainStrategy 'IPOnDemand'
|
||||||
option domainMatcher 'hybrid'
|
option domainMatcher 'hybrid'
|
||||||
|
|||||||
@@ -1937,7 +1937,6 @@ local function curl(url, file, ua, mode)
|
|||||||
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua
|
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua
|
||||||
curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"'
|
curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"'
|
||||||
end
|
end
|
||||||
curl_args[#curl_args + 1] = get_headers()
|
|
||||||
local return_code, result
|
local return_code, result
|
||||||
if mode == "direct" then
|
if mode == "direct" then
|
||||||
return_code, result = api.curl_direct(url, file, curl_args)
|
return_code, result = api.curl_direct(url, file, curl_args)
|
||||||
@@ -1949,57 +1948,6 @@ local function curl(url, file, ua, mode)
|
|||||||
return return_code, tonumber(result)
|
return return_code, tonumber(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_headers()
|
|
||||||
local cache_file = "/tmp/etc/" .. appname .. "_tmp/sub_curl_headers"
|
|
||||||
if fs.access(cache_file) then
|
|
||||||
return luci.sys.exec("cat " .. cache_file)
|
|
||||||
end
|
|
||||||
local headers = {}
|
|
||||||
|
|
||||||
local function readfile(path)
|
|
||||||
local f = io.open(path, "r")
|
|
||||||
if not f then return nil end
|
|
||||||
local c = f:read("*a")
|
|
||||||
f:close()
|
|
||||||
return api.trim(c)
|
|
||||||
end
|
|
||||||
|
|
||||||
headers[#headers + 1] = "x-device-os: OpenWrt"
|
|
||||||
|
|
||||||
local rel = readfile("/etc/openwrt_release")
|
|
||||||
local os_ver = rel and rel:match("DISTRIB_RELEASE='([^']+)'")
|
|
||||||
if os_ver then
|
|
||||||
headers[#headers + 1] = "x-ver-os: " .. os_ver
|
|
||||||
end
|
|
||||||
|
|
||||||
local model = readfile("/tmp/sysinfo/model")
|
|
||||||
if model then
|
|
||||||
headers[#headers + 1] = "x-device-model: " .. model
|
|
||||||
end
|
|
||||||
|
|
||||||
local mac = readfile("/sys/class/net/eth0/address")
|
|
||||||
if mac and model then
|
|
||||||
local raw = mac .. "-" .. model
|
|
||||||
local p = io.popen("printf '%s' '" .. raw:gsub("'", "'\\''") .. "' | sha256sum")
|
|
||||||
if p then
|
|
||||||
local hash = p:read("*l")
|
|
||||||
p:close()
|
|
||||||
hash = hash and hash:match("^%w+")
|
|
||||||
if hash then
|
|
||||||
headers[#headers + 1] = "x-hwid: " .. hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local out = {}
|
|
||||||
for i = 1, #headers do
|
|
||||||
out[i] = "-H '" .. headers[i]:gsub("'", "'\\''") .. "'"
|
|
||||||
end
|
|
||||||
local headers_str = table.concat(out, " ")
|
|
||||||
local f = io.open(cache_file, "w"); if f then f:write(headers_str); f:close() end
|
|
||||||
return headers_str
|
|
||||||
end
|
|
||||||
|
|
||||||
local function truncate_nodes(group)
|
local function truncate_nodes(group)
|
||||||
for _, config in pairs(CONFIG) do
|
for _, config in pairs(CONFIG) do
|
||||||
if config.currentNodes and #config.currentNodes > 0 then
|
if config.currentNodes and #config.currentNodes > 0 then
|
||||||
|
|||||||
@@ -5,17 +5,19 @@
|
|||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI based ipk store
|
LUCI_TITLE:=LuCI based ipk/apk store
|
||||||
LUCI_DESCRIPTION:=luci-app-store is a ipk store developed by LinkEase team
|
LUCI_DESCRIPTION:=luci-app-store is a ipk/apk store developed by LinkEase team
|
||||||
LUCI_DEPENDS:=@(x86_64||aarch64) +curl +opkg +luci-lib-ipkg +tar +libuci-lua +mount-utils +luci-lib-taskd
|
LUCI_DEPENDS:=@(x86_64||aarch64) +curl +tar +libuci-lua +mount-utils +luci-lib-taskd
|
||||||
|
LUCI_DEPENDS+=$(if $(CONFIG_USE_APK),+apk +luci-compat,+opkg)
|
||||||
|
|
||||||
LUCI_EXTRA_DEPENDS:=luci-lib-taskd (>=1.0.19)
|
LUCI_EXTRA_DEPENDS:=luci-lib-taskd (>=1.0.19)
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
PKG_VERSION:=0.1.32-1
|
PKG_VERSION:=0.2.0-r2
|
||||||
# PKG_RELEASE MUST be empty for luci.mk
|
# PKG_RELEASE MUST be empty for luci.mk
|
||||||
PKG_RELEASE:=
|
PKG_RELEASE:=
|
||||||
|
|
||||||
ISTORE_UI_VERSION:=0.1.32
|
ISTORE_UI_VERSION:=0.2.0
|
||||||
ISTORE_UI_RELEASE:=1
|
ISTORE_UI_RELEASE:=1
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
|
|||||||
|
|
||||||
TARGET_CONFIGURE_OPTS= FRONTEND_DIST="$(BUILD_DIR)/istore-ui-$(ISTORE_UI_VERSION)-$(ISTORE_UI_RELEASE)/app-store-ui/src/dist" APP_STORE_VERSION="$(PKG_VERSION)"
|
TARGET_CONFIGURE_OPTS= FRONTEND_DIST="$(BUILD_DIR)/istore-ui-$(ISTORE_UI_VERSION)-$(ISTORE_UI_RELEASE)/app-store-ui/src/dist" APP_STORE_VERSION="$(PKG_VERSION)"
|
||||||
TARGET_CONFIGURE_OPTS+= SED="$(SED)"
|
TARGET_CONFIGURE_OPTS+= SED="$(SED)"
|
||||||
|
TARGET_CONFIGURE_OPTS+= USE_APK="$(CONFIG_USE_APK)"
|
||||||
|
|
||||||
define Package/luci-app-store/conffiles
|
define Package/luci-app-store/conffiles
|
||||||
/etc/.app_store.id
|
/etc/.app_store.id
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ end
|
|||||||
function store_index()
|
function store_index()
|
||||||
local fs = require "nixio.fs"
|
local fs = require "nixio.fs"
|
||||||
local features = { "_lua_force_array_" }
|
local features = { "_lua_force_array_" }
|
||||||
|
if fs.access("/etc/apk/arch") then
|
||||||
|
features[#features+1] = "apk"
|
||||||
|
else
|
||||||
|
features[#features+1] = "opkg"
|
||||||
|
end
|
||||||
if fs.access("/usr/libexec/istore/backup") then
|
if fs.access("/usr/libexec/istore/backup") then
|
||||||
features[#features+1] = "backup"
|
features[#features+1] = "backup"
|
||||||
end
|
end
|
||||||
@@ -251,13 +256,11 @@ function validate_pkgname(val)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function get_installed_and_cache()
|
local function get_installed_and_cache()
|
||||||
local metadir = "/usr/lib/opkg/meta"
|
local metadir = "/tmp/run/istore-meta/meta"
|
||||||
local cachedir = "/tmp/cache/istore"
|
local cachedir = "/tmp/cache/istore"
|
||||||
local cachefile = cachedir .. "/installed.json"
|
local cachefile = cachedir .. "/installed.json"
|
||||||
local metapkgpre = "app-meta-"
|
|
||||||
local nixio = require "nixio"
|
local nixio = require "nixio"
|
||||||
local fs = require "nixio.fs"
|
local fs = require "nixio.fs"
|
||||||
local ipkg = require "luci.model.ipkg"
|
|
||||||
local jsonc = require "luci.jsonc"
|
local jsonc = require "luci.jsonc"
|
||||||
local result = {}
|
local result = {}
|
||||||
local lock, msg = flock("/var/lock/istore-installed.lock", "lock")
|
local lock, msg = flock("/var/lock/istore-installed.lock", "lock")
|
||||||
@@ -274,7 +277,8 @@ local function get_installed_and_cache()
|
|||||||
local pkg
|
local pkg
|
||||||
for pkg in itr do
|
for pkg in itr do
|
||||||
if pkg:match("^.*%.json$") then
|
if pkg:match("^.*%.json$") then
|
||||||
local metadata = fs.readfile(metadir .. "/" .. pkg)
|
local metafile = metadir .. "/" .. pkg
|
||||||
|
local metadata = fs.readfile(metafile)
|
||||||
if metadata ~= nil then
|
if metadata ~= nil then
|
||||||
local meta = jsonc.parse(metadata)
|
local meta = jsonc.parse(metadata)
|
||||||
if meta == nil then
|
if meta == nil then
|
||||||
@@ -291,15 +295,10 @@ local function get_installed_and_cache()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
local time = nil
|
local time = nil
|
||||||
local metapkg = metapkgpre .. meta.name
|
local istat = fs.stat(metafile)
|
||||||
local status = ipkg.status(metapkg)
|
if istat ~= nil then
|
||||||
if next(status) ~= nil then
|
time = istat["mtime"]
|
||||||
time = status[metapkg]["Installed-Time"]
|
|
||||||
else
|
else
|
||||||
local istat = fs.stat("/usr/lib/opkg/info/" .. metapkg .. ".list")
|
|
||||||
if istat ~= nil then
|
|
||||||
time = istat["mtime"]
|
|
||||||
end
|
|
||||||
cacheable = false
|
cacheable = false
|
||||||
end
|
end
|
||||||
if time ~= nil then
|
if time ~= nil then
|
||||||
@@ -313,7 +312,7 @@ local function get_installed_and_cache()
|
|||||||
result = data
|
result = data
|
||||||
if cacheable then
|
if cacheable then
|
||||||
fs.mkdirr(cachedir)
|
fs.mkdirr(cachedir)
|
||||||
local oflags = nixio.open_flags("rdwr", "creat")
|
local oflags = nixio.open_flags("wronly", "creat", "trunc")
|
||||||
local mfile, code, msg = nixio.open(cachefile, oflags)
|
local mfile, code, msg = nixio.open(cachefile, oflags)
|
||||||
mfile:writeall(jsonc.stringify(result))
|
mfile:writeall(jsonc.stringify(result))
|
||||||
mfile:close()
|
mfile:close()
|
||||||
@@ -327,11 +326,10 @@ local function get_installed_and_cache()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function store_action(param)
|
function store_action(param)
|
||||||
local metadir = "/usr/lib/opkg/meta"
|
local metadir = "/tmp/run/istore-meta/meta"
|
||||||
local metapkgpre = "app-meta-"
|
local metapkgpre = "app-meta-"
|
||||||
local code, out, err, ret
|
local code, out, err, ret
|
||||||
local fs = require "nixio.fs"
|
local fs = require "nixio.fs"
|
||||||
local ipkg = require "luci.model.ipkg"
|
|
||||||
local jsonc = require "luci.jsonc"
|
local jsonc = require "luci.jsonc"
|
||||||
local json_parse = jsonc.parse
|
local json_parse = jsonc.parse
|
||||||
local action = param.action or ""
|
local action = param.action or ""
|
||||||
@@ -342,18 +340,22 @@ function store_action(param)
|
|||||||
luci.http.status(400, "Bad Request")
|
luci.http.status(400, "Bad Request")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local metapkg = metapkgpre .. pkg
|
|
||||||
local meta = {}
|
local meta = {}
|
||||||
local metadata = fs.readfile(metadir .. "/" .. pkg .. ".json")
|
local metafile = metadir .. "/" .. pkg .. ".json"
|
||||||
|
local metadata = fs.readfile(metafile)
|
||||||
|
|
||||||
if metadata ~= nil then
|
if metadata ~= nil then
|
||||||
meta = json_parse(metadata) or {}
|
meta = json_parse(metadata) or {}
|
||||||
end
|
end
|
||||||
meta.installed = false
|
meta.installed = false
|
||||||
local status = ipkg.status(metapkg)
|
local time = nil
|
||||||
if next(status) ~= nil then
|
local istat = fs.stat(metafile)
|
||||||
|
if istat ~= nil then
|
||||||
|
time = istat["mtime"]
|
||||||
|
end
|
||||||
|
if time ~= nil then
|
||||||
meta.installed=true
|
meta.installed=true
|
||||||
meta.time=tonumber(status[metapkg]["Installed-Time"])
|
meta.time=tonumber(time)
|
||||||
end
|
end
|
||||||
|
|
||||||
ret = meta
|
ret = meta
|
||||||
@@ -571,7 +573,7 @@ function entrysh()
|
|||||||
errors[#errors+1] = {app=meta.name, code=500, msg="json parse failed: " .. o}
|
errors[#errors+1] = {app=meta.name, code=500, msg="json parse failed: " .. o}
|
||||||
else
|
else
|
||||||
results[#results+1] = status
|
results[#results+1] = status
|
||||||
local oflags = nixio.open_flags("rdwr", "creat")
|
local oflags = nixio.open_flags("wronly", "creat", "trunc")
|
||||||
local mfile, code, msg = nixio.open(cachefile, oflags)
|
local mfile, code, msg = nixio.open(cachefile, oflags)
|
||||||
mfile:writeall(jsonc.stringify(status))
|
mfile:writeall(jsonc.stringify(status))
|
||||||
mfile:close()
|
mfile:close()
|
||||||
|
|||||||
+221
-56
@@ -10,25 +10,46 @@ if [ "${action:0:9}" = "AUTOCONF=" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
IS_ROOT=/tmp/is-root
|
IS_ROOT=/tmp/is-root
|
||||||
DL_DIR=${IS_ROOT}/tmp/dl
|
TMP_SELF_COPY=/var/run/cloned-is-opkg
|
||||||
LISTS_DIR_O=/tmp/opkg-lists
|
USE_APK=false
|
||||||
LISTS_DIR=${IS_ROOT}${LISTS_DIR_O}
|
|
||||||
OPKG_CONF_DIR=${IS_ROOT}/etc/opkg
|
|
||||||
OPKG_CONF_DIR_M=${IS_ROOT}/etc/opkg_m
|
|
||||||
FEEDS_SERVER=https://istore.istoreos.com/repo
|
|
||||||
FEEDS_SERVER_MIRRORS="https://repo.istoreos.com/repo"
|
|
||||||
DISABLE_MIRROR=false
|
DISABLE_MIRROR=false
|
||||||
ARCH=`sed -n -e 's/^Architecture: *\([^ ]\+\) *$/\1/p' /rom/usr/lib/opkg/info/libc.control /usr/lib/opkg/info/libc.control 2>/dev/null | head -1`
|
if [[ -s /etc/apk/arch ]]; then
|
||||||
|
USE_APK=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $USE_APK; then
|
||||||
|
LISTS_DIR=${IS_ROOT}/apk
|
||||||
|
APK_CONF=${IS_ROOT}/etc/apk.conf
|
||||||
|
FEEDS_SERVER=https://istore.istoreos.com/repo-apk
|
||||||
|
FEEDS_SERVER_MIRRORS="https://repo.istoreos.com/repo-apk"
|
||||||
|
ISTORE_INDEX=$FEEDS_SERVER/all/store/packages.adb
|
||||||
|
META_DIR=/lib/apk
|
||||||
|
|
||||||
|
ARCH=`cat /rom/etc/apk/arch /etc/apk/arch 2>/dev/null | head -1`
|
||||||
|
else
|
||||||
|
LISTS_DIR_O=/tmp/opkg-lists
|
||||||
|
LISTS_DIR=${IS_ROOT}${LISTS_DIR_O}
|
||||||
|
OPKG_CONF_DIR=${IS_ROOT}/etc/opkg
|
||||||
|
OPKG_CONF_DIR_M=${IS_ROOT}/etc/opkg_m
|
||||||
|
FEEDS_SERVER=https://istore.istoreos.com/repo
|
||||||
|
FEEDS_SERVER_MIRRORS="https://repo.istoreos.com/repo"
|
||||||
|
ISTORE_INDEX=$FEEDS_SERVER/all/store/Packages.gz
|
||||||
|
META_DIR=/usr/lib/opkg
|
||||||
|
|
||||||
|
ARCH=`sed -n -e 's/^Architecture: *\([^ ]\+\) *$/\1/p' /rom/usr/lib/opkg/info/libc.control /usr/lib/opkg/info/libc.control 2>/dev/null | head -1`
|
||||||
|
fi
|
||||||
|
|
||||||
# for istore self upgrade
|
# for istore self upgrade
|
||||||
ISTORE_PKG=luci-app-store
|
ISTORE_PKG=luci-app-store
|
||||||
ISTORE_DEP_PKGS="luci-lib-taskd luci-lib-xterm taskd"
|
ISTORE_DEP_PKGS="luci-lib-taskd luci-lib-xterm taskd"
|
||||||
ISTORE_INDEX=https://istore.istoreos.com/repo/all/store/Packages.gz
|
|
||||||
|
|
||||||
NEWLINE=$'\n'
|
NEWLINE=$'\n'
|
||||||
|
|
||||||
is_init() {
|
is_init() {
|
||||||
mkdir -p ${DL_DIR} ${LISTS_DIR} ${IS_ROOT}/etc ${IS_ROOT}/var
|
mkdir -p ${LISTS_DIR} ${IS_ROOT}/etc ${IS_ROOT}/var
|
||||||
|
if $USE_APK; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
cat /etc/opkg.conf | grep -Fv lists_dir | grep -Fv check_signature > ${IS_ROOT}/etc/opkg.conf
|
cat /etc/opkg.conf | grep -Fv lists_dir | grep -Fv check_signature > ${IS_ROOT}/etc/opkg.conf
|
||||||
|
|
||||||
@@ -44,35 +65,71 @@ is_init() {
|
|||||||
[ -e ${IS_ROOT}/var/lock ] || ln -s /var/lock ${IS_ROOT}/var/lock
|
[ -e ${IS_ROOT}/var/lock ] || ln -s /var/lock ${IS_ROOT}/var/lock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apk_wrap() {
|
||||||
|
APK_CONFIG=${APK_CONF} apk "$@"
|
||||||
|
}
|
||||||
|
|
||||||
opkg_wrap() {
|
opkg_wrap() {
|
||||||
OPKG_CONF_DIR=${OPKG_CONF_DIR} opkg -f ${IS_ROOT}/etc/opkg.conf "$@"
|
OPKG_CONF_DIR=${OPKG_CONF_DIR} opkg -f ${IS_ROOT}/etc/opkg.conf "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
opkg_wrap_mirrors() {
|
simple_apk_opkg() {
|
||||||
|
local action="$1"
|
||||||
|
shift
|
||||||
|
if $USE_APK; then
|
||||||
|
case "$action" in
|
||||||
|
"install")
|
||||||
|
action=add
|
||||||
|
;;
|
||||||
|
"remove")
|
||||||
|
action=del
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
apk_wrap "$action" "$@"
|
||||||
|
else
|
||||||
|
opkg_wrap "$action" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_in_mirrors() {
|
||||||
local server
|
local server
|
||||||
local file
|
|
||||||
if ! $DISABLE_MIRROR; then
|
if ! $DISABLE_MIRROR; then
|
||||||
|
local newpath="/usr/libexec/istore/mirror-bin:$PATH"
|
||||||
for server in $FEEDS_SERVER_MIRRORS ; do
|
for server in $FEEDS_SERVER_MIRRORS ; do
|
||||||
rm -rf "${OPKG_CONF_DIR_M}" 2>/dev/null
|
|
||||||
mkdir -p "${OPKG_CONF_DIR_M}" 2>/dev/null
|
|
||||||
ls "${OPKG_CONF_DIR}/" | while read; do
|
|
||||||
file="$REPLY"
|
|
||||||
if [ -f "${OPKG_CONF_DIR}/$file" -a "${file: -5}" = ".conf" ]; then
|
|
||||||
sed "s#$FEEDS_SERVER/#$server/#g" "${OPKG_CONF_DIR}/$file" >"${OPKG_CONF_DIR_M}/$file"
|
|
||||||
touch -r "${OPKG_CONF_DIR}/$file" "${OPKG_CONF_DIR_M}/$file" 2>/dev/null
|
|
||||||
else
|
|
||||||
cp -a "${OPKG_CONF_DIR}/$file" "${OPKG_CONF_DIR_M}/"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "Try mirror server $server"
|
echo "Try mirror server $server"
|
||||||
OPKG_CONF_DIR=${OPKG_CONF_DIR_M} opkg -f ${IS_ROOT}/etc/opkg.conf "$@" && return 0
|
PATH="$newpath" IS_ORIG_SERVER=$FEEDS_SERVER IS_MIRROR_SERVER=$server "$@" && return 0
|
||||||
done
|
done
|
||||||
DISABLE_MIRROR=true
|
DISABLE_MIRROR=true
|
||||||
fi
|
fi
|
||||||
echo "Try origin server $FEEDS_SERVER"
|
echo "Try origin server $FEEDS_SERVER"
|
||||||
OPKG_CONF_DIR=${OPKG_CONF_DIR} opkg -f ${IS_ROOT}/etc/opkg.conf "$@"
|
"$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apk_wrap_mirrors() {
|
||||||
|
do_in_mirrors apk_wrap "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
opkg_wrap_mirrors() {
|
||||||
|
do_in_mirrors opkg_wrap "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_install_in_mirrors() {
|
||||||
|
if $USE_APK; then
|
||||||
|
apk_wrap_mirrors add "$@"
|
||||||
|
else
|
||||||
|
opkg_wrap_mirrors install "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_upgrade_in_mirrors() {
|
||||||
|
if $USE_APK; then
|
||||||
|
apk_wrap_mirrors upgrade "$@"
|
||||||
|
else
|
||||||
|
opkg_wrap_mirrors upgrade "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
alias fcurl='curl -L --fail --show-error'
|
alias fcurl='curl -L --fail --show-error'
|
||||||
|
|
||||||
check_space() {
|
check_space() {
|
||||||
@@ -91,14 +148,41 @@ update() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Fetch feed list for ${ARCH}"
|
echo "Fetch feed list for ${ARCH}"
|
||||||
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
|
if $USE_APK; then
|
||||||
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
|
rm -rf ${IS_ROOT}/var/tmp ${IS_ROOT}/etc/arch ${IS_ROOT}/etc/istore.pem
|
||||||
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" || \
|
mkdir -p ${IS_ROOT}/var/tmp
|
||||||
return 1
|
fcurl --no-progress-meter -o ${IS_ROOT}/var/tmp/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${IS_ROOT}/var/tmp/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${IS_ROOT}/var/tmp/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${IS_ROOT}/etc/arch "${FEEDS_SERVER}/${ARCH}/archlist.conf" || \
|
||||||
|
return 1
|
||||||
|
echo "cache-dir $LISTS_DIR" > ${APK_CONF}
|
||||||
|
cat ${IS_ROOT}/var/tmp/arch.conf ${IS_ROOT}/var/tmp/all.conf ${IS_ROOT}/var/tmp/meta.conf >> ${APK_CONF}
|
||||||
|
local line
|
||||||
|
while read -r line; do
|
||||||
|
if [[ -n "$line" ]]; then
|
||||||
|
grep -Fxq "$line" /etc/apk/arch || echo "$line" >> /etc/apk/arch
|
||||||
|
fi
|
||||||
|
done < ${IS_ROOT}/etc/arch
|
||||||
|
sync /etc/apk/arch
|
||||||
|
if [[ ! -s /etc/apk/keys/istore.pem && ! -s /etc/apk/keys/istore-tmp.pem ]]; then
|
||||||
|
fcurl --no-progress-meter -o ${IS_ROOT}/etc/istore.pem "${FEEDS_SERVER}/istore-apk.pem" && \
|
||||||
|
cp -a ${IS_ROOT}/etc/istore.pem /etc/apk/keys/istore-tmp.pem || \
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
|
||||||
|
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" || \
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Update feeds index"
|
echo "Update feeds index"
|
||||||
opkg -f ${IS_ROOT}/etc/opkg_o.conf --offline-root ${IS_ROOT} update
|
if $USE_APK; then
|
||||||
|
apk_wrap update
|
||||||
|
else
|
||||||
|
opkg -f ${IS_ROOT}/etc/opkg_o.conf --offline-root ${IS_ROOT} update
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +194,30 @@ update_if_outdate() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_online_version() {
|
||||||
|
local index="$1"
|
||||||
|
local pkg="$2"
|
||||||
|
if $USE_APK; then
|
||||||
|
apk --cache-dir $LISTS_DIR --cache-max-age 0 --repositories-file /dev/null --repository "$index" update >/dev/null 2>&1
|
||||||
|
local ver=$(apk --cache-dir $LISTS_DIR --cache-max-age 0 --repositories-file /dev/null --repository "$index" query --available --fields version "$pkg" 2>/dev/null | grep -Fm1 'Version: ' | sed 's/^Version: //')
|
||||||
|
echo "${ver:-0.2.0-r1}"
|
||||||
|
else
|
||||||
|
curl --connect-timeout 2 --max-time 5 -s "$index" | gunzip | grep -FA10 "Package: $pkg" | grep -Fm1 'Version: ' | sed 's/^Version: //'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_local_version() {
|
||||||
|
local pkg="$1"
|
||||||
|
if $USE_APK; then
|
||||||
|
apk query --installed --fields version "$pkg" 2>/dev/null | grep -Fm1 'Version: ' | sed 's/^Version: //'
|
||||||
|
else
|
||||||
|
grep -Fm1 'Version: ' /usr/lib/opkg/info/$pkg.control 2>/dev/null | sed 's/^Version: //'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
check_self_upgrade() {
|
check_self_upgrade() {
|
||||||
local newest=`curl --connect-timeout 2 --max-time 5 -s ${ISTORE_INDEX} | gunzip | grep -FA10 "Package: ${ISTORE_PKG}" | grep -Fm1 'Version: ' | sed 's/^Version: //'`
|
local newest=`get_online_version "$ISTORE_INDEX" "$ISTORE_PKG"`
|
||||||
local current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
|
local current=`get_local_version "$ISTORE_PKG"`
|
||||||
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
||||||
echo "Check version failed!" >&2
|
echo "Check version failed!" >&2
|
||||||
exit 255
|
exit 255
|
||||||
@@ -124,13 +229,13 @@ check_self_upgrade() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do_self_upgrade_0() {
|
do_self_upgrade_0() {
|
||||||
opkg_wrap upgrade ${ISTORE_DEP_PKGS} && opkg_wrap upgrade ${ISTORE_PKG}
|
simple_apk_opkg upgrade ${ISTORE_DEP_PKGS} && simple_apk_opkg upgrade ${ISTORE_PKG}
|
||||||
}
|
}
|
||||||
|
|
||||||
do_self_upgrade() {
|
do_self_upgrade() {
|
||||||
check_mtime || return 1
|
check_mtime || return 1
|
||||||
local newest=`curl --connect-timeout 2 --max-time 5 -s ${ISTORE_INDEX} | gunzip | grep -FA10 "Package: ${ISTORE_PKG}" | grep -Fm1 'Version: ' | sed 's/^Version: //'`
|
local newest=`get_online_version "$ISTORE_INDEX" "$ISTORE_PKG"`
|
||||||
local current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
|
local current=`get_local_version "$ISTORE_PKG"`
|
||||||
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
||||||
echo "Check version failed!" >&2
|
echo "Check version failed!" >&2
|
||||||
return 1
|
return 1
|
||||||
@@ -139,7 +244,7 @@ do_self_upgrade() {
|
|||||||
echo "Already the latest version!" >&2
|
echo "Already the latest version!" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if opkg_wrap info ${ISTORE_PKG} | grep -qFm1 "Version: $newest"; then
|
if $USE_APK || opkg_wrap info ${ISTORE_PKG} | grep -qFm1 "Version: $newest"; then
|
||||||
do_self_upgrade_0 && return 0
|
do_self_upgrade_0 && return 0
|
||||||
update_if_outdate || return 1
|
update_if_outdate || return 1
|
||||||
do_self_upgrade_0
|
do_self_upgrade_0
|
||||||
@@ -150,7 +255,13 @@ do_self_upgrade() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_mtime() {
|
check_mtime() {
|
||||||
find ${OPKG_CONF_DIR}/arch.conf -mtime -1 2>/dev/null | grep -q . || update
|
local file
|
||||||
|
if $USE_APK; then
|
||||||
|
file=${APK_CONF}
|
||||||
|
else
|
||||||
|
file=${OPKG_CONF_DIR}/arch.conf
|
||||||
|
fi
|
||||||
|
find "$file" -mtime -1 2>/dev/null | grep -q . || update
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped_in_update() {
|
wrapped_in_update() {
|
||||||
@@ -160,6 +271,16 @@ wrapped_in_update() {
|
|||||||
eval "$@"
|
eval "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refresh_installed() {
|
||||||
|
local pkg
|
||||||
|
for pkg in $@; do
|
||||||
|
[[ $pkg == app-meta-* ]] || continue
|
||||||
|
pkg="$META_DIR/meta/${pkg#app-meta-}.json"
|
||||||
|
[[ -f "$pkg" ]] && touch "$pkg"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
step_upgrade() {
|
step_upgrade() {
|
||||||
local pkg
|
local pkg
|
||||||
local pkgs=""
|
local pkgs=""
|
||||||
@@ -172,10 +293,11 @@ step_upgrade() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ -n "$pkgs" ]; then
|
if [ -n "$pkgs" ]; then
|
||||||
opkg_wrap_mirrors upgrade $pkgs || return 1
|
do_upgrade_in_mirrors $pkgs || return 1
|
||||||
fi
|
fi
|
||||||
if [ -n "$metapkg" ]; then
|
if [ -n "$metapkg" ]; then
|
||||||
opkg_wrap_mirrors upgrade $metapkg || return 1
|
do_upgrade_in_mirrors $metapkg || return 1
|
||||||
|
refresh_installed $metapkg
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -183,7 +305,7 @@ step_upgrade() {
|
|||||||
new_upgrade() {
|
new_upgrade() {
|
||||||
check_mtime || return 1
|
check_mtime || return 1
|
||||||
local metapkg=`echo "$@" | sed 's/ /\n/g' | grep -F app-meta-`
|
local metapkg=`echo "$@" | sed 's/ /\n/g' | grep -F app-meta-`
|
||||||
if [ -z "$metapkg" ] || opkg_wrap info $metapkg | grep -qF not-installed ; then
|
if [[ -z "$metapkg" ]] || [[ -z "$(get_local_version $metapkg)" ]]; then
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
update_if_outdate
|
update_if_outdate
|
||||||
@@ -192,7 +314,11 @@ new_upgrade() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
opkg_wrap --autoremove --force-removal-of-dependent-packages remove "$@"
|
if $USE_APK; then
|
||||||
|
apk_wrap del -r "$@"
|
||||||
|
else
|
||||||
|
opkg_wrap --autoremove --force-removal-of-dependent-packages remove "$@"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
autoconf_to_env() {
|
autoconf_to_env() {
|
||||||
@@ -224,16 +350,29 @@ try_autoconf() {
|
|||||||
PATH="$CLEANPATH" /usr/libexec/istorea/${ISTORE_AUTOCONF}.sh
|
PATH="$CLEANPATH" /usr/libexec/istorea/${ISTORE_AUTOCONF}.sh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try_upgrade_depends() {
|
try_upgrade_depends() {
|
||||||
local pkg="$1"
|
local pkg="$1"
|
||||||
if [[ $pkg == app-meta-* ]]; then
|
if [[ $pkg == app-meta-* ]]; then
|
||||||
local deps=$(grep '^Depends: ' /usr/lib/opkg/info/$pkg.control | busybox sed -e 's/^Depends: //' -e 's/,/\n/g' -e 's/ //g' | grep -vFw libc | xargs echo)
|
local deps
|
||||||
[ -z "$deps" ] || opkg_wrap_mirrors install $deps
|
if $USE_APK; then
|
||||||
|
deps=$(apk --repositories-file /dev/null info -R "$pkg" | tail +2 | grep -vFw libc | xargs echo)
|
||||||
|
else
|
||||||
|
deps=$(grep '^Depends: ' "/usr/lib/opkg/info/$pkg.control" | busybox sed -e 's/^Depends: //' -e 's/,/\n/g' -e 's/ //g' | grep -vFw libc | xargs echo)
|
||||||
|
fi
|
||||||
|
[ -z "$deps" ] || do_install_in_mirrors $deps
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
CMD_INSTALLED="opkg list-installed | cut -d' ' -f1 | sort -u"
|
if $USE_APK; then
|
||||||
|
CMD_INSTALLED="apk --repositories-file /dev/null info | cut -d' ' -f1 | sort -u"
|
||||||
|
CMD_INSTALL="apk add --allow-untrusted"
|
||||||
|
else
|
||||||
|
CMD_INSTALLED="opkg list-installed | cut -d' ' -f1 | sort -u"
|
||||||
|
CMD_INSTALL="opkg install"
|
||||||
|
fi
|
||||||
|
|
||||||
dotrun() {
|
dotrun() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
[ -f "$path" ] || { echo "file not found: $path" >&2; return 1; }
|
[ -f "$path" ] || { echo "file not found: $path" >&2; return 1; }
|
||||||
@@ -248,8 +387,8 @@ dotrun() {
|
|||||||
echo "Executing .run file"
|
echo "Executing .run file"
|
||||||
chmod 0755 "$path" && "$path"
|
chmod 0755 "$path" && "$path"
|
||||||
else
|
else
|
||||||
echo "Installing .ipk file"
|
echo "Installing pkg file"
|
||||||
opkg install "$path"
|
$CMD_INSTALL "$path"
|
||||||
fi
|
fi
|
||||||
local RET=$?
|
local RET=$?
|
||||||
rm -f "$path"
|
rm -f "$path"
|
||||||
@@ -286,6 +425,13 @@ run_records() {
|
|||||||
echo "]"
|
echo "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if $USE_APK; then
|
||||||
|
CMD_REMOVE="apk del"
|
||||||
|
else
|
||||||
|
CMD_REMOVE="opkg --autoremove remove"
|
||||||
|
fi
|
||||||
|
|
||||||
unrun() {
|
unrun() {
|
||||||
local id="$1"
|
local id="$1"
|
||||||
[ -s "/usr/share/istore/run-records/$id.txt" ] || { echo "record not found: $id" >&2; return 1; }
|
[ -s "/usr/share/istore/run-records/$id.txt" ] || { echo "record not found: $id" >&2; return 1; }
|
||||||
@@ -295,9 +441,9 @@ unrun() {
|
|||||||
local round max_round=6
|
local round max_round=6
|
||||||
for round in `seq 1 $max_round`; do
|
for round in `seq 1 $max_round`; do
|
||||||
if [ $round == $max_round ]; then
|
if [ $round == $max_round ]; then
|
||||||
opkg --autoremove remove `cat "/tmp/is-root/tmp/to_unrun_$id.txt"`
|
$CMD_REMOVE `cat "/tmp/is-root/tmp/to_unrun_$id.txt"`
|
||||||
else
|
else
|
||||||
opkg --autoremove remove `cat "/tmp/is-root/tmp/to_unrun_$id.txt"` 2>/dev/null && break
|
$CMD_REMOVE `cat "/tmp/is-root/tmp/to_unrun_$id.txt"` 2>/dev/null && break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
sh -c "$CMD_INSTALLED" > "/tmp/is-root/tmp/post_unrun_$id.txt"
|
sh -c "$CMD_INSTALLED" > "/tmp/is-root/tmp/post_unrun_$id.txt"
|
||||||
@@ -327,13 +473,19 @@ usage() {
|
|||||||
echo " do_self_upgrade Upgrade iStore"
|
echo " do_self_upgrade Upgrade iStore"
|
||||||
echo " arch Show libc architecture"
|
echo " arch Show libc architecture"
|
||||||
echo " opkg sys opkg wrap"
|
echo " opkg sys opkg wrap"
|
||||||
echo " dotrun {path} install .run/.ipk"
|
echo " dotrun {path} install .run/.ipk/.apk"
|
||||||
echo " run_records list .run/.ipk install records"
|
echo " run_records list .run/.ipk/.apk install records"
|
||||||
echo " del_record {id} delete .run/.ipk install record"
|
echo " del_record {id} delete .run/.ipk/.apk install record"
|
||||||
echo " unrun {id} Remove installed packages by .run/.ipk"
|
echo " unrun {id} Remove installed packages by .run/.ipk/.apk"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if [[ "$action" = "do_self_upgrade" && "$0" != "$TMP_SELF_COPY" ]]; then
|
||||||
|
echo "copy self $0 to $TMP_SELF_COPY when self upgrade"
|
||||||
|
cp -af "$0" "$TMP_SELF_COPY" || exit 1
|
||||||
|
exec "$TMP_SELF_COPY" do_self_upgrade "$@" || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
is_init >/dev/null 2>&1
|
is_init >/dev/null 2>&1
|
||||||
|
|
||||||
CLEANPATH="$PATH"
|
CLEANPATH="$PATH"
|
||||||
@@ -352,7 +504,7 @@ case $action in
|
|||||||
;;
|
;;
|
||||||
"install")
|
"install")
|
||||||
check_space
|
check_space
|
||||||
wrapped_in_update opkg_wrap_mirrors install "$@" && try_upgrade_depends "$1" && try_autoconf
|
wrapped_in_update do_install_in_mirrors "$@" && refresh_installed "$1" && try_upgrade_depends "$1" && try_autoconf
|
||||||
;;
|
;;
|
||||||
"autoconf")
|
"autoconf")
|
||||||
try_autoconf
|
try_autoconf
|
||||||
@@ -364,10 +516,18 @@ case $action in
|
|||||||
remove "$@" || remove "$@"
|
remove "$@" || remove "$@"
|
||||||
;;
|
;;
|
||||||
"info")
|
"info")
|
||||||
opkg_wrap info "$@"
|
if $USE_APK; then
|
||||||
|
apk_wrap query "$@"
|
||||||
|
else
|
||||||
|
opkg_wrap info "$@"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
"list-upgradable")
|
"list-upgradable")
|
||||||
opkg_wrap list-upgradable
|
if $USE_APK; then
|
||||||
|
apk_wrap query --upgradable --summarize package '*'
|
||||||
|
else
|
||||||
|
opkg_wrap list-upgradable
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
"check_self_upgrade")
|
"check_self_upgrade")
|
||||||
check_self_upgrade
|
check_self_upgrade
|
||||||
@@ -394,6 +554,11 @@ case $action in
|
|||||||
"unrun")
|
"unrun")
|
||||||
unrun "$1"
|
unrun "$1"
|
||||||
;;
|
;;
|
||||||
|
"link_meta")
|
||||||
|
[[ -d "$META_DIR/meta" ]] || mkdir -p "$META_DIR/meta"
|
||||||
|
mkdir -p /tmp/run/istore
|
||||||
|
ln -sf "$META_DIR" /tmp/run/istore-meta
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ boot() {
|
|||||||
local ARCH=`/bin/is-opkg arch`
|
local ARCH=`/bin/is-opkg arch`
|
||||||
generate_store_id $ARCH
|
generate_store_id $ARCH
|
||||||
generate_super_arch $ARCH
|
generate_super_arch $ARCH
|
||||||
|
/bin/is-opkg link_meta
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_store_id() {
|
generate_store_id() {
|
||||||
|
|||||||
@@ -9,4 +9,7 @@ if [ -z "`uci -q get istore.istore.hide_docker`" ] && ! which docker >/dev/null;
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# remove tmp keys that used for self-installation, which is not needed after installation
|
||||||
|
rm -f /etc/apk/keys/istore-tmp.pem
|
||||||
|
|
||||||
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
|
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
|
||||||
@@ -5,11 +5,12 @@
|
|||||||
IS_ROOT=/tmp/is-backup
|
IS_ROOT=/tmp/is-backup
|
||||||
APP_LIST_FILE=/etc/istore/app.list
|
APP_LIST_FILE=/etc/istore/app.list
|
||||||
BACKUP_CONFIG_FILE=/etc/config/istore
|
BACKUP_CONFIG_FILE=/etc/config/istore
|
||||||
|
USE_APK=false
|
||||||
|
[[ -s /etc/apk/arch ]] && USE_APK=true
|
||||||
|
|
||||||
action=${1}
|
action=${1}
|
||||||
shift
|
shift
|
||||||
|
|
||||||
|
|
||||||
is_init() {
|
is_init() {
|
||||||
mkdir -p ${IS_ROOT}
|
mkdir -p ${IS_ROOT}
|
||||||
}
|
}
|
||||||
@@ -90,23 +91,23 @@ ipk_build() {
|
|||||||
|
|
||||||
# if arg is NULL, use light backup, otherwise use local backup
|
# if arg is NULL, use light backup, otherwise use local backup
|
||||||
backup() {
|
backup() {
|
||||||
[ -n "$1" ] && BACKUP_PATH=$1
|
if [ -n "$1" ]; then
|
||||||
|
if $USE_APK; then
|
||||||
|
echo "does not support apk package manager" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
BACKUP_PATH=$1
|
||||||
|
fi
|
||||||
|
|
||||||
#1.add all istore self data to sysupgrade config file,
|
if $USE_APK; then
|
||||||
#sysupgrade will backup/restore it auto when flash new firmware
|
( cd /lib/apk/packages && ls *.list ) | grep '^app-meta-' | sed 's/.list$//' | sort -u > ${IS_ROOT}/istore_installed_package.list
|
||||||
echo "/etc/.app_store.id" > /lib/upgrade/keep.d/luci-app-store
|
else
|
||||||
cat /usr/lib/opkg/info/luci-app-store.list >> /lib/upgrade/keep.d/luci-app-store
|
#write user installed package list to file
|
||||||
echo "/etc/rc.d/S45istore" >> /lib/upgrade/keep.d/luci-app-store
|
opkg_list_installed_packages "userinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/user_installed_package.list
|
||||||
echo "/etc/istore/uci-defaults_bak" >> /lib/upgrade/keep.d/luci-app-store
|
#write installed package list by istore feed to file
|
||||||
echo "${APP_LIST_FILE}" >> /lib/upgrade/keep.d/luci-app-store
|
cat ${IS_ROOT}/user_installed_package.list | \
|
||||||
echo "${BACKUP_CONFIG_FILE}" >> /lib/upgrade/keep.d/luci-app-store
|
grep '^app-meta-' > ${IS_ROOT}/istore_installed_package.list
|
||||||
|
fi
|
||||||
#write user installed package list to file
|
|
||||||
opkg_list_installed_packages "userinstalled" 2>/dev/null | sort -u > ${IS_ROOT}/user_installed_package.list
|
|
||||||
|
|
||||||
#write installed package list by istore feed to file
|
|
||||||
cat ${IS_ROOT}/user_installed_package.list | \
|
|
||||||
grep '^app-meta-' > ${IS_ROOT}/istore_installed_package.list
|
|
||||||
|
|
||||||
#if no input backup path, only back app.list
|
#if no input backup path, only back app.list
|
||||||
mkdir -p /etc/istore
|
mkdir -p /etc/istore
|
||||||
@@ -186,6 +187,10 @@ backup() {
|
|||||||
# if arg is NULL, use light backup, otherwise use local backup
|
# if arg is NULL, use light backup, otherwise use local backup
|
||||||
restore() {
|
restore() {
|
||||||
if [ -n "$1" ]; then
|
if [ -n "$1" ]; then
|
||||||
|
if $USE_APK; then
|
||||||
|
echo "does not support apk package manager" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
BACKUP_PATH_FILE=$1
|
BACKUP_PATH_FILE=$1
|
||||||
else
|
else
|
||||||
echo "install package by ${APP_LIST_FILE}"
|
echo "install package by ${APP_LIST_FILE}"
|
||||||
@@ -233,7 +238,7 @@ restore() {
|
|||||||
get_support_backup_features() {
|
get_support_backup_features() {
|
||||||
echo "light_backup"
|
echo "light_backup"
|
||||||
#istore custom img mean support local_backup
|
#istore custom img mean support local_backup
|
||||||
if [ -f /etc/istore_img_flag ];then
|
if ! $USE_APK; then
|
||||||
echo "local_backup"
|
echo "local_backup"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
newpath=$(echo ":$PATH:" | sed -e 's#:/usr/libexec/istore/mirror-bin:#:#g' -e 's/^:*//' -e 's/:*$//')
|
||||||
|
[ -z "$newpath" ] && newpath="/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
|
export PATH="$newpath"
|
||||||
|
|
||||||
|
# if url starts with $IS_ORIG_SERVER, replace it with $IS_MIRROR_SERVER
|
||||||
|
if [ -n "$IS_ORIG_SERVER" ] && [ -n "$IS_MIRROR_SERVER" ]; then
|
||||||
|
n=$#
|
||||||
|
while [ "$n" -gt 0 ]; do
|
||||||
|
i="$1"
|
||||||
|
shift
|
||||||
|
case "$i" in
|
||||||
|
"$IS_ORIG_SERVER"/*.apk|\
|
||||||
|
"$IS_ORIG_SERVER"/*.ipk)
|
||||||
|
i="$IS_MIRROR_SERVER/${i#$IS_ORIG_SERVER/}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set -- "$@" "$i"
|
||||||
|
n=$((n - 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
export -n IS_ORIG_SERVER IS_MIRROR_SERVER
|
||||||
|
|
||||||
|
exec curl "$@"
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
newpath=$(echo ":$PATH:" | sed -e 's#:/usr/libexec/istore/mirror-bin:#:#g' -e 's/^:*//' -e 's/:*$//')
|
||||||
|
[ -z "$newpath" ] && newpath="/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
|
export PATH="$newpath"
|
||||||
|
|
||||||
|
# if url starts with $IS_ORIG_SERVER, replace it with $IS_MIRROR_SERVER
|
||||||
|
if [ -n "$IS_ORIG_SERVER" ] && [ -n "$IS_MIRROR_SERVER" ]; then
|
||||||
|
n=$#
|
||||||
|
while [ "$n" -gt 0 ]; do
|
||||||
|
i="$1"
|
||||||
|
shift
|
||||||
|
case "$i" in
|
||||||
|
"$IS_ORIG_SERVER"/*.apk|\
|
||||||
|
"$IS_ORIG_SERVER"/*.ipk)
|
||||||
|
i="$IS_MIRROR_SERVER/${i#$IS_ORIG_SERVER/}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set -- "$@" "$i"
|
||||||
|
n=$((n - 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
export -n IS_ORIG_SERVER IS_MIRROR_SERVER
|
||||||
|
|
||||||
|
exec wget "$@"
|
||||||
@@ -10,13 +10,17 @@ include $(TOPDIR)/feeds/luci/luci.mk
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
[ -n "$(APP_STORE_VERSION)" ]
|
[ -n "$(APP_STORE_VERSION)" ]
|
||||||
mkdir -p "$(DESTDIR)/www/luci-static"
|
mkdir -p "$(DESTDIR)/www/luci-static" "$(DESTDIR)/etc"
|
||||||
cp -a "$(FRONTEND_DIST)/luci-static/istore" "$(DESTDIR)/www/luci-static/"
|
cp -a "$(FRONTEND_DIST)/luci-static/istore" "$(DESTDIR)/www/luci-static/"
|
||||||
$(SED) 's#\.js"#.js?v=$(APP_STORE_VERSION)"#g' "$(DESTDIR)/www/luci-static/istore/index.js"
|
$(SED) 's#\.js"#.js?v=$(APP_STORE_VERSION)"#g' "$(DESTDIR)/www/luci-static/istore/index.js"
|
||||||
mkdir -p "$(DESTDIR)/etc/opkg/keys"
|
|
||||||
echo "$(APP_STORE_VERSION)" > "$(DESTDIR)/etc/.app_store.version"
|
echo "$(APP_STORE_VERSION)" > "$(DESTDIR)/etc/.app_store.version"
|
||||||
cp -a ./compat.conf "$(DESTDIR)/etc/opkg/compatfeeds.conf"
|
ifneq ($(USE_APK),)
|
||||||
|
cp -pR ./root-apk/* "$(DESTDIR)/"
|
||||||
|
else
|
||||||
|
cp -pR ./root-opkg/* "$(DESTDIR)/"
|
||||||
|
mkdir -p "$(DESTDIR)/etc/opkg/keys"
|
||||||
cp -a ./key-build.pub $(DESTDIR)/etc/opkg/keys/`$(STAGING_DIR_HOST)/bin/usign -F -p ./key-build.pub`
|
cp -a ./key-build.pub $(DESTDIR)/etc/opkg/keys/`$(STAGING_DIR_HOST)/bin/usign -F -p ./key-build.pub`
|
||||||
|
endif
|
||||||
mkdir -p "$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n"
|
mkdir -p "$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n"
|
||||||
$(foreach lang,$(LUCI_LANGUAGES),$(foreach po,$(wildcard ${CURDIR}/po/$(lang)/*.po), \
|
$(foreach lang,$(LUCI_LANGUAGES),$(foreach po,$(wildcard ${CURDIR}/po/$(lang)/*.po), \
|
||||||
po2lmo $(po) \
|
po2lmo $(po) \
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEybK5eaXsrK06Thc1dbA3FC45Tlp8
|
||||||
|
8X5Z831vU6JggiV6tJiwI/B05IsdklTaZE8SYh0XCHcnXSjzRMLtarBisg==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
https://istore.istoreos.com/repo-apk/all/compat/packages.adb
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_VERSION:=1.0.17-r1
|
PKG_VERSION:=1.0.18-r1
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI support for SysTools
|
LUCI_TITLE:=LuCI support for SysTools
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ FCURL="curl --fail --show-error"
|
|||||||
|
|
||||||
curl -V >/dev/null 2>&1 || {
|
curl -V >/dev/null 2>&1 || {
|
||||||
echo "prereq: install curl"
|
echo "prereq: install curl"
|
||||||
opkg info curl | grep -Fqm1 curl || opkg update
|
if [ -f /etc/apk/arch ]; then
|
||||||
opkg install curl
|
apk add curl
|
||||||
|
else
|
||||||
|
opkg info curl | grep -Fqm1 curl || opkg update
|
||||||
|
opkg install curl
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
IPK=`$FCURL "$ISTORE_REPO/Packages.gz" | zcat | grep -m1 '^Filename: luci-app-store.*\.ipk$' | sed -n -e 's/^Filename: \(.\+\)$/\1/p'`
|
IPK=`$FCURL "$ISTORE_REPO/Packages.gz" | zcat | grep -m1 '^Filename: luci-app-store.*\.ipk$' | sed -n -e 's/^Filename: \(.\+\)$/\1/p'`
|
||||||
@@ -19,7 +23,7 @@ $FCURL "$ISTORE_REPO/$IPK" | tar -xzO ./data.tar.gz | tar -xzO ./bin/is-opkg > /
|
|||||||
chmod 755 /tmp/is-opkg
|
chmod 755 /tmp/is-opkg
|
||||||
/tmp/is-opkg update
|
/tmp/is-opkg update
|
||||||
# /tmp/is-opkg install taskd
|
# /tmp/is-opkg install taskd
|
||||||
/tmp/is-opkg opkg install --force-reinstall luci-lib-taskd luci-lib-xterm
|
/tmp/is-opkg install --force-reinstall luci-lib-taskd luci-lib-xterm
|
||||||
/tmp/is-opkg opkg install --force-reinstall luci-app-store || exit $?
|
/tmp/is-opkg install --force-reinstall luci-app-store || exit $?
|
||||||
[ -s "/etc/init.d/tasks" ] || /tmp/is-opkg opkg install --force-reinstall taskd
|
[ -s "/etc/init.d/tasks" ] || /tmp/is-opkg install --force-reinstall taskd
|
||||||
[ -s "/usr/lib/lua/luci/cbi.lua" ] || /tmp/is-opkg opkg install luci-compat >/dev/null 2>&1
|
[ -s "/usr/lib/lua/luci/cbi.lua" ] || /tmp/is-opkg install luci-compat >/dev/null 2>&1
|
||||||
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
LUCI_NAME:=luci-app-tinyfilemanager
|
LUCI_NAME:=luci-app-tinyfilemanager
|
||||||
PKG_VERSION:=2.6
|
PKG_VERSION:=2.6
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI Tiny File Manager
|
LUCI_TITLE:=LuCI Tiny File Manager
|
||||||
LUCI_DEPENDS:=+php8 +php8-cgi +php8-fastcgi +php8-fpm +php8-mod-session +php8-mod-ctype +php8-mod-fileinfo +php8-mod-zip +php8-mod-iconv +php8-mod-mbstring +coreutils-stat +zoneinfo-asia +bash +curl +tar +inotifywait
|
LUCI_DEPENDS:=+php8 +php8-cgi +php8-fastcgi +php8-fpm +php8-mod-session +php8-mod-ctype +php8-mod-fileinfo +php8-mod-zip +php8-mod-iconv +php8-mod-mbstring +coreutils-stat +zoneinfo-asia +bash +curl +tar +inotifywait
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ return view.extend({
|
|||||||
return fs.exec('/etc/init.d/tinyfilemanager', ['reload'])
|
return fs.exec('/etc/init.d/tinyfilemanager', ['reload'])
|
||||||
.catch((e) => { ui.addNotification(null, E('p', e.message), 'error') });
|
.catch((e) => { ui.addNotification(null, E('p', e.message), 'error') });
|
||||||
};
|
};
|
||||||
if (! has_location)
|
|
||||||
o.description = _('To enable SSL support, you may need to install <b>%s</b><br/>').format(['php-nginx']);
|
|
||||||
|
|
||||||
o = s.option(form.Flag, 'use_auth', _('Enable Authentication'));
|
o = s.option(form.Flag, 'use_auth', _('Enable Authentication'));
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|||||||
@@ -725,20 +725,24 @@ const methods = {
|
|||||||
|
|
||||||
getCPUInfo: {
|
getCPUInfo: {
|
||||||
call: function() {
|
call: function() {
|
||||||
if (!access('/sbin/cpuinfo'))
|
if (access('/sbin/cpuinfo')) {
|
||||||
return {};
|
const fd = popen('/sbin/cpuinfo');
|
||||||
|
if (fd) {
|
||||||
|
let cpuinfo = fd.read('all');
|
||||||
|
if (!cpuinfo)
|
||||||
|
cpuinfo = '?';
|
||||||
|
fd.close();
|
||||||
|
|
||||||
const fd = popen('/sbin/cpuinfo');
|
return { cpuinfo: cpuinfo };
|
||||||
if (fd) {
|
} else {
|
||||||
let cpuinfo = fd.read('all');
|
return { cpuinfo: error() };
|
||||||
if (!cpuinfo)
|
}
|
||||||
cpuinfo = '?';
|
|
||||||
fd.close();
|
|
||||||
|
|
||||||
return { cpuinfo: cpuinfo };
|
|
||||||
} else {
|
|
||||||
return { cpuinfo: error() };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cpuinfo = readfile('/proc/cpuinfo');
|
||||||
|
const m = cpuinfo ? match(cpuinfo, /(^|\n)(Model Name|model name|Hardware|Processor)[ \t]*:[ \t]*([^\n]+)/) : null;
|
||||||
|
|
||||||
|
return m?.[3] ? { cpuinfo: trim(m[3]) } : {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,36 @@
|
|||||||
header {
|
header {
|
||||||
@apply bg-bg sticky top-0 z-40 mb-2;
|
@apply bg-bg sticky top-0 z-40 mb-2 transition-colors duration-[300ms] ease-[cubic-bezier(0.4,0,0.6,1)];
|
||||||
|
|
||||||
& .header-content {
|
& .header-content {
|
||||||
@apply relative flex h-14 items-center justify-between px-6 py-3 max-md:px-4 max-md:py-2;
|
@apply relative flex h-14 items-center justify-between px-6 py-3 max-md:px-4 max-md:py-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-nav-type="mega-menu"] & {
|
[data-nav-type="mega-menu"] & {
|
||||||
|
/* Apple's layer split: the panel that animates GEOMETRY must not carry a
|
||||||
|
backdrop-filter, or every height frame re-rasterises the blur (the old
|
||||||
|
clip-path flicker). So this is a fully-opaque SOLID surface that retracts
|
||||||
|
via height — the drawer close — while the frost lives entirely on the
|
||||||
|
separate .desktop-menu-overlay curtain (opacity/visibility only). Height
|
||||||
|
is driven by --mega-menu-height (set on open, dropped on close in
|
||||||
|
menu-aurora.js); overflow-hidden clips the absolutely-positioned content
|
||||||
|
as the canvas grows and collapses.
|
||||||
|
|
||||||
|
Opacity is NOT animated — it's gated by `visibility` (like the curtain).
|
||||||
|
Fading the panel in left it half-transparent for the whole open, so the
|
||||||
|
header brand/icons showed THROUGH it until opacity settled (the flash).
|
||||||
|
Opaque-from-the-first-frame means the growing height wipes solidly down
|
||||||
|
over the header instead. The closing state keeps the panel visible and
|
||||||
|
above the curtain while height animates down, without re-enabling input. */
|
||||||
& .desktop-menu-container {
|
& .desktop-menu-container {
|
||||||
@apply border-hairline bg-mega-menu-bg pointer-events-none absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden border-b opacity-0 shadow-xl backdrop-blur-lg backdrop-saturate-150 transition-[clip-path,opacity] duration-[250ms] ease-out [will-change:clip-path,opacity] [clip-path:inset(0_0_100%_0)] max-md:hidden;
|
@apply border-hairline bg-mega-menu-bg pointer-events-none invisible absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden border-b shadow-xl transition-[height,visibility] duration-[300ms] ease-[cubic-bezier(0.4,0,0.6,1)] [will-change:height] motion-reduce:transition-none max-md:hidden;
|
||||||
|
|
||||||
|
&.active,
|
||||||
|
&.closing {
|
||||||
|
@apply visible;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
@apply pointer-events-auto opacity-100 [clip-path:inset(0)];
|
@apply pointer-events-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Category panels live inside the container, not the nav items: the
|
/* Category panels live inside the container, not the nav items: the
|
||||||
@@ -31,7 +51,7 @@ header {
|
|||||||
is a pure cross-fade. Below lg the side tracks drop and only the
|
is a pure cross-fade. Below lg the side tracks drop and only the
|
||||||
links column remains (tablet → just the submenu). The panel scrolls
|
links column remains (tablet → just the submenu). The panel scrolls
|
||||||
internally when its content exceeds the viewport below the header. */
|
internally when its content exceeds the viewport below the header. */
|
||||||
@apply pointer-events-none absolute inset-x-0 top-14 mx-auto grid max-h-[calc(100dvh-3.5rem)] max-w-[72rem] translate-y-1.5 grid-cols-[12rem_minmax(0,1fr)_13rem] gap-x-8 overflow-y-auto overscroll-contain px-10 pt-7 pb-8 opacity-0 transition-[opacity,transform] duration-[250ms] ease-out max-lg:max-w-[44rem] max-lg:grid-cols-1 max-lg:px-8;
|
@apply pointer-events-none absolute inset-x-0 top-14 mx-auto grid max-h-[calc(100dvh-3.5rem)] max-w-[72rem] translate-y-1.5 grid-cols-[12rem_minmax(0,1fr)_13rem] gap-x-8 overflow-y-auto overscroll-contain px-10 pt-13 pb-8 opacity-0 transition-[opacity,transform] duration-[250ms] ease-out max-lg:max-w-[44rem] max-lg:grid-cols-1 max-lg:px-8;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
@apply pointer-events-auto translate-y-0 opacity-100;
|
@apply pointer-events-auto translate-y-0 opacity-100;
|
||||||
@@ -136,8 +156,12 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-nav-type="mega-menu"] &:has(.desktop-menu-container.active) {
|
/* When a category is open the bar lifts to the panel's opened colour so the
|
||||||
@apply z-70;
|
header and the mega-menu read as one continuous surface — no tonal seam
|
||||||
|
where the bar (bg) meets the panel (mega-menu-bg). */
|
||||||
|
[data-nav-type="mega-menu"]
|
||||||
|
&:has(.desktop-menu-container:is(.active, .closing)) {
|
||||||
|
@apply bg-mega-menu-bg z-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
--danger-surface: oklch(94% 0.05 25);
|
--danger-surface: oklch(94% 0.05 25);
|
||||||
--danger-surface-hover: oklch(90% 0.05 25);
|
--danger-surface-hover: oklch(90% 0.05 25);
|
||||||
--scrim: oklch(0% 0 0 / 0.6);
|
--scrim: oklch(0% 0 0 / 0.6);
|
||||||
--mega-menu-bg: oklch(98.3% 0.003 264 / 0.66);
|
--mega-menu-bg: oklch(98.3% 0.003 264);
|
||||||
|
--mega-menu-scrim: oklch(0% 0 0 / 0.32);
|
||||||
--app-shadow-sm: 0 1px 3px oklch(0 0 0 / 0.06), 0 1px 2px oklch(0 0 0 / 0.04);
|
--app-shadow-sm: 0 1px 3px oklch(0 0 0 / 0.06), 0 1px 2px oklch(0 0 0 / 0.04);
|
||||||
--app-shadow-md: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 3px oklch(0 0 0 / 0.04);
|
--app-shadow-md: 0 4px 16px oklch(0 0 0 / 0.08), 0 1px 3px oklch(0 0 0 / 0.04);
|
||||||
--app-shadow-lg: 0 12px 32px oklch(0 0 0 / 0.12);
|
--app-shadow-lg: 0 12px 32px oklch(0 0 0 / 0.12);
|
||||||
@@ -67,15 +68,16 @@
|
|||||||
--brand-subtle: oklch(20.52% 0.0287 219.4);
|
--brand-subtle: oklch(20.52% 0.0287 219.4);
|
||||||
--brand-subtle-hover: oklch(24.52% 0.0287 219.4);
|
--brand-subtle-hover: oklch(24.52% 0.0287 219.4);
|
||||||
--focus-ring: oklch(60% 0.13 188.7 / 0.6);
|
--focus-ring: oklch(60% 0.13 188.7 / 0.6);
|
||||||
--progress-start: oklch(43.18% 0.0865 166.9);
|
--progress-start: oklch(44.78% 0.0865 193.2);
|
||||||
--progress-end: oklch(62.1% 0.145 189.6);
|
--progress-end: oklch(0.6 0.13 188.745);
|
||||||
--info-surface: oklch(32% 0.05 255);
|
--info-surface: oklch(32% 0.05 255);
|
||||||
--warning-surface: oklch(33% 0.06 80);
|
--warning-surface: oklch(33% 0.06 80);
|
||||||
--success-surface: oklch(30% 0.05 158);
|
--success-surface: oklch(30% 0.05 158);
|
||||||
--danger-surface: oklch(32% 0.08 22);
|
--danger-surface: oklch(32% 0.08 22);
|
||||||
--danger-surface-hover: oklch(36% 0.08 22);
|
--danger-surface-hover: oklch(36% 0.08 22);
|
||||||
--scrim: oklch(0% 0 0 / 0.6);
|
--scrim: oklch(0% 0 0 / 0.6);
|
||||||
--mega-menu-bg: oklch(23% 0.02 264 / 0.62);
|
--mega-menu-bg: oklch(16.5% 0.02 264);
|
||||||
|
--mega-menu-scrim: oklch(0% 0 0 / 0.5);
|
||||||
--app-shadow-sm: 0 4px 12px oklch(0 0 0 / 0.3);
|
--app-shadow-sm: 0 4px 12px oklch(0 0 0 / 0.3);
|
||||||
--app-shadow-md: 0 10px 28px oklch(0 0 0 / 0.42);
|
--app-shadow-md: 0 10px 28px oklch(0 0 0 / 0.42);
|
||||||
--app-shadow-lg: 0 20px 48px oklch(0 0 0 / 0.55);
|
--app-shadow-lg: 0 20px 48px oklch(0 0 0 / 0.55);
|
||||||
@@ -111,6 +113,7 @@
|
|||||||
--color-danger-surface-hover: var(--danger-surface-hover);
|
--color-danger-surface-hover: var(--danger-surface-hover);
|
||||||
--color-scrim: var(--scrim);
|
--color-scrim: var(--scrim);
|
||||||
--color-mega-menu-bg: var(--mega-menu-bg);
|
--color-mega-menu-bg: var(--mega-menu-bg);
|
||||||
|
--color-mega-menu-scrim: var(--mega-menu-scrim);
|
||||||
|
|
||||||
--shadow-sm: var(--app-shadow-sm);
|
--shadow-sm: var(--app-shadow-sm);
|
||||||
--shadow-md: var(--app-shadow-md);
|
--shadow-md: var(--app-shadow-md);
|
||||||
|
|||||||
@@ -69,11 +69,18 @@ body.mobile-navigation-open > header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-nav-type="mega-menu"] {
|
[data-nav-type="mega-menu"] {
|
||||||
|
/* Apple's curtain technique: backdrop-filter is DECLARED here permanently and
|
||||||
|
never toggled. Gating the blur on `.active` snapped the page from blurred
|
||||||
|
to sharp the instant the menu closed, while the dim was still fading — the
|
||||||
|
flash. Instead opacity + visibility carry it: closing keeps the layer
|
||||||
|
visible (visibility flips only at the end of the transition) so the blur
|
||||||
|
fades out together with the dim; once hidden, visibility:hidden fully
|
||||||
|
disables the filter so an invisible full-screen blur costs nothing. */
|
||||||
& .desktop-menu-overlay {
|
& .desktop-menu-overlay {
|
||||||
@apply bg-scrim pointer-events-none fixed inset-0 z-60 opacity-0 transition-opacity duration-[250ms] max-md:hidden;
|
@apply bg-mega-menu-scrim pointer-events-none invisible fixed inset-0 z-60 opacity-0 backdrop-blur-lg backdrop-saturate-150 transition-[opacity,visibility] delay-[80ms] duration-[300ms] ease-[cubic-bezier(0.4,0,0.6,1)] max-md:hidden;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
@apply pointer-events-auto opacity-100 backdrop-blur-lg backdrop-saturate-150;
|
@apply pointer-events-auto visible opacity-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,6 +605,44 @@ return baseclass.extend({
|
|||||||
resizeTimer = setTimeout(applyCanvasHeight, 150);
|
resizeTimer = setTimeout(applyCanvasHeight, 150);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pointer trajectory: keep a sample from ~60ms ago so a pill's mouseenter
|
||||||
|
// can tell whether the cursor is diving down into the open panel (heading
|
||||||
|
// for a submenu link) versus scanning along the pill row. Diving across
|
||||||
|
// the row would otherwise hijack the open category on every pill it grazes.
|
||||||
|
let agedPos = { x: 0, y: 0 };
|
||||||
|
let agedAt = 0;
|
||||||
|
let livePos = { x: 0, y: 0 };
|
||||||
|
header.addEventListener("mousemove", (e) => {
|
||||||
|
if (e.timeStamp - agedAt > 60) {
|
||||||
|
agedPos = livePos;
|
||||||
|
agedAt = e.timeStamp;
|
||||||
|
}
|
||||||
|
livePos = { x: e.clientX, y: e.clientY };
|
||||||
|
});
|
||||||
|
|
||||||
|
// menu-aim / "safe area": instead of the cursor's instantaneous slope
|
||||||
|
// (which reads near-horizontal exactly when it grazes the neighbouring
|
||||||
|
// pills at the very start of a dive), project the travel vector forward and
|
||||||
|
// ask whether it lands inside the OPEN panel. If the user is aiming at a
|
||||||
|
// submenu link — even the far top-left one, reached by a shallow diagonal
|
||||||
|
// across other pills — the projection falls within the panel rect, so we
|
||||||
|
// hold the current category instead of letting each grazed pill hijack it.
|
||||||
|
const isAimingIntoOpenPanel = () => {
|
||||||
|
const openNav = header.querySelector(".desktop-nav.active");
|
||||||
|
if (!openNav) return false;
|
||||||
|
|
||||||
|
const vx = livePos.x - agedPos.x;
|
||||||
|
const vy = livePos.y - agedPos.y;
|
||||||
|
if (vy <= 2) return false; // horizontal / upward → a deliberate switch
|
||||||
|
|
||||||
|
const rect = openNav.getBoundingClientRect();
|
||||||
|
if (livePos.y >= rect.top) return true; // already descending into it
|
||||||
|
|
||||||
|
// Where the current heading crosses the panel's top edge.
|
||||||
|
const xAtTop = livePos.x + (vx * (rect.top - livePos.y)) / vy;
|
||||||
|
return xAtTop >= rect.left - 24 && xAtTop <= rect.right + 24;
|
||||||
|
};
|
||||||
|
|
||||||
children.forEach((child) => {
|
children.forEach((child) => {
|
||||||
const { li, nav, menuLink, hasSubmenu } = this.buildDropdownItem(
|
const { li, nav, menuLink, hasSubmenu } = this.buildDropdownItem(
|
||||||
child,
|
child,
|
||||||
@@ -626,6 +664,14 @@ return baseclass.extend({
|
|||||||
hideTimer = null;
|
hideTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First open waits for hover intent. Once open, scanning the pill row
|
||||||
|
// switches instantly (Apple's flyout-change feel), but a cursor diving
|
||||||
|
// diagonally toward a submenu link must NOT hijack every pill it grazes
|
||||||
|
// — so while diving we fall back to a short intent dwell that a quick
|
||||||
|
// pass-through cancels via mouseleave before it ever fires.
|
||||||
|
const isOpen = container?.classList.contains("active");
|
||||||
|
const delay = !isOpen ? 100 : isAimingIntoOpenPanel() ? 260 : 0;
|
||||||
|
|
||||||
showTimer = setTimeout(() => {
|
showTimer = setTimeout(() => {
|
||||||
const wasActive = nav.classList.contains("active");
|
const wasActive = nav.classList.contains("active");
|
||||||
|
|
||||||
@@ -638,10 +684,11 @@ return baseclass.extend({
|
|||||||
if (container) {
|
if (container) {
|
||||||
applyCanvasHeight();
|
applyCanvasHeight();
|
||||||
container.classList.add("active");
|
container.classList.add("active");
|
||||||
|
container.classList.remove("closing");
|
||||||
overlay.classList.add("active");
|
overlay.classList.add("active");
|
||||||
}
|
}
|
||||||
nav.classList.add("active");
|
nav.classList.add("active");
|
||||||
}, 100);
|
}, delay);
|
||||||
});
|
});
|
||||||
|
|
||||||
li.addEventListener("mouseleave", () => {
|
li.addEventListener("mouseleave", () => {
|
||||||
@@ -724,42 +771,47 @@ return baseclass.extend({
|
|||||||
document.querySelector(".desktop-menu-overlay")?.classList.remove("active");
|
document.querySelector(".desktop-menu-overlay")?.classList.remove("active");
|
||||||
|
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
if (
|
||||||
|
!container.classList.contains("active") &&
|
||||||
|
!container.classList.contains("closing")
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
const wasActive = container.classList.contains("active");
|
container.classList.add("closing");
|
||||||
container.classList.remove("active");
|
container.classList.remove("active");
|
||||||
|
|
||||||
// --mega-menu-height is set on open in initMegaMenu and otherwise never
|
// Retract the canvas: dropping --mega-menu-height lets the container's
|
||||||
// cleared. Drop it back to the h-0 fallback once the container is fully
|
// height fall to its 0 fallback, and because the container transitions
|
||||||
// hidden, so a closed menu doesn't leave invisible scrollable space
|
// height (see _layout.css) it animates the collapse — the drawer close —
|
||||||
// below the header on pages shorter than the canvas.
|
// instead of the height vanishing in one frame after the fade. overflow
|
||||||
const resetHeight = () => {
|
// -hidden clips the content as it goes; the end state is already h-0 so no
|
||||||
|
// post-transition reset is needed.
|
||||||
|
container.style.removeProperty("--mega-menu-height");
|
||||||
|
|
||||||
|
let closeFallback = null;
|
||||||
|
const finishClosing = (event) => {
|
||||||
|
if (event?.target && event.target !== container) return;
|
||||||
|
if (event?.propertyName && event.propertyName !== "height") return;
|
||||||
|
|
||||||
|
if (closeFallback !== null) {
|
||||||
|
clearTimeout(closeFallback);
|
||||||
|
closeFallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.removeEventListener("transitionend", finishClosing);
|
||||||
|
|
||||||
if (!container.classList.contains("active")) {
|
if (!container.classList.contains("active")) {
|
||||||
container.style.removeProperty("--mega-menu-height");
|
container.classList.remove("closing");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the menu was never open, removing "active" is a no-op: clip-path
|
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||||
// never changes, so transitionend/transitioncancel would never fire and
|
finishClosing();
|
||||||
// these listeners would pile up on every header mouseleave.
|
|
||||||
if (
|
|
||||||
!wasActive ||
|
|
||||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
||||||
) {
|
|
||||||
resetHeight();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClipPathSettled = (event) => {
|
container.addEventListener("transitionend", finishClosing);
|
||||||
if (event.target !== container || event.propertyName !== "clip-path") {
|
closeFallback = setTimeout(finishClosing, 350);
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.removeEventListener("transitionend", onClipPathSettled);
|
|
||||||
container.removeEventListener("transitioncancel", onClipPathSettled);
|
|
||||||
resetHeight();
|
|
||||||
};
|
|
||||||
|
|
||||||
container.addEventListener("transitionend", onClipPathSettled);
|
|
||||||
container.addEventListener("transitioncancel", onClipPathSettled);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderModeMenu(tree) {
|
renderModeMenu(tree) {
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ class FakeElement {
|
|||||||
this.listeners.set(type, listeners);
|
this.listeners.set(type, listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatchEvent(event) {
|
||||||
|
for (const listener of this.listeners.get(event.type) ?? []) {
|
||||||
|
listener(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closest(selector) {
|
closest(selector) {
|
||||||
let current = this;
|
let current = this;
|
||||||
|
|
||||||
@@ -169,6 +175,15 @@ class FakeElement {
|
|||||||
if (name === "class") this.classList.replace("");
|
if (name === "class") this.classList.replace("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeEventListener(type, listener) {
|
||||||
|
const listeners = this.listeners.get(type) ?? [];
|
||||||
|
|
||||||
|
this.listeners.set(
|
||||||
|
type,
|
||||||
|
listeners.filter((item) => item !== listener),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setAttribute(name, value) {
|
setAttribute(name, value) {
|
||||||
this.attributes.set(name, String(value));
|
this.attributes.set(name, String(value));
|
||||||
if (name === "class") this.classList.replace(String(value));
|
if (name === "class") this.classList.replace(String(value));
|
||||||
@@ -752,3 +767,36 @@ test("renders an active group expanded when an open mobile list was initially em
|
|||||||
assert.equal(region.getAttribute("aria-hidden"), "false");
|
assert.equal(region.getAttribute("aria-hidden"), "false");
|
||||||
assert.equal(region.hasAttribute("inert"), false);
|
assert.equal(region.hasAttribute("inert"), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps mega-menu stacking state while the close height transition runs", () => {
|
||||||
|
const container = new FakeElement("div", {
|
||||||
|
class: "desktop-menu-container active",
|
||||||
|
});
|
||||||
|
const overlay = new FakeElement("div", {
|
||||||
|
class: "desktop-menu-overlay active",
|
||||||
|
});
|
||||||
|
const document = createFakeDocument({
|
||||||
|
elements: {
|
||||||
|
".desktop-menu-container": container,
|
||||||
|
".desktop-menu-overlay": overlay,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const menu = loadMenuModule({ document });
|
||||||
|
|
||||||
|
container.style.setProperty("--mega-menu-height", "320px");
|
||||||
|
|
||||||
|
menu.hideDesktopNav();
|
||||||
|
|
||||||
|
assert.equal(overlay.classList.contains("active"), false);
|
||||||
|
assert.equal(container.classList.contains("active"), false);
|
||||||
|
assert.equal(container.classList.contains("closing"), true);
|
||||||
|
assert.equal(container.style.properties.has("--mega-menu-height"), false);
|
||||||
|
|
||||||
|
container.dispatchEvent({
|
||||||
|
type: "transitionend",
|
||||||
|
target: container,
|
||||||
|
propertyName: "height",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(container.classList.contains("closing"), false);
|
||||||
|
});
|
||||||
@@ -166,6 +166,24 @@ test("mega-menu panels scroll within the viewport", () => {
|
|||||||
assertIncludesUtilities(panel, ["overflow-y-auto", "overscroll-contain"]);
|
assertIncludesUtilities(panel, ["overflow-y-auto", "overscroll-contain"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mega-menu closing state keeps the panel above the curtain", () => {
|
||||||
|
const megaMenu = getBlock(layoutStyles, '[data-nav-type="mega-menu"] &');
|
||||||
|
const container = getBlock(megaMenu, "& .desktop-menu-container");
|
||||||
|
const headerLift = layoutStyles.match(
|
||||||
|
/When a category is open[\s\S]*?(\[data-nav-type="mega-menu"\][\s\S]*?)\n\s*\.brand/,
|
||||||
|
)?.[1];
|
||||||
|
|
||||||
|
assert.match(
|
||||||
|
container,
|
||||||
|
/&\.active,\s*&\.closing\s*\{[\s\S]*@apply[^;]*\bvisible\b/,
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
container,
|
||||||
|
/&\.active\s*\{[\s\S]*@apply[^;]*pointer-events-auto/,
|
||||||
|
);
|
||||||
|
assert.match(headerLift ?? "", /desktop-menu-container[\s\S]*closing/);
|
||||||
|
});
|
||||||
|
|
||||||
test("mega-menu category masks use Tailwind arbitrary utilities", () => {
|
test("mega-menu category masks use Tailwind arbitrary utilities", () => {
|
||||||
const title = getBlock(layoutStyles, "& .desktop-nav-title");
|
const title = getBlock(layoutStyles, "& .desktop-nav-title");
|
||||||
const icon = getBlock(title, "&::before");
|
const icon = getBlock(title, "&::before");
|
||||||
|
|||||||
@@ -39,11 +39,15 @@ test("scrim uses one 60% alpha value in both modes", () => {
|
|||||||
assert.equal(resolveMode("dark").scrim, "oklch(0% 0 0 / 0.6)");
|
assert.equal(resolveMode("dark").scrim, "oklch(0% 0 0 / 0.6)");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("menu backgrounds are translucent Gray glass (softer than 0.8)", () => {
|
test("menu panel is fully opaque (frost lives on the curtain, not the panel)", () => {
|
||||||
const l = resolveMode("light");
|
const l = resolveMode("light");
|
||||||
const d = resolveMode("dark");
|
const d = resolveMode("dark");
|
||||||
assert.ok(l.mega_menu_bg.includes("/ 0.66"), `light: ${l.mega_menu_bg}`);
|
// The panel animates height, so it carries no backdrop-filter; and it is
|
||||||
assert.ok(d.mega_menu_bg.includes("/ 0.62"), `dark: ${d.mega_menu_bg}`);
|
// fully opaque so the dimmed curtain never bleeds through to grey it off the
|
||||||
|
// header tone or leak page content. The header lifts to this same colour on
|
||||||
|
// open, so any alpha here would break the one-continuous-surface look.
|
||||||
|
assert.ok(!l.mega_menu_bg.includes("/"), `light opaque: ${l.mega_menu_bg}`);
|
||||||
|
assert.ok(!d.mega_menu_bg.includes("/"), `dark opaque: ${d.mega_menu_bg}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("light bg is the Gray canvas; surface stays pure white", () => {
|
test("light bg is the Gray canvas; surface stays pure white", () => {
|
||||||
@@ -57,8 +61,9 @@ test("dark brand and progress colors match the mobile navigation design", () =>
|
|||||||
|
|
||||||
assert.equal(dark.brand, "oklch(0.6 0.13 188.745)");
|
assert.equal(dark.brand, "oklch(0.6 0.13 188.745)");
|
||||||
assert.equal(dark.on_brand, "oklch(1 0 0)");
|
assert.equal(dark.on_brand, "oklch(1 0 0)");
|
||||||
assert.equal(dark.progress_start, "oklch(43.18% 0.0865 166.9)");
|
// Derived from brand like light: end aliases brand, start mixes toward sunken.
|
||||||
assert.equal(dark.progress_end, "oklch(62.1% 0.145 189.6)");
|
assert.equal(dark.progress_start, "oklch(44.78% 0.0865 193.2)");
|
||||||
|
assert.equal(dark.progress_end, dark.brand);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("dark differs from light", () => {
|
test("dark differs from light", () => {
|
||||||
|
|||||||
@@ -49,17 +49,25 @@ test("dark layering: page < card, sunken between page and card", () => {
|
|||||||
assert.ok(sunk >= bg && sunk < surf, `sunken not between page and card: ${d.surface_sunken}`);
|
assert.ok(sunk >= bg && sunk < surf, `sunken not between page and card: ${d.surface_sunken}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("mega-menu panel and overlays use stronger blur", () => {
|
test("mega-menu frost lives on the curtain, not the height-animating panel", () => {
|
||||||
const layout = read("../src/media/_layout.css");
|
const layout = read("../src/media/_layout.css");
|
||||||
const overlay = read("../src/media/components/_overlay.css");
|
const overlay = read("../src/media/components/_overlay.css");
|
||||||
// The mega-menu panel line carries the menu background + blur.
|
// The panel animates geometry (height) for the drawer open/close, so it must
|
||||||
|
// carry NO backdrop-filter — blurring a resizing box re-rasterises every
|
||||||
|
// frame (the old clip-path flicker). The frost moved entirely to the curtain.
|
||||||
const panel = layout.split("\n").find((l) => l.includes("bg-mega-menu-bg"));
|
const panel = layout.split("\n").find((l) => l.includes("bg-mega-menu-bg"));
|
||||||
assert.ok(panel?.includes("backdrop-blur-lg"), `panel blur: ${panel}`);
|
assert.ok(!panel?.includes("backdrop-blur"), `panel must not blur: ${panel}`);
|
||||||
assert.ok(overlay.includes("max-md:backdrop-blur-lg"), "mobile overlay blur");
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
/&\.active\s*\{\s*@apply[^;]*backdrop-blur-lg/.test(overlay),
|
panel?.includes("transition-[height,visibility]"),
|
||||||
"desktop overlay blur",
|
`panel animates height: ${panel}`,
|
||||||
);
|
);
|
||||||
|
// The curtain carries the blur permanently (Apple's globalnav-curtain) and
|
||||||
|
// fades it with opacity/visibility, never snapping it on/off via .active.
|
||||||
|
assert.ok(overlay.includes("max-md:backdrop-blur-lg"), "mobile overlay blur");
|
||||||
|
const curtain = overlay
|
||||||
|
.split("\n")
|
||||||
|
.find((l) => l.includes("bg-mega-menu-scrim"));
|
||||||
|
assert.ok(curtain?.includes("backdrop-blur-lg"), `desktop curtain blur: ${curtain}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("maincontent cards use a hairline border, not heavy shadow", () => {
|
test("maincontent cards use a hairline border, not heavy shadow", () => {
|
||||||
@@ -94,7 +102,7 @@ test("content dropdowns stay above the closed header and below the open mega-men
|
|||||||
const headerRule = layout.match(/^header\s*\{\s*@apply\s+([^;]+);/m)?.[1] ?? "";
|
const headerRule = layout.match(/^header\s*\{\s*@apply\s+([^;]+);/m)?.[1] ?? "";
|
||||||
const activeHeaderRule =
|
const activeHeaderRule =
|
||||||
layout.match(
|
layout.match(
|
||||||
/\[data-nav-type="mega-menu"\] &:has\(\.desktop-menu-container\.active\)\s*\{\s*@apply\s+([^;]+);/,
|
/\[data-nav-type="mega-menu"\]\s*&:has\([\s\S]*?\.desktop-menu-container[\s\S]*?\.active[\s\S]*?\)\s*\{\s*@apply\s+([^;]+);/,
|
||||||
)?.[1] ?? "";
|
)?.[1] ?? "";
|
||||||
const dropdownRule = dropdown.match(/&\.dropdown\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
|
const dropdownRule = dropdown.match(/&\.dropdown\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
|
||||||
const messageRule = message.match(/\.alert-message\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
|
const messageRule = message.match(/\.alert-message\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
|
||||||
|
|||||||
@@ -20,7 +20,19 @@ export const DERIVATIONS = {
|
|||||||
danger_surface: ["set", "danger", 0.94, 0.05],
|
danger_surface: ["set", "danger", 0.94, 0.05],
|
||||||
danger_surface_hover: ["shade", "danger_surface", -0.04],
|
danger_surface_hover: ["shade", "danger_surface", -0.04],
|
||||||
scrim: ["const", "oklch(0 0 0 / 0.6)"],
|
scrim: ["const", "oklch(0 0 0 / 0.6)"],
|
||||||
mega_menu_bg: ["alpha", "surface_overlay", 0.66],
|
// Fully opaque: a clean solid panel (Apple's #fafafc). Any translucency let
|
||||||
|
// the dimmed curtain bleed through, greying the panel off the header tone
|
||||||
|
// and leaking faint blurred page content into the empty columns. The header
|
||||||
|
// lifts to this same colour when the menu opens (see _layout.css) so bar and
|
||||||
|
// panel read as one continuous surface.
|
||||||
|
mega_menu_bg: ["alpha", "surface_overlay", 1],
|
||||||
|
// Mega-menu curtain: a real dimming layer. A near-page-light grey (the
|
||||||
|
// earlier #e8e8ed attempt) only blurred without darkening, so the mask read
|
||||||
|
// as absent. Black at a modest alpha actually dims the page; the now-opaque
|
||||||
|
// panel + its shadow give a clean edge, so this no longer bands the way the
|
||||||
|
// old translucent panel over a heavy scrim did. Lighter than the 0.6 modal
|
||||||
|
// scrim — it's a menu backdrop, not a dialog.
|
||||||
|
mega_menu_scrim: ["const", "oklch(0 0 0 / 0.32)"],
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
text_muted: ["mix", "text", "bg", 0.62],
|
text_muted: ["mix", "text", "bg", 0.62],
|
||||||
@@ -33,15 +45,24 @@ export const DERIVATIONS = {
|
|||||||
brand_subtle: ["mix", "brand", "bg", 0.16],
|
brand_subtle: ["mix", "brand", "bg", 0.16],
|
||||||
brand_subtle_hover: ["shade", "brand_subtle", 0.04],
|
brand_subtle_hover: ["shade", "brand_subtle", 0.04],
|
||||||
focus_ring: ["alpha", "brand", 0.6],
|
focus_ring: ["alpha", "brand", 0.6],
|
||||||
progress_start: ["const", "oklch(0.4318 0.0865 166.91)"],
|
// Derived from brand like light — not hardcoded — so the bar tracks the
|
||||||
progress_end: ["const", "oklch(0.621 0.145 189.632)"],
|
// active brand colour instead of a frozen teal.
|
||||||
|
progress_start: ["mix", "brand", "surface_sunken", 0.65],
|
||||||
|
progress_end: ["const", "var:brand"],
|
||||||
info_surface: ["set", "info", 0.32, 0.05],
|
info_surface: ["set", "info", 0.32, 0.05],
|
||||||
warning_surface: ["set", "warning", 0.33, 0.06],
|
warning_surface: ["set", "warning", 0.33, 0.06],
|
||||||
success_surface: ["set", "success", 0.3, 0.05],
|
success_surface: ["set", "success", 0.3, 0.05],
|
||||||
danger_surface: ["set", "danger", 0.32, 0.08],
|
danger_surface: ["set", "danger", 0.32, 0.08],
|
||||||
danger_surface_hover: ["shade", "danger_surface", 0.04],
|
danger_surface_hover: ["shade", "danger_surface", 0.04],
|
||||||
scrim: ["const", "oklch(0 0 0 / 0.6)"],
|
scrim: ["const", "oklch(0 0 0 / 0.6)"],
|
||||||
mega_menu_bg: ["alpha", "surface_overlay", 0.62],
|
// Deeper than surface_overlay (23%): surface_sunken (16.5%) tracks Apple's
|
||||||
|
// opened-panel #161617 (~18.5%). Fully opaque — a clean solid panel with no
|
||||||
|
// curtain bleed; the header lifts to this colour on open (see _layout.css)
|
||||||
|
// so bar and panel are one continuous surface.
|
||||||
|
mega_menu_bg: ["alpha", "surface_sunken", 1],
|
||||||
|
// Dark curtain stays a near-black dim (Apple's rgba(0,0,0,.4)); the dark
|
||||||
|
// panel and dark page already share a tone, so this only needs to deepen.
|
||||||
|
mega_menu_scrim: ["const", "oklch(0 0 0 / 0.5)"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||||
LUCI_DEPENDS:=+luci-base
|
LUCI_DEPENDS:=+luci-base
|
||||||
|
|
||||||
PKG_VERSION:=0.12.11
|
PKG_VERSION:=0.12.12
|
||||||
PKG_RELEASE:=28
|
PKG_RELEASE:=29
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
|
|
||||||
LUCI_MINIFY_CSS:=
|
LUCI_MINIFY_CSS:=
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:wheLine truncated
|
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:wheLine truncated
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feLine truncated
|
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feLine truncated
|
||||||
@@ -1 +1 @@
|
|||||||
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const mobileList=overlay.querySelector("#mobile-nav-list");const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const updateToggleState=expanded=>{const desktopSidebar=isDesktopSidebar();navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded?"true":"false");navigationToggle.setAttribute("aria-controls",desktopSidebar?"sidebar-panel":"mobile-menu-overlay");const label=expanded&&!desktopSidebar?_("Close"):_("Navigation");navigationToggle.setAttribute("title",label);navigationToggle.setAttribute("aria-label",label)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.classList.remove("mobile-navigation-open");document.body.style.overflow="";this.resetNavigationGroups(mobileList)};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.classList.toggle("mobile-navigation-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)this.expandActiveNavigationGroup(mobileList);else this.resetNavigationGroups(mobileList);updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const destination=e.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");if(destination&&overlay.contains(destination)){setNavigationExpanded(false)}})},renderMobileMenu(items){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");if(list)list.innerHTML="";if(footerAction)footerAction.innerHTML="";if(!list)return;items.forEach(item=>{if(item.isLogout){if(footerAction){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:item.href},[item.title]))}return}list.appendChild(this.renderNavigationItem(item,"mobile"))});this.bindNavigationAccordion(list)},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,level=0){const container=document.querySelector("#tabmenu");const ul=E("ul",{class:"tabs"});const children=ui.menu.getChildren(tree);let activeNode=null;children.forEach(child=>{const isActive=L.env.dispatchpath[3+level]===child.name;ul.appendChild(E("li",{class:`tabmenu-item-${child.name}${isActive?" active":""}`},[E("a",{href:L.url(url,child.name)},[_(child.title)])]));if(isActive)activeNode=child});if(!ul.children.length)return E([]);container.appendChild(ul);container.style.display="";if(activeNode){this.renderTabMenu(activeNode,`${url}/${activeNode.name}`,level+1)}return ul},renderMainMenu(tree,url,level=0,navigationItems=null){const ul=level?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu");const children=ui.menu.getChildren(tree);if(level>1)return E([]);if(level===0){const navType=document.body?.dataset?.navType||"mega-menu";if(navType==="sidebar"){this.renderSidebLine truncated
|
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const mobileList=overlay.querySelector("#mobile-nav-list");const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const updateToggleState=expanded=>{const desktopSidebar=isDesktopSidebar();navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded?"true":"false");navigationToggle.setAttribute("aria-controls",desktopSidebar?"sidebar-panel":"mobile-menu-overlay");const label=expanded&&!desktopSidebar?_("Close"):_("Navigation");navigationToggle.setAttribute("title",label);navigationToggle.setAttribute("aria-label",label)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.classList.remove("mobile-navigation-open");document.body.style.overflow="";this.resetNavigationGroups(mobileList)};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.classList.toggle("mobile-navigation-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)this.expandActiveNavigationGroup(mobileList);else this.resetNavigationGroups(mobileList);updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const destination=e.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");if(destination&&overlay.contains(destination)){setNavigationExpanded(false)}})},renderMobileMenu(items){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");if(list)list.innerHTML="";if(footerAction)footerAction.innerHTML="";if(!list)return;items.forEach(item=>{if(item.isLogout){if(footerAction){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:item.href},[item.title]))}return}list.appendChild(this.renderNavigationItem(item,"mobile"))});this.bindNavigationAccordion(list)},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,level=0){const container=document.querySelector("#tabmenu");const ul=E("ul",{class:"tabs"});const children=ui.menu.getChildren(tree);let activeNode=null;children.forEach(child=>{const isActive=L.env.dispatchpath[3+level]===child.name;ul.appendChild(E("li",{class:`tabmenu-item-${child.name}${isActive?" active":""}`},[E("a",{href:L.url(url,child.name)},[_(child.title)])]));if(isActive)activeNode=child});if(!ul.children.length)return E([]);container.appendChild(ul);container.style.display="";if(activeNode){this.renderTabMenu(activeNode,`${url}/${activeNode.name}`,level+1)}return ul},renderMainMenu(tree,url,level=0,navigationItems=null){const ul=level?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu");const children=ui.menu.getChildren(tree);if(level>1)return E([]);if(level===0){const navType=document.body?.dataset?.navType||"mega-menu";if(navType==="sidebar"){this.renderSidebLine truncated
|
||||||
@@ -9,7 +9,7 @@ LUCI_TITLE:=Design Theme
|
|||||||
LUCI_DEPENDS:=
|
LUCI_DEPENDS:=
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
PKG_VERSION:=7.1
|
PKG_VERSION:=7.1
|
||||||
PKG_RELEASE:=6
|
PKG_RELEASE:=7
|
||||||
|
|
||||||
LUCI_MINIFY_CSS:=
|
LUCI_MINIFY_CSS:=
|
||||||
CONFIG_LUCI_CSSTIDY:=
|
CONFIG_LUCI_CSSTIDY:=
|
||||||
|
|||||||
@@ -89,18 +89,17 @@ return baseclass.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getConditionalMenuStamp: function() {
|
getConditionalMenuStamp: function() {
|
||||||
var googleFuMode = !!document.querySelector('.navbar a[href*="/admin/services/openclash"]');
|
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.getFileListStamp('/usr/share/luci/menu.d', '.json'),
|
this.getFileListStamp('/usr/share/luci/menu.d', '.json'),
|
||||||
this.getFileListStamp('/usr/share/rpcd/acl.d', '.json'),
|
this.getFileListStamp('/usr/share/rpcd/acl.d', '.json'),
|
||||||
L.resolveDefault(fs.stat('/etc/config/wireless'), null)
|
L.resolveDefault(fs.stat('/etc/config/wireless'), null),
|
||||||
|
L.resolveDefault(fs.stat('/etc/config/google_fu_mode'), null)
|
||||||
]).then(function(stamps) {
|
]).then(function(stamps) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
google_fu_mode: googleFuMode,
|
|
||||||
menu_definitions: stamps[0],
|
menu_definitions: stamps[0],
|
||||||
acl_definitions: stamps[1],
|
acl_definitions: stamps[1],
|
||||||
wireless_config: stamps[2] ? [ stamps[2].mtime || 0, stamps[2].size || 0 ].join(':') : 'absent'
|
wireless_config: stamps[2] ? [ stamps[2].mtime || 0, stamps[2].size || 0 ].join(':') : 'absent',
|
||||||
|
google_fu_mode: stamps[3] ? [ stamps[3].mtime || 0, stamps[3].size || 0 ].join(':') : 'absent'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
VERSION="$1"
|
||||||
|
CURDIR="$2"
|
||||||
|
BIN_PATH="$3"
|
||||||
|
|
||||||
|
if [ -d "$CURDIR/.git" ]; then
|
||||||
|
config="$CURDIR/.git/config"
|
||||||
|
else
|
||||||
|
config="$(sed "s|^gitdir:\s*|$CURDIR/|;s|$|/config|" "$CURDIR/.git")"
|
||||||
|
fi
|
||||||
|
[ -n "$(sed -En '/^\[remote /{h;:top;n;/^\[/b;s,(https?://gitcode\.(com|net)),\1,;T top;H;x;s|\n\s*|: |;p;}' "$config")" ] && {
|
||||||
|
echo -e "#!/bin/sh\necho $VERSION" > "$BIN_PATH"
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
+30
-25
@@ -1,30 +1,32 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-2026 Anya Lin <hukk1996@gmail.com>
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=mihomo
|
PKG_NAME:=mihomo
|
||||||
PKG_VERSION:=1.19.27
|
PKG_VERSION:=1.19.27
|
||||||
PKG_RELEASE:=21
|
PKG_RELEASE:=22
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
|
||||||
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
|
|
||||||
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)?
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL3.0+
|
PKG_MAINTAINER:=Anya Lin <hukk1996@gmail.com>
|
||||||
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
PKG_LICENSE:=GPL-2.0
|
||||||
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
|
||||||
PKG_BUILD_DEPENDS:=golang/host
|
PKG_BUILD_DEPENDS:=golang/host
|
||||||
PKG_BUILD_PARALLEL:=1
|
PKG_BUILD_PARALLEL:=1
|
||||||
PKG_BUILD_FLAGS:=no-mips16
|
PKG_BUILD_FLAGS:=no-mips16
|
||||||
|
|
||||||
PKG_BUILD_VERSION:=v$(PKG_VERSION)
|
|
||||||
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
|
|
||||||
|
|
||||||
GO_PKG:=github.com/metacubex/mihomo
|
GO_PKG:=github.com/metacubex/mihomo
|
||||||
GO_PKG_LDFLAGS_X:=$(GO_PKG)/constant.Version=$(PKG_BUILD_VERSION) $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME)
|
|
||||||
|
PKG_BUILD_TIME:=$(shell date -u +%FT%TZ%z)
|
||||||
|
GO_PKG_LDFLAGS_X:=\
|
||||||
|
$(GO_PKG)/constant.Version=v$(PKG_VERSION) \
|
||||||
|
$(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME)
|
||||||
GO_PKG_TAGS:=with_gvisor
|
GO_PKG_TAGS:=with_gvisor
|
||||||
GO_PKG_INSTALL_BIN_PATH:=/usr/libexec
|
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||||
@@ -32,24 +34,27 @@ include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
|||||||
define Package/mihomo
|
define Package/mihomo
|
||||||
SECTION:=net
|
SECTION:=net
|
||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
TITLE:=A rule based proxy in Go.
|
TITLE:=Another Mihomo Kernel.
|
||||||
URL:=https://wiki.metacubex.one
|
URL:=https://wiki.metacubex.one
|
||||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||||
|
PROVIDES:=mihomo
|
||||||
ALTERNATIVES:=\
|
ALTERNATIVES:=\
|
||||||
300:/usr/bin/mihomo:/usr/libexec/mihomo
|
100:/usr/bin/mihomo:/usr/libexec/mihomo-core
|
||||||
endef
|
USERID:=mihomo=7890:mihomo=7890
|
||||||
|
|
||||||
define Package/mihomo/description
|
|
||||||
Mihomo is a rule based proxy in Go.
|
|
||||||
endef
|
|
||||||
|
|
||||||
define Package/mihomo/install
|
|
||||||
$(call GoPackage/Package/Install/Bin,$(1))
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Build/Prepare
|
define Build/Prepare
|
||||||
$(Build/Prepare/Default)
|
$(Build/Prepare/Default)
|
||||||
$(RM) -r $(PKG_BUILD_DIR)/rules/logic_test
|
# rm unit test
|
||||||
|
rm -f $(PKG_BUILD_DIR)/rules/logic_test/logic_test.go
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/mihomo/install
|
||||||
|
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||||
|
$(CURDIR)/.prepare.sh $(VERSION) $(CURDIR) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME)
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/libexec/
|
||||||
|
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME) $(1)/usr/libexec/mihomo-core
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call GoBinPackage,mihomo))
|
$(eval $(call GoBinPackage,mihomo))
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=momo
|
PKG_NAME:=momo
|
||||||
PKG_VERSION:=2026.06.03
|
PKG_VERSION:=2026.06.03
|
||||||
PKG_RELEASE:=10
|
PKG_RELEASE:=11
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-3.0+
|
PKG_LICENSE:=GPL-3.0+
|
||||||
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ FIREWALL_INCLUDE_SH="$SH_DIR/firewall_include.sh"
|
|||||||
DEBUG_SH="$SH_DIR/debug.sh"
|
DEBUG_SH="$SH_DIR/debug.sh"
|
||||||
|
|
||||||
## firewall
|
## firewall
|
||||||
NFT_DIR="$HOME_DIR/firewall"
|
FIREWALL_DIR="$HOME_DIR/firewall"
|
||||||
GEOIP_CN_NFT="$NFT_DIR/geoip_cn.nft"
|
GEOIP_CN_NFT="$FIREWALL_DIR/geoip_cn.nft"
|
||||||
GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft"
|
GEOIP6_CN_NFT="$FIREWALL_DIR/geoip6_cn.nft"
|
||||||
|
|
||||||
## log
|
## log
|
||||||
LOG_DIR="/var/log/momo"
|
LOG_DIR="/var/log/momo"
|
||||||
@@ -63,7 +63,7 @@ get_paths() {
|
|||||||
json_add_string firewall_include_sh "$FIREWALL_INCLUDE_SH"
|
json_add_string firewall_include_sh "$FIREWALL_INCLUDE_SH"
|
||||||
json_add_string debug_sh "$DEBUG_SH"
|
json_add_string debug_sh "$DEBUG_SH"
|
||||||
|
|
||||||
json_add_string nft_dir "$NFT_DIR"
|
json_add_string firewall_dir "$FIREWALL_DIR"
|
||||||
json_add_string geoip_cn_nft "$GEOIP_CN_NFT"
|
json_add_string geoip_cn_nft "$GEOIP_CN_NFT"
|
||||||
json_add_string geoip6_cn_nft "$GEOIP6_CN_NFT"
|
json_add_string geoip6_cn_nft "$GEOIP6_CN_NFT"
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
import { readfile } from 'fs';
|
import { readfile } from 'fs';
|
||||||
import { cursor } from 'uci';
|
import { cursor } from 'uci';
|
||||||
import { connect } from 'ubus';
|
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';
|
import { uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups, load_profile } from '/etc/momo/ucode/include.uc';
|
||||||
|
|
||||||
const fw4 = require('fw4');
|
const fw4 = require('fw4');
|
||||||
|
|
||||||
const paths = get_paths();
|
|
||||||
|
|
||||||
const cgroups_version = get_cgroups_version();
|
const cgroups_version = get_cgroups_version();
|
||||||
|
|
||||||
const users = get_users();
|
const users = get_users();
|
||||||
@@ -21,7 +19,7 @@
|
|||||||
const uci = cursor();
|
const uci = cursor();
|
||||||
const ubus = connect();
|
const ubus = connect();
|
||||||
|
|
||||||
const profile = json(readfile(paths.run_profile_path));
|
const profile = load_profile();
|
||||||
|
|
||||||
uci.load('momo');
|
uci.load('momo');
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=nexttrace
|
PKG_NAME:=nexttrace
|
||||||
PKG_VERSION:=1.7.0
|
PKG_VERSION:=1.7.1
|
||||||
PKG_RELEASE:=6
|
PKG_RELEASE:=7
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=opera-proxy
|
PKG_NAME:=opera-proxy
|
||||||
PKG_VERSION:=1.24.0
|
PKG_VERSION:=1.24.0
|
||||||
PKG_RELEASE:=4
|
PKG_RELEASE:=5
|
||||||
|
|
||||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
|
|||||||
@@ -8,23 +8,26 @@ NAME=opera-proxy
|
|||||||
PROG=/usr/bin/${NAME}
|
PROG=/usr/bin/${NAME}
|
||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
local country bind_address verbosity socks_mode timeout
|
local country bind_address verbosity socks_mode timeout fake_sni args
|
||||||
|
|
||||||
config_load opera-proxy
|
config_load opera-proxy
|
||||||
config_get country main country "EU"
|
config_get country main country "EU"
|
||||||
config_get bind_address main bind_address "127.0.0.1:18080"
|
config_get bind_address main bind_address "127.0.0.1:18080"
|
||||||
config_get verbosity main verbosity "20"
|
config_get verbosity main verbosity "20"
|
||||||
config_get timeout main timeout "10s"
|
config_get timeout main timeout "10s"
|
||||||
|
config_get fake_sni main fake_sni
|
||||||
|
config_get args main args
|
||||||
config_get_bool socks_mode main socks_mode 0
|
config_get_bool socks_mode main socks_mode 0
|
||||||
|
|
||||||
procd_open_instance
|
procd_open_instance
|
||||||
procd_set_param command "$PROG"
|
procd_set_param command "$PROG"
|
||||||
procd_append_param command -country "$country"
|
[ -n "$country" ] && procd_append_param command -country "$country"
|
||||||
procd_append_param command -bind-address "$bind_address"
|
[ -n "$bind_address" ] && procd_append_param command -bind-address "$bind_address"
|
||||||
procd_append_param command -verbosity "$verbosity"
|
[ -n "$verbosity" ] && procd_append_param command -verbosity "$verbosity"
|
||||||
procd_append_param command -timeout "$timeout"
|
[ -n "$timeout" ] && procd_append_param command -timeout "$timeout"
|
||||||
|
|
||||||
[ "$socks_mode" = "1" ] && procd_append_param command -socks-mode
|
[ "$socks_mode" = "1" ] && procd_append_param command -socks-mode
|
||||||
|
[ -n "$fake_sni" ] && procd_append_param command -fake-SNI "$fake_sni"
|
||||||
|
[ -n "$args" ] && procd_append_param command "$args"
|
||||||
|
|
||||||
procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
|
procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
|
||||||
procd_set_param stdout 1
|
procd_set_param stdout 1
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=qBittorrent
|
PKG_NAME:=qBittorrent
|
||||||
PKG_VERSION:=5.2.1
|
PKG_VERSION:=5.2.2
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/qbittorrent/qBittorrent/tar.gz/release-$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/qbittorrent/qBittorrent/tar.gz/release-$(PKG_VERSION)?
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=tuic-client
|
PKG_NAME:=tuic-client
|
||||||
PKG_VERSION:=1.8.6
|
PKG_VERSION:=1.8.7
|
||||||
PKG_RELEASE:=10
|
PKG_RELEASE:=11
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/Itsusinn/tuic/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/Itsusinn/tuic/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
Reference in new issue
Block a user