Compare commits

..
4 Commits
Author SHA1 Message Date
action ec44f6fe2e update 2026-09-06 05:51:06
marry-jell / merge (push) Canceled after 0s
2026-09-06 05:51:06 +08:00
action 37eb246de8 update 2026-09-06 01:51:51 2026-09-06 01:51:51 +08:00
action 47831f020d update 2026-09-05 23:35:35 2026-09-05 23:35:35 +08:00
cwx 02766f9a2f Remove 'luci-app-daede' from git_sparse_clone 2026-09-05 23:35:06 +08:00
46 changed files with 922 additions and 565 deletions
+1 -1
View File
@@ -178,7 +178,7 @@ jobs:
git_sparse_clone master https://github.com/fengqi/luci-app-uugamebooster \
uugamebooster luci-app-uugamebooster
git_sparse_clone master https://github.com/kenzok8/small \
clashoo luci-app-clashoo luci-app-daede
clashoo luci-app-clashoo
) &
(
git_clone https://github.com/muink/luci-app-dnsproxy
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.08.28
PKG_RELEASE:=3
PKG_RELEASE:=4
PKG_SOURCE:=dae-src-2026.08.28-d6aca8a10b35.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
-1
View File
@@ -30,7 +30,6 @@ start_service() {
procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray" TZ="$(uci -q get system.@system[0].zonename)"
procd_set_param command "$PROG" run
procd_append_param command --config "$config_file"
procd_append_param command --disable-timestamp
procd_append_param command --logfile "$LOG_DIR/dae.log"
procd_append_param command --logfile-maxbackups "$log_maxbackups"
procd_append_param command --logfile-maxsize "$log_maxsize"
+78
View File
@@ -0,0 +1,78 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=filebrowser-q
PKG_VERSION:=1.5.6-stable
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/gtsteffaniak/filebrowser/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=skip
PKG_BUILD_DIR:=$(BUILD_DIR)/filebrowser-$(PKG_VERSION)
PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=kiddin9
PKG_BUILD_DEPENDS:=golang/host node/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
GO_PKG:=github.com/gtsteffaniak/filebrowser/backend
GO_PKG_BUILD_DIR:=$(PKG_BUILD_DIR)/backend
GO_PKG_LDFLAGS_X:= \
$(GO_PKG)/internal/version.Version=v$(PKG_VERSION) \
$(GO_PKG)/internal/version.CommitSHA=$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/filebrowser-q
SECTION:=utils
CATEGORY:=Utilities
SUBMENU:=Filesystem
TITLE:=FileBrowser Quantum - Modern Web File Manager
URL:=https://github.com/gtsteffaniak/filebrowser
DEPENDS:=$(GO_ARCH_DEPENDS)
endef
define Package/filebrowser-q/description
FileBrowser Quantum provides a modern, responsive web-based file management
interface with multi-source, real-time search, and enhanced preview features.
endef
define Package/filebrowser-q/conffiles
/etc/filebrowser-q/
/etc/config/filebrowser-q
endef
define Build/Prepare
$(call Build/Prepare/Default)
endef
define Build/Compile
( \
pushd $(PKG_BUILD_DIR)/frontend && \
npm install && \
npm run build ; \
)
( \
cd $(PKG_BUILD_DIR)/backend && \
$(GO_PKG_VARS) \
go build \
-trimpath \
-ldflags="-w -s" \
-o $(PKG_BUILD_DIR)/filebrowser . ; \
)
endef
define Package/filebrowser-q/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/filebrowser $(1)/usr/bin/filebrowser-q
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) $(CURDIR)/files/filebrowser.config $(1)/etc/config/filebrowser-q
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/filebrowser.init $(1)/etc/init.d/filebrowser-q
endef
$(eval $(call BuildPackage,filebrowser-q))
+13
View File
@@ -0,0 +1,13 @@
server:
port: 8989
database: "/etc/filebrowser-q/database.db"
sources:
- path: "/" # Do not use a root "/" directory or include the "/var" folder
config:
defaultEnabled: true
auth:
adminUsername: admin
adminPassword: "admin"
userDefaults:
ui:
locale: "zhCN"
+5
View File
@@ -0,0 +1,5 @@
config filebrowser 'config'
option enabled '0'
option listen_port '8989'
option root_path '/'
+60
View File
@@ -0,0 +1,60 @@
#!/bin/sh /etc/rc.common
START=99
STOP=10
CONF="filebrowser-q"
PROG="/usr/bin/filebrowser-q"
CONF_PATH="/etc/filebrowser-q/config.yaml"
PID_FILE="/var/run/filebrowser-q.pid"
start() {
config_load "$CONF"
local enabled
config_get_bool enabled "config" "enabled" "0"
[ "$enabled" -eq "1" ] || return 1
local listen_port root_path root_name
config_get listen_port "config" "listen_port" "8787"
config_get root_path "config" "root_path" "/"
# 路径为 / 时 name 设为 root,否则留空
root_name=""
[ "$root_path" = "/" ] && root_name="root"
if [ ! -f "$CONF_PATH" ]; then
mkdir -p "$(dirname "$CONF_PATH")"
cat <<EOF > "$CONF_PATH"
server:
port: $listen_port
database: "/etc/filebrowser-q/database.db"
sources:
- path: "$root_path"
name: "$root_name"
config:
defaultEnabled: true
auth:
adminUsername: "admin"
adminPassword: "admin"
userDefaults:
ui:
locale: "zhCN"
EOF
else
grep -q " name:" "$CONF_PATH" || sed -i "s,.*- path:.*,&\n name: \"\"," "$CONF_PATH"
sed -e "s/ port:.*/ port: $listen_port/" \
-e "s, - path:.*, - path: \"$root_path\"," \
-e "s/ name: \".*\"/ name: \"$root_name\"/" \
-i "$CONF_PATH"
fi
echo "Starting filebrowser..."
start-stop-daemon -S -q -b -m -p "$PID_FILE" -x "$PROG" -- -c "$CONF_PATH"
}
stop() {
kill -9 `pidof filebrowser-q | sed "s/$$//g"` 2>/dev/null
rm -f "$PID_FILE"
echo "filebrowser stopped"
}
+1
View File
@@ -37,6 +37,7 @@ define KernelPackage/mt7603e
CATEGORY:=Kernel modules
TITLE:=MTK wifi AP driver
DEPENDS:=@TARGET_ramips
CONFLICTS:=kmod-mt7603
FILES:=$(PKG_BUILD_DIR)/mt7603_wifi_ap/mt7603e.ko
SUBMENU:=Wireless Drivers
MENU:=1
+1
View File
@@ -25,6 +25,7 @@ define KernelPackage/mt76x2e
CATEGORY:=Kernel modules
TITLE:=MTK MT76x2e wifi AP driver
DEPENDS:=@TARGET_ramips
CONFLICTS:=kmod-mt76x2
FILES:=$(PKG_BUILD_DIR)/mt76x2_ap/mt76x2_ap.ko
SUBMENU:=Wireless Drivers
MENU:=1
+6 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-daede
PKG_VERSION:=1.14.7
PKG_RELEASE:=24
PKG_RELEASE:=25
PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -78,6 +78,11 @@ define Package/$(PKG_NAME)/install
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/proxy-check.sh $(1)/usr/share/luci-app-daede/proxy-check.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/fetch-clash-yaml.sh $(1)/usr/share/luci-app-daede/fetch-clash-yaml.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-backup.sh $(1)/usr/share/luci-app-daede/config-backup.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-defaults.sh $(1)/usr/share/luci-app-daede/config-defaults.sh
$(INSTALL_DIR) $(1)/usr/share/luci-app-daede/defaults
$(INSTALL_DATA) $(CURDIR)/../dae/files/dae.config $(1)/usr/share/luci-app-daede/defaults/dae
$(INSTALL_DATA) $(CURDIR)/../daed/files/daed.config $(1)/usr/share/luci-app-daede/defaults/daed
$(INSTALL_DATA) ./root/etc/config/daede $(1)/usr/share/luci-app-daede/defaults/daede
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/refresh-index.sh $(1)/usr/share/luci-app-daede/refresh-index.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/geo-cron.sh $(1)/usr/share/luci-app-daede/geo-cron.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/daed-sub-update.sh $(1)/usr/share/luci-app-daede/daed-sub-update.sh
@@ -6,6 +6,7 @@
'require ui';
'require view';
'require view.daede.backend as backend';
'require view.daede.styles as styles';
const MAX_LINES = 5000;
@@ -54,6 +55,7 @@ const CSS = [
/* 拆字段:time="May 25 07:04:59" level=info msg="..." key=val key="val with space" ... */
const RE_LINE = /^time="([^"]*)"\s+level=(\w+)\s+msg=(?:"((?:[^"\\]|\\.)*)"|(\S+))\s*(.*)$/;
const RE_PREFIXED_LINE = /^\[([^\]]+)\]\s+(DEBUG|INFO|WARN(?:ING)?|ERROR|FATAL|PANIC)\s*(.*)$/i;
const RE_PLAIN_LEVEL_LINE = /^\s*(DEBUG|INFO|WARN(?:ING)?|ERROR|FATAL|PANIC)\s+(.*)$/i;
function detectLevel(line) {
// daed/dae logs use lvl=info / [INFO] / level=warning style
@@ -117,13 +119,21 @@ function parseLine(line) {
}
m = line.match(RE_PREFIXED_LINE);
let ts = '', lvl = '', body = '';
if (m) {
ts = formatTs(m[1]);
lvl = m[2];
body = m[3] || '';
} else {
m = line.match(RE_PLAIN_LEVEL_LINE);
if (!m) return null;
const body = m[3] || '';
lvl = m[1];
body = m[2] || '';
}
const kvStart = body.search(/(?:^|\s)(?=[A-Za-z_][\w.-]*=)/);
return {
ts: formatTs(m[1]),
lvl: m[2],
ts: ts,
lvl: lvl,
msg: kvStart === -1 ? body : body.slice(0, kvStart).trimEnd(),
kv: kvStart === -1 ? '' : body.slice(kvStart).trim()
};
@@ -135,11 +145,10 @@ function buildLine(ln) {
if (!parsed) {
return E('div', { 'class': 'dd-line ' + cls }, ln);
}
const parts = [
E('span', { 'class': 'dd-ts' }, parsed.ts),
E('span', { 'class': 'dd-lvl ' + lvlClass(parsed.lvl) }, lvlShort(parsed.lvl)),
E('span', { 'class': 'dd-msg' }, parsed.msg)
];
const parts = [];
if (parsed.ts) parts.push(E('span', { 'class': 'dd-ts' }, parsed.ts));
parts.push(E('span', { 'class': 'dd-lvl ' + lvlClass(parsed.lvl) }, lvlShort(parsed.lvl)));
parts.push(E('span', { 'class': 'dd-msg' }, parsed.msg));
if (parsed.kv) parts.push(E('span', { 'class': 'dd-kv' }, parsed.kv));
return E('div', { 'class': 'dd-line ' + cls }, parts);
}
@@ -158,7 +167,8 @@ return view.extend({
paused: false,
autoScroll: true,
filter: '',
userScrolled: false
userScrolled: false,
reading: false
};
const pane = E('div', { 'class': 'dd-log-pane', 'id': 'dd-log-pane' }, [
@@ -173,19 +183,29 @@ return view.extend({
const meta = E('span', { 'class': 'dd-log-meta' }, '');
const cbAuto = E('input', { 'type': 'checkbox', 'checked': 'checked' });
cbAuto.addEventListener('change', function() {
state.autoScroll = cbAuto.checked;
const btnAuto = E('button', { 'type': 'button', 'class': 'dd-log-btn dd-log-toggle', 'id': 'dd-log-auto' });
const btnPause = E('button', { 'type': 'button', 'class': 'dd-log-btn dd-log-toggle', 'id': 'dd-log-pause' });
const syncControls = function() {
btnAuto.textContent = state.autoScroll ? '✓ ' + _('Auto-scroll: On') : '○ ' + _('Auto-scroll: Off');
btnAuto.setAttribute('aria-pressed', String(state.autoScroll));
btnPause.textContent = state.paused ? '▶ ' + _('Resume') : 'Ⅱ ' + _('Pause');
btnPause.setAttribute('aria-pressed', String(state.paused));
meta.textContent = state.paused ? _('Paused') : '';
};
btnAuto.addEventListener('click', function() {
state.autoScroll = !state.autoScroll;
if (state.autoScroll) {
pane.scrollTop = pane.scrollHeight;
state.userScrolled = false;
}
syncControls();
});
const cbPause = E('input', { 'type': 'checkbox' });
cbPause.addEventListener('change', function() {
state.paused = cbPause.checked;
btnPause.addEventListener('click', function() {
state.paused = !state.paused;
syncControls();
if (!state.paused) tick();
});
syncControls();
const selFilter = E('select', { 'class': 'dd-log-btn' }, [
E('option', { 'value': '' }, _('All')),
@@ -287,9 +307,11 @@ return view.extend({
}
function tick() {
if (state.paused) return Promise.resolve();
if (state.paused || state.reading) return Promise.resolve();
state.reading = true;
return fs.stat(LOG_PATH).then(function(st) {
if (state.paused) return;
const size = st.size || 0;
if (size === state.lastSize) {
meta.textContent = '%d bytes · live'.format(size);
@@ -299,6 +321,7 @@ return view.extend({
// File rotated/truncated → full reload
const rotated = size < state.lastSize;
return fs.read_direct(LOG_PATH, 'text').then(function(content) {
if (state.paused) return;
content = content || '';
let delta;
if (rotated || state.lastContent === '') {
@@ -323,6 +346,7 @@ return view.extend({
pane.scrollTop = pane.scrollHeight;
});
}).catch(function(e) {
if (state.paused) return;
const msg = String(e);
if (msg.indexOf('NotFoundError') !== -1 || msg.indexOf('No such') !== -1)
renderEmpty(_('Log file does not exist yet.'));
@@ -331,15 +355,15 @@ return view.extend({
state.lastSize = -1;
state.lastContent = '';
meta.textContent = '';
});
}).finally(function() { state.reading = false; });
}
poll.add(tick);
tick();
const toolbarItems = [
E('label', {}, [ cbAuto, _('Auto-scroll') ]),
E('label', {}, [ cbPause, _('Pause') ]),
btnAuto,
btnPause,
selFilter,
btnClear,
btnDownload,
@@ -350,7 +374,7 @@ return view.extend({
const toolbar = E('div', { 'class': 'dd-log-toolbar' }, toolbarItems);
return E('div', { 'class': 'dd-log-wrap' }, [
E('style', {}, CSS),
E('style', {}, CSS + styles.CSS),
E('div', { 'class': 'dd-log-card' }, [
E('h4', { 'class': 'dd-log-card-title' }, _('%s Realtime Log').format(ctx.name)),
toolbar,
@@ -4,6 +4,9 @@
'require baseclass';
const CSS = [
'.dd-log-toolbar .dd-log-toggle[aria-pressed="true"]{background:rgba(74,160,101,.16);border-color:#4aa065;box-shadow:inset 0 0 0 1px rgba(74,160,101,.2)}',
'.dd-log-toolbar .dd-log-toggle:focus-visible{outline:2px solid #4aa065;outline-offset:2px}',
'.dd-wrap{padding:4px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}',
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:9px 14px;margin-bottom:7px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
/* padding:0 neutralizes Argon's h4{padding:.75rem 1.25rem}, which otherwise
@@ -7,6 +7,7 @@
'require ui';
'require view';
'require view.daede.backend as backend';
'require view.daede.daed-session as daedSession';
const DATA_PATHS = {
geoip: '/usr/share/v2ray/geoip.dat',
@@ -53,6 +54,9 @@ 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-backup-buttons{display:flex;align-items:center;gap:8px}',
'.dd-backup-reset{border-color:rgba(217,109,109,.55);color:#d96d6d}',
'@media(max-width:640px){.dd-backup-row{grid-template-columns:24px minmax(0,1fr)}.dd-backup-row .dd-up-meta{grid-column:2;white-space:normal}.dd-backup-row>span:nth-child(4){display:none}.dd-backup-buttons{grid-column:2;flex-wrap:wrap}}',
'.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%}',
@@ -202,65 +206,126 @@ 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'));
// Configuration operations share a backend lock and one UI busy state.
const backupScript = '/usr/share/luci-app-daede/config-backup.sh';
const exportBtn = E('button', { 'class': 'dd-up-btn', 'type': 'button' }, _('Export'));
const importBtn = E('button', { 'class': 'dd-up-btn', 'type': 'button' }, _('Import'));
const resetBtn = E('button', { 'class': 'dd-up-btn dd-backup-reset', 'type': 'button' }, _('Restore Defaults'));
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();
let backupBusy = false;
const showBackupError = function(e) {
ui.addNotification(null, E('p', {}, e.message || String(e)), 'error');
};
const configOperation = function(button, operation) {
if (backupBusy) return Promise.resolve();
backupBusy = true;
const label = button.textContent;
[exportBtn, importBtn, resetBtn].forEach(function(b) { b.disabled = true; });
button.textContent = _('Working…');
return uci.changes().then(function(changes) {
if (['dae', 'daed', 'daede'].some(function(name) { return changes && changes[name] && changes[name].length; }))
throw new Error(_('Apply or discard pending daede changes first.'));
return operation();
}).catch(showBackupError).finally(function() {
backupBusy = false;
[exportBtn, importBtn, resetBtn].forEach(function(b) { b.disabled = false; });
button.textContent = label;
fileInput.value = '';
});
};
const confirmBackup = function(message) {
return new Promise(function(resolve) {
const answer = function(value) { ui.hideModal(); resolve(value); };
ui.showModal(_('Confirm configuration operation'), [
E('p', {}, message),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'cbi-button', 'click': function() { answer(false); } }, _('Cancel')),
' ',
E('button', { 'class': 'cbi-button cbi-button-negative', 'click': function() { answer(true); } }, _('Continue'))
])
]);
});
};
const execBackup = function(args) {
return fs.exec(backupScript, args).then(function(res) {
if (!res || res.code !== 0)
throw new Error(String(res && (res.stderr || res.stdout) || _('Configuration operation failed.')).trim());
return res;
});
};
const waitBackup = function() {
let tries = 0;
const check = function() {
return fs.read_direct('/tmp/luci-app-daede.backup.log', 'text').then(function(content) {
logPane.textContent = content;
logPane.classList.add('show');
return;
}
let bin;
try {
bin = atob(res.stdout.trim());
} catch (e) {
logPane.textContent = _('Export failed: invalid base64 data from server');
logPane.classList.add('show');
return;
}
if (!bin || bin.length === 0) {
logPane.textContent = _('Export failed: empty archive');
logPane.classList.add('show');
return;
}
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' }));
if (/^✗/m.test(content)) throw new Error(_('Configuration operation failed. See the log below.'));
if (/^✓/m.test(content)) return;
if (++tries > 90) throw new Error(_('Operation is still running. Check the log before retrying.'));
return new Promise(function(resolve) { setTimeout(resolve, 2000); }).then(check);
});
};
return check();
};
const reloadConfig = function(message) {
daedSession.clear(window.localStorage);
['dae', 'daed', 'daede'].forEach(function(name) { uci.unload(name); });
ui.showModal(_('Configuration restored'), [
E('p', {}, message),
E('div', { 'class': 'right' }, E('button', {
'class': 'cbi-button cbi-button-positive', 'click': function() { window.location.reload(); }
}, _('Reload Page')))
]);
};
exportBtn.addEventListener('click', async function() {
if (!await confirmBackup(_('Export briefly stops running dae/daed services to back up the database safely, then resumes them. Continue?'))) return;
configOperation(exportBtn, function() {
return execBackup(['export']).then(function(res) {
const bin = atob((res.stdout || '').trim());
if (!bin) throw new Error(_('Export failed: empty archive'));
const bytes = Uint8Array.from(bin, function(c) { return c.charCodeAt(0); });
const url = URL.createObjectURL(new Blob([bytes], { '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);
document.body.appendChild(a); a.click(); a.remove();
setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
}).catch(function(e) {
logPane.textContent = _('Export failed') + ': ' + (e ? (e.message || String(e)) : _('script not found'));
logPane.classList.add('show');
}).finally(function() {
exportBtn.disabled = false; exportBtn.textContent = orig;
});
});
});
importBtn.addEventListener('click', function() { fileInput.click(); });
fileInput.addEventListener('change', function(ev) {
fileInput.addEventListener('change', async 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?'))) {
if (file.size > 184320) {
showBackupError(new Error(_('Backup exceeds the 180 KiB limit. Use System Backup for larger files.')));
fileInput.value = ''; return;
}
if (!await confirmBackup(_('Import replaces daede settings, nodes, subscriptions and dashboard data. Services stop first; the saved active backend starts only if enabled in the backup. Continue?'))) {
fileInput.value = ''; return;
}
configOperation(importBtn, function() {
return new Promise(function(resolve, reject) {
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(e) {
logPane.textContent = _('Import failed') + ': ' + (e ? (e.message || String(e)) : _('unknown error'));
logPane.classList.add('show');
}).finally(function() { fileInput.value = ''; });
};
reader.onerror = function() { reject(new Error(_('Unable to read backup file.'))); };
reader.onload = function() { resolve(String(reader.result).split(',')[1] || ''); };
reader.readAsDataURL(file);
}).then(function(b64) {
// Per-tab uploads cannot overwrite each other before the backend locks.
const token = Array.from(window.crypto.getRandomValues(new Uint8Array(16)), function(n) { return n.toString(16).padStart(2, '0'); }).join('');
const upload = '/tmp/daede-import.' + token + '.b64';
return fs.write(upload, b64).then(function() { return execBackup(['import', upload]); })
.finally(function() { return fs.remove(upload).catch(function() {}); });
}).then(waitBackup).then(function() {
reloadConfig(_('Backup restored. The active backend follows the saved enabled setting.'));
});
});
});
resetBtn.addEventListener('click', async function() {
if (!await confirmBackup(_('Restore ALL daede defaults? This clears dae/daed settings, nodes, subscriptions and daed dashboard accounts/data. Export a backup first if needed. Both backends will remain disabled. Installed programs and GeoData are kept.'))) return;
configOperation(resetBtn, function() {
return execBackup(['reset']).then(waitBackup).then(function() {
reloadConfig(_('All defaults restored. Both backends are disabled. Configure your nodes and routing before starting manually.'));
});
});
});
const refresh = function() {
@@ -508,12 +573,15 @@ return view.extend({
]),
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('div', { 'class': 'dd-up-row dd-backup-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
E('span', {
'class': 'dd-up-meta',
'title': _('Backups contain account and subscription credentials. Store them securely.')
}, _('Back up / restore the whole daede config (kernels excluded)')),
E('span', {}, ''),
E('div', { 'class': 'dd-backup-buttons' }, [exportBtn, importBtn, resetBtn])
]),
fileInput
]),
+78
View File
@@ -1025,6 +1025,12 @@ msgstr "导出"
msgid "Import"
msgstr "导入"
msgid "Restore Defaults"
msgstr "恢复默认"
msgid "Backups contain account and subscription credentials. Store them securely."
msgstr "备份中包含账号和订阅凭据,请妥善保管。"
msgid "Back up / restore the whole daede config (kernels excluded)"
msgstr "备份 / 还原整个 daede 配置(不含内核)"
@@ -1086,3 +1092,75 @@ msgid ""
"Uplink. auto = detect by default route; on legacy swconfig or multi-WAN set "
"it, e.g. eth0.2."
msgstr "上行 WAN 接口。auto 按默认路由自动检测;老式 swconfig 交换机或多 WAN 时手动指定,例如 eth0.2。"
msgid "Export Backup"
msgstr "导出备份"
msgid "Import Backup"
msgstr "导入还原"
msgid "Restore All Defaults"
msgstr "恢复全部默认配置"
msgid "Working…"
msgstr "正在处理…"
msgid "Apply or discard pending daede changes first."
msgstr "请先应用或放弃尚未保存的 daede 修改。"
msgid "Configuration operation failed."
msgstr "配置操作失败。"
msgid "Configuration operation failed. See the log below."
msgstr "配置操作失败,请查看下方日志。"
msgid "Operation is still running. Check the log before retrying."
msgstr "操作仍在进行,请检查日志后再重试。"
msgid "Configuration restored"
msgstr "配置已恢复"
msgid "Reload Page"
msgstr "刷新页面"
msgid "Export briefly stops running dae/daed services to back up the database safely, then resumes them. Continue?"
msgstr "导出时会短暂停止正在运行的 dae/daed,以保证数据库备份完整,完成后恢复运行。是否继续?"
msgid "Backup exceeds the 180 KiB limit. Use System Backup for larger files."
msgstr "备份文件超过 180 KiB 限制,请使用系统备份功能处理较大的文件。"
msgid "Import replaces daede settings, nodes, subscriptions and dashboard data. Services stop first; the saved active backend starts only if enabled in the backup. Continue?"
msgstr "导入将覆盖 daede 设置、节点、订阅和面板数据。服务会先停止,仅在备份中为启用状态时启动对应后端。是否继续?"
msgid "Unable to read backup file."
msgstr "无法读取备份文件。"
msgid "Backup restored. The active backend follows the saved enabled setting."
msgstr "备份已还原,当前后端按备份中的启用状态运行。"
msgid "Restore ALL daede defaults? This clears dae/daed settings, nodes, subscriptions and daed dashboard accounts/data. Export a backup first if needed. Both backends will remain disabled. Installed programs and GeoData are kept."
msgstr "确定恢复 daede 全部默认配置?此操作会清空 dae/daed 设置、节点、订阅以及 daed 面板账号和数据。需要保留时请先导出备份。完成后两个后端均保持关闭,已安装程序和 GeoData 数据文件保留。"
msgid "All defaults restored. Both backends are disabled. Configure your nodes and routing before starting manually."
msgstr "已恢复全部默认配置,两个后端均已关闭。请重新配置节点和路由后手动开启。"
msgid "Back up settings, nodes, subscriptions and dashboard data. Backups contain credentials; store them securely. Kernels and GeoData are excluded."
msgstr "备份包括设置、节点、订阅和面板数据,不包括内核和 GeoData。备份中含有账号和订阅凭据,请妥善保管。"
msgid "Auto-scroll: On"
msgstr "自动滚动:开启"
msgid "Auto-scroll: Off"
msgstr "自动滚动:关闭"
msgid "Resume"
msgstr "继续"
msgid "Paused"
msgstr "已暂停"
msgid "Confirm configuration operation"
msgstr "确认配置操作"
msgid "Continue"
msgstr "继续"
+78
View File
@@ -1025,6 +1025,12 @@ msgstr "导出"
msgid "Import"
msgstr "导入"
msgid "Restore Defaults"
msgstr "恢复默认"
msgid "Backups contain account and subscription credentials. Store them securely."
msgstr "备份中包含账号和订阅凭据,请妥善保管。"
msgid "Back up / restore the whole daede config (kernels excluded)"
msgstr "备份 / 还原整个 daede 配置(不含内核)"
@@ -1086,3 +1092,75 @@ msgid ""
"Uplink. auto = detect by default route; on legacy swconfig or multi-WAN set "
"it, e.g. eth0.2."
msgstr "上行 WAN 接口。auto 按默认路由自动检测;老式 swconfig 交换机或多 WAN 时手动指定,例如 eth0.2。"
msgid "Export Backup"
msgstr "导出备份"
msgid "Import Backup"
msgstr "导入还原"
msgid "Restore All Defaults"
msgstr "恢复全部默认配置"
msgid "Working…"
msgstr "正在处理…"
msgid "Apply or discard pending daede changes first."
msgstr "请先应用或放弃尚未保存的 daede 修改。"
msgid "Configuration operation failed."
msgstr "配置操作失败。"
msgid "Configuration operation failed. See the log below."
msgstr "配置操作失败,请查看下方日志。"
msgid "Operation is still running. Check the log before retrying."
msgstr "操作仍在进行,请检查日志后再重试。"
msgid "Configuration restored"
msgstr "配置已恢复"
msgid "Reload Page"
msgstr "刷新页面"
msgid "Export briefly stops running dae/daed services to back up the database safely, then resumes them. Continue?"
msgstr "导出时会短暂停止正在运行的 dae/daed,以保证数据库备份完整,完成后恢复运行。是否继续?"
msgid "Backup exceeds the 180 KiB limit. Use System Backup for larger files."
msgstr "备份文件超过 180 KiB 限制,请使用系统备份功能处理较大的文件。"
msgid "Import replaces daede settings, nodes, subscriptions and dashboard data. Services stop first; the saved active backend starts only if enabled in the backup. Continue?"
msgstr "导入将覆盖 daede 设置、节点、订阅和面板数据。服务会先停止,仅在备份中为启用状态时启动对应后端。是否继续?"
msgid "Unable to read backup file."
msgstr "无法读取备份文件。"
msgid "Backup restored. The active backend follows the saved enabled setting."
msgstr "备份已还原,当前后端按备份中的启用状态运行。"
msgid "Restore ALL daede defaults? This clears dae/daed settings, nodes, subscriptions and daed dashboard accounts/data. Export a backup first if needed. Both backends will remain disabled. Installed programs and GeoData are kept."
msgstr "确定恢复 daede 全部默认配置?此操作会清空 dae/daed 设置、节点、订阅以及 daed 面板账号和数据。需要保留时请先导出备份。完成后两个后端均保持关闭,已安装程序和 GeoData 数据文件保留。"
msgid "All defaults restored. Both backends are disabled. Configure your nodes and routing before starting manually."
msgstr "已恢复全部默认配置,两个后端均已关闭。请重新配置节点和路由后手动开启。"
msgid "Back up settings, nodes, subscriptions and dashboard data. Backups contain credentials; store them securely. Kernels and GeoData are excluded."
msgstr "备份包括设置、节点、订阅和面板数据,不包括内核和 GeoData。备份中含有账号和订阅凭据,请妥善保管。"
msgid "Auto-scroll: On"
msgstr "自动滚动:开启"
msgid "Auto-scroll: Off"
msgstr "自动滚动:关闭"
msgid "Resume"
msgstr "继续"
msgid "Paused"
msgstr "已暂停"
msgid "Confirm configuration operation"
msgstr "确认配置操作"
msgid "Continue"
msgstr "继续"
@@ -79,27 +79,7 @@ fi
# Idempotent: only add what's missing. Lives in the `dae` UCI package,
# consumed by gen-dae-config.sh.
if [ -f /etc/config/dae ]; then
if ! uci -q get dae.config.lan_interface >/dev/null 2>&1; then
uci -q set dae.config.lan_interface='br-lan'
fi
if ! uci -q show dae | grep -q "=group$"; then
g="$(uci add dae group)"
uci -q set "dae.$g.name=proxy"
uci -q set "dae.$g.policy=min_moving_avg"
fi
if ! uci -q get dae.routing >/dev/null 2>&1; then
uci -q set dae.routing=routing
uci -q set dae.routing.private_direct=1
uci -q set dae.routing.cn_direct=1
uci -q set dae.routing.block_ads=0
uci -q set dae.routing.fallback=proxy
fi
if ! uci -q get dae.dns >/dev/null 2>&1; then
uci -q set dae.dns=dns
uci -q set dae.dns.cn_upstream='udp://dns.alidns.com:53'
uci -q set dae.dns.fallback_upstream='tcp+udp://dns.google:53'
uci -q set dae.dns.response_ttl='0'
fi
/usr/share/luci-app-daede/config-defaults.sh
# Migrate legacy group filters to the unified source / name_filter fields,
# then drop the old keys so the form shows real, editable values (the old
# fields used to re-populate the new ones and looked un-deletable).
+237 -58
View File
@@ -1,71 +1,250 @@
#!/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.
# Whole-plugin backup/import/reset. Only fixed configuration paths are managed.
set -eu
umask 077
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"
UPLOAD="${2:-/tmp/daede-import.b64}"
LOG=/tmp/luci-app-daede.backup.log
LOCK=/tmp/luci-app-daede.config.lock
SHARE=/usr/share/luci-app-daede
MAX_TAR=184320 # Base64 must fit inside the ubus reply (~240 KiB).
MAX_RAW=16777216
FILES="etc/config/dae etc/config/daed etc/config/daede etc/dae/config.dae etc/daed/wing.db etc/daed/wing.db-wal etc/daed/wing.db-shm"
WORK=""
SNAPSHOT=0
MUTATING=0
KEEP=0
RESTART=""
fail() { echo "$*" >&2; exit 1; }
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"; }
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"); if(!o || o.write(r)!=length(r))exit(1); o.close();' -- "$1" "$2"; }
# Do not merge pending CLI UCI deltas into snapshots/defaults.
config() { /sbin/uci -c /etc/config -t "$WORK/uci" "$@"; }
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
paths() {
printf '%s\n' $FILES
for sub in "$1"/etc/dae/subscriptions/*.sub; do
[ -e "$sub" ] || [ -L "$sub" ] || continue
name="${sub##*/}"
case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'unexpected subscription filename' ;; esac
printf 'etc/dae/subscriptions/%s\n' "$name"
done
}
check_paths() {
for dir in /etc/config /etc/dae /etc/daed /etc/dae/subscriptions; do
[ ! -L "$dir" ] || fail "refusing symlink: $dir"
done
paths / > "$WORK/paths"
while IFS= read -r p; do
[ ! -L "/$p" ] || fail "refusing symlink: /$p"
[ ! -e "/$p" ] || [ -f "/$p" ] || fail "not a regular file: /$p"
done < "$WORK/paths"
}
snapshot() {
mkdir -p "$WORK/before"
while IFS= read -r p; do
[ -f "/$p" ] || continue
mkdir -p "$WORK/before/${p%/*}"
cp -p "/$p" "$WORK/before/$p"
done < "$WORK/paths"
SNAPSHOT=1
}
stop_backends() {
for svc in dae daed; do
[ -x "/etc/init.d/$svc" ] || continue
if /etc/init.d/"$svc" running >/dev/null 2>&1; then RESTART="$RESTART $svc"; fi
/etc/init.d/"$svc" stop >/dev/null 2>&1 || fail "failed to stop $svc; configuration unchanged"
done
# procd termination can be asynchronous. Never copy a live SQLite database.
for attempt in 1 2 3 4 5; do
if ! pidof dae daed daed-guard >/dev/null 2>&1; then return; fi
sleep 1
done
fail 'backend still running; configuration unchanged'
}
disable_backends() {
for svc in dae daed; do
if [ -f "/etc/config/$svc" ]; then
config -q set "$svc.config=$svc" || return 1
config -q set "$svc.config.enabled=0" || return 1
config -q commit "$svc" || return 1
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
if [ -x "/etc/init.d/$svc" ]; then
/etc/init.d/"$svc" disable >/dev/null 2>&1 || return 1
/etc/init.d/"$svc" stop >/dev/null 2>&1 || return 1
fi
done
}
restore_snapshot() {
# Remove files created by the failed operation as well as original files.
paths / > "$WORK/current" || return 1
while IFS= read -r p; do rm -f "/$p" || return 1; done < "$WORK/current"
while IFS= read -r p; do
[ -f "$WORK/before/$p" ] || continue
mkdir -p "/${p%/*}" || return 1
cp -p "$WORK/before/$p" "/$p" || return 1
done < "$WORK/paths"
}
sync_cron() {
geo=disable; sub=disable
[ "$(config -q get daede.config.geo_auto || :)" != 1 ] || geo=enable
[ "$(config -q get daed.config.subscribe_auto_update || :)" != 1 ] || sub=enable
"$SHARE/geo-cron.sh" "$geo" >/dev/null 2>&1
"$SHARE/daed-sub-cron.sh" "$sub" >/dev/null 2>&1
}
finish() {
rc=$?
trap - EXIT HUP INT TERM
if [ "$rc" -ne 0 ] && [ "$MUTATING" = 1 ]; then
disable_backends || true
if [ "$SNAPSHOT" = 1 ] && ! restore_snapshot; then
KEEP=1
echo "rollback failed; root-only recovery files retained at $WORK" >&2
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
echo 'previous configuration restored; backends remain disabled' >&2
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
disable_backends || { KEEP=1; echo 'failed to disable backend; check service state' >&2; }
sync_cron || true
fi
if [ "$ACTION" = export ]; then
for svc in $RESTART; do
/etc/init.d/"$svc" start >/dev/null 2>&1 || { rc=1; echo "failed to resume $svc" >&2; }
done
fi
rm -f "$tmp" "$IMPORT_B64"
if [ "$rc" = 0 ]; then echo "result: config restored, backend restarted"; else echo "result: import failed"; fi
if [ "$rc" = 0 ]; then echo "$(date '+%F %T') ✓ 完成"; else echo "$(date '+%F %T') ✗ 失败 (rc=$rc)"; fi
) </dev/null >/dev/null 2>&1 &
echo "started in background, see $LOG"
;;
*)
echo "usage: $0 export|import" >&2
exit 64
;;
if [ "$ACTION" != export ]; then
if [ "$rc" = 0 ]; then echo '✓ 完成'; else echo '✗ 失败'; fi
fi
[ "$KEEP" = 1 ] || rm -rf "$WORK"
rmdir "$LOCK"
exit "$rc"
}
validate_archive() {
[ "$(wc -c < "$WORK/upload.b64")" -le 245760 ] || fail 'upload too large'
b64dec "$WORK/upload.b64" "$WORK/import.gz" || fail 'invalid base64 upload'
[ "$(wc -c < "$WORK/import.gz")" -le "$MAX_TAR" ] || fail 'archive too large'
gzip -t "$WORK/import.gz" 2>/dev/null || fail 'invalid gzip archive'
gzip -dc "$WORK/import.gz" | head -c 16777217 > "$WORK/import.tar"
[ "$(wc -c < "$WORK/import.tar")" -le "$MAX_RAW" ] || fail 'unpacked backup exceeds 16 MiB'
tar -tf "$WORK/import.tar" > "$WORK/entries" 2>/dev/null || fail 'invalid tar archive'
[ -s "$WORK/entries" ] || fail 'empty archive'
# Reject links, directories, devices and duplicate entries before extraction.
tar -tvf "$WORK/import.tar" > "$WORK/types" 2>/dev/null || fail 'invalid tar headers'
if grep -qv '^-' "$WORK/types"; then fail 'only regular files are allowed'; fi
[ -z "$(sort "$WORK/entries" | uniq -d)" ] || fail 'duplicate archive entry'
while IFS= read -r p; do
case "$p" in
etc/config/dae|etc/config/daed|etc/config/daede|etc/dae/config.dae|etc/daed/wing.db|etc/daed/wing.db-wal|etc/daed/wing.db-shm) ;;
etc/dae/subscriptions/*.sub)
name="${p#etc/dae/subscriptions/}"
case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'invalid subscription path' ;; esac ;;
*) fail 'unexpected archive entry' ;;
esac
done < "$WORK/entries"
mkdir -p "$WORK/after"
tar -xf "$WORK/import.tar" -C "$WORK/after"
found=0
for svc in dae daed daede; do
[ -f "$WORK/after/etc/config/$svc" ] || continue
found=1
/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" -q export "$svc" >/dev/null || fail "invalid $svc configuration"
done
[ "$found" = 1 ] || fail 'backup has no UCI configuration'
if [ -f "$WORK/after/etc/daed/wing.db-wal" ]; then
[ -f "$WORK/after/etc/daed/wing.db" ] || fail 'database WAL has no database'
fi
ab="$(/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" -q get daede.config.active_backend || echo dae)"
case "$ab" in dae|daed) ;; *) fail 'invalid backend in backup' ;; esac
[ -x "/etc/init.d/$ab" ] || fail "backup backend $ab is not installed"
}
run_action() {
trap finish EXIT
trap 'exit 1' HUP INT TERM
check_paths
if [ "$ACTION" = import ]; then validate_archive; fi
if [ "$ACTION" = reset ]; then
mkdir -p "$WORK/after/etc/config"
for svc in dae daed daede; do
cp "$SHARE/defaults/$svc" "$WORK/after/etc/config/$svc"
done
"$SHARE/config-defaults.sh" "$WORK/after/etc/config"
if [ ! -x /etc/init.d/daed ]; then
/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" set daede.config.active_backend=dae
/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" commit daede
fi
fi
stop_backends
snapshot
if [ "$ACTION" = export ]; then
paths "$WORK/before" > "$WORK/candidates"
: > "$WORK/entries"
while IFS= read -r p; do [ ! -f "$WORK/before/$p" ] || echo "$p" >> "$WORK/entries"; done < "$WORK/candidates"
[ -s "$WORK/entries" ] || fail 'no configuration to export'
tar -czf "$WORK/export.gz" -C "$WORK/before" -T "$WORK/entries"
[ "$(wc -c < "$WORK/export.gz")" -le "$MAX_TAR" ] || fail 'config too large; use System Backup instead'
b64enc "$WORK/export.gz"
return
fi
MUTATING=1
disable_backends
# Old backups may omit an uninstalled backend. Keep its UCI file, but remove
# all managed data first so stale WAL/SHM and local subscriptions cannot leak in.
while IFS= read -r p; do
case "$p" in etc/config/*) continue ;; esac
rm -f "/$p"
done < "$WORK/paths"
paths "$WORK/after" > "$WORK/replacement"
while IFS= read -r p; do
[ -f "$WORK/after/$p" ] || continue
mkdir -p "/${p%/*}"
cp "$WORK/after/$p" "/$p"
chmod 600 "/$p"
done < "$WORK/replacement"
if [ "$ACTION" = reset ]; then
disable_backends
sync_cron
echo 'All defaults restored. Backends are disabled; configure before starting manually.'
else
# Preserve the imported active backend's enabled flag. The other backend
# stays disabled even when the archive was made on a dual-backend router.
for svc in dae daed; do
[ "$svc" = "$ab" ] || [ ! -f "/etc/config/$svc" ] || { config -q set "$svc.config.enabled=0"; config -q commit "$svc"; }
done
sync_cron
if [ "$(config -q get "$ab.config.enabled" || :)" = 1 ]; then
/etc/init.d/"$ab" enable
/etc/init.d/"$ab" start
/etc/init.d/"$ab" running >/dev/null 2>&1 || fail 'restored backend failed to start'
else
/etc/init.d/"$ab" disable
fi
echo 'Backup restored. Active backend follows the saved enabled setting.'
fi
}
case "$ACTION" in export|import|reset) ;; *) fail "usage: $0 export|import [upload]|reset" ;; esac
# An atomic lock covers the complete operation, including background execution.
mkdir "$LOCK" 2>/dev/null || fail 'another configuration operation is running'
WORK="$(mktemp -d /tmp/daede-config.XXXXXX)" || { rmdir "$LOCK"; exit 1; }
mkdir -p "$WORK/uci"
if [ "$ACTION" = import ]; then
case "$UPLOAD" in
/tmp/daede-import.b64) ;;
/tmp/daede-import.*.b64)
token="${UPLOAD#/tmp/daede-import.}"; token="${token%.b64}"
case "$token" in ''|*[!a-f0-9]*) rm -rf "$WORK"; rmdir "$LOCK"; fail 'invalid upload path' ;; esac ;;
*) rm -rf "$WORK"; rmdir "$LOCK"; fail 'invalid upload path' ;;
esac
if [ ! -f "$UPLOAD" ] || [ -L "$UPLOAD" ]; then rm -rf "$WORK"; rmdir "$LOCK"; fail 'upload missing or unsafe'; fi
mv "$UPLOAD" "$WORK/upload.b64" || { rm -rf "$WORK"; rmdir "$LOCK"; exit 1; }
fi
if [ "$ACTION" = export ]; then
run_action
else
# Truncate before returning, so the frontend cannot mistake an earlier job
# for this one. The child owns the lock and all cleanup from here on.
: > "$LOG"
(run_action) </dev/null >"$LOG" 2>&1 &
echo "started in background, see $LOG"
fi
@@ -0,0 +1,31 @@
#!/bin/sh
# Shared installation/reset defaults. Optional directory is an isolated UCI tree.
set -eu
if [ "$#" -gt 0 ]; then
config_dir="$1"
mkdir -p "$config_dir/.uci"
uci() { /sbin/uci -c "$config_dir" -t "$config_dir/.uci" "$@"; }
fi
if ! uci -q get dae.config.lan_interface >/dev/null 2>&1; then
uci -q set dae.config.lan_interface='br-lan'
fi
if ! uci -q show dae | grep -q "=group$"; then
g="$(uci add dae group)"
uci -q set "dae.$g.name=proxy"
uci -q set "dae.$g.policy=min_moving_avg"
fi
if ! uci -q get dae.routing >/dev/null 2>&1; then
uci -q set dae.routing=routing
uci -q set dae.routing.private_direct=1
uci -q set dae.routing.cn_direct=1
uci -q set dae.routing.block_ads=0
uci -q set dae.routing.fallback=proxy
fi
if ! uci -q get dae.dns >/dev/null 2>&1; then
uci -q set dae.dns=dns
uci -q set dae.dns.cn_upstream='udp://dns.alidns.com:53'
uci -q set dae.dns.fallback_upstream='tcp+udp://dns.google:53'
uci -q set dae.dns.response_ttl='0'
fi
uci -q commit dae
@@ -30,8 +30,7 @@
"/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/config-backup.sh export": [ "exec" ]
"/usr/share/luci-app-daede/proxy-check.sh": [ "exec" ]
},
"ubus": {
"service": [ "list" ]
@@ -83,7 +82,11 @@
"/usr/share/luci-app-daede/gen-dae-config.sh import": [ "exec" ],
"/usr/share/luci-app-daede/daed-sub-update.sh": [ "exec" ],
"/usr/share/luci-app-daede/daed-sub-cron.sh enable": [ "exec" ],
"/usr/share/luci-app-daede/daed-sub-cron.sh disable": [ "exec" ]
"/usr/share/luci-app-daede/daed-sub-cron.sh disable": [ "exec" ],
"/usr/share/luci-app-daede/config-backup.sh export": [ "exec" ],
"/usr/share/luci-app-daede/config-backup.sh reset": [ "exec" ],
"/usr/share/luci-app-daede/config-backup.sh import /tmp/daede-import.*.b64": [ "exec" ],
"/tmp/daede-import.*.b64": [ "write" ]
},
"uci": [ "dae", "daed", "daede" ]
}
@@ -3,8 +3,8 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI app for FileBrowser
LUCI_DEPENDS:=+filebrowser
LUCI_TITLE:=LuCI app for FileBrowser Quantum
LUCI_DEPENDS:=+filebrowser-q
include $(TOPDIR)/feeds/luci/luci.mk
@@ -28,8 +28,8 @@ function renderStatus(isRunning, port) {
return view.extend({
load: async function () {
const promises = await Promise.all([
L.resolveDefault(fs.stat('/var/run/filebrowser.pid'), null),
uci.load('filebrowser')
L.resolveDefault(fs.stat('/var/run/filebrowser-q.pid'), null),
uci.load('filebrowser-q')
]);
const data = {
isRunning: promises[0],
@@ -42,15 +42,15 @@ return view.extend({
let m, s, o;
let webport = (uci.get(data.conf, 'config', 'listen_port') || '8989');
m = new form.Map('filebrowser', _('FileBrowser'),
_('FileBrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files..') + '<br />'+
m = new form.Map('filebrowser-q', _('FileBrowser Quantum'),
_('The best free self-hosted web-based file manager.') + '<br />'+
_('Default login username is %s and password is %s.').format('<code>admin</code>', '<code>admin</code>'));
s = m.section(form.TypedSection);
s.anonymous = true;
s.render = function() {
poll.add(function() {
return fs.stat('/var/run/filebrowser.pid').then(function(stat) {
return fs.stat('/var/run/filebrowser-q.pid').then(function(stat) {
let view = document.getElementById('service_status');
if (view) {
view.innerHTML = renderStatus(stat, webport);
@@ -76,7 +76,7 @@ return view.extend({
o = s.option(form.Value, 'listen_port', _('Listen port'));
o.datatype = 'port';
o.default = '8989';
o.default = '8787';
o.rmempty = false;
o = s.option(form.Value, 'root_path', _('Root directory'));
@@ -0,0 +1,33 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh-Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Collecting data..."
msgstr "正在收集数据中..."
msgid "Default login username is %s and password is %s."
msgstr "默认登录用户名为 %s,密码为 %s。"
msgid "Enable"
msgstr "启用"
msgid "Listen port"
msgstr "监听端口"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "Open Web Interface"
msgstr "打开 Web 界面"
msgid "RUNNING"
msgstr "运行中"
msgid "Root directory"
msgstr "根目录"
@@ -0,0 +1,14 @@
{
"admin/services/filebrowser-q": {
"title": "FileBrowser Quantum",
"action": {
"order": 30,
"type": "view",
"path": "filebrowser-q"
},
"depends": {
"acl": [ "luci-app-filebrowser-q" ],
"uci": { "filebrowser-q": true }
}
}
}
@@ -0,0 +1,14 @@
{
"luci-app-filebrowser-q": {
"description": "Grant UCI access for luci-app-filebrowser-q",
"read": {
"ubus": {
"service": [ "list" ]
},
"uci": [ "filebrowser-q" ]
},
"write": {
"uci": [ "filebrowser-q" ]
}
}
}
@@ -0,0 +1,4 @@
{
"config": "filebrowser-q",
"init": "filebrowser-q"
}
@@ -1,55 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:63
msgid "Collecting data..."
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:50
msgid "Default login username is %s and password is %s."
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:82
msgid "Disable Command Runner feature"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:69
msgid "Enable"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:48
#: applications/luci-app-filebrowser-go/root/usr/share/luci/menu.d/luci-app-filebrowser.json:3
msgid "FileBrowser"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:49
msgid ""
"FileBrowser provides a file managing interface within a specified directory "
"and it can be used to upload, delete, preview, rename and edit your files.."
msgstr ""
#: applications/luci-app-filebrowser-go/root/usr/share/rpcd/acl.d/luci-app-filebrowser.json:3
msgid "Grant UCI access for luci-app-filebrowser"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:73
msgid "Listen port"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
msgid "NOT RUNNING"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:30
msgid "Open Web Interface"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
msgid "RUNNING"
msgstr ""
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:78
msgid "Root directory"
msgstr ""
-1
View File
@@ -1 +0,0 @@
zh_Hans
@@ -1,64 +0,0 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh-Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:63
msgid "Collecting data..."
msgstr "正在收集数据中..."
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:50
msgid "Default login username is %s and password is %s."
msgstr "默认登录用户名为 %s,密码为 %s。"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:82
msgid "Disable Command Runner feature"
msgstr "禁用命令执行功能"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:69
msgid "Enable"
msgstr "启用"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:48
#: applications/luci-app-filebrowser-go/root/usr/share/luci/menu.d/luci-app-filebrowser.json:3
msgid "FileBrowser"
msgstr "FileBrowser"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:49
msgid ""
"FileBrowser provides a file managing interface within a specified directory "
"and it can be used to upload, delete, preview, rename and edit your files.."
msgstr ""
"FileBrowser 提供指定目录下的文件管理界面,可用于上传、删除、预览、重命名和编"
"辑文件。"
#: applications/luci-app-filebrowser-go/root/usr/share/rpcd/acl.d/luci-app-filebrowser.json:3
msgid "Grant UCI access for luci-app-filebrowser"
msgstr "授予 luci-app-filebrowser 访问 UCI 配置的权限"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:73
msgid "Listen port"
msgstr "监听端口"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
msgid "NOT RUNNING"
msgstr "未运行"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:30
msgid "Open Web Interface"
msgstr "打开 Web 界面"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
msgid "RUNNING"
msgstr "运行中"
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:78
msgid "Root directory"
msgstr "根目录"
@@ -1,14 +0,0 @@
{
"admin/services/filebrowser": {
"title": "FileBrowser",
"action": {
"order": 30,
"type": "view",
"path": "filebrowser"
},
"depends": {
"acl": [ "luci-app-filebrowser" ],
"uci": { "filebrowser": true }
}
}
}
@@ -1,14 +0,0 @@
{
"luci-app-filebrowser": {
"description": "Grant UCI access for luci-app-filebrowser",
"read": {
"ubus": {
"service": [ "list" ]
},
"uci": [ "filebrowser" ]
},
"write": {
"uci": [ "filebrowser" ]
}
}
}
@@ -1,4 +0,0 @@
{
"config": "filebrowser",
"init": "filebrowser"
}
@@ -272,9 +272,17 @@ o = s:option(ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("udp", "UDP")
if m.is_js_luci then
if current_node.type == "sing-box" then
o:value("tls", "TLS(DoT)")
o:value("quic", "QUIC(DoQ)")
o:value("http3", "HTTP3(DoH3)")
end
else
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
end
o:depends("_show_dns_option", "1")
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
@@ -382,9 +390,8 @@ for k, v in pairs(nodes_table) do
end
end
--m:appendTemplate("/acl/options", {section = arg[1]})
-- Shunt Start
--[[
-- Shunt
if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
@@ -396,5 +403,6 @@ if current_node.protocol == "_shunt" then
end
m:appendTemplate("/acl/shunt", { shunt_list = api.jsonc.stringify(shunt_list), section = s.section })
]]--
return api.return_map(m)
@@ -201,9 +201,17 @@ o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS P
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("udp", "UDP")
if m.is_js_luci then
if current_node.type == "sing-box" then
o:value("tls", "TLS(DoT)")
o:value("quic", "QUIC(DoQ)")
o:value("http3", "HTTP3(DoH3)")
end
else
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
end
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
@@ -259,11 +267,10 @@ o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
if current_node.type == "sing-box" then
o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
o.datatype = "min(1)"
o.default = "30"
end
o:depends("_is_singbox", "1")
o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
o.rows = 5
@@ -1,56 +0,0 @@
<%
local map = self.map
local api = map.api
local config = map.config
local section = self.section
-%>
<script type="text/javascript">
//<![CDATA[
function setOption(option, val) {
const dom = document.getElementById('cbid.<%=config%>.<%=section%>.' + option);
if (dom) {
dom.value = val;
}
const combobox_dom = document.getElementById('cbi.combobox.cbid.<%=config%>.<%=section%>.' + option);
if (combobox_dom) {
combobox_dom.value = val;
//combobox_dom.selectedIndex = val;
//combobox_dom.dispatchEvent(new Event("change", { bubbles: true }));
}
}
document.addEventListener("DOMContentLoaded", function () {
function dom_event() {
waitForElementId('cbi.combobox.cbid.<%=config%>.<%=section%>.tcp_no_redir_ports', function(el) {
const o_val = el.value;
el.addEventListener("change", () => {
const udp_no_redir_ports = getOption("<%=config%>", "<%=section%>", "udp_no_redir_ports");
if (el.value == "1:65535" && udp_no_redir_ports && udp_no_redir_ports.value == "1:65535") {
setOption("tcp_no_redir_ports", "");
setOption("udp_no_redir_ports", "");
setOption("mode", 0);
cbi_d_update();
}
});
});
waitForElementId('cbi.combobox.cbid.<%=config%>.<%=section%>.udp_no_redir_ports', function(el) {
const o_val = el.value;
el.addEventListener("change", () => {
const tcp_no_redir_ports = getOption("<%=config%>", "<%=section%>", "tcp_no_redir_ports");
if (el.value == "1:65535" && tcp_no_redir_ports && tcp_no_redir_ports.value == "1:65535") {
setOption("tcp_no_redir_ports", "");
setOption("udp_no_redir_ports", "");
setOption("mode", 0);
cbi_d_update();
}
});
});
}
const ori_cbi_d_update = cbi_d_update;
cbi_d_update = function() {
ori_cbi_d_update();
dom_event();
};
dom_event();
});
//]]>
</script>
@@ -1507,15 +1507,17 @@ table td, .table .td {
//Node list option saving logic
document.addEventListener("DOMContentLoaded", function () {
function onChange(option, value) {
function onChange(option, value, refresh) {
ajax.abortAll();
XHR.get('<%=api.url("save_node_list_opt")%>', {
option: option,
value: value
}, function(x) {
if (x && x.status == 200) {
if (refresh) {
document.getElementById("node_list").innerHTML = "";
loadNodeList();
}
} else {
alert("<%:Error%>");
}
@@ -1527,7 +1529,7 @@ table td, .table .td {
el.addEventListener("change", () => {
el.blur();
show_node_info = el.checked ? "1" : "0";
onChange("show_node_info", show_node_info);
onChange("show_node_info", show_node_info, true);
});
});
@@ -1535,32 +1537,47 @@ table td, .table .td {
el.addEventListener("change", () => {
el.blur();
auto_detection_time = el.value;
onChange("auto_detection_time", auto_detection_time);
onChange("auto_detection_time", auto_detection_time, true);
});
});
<% if api.is_js_luci() then -%>
waitForElement('div[id*="cbid.<%=appname%>"][id*="url_test_url"]', function(el) {
el.addEventListener("cbi-dropdown-change", () => {
if (el.value && (!el.new_val || el.new_val != el.value)) {
onChange("url_test_url", el.value, false);
el.new_val = el.value;
}
});
});
<% else -%>
waitForElement('select[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
el.addEventListener("change", () => {
if (el.value) {
onChange("url_test_url", el.value);
if (el.value && (!el.new_val || el.new_val != el.value)) {
onChange("url_test_url", el.value, false);
el.new_val = el.value;
}
});
});
waitForElement('input[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
el.addEventListener("change", () => {
if (el.value) {
onChange("url_test_url", el.value);
if (el.value && (!el.new_val || el.new_val != el.value)) {
onChange("url_test_url", el.value, false);
el.new_val = el.value;
}
});
});
<% end -%>
}
<% if not api.is_js_luci() then -%>
const ori_cbi_d_update = cbi_d_update;
cbi_d_update = function() {
ori_cbi_d_update();
dom_event();
};
<% end -%>
dom_event();
const links = document.querySelectorAll('a');
@@ -3,7 +3,7 @@
. /usr/share/passwall2/utils.sh
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
LEASE_FILE="/tmp/dhcp.leases"
LEASE_FILE=$(config_t_get dnsmasq leasefile "/tmp/dhcp.leases")
HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
TMP_FILE="/tmp/dhcp-hosts.tmp"
@@ -15,7 +15,7 @@ fi
reload_dnsmasq_pids() {
local pidfile pid
find $TMP_PATH/acl -type f -name 'dnsmasq.pid' 2>/dev/null | while read pidfile; do
find $TMP_PATH/acl -type f -name '*_dnsmasq.pid' 2>/dev/null | while read pidfile; do
if [ -s "$pidfile" ]; then
read pid < "$pidfile"
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196
PKG_RELEASE:=7
PKG_RELEASE:=8
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
Submodule luci-app-syncthing added at b7a197a93c
-22
View File
@@ -1,22 +0,0 @@
# Copyright (C) 2020 Gyj1109
# 适配 OpenWrt 25.12 修改版
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-syncthing
PKG_VERSION:=1.0
PKG_RELEASE:=3
LUCI_TITLE:=LuCI support for Syncthing
LUCI_PKGARCH:=all
# 适配 25.12:确保安装了 syncthing 后,LuCI 界面能正确拉起
LUCI_DEPENDS:=+syncthing
include $(TOPDIR)/feeds/luci/luci.mk
# 明确定义配置文件的归属,这对 25.12 的 apk 管理器非常重要
define Package/$(PKG_NAME)/conffiles
/etc/config/syncthing
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
-2
View File
@@ -1,2 +0,0 @@
# luci-app-syncthing
在Potat0000源码基础上进行了汉化优化,官方openwrt23.05.3编译后可使用
@@ -1,17 +0,0 @@
module("luci.controller.syncthing", package.seeall)
function index()
if not nixio.fs.access("/etc/config/syncthing") then
return
end
entry({"admin", "services", "syncthing"}, cbi("syncthing"), _("文件同步"), 10).dependent = true
entry({"admin", "services", "syncthing", "status"}, call("act_status")).leaf = true
end
function act_status()
local e = {}
e.running = luci.sys.call("pgrep syncthing >/dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
@@ -1,48 +0,0 @@
require("nixio.fs")
m = Map("syncthing", translate("Syncthing同步工具"))
m:section(SimpleSection).template = "syncthing/syncthing_status"
s = m:section(TypedSection, "syncthing")
s.anonymous = true
o = s:option(Flag, "enabled", translate("启用"))
o.default = 0
o.rmempty = false
gui_address = s:option(Value, "gui_address", translate("GUI访问地址"))
gui_address.description = translate("使用0.0.0.0以监控所有访问。")
gui_address.default = "http://0.0.0.0:8384"
gui_address.placeholder = "http://0.0.0.0:8384"
gui_address.rmempty = false
home = s:option(Value, "home", translate("配置文件目录"))
home.description = translate("只有保存在/etc/syncthing中的配置会自动备份!")
home.default = "/etc/syncthing"
home.placeholder = "/etc/syncthing"
home.rmempty = false
user = s:option(ListValue, "user", translate("用户"))
user.description = translate("默认是syncthing,但这可能会导致权限被拒绝。Syncthing官方不建议以root身份运行。")
user:value("", translate("syncthing"))
for u in luci.util.execi("cat /etc/passwd | cut -d ':' -f1") do
user:value(u)
end
macprocs = s:option(Value, "macprocs", translate("线程限制"))
macprocs.description = translate("0表示匹配CPU数量(默认),>0表示显式指定并发数。")
macprocs.default = "0"
macprocs.placeholder = "0"
macprocs.datatype = "range(0,32)"
macprocs.rmempty = false
nice = s:option(Value, "nice", translate("优先级"))
nice.description = translate("显式指定优先级值。0是最高,19是最低。(暂时不允许设置负值)")
nice.default = "19"
nice.placeholder = "19"
nice.datatype = "range(0,19)"
nice.rmempty = false
return m
@@ -1,27 +0,0 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(1, '<%=url([[admin]], [[services]], [[syncthing]], [[status]])%>', null,
function (x, data) {
var tb = document.getElementById('syncthing_status');
if (data && tb) {
if (data.running) {
var links = '<em><b><font color="green">Syncthing <%:运行中%></font></b></em><input class="btn cbi-button mar-10" type="button" value="<%:打开Syncthing页面%>" onclick="openwebui();" />';
tb.innerHTML = links;
} else {
tb.innerHTML = '<em><b><font color="red">Syncthing <%:未运行%></font></b></em>';
}
}
}
);
function openwebui(){
var url = window.location.host+":<%=luci.sys.exec("uci -q get syncthing.syncthing.gui_address"):match(":[0-9]+"):gsub(":", "")%>";
window.open('http://'+url,'target','');
}
//]]>
</script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<p id="syncthing_status">
<em><%:正在收集数据...%></em>
</p>
</fieldset>
@@ -1,12 +0,0 @@
#!/bin/sh
touch /etc/config/syncthing
uci -q batch <<-EOF >/dev/null
delete ucitrack.@syncthing[-1]
add ucitrack syncthing
set ucitrack.@syncthing[-1].exec='/etc/init.d/syncthing stop && /etc/init.d/syncthing start'
commit ucitrack
EOF
# remove LuCI cache
rm -f /tmp/luci*
exit 0
@@ -1,11 +0,0 @@
{
"luci-app-syncthing": {
"description": "Grant UCI access for luci-app-syncthing",
"read": {
"uci": [ "syncthing" ]
},
"write": {
"uci": [ "syncthing" ]
}
}
}
@@ -1767,6 +1767,9 @@ static int __init fast_classifier_init(void)
printk(KERN_ALERT "fast-classifier: starting up\n");
DEBUG_INFO("SFE CM init\n");
/* Initialize state before registering callbacks that can use it. */
spin_lock_init(&sc->lock);
hash_init(fc_conn_ht);
/*
@@ -1882,8 +1885,6 @@ static int __init fast_classifier_init(void)
printk(KERN_ALERT "fast-classifier: registered\n");
spin_lock_init(&sc->lock);
/*
* Hook the receive path in the network stack.
*/
@@ -2005,4 +2006,3 @@ module_exit(fast_classifier_exit)
MODULE_DESCRIPTION("Shortcut Forwarding Engine - Connection Manager");
MODULE_LICENSE("Dual BSD/GPL");