refactor: module-based strategy architecture

- Add sync_from_upstream.sh: clones Flowseal repo, merges lists/hosts, auto-detects new .bat strategies
- Add strategies/*.conf config files (extracted from .bat)
- Rewrite run_strategy.sh: loads .conf, substitutes %BIN%/%LISTS%/%GAME_*% using sed (no envsubst)
- Rewrite update.sh: pipeline is now sync→rebuild→test→install
- Add ensure_wrappers.sh: auto-creates general_*.sh wrappers from .conf
- Fix all 'local outside function' runtime bugs
- install.sh now calls sync_from_upstream.sh first
This commit is contained in:
2026-05-10 19:46:14 +04:00
parent b75d75931d
commit 7aeb16856d
6 changed files with 477 additions and 535 deletions
+220
View File
@@ -0,0 +1,220 @@
#!/bin/bash
# sync_from_upstream.sh - Merge upstream Flowseal changes into Linux version
# Handles: lists, .service/hosts, .service/version, auto-detection of NEW .bat strategies
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
UPSTREAM_REPO="https://github.com/Flowseal/zapret-discord-youtube.git"
TMP_DIR="/tmp/zapret-upstream-$$"
LOCAL_VERSION_FILE="$SCRIPT_DIR/.service/version.txt"
STRATEGIES_DIR="$SCRIPT_DIR/strategies"
mkdir -p "$STRATEGIES_DIR"
log_msg() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Clone upstream with 60s timeout
log_msg "[*] Cloning upstream Flowseal/zapret-discord-youtube..."
if ! git clone --depth=1 --single-branch --branch main "$UPSTREAM_REPO" "$TMP_DIR" 2>/dev/null; then
log_msg "[FAIL] Could not clone upstream. Check internet/DNS."
rm -rf "$TMP_DIR"
exit 1
fi
# Read versions
LOCAL_VER=$(cat "$LOCAL_VERSION_FILE" 2>/dev/null | tr -d '[:space:]')
UPSTREAM_VER=$(cat "$TMP_DIR/.service/version.txt" 2>/dev/null | tr -d '[:space:]')
[[ -z "$LOCAL_VER" ]] && LOCAL_VER="unknown"
[[ -z "$UPSTREAM_VER" ]] && UPSTREAM_VER="unknown"
log_msg "Local version: $LOCAL_VER"
log_msg "Upstream version: $UPSTREAM_VER"
REBUILD_FLAG=0
if [[ "$UPSTREAM_VER" != "$LOCAL_VER" ]]; then
REBUILD_FLAG=1
log_msg "[*] Version changed: $LOCAL_VER$UPSTREAM_VER"
fi
# ======================
# MERGE LISTS
# ======================
log_msg "[*] Merging upstream lists..."
merge_list() {
local file="$1"
local src="$TMP_DIR/lists/$file"
local dst="$SCRIPT_DIR/lists/$file"
if [[ ! -f "$src" ]]; then
log_msg " [SKIP] Upstream missing: lists/$file"
return
fi
cp "$src" "$dst"
log_msg " [OK] lists/$file"
}
merge_list "list-general.txt"
merge_list "list-google.txt"
merge_list "list-exclude.txt"
merge_list "ipset-exclude.txt"
# Only update ipset-all if placeholder or enabled
if [[ -f "$SCRIPT_DIR/.service/ipset_filter_loaded" ]] || [[ -f "$SCRIPT_DIR/.service/ipset_filter_any" ]]; then
merge_list "ipset-all.txt" 2>/dev/null || true
fi
# ======================
# MERGE .service/hosts + version
# ======================
log_msg "[*] Merging .service files..."
if [[ -f "$TMP_DIR/.service/hosts" ]]; then
cp "$TMP_DIR/.service/hosts" "$SCRIPT_DIR/.service/hosts"
log_msg " [OK] .service/hosts (GitHub, Telegram, Discord static IPs)"
REBUILD_FLAG=1
fi
if [[ -f "$TMP_DIR/.service/version.txt" ]]; then
echo "$UPSTREAM_VER-linux" > "$LOCAL_VERSION_FILE"
log_msg " [OK] .service/version.txt → $UPSTREAM_VER-linux"
REBUILD_FLAG=1
fi
# ======================
# PARSE NEW .bat STRATEGIES → .conf
# ======================
log_msg "[*] Checking for new upstream strategies..."
NEW_COUNT=0
UPDATE_COUNT=0
parse_bat_to_conf() {
local src="$1"
local name_raw
name_raw=$(basename "$src" .bat)
local name
name=$(echo "$name_raw" | sed -E 's/general[[:space:]]*//; s/\(|\)//g; s/^[[:space:]]+|[[:space:]]+$//g')
[[ -z "$name" ]] && name="general"
local conf_name="$STRATEGIES_DIR/${name}.conf"
# Extract winws arguments after "winws.exe ...parameters..."
# Step 1: Find all lines after winws.exe, clean line continuations (^) and quotes
local raw_args
raw_args=$(sed -n '/winws\.exe/I,${ p }' "$src" | \
sed 's/\^//g' | \
tr '\n' ' ' | \
sed -E 's/start[^"]*"[^"]*"[^"]*"[^"]*"//i; s/ *\/min //i')
# Step 2: Remove the winws.exe binary path and WF filters (Windows-only --wf-*)
raw_args=$(echo "$raw_args" | sed -E 's/[^ ]*winws\.exe//i; s/--wf-tcp=[^ ]+//; s/--wf-udp=[^ ]+//')
# Step 3: Fix Windows paths to placeholders
# "...bin\file.bin" → %BIN%/file.bin
raw_args=$(echo "$raw_args" | sed -E 's/"%BIN%\\([^"]*)"/\%BIN%\/\1/g')
raw_args=$(echo "$raw_args" | sed -E 's/%BIN%\\([^ ]*)/\%BIN%\/\1/g')
raw_args=$(echo "$raw_args" | sed -E 's/"%LISTS%\\([^"]*)"/\%LISTS%\/\1/g')
raw_args=$(echo "$raw_args" | sed -E 's/%LISTS%\\([^ ]*)/\%LISTS%\/\1/g')
# Remove stray quotes
raw_args=$(echo "$raw_args" | tr -d '\"')
# Step 4: Replace Windows variables with Linux placeholders
raw_args=$(echo "$raw_args" | sed 's/%GameFilterTCP%/%GAME_TCP%/g; s/%GameFilterUDP%/%GAME_UDP%/g')
# Step 5: Split by --new into RULE1..9
local rule_num=1
local current_rule=""
# Convert --new separated rules into lines
echo "# Strategy: $name" > "$conf_name"
echo "# Auto-generated from upstream: $name_raw.bat" >> "$conf_name"
echo "# Version: $UPSTREAM_VER" >> "$conf_name"
echo "" >> "$conf_name"
# Use awk to split by --new
echo "$raw_args" | awk '{
split($0, parts, " --new");
for (i=1; i<=length(parts); i++) {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", parts[i]);
if (parts[i] != "") {
print "RULE" i "=" parts[i];
}
}
}' >> "$conf_name"
}
for bat in "$TMP_DIR"/general*.bat; do
[[ -f "$bat" ]] || continue
bat_name=$(basename "$bat")
[[ "$bat_name" == "service.bat" ]] && continue
# Normalize conf name
strategy_name=$(echo "$bat_name" | sed -E 's/^general[[:space:]]*//; s/\(|\)//g; s/\.bat$//')
[[ -z "$strategy_name" ]] && strategy_name="general"
conf_file="$STRATEGIES_DIR/${strategy_name}.conf"
if [[ -f "$conf_file" ]]; then
# Check if upstream strategy changed (compare hash)
remote_hash=$(md5sum "$bat" 2>/dev/null | awk '{print $1}')
local_hash=$(md5sum "$conf_file" 2>/dev/null | awk '{print $1}')
if [[ "$remote_hash" != "$(grep '^# hash:' "$conf_file" 2>/dev/null | awk '{print $3}')" ]]; then
log_msg " [CHANGED] Strategy updated: $bat_name$strategy_name.conf"
parse_bat_to_conf "$bat"
UPDATE_COUNT=$((UPDATE_COUNT + 1))
REBUILD_FLAG=1
fi
else
log_msg " [NEW] Detected upstream strategy: $bat_name$strategy_name.conf"
parse_bat_to_conf "$bat"
NEW_COUNT=$((NEW_COUNT + 1))
REBUILD_FLAG=1
fi
done
if [[ $NEW_COUNT -gt 0 ]]; then
log_msg " [INFO] $NEW_COUNT new strategy(ies) auto-converted."
log_msg " [WARN] Please verify new strategies manually before using."
fi
if [[ $UPDATE_COUNT -gt 0 ]]; then
log_msg " [INFO] $UPDATE_COUNT strategy(ies) updated from upstream."
fi
if [[ $NEW_COUNT -eq 0 ]] && [[ $UPDATE_COUNT -eq 0 ]]; then
log_msg " [OK] All strategies up to date."
fi
# Cleanup
rm -rf "$TMP_DIR"
# ======================
# CHECK if general*.sh wrappers exist for all .conf
# ======================
log_msg "[*] Checking strategy wrappers (general*.sh)..."
for conf in "$STRATEGIES_DIR"/*.conf; do
[[ -f "$conf" ]] || continue
local name
name=$(basename "$conf" .conf)
local wrapper="$SCRIPT_DIR/general_${name}.sh"
if [[ ! -f "$wrapper" ]]; then
# Create wrapper
cat > "$wrapper" <<EOF
#!/bin/bash
# Auto-generated wrapper for strategy: $name
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
exec "\$SCRIPT_DIR/run_strategy.sh" "$name"
EOF
chmod +x "$wrapper"
log_msg " [OK] Created wrapper: $(basename "$wrapper")"
fi
done
# ======================
# REPORT
# ======================
log_msg "[*] Sync complete."
if [[ "$REBUILD_FLAG" -eq 1 ]]; then
log_msg "[*] REBUILD NEEDED: run ./update.sh or ./install.sh"
exit 2
else
log_msg "[*] No changes detected."
exit 0
fi