Dew-OF-Aurora 1 сар өмнө
parent
commit
db2622f200

+ 23 - 7
scripts/domain_updater.py

@@ -29,18 +29,25 @@ def read_json_file(path, default=None):
         default = {}
     if not os.path.exists(path):
         return default
-    with open(path, "r", encoding="utf-8") as f:
-        return json.load(f)
+    try:
+        with open(path, "r", encoding="utf-8") as f:
+            return json.load(f)
+    except (ValueError, json.JSONDecodeError):
+        return default
 
 
 def write_json_file(path, data):
-    os.makedirs(os.path.dirname(path), exist_ok=True)
+    parent = os.path.dirname(path)
+    if parent:
+        os.makedirs(parent, exist_ok=True)
     with open(path, "w", encoding="utf-8") as f:
         json.dump(data, f, ensure_ascii=True, indent=2)
 
 
 def write_text_file(path, data):
-    os.makedirs(os.path.dirname(path), exist_ok=True)
+    parent = os.path.dirname(path)
+    if parent:
+        os.makedirs(parent, exist_ok=True)
     with open(path, "w", encoding="utf-8") as f:
         f.write(data)
 
@@ -981,8 +988,7 @@ def main():
 
             if cfg.get("healthcheck", {}).get("enabled", False):
                 check_results = check_domains(filtered, cfg.get("healthcheck", {}))
-                selected, _ = choose_domain(filtered, check_results, top_n, [])
-                top_candidates = cfst_rows[:top_n]
+                selected, top_candidates = choose_domain(filtered, check_results, top_n, [])
             else:
                 selected = cfst_rows[0]["domain"]
                 top_candidates = cfst_rows[:top_n]
@@ -1006,12 +1012,14 @@ def main():
             scored_records = [r for r in scored_records if r["domain"] in filtered_set]
             ranked_scored = rank_scored_records(scored_records, scoring_cfg)
 
-            if cfg.get("healthcheck", {}).get("enabled", True):
+            if cfg.get("healthcheck", {}).get("enabled", False):
                 check_results = check_domains(filtered, cfg.get("healthcheck", {}))
 
             selected, top_candidates = choose_domain(filtered, check_results, top_n, ranked_scored)
 
         status = "ok"
+        if check_results and all(x["success_ratio"] == 0 for x in check_results):
+            status = "ok_no_healthy"
         if not selected and last_good:
             selected = last_good
             status = "fallback_last_good"
@@ -1077,6 +1085,14 @@ def main():
                     "source_type": source_type,
                 },
             )
+            write_json_file(
+                vars_file,
+                {
+                    vars_value_key: last_good,
+                    "UPDATED_AT": now,
+                    "STATUS": "error_use_last_good",
+                },
+            )
             run_notify(notify_cfg.get("command", ""), last_good, "error_use_last_good")
             print(json.dumps({"status": "error_use_last_good", "error": str(e)}, ensure_ascii=True))
             return

+ 3 - 1
scripts/router_local_update.sh

