mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 14:05:13 +08:00
🦄 Sync 2026-06-17 03:41:10
This commit is contained in:
1 parent
aa76a8f79d
commit
08b7eb4ffb
79 files changed
+1354
-486
No files matched your search
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-daede
|
||||
PKG_VERSION:=1.12.0
|
||||
PKG_RELEASE:=11
|
||||
PKG_VERSION:=1.13.0
|
||||
PKG_RELEASE:=12
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
|
||||
@@ -13,6 +13,22 @@ const DATA_PATHS = {
|
||||
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 = {
|
||||
btf: '/sys/kernel/btf/vmlinux',
|
||||
netns: '/run/netns/daens'
|
||||
@@ -36,6 +52,11 @@ const CSS = [
|
||||
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
|
||||
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
|
||||
'.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.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}',
|
||||
@@ -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({
|
||||
load: function() {
|
||||
// background apk update so new versions show on next poll
|
||||
fs.exec('/usr/share/luci-app-daede/refresh-index.sh', []).catch(function() {});
|
||||
return Promise.all([
|
||||
backend.detectBackend(),
|
||||
uci.load('daed').catch(function() {})
|
||||
uci.load('daed').catch(function() {}),
|
||||
uci.load('daede').catch(function() {})
|
||||
]).then(function(r) {
|
||||
return r[0];
|
||||
});
|
||||
@@ -172,6 +200,49 @@ return view.extend({
|
||||
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 probes = [
|
||||
probeFile(DATA_PATHS.geoip),
|
||||
@@ -309,16 +380,113 @@ return view.extend({
|
||||
poll.add(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' }, [
|
||||
E('style', {}, CSS),
|
||||
E('div', { 'class': 'dd-card' }, [
|
||||
E('h4', { 'class': 'dd-card-title' }, _('Data Updates')),
|
||||
dataBody
|
||||
dataBody,
|
||||
geoSettings
|
||||
]),
|
||||
E('div', { 'class': 'dd-card' }, [
|
||||
E('h4', { 'class': 'dd-card-title' }, _('Package Updates')),
|
||||
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() {
|
||||
const adv = E('div', { 'class': 'dd-adv dd-closed' }, [
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
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.
|
||||
|
||||
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
|
||||
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"
|
||||
;;
|
||||
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"
|
||||
;;
|
||||
*)
|
||||
@@ -19,6 +24,7 @@ case "$TYPE" in
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
[ -n "$URL" ] || URL="$DEF_URL"
|
||||
|
||||
LOCK="/tmp/luci-app-daede.${TYPE}.lock"
|
||||
LOG="/tmp/luci-app-daede.${TYPE}.log"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"/tmp/luci-app-daede.pkg-dae.log": [ "read" ],
|
||||
"/tmp/luci-app-daede.pkg-daed.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 daed": [ "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 luci-app-daede": [ "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": {
|
||||
"service": [ "list" ]
|
||||
@@ -39,6 +41,8 @@
|
||||
"file": {
|
||||
"/etc/dae/config.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" ],
|
||||
"/var/log/dae/dae.log": [ "write" ],
|
||||
"/var/log/daed/daed.log": [ "write" ],
|
||||
@@ -63,6 +67,8 @@
|
||||
"/sbin/ip netns del daens": [ "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/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 daed": [ "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)
|
||||
|
||||
/* 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.default = o.disabled;
|
||||
o.validate = function(section_id, value) {
|
||||
const type = this.section.getOption('type').formvalue(section_id);
|
||||
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_reality = this.section.getUIElement(section_id, 'tls_reality').node.querySelector('input');
|
||||
|
||||
@@ -935,6 +930,12 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
tls_alpn.node.querySelector('input').disabled = true;
|
||||
tls_alpn.setValue('');
|
||||
@@ -950,7 +951,13 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
|
||||
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 = s.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
||||
|
||||
@@ -211,7 +211,7 @@ return view.extend({
|
||||
render(data) {
|
||||
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');
|
||||
|
||||
@@ -1050,7 +1050,7 @@ return view.extend({
|
||||
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
||||
_('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', tls_disable_sni: '0', type: /^(tuic)$/});
|
||||
so.depends({tls: '1', type: /^(tuic)$/, tls_disable_sni: '0'});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
||||
|
||||
@@ -805,6 +805,10 @@ msgstr ""
|
||||
msgid "Edit node"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:214
|
||||
msgid "Edit outbound"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
||||
msgid "Edit ruleset"
|
||||
msgstr ""
|
||||
|
||||
@@ -798,7 +798,7 @@ msgstr "强制覆盖 ECS"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/client.js:1605
|
||||
msgid "EDNS Client Subnet"
|
||||
msgstr ""
|
||||
msgstr "EDNS 客户端子网"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/global.js:433
|
||||
msgid "ETag support"
|
||||
@@ -820,6 +820,10 @@ msgstr "编辑入站"
|
||||
msgid "Edit node"
|
||||
msgstr "编辑节点"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:214
|
||||
msgid "Edit outbound"
|
||||
msgstr "编辑出站"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
||||
msgid "Edit ruleset"
|
||||
msgstr "编辑规则集"
|
||||
@@ -1104,7 +1108,7 @@ msgstr "兜底 DNS 服务器 (用于已被投毒污染的域名)"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:137
|
||||
msgid "Firewall"
|
||||
msgstr ""
|
||||
msgstr "防火墙"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:535
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:660
|
||||
@@ -1335,7 +1339,7 @@ msgstr "握手超时"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:244
|
||||
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
|
||||
msgid "Health check"
|
||||
@@ -1525,7 +1529,7 @@ msgstr "单位为毫秒。留空则使用 <code>%s</code>。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1350
|
||||
msgid "In milliseconds."
|
||||
msgstr ""
|
||||
msgstr "单位为毫秒。"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:418
|
||||
#: 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:1585
|
||||
msgid "valid format: 2x, 2p, 4v"
|
||||
msgstr ""
|
||||
msgstr "有效格式: 2x, 2p, 4v"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo.js:1566
|
||||
msgid "valid key length with %d characters"
|
||||
|
||||
@@ -798,7 +798,7 @@ msgstr "強制覆蓋 ECS"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/client.js:1605
|
||||
msgid "EDNS Client Subnet"
|
||||
msgstr ""
|
||||
msgstr "EDNS 客戶端子網"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/global.js:433
|
||||
msgid "ETag support"
|
||||
@@ -820,6 +820,10 @@ msgstr "編輯入站"
|
||||
msgid "Edit node"
|
||||
msgstr "編輯節點"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:214
|
||||
msgid "Edit outbound"
|
||||
msgstr "編輯出站"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/ruleset.js:138
|
||||
msgid "Edit ruleset"
|
||||
msgstr "編輯規則集"
|
||||
@@ -1104,7 +1108,7 @@ msgstr "兜底 DNS 伺服器 (用於已被投毒汙染的網域)"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:137
|
||||
msgid "Firewall"
|
||||
msgstr ""
|
||||
msgstr "防火牆"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:535
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:660
|
||||
@@ -1335,7 +1339,7 @@ msgstr "握手超時"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:244
|
||||
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
|
||||
msgid "Health check"
|
||||
@@ -1525,7 +1529,7 @@ msgstr "單位為毫秒。留空則使用 <code>%s</code>。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1350
|
||||
msgid "In milliseconds."
|
||||
msgstr ""
|
||||
msgstr "單位為毫秒。"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo/listeners.js:418
|
||||
#: 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:1585
|
||||
msgid "valid format: 2x, 2p, 4v"
|
||||
msgstr ""
|
||||
msgstr "有效格式: 2x, 2p, 4v"
|
||||
|
||||
#: htdocs/luci-static/resources/fchomo.js:1566
|
||||
msgid "valid key length with %d characters"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.2.1
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
LUCI_TITLE:=LuCI Support for momo
|
||||
LUCI_DEPENDS:=+luci-base +momo
|
||||
|
||||
@@ -19,6 +19,12 @@ const callRCInit = rpc.declare({
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callFileWrite = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'write',
|
||||
params: ['path', 'data', 'append', 'mode']
|
||||
});
|
||||
|
||||
const callMomoGetPaths = rpc.declare({
|
||||
object: 'luci.momo',
|
||||
method: 'get_paths',
|
||||
@@ -81,6 +87,31 @@ return baseclass.extend({
|
||||
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 () {
|
||||
return callMomoVersion();
|
||||
},
|
||||
@@ -138,12 +169,12 @@ return baseclass.extend({
|
||||
|
||||
clearAppLog: async function () {
|
||||
const paths = await this.getPaths();
|
||||
return fs.write(paths.app_log_path);
|
||||
return this.writefile(paths.app_log_path);
|
||||
},
|
||||
|
||||
clearCoreLog: async function () {
|
||||
const paths = await this.getPaths();
|
||||
return fs.write(paths.core_log_path);
|
||||
return this.writefile(paths.core_log_path);
|
||||
},
|
||||
|
||||
debug: function () {
|
||||
|
||||
@@ -51,11 +51,11 @@ return view.extend({
|
||||
o.wrap = false;
|
||||
o.write = function (section_id, formvalue) {
|
||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||
return fs.write(path, formvalue);
|
||||
return momo.writefile(path, formvalue);
|
||||
};
|
||||
o.remove = function (section_id) {
|
||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||
return fs.write(path);
|
||||
return momo.writefile(path);
|
||||
};
|
||||
|
||||
return m.render();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { access, popen, readfile } from 'fs';
|
||||
import { get_paths, merge_exists, get_users, get_groups, get_cgroups, load_profile, save_profile } from '/etc/momo/ucode/include.uc';
|
||||
import { popen } from 'fs';
|
||||
import { get_paths, merge_exists, get_users, get_groups, get_cgroups, load_profile } from '/etc/momo/ucode/include.uc';
|
||||
|
||||
const methods = {
|
||||
get_paths: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.26.1
|
||||
PKG_RELEASE:=5
|
||||
PKG_RELEASE:=6
|
||||
|
||||
LUCI_TITLE:=LuCI Support for nikki
|
||||
LUCI_DEPENDS:=+luci-base +nikki
|
||||
|
||||
@@ -19,6 +19,12 @@ const callRCInit = rpc.declare({
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callFileWrite = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'write',
|
||||
params: ['path', 'data', 'append', 'mode']
|
||||
});
|
||||
|
||||
const callNikkiVersion = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'version',
|
||||
@@ -98,6 +104,31 @@ return baseclass.extend({
|
||||
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 () {
|
||||
return callNikkiVersion();
|
||||
},
|
||||
@@ -183,11 +214,11 @@ return baseclass.extend({
|
||||
},
|
||||
|
||||
clearAppLog: function () {
|
||||
return fs.write(this.appLogPath);
|
||||
return this.writefile(this.appLogPath, '');
|
||||
},
|
||||
|
||||
clearCoreLog: function () {
|
||||
return fs.write(this.coreLogPath);
|
||||
return this.writefile(this.coreLogPath, '');
|
||||
},
|
||||
|
||||
debug: function () {
|
||||
|
||||
@@ -62,11 +62,11 @@ return view.extend({
|
||||
o.wrap = false;
|
||||
o.write = function (section_id, formvalue) {
|
||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||
return fs.write(path, formvalue);
|
||||
return nikki.writefile(path, formvalue);
|
||||
};
|
||||
o.remove = function (section_id) {
|
||||
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
|
||||
return fs.write(path);
|
||||
return nikki.writefile(path);
|
||||
};
|
||||
|
||||
return m.render();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.6.3
|
||||
PKG_RELEASE:=54
|
||||
PKG_VERSION:=26.6.16
|
||||
PKG_RELEASE:=55
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -320,15 +320,6 @@ o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||
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.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
@@ -420,7 +411,6 @@ o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
-- o = s:option(Value, _n("minversion"), translate("minversion"))
|
||||
-- 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.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:value("", translate("Auto"))
|
||||
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
|
||||
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.default = "tcp,udp"
|
||||
o:value("tcp", "TCP")
|
||||
@@ -232,7 +229,6 @@ o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
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.default = "0"
|
||||
|
||||
@@ -1611,27 +1611,23 @@ end
|
||||
|
||||
function parse_realm_uri(uri)
|
||||
if type(uri) ~= "string" then return nil end
|
||||
-- realm://token@server/realm_id?query
|
||||
local token, server_url, realm_id, query = trim(uri):match("^realm://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||||
if not token or not server_url or not realm_id then return nil end
|
||||
-- realm[+http]://token@server/realm_id?query
|
||||
local scheme, token, server_url, realm_id, query = trim(uri):match("^(realm%+http|realm)://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||||
if not scheme or not token or not server_url or not realm_id then return nil end
|
||||
realm_id = realm_id:gsub("/+$", "")
|
||||
local realm = {
|
||||
scheme = scheme,
|
||||
token = token,
|
||||
server_url = server_url,
|
||||
realm_id = realm_id
|
||||
}
|
||||
-- 解析 query 中的 stun=
|
||||
if query and query ~= "" then
|
||||
local stun_servers = {}
|
||||
for key, value in query:gmatch("([^&=?]+)=([^&]+)") do
|
||||
if key == "stun" and value ~= "" then
|
||||
stun_servers[#stun_servers + 1] = value
|
||||
end
|
||||
end
|
||||
if #stun_servers > 0 then
|
||||
realm.stun_servers = stun_servers
|
||||
end
|
||||
-- Parse stun= in the query
|
||||
local stun_servers
|
||||
for value in (query or ""):gmatch("[?&]?[Ss][Tt][Uu][Nn]=([^&]+)") do
|
||||
if not stun_servers then stun_servers = {} end
|
||||
stun_servers[#stun_servers + 1] = value
|
||||
end
|
||||
realm.stun_servers = stun_servers
|
||||
return realm
|
||||
end
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ function gen_config(var)
|
||||
server_host = api.get_ipv6_full(server_host)
|
||||
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 = {
|
||||
server = (function()
|
||||
|
||||
@@ -617,8 +617,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
result.server_port = nil
|
||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||
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.scheme = nil
|
||||
return realm
|
||||
end
|
||||
return nil
|
||||
@@ -959,8 +960,9 @@ function gen_config_server(node)
|
||||
realm = node.hysteria2_realms and (function()
|
||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||
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.scheme = nil
|
||||
realm.stun_domain_resolver = "direct"
|
||||
return realm
|
||||
end
|
||||
@@ -1797,13 +1799,12 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(route.rules, {
|
||||
action = "route",
|
||||
ip_is_private = true,
|
||||
outbound = "direct"
|
||||
})
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
@@ -285,21 +285,22 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
local finalmask = {}
|
||||
local TP = node.transport
|
||||
if TP == "mkcp" then
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
local map = {none = "none", srtp = "srtp", utp = "utp", ["wechat-video"] = "wechat",
|
||||
dtls = "dtls", wireguard = "wireguard", dns = "dns"}
|
||||
local udp = {}
|
||||
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
|
||||
g.settings = { domain = node.mkcp_domain }
|
||||
g.settings.value = node.mkcp_domain
|
||||
end
|
||||
udp[#udp+1] = g
|
||||
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
|
||||
c.settings = { password = node.mkcp_seed }
|
||||
s.settings = { value = node.mkcp_seed }
|
||||
end
|
||||
udp[#udp+1] = c
|
||||
udp[#udp+1] = s
|
||||
finalmask.udp = udp
|
||||
elseif TP == "hysteria" then
|
||||
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 url, stun
|
||||
if realm then
|
||||
if realm.token and realm.server_url and realm.realm_id then
|
||||
url = "realm://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||
end
|
||||
url = realm.scheme .. "://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||
stun = realm.stun_servers or node.hysteria2_realm_stun
|
||||
end
|
||||
local r = {
|
||||
@@ -386,43 +385,18 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
end)()
|
||||
} or nil,
|
||||
settings = {
|
||||
vnext = (node.protocol == "vmess" or node.protocol == "vless") and {
|
||||
{
|
||||
address = node.address,
|
||||
port = tonumber(node.port),
|
||||
users = {
|
||||
{
|
||||
id = node.uuid,
|
||||
level = 0,
|
||||
security = (node.protocol == "vmess") and node.security or nil,
|
||||
testpre = (node.protocol == "vless") and tonumber(node.preconns) or nil,
|
||||
encryption = (node.protocol == "vless") and ((node.encryption and node.encryption ~= "") and node.encryption or "none") or nil,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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,
|
||||
address = (node.protocol == "wireguard") and node.wireguard_local_address or node.address,
|
||||
port = tonumber(node.port),
|
||||
id = (node.protocol == "vmess" or node.protocol == "vless") and node.uuid or nil,
|
||||
encryption = (node.protocol == "vless") and ((node.encryption and node.encryption ~= "") and node.encryption or "none") or nil,
|
||||
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,
|
||||
security = (node.protocol == "vmess") and node.security or nil,
|
||||
user = (node.protocol == "socks" or node.protocol == "http") and node.username or nil,
|
||||
pass = (node.protocol == "socks" or node.protocol == "http") and node.password or nil,
|
||||
password = (node.protocol == "shadowsocks" or node.protocol == "trojan") and node.password or nil,
|
||||
method = (node.protocol == "shadowsocks") and ((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) or nil,
|
||||
secretKey = (node.protocol == "wireguard") and node.wireguard_secret_key or nil,
|
||||
peers = (node.protocol == "wireguard") and {
|
||||
{
|
||||
@@ -434,13 +408,13 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
} 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,
|
||||
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
|
||||
result.settings.kernelMode = false
|
||||
result.settings.noKernelTun = true
|
||||
if node.finalmask and node.finalmask ~= "" then
|
||||
local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask))
|
||||
if ok and type(fm) == "table" then
|
||||
@@ -561,7 +535,6 @@ function gen_config_server(node)
|
||||
settings = {
|
||||
method = node.method,
|
||||
password = node.password,
|
||||
ivCheck = ("1" == node.iv_check) and true or false,
|
||||
network = node.ss_network or "TCP,UDP"
|
||||
}
|
||||
elseif node.protocol == "trojan" then
|
||||
@@ -740,21 +713,22 @@ function gen_config_server(node)
|
||||
finalmask = (function()
|
||||
local finalmask = {}
|
||||
if node.transport == "mkcp" then
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
local map = {none = "none", srtp = "srtp", utp = "utp", ["wechat-video"] = "wechat",
|
||||
dtls = "dtls", wireguard = "wireguard", dns = "dns"}
|
||||
local udp = {}
|
||||
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
|
||||
g.settings = { domain = node.mkcp_domain }
|
||||
g.settings.value = node.mkcp_domain
|
||||
end
|
||||
udp[#udp+1] = g
|
||||
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
|
||||
c.settings = { password = node.mkcp_seed }
|
||||
s.settings = { value = node.mkcp_seed }
|
||||
end
|
||||
udp[#udp+1] = c
|
||||
udp[#udp+1] = s
|
||||
finalmask.udp = udp
|
||||
elseif node.transport == "hysteria" then
|
||||
local udp = {}
|
||||
@@ -772,9 +746,7 @@ function gen_config_server(node)
|
||||
local realm = api.parse_realm_uri(node.hysteria2_realm_url)
|
||||
local url, stun
|
||||
if realm then
|
||||
if realm.token and realm.server_url and realm.realm_id then
|
||||
url = "realm://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||
end
|
||||
url = realm.scheme .. "://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||
stun = realm.stun_servers or node.hysteria2_realm_stun
|
||||
end
|
||||
local r = {
|
||||
@@ -1524,7 +1496,7 @@ function gen_config(var)
|
||||
outboundTag = default_outboundTag,
|
||||
}
|
||||
if node.domainStrategy == "IPIfNonMatch" then
|
||||
rule.ip = { "0.0.0.0/0", "::/0" }
|
||||
rule.port = "1-65535"
|
||||
else
|
||||
rule.network = "tcp,udp"
|
||||
end
|
||||
@@ -1718,7 +1690,8 @@ function gen_config(var)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local dns_outbound
|
||||
if dns_listen_port then
|
||||
table.insert(inbounds, {
|
||||
listen = "127.0.0.1",
|
||||
@@ -1775,12 +1748,13 @@ function gen_config(var)
|
||||
}
|
||||
}
|
||||
local type_dns = direct_type_dns
|
||||
table.insert(outbounds, {
|
||||
dns_outbound = {
|
||||
tag = "dns-out",
|
||||
protocol = "dns",
|
||||
proxySettings = type_dns.proxySettings,
|
||||
settings = type_dns.settings
|
||||
})
|
||||
}
|
||||
table.insert(outbounds, dns_outbound)
|
||||
table.insert(routing.rules, 1, {
|
||||
inboundTag = {
|
||||
"dns-in"
|
||||
@@ -1815,6 +1789,7 @@ function gen_config(var)
|
||||
end
|
||||
|
||||
-- Shunt rule DNS logic
|
||||
local dns_out_rules = {}
|
||||
if dns_domain_rules and #dns_domain_rules > 0 then
|
||||
for index, value in ipairs(dns_domain_rules) do
|
||||
if value.domain and value.outboundTag then
|
||||
@@ -1834,19 +1809,19 @@ function gen_config(var)
|
||||
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_block then
|
||||
local dns_block_mode = "Hosts" -- use the Hosts mode to block.
|
||||
if dns_block_mode == "Hosts" then
|
||||
for d_i, d_k in ipairs(value.domain) do
|
||||
dns.hosts[d_k] = "0.0.0.0"
|
||||
end
|
||||
dns_server = nil
|
||||
end
|
||||
else
|
||||
dns_server = nil
|
||||
end
|
||||
table.insert(dns_out_rules, {
|
||||
action = "return",
|
||||
rCode = 0,
|
||||
domain = api.clone(value.domain)
|
||||
})
|
||||
dns_server = nil
|
||||
else
|
||||
table.insert(dns_out_rules, {
|
||||
action = "hijack",
|
||||
qType = "1,28",
|
||||
domain = api.clone(value.domain)
|
||||
})
|
||||
end
|
||||
if dns_server then
|
||||
dns_server.finalQuery = true
|
||||
@@ -1861,6 +1836,11 @@ function gen_config(var)
|
||||
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
|
||||
|
||||
|
||||
@@ -1251,9 +1251,6 @@ msgstr "پاک کردن گزارشها"
|
||||
msgid "Password"
|
||||
msgstr "رمز عبور"
|
||||
|
||||
msgid "IV Check"
|
||||
msgstr "بررسی IV"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "UDP بر روی TCP"
|
||||
|
||||
@@ -1266,12 +1263,6 @@ msgstr "پورت محلی"
|
||||
msgid "Fast Open"
|
||||
msgstr "بازگشایی سریع (Fast Open)"
|
||||
|
||||
msgid "Pre-connections"
|
||||
msgstr "پیشاتصالات"
|
||||
|
||||
msgid "Number of early established connections to reduce latency."
|
||||
msgstr "تعداد اتصالات از پیش برقرار شده برای کاهش تاخیر."
|
||||
|
||||
msgid "plugin"
|
||||
msgstr "افزونه"
|
||||
|
||||
|
||||
@@ -1252,9 +1252,6 @@ msgstr "Очистить журнал"
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
msgid "IV Check"
|
||||
msgstr "Проверка IV"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "UDP через TCP"
|
||||
|
||||
@@ -1267,12 +1264,6 @@ msgstr "Локальный порт"
|
||||
msgid "Fast Open"
|
||||
msgstr "Fast Open"
|
||||
|
||||
msgid "Pre-connections"
|
||||
msgstr "Предварительные соединения"
|
||||
|
||||
msgid "Number of early established connections to reduce latency."
|
||||
msgstr "Количество заранее установленных соединений для снижения задержки."
|
||||
|
||||
msgid "plugin"
|
||||
msgstr "Плагин"
|
||||
|
||||
|
||||
@@ -1243,9 +1243,6 @@ msgstr "清空日志"
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
msgid "IV Check"
|
||||
msgstr "IV 检查"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "TCP 封装 UDP"
|
||||
|
||||
@@ -1258,12 +1255,6 @@ msgstr "本地端口"
|
||||
msgid "Fast Open"
|
||||
msgstr "快速打开"
|
||||
|
||||
msgid "Pre-connections"
|
||||
msgstr "预连接"
|
||||
|
||||
msgid "Number of early established connections to reduce latency."
|
||||
msgstr "预连接的数量,用于降低延迟。"
|
||||
|
||||
msgid "plugin"
|
||||
msgstr "插件"
|
||||
|
||||
|
||||
@@ -1243,9 +1243,6 @@ msgstr "清空日誌"
|
||||
msgid "Password"
|
||||
msgstr "密碼"
|
||||
|
||||
msgid "IV Check"
|
||||
msgstr "IV 檢查"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "TCP 封装 UDP"
|
||||
|
||||
@@ -1258,12 +1255,6 @@ msgstr "本地端口"
|
||||
msgid "Fast Open"
|
||||
msgstr "快速打開"
|
||||
|
||||
msgid "Pre-connections"
|
||||
msgstr "預連接"
|
||||
|
||||
msgid "Number of early established connections to reduce latency."
|
||||
msgstr "預連接的數量,用於降低延遲。"
|
||||
|
||||
msgid "plugin"
|
||||
msgstr "插件"
|
||||
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
START=99
|
||||
|
||||
boot_func() {
|
||||
sleep 90
|
||||
restart
|
||||
}
|
||||
|
||||
boot() {
|
||||
boot_func >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
start() {
|
||||
lua /usr/lib/lua/luci/passwall2/server_app.lua start
|
||||
}
|
||||
|
||||
@@ -88,6 +88,23 @@ config shunt_rules 'Russia'
|
||||
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'
|
||||
|
||||
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'
|
||||
option remarks 'Example'
|
||||
option type 'Xray'
|
||||
@@ -104,6 +121,7 @@ config nodes 'rulenode'
|
||||
option remarks 'rulenode'
|
||||
option type 'Xray'
|
||||
option protocol '_shunt'
|
||||
option PrivateIP '_direct'
|
||||
option default_node 'examplenode'
|
||||
option domainStrategy 'IPOnDemand'
|
||||
option domainMatcher 'hybrid'
|
||||
|
||||
@@ -1937,7 +1937,6 @@ local function curl(url, file, ua, mode)
|
||||
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua
|
||||
curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"'
|
||||
end
|
||||
curl_args[#curl_args + 1] = get_headers()
|
||||
local return_code, result
|
||||
if mode == "direct" then
|
||||
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)
|
||||
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)
|
||||
for _, config in pairs(CONFIG) do
|
||||
if config.currentNodes and #config.currentNodes > 0 then
|
||||
|
||||
@@ -5,17 +5,19 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI based ipk store
|
||||
LUCI_DESCRIPTION:=luci-app-store is a ipk store developed by LinkEase team
|
||||
LUCI_DEPENDS:=@(x86_64||aarch64) +curl +opkg +luci-lib-ipkg +tar +libuci-lua +mount-utils +luci-lib-taskd
|
||||
LUCI_TITLE:=LuCI based ipk/apk store
|
||||
LUCI_DESCRIPTION:=luci-app-store is a ipk/apk store developed by LinkEase team
|
||||
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_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.32-1
|
||||
PKG_VERSION:=0.2.0-r2
|
||||
# PKG_RELEASE MUST be empty for luci.mk
|
||||
PKG_RELEASE:=
|
||||
|
||||
ISTORE_UI_VERSION:=0.1.32
|
||||
ISTORE_UI_VERSION:=0.2.0
|
||||
ISTORE_UI_RELEASE:=1
|
||||
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+= SED="$(SED)"
|
||||
TARGET_CONFIGURE_OPTS+= USE_APK="$(CONFIG_USE_APK)"
|
||||
|
||||
define Package/luci-app-store/conffiles
|
||||
/etc/.app_store.id
|
||||
|
||||
@@ -166,6 +166,11 @@ end
|
||||
function store_index()
|
||||
local fs = require "nixio.fs"
|
||||
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
|
||||
features[#features+1] = "backup"
|
||||
end
|
||||
@@ -251,13 +256,11 @@ function validate_pkgname(val)
|
||||
end
|
||||
|
||||
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 cachefile = cachedir .. "/installed.json"
|
||||
local metapkgpre = "app-meta-"
|
||||
local nixio = require "nixio"
|
||||
local fs = require "nixio.fs"
|
||||
local ipkg = require "luci.model.ipkg"
|
||||
local jsonc = require "luci.jsonc"
|
||||
local result = {}
|
||||
local lock, msg = flock("/var/lock/istore-installed.lock", "lock")
|
||||
@@ -274,7 +277,8 @@ local function get_installed_and_cache()
|
||||
local pkg
|
||||
for pkg in itr do
|
||||
if pkg:match("^.*%.json$") then
|
||||
local metadata = fs.readfile(metadir .. "/" .. pkg)
|
||||
local metafile = metadir .. "/" .. pkg
|
||||
local metadata = fs.readfile(metafile)
|
||||
if metadata ~= nil then
|
||||
local meta = jsonc.parse(metadata)
|
||||
if meta == nil then
|
||||
@@ -291,15 +295,10 @@ local function get_installed_and_cache()
|
||||
}
|
||||
end
|
||||
local time = nil
|
||||
local metapkg = metapkgpre .. meta.name
|
||||
local status = ipkg.status(metapkg)
|
||||
if next(status) ~= nil then
|
||||
time = status[metapkg]["Installed-Time"]
|
||||
local istat = fs.stat(metafile)
|
||||
if istat ~= nil then
|
||||
time = istat["mtime"]
|
||||
else
|
||||
local istat = fs.stat("/usr/lib/opkg/info/" .. metapkg .. ".list")
|
||||
if istat ~= nil then
|
||||
time = istat["mtime"]
|
||||
end
|
||||
cacheable = false
|
||||
end
|
||||
if time ~= nil then
|
||||
@@ -313,7 +312,7 @@ local function get_installed_and_cache()
|
||||
result = data
|
||||
if cacheable then
|
||||
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)
|
||||
mfile:writeall(jsonc.stringify(result))
|
||||
mfile:close()
|
||||
@@ -327,11 +326,10 @@ local function get_installed_and_cache()
|
||||
end
|
||||
|
||||
function store_action(param)
|
||||
local metadir = "/usr/lib/opkg/meta"
|
||||
local metadir = "/tmp/run/istore-meta/meta"
|
||||
local metapkgpre = "app-meta-"
|
||||
local code, out, err, ret
|
||||
local fs = require "nixio.fs"
|
||||
local ipkg = require "luci.model.ipkg"
|
||||
local jsonc = require "luci.jsonc"
|
||||
local json_parse = jsonc.parse
|
||||
local action = param.action or ""
|
||||
@@ -342,18 +340,22 @@ function store_action(param)
|
||||
luci.http.status(400, "Bad Request")
|
||||
return
|
||||
end
|
||||
local metapkg = metapkgpre .. pkg
|
||||
local meta = {}
|
||||
local metadata = fs.readfile(metadir .. "/" .. pkg .. ".json")
|
||||
local metafile = metadir .. "/" .. pkg .. ".json"
|
||||
local metadata = fs.readfile(metafile)
|
||||
|
||||
if metadata ~= nil then
|
||||
meta = json_parse(metadata) or {}
|
||||
end
|
||||
meta.installed = false
|
||||
local status = ipkg.status(metapkg)
|
||||
if next(status) ~= nil then
|
||||
local time = nil
|
||||
local istat = fs.stat(metafile)
|
||||
if istat ~= nil then
|
||||
time = istat["mtime"]
|
||||
end
|
||||
if time ~= nil then
|
||||
meta.installed=true
|
||||
meta.time=tonumber(status[metapkg]["Installed-Time"])
|
||||
meta.time=tonumber(time)
|
||||
end
|
||||
|
||||
ret = meta
|
||||
@@ -571,7 +573,7 @@ function entrysh()
|
||||
errors[#errors+1] = {app=meta.name, code=500, msg="json parse failed: " .. o}
|
||||
else
|
||||
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)
|
||||
mfile:writeall(jsonc.stringify(status))
|
||||
mfile:close()
|
||||
|
||||
+221
-56
@@ -10,25 +10,46 @@ if [ "${action:0:9}" = "AUTOCONF=" ]; then
|
||||
fi
|
||||
|
||||
IS_ROOT=/tmp/is-root
|
||||
DL_DIR=${IS_ROOT}/tmp/dl
|
||||
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"
|
||||
TMP_SELF_COPY=/var/run/cloned-is-opkg
|
||||
USE_APK=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
|
||||
ISTORE_PKG=luci-app-store
|
||||
ISTORE_DEP_PKGS="luci-lib-taskd luci-lib-xterm taskd"
|
||||
ISTORE_INDEX=https://istore.istoreos.com/repo/all/store/Packages.gz
|
||||
|
||||
NEWLINE=$'\n'
|
||||
|
||||
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
|
||||
|
||||
@@ -44,35 +65,71 @@ is_init() {
|
||||
[ -e ${IS_ROOT}/var/lock ] || ln -s /var/lock ${IS_ROOT}/var/lock
|
||||
}
|
||||
|
||||
apk_wrap() {
|
||||
APK_CONFIG=${APK_CONF} apk "$@"
|
||||
}
|
||||
|
||||
opkg_wrap() {
|
||||
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 file
|
||||
if ! $DISABLE_MIRROR; then
|
||||
local newpath="/usr/libexec/istore/mirror-bin:$PATH"
|
||||
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"
|
||||
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
|
||||
DISABLE_MIRROR=true
|
||||
fi
|
||||
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'
|
||||
|
||||
check_space() {
|
||||
@@ -91,14 +148,41 @@ update() {
|
||||
fi
|
||||
|
||||
echo "Fetch feed list for ${ARCH}"
|
||||
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
|
||||
if $USE_APK; then
|
||||
rm -rf ${IS_ROOT}/var/tmp ${IS_ROOT}/etc/arch ${IS_ROOT}/etc/istore.pem
|
||||
mkdir -p ${IS_ROOT}/var/tmp
|
||||
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"
|
||||
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
|
||||
}
|
||||
|
||||
@@ -110,9 +194,30 @@ update_if_outdate() {
|
||||
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() {
|
||||
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 current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
|
||||
local newest=`get_online_version "$ISTORE_INDEX" "$ISTORE_PKG"`
|
||||
local current=`get_local_version "$ISTORE_PKG"`
|
||||
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
||||
echo "Check version failed!" >&2
|
||||
exit 255
|
||||
@@ -124,13 +229,13 @@ check_self_upgrade() {
|
||||
}
|
||||
|
||||
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() {
|
||||
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 current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
|
||||
local newest=`get_online_version "$ISTORE_INDEX" "$ISTORE_PKG"`
|
||||
local current=`get_local_version "$ISTORE_PKG"`
|
||||
if [ "v$newest" = "v" -o "v$current" = "v" ]; then
|
||||
echo "Check version failed!" >&2
|
||||
return 1
|
||||
@@ -139,7 +244,7 @@ do_self_upgrade() {
|
||||
echo "Already the latest version!" >&2
|
||||
return 1
|
||||
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
|
||||
update_if_outdate || return 1
|
||||
do_self_upgrade_0
|
||||
@@ -150,7 +255,13 @@ do_self_upgrade() {
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -160,6 +271,16 @@ wrapped_in_update() {
|
||||
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() {
|
||||
local pkg
|
||||
local pkgs=""
|
||||
@@ -172,10 +293,11 @@ step_upgrade() {
|
||||
fi
|
||||
done
|
||||
if [ -n "$pkgs" ]; then
|
||||
opkg_wrap_mirrors upgrade $pkgs || return 1
|
||||
do_upgrade_in_mirrors $pkgs || return 1
|
||||
fi
|
||||
if [ -n "$metapkg" ]; then
|
||||
opkg_wrap_mirrors upgrade $metapkg || return 1
|
||||
do_upgrade_in_mirrors $metapkg || return 1
|
||||
refresh_installed $metapkg
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
@@ -183,7 +305,7 @@ step_upgrade() {
|
||||
new_upgrade() {
|
||||
check_mtime || return 1
|
||||
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
|
||||
else
|
||||
update_if_outdate
|
||||
@@ -192,7 +314,11 @@ new_upgrade() {
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -224,16 +350,29 @@ try_autoconf() {
|
||||
PATH="$CLEANPATH" /usr/libexec/istorea/${ISTORE_AUTOCONF}.sh
|
||||
}
|
||||
|
||||
|
||||
try_upgrade_depends() {
|
||||
local pkg="$1"
|
||||
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)
|
||||
[ -z "$deps" ] || opkg_wrap_mirrors install $deps
|
||||
local 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
|
||||
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() {
|
||||
local path="$1"
|
||||
[ -f "$path" ] || { echo "file not found: $path" >&2; return 1; }
|
||||
@@ -248,8 +387,8 @@ dotrun() {
|
||||
echo "Executing .run file"
|
||||
chmod 0755 "$path" && "$path"
|
||||
else
|
||||
echo "Installing .ipk file"
|
||||
opkg install "$path"
|
||||
echo "Installing pkg file"
|
||||
$CMD_INSTALL "$path"
|
||||
fi
|
||||
local RET=$?
|
||||
rm -f "$path"
|
||||
@@ -286,6 +425,13 @@ run_records() {
|
||||
echo "]"
|
||||
}
|
||||
|
||||
|
||||
if $USE_APK; then
|
||||
CMD_REMOVE="apk del"
|
||||
else
|
||||
CMD_REMOVE="opkg --autoremove remove"
|
||||
fi
|
||||
|
||||
unrun() {
|
||||
local id="$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
|
||||
for round in `seq 1 $max_round`; do
|
||||
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
|
||||
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
|
||||
done
|
||||
sh -c "$CMD_INSTALLED" > "/tmp/is-root/tmp/post_unrun_$id.txt"
|
||||
@@ -327,13 +473,19 @@ usage() {
|
||||
echo " do_self_upgrade Upgrade iStore"
|
||||
echo " arch Show libc architecture"
|
||||
echo " opkg sys opkg wrap"
|
||||
echo " dotrun {path} install .run/.ipk"
|
||||
echo " run_records list .run/.ipk install records"
|
||||
echo " del_record {id} delete .run/.ipk install record"
|
||||
echo " unrun {id} Remove installed packages by .run/.ipk"
|
||||
echo " dotrun {path} install .run/.ipk/.apk"
|
||||
echo " run_records list .run/.ipk/.apk install records"
|
||||
echo " del_record {id} delete .run/.ipk/.apk install record"
|
||||
echo " unrun {id} Remove installed packages by .run/.ipk/.apk"
|
||||
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
|
||||
|
||||
CLEANPATH="$PATH"
|
||||
@@ -352,7 +504,7 @@ case $action in
|
||||
;;
|
||||
"install")
|
||||
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")
|
||||
try_autoconf
|
||||
@@ -364,10 +516,18 @@ case $action in
|
||||
remove "$@" || remove "$@"
|
||||
;;
|
||||
"info")
|
||||
opkg_wrap info "$@"
|
||||
if $USE_APK; then
|
||||
apk_wrap query "$@"
|
||||
else
|
||||
opkg_wrap info "$@"
|
||||
fi
|
||||
;;
|
||||
"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
|
||||
@@ -394,6 +554,11 @@ case $action in
|
||||
"unrun")
|
||||
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
|
||||
;;
|
||||
|
||||
@@ -7,6 +7,7 @@ boot() {
|
||||
local ARCH=`/bin/is-opkg arch`
|
||||
generate_store_id $ARCH
|
||||
generate_super_arch $ARCH
|
||||
/bin/is-opkg link_meta
|
||||
}
|
||||
|
||||
generate_store_id() {
|
||||
|
||||
@@ -9,4 +9,7 @@ if [ -z "`uci -q get istore.istore.hide_docker`" ] && ! which docker >/dev/null;
|
||||
EOF
|
||||
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
|
||||
@@ -5,11 +5,12 @@
|
||||
IS_ROOT=/tmp/is-backup
|
||||
APP_LIST_FILE=/etc/istore/app.list
|
||||
BACKUP_CONFIG_FILE=/etc/config/istore
|
||||
USE_APK=false
|
||||
[[ -s /etc/apk/arch ]] && USE_APK=true
|
||||
|
||||
action=${1}
|
||||
shift
|
||||
|
||||
|
||||
is_init() {
|
||||
mkdir -p ${IS_ROOT}
|
||||
}
|
||||
@@ -90,23 +91,23 @@ ipk_build() {
|
||||
|
||||
# if arg is NULL, use light backup, otherwise use local 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,
|
||||
#sysupgrade will backup/restore it auto when flash new firmware
|
||||
echo "/etc/.app_store.id" > /lib/upgrade/keep.d/luci-app-store
|
||||
cat /usr/lib/opkg/info/luci-app-store.list >> /lib/upgrade/keep.d/luci-app-store
|
||||
echo "/etc/rc.d/S45istore" >> /lib/upgrade/keep.d/luci-app-store
|
||||
echo "/etc/istore/uci-defaults_bak" >> /lib/upgrade/keep.d/luci-app-store
|
||||
echo "${APP_LIST_FILE}" >> /lib/upgrade/keep.d/luci-app-store
|
||||
echo "${BACKUP_CONFIG_FILE}" >> /lib/upgrade/keep.d/luci-app-store
|
||||
|
||||
#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 $USE_APK; then
|
||||
( cd /lib/apk/packages && ls *.list ) | grep '^app-meta-' | sed 's/.list$//' | sort -u > ${IS_ROOT}/istore_installed_package.list
|
||||
else
|
||||
#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
|
||||
fi
|
||||
|
||||
#if no input backup path, only back app.list
|
||||
mkdir -p /etc/istore
|
||||
@@ -186,6 +187,10 @@ backup() {
|
||||
# if arg is NULL, use light backup, otherwise use local backup
|
||||
restore() {
|
||||
if [ -n "$1" ]; then
|
||||
if $USE_APK; then
|
||||
echo "does not support apk package manager" >&2
|
||||
exit 1
|
||||
fi
|
||||
BACKUP_PATH_FILE=$1
|
||||
else
|
||||
echo "install package by ${APP_LIST_FILE}"
|
||||
@@ -233,7 +238,7 @@ restore() {
|
||||
get_support_backup_features() {
|
||||
echo "light_backup"
|
||||
#istore custom img mean support local_backup
|
||||
if [ -f /etc/istore_img_flag ];then
|
||||
if ! $USE_APK; then
|
||||
echo "local_backup"
|
||||
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:
|
||||
[ -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/"
|
||||
$(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"
|
||||
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`
|
||||
endif
|
||||
mkdir -p "$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n"
|
||||
$(foreach lang,$(LUCI_LANGUAGES),$(foreach po,$(wildcard ${CURDIR}/po/$(lang)/*.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
|
||||
|
||||
PKG_VERSION:=1.0.17-r1
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=1.0.18-r1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
LUCI_TITLE:=LuCI support for SysTools
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@@ -4,8 +4,12 @@ FCURL="curl --fail --show-error"
|
||||
|
||||
curl -V >/dev/null 2>&1 || {
|
||||
echo "prereq: install curl"
|
||||
opkg info curl | grep -Fqm1 curl || opkg update
|
||||
opkg install curl
|
||||
if [ -f /etc/apk/arch ]; then
|
||||
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'`
|
||||
@@ -19,7 +23,7 @@ $FCURL "$ISTORE_REPO/$IPK" | tar -xzO ./data.tar.gz | tar -xzO ./bin/is-opkg > /
|
||||
chmod 755 /tmp/is-opkg
|
||||
/tmp/is-opkg update
|
||||
# /tmp/is-opkg install taskd
|
||||
/tmp/is-opkg opkg install --force-reinstall luci-lib-taskd luci-lib-xterm
|
||||
/tmp/is-opkg opkg install --force-reinstall luci-app-store || exit $?
|
||||
[ -s "/etc/init.d/tasks" ] || /tmp/is-opkg opkg install --force-reinstall taskd
|
||||
[ -s "/usr/lib/lua/luci/cbi.lua" ] || /tmp/is-opkg opkg install luci-compat >/dev/null 2>&1
|
||||
/tmp/is-opkg install --force-reinstall luci-lib-taskd luci-lib-xterm
|
||||
/tmp/is-opkg install --force-reinstall luci-app-store || exit $?
|
||||
[ -s "/etc/init.d/tasks" ] || /tmp/is-opkg install --force-reinstall taskd
|
||||
[ -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
|
||||
PKG_VERSION:=2.6
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
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
|
||||
|
||||
@@ -38,8 +38,6 @@ return view.extend({
|
||||
return fs.exec('/etc/init.d/tinyfilemanager', ['reload'])
|
||||
.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.rmempty = false;
|
||||
|
||||
@@ -725,20 +725,24 @@ const methods = {
|
||||
|
||||
getCPUInfo: {
|
||||
call: function() {
|
||||
if (!access('/sbin/cpuinfo'))
|
||||
return {};
|
||||
if (access('/sbin/cpuinfo')) {
|
||||
const fd = popen('/sbin/cpuinfo');
|
||||
if (fd) {
|
||||
let cpuinfo = fd.read('all');
|
||||
if (!cpuinfo)
|
||||
cpuinfo = '?';
|
||||
fd.close();
|
||||
|
||||
const fd = popen('/sbin/cpuinfo');
|
||||
if (fd) {
|
||||
let cpuinfo = fd.read('all');
|
||||
if (!cpuinfo)
|
||||
cpuinfo = '?';
|
||||
fd.close();
|
||||
|
||||
return { cpuinfo: cpuinfo };
|
||||
} else {
|
||||
return { cpuinfo: error() };
|
||||
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 {
|
||||
@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 {
|
||||
@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"] & {
|
||||
/* 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 {
|
||||
@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 {
|
||||
@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
|
||||
@@ -31,7 +51,7 @@ header {
|
||||
is a pure cross-fade. Below lg the side tracks drop and only the
|
||||
links column remains (tablet → just the submenu). The panel scrolls
|
||||
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 {
|
||||
@apply pointer-events-auto translate-y-0 opacity-100;
|
||||
@@ -136,8 +156,12 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
[data-nav-type="mega-menu"] &:has(.desktop-menu-container.active) {
|
||||
@apply z-70;
|
||||
/* When a category is open the bar lifts to the panel's opened colour so the
|
||||
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 {
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
--danger-surface: oklch(94% 0.05 25);
|
||||
--danger-surface-hover: oklch(90% 0.05 25);
|
||||
--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-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);
|
||||
@@ -67,15 +68,16 @@
|
||||
--brand-subtle: oklch(20.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);
|
||||
--progress-start: oklch(43.18% 0.0865 166.9);
|
||||
--progress-end: oklch(62.1% 0.145 189.6);
|
||||
--progress-start: oklch(44.78% 0.0865 193.2);
|
||||
--progress-end: oklch(0.6 0.13 188.745);
|
||||
--info-surface: oklch(32% 0.05 255);
|
||||
--warning-surface: oklch(33% 0.06 80);
|
||||
--success-surface: oklch(30% 0.05 158);
|
||||
--danger-surface: oklch(32% 0.08 22);
|
||||
--danger-surface-hover: oklch(36% 0.08 22);
|
||||
--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-md: 0 10px 28px oklch(0 0 0 / 0.42);
|
||||
--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-scrim: var(--scrim);
|
||||
--color-mega-menu-bg: var(--mega-menu-bg);
|
||||
--color-mega-menu-scrim: var(--mega-menu-scrim);
|
||||
|
||||
--shadow-sm: var(--app-shadow-sm);
|
||||
--shadow-md: var(--app-shadow-md);
|
||||
|
||||
@@ -69,11 +69,18 @@ body.mobile-navigation-open > header {
|
||||
}
|
||||
|
||||
[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 {
|
||||
@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 {
|
||||
@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);
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
const { li, nav, menuLink, hasSubmenu } = this.buildDropdownItem(
|
||||
child,
|
||||
@@ -626,6 +664,14 @@ return baseclass.extend({
|
||||
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(() => {
|
||||
const wasActive = nav.classList.contains("active");
|
||||
|
||||
@@ -638,10 +684,11 @@ return baseclass.extend({
|
||||
if (container) {
|
||||
applyCanvasHeight();
|
||||
container.classList.add("active");
|
||||
container.classList.remove("closing");
|
||||
overlay.classList.add("active");
|
||||
}
|
||||
nav.classList.add("active");
|
||||
}, 100);
|
||||
}, delay);
|
||||
});
|
||||
|
||||
li.addEventListener("mouseleave", () => {
|
||||
@@ -724,42 +771,47 @@ return baseclass.extend({
|
||||
document.querySelector(".desktop-menu-overlay")?.classList.remove("active");
|
||||
|
||||
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");
|
||||
|
||||
// --mega-menu-height is set on open in initMegaMenu and otherwise never
|
||||
// cleared. Drop it back to the h-0 fallback once the container is fully
|
||||
// hidden, so a closed menu doesn't leave invisible scrollable space
|
||||
// below the header on pages shorter than the canvas.
|
||||
const resetHeight = () => {
|
||||
// Retract the canvas: dropping --mega-menu-height lets the container's
|
||||
// height fall to its 0 fallback, and because the container transitions
|
||||
// height (see _layout.css) it animates the collapse — the drawer close —
|
||||
// instead of the height vanishing in one frame after the fade. overflow
|
||||
// -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")) {
|
||||
container.style.removeProperty("--mega-menu-height");
|
||||
container.classList.remove("closing");
|
||||
}
|
||||
};
|
||||
|
||||
// If the menu was never open, removing "active" is a no-op: clip-path
|
||||
// never changes, so transitionend/transitioncancel would never fire and
|
||||
// these listeners would pile up on every header mouseleave.
|
||||
if (
|
||||
!wasActive ||
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
) {
|
||||
resetHeight();
|
||||
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||
finishClosing();
|
||||
return;
|
||||
}
|
||||
|
||||
const onClipPathSettled = (event) => {
|
||||
if (event.target !== container || event.propertyName !== "clip-path") {
|
||||
return;
|
||||
}
|
||||
container.removeEventListener("transitionend", onClipPathSettled);
|
||||
container.removeEventListener("transitioncancel", onClipPathSettled);
|
||||
resetHeight();
|
||||
};
|
||||
|
||||
container.addEventListener("transitionend", onClipPathSettled);
|
||||
container.addEventListener("transitioncancel", onClipPathSettled);
|
||||
container.addEventListener("transitionend", finishClosing);
|
||||
closeFallback = setTimeout(finishClosing, 350);
|
||||
},
|
||||
|
||||
renderModeMenu(tree) {
|
||||
|
||||
@@ -94,6 +94,12 @@ class FakeElement {
|
||||
this.listeners.set(type, listeners);
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
for (const listener of this.listeners.get(event.type) ?? []) {
|
||||
listener(event);
|
||||
}
|
||||
}
|
||||
|
||||
closest(selector) {
|
||||
let current = this;
|
||||
|
||||
@@ -169,6 +175,15 @@ class FakeElement {
|
||||
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) {
|
||||
this.attributes.set(name, 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.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"]);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const title = getBlock(layoutStyles, "& .desktop-nav-title");
|
||||
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)");
|
||||
});
|
||||
|
||||
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 d = resolveMode("dark");
|
||||
assert.ok(l.mega_menu_bg.includes("/ 0.66"), `light: ${l.mega_menu_bg}`);
|
||||
assert.ok(d.mega_menu_bg.includes("/ 0.62"), `dark: ${d.mega_menu_bg}`);
|
||||
// The panel animates height, so it carries no backdrop-filter; and it is
|
||||
// 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", () => {
|
||||
@@ -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.on_brand, "oklch(1 0 0)");
|
||||
assert.equal(dark.progress_start, "oklch(43.18% 0.0865 166.9)");
|
||||
assert.equal(dark.progress_end, "oklch(62.1% 0.145 189.6)");
|
||||
// Derived from brand like light: end aliases brand, start mixes toward sunken.
|
||||
assert.equal(dark.progress_start, "oklch(44.78% 0.0865 193.2)");
|
||||
assert.equal(dark.progress_end, dark.brand);
|
||||
});
|
||||
|
||||
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}`);
|
||||
});
|
||||
|
||||
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 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"));
|
||||
assert.ok(panel?.includes("backdrop-blur-lg"), `panel blur: ${panel}`);
|
||||
assert.ok(overlay.includes("max-md:backdrop-blur-lg"), "mobile overlay blur");
|
||||
assert.ok(!panel?.includes("backdrop-blur"), `panel must not blur: ${panel}`);
|
||||
assert.ok(
|
||||
/&\.active\s*\{\s*@apply[^;]*backdrop-blur-lg/.test(overlay),
|
||||
"desktop overlay blur",
|
||||
panel?.includes("transition-[height,visibility]"),
|
||||
`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", () => {
|
||||
@@ -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 activeHeaderRule =
|
||||
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] ?? "";
|
||||
const dropdownRule = dropdown.match(/&\.dropdown\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_hover: ["shade", "danger_surface", -0.04],
|
||||
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: {
|
||||
text_muted: ["mix", "text", "bg", 0.62],
|
||||
@@ -33,15 +45,24 @@ export const DERIVATIONS = {
|
||||
brand_subtle: ["mix", "brand", "bg", 0.16],
|
||||
brand_subtle_hover: ["shade", "brand_subtle", 0.04],
|
||||
focus_ring: ["alpha", "brand", 0.6],
|
||||
progress_start: ["const", "oklch(0.4318 0.0865 166.91)"],
|
||||
progress_end: ["const", "oklch(0.621 0.145 189.632)"],
|
||||
// Derived from brand like light — not hardcoded — so the bar tracks the
|
||||
// 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],
|
||||
warning_surface: ["set", "warning", 0.33, 0.06],
|
||||
success_surface: ["set", "success", 0.3, 0.05],
|
||||
danger_surface: ["set", "danger", 0.32, 0.08],
|
||||
danger_surface_hover: ["shade", "danger_surface", 0.04],
|
||||
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_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=0.12.11
|
||||
PKG_RELEASE:=28
|
||||
PKG_VERSION:=0.12.12
|
||||
PKG_RELEASE:=29
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! 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 */
|
||||
@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:=
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_VERSION:=7.1
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
CONFIG_LUCI_CSSTIDY:=
|
||||
|
||||
@@ -89,18 +89,17 @@ return baseclass.extend({
|
||||
},
|
||||
|
||||
getConditionalMenuStamp: function() {
|
||||
var googleFuMode = !!document.querySelector('.navbar a[href*="/admin/services/openclash"]');
|
||||
|
||||
return Promise.all([
|
||||
this.getFileListStamp('/usr/share/luci/menu.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) {
|
||||
return JSON.stringify({
|
||||
google_fu_mode: googleFuMode,
|
||||
menu_definitions: stamps[0],
|
||||
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
|
||||
|
||||
PKG_NAME:=mihomo
|
||||
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_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MIRROR_HASH:=skip
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL3.0+
|
||||
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
||||
PKG_MAINTAINER:=Anya Lin <hukk1996@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
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_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_INSTALL_BIN_PATH:=/usr/libexec
|
||||
|
||||
include $(INCLUDE_DIR)/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
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=A rule based proxy in Go.
|
||||
TITLE:=Another Mihomo Kernel.
|
||||
URL:=https://wiki.metacubex.one
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
PROVIDES:=mihomo
|
||||
ALTERNATIVES:=\
|
||||
300:/usr/bin/mihomo:/usr/libexec/mihomo
|
||||
endef
|
||||
|
||||
define Package/mihomo/description
|
||||
Mihomo is a rule based proxy in Go.
|
||||
endef
|
||||
|
||||
define Package/mihomo/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
100:/usr/bin/mihomo:/usr/libexec/mihomo-core
|
||||
USERID:=mihomo=7890:mihomo=7890
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(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
|
||||
|
||||
$(eval $(call GoBinPackage,mihomo))
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=momo
|
||||
PKG_VERSION:=2026.06.03
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
|
||||
PKG_LICENSE:=GPL-3.0+
|
||||
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"
|
||||
|
||||
## firewall
|
||||
NFT_DIR="$HOME_DIR/firewall"
|
||||
GEOIP_CN_NFT="$NFT_DIR/geoip_cn.nft"
|
||||
GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft"
|
||||
FIREWALL_DIR="$HOME_DIR/firewall"
|
||||
GEOIP_CN_NFT="$FIREWALL_DIR/geoip_cn.nft"
|
||||
GEOIP6_CN_NFT="$FIREWALL_DIR/geoip6_cn.nft"
|
||||
|
||||
## log
|
||||
LOG_DIR="/var/log/momo"
|
||||
@@ -63,7 +63,7 @@ get_paths() {
|
||||
json_add_string firewall_include_sh "$FIREWALL_INCLUDE_SH"
|
||||
json_add_string debug_sh "$DEBUG_SH"
|
||||
|
||||
json_add_string nft_dir "$NFT_DIR"
|
||||
json_add_string firewall_dir "$FIREWALL_DIR"
|
||||
json_add_string geoip_cn_nft "$GEOIP_CN_NFT"
|
||||
json_add_string geoip6_cn_nft "$GEOIP6_CN_NFT"
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
import { readfile } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { connect } from 'ubus';
|
||||
import { get_paths, uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups } from '/etc/momo/ucode/include.uc';
|
||||
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 paths = get_paths();
|
||||
|
||||
const cgroups_version = get_cgroups_version();
|
||||
|
||||
const users = get_users();
|
||||
@@ -21,7 +19,7 @@
|
||||
const uci = cursor();
|
||||
const ubus = connect();
|
||||
|
||||
const profile = json(readfile(paths.run_profile_path));
|
||||
const profile = load_profile();
|
||||
|
||||
uci.load('momo');
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=nexttrace
|
||||
PKG_VERSION:=1.7.0
|
||||
PKG_RELEASE:=6
|
||||
PKG_VERSION:=1.7.1
|
||||
PKG_RELEASE:=7
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
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_VERSION:=1.24.0
|
||||
PKG_RELEASE:=4
|
||||
PKG_RELEASE:=5
|
||||
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
@@ -8,23 +8,26 @@ NAME=opera-proxy
|
||||
PROG=/usr/bin/${NAME}
|
||||
|
||||
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_get country main country "EU"
|
||||
config_get bind_address main bind_address "127.0.0.1:18080"
|
||||
config_get verbosity main verbosity "20"
|
||||
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
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG"
|
||||
procd_append_param command -country "$country"
|
||||
procd_append_param command -bind-address "$bind_address"
|
||||
procd_append_param command -verbosity "$verbosity"
|
||||
procd_append_param command -timeout "$timeout"
|
||||
|
||||
[ -n "$country" ] && procd_append_param command -country "$country"
|
||||
[ -n "$bind_address" ] && procd_append_param command -bind-address "$bind_address"
|
||||
[ -n "$verbosity" ] && procd_append_param command -verbosity "$verbosity"
|
||||
[ -n "$timeout" ] && procd_append_param command -timeout "$timeout"
|
||||
[ "$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 stdout 1
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=qBittorrent
|
||||
PKG_VERSION:=5.2.1
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=5.2.2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/qbittorrent/qBittorrent/tar.gz/release-$(PKG_VERSION)?
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=tuic-client
|
||||
PKG_VERSION:=1.8.6
|
||||
PKG_RELEASE:=10
|
||||
PKG_VERSION:=1.8.7
|
||||
PKG_RELEASE:=11
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/Itsusinn/tuic/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
Reference in new issue
Block a user