mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-15 12:42:55 +08:00
update 2026-09-26 11:07:27
This commit is contained in:
1 parent
7b88b273d0
commit
97ae9f68da
4898 files changed
-1712274
No files matched your search
@@ -1,268 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Whole-plugin backup/import/reset. Only fixed configuration paths are managed.
|
||||
set -eu
|
||||
umask 077
|
||||
|
||||
ACTION="${1:-}"
|
||||
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"); 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" "$@"; }
|
||||
|
||||
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
|
||||
for sub in "$1"/etc/daed/daede-sub-*; do
|
||||
[ -e "$sub" ] || [ -L "$sub" ] || continue
|
||||
name="${sub##*/daede-sub-}"
|
||||
case "$name" in ''|*[!A-Za-z0-9]*) fail 'unexpected daed subscription filename' ;; esac
|
||||
printf 'etc/daed/daede-sub-%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"
|
||||
}
|
||||
cleanup_stages() {
|
||||
for stage in /etc/daed/.daede-sub-stage-*; do
|
||||
[ -e "$stage" ] || [ -L "$stage" ] || continue
|
||||
[ ! -L "$stage" ] || fail "refusing symlink: $stage"
|
||||
[ -f "$stage" ] || fail "not a regular file: $stage"
|
||||
rm -f "$stage" || fail "failed to remove stale snapshot stage: $stage"
|
||||
done
|
||||
}
|
||||
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
|
||||
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
|
||||
echo 'previous configuration restored; backends remain disabled' >&2
|
||||
fi
|
||||
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
|
||||
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 ;;
|
||||
etc/daed/daede-sub-*)
|
||||
name="${p#etc/daed/daede-sub-}"
|
||||
case "$name" in ''|*[!A-Za-z0-9]*) fail 'invalid daed 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
|
||||
[ "$ACTION" = reset ] && cleanup_stages
|
||||
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
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Manage daed subscription auto-update cron entry.
|
||||
|
||||
ACTION="$1"
|
||||
CRONTAB="/etc/crontabs/root"
|
||||
TAG="# luci-app-daede daed-sub-update"
|
||||
SCRIPT="/usr/share/luci-app-daede/daed-sub-update.sh"
|
||||
|
||||
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" ] && [ "$(uci -q get daed.config.subscribe_auto_update)" = "1" ]; then
|
||||
hour="$(uci -q get daed.config.subscribe_update_hour)"
|
||||
case "$hour" in
|
||||
''|*[!0-9]*) hour=4 ;;
|
||||
esac
|
||||
[ "$hour" -ge 0 ] 2>/dev/null && [ "$hour" -le 23 ] 2>/dev/null || hour=4
|
||||
|
||||
case "$(uci -q get daed.config.subscribe_update_cycle)" in
|
||||
weekly) sched="17 $hour * * 0" ;;
|
||||
*) sched="17 $hour * * *" ;;
|
||||
esac
|
||||
|
||||
mkdir -p /etc/crontabs
|
||||
{
|
||||
echo "$TAG"
|
||||
echo "$sched $SCRIPT >/dev/null 2>&1"
|
||||
} >> "$CRONTAB"
|
||||
/etc/init.d/cron enable >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
/etc/init.d/cron restart >/dev/null 2>&1
|
||||
exit 0
|
||||
@@ -1,148 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Update all daed subscriptions through dae-wing GraphQL.
|
||||
|
||||
LOG="/tmp/luci-app-daede.daed-sub-update.log"
|
||||
LOCK="/tmp/luci-app-daede.daed-sub-update.lock"
|
||||
|
||||
log() {
|
||||
echo "$(date '+%F %T') $*" >> "$LOG"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local code="$1"; shift
|
||||
log "$*"
|
||||
echo "$*" >&2
|
||||
exit "$code"
|
||||
}
|
||||
|
||||
json_escape() {
|
||||
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
[ -n "$TMPDIR" ] && rm -rf "$TMPDIR"
|
||||
rmdir "$LOCK" 2>/dev/null
|
||||
}
|
||||
|
||||
if ! mkdir "$LOCK" 2>/dev/null; then
|
||||
fail 1 "daed subscription update already running"
|
||||
fi
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
TMPDIR="$(mktemp -d /tmp/daed-sub-update.XXXXXX)" || exit 1
|
||||
|
||||
port="$(uci -q get daed.config.listen_addr | sed -n 's/.*:\([0-9]\{1,5\}\)$/\1/p')"
|
||||
[ -n "$port" ] || port=2023
|
||||
endpoint="http://127.0.0.1:$port/graphql"
|
||||
|
||||
username="$(uci -q get daed.config.dashboard_username)"
|
||||
password="$(uci -q get daed.config.dashboard_password)"
|
||||
|
||||
if [ -z "$username" ] || [ -z "$password" ]; then
|
||||
fail 2 "missing daed dashboard username or password"
|
||||
fi
|
||||
|
||||
if ! /bin/pidof daed >/dev/null 2>&1; then
|
||||
fail 3 "daed is not running"
|
||||
fi
|
||||
|
||||
post_graphql() {
|
||||
local body="$1" outfile="$2" auth="$3"
|
||||
if [ -n "$auth" ]; then
|
||||
uclient-fetch -q -O "$outfile" \
|
||||
--header="Content-Type: application/json" \
|
||||
--header="Authorization: Bearer $auth" \
|
||||
--post-file="$body" \
|
||||
"$endpoint" 2>>"$LOG"
|
||||
else
|
||||
uclient-fetch -q -O "$outfile" \
|
||||
--header="Content-Type: application/json" \
|
||||
--post-file="$body" \
|
||||
"$endpoint" 2>>"$LOG"
|
||||
fi
|
||||
}
|
||||
|
||||
login_body="$TMPDIR/login.json"
|
||||
login_resp="$TMPDIR/login.out"
|
||||
printf '{"query":"query Login($username:String!,$password:String!){token(username:$username,password:$password)}","variables":{"username":"%s","password":"%s"}}' \
|
||||
"$(json_escape "$username")" "$(json_escape "$password")" > "$login_body"
|
||||
|
||||
log "starting daed subscription update"
|
||||
if ! post_graphql "$login_body" "$login_resp"; then
|
||||
fail 4 "login request failed"
|
||||
fi
|
||||
|
||||
token="$(sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$login_resp")"
|
||||
if [ -z "$token" ]; then
|
||||
fail 5 "login failed: $(cat "$login_resp" 2>/dev/null)"
|
||||
fi
|
||||
|
||||
list_body="$TMPDIR/list.json"
|
||||
list_resp="$TMPDIR/list.out"
|
||||
printf '{"query":"query Subscriptions{subscriptions{id tag link}}","variables":{}}' > "$list_body"
|
||||
|
||||
if ! post_graphql "$list_body" "$list_resp" "$token"; then
|
||||
fail 6 "subscription list request failed"
|
||||
fi
|
||||
if grep -q '"errors"' "$list_resp"; then
|
||||
fail 7 "subscription list failed: $(cat "$list_resp" 2>/dev/null)"
|
||||
fi
|
||||
|
||||
sed 's/"id"/\
|
||||
"id"/g' "$list_resp" | sed -n 's/.*"id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' > "$TMPDIR/ids"
|
||||
|
||||
count="$(grep -c . "$TMPDIR/ids" 2>/dev/null || echo 0)"
|
||||
if [ "${count:-0}" -eq 0 ]; then
|
||||
log "no daed subscriptions to update"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ok=0
|
||||
fail=0
|
||||
while IFS= read -r id || [ -n "$id" ]; do
|
||||
[ -n "$id" ] || continue
|
||||
update_body="$TMPDIR/update.json"
|
||||
update_resp="$TMPDIR/update.out"
|
||||
printf '{"query":"mutation Update($id:ID!){updateSubscription(id:$id){id tag status}}","variables":{"id":"%s"}}' \
|
||||
"$(json_escape "$id")" > "$update_body"
|
||||
if post_graphql "$update_body" "$update_resp" "$token" && ! grep -q '"errors"' "$update_resp"; then
|
||||
ok=$((ok + 1))
|
||||
log "updated subscription $id"
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
log "failed subscription $id: $(cat "$update_resp" 2>/dev/null)"
|
||||
fi
|
||||
done < "$TMPDIR/ids"
|
||||
|
||||
log "finished daed subscription update: ok=$ok fail=$fail total=$count"
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$count" -gt 0 ] && [ "$ok" -gt 0 ] && /bin/pidof daed >/dev/null 2>&1; then
|
||||
state_body="$TMPDIR/state.json"
|
||||
state_resp="$TMPDIR/state.out"
|
||||
printf '%s' '{"query":"query General{general{dae{running modified}}}","variables":{}}' > "$state_body"
|
||||
if ! post_graphql "$state_body" "$state_resp" "$token" || grep -q '"errors"' "$state_resp"; then
|
||||
fail 8 "failed to query daed runtime state: $(cat "$state_resp" 2>/dev/null)"
|
||||
fi
|
||||
if ! grep -q '"running"[[:space:]]*:[[:space:]]*true' "$state_resp"; then
|
||||
log "daed proxy is stopped; skipping runtime apply"
|
||||
exit 0
|
||||
fi
|
||||
if ! grep -q '"modified"[[:space:]]*:[[:space:]]*true' "$state_resp"; then
|
||||
log "daed runtime is already up to date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run_body="$TMPDIR/run.json"
|
||||
run_resp="$TMPDIR/run.out"
|
||||
printf '{"query":"mutation Run($dry:Boolean!){run(dry:$dry)}","variables":{"dry":false}}' > "$run_body"
|
||||
log "applying updated subscriptions"
|
||||
if ! post_graphql "$run_body" "$run_resp" "$token" || grep -q '"errors"' "$run_resp"; then
|
||||
fail 9 "failed to apply updated subscriptions: $(cat "$run_resp" 2>/dev/null)"
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,134 +0,0 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -eu
|
||||
|
||||
CHUNK_BYTES=16384
|
||||
ACTION="${1:-}"
|
||||
|
||||
case "$ACTION" in
|
||||
chunk)
|
||||
TOKEN="${2:-}"
|
||||
INDEX="${3:-}"
|
||||
case "$TOKEN" in *[!A-Za-z0-9]*|'') exit 2 ;; esac
|
||||
case "$INDEX" in *[!0-9]*|'') exit 2 ;; esac
|
||||
FILE="/tmp/daede-clash-yaml-result.$TOKEN"
|
||||
[ -f "$FILE" ] || exit 4
|
||||
dd if="$FILE" bs="$CHUNK_BYTES" skip="$INDEX" count=1 2>/dev/null | hexdump -ve '1/1 "%02x"'
|
||||
exit 0
|
||||
;;
|
||||
cleanup)
|
||||
TOKEN="${2:-}"
|
||||
case "$TOKEN" in *[!A-Za-z0-9]*|'') exit 2 ;; esac
|
||||
rm -f "/tmp/daede-clash-yaml-result.$TOKEN"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
URL="${1:-}"
|
||||
UA_MODE="${2:-auto}"
|
||||
OUTPUT_MODE="${3:-stdout}"
|
||||
MAX_BYTES=5242880
|
||||
FETCH_BIN="${DAEDE_FETCH_BIN:-uclient-fetch}"
|
||||
|
||||
case "$URL" in
|
||||
http://*|https://*) ;;
|
||||
*)
|
||||
echo "Subscription URL must use HTTP or HTTPS" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
TMP="$(mktemp /tmp/daede-clash-yaml.XXXXXX)"
|
||||
FIRST="$(mktemp /tmp/daede-clash-yaml-first.XXXXXX)"
|
||||
ERR="$(mktemp /tmp/daede-clash-yaml-error.XXXXXX)"
|
||||
trap 'rm -f "$TMP" "$FIRST" "$ERR"' EXIT INT TERM
|
||||
chmod 600 "$TMP"
|
||||
chmod 600 "$FIRST"
|
||||
chmod 600 "$ERR"
|
||||
|
||||
case "$UA_MODE" in
|
||||
auto|browser|ClashMeta|clash-verge/v2.4.2|ClashForWindows/0.20.39|Clash) ;;
|
||||
*)
|
||||
echo "Unsupported subscription User-Agent" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$OUTPUT_MODE" in
|
||||
stdout|handle) ;;
|
||||
*)
|
||||
echo "Unsupported output mode" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
LAST_ERROR="Failed to fetch subscription"
|
||||
|
||||
emit_result() {
|
||||
local source="$1" result token size
|
||||
if [ "$OUTPUT_MODE" = "stdout" ]; then
|
||||
cat "$source"
|
||||
return
|
||||
fi
|
||||
|
||||
# Keep each RPC response small. The browser reads this root-only temporary
|
||||
# file through the chunk action and removes it in a finally handler.
|
||||
find /tmp -maxdepth 1 -name 'daede-clash-yaml-result.*' -mmin +10 -delete 2>/dev/null || true
|
||||
result="$(mktemp /tmp/daede-clash-yaml-result.XXXXXX)"
|
||||
chmod 600 "$result"
|
||||
cp "$source" "$result"
|
||||
token="${result##*.}"
|
||||
size="$(wc -c <"$result" | tr -d ' ')"
|
||||
printf '%s\t%s\n' "$token" "$size"
|
||||
}
|
||||
|
||||
fetch_one() {
|
||||
local ua="$1" size response_error
|
||||
if [ "$ua" = "browser" ]; then
|
||||
ua="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36"
|
||||
fi
|
||||
: >"$TMP"
|
||||
: >"$ERR"
|
||||
if ! "$FETCH_BIN" -q -T 20 -U "$ua" -O "$TMP" "$URL" 2>"$ERR"; then
|
||||
response_error="$(head -c 512 "$TMP" | tr '\r\n\t' ' ')"
|
||||
LAST_ERROR="${response_error:-$(cat "$ERR")}"
|
||||
[ -n "$LAST_ERROR" ] || LAST_ERROR="Failed to fetch subscription"
|
||||
return 1
|
||||
fi
|
||||
|
||||
size="$(wc -c <"$TMP" | tr -d ' ')"
|
||||
if [ "$size" -eq 0 ]; then
|
||||
LAST_ERROR="Subscription response is empty"
|
||||
return 1
|
||||
fi
|
||||
if [ "$size" -gt "$MAX_BYTES" ]; then
|
||||
LAST_ERROR="Subscription response is too large (maximum 5 MiB)"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "$UA_MODE" = "auto" ]; then
|
||||
for ua in ClashMeta clash-verge/v2.4.2 ClashForWindows/0.20.39 Clash browser; do
|
||||
if fetch_one "$ua"; then
|
||||
[ -s "$FIRST" ] || cp "$TMP" "$FIRST"
|
||||
if grep -Eq '^[[:space:]]*proxies[[:space:]]*:' "$TMP"; then
|
||||
emit_result "$TMP"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -s "$FIRST" ]; then
|
||||
emit_result "$FIRST"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
if fetch_one "$UA_MODE"; then
|
||||
emit_result "$TMP"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$LAST_ERROR" >&2
|
||||
exit 3
|
||||
@@ -1,424 +0,0 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# gen-dae-config.sh - build /etc/dae/config.dae from the dae UCI form sections,
|
||||
# validate with `dae validate`, then overwrite + hot reload only on success.
|
||||
#
|
||||
# generate read UCI -> render config.dae -> validate -> overwrite + hot reload
|
||||
# import parse existing config.dae subscription{}/node{} -> fill UCI form
|
||||
#
|
||||
# UCI model (package dae):
|
||||
# config subscription tag/url, enabled
|
||||
# config node tag/link, enabled
|
||||
# config group name/policy, list source, list name_filter
|
||||
# (old list filter_sub/filter_node still read as fallback)
|
||||
# config routing 'routing' private_direct/cn_direct/block_ads/fallback, list custom
|
||||
# config dns 'dns' cn_upstream/fallback_upstream/response_ttl
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
CONFIG_DAE="/etc/dae/config.dae"
|
||||
TMP_GEN="/tmp/dae-gen.dae"
|
||||
DAE_BIN="/usr/bin/dae"
|
||||
DAE_INITD="/etc/init.d/dae"
|
||||
SUBS_DIR="/etc/dae/subscriptions"
|
||||
SUB_STAGE="/tmp/daede-sub.txt"
|
||||
|
||||
# dae single-quoted strings have no escaping; strip quotes/newlines defensively.
|
||||
sanitize() {
|
||||
printf '%s' "$1" | tr -d "'" | tr -d '\n\r'
|
||||
}
|
||||
|
||||
# dae subscription/node tags are bareword identifiers: only [A-Za-z0-9_] are
|
||||
# valid tokens, so a CJK/spaced tag (e.g. 白月光) makes the parser choke. Map
|
||||
# any such tag to a stable ASCII slug; pure-word tags pass through unchanged.
|
||||
# Deterministic on the input, so a group's source reference resolves to the
|
||||
# same slug as the subscription it points at. Empty stays empty (anonymous).
|
||||
dae_tag() {
|
||||
[ -n "$1" ] || return 0
|
||||
case "$1" in
|
||||
*[!A-Za-z0-9_]*) printf 's_%s' "$(printf '%s' "$1" | md5sum | cut -c1-10)" ;;
|
||||
*) printf '%s' "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ===================== generate =====================
|
||||
|
||||
SUB_BUF=""
|
||||
NODE_BUF=""
|
||||
GROUP_BUF=""
|
||||
FIRST_GROUP=""
|
||||
|
||||
emit_sub() {
|
||||
local s="$1" tag url enabled
|
||||
config_get tag "$s" tag ""
|
||||
config_get url "$s" url ""
|
||||
config_get_bool enabled "$s" enabled 1
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
url="$(sanitize "$url")"
|
||||
[ -n "$url" ] || return 0
|
||||
tag="$(dae_tag "$(sanitize "$tag")")"
|
||||
if [ -n "$tag" ]; then
|
||||
SUB_BUF="${SUB_BUF} ${tag}: '${url}'
|
||||
"
|
||||
else
|
||||
SUB_BUF="${SUB_BUF} '${url}'
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
emit_node() {
|
||||
local s="$1" tag link enabled
|
||||
config_get tag "$s" tag ""
|
||||
config_get link "$s" link ""
|
||||
config_get_bool enabled "$s" enabled 1
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
link="$(sanitize "$link")"
|
||||
[ -n "$link" ] || return 0
|
||||
tag="$(dae_tag "$(sanitize "$tag")")"
|
||||
if [ -n "$tag" ]; then
|
||||
NODE_BUF="${NODE_BUF} ${tag}: '${link}'
|
||||
"
|
||||
else
|
||||
NODE_BUF="${NODE_BUF} '${link}'
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
emit_group() {
|
||||
local s="$1" name policy
|
||||
config_get name "$s" name ""
|
||||
config_get policy "$s" policy "min_moving_avg"
|
||||
name="$(sanitize "$name")"
|
||||
[ -n "$name" ] || return 0
|
||||
# dae fatals on duplicate outbound names; keep the first, skip later dupes
|
||||
case " $GROUP_NAMES_SEEN " in *" $name "*) return 0 ;; esac
|
||||
GROUP_NAMES_SEEN="$GROUP_NAMES_SEEN $name"
|
||||
[ -n "$FIRST_GROUP" ] || FIRST_GROUP="$name"
|
||||
|
||||
GROUP_BUF="${GROUP_BUF} ${name} {
|
||||
"
|
||||
# Build the source: 'source' entries that match a subscription tag become
|
||||
# subtag(), the rest (node tags / keywords) become name(). Old filter_sub /
|
||||
# filter_node configs still work as a fallback when 'source' is unset.
|
||||
SUBS_ACC=""; NODES_ACC=""; KW_ACC=""
|
||||
# source: new field, else old filter_sub (decoupled from the name filter so
|
||||
# editing one never silently drops the other)
|
||||
config_list_foreach "$s" source _collect_source
|
||||
[ -z "$SUBS_ACC" ] && [ -z "$NODES_ACC" ] && config_list_foreach "$s" filter_sub _collect_source
|
||||
# optional name filter narrows the subscription pool only (AND); hand-picked
|
||||
# nodes are explicit choices and stay in unconditionally
|
||||
config_list_foreach "$s" name_filter _collect_kw
|
||||
[ -z "$KW_ACC" ] && config_list_foreach "$s" filter_node _collect_kw
|
||||
local andclause=""
|
||||
[ -n "$KW_ACC" ] && andclause=" && name(${KW_ACC})"
|
||||
|
||||
local emitted=0
|
||||
# multiple filter lines = OR, so subtag and name lines union the sources
|
||||
if [ -n "$SUBS_ACC" ]; then
|
||||
GROUP_BUF="${GROUP_BUF} filter: subtag(${SUBS_ACC})${andclause}
|
||||
"
|
||||
emitted=1
|
||||
fi
|
||||
if [ -n "$NODES_ACC" ]; then
|
||||
GROUP_BUF="${GROUP_BUF} filter: name(${NODES_ACC})
|
||||
"
|
||||
emitted=1
|
||||
fi
|
||||
# name filter with no source = filter the whole pool by name
|
||||
if [ "$emitted" = 0 ] && [ -n "$KW_ACC" ]; then
|
||||
GROUP_BUF="${GROUP_BUF} filter: name(${KW_ACC})
|
||||
"
|
||||
fi
|
||||
GROUP_BUF="${GROUP_BUF} policy: ${policy}
|
||||
}
|
||||
"
|
||||
SUBS_ACC=""; NODES_ACC=""; KW_ACC=""
|
||||
}
|
||||
|
||||
SUBS_ACC=""
|
||||
NODES_ACC=""
|
||||
KW_ACC=""
|
||||
# Subscription tags, space-padded for membership tests (" tag " match).
|
||||
SUB_TAGS=""
|
||||
collect_subtag() {
|
||||
local t; config_get t "$1" tag ""
|
||||
t="$(dae_tag "$(sanitize "$t")")"
|
||||
[ -n "$t" ] && SUB_TAGS="${SUB_TAGS} ${t} "
|
||||
}
|
||||
is_subtag() {
|
||||
case "$SUB_TAGS" in *" $1 "*) return 0 ;; *) return 1 ;; esac
|
||||
}
|
||||
# dae filter args are bare only for plain identifiers; anything with regex
|
||||
# chars / CJK / spaces must be wrapped as regex: '...' or the lexer chokes.
|
||||
filter_arg() {
|
||||
case "$1" in
|
||||
*[!A-Za-z0-9_]*) printf "regex: '%s'" "$1" ;;
|
||||
*) printf '%s' "$1" ;;
|
||||
esac
|
||||
}
|
||||
# name-filter args do substring matching: regex metachars -> regex:, else keyword:.
|
||||
# Intentionally stricter than filter_arg: a plain CJK keyword here stays a
|
||||
# substring `keyword:` match, whereas filter_arg wraps any non-word char as regex.
|
||||
kw_arg() {
|
||||
if printf '%s' "$1" | grep -q '[][|().*+?^$\\]'; then
|
||||
printf "regex: '%s'" "$1"
|
||||
else
|
||||
printf "keyword: '%s'" "$1"
|
||||
fi
|
||||
}
|
||||
# classify a source entry: subscription tag -> subtag bucket, else name bucket
|
||||
_collect_source() {
|
||||
local v="$(sanitize "$1")"
|
||||
[ -n "$v" ] || return 0
|
||||
# subscription refs resolve to the (ASCII) dae tag; node-name refs keep the
|
||||
# original text (matched as a quoted regex against node names)
|
||||
local dt="$(dae_tag "$v")"
|
||||
if is_subtag "$dt"; then
|
||||
SUBS_ACC="${SUBS_ACC:+$SUBS_ACC, }$(filter_arg "$dt")"
|
||||
else
|
||||
NODES_ACC="${NODES_ACC:+$NODES_ACC, }$(filter_arg "$v")"
|
||||
fi
|
||||
}
|
||||
_collect_kw() {
|
||||
local v="$(sanitize "$1")"
|
||||
[ -n "$v" ] || return 0
|
||||
KW_ACC="${KW_ACC:+$KW_ACC, }$(kw_arg "$v")"
|
||||
}
|
||||
|
||||
generate() {
|
||||
config_load dae
|
||||
|
||||
# global overridable knobs (fall back to defaults)
|
||||
local dial_mode log_level wan_interface lan_interface
|
||||
config_get dial_mode config dial_mode "domain"
|
||||
config_get log_level config log_level "info"
|
||||
config_get wan_interface config wan_interface "auto"
|
||||
config_get lan_interface config lan_interface "br-lan"
|
||||
|
||||
SUB_BUF=""; NODE_BUF=""; GROUP_BUF=""; FIRST_GROUP=""; GROUP_NAMES_SEEN=""
|
||||
SUB_TAGS=""
|
||||
config_foreach collect_subtag subscription
|
||||
config_foreach emit_sub subscription
|
||||
config_foreach emit_node node
|
||||
config_foreach emit_group group
|
||||
|
||||
# fall back to a default group when none defined
|
||||
if [ -z "$GROUP_BUF" ]; then
|
||||
GROUP_BUF=" proxy {
|
||||
policy: min_moving_avg
|
||||
}
|
||||
"
|
||||
FIRST_GROUP="proxy"
|
||||
fi
|
||||
|
||||
# routing / dns singletons
|
||||
local private_direct cn_direct block_ads fallback cn_up fb_up response_ttl
|
||||
config_get_bool private_direct routing private_direct 1
|
||||
config_get_bool cn_direct routing cn_direct 1
|
||||
config_get_bool block_ads routing block_ads 0
|
||||
config_get fallback routing fallback "$FIRST_GROUP"
|
||||
[ -n "$fallback" ] || fallback="$FIRST_GROUP"
|
||||
fallback="$(sanitize "$fallback")"
|
||||
config_get cn_up dns cn_upstream "udp://dns.alidns.com:53"
|
||||
config_get fb_up dns fallback_upstream "tcp+udp://dns.google:53"
|
||||
config_get response_ttl dns response_ttl "0"
|
||||
cn_up="$(sanitize "$cn_up")"
|
||||
fb_up="$(sanitize "$fb_up")"
|
||||
case "$response_ttl" in
|
||||
''|*[!0-9]*) response_ttl=0 ;;
|
||||
esac
|
||||
|
||||
{
|
||||
echo "# 本配置由 luci-app-daede 表单自动生成,再次保存表单会覆盖你的手动修改。"
|
||||
echo "global {"
|
||||
echo " tproxy_port: 12345"
|
||||
echo " tproxy_port_protect: true"
|
||||
echo " so_mark_from_dae: 0"
|
||||
echo " log_level: ${log_level}"
|
||||
[ -n "$lan_interface" ] && echo " lan_interface: ${lan_interface}"
|
||||
echo " wan_interface: ${wan_interface}"
|
||||
echo " auto_config_kernel_parameter: true"
|
||||
echo " dial_mode: ${dial_mode}"
|
||||
echo " tcp_check_url: 'http://cp.cloudflare.com,1.1.1.1,2606:4700:4700::1111'"
|
||||
echo " udp_check_dns: 'dns.google:53,8.8.8.8,2001:4860:4860::8888'"
|
||||
echo " check_interval: 30s"
|
||||
echo " check_tolerance: 50ms"
|
||||
echo "}"
|
||||
echo ""
|
||||
|
||||
if [ -n "$SUB_BUF" ]; then
|
||||
echo "subscription {"
|
||||
printf '%s' "$SUB_BUF"
|
||||
echo "}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "$NODE_BUF" ]; then
|
||||
echo "node {"
|
||||
printf '%s' "$NODE_BUF"
|
||||
echo "}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "dns {"
|
||||
echo " ipversion_prefer: 4"
|
||||
[ "$response_ttl" -gt 0 ] && echo " response_ttl: ${response_ttl}"
|
||||
echo " upstream {"
|
||||
echo " cndns: '${cn_up}'"
|
||||
echo " fallbackdns: '${fb_up}'"
|
||||
echo " }"
|
||||
echo " routing {"
|
||||
echo " request {"
|
||||
[ "$block_ads" = "1" ] && echo " qname(geosite:category-ads-all) -> reject"
|
||||
echo " qname(suffix:googleapis.cn) -> fallbackdns"
|
||||
echo " qname(geosite:cn) -> cndns"
|
||||
echo " fallback: fallbackdns"
|
||||
echo " }"
|
||||
echo " }"
|
||||
echo "}"
|
||||
echo ""
|
||||
|
||||
echo "group {"
|
||||
printf '%s' "$GROUP_BUF"
|
||||
echo "}"
|
||||
echo ""
|
||||
|
||||
echo "routing {"
|
||||
echo " pname(NetworkManager) -> direct"
|
||||
# multicast / broadcast direct (geoip:private doesn't cover these)
|
||||
echo " dip(224.0.0.0/3) -> direct"
|
||||
echo " dip(255.255.255.255/32) -> direct"
|
||||
echo " dip('ff00::/8') -> direct"
|
||||
[ "$private_direct" = "1" ] && echo " dip(geoip:private) -> direct"
|
||||
# NTP time sync direct
|
||||
echo " l4proto(udp) && dport(123) -> direct"
|
||||
# OS connectivity checks direct (avoid captive-portal false positives)
|
||||
echo " domain(connectivitycheck.gstatic.com) -> direct"
|
||||
echo " domain(msftconnecttest.com) -> direct"
|
||||
echo " domain(suffix:googleapis.cn) -> ${fallback}"
|
||||
if [ "$cn_direct" = "1" ]; then
|
||||
echo " dip(geoip:cn) -> direct"
|
||||
echo " domain(geosite:cn) -> direct"
|
||||
fi
|
||||
[ "$block_ads" = "1" ] && echo " domain(geosite:category-ads-all) -> block"
|
||||
config_list_foreach routing custom _emit_custom
|
||||
echo " fallback: ${fallback}"
|
||||
echo "}"
|
||||
} > "$TMP_GEN"
|
||||
chmod 600 "$TMP_GEN" # dae rejects world/group-accessible config files
|
||||
|
||||
# overwrite live config only if validation passes
|
||||
local out
|
||||
out="$(DAE_LOCATION_ASSET="/usr/share/v2ray" "$DAE_BIN" validate -c "$TMP_GEN" 2>&1)"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "validate failed: $(echo "$out" | head -1)" >&2
|
||||
rm -f "$TMP_GEN"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p /etc/dae
|
||||
cat "$TMP_GEN" > "$CONFIG_DAE"
|
||||
chmod 600 "$CONFIG_DAE"
|
||||
rm -f "$TMP_GEN"
|
||||
|
||||
if /usr/bin/pgrep -x dae >/dev/null 2>&1; then
|
||||
"$DAE_INITD" hot_reload >/dev/null 2>&1
|
||||
fi
|
||||
echo "ok"
|
||||
return 0
|
||||
}
|
||||
|
||||
_emit_custom() {
|
||||
local line="$(printf '%s' "$1" | tr -d '\n\r')"
|
||||
[ -n "$line" ] || return 0
|
||||
echo " ${line}"
|
||||
}
|
||||
|
||||
# ===================== import =====================
|
||||
# Fill the UCI form from an existing config.dae subscription{}/node{} block.
|
||||
# Idempotent: wipe same-type sections first, then rebuild from file.
|
||||
|
||||
import_block() {
|
||||
# $1=block name (subscription|node) $2=uci section type $3=link/url field
|
||||
local block="$1" stype="$2" field="$3"
|
||||
awk -v blk="$block" '
|
||||
$0 ~ "^[[:space:]]*"blk"[[:space:]]*\\{" { inb=1; next }
|
||||
inb && /^[[:space:]]*\}/ { inb=0 }
|
||||
inb { print }
|
||||
' "$CONFIG_DAE" 2>/dev/null | while IFS= read -r raw; do
|
||||
# drop comment, trim
|
||||
line="$(printf '%s' "$raw" | sed 's/#.*$//' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
[ -n "$line" ] || continue
|
||||
tag=""; val=""
|
||||
case "$line" in
|
||||
\'*|\"*)
|
||||
# starts with a quote -> untagged link
|
||||
val="$line"
|
||||
;;
|
||||
[A-Za-z0-9_]*:*)
|
||||
# identifier ':' link -> tagged (split on first colon)
|
||||
tag="$(printf '%s' "$line" | sed "s/[[:space:]]*:.*//")"
|
||||
val="$(printf '%s' "$line" | sed "s/^[^:]*:[[:space:]]*//")"
|
||||
;;
|
||||
*)
|
||||
val="$line"
|
||||
;;
|
||||
esac
|
||||
# strip surrounding quotes
|
||||
val="$(printf '%s' "$val" | sed "s/^['\"]//; s/['\"]$//")"
|
||||
[ -n "$val" ] || continue
|
||||
# skip dae example placeholders (ss://LINK, server-ip:port, localhost:1080 ...)
|
||||
case "$val" in
|
||||
*://LINK|*LINK*|*server-ip*|*://localhost:*|*example.com*) continue ;;
|
||||
esac
|
||||
sid="$(uci add dae "$stype")"
|
||||
[ -n "$tag" ] && uci set "dae.$sid.tag=$tag"
|
||||
uci set "dae.$sid.$field=$val"
|
||||
uci set "dae.$sid.enabled=1"
|
||||
done
|
||||
}
|
||||
|
||||
do_import() {
|
||||
[ -f "$CONFIG_DAE" ] || { echo "no config.dae" >&2; return 1; }
|
||||
while uci -q delete dae.@subscription[0] >/dev/null 2>&1; do :; done
|
||||
while uci -q delete dae.@node[0] >/dev/null 2>&1; do :; done
|
||||
import_block subscription subscription url
|
||||
import_block node node link
|
||||
uci commit dae
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
# Converter writes a batch of share links to a local file that dae reads as a
|
||||
# file:// subscription, so a converted airport is one subscription instead of
|
||||
# hundreds of manual nodes. $1=id (airport id), reads links from SUB_STAGE.
|
||||
# id is restricted to [A-Za-z0-9_] to keep the path inside SUBS_DIR.
|
||||
do_write_sub() {
|
||||
local id="$1"
|
||||
case "$id" in ''|*[!A-Za-z0-9_]*) echo "bad id" >&2; return 2 ;; esac
|
||||
[ -f "$SUB_STAGE" ] || { echo "no staged data" >&2; return 1; }
|
||||
mkdir -p "$SUBS_DIR"
|
||||
# dae rejects subscription/config files readable by group/other (needs <=0640)
|
||||
cp "$SUB_STAGE" "$SUBS_DIR/$id.sub.tmp" || { echo "write failed" >&2; return 1; }
|
||||
chmod 0600 "$SUBS_DIR/$id.sub.tmp"
|
||||
mv "$SUBS_DIR/$id.sub.tmp" "$SUBS_DIR/$id.sub"
|
||||
rm -f "$SUB_STAGE"
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
do_delete_sub() {
|
||||
local id="$1"
|
||||
case "$id" in ''|*[!A-Za-z0-9_]*) return 0 ;; esac
|
||||
rm -f "$SUBS_DIR/$id.sub"
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
# ===================== entry =====================
|
||||
|
||||
case "$1" in
|
||||
generate) generate ;;
|
||||
import) do_import ;;
|
||||
write-sub) do_write_sub "$2" ;;
|
||||
delete-sub) do_delete_sub "$2" ;;
|
||||
*) echo "usage: $0 {generate|import|write-sub <id>|delete-sub <id>}" >&2; exit 2 ;;
|
||||
esac
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/sh
|
||||
# pkg-info.sh <package>
|
||||
# Prints "<installed>\t<latest>" for the named package, where either field is
|
||||
# empty when unknown. Used by the Updates view to decide whether to enable
|
||||
# the [Upgrade] button.
|
||||
|
||||
PKG="$1"
|
||||
case "$PKG" in
|
||||
dae|daed|luci-app-daede) ;;
|
||||
*) echo "" ; exit 64 ;;
|
||||
esac
|
||||
|
||||
installed=""
|
||||
latest=""
|
||||
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
if apk info -e "$PKG" >/dev/null 2>&1; then
|
||||
installed=$(apk list -I "$PKG" 2>/dev/null | awk -v p="$PKG" '
|
||||
$1 ~ "^" p "-" {
|
||||
sub("^" p "-", "", $1);
|
||||
print $1;
|
||||
exit
|
||||
}
|
||||
')
|
||||
fi
|
||||
# apk search treats ^ and $ as literal chars (not regex anchors), so the old
|
||||
# `apk search "^pkg$"` matched nothing. Enumerate the exact-name package
|
||||
# across all feeds via `apk list` and take the highest version. Restricting
|
||||
# to "<pkg>-<digit>" avoids sibling names like <pkg>-geoip / luci-app-<pkg>.
|
||||
latest=$(apk list "$PKG" 2>/dev/null | awk -v p="$PKG" '
|
||||
$1 ~ "^" p "-[0-9]" {
|
||||
v = $1; sub("^" p "-", "", v);
|
||||
print v
|
||||
}
|
||||
' | sort -V | tail -1)
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
installed=$(opkg status "$PKG" 2>/dev/null | awk -F': ' '$1=="Version"{print $2; exit}')
|
||||
latest=$(opkg info "$PKG" 2>/dev/null | awk -F': ' '$1=="Version"{print $2; exit}')
|
||||
fi
|
||||
|
||||
case "$PKG" in
|
||||
dae|daed)
|
||||
case "$installed" in
|
||||
20[0-9][0-9].[0-9]*) ;;
|
||||
*) installed="" ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s\t%s\n' "$installed" "$latest"
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/bin/sh
|
||||
# proxy-check.sh - test whether dae's transparent proxy can actually reach a
|
||||
# blocked site (default YouTube). dae proxies the router's own WAN-bound traffic
|
||||
# (wan_interface), so a plain request from here is routed through dae just like a
|
||||
# LAN client would be. Prints one line: ok=<0|1> code=<http> ms=<n>
|
||||
|
||||
URL="${1:-https://www.youtube.com/generate_204}"
|
||||
|
||||
code=000
|
||||
ms=0
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
out="$(curl -4 -sL --max-time 8 --connect-timeout 4 \
|
||||
-o /dev/null -w '%{http_code} %{time_total}' "$URL" 2>/dev/null)"
|
||||
code="$(printf '%s' "$out" | awk '{print $1}')"
|
||||
t="$(printf '%s' "$out" | awk '{print $2}')"
|
||||
[ -n "$code" ] || code=000
|
||||
ms="$(awk -v x="${t:-0}" 'BEGIN{printf "%d", x*1000}')"
|
||||
else
|
||||
# busybox wget has no status/timing output — success is exit 0
|
||||
if wget -q -T 8 -O /dev/null "$URL" 2>/dev/null; then code=204; else code=000; fi
|
||||
fi
|
||||
|
||||
ok=0
|
||||
case "$code" in 200|204|301|302) ok=1 ;; esac
|
||||
|
||||
echo "ok=$ok code=$code ms=$ms"
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Background apk/opkg update for the Updates view. Returns at once; throttled.
|
||||
|
||||
LOCK=/tmp/luci-app-daede.idx.lock
|
||||
|
||||
if [ -f "$LOCK" ]; then
|
||||
mtime=$(date -r "$LOCK" +%s 2>/dev/null || echo 0)
|
||||
[ "$(( $(date +%s) - mtime ))" -lt 90 ] && exit 0
|
||||
fi
|
||||
: > "$LOCK"
|
||||
|
||||
(
|
||||
# -n: skip if a package op holds the shared apk lock (no need to refresh then)
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
flock -n /tmp/luci-app-daede.apk.lock apk update
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
opkg update
|
||||
fi
|
||||
) >/dev/null 2>&1 </dev/null &
|
||||
|
||||
exit 0
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Publish converter snapshots without exposing a partially written final file.
|
||||
|
||||
set -eu
|
||||
|
||||
DIR=/etc/daed
|
||||
PREFIX=daede-sub
|
||||
STAGE_PREFIX=.daede-sub-stage
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
valid_token() {
|
||||
[ "$#" -eq 1 ] || return 1
|
||||
case "$1" in
|
||||
''|*[!A-Za-z0-9]*) return 1 ;;
|
||||
esac
|
||||
[ "${#1}" -le 64 ]
|
||||
}
|
||||
|
||||
stage_path() { printf '%s/%s-%s\n' "$DIR" "$STAGE_PREFIX" "$1"; }
|
||||
final_path() { printf '%s/%s-%s\n' "$DIR" "$PREFIX" "$1"; }
|
||||
|
||||
regular_file() {
|
||||
[ -f "$1" ] && [ ! -L "$1" ]
|
||||
}
|
||||
|
||||
safe_dir() {
|
||||
[ -d "$DIR" ] && [ ! -L "$DIR" ] || fail "invalid snapshot directory"
|
||||
}
|
||||
|
||||
publish() {
|
||||
[ "$#" -eq 1 ] || fail 'publish expects one token'
|
||||
valid_token "$1" || fail 'invalid snapshot token'
|
||||
safe_dir
|
||||
stage=$(stage_path "$1")
|
||||
final=$(final_path "$1")
|
||||
regular_file "$stage" || fail 'snapshot stage is not a regular file'
|
||||
[ ! -e "$final" ] && [ ! -L "$final" ] || fail 'snapshot already exists'
|
||||
chmod 600 "$stage" || fail 'cannot secure snapshot stage'
|
||||
mv -f "$stage" "$final" || fail 'cannot publish snapshot'
|
||||
}
|
||||
|
||||
replace() {
|
||||
[ "$#" -eq 2 ] || fail 'replace expects existing and stage tokens'
|
||||
valid_token "$1" || fail 'invalid existing snapshot token'
|
||||
valid_token "$2" || fail 'invalid stage snapshot token'
|
||||
safe_dir
|
||||
[ "$1" != "$2" ] || fail 'existing and stage tokens must differ'
|
||||
stage=$(stage_path "$2")
|
||||
final=$(final_path "$1")
|
||||
regular_file "$stage" || fail 'snapshot stage is not a regular file'
|
||||
if [ -e "$final" ] || [ -L "$final" ]; then
|
||||
regular_file "$final" || fail 'existing snapshot is not a regular file'
|
||||
fi
|
||||
chmod 600 "$stage" || fail 'cannot secure snapshot stage'
|
||||
mv -f "$stage" "$final" || fail 'cannot replace snapshot'
|
||||
}
|
||||
|
||||
discard() {
|
||||
[ "$#" -eq 1 ] || fail 'discard expects one token'
|
||||
valid_token "$1" || fail 'invalid snapshot token'
|
||||
safe_dir
|
||||
stage=$(stage_path "$1")
|
||||
if [ -L "$stage" ]; then
|
||||
fail 'refusing symlink snapshot stage'
|
||||
fi
|
||||
if [ -e "$stage" ]; then
|
||||
[ -f "$stage" ] || fail 'snapshot stage is not a regular file'
|
||||
rm -f "$stage" || fail 'cannot discard snapshot stage'
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
publish) shift; publish "$@" ;;
|
||||
replace) shift; replace "$@" ;;
|
||||
discard) shift; discard "$@" ;;
|
||||
*) fail 'unknown snapshot action' ;;
|
||||
esac
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/bin/sh
|
||||
# update-geo.sh <geoip|geosite>
|
||||
# Fork-and-detach so the LuCI RPC call returns immediately. The diagnostics
|
||||
# card polls the .dat file's mtime/size to detect completion. A lock file
|
||||
# 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)
|
||||
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)
|
||||
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"
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0 <geoip|geosite>" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
[ -n "$URL" ] || URL="$DEF_URL"
|
||||
|
||||
LOCK="/tmp/luci-app-daede.${TYPE}.lock"
|
||||
LOG="/tmp/luci-app-daede.${TYPE}.log"
|
||||
RLOCK="/tmp/luci-app-daede.geo-reload.lock"
|
||||
|
||||
schedule_backend_reload() {
|
||||
if [ -d "$RLOCK" ]; then
|
||||
rmt=$(date -r "$RLOCK" +%s 2>/dev/null || echo 0)
|
||||
[ $(( $(date +%s) - rmt )) -gt 300 ] && rmdir "$RLOCK" 2>/dev/null
|
||||
fi
|
||||
mkdir "$RLOCK" 2>/dev/null || return 0
|
||||
(
|
||||
sleep 25
|
||||
ab="$(uci -q get daede.config.active_backend 2>/dev/null || echo daed)"
|
||||
if [ "$ab" = dae ]; then
|
||||
bin=dae; act=hot_reload
|
||||
else
|
||||
bin=daed; act=reload
|
||||
fi
|
||||
if /bin/pidof "$bin" >/dev/null 2>&1; then
|
||||
if "/etc/init.d/$bin" "$act" >/dev/null 2>&1; then
|
||||
echo "$(date '+%F %T') reloaded $bin"
|
||||
else
|
||||
echo "$(date '+%F %T') $bin reload failed"
|
||||
fi
|
||||
else
|
||||
echo "$(date '+%F %T') $bin not running; skip reload"
|
||||
fi
|
||||
rmdir "$RLOCK" 2>/dev/null
|
||||
) </dev/null >>"$LOG" 2>&1 &
|
||||
}
|
||||
|
||||
if [ -f "$LOCK" ]; then
|
||||
# Lock is fresh (< 5 min)? Refuse. Stale? Remove and proceed.
|
||||
mtime=$(date -r "$LOCK" +%s 2>/dev/null || echo 0)
|
||||
age=$(( $(date +%s) - mtime ))
|
||||
if [ "$age" -lt 300 ]; then
|
||||
echo "${TYPE} update already in progress (PID $(cat "$LOCK" 2>/dev/null), age ${age}s)" >&2
|
||||
exit 75
|
||||
fi
|
||||
rm -f "$LOCK"
|
||||
fi
|
||||
|
||||
if ! ( set -C; echo "$$" >"$LOCK" ) 2>/dev/null; then
|
||||
echo "${TYPE} update already in progress" >&2
|
||||
exit 75
|
||||
fi
|
||||
|
||||
# Spawn detached worker — parent returns immediately to LuCI.
|
||||
(
|
||||
exec >"$LOG" 2>&1
|
||||
trap 'rm -f "$LOCK"' EXIT INT TERM
|
||||
|
||||
rc=0
|
||||
TMP="${DEST}.new"
|
||||
mkdir -p "$(dirname "$DEST")"
|
||||
echo "$(date '+%F %T') begin: $URL"
|
||||
|
||||
if ! curl -fsSL --connect-timeout 15 --max-time 240 -o "$TMP" "$URL"; then
|
||||
echo "$(date '+%F %T') download failed"
|
||||
rm -f "$TMP"
|
||||
rc=1
|
||||
else
|
||||
size=$(wc -c < "$TMP" 2>/dev/null || echo 0)
|
||||
magic=$(hexdump -n 1 -e '1/1 "%02x"' "$TMP" 2>/dev/null)
|
||||
if [ "$size" -lt 1024 ]; then
|
||||
echo "$(date '+%F %T') file too small ($size bytes)"
|
||||
rm -f "$TMP"
|
||||
rc=2
|
||||
elif [ "$magic" != "0a" ]; then
|
||||
echo "$(date '+%F %T') invalid geodata format"
|
||||
rm -f "$TMP"
|
||||
rc=3
|
||||
else
|
||||
mv "$TMP" "$DEST"
|
||||
echo "$(date '+%F %T') updated $DEST ($size bytes)"
|
||||
schedule_backend_reload
|
||||
fi
|
||||
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"
|
||||
exit 0
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/bin/sh
|
||||
# update-pkg.sh <dae|daed|luci-app-daede>
|
||||
# Refresh package indexes and upgrade the named package via apk (25.12+) or
|
||||
# opkg (24.10). Forks the work to background so the LuCI RPC call returns
|
||||
# immediately; the result is streamed to /tmp/luci-app-daede.pkg.<name>.log.
|
||||
|
||||
PKG="$1"
|
||||
case "$PKG" in
|
||||
dae|daed|luci-app-daede) ;;
|
||||
*)
|
||||
echo "usage: $0 <dae|daed|luci-app-daede>" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
# run from a /tmp copy so upgrading luci-app-daede (which replaces this script)
|
||||
# can't corrupt the in-flight upgrade
|
||||
case "$0" in
|
||||
/tmp/.daede-upd-*) ;;
|
||||
*)
|
||||
_self="/tmp/.daede-upd-$$"
|
||||
cp "$0" "$_self" 2>/dev/null && exec sh "$_self" "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
LOCK="/tmp/luci-app-daede.pkg-${PKG}.lock"
|
||||
LOG="/tmp/luci-app-daede.pkg-${PKG}.log"
|
||||
|
||||
if [ -f "$LOCK" ]; then
|
||||
mtime=$(date -r "$LOCK" +%s 2>/dev/null || echo 0)
|
||||
age=$(( $(date +%s) - mtime ))
|
||||
if [ "$age" -lt 300 ]; then
|
||||
echo "${PKG} update already in progress (PID $(cat "$LOCK" 2>/dev/null), age ${age}s)" >&2
|
||||
exit 75
|
||||
fi
|
||||
rm -f "$LOCK"
|
||||
fi
|
||||
|
||||
if ! ( set -C; echo "$$" >"$LOCK" ) 2>/dev/null; then
|
||||
echo "${PKG} update already in progress" >&2
|
||||
exit 75
|
||||
fi
|
||||
|
||||
(
|
||||
exec >"$LOG" 2>&1
|
||||
trap 'rm -f "$LOCK"; [ "${0#/tmp/.daede-upd-}" != "$0" ] && rm -f "$0"' EXIT INT TERM
|
||||
|
||||
echo "$(date '+%F %T') begin upgrade: $PKG"
|
||||
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
# shared apk lock with the bg index refresh (avoid "Unable to lock database")
|
||||
(
|
||||
flock 9
|
||||
apk update 2>&1
|
||||
ver=$(apk list "$PKG" 2>/dev/null | awk -v p="$PKG" '$1 ~ "^" p "-[0-9]" { v=$1; sub("^" p "-", "", v); print v }' | sort -V | tail -1)
|
||||
if [ -n "$ver" ]; then
|
||||
constraint="$PKG=$ver"
|
||||
else
|
||||
echo "result: 软件源中没有找到 $PKG,请检查网络或软件源配置"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- apk add -s $constraint ---"
|
||||
if ! apk add -s "$constraint" 2>&1; then
|
||||
echo "note: apk 预检失败,通常是系统上其它软件包的问题,继续尝试升级"
|
||||
fi
|
||||
echo "--- apk add $constraint ---"
|
||||
apk add "$constraint" 2>&1
|
||||
exit 0
|
||||
) 9>/tmp/luci-app-daede.apk.lock
|
||||
rc=$?
|
||||
if [ "$rc" != 0 ]; then
|
||||
:
|
||||
elif ! apk list --installed 2>/dev/null | grep -q "^${PKG}-"; then
|
||||
echo "result: $PKG is not installed"
|
||||
rc=1
|
||||
elif apk list -u 2>/dev/null | grep -q "^${PKG}-"; then
|
||||
echo "result: $PKG still has a pending upgrade"
|
||||
rc=1
|
||||
else
|
||||
echo "result: $PKG is at the latest available version"
|
||||
rc=0
|
||||
fi
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
echo "--- opkg update ---"
|
||||
opkg update 2>&1
|
||||
echo "--- opkg upgrade $PKG ---"
|
||||
opkg upgrade "$PKG" 2>&1
|
||||
rc=$?
|
||||
else
|
||||
echo "no package manager found"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if [ "$rc" = 0 ]; then echo "$(date '+%F %T') ✓ 完成"; else echo "$(date '+%F %T') ✗ 失败 (rc=$rc)"; fi
|
||||
|
||||
# luci-app-daede upgrade replaces ACL JSON — reload rpcd so changes apply.
|
||||
if [ "$PKG" = "luci-app-daede" ] && [ "$rc" = "0" ]; then
|
||||
echo "reloading rpcd to pick up new ACL"
|
||||
/etc/init.d/rpcd reload 2>&1
|
||||
fi
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
|
||||
echo "started in background, see $LOG"
|
||||
exit 0
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"admin/services/daede": {
|
||||
"title": "daede",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-daede" ]
|
||||
}
|
||||
},
|
||||
"admin/services/daede/config": {
|
||||
"title": "Settings",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "daede/config"
|
||||
}
|
||||
},
|
||||
"admin/services/daede/converter": {
|
||||
"title": "Subscription Converter",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "daede/converter"
|
||||
}
|
||||
},
|
||||
"admin/services/daede/updates": {
|
||||
"title": "Updates",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "daede/updates"
|
||||
}
|
||||
},
|
||||
"admin/services/daede/log": {
|
||||
"title": "Log",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "daede/log"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
{
|
||||
"luci-app-daede": {
|
||||
"description": "Grant access to dae/daed configuration",
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/init.d/dae": [ "read" ],
|
||||
"/etc/init.d/daed": [ "read" ],
|
||||
"/etc/config/daede": [ "read" ],
|
||||
"/etc/dae/config.dae": [ "read" ],
|
||||
"/etc/dae/example.dae": [ "read" ],
|
||||
"/var/log/dae/dae.log": [ "read" ],
|
||||
"/var/log/daed/daed.log": [ "read" ],
|
||||
"/usr/share/v2ray/geoip.dat": [ "read" ],
|
||||
"/usr/share/v2ray/geosite.dat": [ "read" ],
|
||||
"/sys/kernel/btf/vmlinux": [ "read" ],
|
||||
"/usr/lib/debug/boot/vmlinux": [ "read" ],
|
||||
"/run/netns/daens": [ "read" ],
|
||||
"/tmp/luci-app-daede.geoip.log": [ "read" ],
|
||||
"/tmp/luci-app-daede.geosite.log": [ "read" ],
|
||||
"/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/env DAE_LOCATION_ASSET=/usr/share/v2ray /usr/bin/dae validate -c /tmp/dae-validate.dae": [ "exec" ],
|
||||
"/usr/bin/dae --version": [ "exec" ],
|
||||
"/bin/rm -f /tmp/dae-validate.dae": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/pkg-info.sh dae": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/pkg-info.sh daed": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/pkg-info.sh luci-app-daede": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/refresh-index.sh": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/proxy-check.sh": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"service": [ "list" ]
|
||||
},
|
||||
"uci": [ "dae", "daed", "daede" ]
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/dae/config.dae": [ "write" ],
|
||||
"/tmp/dae-validate.dae": [ "write" ],
|
||||
"/tmp/daede-import.b64": [ "write" ],
|
||||
"/tmp/daede-sub.txt": [ "write" ],
|
||||
"/etc/daed/.daede-sub-stage-*": [ "write" ],
|
||||
"/etc/daed/daede-sub-*": [ "write" ],
|
||||
"/usr/share/luci-app-daede/config-backup.sh import": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/snapshot-file.sh publish *": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/snapshot-file.sh replace * *": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/snapshot-file.sh discard *": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/gen-dae-config.sh write-sub *": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/gen-dae-config.sh delete-sub *": [ "exec" ],
|
||||
"/etc/config/daede": [ "write" ],
|
||||
"/var/log/dae/dae.log": [ "write" ],
|
||||
"/var/log/daed/daed.log": [ "write" ],
|
||||
"/etc/init.d/dae start": [ "exec" ],
|
||||
"/etc/init.d/dae stop": [ "exec" ],
|
||||
"/etc/init.d/dae restart": [ "exec" ],
|
||||
"/etc/init.d/dae enable": [ "exec" ],
|
||||
"/etc/init.d/dae disable": [ "exec" ],
|
||||
"/etc/init.d/dae hot_reload": [ "exec" ],
|
||||
"/etc/init.d/daed start": [ "exec" ],
|
||||
"/etc/init.d/daed stop": [ "exec" ],
|
||||
"/etc/init.d/daed restart": [ "exec" ],
|
||||
"/etc/init.d/daed enable": [ "exec" ],
|
||||
"/etc/init.d/daed disable": [ "exec" ],
|
||||
"/sbin/uci set dae.config.enabled=*": [ "exec" ],
|
||||
"/sbin/uci set daed.config.enabled=*": [ "exec" ],
|
||||
"/sbin/uci set daede.config=daede": [ "exec" ],
|
||||
"/sbin/uci set daede.config.active_backend=*": [ "exec" ],
|
||||
"/sbin/uci commit dae": [ "exec" ],
|
||||
"/sbin/uci commit daed": [ "exec" ],
|
||||
"/sbin/uci commit daede": [ "exec" ],
|
||||
"/sbin/ip netns del daens": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/update-geo.sh geoip": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/update-geo.sh 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" ],
|
||||
"/usr/share/luci-app-daede/fetch-clash-yaml.sh *": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/gen-dae-config.sh generate": [ "exec" ],
|
||||
"/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/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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user