mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-01 16:44:45 +08:00
update 2026-09-05 22:03:25
This commit is contained in:
1 parent
f3074ed22d
commit
063eca5fe2
2070 files changed
+46005
-1523828
No files matched your search
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
# config-backup.sh export|import — back up/restore the whole daede config
|
||||
# (dae + daed + active backend). export: prints base64(tar.gz) to stdout.
|
||||
# import: decodes /tmp/daede-import.b64, validates, restores, restarts backend.
|
||||
set -eu
|
||||
|
||||
ACTION="${1:-}"
|
||||
LOG="/tmp/luci-app-daede.backup.log"
|
||||
IMPORT_B64="/tmp/daede-import.b64"
|
||||
MAX_TAR=184320 # 180 KiB tar.gz -> ~240 KiB base64, under the ubus limit
|
||||
|
||||
# relative paths (no leading /) so tar entries match the extraction whitelist
|
||||
WHITELIST="etc/config/dae etc/config/daed etc/config/daede etc/dae/config.dae etc/daed/wing.db"
|
||||
|
||||
b64enc() { ucode -e 'let f=require("fs"); print(b64enc(f.open(ARGV[0],"r").read("all")));' -- "$1"; }
|
||||
b64dec() { ucode -e 'let f=require("fs"); let r=b64dec(trim(f.open(ARGV[0],"r").read("all"))); if(!r)exit(1); let o=f.open(ARGV[1],"w"); o.write(r); o.close();' -- "$1" "$2"; }
|
||||
|
||||
case "$ACTION" in
|
||||
export)
|
||||
exist=""
|
||||
for p in $WHITELIST; do [ -e "/$p" ] && exist="$exist $p"; done
|
||||
[ -n "$exist" ] || { echo "no config to back up" >&2; exit 1; }
|
||||
tmp="$(mktemp)"
|
||||
( cd / && tar -czf "$tmp" $exist ) 2>/dev/null || { rm -f "$tmp"; echo "tar failed" >&2; exit 1; }
|
||||
if [ "$(wc -c < "$tmp")" -gt "$MAX_TAR" ]; then
|
||||
rm -f "$tmp"; echo "config too large; use System Backup instead" >&2; exit 2
|
||||
fi
|
||||
b64enc "$tmp"
|
||||
rm -f "$tmp"
|
||||
;;
|
||||
|
||||
import)
|
||||
[ -f "$IMPORT_B64" ] || { echo "no upload found" >&2; exit 1; }
|
||||
(
|
||||
exec >"$LOG" 2>&1
|
||||
echo "$(date '+%F %T') begin import"
|
||||
rc=0
|
||||
tmp="$(mktemp)"
|
||||
if ! b64dec "$IMPORT_B64" "$tmp"; then
|
||||
echo "decode failed"; rc=1
|
||||
elif ! gzip -t "$tmp" 2>/dev/null; then
|
||||
echo "not a valid backup archive"; rc=1
|
||||
else
|
||||
# reject any entry outside the whitelist (path-traversal guard)
|
||||
bad="$(tar -tzf "$tmp" 2>/dev/null | grep -vxE 'etc/config/dae|etc/config/daed|etc/config/daede|etc/dae/config\.dae|etc/daed/wing\.db' | head -1)"
|
||||
if [ -n "$bad" ]; then
|
||||
echo "rejected: unexpected entry '$bad'"; rc=1
|
||||
else
|
||||
ab="$(uci -q get daede.config.active_backend || echo dae)"
|
||||
[ -x "/etc/init.d/$ab" ] && /etc/init.d/"$ab" stop 2>/dev/null || true
|
||||
if ( cd / && tar -xzf "$tmp" ); then
|
||||
echo "restored config"
|
||||
else
|
||||
echo "extract failed"; rc=1
|
||||
fi
|
||||
ab="$(uci -q get daede.config.active_backend || echo "$ab")"
|
||||
[ -x "/etc/init.d/$ab" ] && { /etc/init.d/"$ab" enabled || /etc/init.d/"$ab" enable; /etc/init.d/"$ab" restart 2>/dev/null || /etc/init.d/"$ab" start 2>/dev/null; } || true
|
||||
fi
|
||||
fi
|
||||
rm -f "$tmp" "$IMPORT_B64"
|
||||
if [ "$rc" = 0 ]; then echo "result: config restored, backend restarted"; else echo "result: import failed"; fi
|
||||
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
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,422 @@
|
||||
#!/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(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"
|
||||
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_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
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# geo-cron.sh enable|disable
|
||||
# Manage the geo auto-update crontab entry. enable reads the schedule from
|
||||
# daede.config.geo_auto_freq (daily|weekly); both actions are idempotent.
|
||||
|
||||
ACTION="$1"
|
||||
CRONTAB="/etc/crontabs/root"
|
||||
TAG="# luci-app-daede geo-update"
|
||||
SCRIPT="/usr/share/luci-app-daede/update-geo.sh"
|
||||
|
||||
# Drop any previous daede geo entry (tag line + its command line).
|
||||
clean() {
|
||||
[ -f "$CRONTAB" ] || return 0
|
||||
grep -vF -e "$TAG" -e "$SCRIPT" "$CRONTAB" > "$CRONTAB.tmp" 2>/dev/null
|
||||
mv "$CRONTAB.tmp" "$CRONTAB"
|
||||
}
|
||||
|
||||
clean
|
||||
|
||||
if [ "$ACTION" = "enable" ]; then
|
||||
case "$(uci -q get daede.config.geo_auto_freq)" in
|
||||
weekly) sched="17 4 * * 0" ;; # Sunday 04:17
|
||||
*) sched="17 4 * * *" ;; # every day 04:17
|
||||
esac
|
||||
mkdir -p /etc/crontabs
|
||||
{
|
||||
echo "$TAG"
|
||||
echo "$sched $SCRIPT geoip; $SCRIPT geosite"
|
||||
} >> "$CRONTAB"
|
||||
/etc/init.d/cron enable >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
/etc/init.d/cron restart >/dev/null 2>&1
|
||||
exit 0
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/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
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
#!/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
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"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/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" ],
|
||||
"/usr/share/luci-app-daede/config-backup.sh export": [ "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" ],
|
||||
"/tmp/daede-daedsub-*": [ "write" ],
|
||||
"/bin/rm -f /tmp/daede-daedsub-*": [ "exec" ],
|
||||
"/usr/share/luci-app-daede/config-backup.sh import": [ "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" ]
|
||||
},
|
||||
"uci": [ "dae", "daed", "daede" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user