mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 23:32:51 +08:00
💐 Sync 2026-09-13 10:49:47
This commit is contained in:
1 parent
2f26109690
commit
886123f647
21 files changed
+881
-807
No files matched your search
+5
-3
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gecoosac
|
||||
PKG_VERSION:=2.2.20251015
|
||||
PKG_RELEASE:=20
|
||||
PKG_RELEASE:=21
|
||||
|
||||
PKG_MAINTAINER:=Roc Lai <laipeng668@qq.com>
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
@@ -43,8 +43,8 @@ define Package/$(PKG_NAME)
|
||||
CATEGORY:=Network
|
||||
TITLE:=gecoosac server (version $(PKG_VERSION))
|
||||
URL:=http://www.cnrouter.com/
|
||||
DEPENDS:=@(aarch64||arm||i386||mips||mipsel||x86_64) +openssl-util
|
||||
PROVIDES:=gecoosac-files
|
||||
DEPENDS:=@(aarch64||arm||i386||mips||mipsel||x86_64) +openssl-util +coreutils-timeout
|
||||
PROVIDES:=gecoosac-files gecoosac-common
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
@@ -66,11 +66,13 @@ define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/etc/gecoosac/tls
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_DIR) $(1)/usr/share/gecoosac
|
||||
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin
|
||||
$(INSTALL_BIN) ./files/etc/init.d/gecoosac $(1)/etc/init.d/gecoosac
|
||||
$(INSTALL_BIN) ./files/etc/uci-defaults/gecoosac $(1)/etc/uci-defaults/gecoosac
|
||||
$(INSTALL_CONF) ./files/etc/config/gecoosac $(1)/etc/config/gecoosac
|
||||
$(INSTALL_DATA) ./files/usr/share/gecoosac/common.sh $(1)/usr/share/gecoosac/common.sh
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
. /lib/functions.sh
|
||||
. /lib/functions/procd.sh
|
||||
. /usr/share/gecoosac/common.sh
|
||||
|
||||
USE_PROCD=1
|
||||
|
||||
@@ -10,13 +11,9 @@ START=90
|
||||
STOP=10
|
||||
|
||||
PROG=/usr/bin/gecoosac
|
||||
DEFAULT_DB_DIR=/etc/gecoosac
|
||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
||||
DEFAULT_PID_DIR=/var/run
|
||||
DEFAULT_LANG=zh
|
||||
CERT_TIMEOUT=60
|
||||
CERT_RENEW_BEFORE=2592000
|
||||
|
||||
init_conf() {
|
||||
local section_type
|
||||
@@ -47,193 +44,6 @@ init_conf() {
|
||||
config_get "log" "config" "log" "0"
|
||||
}
|
||||
|
||||
is_abs_path() {
|
||||
case "$1" in
|
||||
/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
normalize_path() {
|
||||
local path="$1"
|
||||
local part normalized parent
|
||||
|
||||
is_abs_path "$path" || return 1
|
||||
normalized="/"
|
||||
path="${path#/}"
|
||||
|
||||
while [ -n "$path" ]; do
|
||||
part="${path%%/*}"
|
||||
if [ "$part" = "$path" ]; then
|
||||
path=""
|
||||
else
|
||||
path="${path#*/}"
|
||||
fi
|
||||
|
||||
case "$part" in
|
||||
""|.) ;;
|
||||
..)
|
||||
if [ "$normalized" != "/" ]; then
|
||||
parent="${normalized%/*}"
|
||||
[ -n "$parent" ] || parent="/"
|
||||
normalized="$parent"
|
||||
fi
|
||||
;;
|
||||
*) normalized="${normalized%/}/$part" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
printf '%s\n' "$normalized"
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_safe_upload_dir() {
|
||||
local path physical
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
physical="$(managed_dir_path upload "$path")" || return 1
|
||||
is_supported_upload_path "$path" || return 1
|
||||
is_supported_upload_path "$physical"
|
||||
}
|
||||
|
||||
is_path_in_dir() {
|
||||
local path root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
root="$(normalize_path "$2")" || return 1
|
||||
|
||||
[ "$root" != "/" ] || return 1
|
||||
[ "$path" = "$root" ] && return 0
|
||||
[ "${path#"$root"/}" != "$path" ]
|
||||
}
|
||||
|
||||
is_secure_dir() {
|
||||
local allow_sticky="$2" owner permissions metadata
|
||||
|
||||
[ -d "$1" ] && [ ! -L "$1" ] || return 1
|
||||
metadata="$(ls -ldn "$1" 2>/dev/null)" || return 1
|
||||
set -- $metadata
|
||||
permissions="$1"
|
||||
owner="$3"
|
||||
[ "$owner" = "0" ] || return 1
|
||||
case "$permissions" in
|
||||
d?????????) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
|
||||
if [ "$(printf '%s' "$permissions" | cut -c6)" = "w" ] || \
|
||||
[ "$(printf '%s' "$permissions" | cut -c9)" = "w" ]; then
|
||||
[ "$allow_sticky" = "1" ] && [ "$(printf '%s' "$permissions" | cut -c10)" = "t" ] || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
is_secure_upload_dir() {
|
||||
local path current part rest
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
[ -d "$path" ] && [ ! -L "$path" ] || return 1
|
||||
current="/"
|
||||
rest="${path#/}"
|
||||
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
current="${current%/}/$part"
|
||||
case "$current" in
|
||||
/tmp) is_secure_dir "$current" 1 || return 1 ;;
|
||||
*) is_secure_dir "$current" || return 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
is_safe_db_dir() {
|
||||
local path upload_root physical physical_upload_root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||
|
||||
case "$path" in
|
||||
/etc/gecoosac|/etc/gecoosac/*|/tmp/gecoosac|/tmp/gecoosac/*|/var/lib/gecoosac|/var/lib/gecoosac/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
is_path_in_dir "$path" "$upload_root" && return 1
|
||||
physical="$(managed_dir_path db "$path")" || return 1
|
||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
is_safe_pid_dir() {
|
||||
local path upload_root physical physical_upload_root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||
|
||||
case "$path" in
|
||||
/var/run|/var/run/*|/tmp/gecoosac|/tmp/gecoosac/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
is_path_in_dir "$path" "$upload_root" && return 1
|
||||
physical="$(managed_dir_path pid "$path")" || return 1
|
||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
is_port() {
|
||||
case "$1" in
|
||||
""|*[!0-9]*) return 1 ;;
|
||||
@@ -242,66 +52,6 @@ is_port() {
|
||||
[ "$1" -ge 1 ] 2>/dev/null && [ "$1" -le 65535 ]
|
||||
}
|
||||
|
||||
run_with_timeout() {
|
||||
local timeout pid i
|
||||
|
||||
timeout="$1"
|
||||
shift
|
||||
|
||||
"$@" >/dev/null 2>&1 &
|
||||
pid="$!"
|
||||
i=0
|
||||
|
||||
while kill -0 "$pid" >/dev/null 2>&1; do
|
||||
if [ "$i" -ge "$timeout" ]; then
|
||||
kill "$pid" >/dev/null 2>&1
|
||||
sleep 1
|
||||
kill -9 "$pid" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
wait "$pid"
|
||||
}
|
||||
|
||||
run_with_timeout_output() {
|
||||
local timeout output pid i
|
||||
|
||||
timeout="$1"
|
||||
output="$2"
|
||||
shift 2
|
||||
|
||||
"$@" >"$output" 2>/dev/null &
|
||||
pid="$!"
|
||||
i=0
|
||||
|
||||
while kill -0 "$pid" >/dev/null 2>&1; do
|
||||
if [ "$i" -ge "$timeout" ]; then
|
||||
kill "$pid" >/dev/null 2>&1
|
||||
kill -9 "$pid" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
wait "$pid"
|
||||
}
|
||||
|
||||
is_readable_regular_file() {
|
||||
local path="$1" tmp_dir status
|
||||
|
||||
tmp_dir="$(mktemp -d /tmp/gecoosac-file.XXXXXX)" || return 1
|
||||
run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/check" \
|
||||
/bin/sh -c '[ -f "$1" ] && [ -r "$1" ] && [ -s "$1" ]' \
|
||||
gecoosac-file-check "$path" </dev/null
|
||||
status="$?"
|
||||
rm -rf "$tmp_dir"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
is_ipv4() {
|
||||
local value="$1"
|
||||
local part count
|
||||
@@ -328,48 +78,55 @@ san_has_entry() {
|
||||
local san="$1"
|
||||
local entry="$2"
|
||||
|
||||
echo "$san" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -F -x -q "$entry"
|
||||
printf '%s\n' "$san" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -F -x -q "$entry"
|
||||
}
|
||||
|
||||
cert_matches_san() {
|
||||
local cert_file="$1" cert_host="$2" cert_ip="$3"
|
||||
local san tmp_dir
|
||||
run_with_timeout_output() {
|
||||
local limit="$1" output="$2"
|
||||
shift 2
|
||||
|
||||
is_readable_regular_file "$cert_file" || return 1
|
||||
tmp_dir="$(mktemp -d /tmp/gecoosac-cert.XXXXXX)" || return 1
|
||||
if ! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/san" \
|
||||
openssl x509 -in "$cert_file" -noout -ext subjectAltName </dev/null; then
|
||||
rm -rf "$tmp_dir"
|
||||
return 1
|
||||
fi
|
||||
san="$(cat "$tmp_dir/san" 2>/dev/null)"
|
||||
rm -rf "$tmp_dir"
|
||||
[ -n "$san" ] || return 1
|
||||
san_has_entry "$san" "DNS:${cert_host}" || return 1
|
||||
[ -z "$cert_ip" ] || san_has_entry "$san" "IP Address:${cert_ip}" || return 1
|
||||
|
||||
return 0
|
||||
# coreutils-timeout waits for completion directly and also bounds commands
|
||||
# which ignore SIGTERM; no polling delay or orphaned sleep process is needed.
|
||||
timeout -k 1 "$limit" "$@" >"$output" 2>/dev/null
|
||||
}
|
||||
|
||||
cert_matches_key() {
|
||||
local cert_file="$1" key_file="$2"
|
||||
local cert_pub key_pub tmp_dir
|
||||
run_with_timeout() {
|
||||
local limit="$1"
|
||||
shift
|
||||
run_with_timeout_output "$limit" /dev/null "$@"
|
||||
}
|
||||
|
||||
cert_pair_valid() {
|
||||
local cert_file="$1" key_file="$2" cert_host="$3" cert_ip="$4"
|
||||
local min_lifetime="${5:-0}" tmp_dir cert_pub key_pub san status
|
||||
|
||||
is_readable_regular_file "$cert_file" || return 1
|
||||
is_readable_regular_file "$key_file" || return 1
|
||||
tmp_dir="$(mktemp -d /tmp/gecoosac-cert.XXXXXX)" || return 1
|
||||
if ! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/cert.pub" \
|
||||
openssl x509 -in "$cert_file" -noout -pubkey </dev/null || \
|
||||
! run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/key.pub" \
|
||||
openssl pkey -in "$key_file" -pubout </dev/null; then
|
||||
rm -rf "$tmp_dir"
|
||||
return 1
|
||||
status=1
|
||||
if run_with_timeout "$CERT_TIMEOUT" /bin/sh -c '
|
||||
[ -f "$1" ] && [ -r "$1" ] && [ -s "$1" ] &&
|
||||
[ -f "$2" ] && [ -r "$2" ] && [ -s "$2" ]
|
||||
' gecoosac-file-check "$cert_file" "$key_file" </dev/null && \
|
||||
run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/cert" \
|
||||
openssl x509 -in "$cert_file" -noout -pubkey -ext subjectAltName -checkend "$min_lifetime" </dev/null && \
|
||||
run_with_timeout_output "$CERT_TIMEOUT" "$tmp_dir/key.pub" \
|
||||
openssl pkey -in "$key_file" -pubout </dev/null && \
|
||||
run_with_timeout "$CERT_TIMEOUT" \
|
||||
openssl verify -partial_chain -trusted "$cert_file" "$cert_file" </dev/null; then
|
||||
# Trust the configured leaf only for the local validity-period check;
|
||||
# browser trust still depends on the certificate installed by the user.
|
||||
cert_pub="$(sed -n '/^-----BEGIN PUBLIC KEY-----$/,/^-----END PUBLIC KEY-----$/p' "$tmp_dir/cert")"
|
||||
key_pub="$(cat "$tmp_dir/key.pub")"
|
||||
if [ -n "$cert_pub" ] && [ "$cert_pub" = "$key_pub" ]; then
|
||||
status=0
|
||||
if [ -n "$cert_host" ]; then
|
||||
san="$(cat "$tmp_dir/cert")"
|
||||
san_has_entry "$san" "DNS:${cert_host}" || status=1
|
||||
[ -z "$cert_ip" ] || san_has_entry "$san" "IP Address:${cert_ip}" || status=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
cert_pub="$(cat "$tmp_dir/cert.pub" 2>/dev/null)"
|
||||
key_pub="$(cat "$tmp_dir/key.pub" 2>/dev/null)"
|
||||
rm -rf "$tmp_dir"
|
||||
|
||||
[ -n "$cert_pub" ] && [ "$cert_pub" = "$key_pub" ]
|
||||
return "$status"
|
||||
}
|
||||
|
||||
generate_default_cert() {
|
||||
@@ -389,7 +146,7 @@ generate_default_cert() {
|
||||
cert_ip="$(uci -q get network.lan.ipaddr)"
|
||||
is_ipv4 "$cert_ip" || cert_ip=""
|
||||
|
||||
if is_readable_regular_file "$DEFAULT_KEY_FILE" && cert_matches_san "$DEFAULT_CRT_FILE" "$cert_host" "$cert_ip" && cert_matches_key "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE"; then
|
||||
if cert_pair_valid "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE" "$cert_host" "$cert_ip" "$CERT_RENEW_BEFORE"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -423,9 +180,7 @@ generate_default_cert() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -s "$tmp_crt" ] || [ ! -s "$tmp_key" ] || \
|
||||
! cert_matches_san "$tmp_crt" "$cert_host" "$cert_ip" || \
|
||||
! cert_matches_key "$tmp_crt" "$tmp_key" || \
|
||||
if ! cert_pair_valid "$tmp_crt" "$tmp_key" "$cert_host" "$cert_ip" "$CERT_RENEW_BEFORE" || \
|
||||
! chmod 600 "$tmp_key" || ! chmod 644 "$tmp_crt"; then
|
||||
rm -f "$tmp_crt" "$tmp_key"
|
||||
logger -t gecoosac "failed to validate default HTTPS certificate"
|
||||
@@ -468,10 +223,8 @@ generate_default_cert() {
|
||||
old_key=1
|
||||
fi
|
||||
|
||||
if mv "$tmp_key" "$DEFAULT_KEY_FILE" && \
|
||||
mv "$tmp_crt" "$DEFAULT_CRT_FILE" && \
|
||||
cert_matches_san "$DEFAULT_CRT_FILE" "$cert_host" "$cert_ip" && \
|
||||
cert_matches_key "$DEFAULT_CRT_FILE" "$DEFAULT_KEY_FILE"; then
|
||||
# Both temporary files were validated above; rename preserves their contents.
|
||||
if mv "$tmp_key" "$DEFAULT_KEY_FILE" && mv "$tmp_crt" "$DEFAULT_CRT_FILE"; then
|
||||
rm -rf "$backup_dir"
|
||||
return 0
|
||||
fi
|
||||
@@ -601,31 +354,6 @@ ensure_secure_dir_tree() {
|
||||
done
|
||||
}
|
||||
|
||||
managed_dir_path() {
|
||||
local role="$1" path anchor
|
||||
|
||||
case "$role" in
|
||||
upload|db|pid|file|tls) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
path="$(normalize_path "$2")" || return 1
|
||||
case "$path" in
|
||||
/var/run|/var/run/*)
|
||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
||||
[ "$anchor" = "/tmp/run" ] || return 1
|
||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
||||
;;
|
||||
/var|/var/*)
|
||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
||||
case "$anchor" in
|
||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
;;
|
||||
*) printf '%s\n' "$path" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_upload_storage_root() {
|
||||
local path="$1" root
|
||||
|
||||
@@ -677,10 +405,8 @@ prepare_service() {
|
||||
if [ "$isonlyoneprot" = "0" ] && [ "$https" = "1" ]; then
|
||||
if [ "$crt_file" = "$DEFAULT_CRT_FILE" ] && [ "$key_file" = "$DEFAULT_KEY_FILE" ]; then
|
||||
generate_default_cert || return 1
|
||||
fi
|
||||
|
||||
if ! cert_matches_key "$crt_file" "$key_file"; then
|
||||
logger -t gecoosac "HTTPS certificate and key do not match or cannot be read"
|
||||
elif ! cert_pair_valid "$crt_file" "$key_file"; then
|
||||
logger -t gecoosac "HTTPS certificate is not currently valid, or its private key cannot be read or does not match"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/gecoosac/common.sh
|
||||
|
||||
changed=0
|
||||
DEFAULT_DB_DIR=/etc/gecoosac
|
||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
||||
LEGACY_UPLOAD_DIR=/etc/gecoosac/upload
|
||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
||||
DEFAULT_PID_DIR=/var/run
|
||||
OLD_CRT_FILE=/etc/gecoosac/tls/1.crt
|
||||
OLD_KEY_FILE=/etc/gecoosac/tls/1.key
|
||||
CONFIG_COMPAT=2
|
||||
@@ -26,192 +23,6 @@ set_default() {
|
||||
changed=1
|
||||
}
|
||||
|
||||
normalize_path() {
|
||||
local path="$1"
|
||||
local part normalized parent
|
||||
|
||||
is_abs_path "$path" || return 1
|
||||
normalized="/"
|
||||
path="${path#/}"
|
||||
|
||||
while [ -n "$path" ]; do
|
||||
part="${path%%/*}"
|
||||
if [ "$part" = "$path" ]; then
|
||||
path=""
|
||||
else
|
||||
path="${path#*/}"
|
||||
fi
|
||||
|
||||
case "$part" in
|
||||
""|.) ;;
|
||||
..)
|
||||
if [ "$normalized" != "/" ]; then
|
||||
parent="${normalized%/*}"
|
||||
[ -n "$parent" ] || parent="/"
|
||||
normalized="$parent"
|
||||
fi
|
||||
;;
|
||||
*) normalized="${normalized%/}/$part" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
printf '%s\n' "$normalized"
|
||||
}
|
||||
|
||||
is_abs_path() {
|
||||
case "$1" in
|
||||
/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
managed_dir_path() {
|
||||
local role="$1" path anchor
|
||||
|
||||
case "$role" in
|
||||
upload|db|pid|file) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
path="$(normalize_path "$2")" || return 1
|
||||
case "$path" in
|
||||
/var/run|/var/run/*)
|
||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
||||
[ "$anchor" = "/tmp/run" ] || return 1
|
||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
||||
;;
|
||||
/var|/var/*)
|
||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
||||
case "$anchor" in
|
||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
;;
|
||||
*) printf '%s\n' "$path" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_safe_upload_dir() {
|
||||
local path physical
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
physical="$(managed_dir_path upload "$path")" || return 1
|
||||
is_supported_upload_path "$path" || return 1
|
||||
is_supported_upload_path "$physical"
|
||||
}
|
||||
|
||||
is_path_in_dir() {
|
||||
local path root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
root="$(normalize_path "$2")" || return 1
|
||||
|
||||
[ "$root" != "/" ] || return 1
|
||||
[ "$path" = "$root" ] && return 0
|
||||
[ "${path#"$root"/}" != "$path" ]
|
||||
}
|
||||
|
||||
path_has_mount() {
|
||||
local root line mount_path
|
||||
|
||||
root="$(normalize_path "$1")" || return 2
|
||||
[ -r /proc/self/mountinfo ] || return 2
|
||||
|
||||
while IFS= read -r line; do
|
||||
mount_path="$(printf '%s\n' "$line" | cut -d ' ' -f 5)" || return 2
|
||||
[ -n "$mount_path" ] || return 2
|
||||
mount_path="$(printf '%b\n' "$mount_path" 2>/dev/null)" || return 2
|
||||
mount_path="$(normalize_path "$mount_path")" || return 2
|
||||
is_path_in_dir "$mount_path" "$root" && return 0
|
||||
done < /proc/self/mountinfo
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_safe_db_dir() {
|
||||
local path upload_root physical physical_upload_root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||
|
||||
case "$path" in
|
||||
/etc/gecoosac|/etc/gecoosac/*|/tmp/gecoosac|/tmp/gecoosac/*|/var/lib/gecoosac|/var/lib/gecoosac/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
is_path_in_dir "$path" "$upload_root" && return 1
|
||||
physical="$(managed_dir_path db "$path")" || return 1
|
||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
is_safe_pid_dir() {
|
||||
local path upload_root physical physical_upload_root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||
|
||||
case "$path" in
|
||||
/var/run|/var/run/*|/tmp/gecoosac|/tmp/gecoosac/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
is_path_in_dir "$path" "$upload_root" && return 1
|
||||
physical="$(managed_dir_path pid "$path")" || return 1
|
||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
normalize_upload_dir() {
|
||||
local upload_dir normalized
|
||||
|
||||
@@ -229,38 +40,45 @@ normalize_upload_dir() {
|
||||
}
|
||||
|
||||
configured_path_in_legacy_upload() {
|
||||
local option path resolved
|
||||
local physical="$1" option role path
|
||||
|
||||
for option in db_dir piddir crt_file key_file; do
|
||||
path="$(uci -q get "gecoosac.config.${option}")"
|
||||
[ -n "$path" ] || continue
|
||||
path="$(normalize_path "$path")" || return 0
|
||||
is_path_in_dir "$path" "$LEGACY_UPLOAD_DIR" && return 0
|
||||
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 0
|
||||
resolved="$(normalize_path "$resolved")" || return 0
|
||||
is_path_in_dir "$resolved" "$LEGACY_UPLOAD_DIR" && return 0
|
||||
fi
|
||||
case "$option" in
|
||||
db_dir) role=db ;;
|
||||
piddir) role=pid ;;
|
||||
*) role="file" ;;
|
||||
esac
|
||||
protected_path_in_dir "$path" "$role" "$physical" "$LEGACY_UPLOAD_DIR" "$physical"
|
||||
case "$?" in
|
||||
1) ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup_legacy_upload_dir() {
|
||||
local mount_state
|
||||
local physical mount_state
|
||||
|
||||
[ -e "$LEGACY_UPLOAD_DIR" ] || [ -L "$LEGACY_UPLOAD_DIR" ] || return 0
|
||||
if [ ! -d "$LEGACY_UPLOAD_DIR" ] || [ -L "$LEGACY_UPLOAD_DIR" ]; then
|
||||
logger -t gecoosac "refusing to remove unsafe legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||
return 0
|
||||
fi
|
||||
if configured_path_in_legacy_upload; then
|
||||
physical="$(readlink -f "$LEGACY_UPLOAD_DIR" 2>/dev/null)" || {
|
||||
logger -t gecoosac "unable to resolve legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||
return 0
|
||||
}
|
||||
physical="$(normalize_path "$physical")" || return 0
|
||||
if configured_path_in_legacy_upload "$physical"; then
|
||||
logger -t gecoosac "preserving legacy upload path referenced by configuration: $LEGACY_UPLOAD_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
path_has_mount "$LEGACY_UPLOAD_DIR"
|
||||
path_has_mount "$physical"
|
||||
mount_state="$?"
|
||||
case "$mount_state" in
|
||||
0)
|
||||
@@ -273,7 +91,13 @@ cleanup_legacy_upload_dir() {
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -rf "$LEGACY_UPLOAD_DIR" || {
|
||||
# Apply the same ownership and ancestor-symlink policy as RPC cleanup.
|
||||
# Relocated legacy directories are preserved for manual review.
|
||||
if ! is_secure_upload_dir "$LEGACY_UPLOAD_DIR" || [ "$physical" != "$LEGACY_UPLOAD_DIR" ]; then
|
||||
logger -t gecoosac "refusing to remove unsafe legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||
return 0
|
||||
fi
|
||||
rm -rf "$physical" || {
|
||||
logger -t gecoosac "failed to remove legacy upload path: $LEGACY_UPLOAD_DIR"
|
||||
return 1
|
||||
}
|
||||
@@ -310,7 +134,7 @@ is_managed_cert_path() {
|
||||
|
||||
is_regular_file_or_absent() {
|
||||
[ ! -e "$1" ] && [ ! -L "$1" ] && return 0
|
||||
[ -f "$1" ] && [ ! -L "$1" ]
|
||||
[ -f "$1" ] && [ ! -L "$1" ] && is_secure_upload_dir "${1%/*}"
|
||||
}
|
||||
|
||||
migrate_default_certificates() {
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
# Shared path policy for the service, configuration migration and LuCI RPC.
|
||||
|
||||
DEFAULT_DB_DIR=/etc/gecoosac
|
||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
||||
DEFAULT_PID_DIR=/var/run
|
||||
|
||||
is_abs_path() {
|
||||
case "$1" in
|
||||
/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
normalize_path() {
|
||||
local path="$1"
|
||||
local part normalized parent
|
||||
|
||||
is_abs_path "$path" || return 1
|
||||
normalized="/"
|
||||
path="${path#/}"
|
||||
|
||||
while [ -n "$path" ]; do
|
||||
part="${path%%/*}"
|
||||
if [ "$part" = "$path" ]; then
|
||||
path=""
|
||||
else
|
||||
path="${path#*/}"
|
||||
fi
|
||||
|
||||
case "$part" in
|
||||
""|.) ;;
|
||||
..)
|
||||
if [ "$normalized" != "/" ]; then
|
||||
parent="${normalized%/*}"
|
||||
[ -n "$parent" ] || parent="/"
|
||||
normalized="$parent"
|
||||
fi
|
||||
;;
|
||||
*) normalized="${normalized%/}/$part" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
printf '%s\n' "$normalized"
|
||||
}
|
||||
|
||||
managed_dir_path() {
|
||||
local role="$1" path anchor
|
||||
|
||||
case "$role" in
|
||||
upload|db|pid|file|tls) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
path="$(normalize_path "$2")" || return 1
|
||||
case "$path" in
|
||||
/var/run|/var/run/*)
|
||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
||||
[ "$anchor" = "/tmp/run" ] || return 1
|
||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
||||
;;
|
||||
/var|/var/*)
|
||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
||||
case "$anchor" in
|
||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
;;
|
||||
*) printf '%s\n' "$path" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_safe_upload_dir() {
|
||||
local path physical
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
physical="$(managed_dir_path upload "$path")" || return 1
|
||||
is_supported_upload_path "$path" || return 1
|
||||
is_supported_upload_path "$physical"
|
||||
}
|
||||
|
||||
is_path_in_dir() {
|
||||
local path root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
root="$(normalize_path "$2")" || return 1
|
||||
|
||||
[ "$root" != "/" ] || return 1
|
||||
[ "$path" = "$root" ] && return 0
|
||||
[ "${path#"$root"/}" != "$path" ]
|
||||
}
|
||||
|
||||
is_secure_dir() {
|
||||
local allow_sticky="$2" owner permissions metadata
|
||||
|
||||
[ -d "$1" ] && [ ! -L "$1" ] || return 1
|
||||
metadata="$(ls -ldn "$1" 2>/dev/null)" || return 1
|
||||
set -- $metadata
|
||||
permissions="$1"
|
||||
owner="$3"
|
||||
[ "$owner" = "0" ] || return 1
|
||||
case "$permissions" in
|
||||
d?????????) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
|
||||
if [ "$(printf '%s' "$permissions" | cut -c6)" = "w" ] || \
|
||||
[ "$(printf '%s' "$permissions" | cut -c9)" = "w" ]; then
|
||||
[ "$allow_sticky" = "1" ] && [ "$(printf '%s' "$permissions" | cut -c10)" = "t" ] || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
is_secure_upload_dir() {
|
||||
local path current part rest
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
[ -d "$path" ] && [ ! -L "$path" ] || return 1
|
||||
current="/"
|
||||
rest="${path#/}"
|
||||
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
current="${current%/}/$part"
|
||||
case "$current" in
|
||||
/tmp) is_secure_dir "$current" 1 || return 1 ;;
|
||||
*) is_secure_dir "$current" || return 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
is_safe_db_dir() {
|
||||
local path upload_root physical physical_upload_root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||
|
||||
case "$path" in
|
||||
/etc/gecoosac|/etc/gecoosac/*|/tmp/gecoosac|/tmp/gecoosac/*|/var/lib/gecoosac|/var/lib/gecoosac/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
is_path_in_dir "$path" "$upload_root" && return 1
|
||||
physical="$(managed_dir_path db "$path")" || return 1
|
||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
is_safe_pid_dir() {
|
||||
local path upload_root physical physical_upload_root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
upload_root="$(normalize_path "${2:-$DEFAULT_UPLOAD_DIR}")" || return 1
|
||||
|
||||
case "$path" in
|
||||
/var/run|/var/run/*|/tmp/gecoosac|/tmp/gecoosac/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
is_path_in_dir "$path" "$upload_root" && return 1
|
||||
physical="$(managed_dir_path pid "$path")" || return 1
|
||||
physical_upload_root="$(managed_dir_path upload "$upload_root")" || return 1
|
||||
is_path_in_dir "$physical" "$physical_upload_root" && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
path_has_mount() {
|
||||
local root mount_id parent_id device mount_root mount_path rest
|
||||
|
||||
root="$(normalize_path "$1")" || return 2
|
||||
[ -r /proc/self/mountinfo ] || return 2
|
||||
|
||||
while IFS=' ' read -r mount_id parent_id device mount_root mount_path rest; do
|
||||
[ -n "$mount_path" ] && [ -n "$rest" ] || return 2
|
||||
mount_path="$(printf '%b\n' "$mount_path" 2>/dev/null)" || return 2
|
||||
mount_path="$(normalize_path "$mount_path")" || return 2
|
||||
is_path_in_dir "$mount_path" "$root" && return 0
|
||||
done < /proc/self/mountinfo
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# The checked root must be a resolved, validated directory. Return 0 for a
|
||||
# protected path, 1 for an unrelated path, or 2 when validation is impossible.
|
||||
# Keep the original spelling when resolving symlinks: link/../file and its
|
||||
# lexical normalization can refer to different files.
|
||||
protected_path_in_dir() {
|
||||
local raw_path="$1" role="$2" checked_root="$3"
|
||||
local live_logical="${4:-$3}" live_physical="${5:-$3}"
|
||||
local path="$1" physical real_path suffix mapped
|
||||
|
||||
[ -n "$path" ] || return 1
|
||||
|
||||
path="$(normalize_path "$path")" || return 2
|
||||
physical="$(managed_dir_path "$role" "$path")" || return 2
|
||||
checked_root="$(normalize_path "$checked_root")" || return 2
|
||||
live_logical="$(normalize_path "$live_logical")" || return 2
|
||||
live_physical="$(normalize_path "$live_physical")" || return 2
|
||||
|
||||
is_path_in_dir "$path" "$checked_root" && return 0
|
||||
is_path_in_dir "$physical" "$checked_root" && return 0
|
||||
real_path="$(readlink -f "$raw_path" 2>/dev/null)" || return 2
|
||||
[ -n "$real_path" ] || return 2
|
||||
real_path="$(normalize_path "$real_path")" || return 2
|
||||
is_path_in_dir "$real_path" "$checked_root" && return 0
|
||||
|
||||
[ "$checked_root" != "$live_physical" ] || {
|
||||
is_path_in_dir "$path" "$live_logical" && return 0
|
||||
is_path_in_dir "$physical" "$live_physical" && return 0
|
||||
if [ -n "$real_path" ] && is_path_in_dir "$real_path" "$live_physical"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
suffix=
|
||||
if is_path_in_dir "$path" "$live_logical"; then
|
||||
suffix="${path#"$live_logical"}"
|
||||
elif is_path_in_dir "$physical" "$live_physical"; then
|
||||
suffix="${physical#"$live_physical"}"
|
||||
elif [ -n "$real_path" ] && is_path_in_dir "$real_path" "$live_physical"; then
|
||||
suffix="${real_path#"$live_physical"}"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
mapped="${checked_root%/}${suffix}"
|
||||
[ -e "$mapped" ] || [ -L "$mapped" ]
|
||||
}
|
||||
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.41
|
||||
PKG_RELEASE:=13
|
||||
PKG_RELEASE:=14
|
||||
|
||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||
ifdef SOURCE_DATE_EPOCH
|
||||
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2025-2026 Lucas Albers <lucas.b.albers@gmail.com>
|
||||
#
|
||||
# Layer 1 adaptive poll cap (#306). Sourced by rpcd/fwlive.
|
||||
# Always on unless test/triage override (no UCI / no product config):
|
||||
# FWLIVE_ADAPTIVE=0|false|off|no
|
||||
# or sentinel ${FWLIVE_ADAPTIVE_OFF_FILE:-<state-dir>/fwlive-adaptive-off}
|
||||
# (default under /var/run next to state — not world-writable /tmp).
|
||||
#
|
||||
# State: ${FWLIVE_ADAPTIVE_STATE_FILE:-/var/run/fwlive-state.json}
|
||||
# Lock: sibling ${FWLIVE_ADAPTIVE_LOCK_FILE:-$STATE.lock} — never the JSON
|
||||
# inode (atomic mv replaces that inode; locking it would not serialize writers).
|
||||
# Lock covers only short update — never held across ubus/filter work.
|
||||
# Hot-path budget: ≤1 flock exec per update (release by closing the fd when the
|
||||
# subshell exits — no flock -u). /proc/uptime + state I/O via shell builtins/
|
||||
# redirects (no sed/cat/jsonfilter on the adaptive path). Fail-open on missing
|
||||
# flock, lock busy, or corrupt state. Lock-busy ⇒ unlocked last-writer-wins is
|
||||
# acceptable (state stays one valid JSON line; ordering is not guaranteed).
|
||||
# Failed ubus log.read must NOT call record() — a ~0 ms failure is not "cold"
|
||||
# health and must not clear an existing hot/shed cap (#329 Hermes Q1).
|
||||
# Outside the measured duration interval: plan (pre), record/merge (post).
|
||||
# messages_received is 0 in Layer 1 — do not ash-scan the filter JSON after
|
||||
# end_cs (Grok #329 P1); accurate count belongs in the filter or a later layer.
|
||||
|
||||
FWLIVE_ADAPTIVE_STATE_FILE="${FWLIVE_ADAPTIVE_STATE_FILE:-/var/run/fwlive-state.json}"
|
||||
# Optional overrides; when unset, lock/off paths are siblings of the current state.
|
||||
# Do not bake STATE into LOCK/OFF at source time — tests may relocate STATE after source.
|
||||
|
||||
# Bucket thresholds (processing duration, ms).
|
||||
FWLIVE_ADAPTIVE_COLD_MS=100
|
||||
FWLIVE_ADAPTIVE_COOL_MS=200
|
||||
FWLIVE_ADAPTIVE_WARM_MS=800
|
||||
FWLIVE_ADAPTIVE_HOT_EXIT_MS=500
|
||||
FWLIVE_ADAPTIVE_COOL_CAP=250
|
||||
FWLIVE_ADAPTIVE_HOT_FLOOR=250
|
||||
FWLIVE_ADAPTIVE_WARM_MIN=50
|
||||
FWLIVE_ADAPTIVE_COOLDOWN_WARM_CS=300
|
||||
FWLIVE_ADAPTIVE_COOLDOWN_HOT_CS=600
|
||||
|
||||
fwlive_adaptive_lock_path() {
|
||||
if [ -n "${FWLIVE_ADAPTIVE_LOCK_FILE:-}" ]; then
|
||||
printf '%s\n' "$FWLIVE_ADAPTIVE_LOCK_FILE"
|
||||
else
|
||||
printf '%s\n' "${FWLIVE_ADAPTIVE_STATE_FILE}.lock"
|
||||
fi
|
||||
}
|
||||
|
||||
fwlive_adaptive_off_path() {
|
||||
if [ -n "${FWLIVE_ADAPTIVE_OFF_FILE:-}" ]; then
|
||||
printf '%s\n' "$FWLIVE_ADAPTIVE_OFF_FILE"
|
||||
else
|
||||
_dir=${FWLIVE_ADAPTIVE_STATE_FILE%/*}
|
||||
[ "$_dir" = "$FWLIVE_ADAPTIVE_STATE_FILE" ] && _dir=/var/run
|
||||
printf '%s\n' "$_dir/fwlive-adaptive-off"
|
||||
fi
|
||||
}
|
||||
|
||||
fwlive_adaptive_enabled() {
|
||||
case "${FWLIVE_ADAPTIVE:-1}" in
|
||||
0|false|off|no|FALSE|OFF|NO) return 1 ;;
|
||||
esac
|
||||
[ -e "$(fwlive_adaptive_off_path)" ] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
fwlive_adaptive_atoi() {
|
||||
# Decimal int from digit string — avoids $((08)) octal errors under ash.
|
||||
_s=$1
|
||||
_n=0
|
||||
case "$_s" in ''|*[!0-9]*) printf '0\n'; return 0 ;; esac
|
||||
while [ -n "$_s" ]; do
|
||||
_d=${_s%"${_s#?}"}
|
||||
_s=${_s#?}
|
||||
_n=$((_n * 10 + _d))
|
||||
done
|
||||
printf '%s\n' "$_n"
|
||||
}
|
||||
|
||||
fwlive_adaptive_clock_cs() {
|
||||
# /proc/uptime centiseconds — BusyBox date lacks %N. Builtin read only.
|
||||
_up=
|
||||
read -r _up _ </proc/uptime 2>/dev/null || {
|
||||
printf '%s\n' 0
|
||||
return 0
|
||||
}
|
||||
_sec=${_up%.*}
|
||||
_frac=${_up#*.}
|
||||
[ "$_frac" = "$_up" ] && _frac=0
|
||||
# At most two decimal digits (centiseconds), no cut(1).
|
||||
case "$_frac" in
|
||||
'') _frac=0 ;;
|
||||
?) _frac="${_frac}0" ;;
|
||||
??) ;;
|
||||
*)
|
||||
_a=${_frac%${_frac#?}}
|
||||
_r=${_frac#?}
|
||||
_b=${_r%${_r#?}}
|
||||
_frac="${_a}${_b}"
|
||||
;;
|
||||
esac
|
||||
_sec=$(fwlive_adaptive_atoi "$_sec")
|
||||
_frac=$(fwlive_adaptive_atoi "$_frac")
|
||||
printf '%s\n' $((_sec * 100 + _frac))
|
||||
}
|
||||
|
||||
fwlive_adaptive_state_dir_ok() {
|
||||
_path=$FWLIVE_ADAPTIVE_STATE_FILE
|
||||
_dir=${_path%/*}
|
||||
[ -n "$_dir" ] || return 1
|
||||
[ "$_dir" = "$_path" ] && return 1
|
||||
[ -L "$_dir" ] && return 1
|
||||
[ -d "$_dir" ] || return 1
|
||||
# shellcheck disable=SC3067
|
||||
[ -O "$_dir" ] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Extract a decimal integer field from a one-line JSON object (builtin only).
|
||||
# Prints default ($2) when missing/corrupt.
|
||||
fwlive_adaptive_json_int() {
|
||||
_blob=$1
|
||||
_key=$2
|
||||
_def=$3
|
||||
case "$_blob" in
|
||||
*"\"$_key\""*) ;;
|
||||
*) printf '%s\n' "$_def"; return 0 ;;
|
||||
esac
|
||||
_rest=${_blob#*"\"$_key\""}
|
||||
_rest=${_rest#*:}
|
||||
while case "$_rest" in ' '*) true;; *) false;; esac; do
|
||||
_rest=${_rest# }
|
||||
done
|
||||
_num=
|
||||
while :; do
|
||||
case "$_rest" in
|
||||
'') break ;;
|
||||
esac
|
||||
_c=${_rest%"${_rest#?}"}
|
||||
case "$_c" in
|
||||
[0-9]) _num="${_num}${_c}"; _rest=${_rest#?} ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
case "$_num" in
|
||||
''|*[!0-9]*) printf '%s\n' "$_def" ;;
|
||||
*) printf '%s\n' "$_num" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Extract a lowercase alpha string field.
|
||||
fwlive_adaptive_json_str() {
|
||||
_blob=$1
|
||||
_key=$2
|
||||
_def=$3
|
||||
case "$_blob" in
|
||||
*"\"$_key\""*) ;;
|
||||
*) printf '%s\n' "$_def"; return 0 ;;
|
||||
esac
|
||||
_rest=${_blob#*"\"$_key\""}
|
||||
_rest=${_rest#*:}
|
||||
while case "$_rest" in ' '*) true;; *) false;; esac; do
|
||||
_rest=${_rest# }
|
||||
done
|
||||
case "$_rest" in
|
||||
\"*) _rest=${_rest#\"}; _val=${_rest%%\"*} ;;
|
||||
*) printf '%s\n' "$_def"; return 0 ;;
|
||||
esac
|
||||
case "$_val" in
|
||||
*[!a-z]*|'') printf '%s\n' "$_def" ;;
|
||||
*) printf '%s\n' "$_val" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Print: duration_ms limit bucket warm_halved shed completed_cs
|
||||
fwlive_adaptive_read_state() {
|
||||
_d=0
|
||||
_l="${POLL_LINES_MAX:-2000}"
|
||||
_b=cold
|
||||
_w=0
|
||||
_s=0
|
||||
_c=0
|
||||
_raw=
|
||||
if [ -f "$FWLIVE_ADAPTIVE_STATE_FILE" ]; then
|
||||
IFS= read -r _raw <"$FWLIVE_ADAPTIVE_STATE_FILE" || _raw=
|
||||
fi
|
||||
if [ -n "$_raw" ]; then
|
||||
_d=$(fwlive_adaptive_json_int "$_raw" duration_ms 0)
|
||||
_l=$(fwlive_adaptive_json_int "$_raw" limit "${POLL_LINES_MAX:-2000}")
|
||||
_b=$(fwlive_adaptive_json_str "$_raw" bucket cold)
|
||||
_w=$(fwlive_adaptive_json_int "$_raw" warm_halved 0)
|
||||
_s=$(fwlive_adaptive_json_int "$_raw" shed 0)
|
||||
_c=$(fwlive_adaptive_json_int "$_raw" completed_cs 0)
|
||||
case "$_w" in 0|1) ;; *) _w=0 ;; esac
|
||||
case "$_s" in 0|1) ;; *) _s=0 ;; esac
|
||||
fi
|
||||
printf '%s %s %s %s %s %s\n' "$_d" "$_l" "$_b" "$_w" "$_s" "$_c"
|
||||
}
|
||||
|
||||
fwlive_adaptive_write_state() {
|
||||
_d=$1
|
||||
_l=$2
|
||||
_b=$3
|
||||
_w=$4
|
||||
_s=$5
|
||||
_c=$6
|
||||
fwlive_adaptive_state_dir_ok || return 0
|
||||
# Refuse to follow a symlinked state path (same discipline as logging.lock).
|
||||
[ -L "$FWLIVE_ADAPTIVE_STATE_FILE" ] && return 0
|
||||
_tmp="${FWLIVE_ADAPTIVE_STATE_FILE}.tmp.$$"
|
||||
umask 077
|
||||
printf '{"duration_ms":%s,"limit":%s,"bucket":"%s","warm_halved":%s,"shed":%s,"completed_cs":%s}\n' \
|
||||
"$_d" "$_l" "$_b" "$_w" "$_s" "$_c" >"$_tmp" 2>/dev/null || {
|
||||
rm -f "$_tmp"
|
||||
return 0
|
||||
}
|
||||
mv -f "$_tmp" "$FWLIVE_ADAPTIVE_STATE_FILE" 2>/dev/null || rm -f "$_tmp"
|
||||
return 0
|
||||
}
|
||||
|
||||
# One non-blocking flock on the persistent sibling lock file; fail-open if
|
||||
# missing/busy. Release by exiting the subshell (closes fd 9) — no flock -u.
|
||||
# Busy ⇒ run unlocked: last-writer-wins is acceptable (valid one-line JSON;
|
||||
# ordering under contention is not guaranteed).
|
||||
# Lock-open failure (redirection) also fail-opens unlocked so record() still runs.
|
||||
fwlive_adaptive_with_lock() {
|
||||
if ! command -v flock >/dev/null 2>&1; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
fwlive_adaptive_state_dir_ok || {
|
||||
"$@"
|
||||
return $?
|
||||
}
|
||||
_lock=$(fwlive_adaptive_lock_path)
|
||||
# Symlinked or non-file lock path: do not create/follow; fail open unlocked.
|
||||
if [ -L "$_lock" ] || [ -d "$_lock" ]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
# Create lock at 0600 (logging.lock #167 — world-readable fd can take LOCK_EX).
|
||||
if [ ! -e "$_lock" ]; then
|
||||
if ! ( umask 077; : >"$_lock" ) 2>/dev/null; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
elif [ ! -w "$_lock" ]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
(
|
||||
if flock -n 9; then
|
||||
"$@"
|
||||
exit $?
|
||||
fi
|
||||
# Lock busy — fail open (unlocked).
|
||||
"$@"
|
||||
) 9>>"$_lock"
|
||||
return $?
|
||||
}
|
||||
|
||||
fwlive_adaptive_bucket_for_ms() {
|
||||
_ms=$1
|
||||
case "$_ms" in ''|*[!0-9]*) _ms=0 ;; esac
|
||||
if [ "$_ms" -lt "$FWLIVE_ADAPTIVE_COLD_MS" ]; then
|
||||
printf '%s\n' cold
|
||||
elif [ "$_ms" -lt "$FWLIVE_ADAPTIVE_COOL_MS" ]; then
|
||||
printf '%s\n' cool
|
||||
elif [ "$_ms" -le "$FWLIVE_ADAPTIVE_WARM_MS" ]; then
|
||||
printf '%s\n' warm
|
||||
else
|
||||
printf '%s\n' hot
|
||||
fi
|
||||
}
|
||||
|
||||
# Args: requested_limit duration_ms prev_limit prev_bucket prev_warm prev_completed_cs
|
||||
# Prints next limit.
|
||||
fwlive_adaptive_compute_limit() {
|
||||
_req=$1
|
||||
_ms=$2
|
||||
_prev_l=$3
|
||||
_prev_b=$4
|
||||
_prev_w=$5
|
||||
_prev_c=$6
|
||||
_max="${POLL_LINES_MAX:-2000}"
|
||||
_now=$(fwlive_adaptive_clock_cs)
|
||||
_bucket=$(fwlive_adaptive_bucket_for_ms "$_ms")
|
||||
_limit=$_max
|
||||
_cd=0
|
||||
|
||||
case "$_prev_b" in
|
||||
hot) _cd=$FWLIVE_ADAPTIVE_COOLDOWN_HOT_CS ;;
|
||||
warm) _cd=$FWLIVE_ADAPTIVE_COOLDOWN_WARM_CS ;;
|
||||
esac
|
||||
|
||||
if [ "$_cd" -gt 0 ] && [ "$_prev_c" -gt 0 ] && \
|
||||
[ "$((_now - _prev_c))" -lt "$_cd" ]; then
|
||||
if [ "$_ms" -le "$FWLIVE_ADAPTIVE_HOT_EXIT_MS" ] && \
|
||||
[ "$_bucket" != hot ]; then
|
||||
:
|
||||
else
|
||||
_limit=$_prev_l
|
||||
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
printf '%s\n' "$_limit"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$_bucket" in
|
||||
cold) _limit=$_max ;;
|
||||
cool) _limit=$FWLIVE_ADAPTIVE_COOL_CAP ;;
|
||||
warm)
|
||||
if [ "$_prev_w" = 1 ] && [ "$_prev_b" = warm ]; then
|
||||
_limit=$_prev_l
|
||||
else
|
||||
_base=$_prev_l
|
||||
[ "$_base" -lt 1 ] && _base=$_max
|
||||
_limit=$((_base / 2))
|
||||
[ "$_limit" -lt "$FWLIVE_ADAPTIVE_WARM_MIN" ] && \
|
||||
_limit=$FWLIVE_ADAPTIVE_WARM_MIN
|
||||
fi
|
||||
;;
|
||||
hot) _limit=$FWLIVE_ADAPTIVE_HOT_FLOOR ;;
|
||||
esac
|
||||
|
||||
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
printf '%s\n' "$_limit"
|
||||
}
|
||||
|
||||
# Apply cap using *previous* poll duration. Prints: lines shed_flag bucket
|
||||
# Unlocked read (stale OK) — flock reserved for record().
|
||||
fwlive_adaptive_plan() {
|
||||
_req=$1
|
||||
_max="${POLL_LINES_MAX:-2000}"
|
||||
if ! fwlive_adaptive_enabled; then
|
||||
_l=$_req
|
||||
[ "$_l" -gt "$_max" ] && _l=$_max
|
||||
printf '%s 0 off\n' "$_l"
|
||||
return 0
|
||||
fi
|
||||
# shellcheck disable=SC2046
|
||||
set -- $(fwlive_adaptive_read_state)
|
||||
_d=$1
|
||||
_prev_l=$2
|
||||
_b=$3
|
||||
_prev_w=$4
|
||||
_s=$5
|
||||
_prev_c=$6
|
||||
_l=$(fwlive_adaptive_compute_limit "$_req" "$_d" "$_prev_l" "$_b" "$_prev_w" "$_prev_c")
|
||||
_nb=$(fwlive_adaptive_bucket_for_ms "$_d")
|
||||
_shed=0
|
||||
[ "$_nb" = hot ] && _shed=1
|
||||
[ "$_s" = 1 ] && [ "$_b" = hot ] && _shed=1
|
||||
printf '%s %s %s\n' "$_l" "$_shed" "$_nb"
|
||||
}
|
||||
|
||||
fwlive_adaptive__record_body() {
|
||||
_ms=$1
|
||||
_served=$2
|
||||
# shellcheck disable=SC2046
|
||||
set -- $(fwlive_adaptive_read_state)
|
||||
_prev_l=$2
|
||||
_prev_b=$3
|
||||
_prev_w=$4
|
||||
_prev_c=$6
|
||||
_bucket=$(fwlive_adaptive_bucket_for_ms "$_ms")
|
||||
_now=$(fwlive_adaptive_clock_cs)
|
||||
_limit=$(fwlive_adaptive_compute_limit "$_served" "$_ms" "$_prev_l" "$_prev_b" "$_prev_w" "$_prev_c")
|
||||
_warm=0
|
||||
_shed=0
|
||||
case "$_bucket" in
|
||||
warm) _warm=1 ;;
|
||||
hot) _shed=1 ;;
|
||||
esac
|
||||
fwlive_adaptive_write_state "$_ms" "$_limit" "$_bucket" "$_warm" "$_shed" "$_now"
|
||||
}
|
||||
|
||||
fwlive_adaptive_record() {
|
||||
_ms=$1
|
||||
_served=$2
|
||||
fwlive_adaptive_enabled || return 0
|
||||
fwlive_adaptive_with_lock fwlive_adaptive__record_body "$_ms" "$_served"
|
||||
}
|
||||
|
||||
fwlive_adaptive_is_hot() {
|
||||
fwlive_adaptive_enabled || return 1
|
||||
# shellcheck disable=SC2046
|
||||
set -- $(fwlive_adaptive_read_state)
|
||||
[ "$5" = 1 ] && return 0
|
||||
[ "$3" = hot ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Merge adaptive siblings into a JSON object ending with }.
|
||||
# Args: json_body shed_flag limit truncated messages_received
|
||||
fwlive_adaptive_merge_reply() {
|
||||
_body=$1
|
||||
_shed=$2
|
||||
_limit=$3
|
||||
_trunc=$4
|
||||
_msgs=$5
|
||||
_adapt=1
|
||||
fwlive_adaptive_enabled || _adapt=0
|
||||
case "$_body" in
|
||||
*\}) ;;
|
||||
*) printf '%s' "$_body"; return 0 ;;
|
||||
esac
|
||||
_base=${_body%\}}
|
||||
if [ "$_adapt" = 0 ]; then
|
||||
printf '%s,"adaptive":0,"messages_received":%s}' "$_base" "$_msgs"
|
||||
return 0
|
||||
fi
|
||||
if [ "$_shed" = 1 ]; then
|
||||
printf '%s,"adaptive":1,"messages_received":%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \
|
||||
"$_base" "$_msgs" "$_trunc" "$_limit"
|
||||
else
|
||||
printf '%s,"adaptive":1,"messages_received":%s,"truncated":%s}' \
|
||||
"$_base" "$_msgs" "$_trunc"
|
||||
fi
|
||||
}
|
||||
|
||||
# Count log[*] via "msg" keys — helper for tests / future filter-side count.
|
||||
# NOT called on the poll hot path (Grok #329 P1): ash-scanning a 2000-entry
|
||||
# reply after end_cs is unmeasured overhead that cannot shed itself.
|
||||
fwlive_adaptive_count_log() {
|
||||
_json=$1
|
||||
_n=0
|
||||
_rest=$_json
|
||||
while :; do
|
||||
case "$_rest" in
|
||||
*'"msg"'*)
|
||||
_n=$((_n + 1))
|
||||
_rest=${_rest#*\"msg\"}
|
||||
;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
printf '%s\n' "$_n"
|
||||
}
|
||||
@@ -18,6 +18,7 @@ FW4_TAG='!fw4: '
|
||||
LIBEXEC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
FILTER_SH="$LIBEXEC_DIR/fwlive-log-filter.sh"
|
||||
LOGGING_SH="$LIBEXEC_DIR/fwlive-logging.sh"
|
||||
ADAPTIVE_SH="$LIBEXEC_DIR/fwlive-adaptive-cap.sh"
|
||||
# Max reverse-DNS lookups per resolve call (~32 names keeps nslookup load bounded on small routers).
|
||||
RESOLVE_MAX=32
|
||||
# Per-lookup nslookup timeout (integer seconds — BusyBox `timeout` truncates
|
||||
@@ -44,6 +45,8 @@ RULESMAP_IPTABLES_FILE='/tmp/rulesmap'
|
||||
# json_escape lives in fwlive-logging.sh so prerm can source that file alone (#222).
|
||||
# shellcheck disable=SC1090,SC1091 # LOGGING_SH resolves to sibling ../fwlive-logging.sh
|
||||
. "$LOGGING_SH"
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
. "$ADAPTIVE_SH"
|
||||
|
||||
slug_key() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' _' '--'
|
||||
@@ -425,12 +428,19 @@ poll_lines_from_input() {
|
||||
|
||||
fetch_firewall_logs() {
|
||||
input="$1"
|
||||
lines=$(poll_lines_from_input "$input")
|
||||
# lines is numeric-clamped; stream/oneshot are fixed (not caller-controlled).
|
||||
# A dead logd must not look like an empty firewall table (#303): return a
|
||||
# structured error instead of filtering an empty fallback into {"log":[]}.
|
||||
if ! raw=$(ubus call log read "{\"lines\":$lines,\"stream\":false,\"oneshot\":true}" 2>/dev/null); then
|
||||
printf '%s' '{"log":[],"error":"log_read_failed"}'
|
||||
requested=$(poll_lines_from_input "$input")
|
||||
# shellcheck disable=SC2046
|
||||
set -- $(fwlive_adaptive_plan "$requested")
|
||||
lines=$1
|
||||
shed_flag=${2:-0}
|
||||
# Adaptive plan prints: lines shed bucket
|
||||
start_cs=$(fwlive_adaptive_clock_cs)
|
||||
# Drop dead oneshot flag — only consumed on logd's stream branch (#306).
|
||||
if ! raw=$(ubus call log read "{\"lines\":$lines,\"stream\":false}" 2>/dev/null); then
|
||||
# Do not record ~0 ms as cold: failure is not healthy fast processing
|
||||
# and must not clear an existing hot/shed cap (#329 Hermes Q1).
|
||||
out='{"log":[],"error":"log_read_failed"}'
|
||||
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" 0 0
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -444,7 +454,17 @@ fetch_firewall_logs() {
|
||||
fi
|
||||
fi
|
||||
[ -n "$out" ] || out='{"log":[],"error":"filter_empty"}'
|
||||
printf '%s' "$out"
|
||||
end_cs=$(fwlive_adaptive_clock_cs)
|
||||
# Uptime wrap / skew: end < start — do not record as cold (Grok #329 P2).
|
||||
if [ "$end_cs" -ge "$start_cs" ]; then
|
||||
_dur=$(((end_cs - start_cs) * 10))
|
||||
fwlive_adaptive_record "$_dur" "$lines"
|
||||
fi
|
||||
# Layer 1: messages_received stays 0 — no post-filter ash count (Grok #329 P1).
|
||||
_trunc=0
|
||||
[ "$shed_flag" = 1 ] && _trunc=1
|
||||
[ "$lines" -lt "$requested" ] && _trunc=1
|
||||
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" "$_trunc" 0
|
||||
}
|
||||
|
||||
poll_logs() {
|
||||
@@ -584,6 +604,12 @@ resolve_addresses() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Load shed: previous poll was hot (#306 Layer 1).
|
||||
if fwlive_adaptive_is_hot; then
|
||||
printf '{"names":{},"disabled":"load"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Wall-clock budget clock starts at the TRUE function entry (luna
|
||||
# folds 2026-08-10): the budget must bound the whole call — including
|
||||
# read_rpc_input (stalled stdin must not escape the budget) and json
|
||||
|
||||
@@ -7,10 +7,10 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-gecoosac
|
||||
PKG_VERSION:=2.2
|
||||
PKG_RELEASE:=20
|
||||
PKG_RELEASE:=21
|
||||
|
||||
LUCI_TITLE:=LuCI Support for gecoosac
|
||||
LUCI_DEPENDS:=+luci-base +gecoosac +gecoosac-files
|
||||
LUCI_DEPENDS:=+luci-base +gecoosac +gecoosac-files +gecoosac-common
|
||||
LUCI_EXTRA_DEPENDS:=gecoosac (>=2.2.20251015)
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
|
||||
@@ -403,6 +403,7 @@ return view.extend({
|
||||
o.default = '8080';
|
||||
o.datatype = 'port';
|
||||
o.depends('isonlyoneprot', '0');
|
||||
o.retain = true;
|
||||
|
||||
portOption.validate = function(section_id, value) {
|
||||
return validatePortValue(section_id, value, managementPortOption, singlePortOption, true);
|
||||
@@ -422,10 +423,11 @@ return view.extend({
|
||||
return true;
|
||||
};
|
||||
httpsOption = s.option(form.Flag, 'https', _('Enable HTTPS service'),
|
||||
_('Default certificate files are generated when HTTPS starts; custom paths must point to a readable certificate and matching key.'));
|
||||
_('When HTTPS starts, default certificates are generated or renewed if fewer than 30 days remain. Custom certificates must be currently valid and match their private keys.'));
|
||||
o = httpsOption;
|
||||
o.default = '0';
|
||||
o.depends('isonlyoneprot', '0');
|
||||
o.retain = true;
|
||||
|
||||
certificateOption = s.option(form.Value, 'crt_file', _('Specify crt certificate file'));
|
||||
o = certificateOption;
|
||||
@@ -433,6 +435,7 @@ return view.extend({
|
||||
o.default = DEFAULT_CRT_FILE;
|
||||
o.datatype = 'file';
|
||||
o.depends({ isonlyoneprot: '0', https: '1' });
|
||||
o.retain = true;
|
||||
o.validate = function(section_id, value) {
|
||||
return validateCertificatePath(section_id, value, singlePortOption, httpsOption);
|
||||
};
|
||||
@@ -443,6 +446,7 @@ return view.extend({
|
||||
o.default = DEFAULT_KEY_FILE;
|
||||
o.datatype = 'file';
|
||||
o.depends({ isonlyoneprot: '0', https: '1' });
|
||||
o.retain = true;
|
||||
o.validate = function(section_id, value) {
|
||||
return validateCertificatePath(section_id, value, singlePortOption, httpsOption);
|
||||
};
|
||||
@@ -481,7 +485,7 @@ return view.extend({
|
||||
};
|
||||
|
||||
o = s.option(form.Value, 'db_dir', _('Database dir path'),
|
||||
_('Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Do not place it inside the upload directory.'));
|
||||
_('Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Only /etc/gecoosac persists across reboots on standard OpenWrt. Do not place it inside the upload directory.'));
|
||||
o.placeholder = DEFAULT_DB_DIR;
|
||||
o.default = DEFAULT_DB_DIR;
|
||||
o.datatype = 'directory';
|
||||
|
||||
@@ -9,8 +9,8 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Default certificate files are generated when HTTPS starts; custom paths must point to a readable certificate and matching key."
|
||||
msgstr "默认证书文件会在 HTTPS 启动时生成;自定义路径必须指向可读取且相互匹配的证书和私钥。"
|
||||
msgid "When HTTPS starts, default certificates are generated or renewed if fewer than 30 days remain. Custom certificates must be currently valid and match their private keys."
|
||||
msgstr "HTTPS 启动时会生成默认证书,或在剩余有效期不足 30 天时更新。自定义证书必须在有效期内且与私钥匹配。"
|
||||
|
||||
msgid "Gecoos AC"
|
||||
msgstr "集客AC控制器"
|
||||
@@ -68,8 +68,8 @@ msgstr "上传目录必须是 /tmp/gecoosac/upload 或 /mnt/storage-name/gecoosa
|
||||
msgid "Database dir path"
|
||||
msgstr "数据库目录"
|
||||
|
||||
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Do not place it inside the upload directory."
|
||||
msgstr "将配置数据库存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。不要放在上传目录内。"
|
||||
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Only /etc/gecoosac persists across reboots on standard OpenWrt. Do not place it inside the upload directory."
|
||||
msgstr "将配置数据库存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。标准 OpenWrt 上只有 /etc/gecoosac 的数据会在重启后保留。不要放在上传目录内。"
|
||||
|
||||
msgid "Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac."
|
||||
msgstr "数据库目录必须位于 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。"
|
||||
|
||||
@@ -9,8 +9,8 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Default certificate files are generated when HTTPS starts; custom paths must point to a readable certificate and matching key."
|
||||
msgstr "預設憑證檔案會在 HTTPS 啟動時產生;自訂路徑必須指向可讀取且相互匹配的憑證與私鑰。"
|
||||
msgid "When HTTPS starts, default certificates are generated or renewed if fewer than 30 days remain. Custom certificates must be currently valid and match their private keys."
|
||||
msgstr "HTTPS 啟動時會產生預設憑證,或在剩餘有效期不足 30 天時更新。自訂憑證必須在有效期內且與私鑰相符。"
|
||||
|
||||
msgid "Gecoos AC"
|
||||
msgstr "集客 AC 控制器"
|
||||
@@ -68,8 +68,8 @@ msgstr "上傳目錄必須是 /tmp/gecoosac/upload 或 /mnt/storage-name/gecoosa
|
||||
msgid "Database dir path"
|
||||
msgstr "資料庫目錄"
|
||||
|
||||
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Do not place it inside the upload directory."
|
||||
msgstr "將設定資料庫存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。不要放在上傳目錄內。"
|
||||
msgid "Store the config database under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac. Only /etc/gecoosac persists across reboots on standard OpenWrt. Do not place it inside the upload directory."
|
||||
msgstr "將設定資料庫存放在 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。標準 OpenWrt 上只有 /etc/gecoosac 的資料會在重新啟動後保留。不要放在上傳目錄內。"
|
||||
|
||||
msgid "Database directory must be under /etc/gecoosac, /tmp/gecoosac, or /var/lib/gecoosac."
|
||||
msgstr "資料庫目錄必須位於 /etc/gecoosac、/tmp/gecoosac 或 /var/lib/gecoosac 下。"
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
. /lib/functions.sh
|
||||
|
||||
DEFAULT_DB_DIR=/etc/gecoosac
|
||||
DEFAULT_UPLOAD_DIR=/tmp/gecoosac/upload
|
||||
DEFAULT_CRT_FILE=/etc/gecoosac/tls/gecoosac.crt
|
||||
DEFAULT_KEY_FILE=/etc/gecoosac/tls/gecoosac.key
|
||||
DEFAULT_PID_DIR=/var/run
|
||||
. /usr/share/gecoosac/common.sh
|
||||
|
||||
json_result() {
|
||||
local ok="$1"
|
||||
@@ -22,116 +17,6 @@ json_result() {
|
||||
json_cleanup
|
||||
}
|
||||
|
||||
normalize_path() {
|
||||
local path="$1"
|
||||
local part normalized parent
|
||||
|
||||
case "$path" in
|
||||
/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
|
||||
normalized="/"
|
||||
path="${path#/}"
|
||||
|
||||
while [ -n "$path" ]; do
|
||||
part="${path%%/*}"
|
||||
if [ "$part" = "$path" ]; then
|
||||
path=""
|
||||
else
|
||||
path="${path#*/}"
|
||||
fi
|
||||
|
||||
case "$part" in
|
||||
""|.) ;;
|
||||
..)
|
||||
if [ "$normalized" != "/" ]; then
|
||||
parent="${normalized%/*}"
|
||||
[ -n "$parent" ] || parent="/"
|
||||
normalized="$parent"
|
||||
fi
|
||||
;;
|
||||
*) normalized="${normalized%/}/$part" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
printf '%s\n' "$normalized"
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
managed_dir_path() {
|
||||
local role="$1" path anchor
|
||||
|
||||
case "$role" in
|
||||
upload|db|pid|file) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
path="$(normalize_path "$2")" || return 1
|
||||
case "$path" in
|
||||
/var/run|/var/run/*)
|
||||
anchor="$(readlink -f /var/run 2>/dev/null)" || return 1
|
||||
[ "$anchor" = "/tmp/run" ] || return 1
|
||||
printf '%s%s\n' "$anchor" "${path#/var/run}"
|
||||
;;
|
||||
/var|/var/*)
|
||||
anchor="$(readlink -f /var 2>/dev/null)" || return 1
|
||||
case "$anchor" in
|
||||
/var|/tmp) printf '%s%s\n' "$anchor" "${path#/var}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
;;
|
||||
*) printf '%s\n' "$path" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
load_clear_config() {
|
||||
local section_type
|
||||
|
||||
@@ -151,135 +36,17 @@ load_clear_config() {
|
||||
config_get clear_key_file config key_file "$DEFAULT_KEY_FILE"
|
||||
}
|
||||
|
||||
safe_upload_path() {
|
||||
local path
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
is_supported_upload_path "$path"
|
||||
}
|
||||
|
||||
path_in_dir() {
|
||||
local path root
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
root="$(normalize_path "$2")" || return 1
|
||||
|
||||
[ "$root" != "/" ] || return 1
|
||||
[ "$path" = "$root" ] && return 0
|
||||
[ "${path#"$root"/}" != "$path" ]
|
||||
}
|
||||
|
||||
path_has_mount() {
|
||||
local root line mount_path
|
||||
|
||||
root="$(normalize_path "$1")" || return 2
|
||||
[ -r /proc/self/mountinfo ] || return 2
|
||||
|
||||
while IFS= read -r line; do
|
||||
mount_path="$(printf '%s\n' "$line" | cut -d ' ' -f 5)" || return 2
|
||||
[ -n "$mount_path" ] || return 2
|
||||
mount_path="$(printf '%b\n' "$mount_path" 2>/dev/null)" || return 2
|
||||
mount_path="$(normalize_path "$mount_path")" || return 2
|
||||
path_in_dir "$mount_path" "$root" && return 0
|
||||
done < /proc/self/mountinfo
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_secure_dir() {
|
||||
local allow_sticky="$2" owner permissions metadata
|
||||
|
||||
[ -d "$1" ] && [ ! -L "$1" ] || return 1
|
||||
metadata="$(ls -ldn "$1" 2>/dev/null)" || return 1
|
||||
set -- $metadata
|
||||
permissions="$1"
|
||||
owner="$3"
|
||||
[ "$owner" = "0" ] || return 1
|
||||
case "$permissions" in
|
||||
d?????????) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
|
||||
if [ "$(printf '%s' "$permissions" | cut -c6)" = "w" ] || \
|
||||
[ "$(printf '%s' "$permissions" | cut -c9)" = "w" ]; then
|
||||
[ "$allow_sticky" = "1" ] && [ "$(printf '%s' "$permissions" | cut -c10)" = "t" ] || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
is_secure_upload_dir() {
|
||||
local path current part rest
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
[ -d "$path" ] && [ ! -L "$path" ] || return 1
|
||||
current="/"
|
||||
rest="${path#/}"
|
||||
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
current="${current%/}/$part"
|
||||
case "$current" in
|
||||
/tmp) is_secure_dir "$current" 1 || return 1 ;;
|
||||
*) is_secure_dir "$current" || return 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
configured_path_in_upload() {
|
||||
local option="$1" checked_root="$2"
|
||||
local live_logical="${3:-$2}" live_physical="${4:-$2}"
|
||||
local role path physical real_path suffix mapped
|
||||
local path role
|
||||
|
||||
case "$option" in
|
||||
case "$1" in
|
||||
db_dir) path="$clear_db_dir"; role=db ;;
|
||||
piddir) path="$clear_piddir"; role=pid ;;
|
||||
crt_file) path="$clear_crt_file"; role="file" ;;
|
||||
key_file) path="$clear_key_file"; role="file" ;;
|
||||
*) return 2 ;;
|
||||
esac
|
||||
[ -n "$path" ] || return 1
|
||||
|
||||
path="$(normalize_path "$path")" || return 2
|
||||
physical="$(managed_dir_path "$role" "$path")" || return 2
|
||||
checked_root="$(normalize_path "$checked_root")" || return 2
|
||||
live_logical="$(normalize_path "$live_logical")" || return 2
|
||||
live_physical="$(normalize_path "$live_physical")" || return 2
|
||||
|
||||
path_in_dir "$path" "$checked_root" && return 0
|
||||
path_in_dir "$physical" "$checked_root" && return 0
|
||||
real_path=
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
real_path="$(readlink -f "$path" 2>/dev/null)" || return 2
|
||||
[ -n "$real_path" ] || return 2
|
||||
real_path="$(normalize_path "$real_path")" || return 2
|
||||
path_in_dir "$real_path" "$checked_root" && return 0
|
||||
fi
|
||||
|
||||
[ "$checked_root" != "$live_physical" ] || {
|
||||
path_in_dir "$path" "$live_logical" && return 0
|
||||
path_in_dir "$physical" "$live_physical" && return 0
|
||||
if [ -n "$real_path" ] && path_in_dir "$real_path" "$live_physical"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
suffix=
|
||||
if path_in_dir "$path" "$live_logical"; then
|
||||
suffix="${path#"$live_logical"}"
|
||||
elif path_in_dir "$physical" "$live_physical"; then
|
||||
suffix="${physical#"$live_physical"}"
|
||||
elif [ -n "$real_path" ] && path_in_dir "$real_path" "$live_physical"; then
|
||||
suffix="${real_path#"$live_physical"}"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
mapped="${checked_root%/}${suffix}"
|
||||
[ -e "$mapped" ] || [ -L "$mapped" ]
|
||||
protected_path_in_dir "$path" "$role" "$2" "${3:-$2}" "${4:-$2}"
|
||||
}
|
||||
|
||||
status_result() {
|
||||
@@ -534,7 +301,7 @@ clear_upload() {
|
||||
esac
|
||||
|
||||
logical="$(normalize_path "$logical")" || { json_result 0 "Expecting an absolute path"; return; }
|
||||
if ! safe_upload_path "$logical"; then
|
||||
if ! is_supported_upload_path "$logical"; then
|
||||
json_result 0 "Only Gecoos upload directories can be cleared" "$logical"
|
||||
return
|
||||
fi
|
||||
@@ -542,7 +309,7 @@ clear_upload() {
|
||||
json_result 0 "Unable to resolve upload directory" "$logical"
|
||||
return
|
||||
}
|
||||
if ! safe_upload_path "$physical"; then
|
||||
if ! is_supported_upload_path "$physical"; then
|
||||
json_result 0 "Only Gecoos upload directories can be cleared" "$logical"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.9.9
|
||||
PKG_RELEASE:=269
|
||||
PKG_RELEASE:=270
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -913,7 +913,7 @@ function gen_config_server(node)
|
||||
stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
|
||||
connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
|
||||
max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
|
||||
disable_path_mtu_discover = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
||||
disable_path_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
||||
tls = tls
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1098,7 +1098,7 @@ add_firewall_rule() {
|
||||
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW"
|
||||
# Only TCP, UDP Invalid.
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT"
|
||||
|
||||
$ipt_m -N PSW_OUTPUT
|
||||
$ipt_m -A PSW_OUTPUT $(dst $IPSET_LAN) -j RETURN
|
||||
@@ -1172,7 +1172,7 @@ add_firewall_rule() {
|
||||
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW"
|
||||
# Only TCP, UDP Invalid.
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT"
|
||||
|
||||
$ip6t_m -N PSW_OUTPUT
|
||||
$ip6t_m -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
||||
@@ -1477,7 +1477,7 @@ gen_include() {
|
||||
[ -z "${_ipt}" ] && return
|
||||
|
||||
echo "*$2"
|
||||
${_ipt}-save -t $2 | grep "PSW" | grep -v "\-j PSW$" | grep -v "mangle\-OUTPUT\-PSW" | grep -v "socket \-j PSW_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
|
||||
${_ipt}-save -t $2 | grep "PSW" | grep -v "\-j PSW$" | grep -v "mangle\-OUTPUT\-PSW" | grep -v "\-m socket .*\-j PSW_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
|
||||
echo 'COMMIT'
|
||||
}
|
||||
local __ipt=""
|
||||
@@ -1501,7 +1501,7 @@ gen_include() {
|
||||
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW")
|
||||
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW")
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT")
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT")
|
||||
EOF
|
||||
)
|
||||
}
|
||||
@@ -1522,7 +1522,7 @@ gen_include() {
|
||||
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW
|
||||
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW")
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT")
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket --transparent -j PSW_DIVERT")
|
||||
EOF
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.9.12
|
||||
PKG_RELEASE:=119
|
||||
PKG_RELEASE:=120
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -934,7 +934,7 @@ function gen_config_server(node)
|
||||
stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
|
||||
connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
|
||||
max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
|
||||
disable_path_mtu_discover = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
||||
disable_path_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
|
||||
tls = tls
|
||||
}
|
||||
end
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264
|
||||
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
|
||||
|
||||
PKG_NAME:=mise
|
||||
PKG_VERSION:=2026.9.5
|
||||
PKG_RELEASE:=11
|
||||
PKG_VERSION:=2026.9.6
|
||||
PKG_RELEASE:=12
|
||||
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=natflow
|
||||
PKG_VERSION:=20260531
|
||||
PKG_RELEASE:=83
|
||||
PKG_RELEASE:=84
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=6f03a5d1e50438ff72b0c9f19a5c9c97c329c62c
|
||||
PKG_SOURCE_VERSION:=36123df5df3371516b648e511dad66d5413b47aa
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
+3
-3
@@ -8,12 +8,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=torrserver
|
||||
PKG_VERSION:=MatriX.144.3
|
||||
PKG_RELEASE:=22
|
||||
PKG_VERSION:=MatriX.144.4
|
||||
PKG_RELEASE:=23
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
|
||||
PKG_SOURCE_VERSION:=80f50702c1e852dcad59cdd1399ac8aa7a092fee
|
||||
PKG_SOURCE_VERSION:=215f6fcaa84685fc0263584748206fca0810a8b6
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
|
||||
+2
-2
@@ -49,8 +49,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_VERSION:=1.6.6_p22
|
||||
PKG_RELEASE:=34
|
||||
PKG_VERSION:=1.6.6_p24
|
||||
PKG_RELEASE:=35
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
|
||||
Reference in new issue
Block a user