@@ -80,7 +80,7 @@ json_get_string() {
 }
 
 json_escape() {
-  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
+  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g' | tr -d '\n\r'
 }
 
 build_top_candidates_json() {
@@ -99,6 +99,7 @@ build_top_candidates_json() {
     NR == 1 { next }
     count >= top_n { exit }
     NF < 1 { next }
+    { if (trim($1) == "") next }
     {
       ip = esc(trim($1))
       sent = esc(trim($2))
@@ -280,6 +281,7 @@ log_update_summary() {
     NR == 1 { next }
     count >= top_n { exit }
     NF < 1 { next }
+    { if (trim($1) == "") next }
     {
       ip = trim($1)
       sent = trim($2)

+ 16 - 1
scripts/run_update_and_commit.sh

@@ -41,6 +41,13 @@ http_token_file="${GIT_HTTP_TOKEN_FILE:-}"
 credential_helper="${GIT_CREDENTIAL_HELPER:-}"
 ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
 
+LOCK_FILE="${TMPDIR:-/tmp}/vmess-domain-rotator.lock"
+exec 200>"$LOCK_FILE"
+if ! flock -n 200; then
+  log "[vmess-domain-rotator] another instance is running, skip"
+  exit 0
+fi
+
 if [[ -z "$http_token" ]] && [[ -n "$http_token_file" ]] && [[ -r "$http_token_file" ]]; then
   http_token="$(tr -d '\r\n' < "$http_token_file")"
 fi
@@ -94,7 +101,12 @@ else:
 PY
 }
 
-/usr/bin/python3 "${APP_DIR}/scripts/domain_updater.py" --config "$CONFIG_PATH"
+updater_output="$(/usr/bin/python3 "${APP_DIR}/scripts/domain_updater.py" --config "$CONFIG_PATH")"
+printf '%s\n' "$updater_output"
+updater_status="$(printf '%s' "$updater_output" | /usr/bin/python3 -c 'import json,sys; print(json.load(sys.stdin).get("status",""))' 2>/dev/null || echo "")"
+if [[ "$updater_status" == "error_use_last_good" ]]; then
+  log "[vmess-domain-rotator] warning: updater using fallback value (source error)"
+fi
 
 if [[ ! -f "$SELECTED_TEXT_FILE" ]]; then
   log "[vmess-domain-rotator] selected value file missing after updater run (${SELECTED_TEXT_FILE}), skip git commit"
@@ -228,6 +240,9 @@ if [[ "$staged_changed" == "0" ]] && [[ "$force_commit" == "1" ]]; then
 fi
 
 commit_message="chore: rotate preferred value to ${after} (${ts})"
+if [[ "$updater_status" == "error_use_last_good" ]]; then
+  commit_message="chore(fallback): rotate preferred value to ${after} (${ts})"
+fi
 if [[ "$force_commit" == "1" ]]; then
   commit_message="manual: value ${after}, updated at ${ts}"
 fi

+ 13 - 0
scripts/uninstall_debian.sh

@@ -80,10 +80,23 @@ if systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then
 fi
 
 # Remove systemd unit files
+SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
+SVC_USER=""
+SVC_APP_DIR=""
+if [[ -f "$SERVICE_FILE" ]]; then
+	SVC_USER="$(grep '^User=' "$SERVICE_FILE" | head -1 | cut -d= -f2)"
+	SVC_APP_DIR="$(grep '^WorkingDirectory=' "$SERVICE_FILE" | head -1 | cut -d= -f2)"
+fi
+
 log "Removing systemd unit files..."
 rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
 rm -f "/etc/systemd/system/${SERVICE_NAME}.timer"
 
+if [[ -n "$SVC_USER" ]] && [[ -n "$SVC_APP_DIR" ]]; then
+	log "Cleaning git safe.directory for user ${SVC_USER}..."
+	runuser -u "$SVC_USER" -- git config --global --unset-all safe.directory "$SVC_APP_DIR" 2>/dev/null || true
+fi
+
 if [[ "$REMOVE_AUTH_FILES" == "1" ]]; then
 	log "Removing auth/env files..."
 	rm -f "$ENV_FILE"

+ 1 - 1
substore/operator_template.js

@@ -10,7 +10,7 @@ const ROUTER_MIN_SPEED_MB_PER_S = 5;
 const VALUE_SOURCE_MODE = "default"; // default | server_only | router_only
 
 const NODE_NAME_REGEX = /(argo|cf|vm|优选)/i;
-const CACHE_KEY = "vmess-domain-rotator:current";
+const CACHE_KEY = `vmess-domain-rotator:${VALUE_SOURCE_MODE}`;
 const CACHE_TTL_MS = 5 * 60 * 1000;
 const JSON_VALUE_KEYS = ["domain", "ip"];