Răsfoiți Sursa

fix: git push

Dew-OF-Aurora 2 săptămâni în urmă
părinte
comite
2ac82ecc5c
4 a modificat fișierele cu 368 adăugiri și 180 ștergeri
  1. 121 0
      CLAUDE.md
  2. 117 0
      git-push-authentication.md
  3. 89 118
      scripts/install_debian.sh
  4. 41 62
      scripts/uninstall_debian.sh

+ 121 - 0
CLAUDE.md

@@ -0,0 +1,121 @@
+# CLAUDE.md
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+VMess domain rotator - automated pipeline that fetches preferred domains from an API, selects the best candidate, and exports runtime files for Sub-Store and V2Ray integration.
+
+## Key Commands
+
+### Development
+```bash
+# Syntax check Python scripts
+python3 -m py_compile scripts/domain_updater.py
+python3 -m py_compile scripts/update_vmess_links.py
+
+# Manual run (single update)
+python3 scripts/domain_updater.py --config config.json
+
+# Manual run with conditional git commit (commits only if domain changed)
+bash scripts/run_update_and_commit.sh config.json
+
+# Update VMess base64 links with selected domain
+python3 scripts/update_vmess_links.py --input ./nodes.txt --output ./nodes.updated.txt --domain-file ./runtime/current_domain.txt
+
+# Update specific nodes by regex
+python3 scripts/update_vmess_links.py --input ./nodes.txt --output ./nodes.updated.txt --domain-file ./runtime/current_domain.txt --name-regex "(argo|cf|vm)"
+
+# Local smoke test HTTP endpoint for runtime outputs
+python3 -m http.server 8080 --directory runtime
+```
+
+### Deployment (Debian)
+```bash
+# Install systemd service+timer (uses current git repo directory)
+sudo bash scripts/install_debian.sh
+
+# Install with custom timer interval
+sudo bash scripts/install_debian.sh --interval 5min
+
+# Uninstall (keeps git repository and files)
+sudo bash scripts/uninstall_debian.sh
+```
+
+## Architecture
+
+### Core Flow
+1. `scripts/domain_updater.py` - Main entrypoint that:
+   - Fetches domain candidates from API (configured in `config.json`)
+   - Parses response using JSON path or regex
+   - Optionally scores candidates based on API fields
+   - Optionally healthchecks domains (TLS handshake)
+   - Selects best domain and writes runtime outputs
+   - Falls back to `last_good_domain` from state.json if all checks fail
+
+2. `scripts/run_update_and_commit.sh` - Scheduler entrypoint that:
+   - Runs domain_updater.py
+   - Compares before/after domain
+   - Commits to `runtime-state` branch if domain changed
+   - Pushes to remote if configured
+
+3. `scripts/update_vmess_links.py` - Post-processor for VMess subscription links:
+   - Decodes vmess:// base64 payloads
+   - Updates `add` field with selected domain
+   - Supports node name filtering by regex
+
+### Configuration Structure
+- `config.json` - Single runtime config file containing:
+  - `api`: endpoint URL, method, headers, timeout
+  - `parser`: JSON paths (`field_paths`) or regex for extracting domains
+  - `domain_filter`: include/exclude patterns (e.g., filter out IPv4 addresses)
+  - `scoring`: ranking logic based on API fields or API order
+  - `healthcheck`: TLS handshake verification settings
+  - `selection`: top_n candidates to consider
+  - `output`: runtime directory and file paths
+  - `v2ray`: template file token replacement (optional)
+
+### Output Files
+All runtime outputs are written to `runtime/`:
+- `current_domain.txt` - Plain text domain
+- `current_domain.json` - JSON payload with domain, timestamp, status, source_count
+- `state.json` - Persistent state including `last_good_domain` for fallback
+- `substore_vars.json` - Variables for Sub-Store operator scripts
+
+### Sub-Store Integration
+`substore/operator_template.js` - Operator script that:
+- Fetches `current_domain.json` via HTTP using `$substore.http.get`
+- Caches domain with TTL (5 minutes by default)
+- Replaces VMess `server` field for nodes matching `NODE_NAME_REGEX`
+- Must be configured with actual DOMAIN_JSON_URL in production
+
+### Systemd Deployment
+- Service runs `run_update_and_commit.sh` oneshot
+- Timer triggers every 1h by default (configurable via install_debian.sh)
+- Uses current git repository directory (in-place mode only)
+- Uses the installing user (from SUDO_USER) as service user
+- Runtime state committed to `runtime-state` branch
+- No separate service user created - uses existing user's git credentials
+
+## Important Behaviors
+
+- **Fallback**: If all healthchecks fail, script uses `last_good_domain` from `runtime/state.json`
+- **Conditional commits**: `run_update_and_commit.sh` only commits when domain actually changes
+- **Branch separation**: Runtime state goes to `runtime-state` branch via git worktree
+- **API order mode**: For vps789 Top20, set `scoring.use_api_order=true` to trust API ranking
+- **Domain filtering**: Use `domain_filter.exclude_regex` to filter out IPv4 addresses when API returns mixed results
+- **Sub-Store caching**: Operator uses `scriptResourceCache` with TTL to avoid excessive HTTP requests
+
+## Configuration Notes
+
+For vps789 Top20 API specifically:
+- Use `parser.field_paths: ["data.good[].ip"]`
+- Enable `scoring.use_api_order: true` to respect API ranking
+- Set `healthcheck.enabled: false` if you want pure API-based selection
+- Add IPv4 exclude regex to `domain_filter.exclude_regex` to filter out IP addresses
+
+## Git Workflow
+
+- Main branch: `main` (source code)
+- Runtime branch: `runtime-state` (auto-committed domain updates)
+- Commits to runtime-state use generic git identity: `vmess-domain-rotator@localhost`
+- Script creates runtime-state branch automatically if missing

+ 117 - 0
git-push-authentication.md

@@ -0,0 +1,117 @@
+# Git Push 认证配置指南
+
+## 问题分析
+
+从日志可以看到,systemd 服务在尝试推送时遇到认证失败:
+
+```
+fatal: could not read Username for 'https://git.dewofaurora.de': No such device or address
+```
+
+这是因为 systemd 服务在非交互式环境下运行,无法提示输入用户名和密码。
+
+## 解决方案
+
+### 方案 1: 使用 Git Credential Store(推荐用于个人服务器)
+
+在服务器上执行以下命令:
+
+```bash
+# 1. 配置全局 credential helper
+git config --global credential.helper store
+
+# 2. 手动推送一次以保存凭据
+cd /opt/vmess-domain-rotator  # 或者你的应用目录
+git push vmess-domain-rotator runtime-state:runtime-state
+
+# 系统会提示输入用户名和密码,输入后会被保存到 ~/.git-credentials
+```
+
+### 方案 2: 使用 Git Credential Cache(临时缓存)
+
+```bash
+# 缓存凭据 1 小时(3600秒)
+git config --global credential.helper 'cache --timeout=3600'
+
+# 第一次推送需要输入凭据
+git push vmess-domain-rotator runtime-state:runtime-state
+```
+
+### 方案 3: 使用 Personal Access Token(推荐用于 Git 服务器)
+
+如果你的 Git 服务器支持 Personal Access Token:
+
+```bash
+# 1. 在 Git 服务器上生成 Token(如 GitLab: Settings -> Access Tokens)
+
+# 2. 使用 Token 作为密码
+# 方法 A: 在推送时使用 Token URL
+git remote set-url vmess-domain-rotator https://username:TOKEN@git.dewofaurora.de/aurora/vmess-domain-rotator.git
+
+# 方法 B: 使用 credential store
+git config --global credential.helper store
+# 然后推送时用户名输入你的用户名,密码输入 Token
+```
+
+### 方案 4: 使用 SSH URL(推荐用于生产环境)
+
+如果你的 Git 服务器支持 SSH:
+
+```bash
+# 1. 切换远程 URL 为 SSH
+git remote set-url vmess-domain-rotator git@git.dewofaurora.de:aurora/vmess-domain-rotator.git
+
+# 2. 确保 systemd 用户有 SSH 密钥
+sudo -u vmessrotator ssh-keygen -t ed25519 -C "vmess-domain-rotator@localhost"
+
+# 3. 将公钥添加到 Git 服务器
+sudo -u vmessrotator cat /home/vmessrotator/.ssh/id_ed25519.pub
+# 复制公钥内容,然后在 Git 服务器的 SSH Keys 设置中添加
+
+# 4. 测试 SSH 连接
+sudo -u vmessrotator ssh -T git@git.dewofaurora.de
+
+# 5. 测试推送
+sudo -u vmessrotator git push vmess-domain-rotator runtime-state:runtime-state
+```
+
+## 验证配置
+
+配置完成后,测试 systemd 服务:
+
+```bash
+# 手动触发服务
+sudo systemctl start vmess-domain-rotator.service
+
+# 查看日志
+sudo journalctl -u vmess-domain-rotator.service -n 50
+
+# 确认看到 "pushed to vmess-domain-rotator/runtime-state" 或类似成功消息
+```
+
+## 推荐方案
+
+根据你的情况,建议使用 **方案 1 (Git Credential Store)**:
+
+1. 简单易用,无需 SSH 密钥配置
+2. 持久化存储,重启后仍有效
+3. 适合个人服务器环境
+
+执行步骤:
+
+```bash
+# 在服务器上以服务用户身份执行
+sudo -u vmessrotator bash << 'EOF'
+cd /opt/vmess-domain-rotator  # 或你的应用目录
+git config --global credential.helper store
+git push vmess-domain-rotator runtime-state:runtime-state
+# 输入用户名和密码后,凭据会被保存
+EOF
+```
+
+## 安全建议
+
+1. **不要在共享服务器上使用 credential store**:如果服务器有其他用户,考虑使用 SSH 方案
+2. **定期轮换凭据**:建议每 90 天更换一次密码或 Token
+3. **使用最小权限 Token**:如果使用 Token,只授予 `write_repository` 权限
+4. **监控推送日志**:定期检查是否有异常推送记录

+ 89 - 118
scripts/install_debian.sh

@@ -2,154 +2,116 @@
 set -euo pipefail
 
 SERVICE_NAME="vmess-domain-rotator"
-APP_DIR="/opt/vmess-domain-rotator"
-RUN_USER="vmessrotator"
-RUN_GROUP="vmessrotator"
-APP_DIR_SET="0"
+RUN_USER=""
+RUN_GROUP=""
 RUN_USER_SET="0"
 RUN_GROUP_SET="0"
 INTERVAL="1h"
 INSTALL_DEPS="1"
-OVERWRITE_CONFIG="0"
 
 usage() {
-  cat <<'EOF'
+	cat <<'EOF'
 Usage: sudo bash scripts/install_debian.sh [options]
 
 Default behavior:
-  - If current source dir is a git repo and --app-dir is not set, install in-place
-    (service runs directly from this repo so auto-commit works on your real git repo).
-  - If --app-dir is set, files are copied into that directory.
+- Uses current git repository directory as working directory (in-place mode)
+- Uses the user executing sudo as service user
 
 Options:
-  --app-dir <path>         Install directory (default: /opt/vmess-domain-rotator)
-  --user <name>            Service user (default: vmessrotator)
-  --group <name>           Service group (default: vmessrotator)
-  --interval <value>       Timer interval, e.g. 1h/10min (default: 1h)
-  --no-install-deps        Skip apt dependency install
-  --overwrite-config       Overwrite existing config.json in app dir
-  -h, --help               Show help
+ --user <name>        Service user (default: current sudo user)
+ --group <name>       Service group (default: current sudo user's group)
+ --interval <value>   Timer interval, e.g. 1h/10min (default: 1h)
+ --no-install-deps    Skip apt dependency install
+ -h, --help           Show help
 
 Examples:
-  sudo bash scripts/install_debian.sh
-  sudo bash scripts/install_debian.sh --app-dir /opt/vmess-domain-rotator
-  sudo bash scripts/install_debian.sh --user root --group root --interval 10min
+ sudo bash scripts/install_debian.sh
+ sudo bash scripts/install_debian.sh --interval 10min
+ sudo bash scripts/install_debian.sh --user root --group root
 EOF
 }
 
 while [[ $# -gt 0 ]]; do
-  case "$1" in
-    --app-dir)
-      APP_DIR="$2"
-      APP_DIR_SET="1"
-      shift 2
-      ;;
-    --user)
-      RUN_USER="$2"
-      RUN_USER_SET="1"
-      shift 2
-      ;;
-    --group)
-      RUN_GROUP="$2"
-      RUN_GROUP_SET="1"
-      shift 2
-      ;;
-    --interval)
-      INTERVAL="$2"
-      shift 2
-      ;;
-    --no-install-deps)
-      INSTALL_DEPS="0"
-      shift
-      ;;
-    --overwrite-config)
-      OVERWRITE_CONFIG="1"
-      shift
-      ;;
-    -h|--help)
-      usage
-      exit 0
-      ;;
-    *)
-      echo "Unknown option: $1" >&2
-      usage
-      exit 1
-      ;;
-  esac
+	case "$1" in
+		--user)
+			RUN_USER="$2"
+			RUN_USER_SET="1"
+			shift 2
+			;;
+		--group)
+			RUN_GROUP="$2"
+			RUN_GROUP_SET="1"
+			shift 2
+			;;
+		--interval)
+			INTERVAL="$2"
+			shift 2
+			;;
+		--no-install-deps)
+			INSTALL_DEPS="0"
+			shift
+			;;
+		-h|--help)
+			usage
+			exit 0
+			;;
+		*)
+			echo "Unknown option: $1" >&2
+			usage
+			exit 1
+			;;
+	esac
 done
 
 if [[ "$(id -u)" -ne 0 ]]; then
-  echo "Please run as root (use sudo)." >&2
-  exit 1
+	echo "Please run as root (use sudo)." >&2
+	exit 1
 fi
 
+# Get source directory (current git repo)
 SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-DEPLOY_MODE="copy"
 
-if [[ "$APP_DIR_SET" != "1" ]] && git -C "$SOURCE_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
-  APP_DIR="$SOURCE_DIR"
-  DEPLOY_MODE="in-place"
+# Verify we're in a git repository
+if ! git -C "$SOURCE_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
+	echo "Error: Current directory is not a git repository." >&2
+	echo "This script must be run from within a git repository." >&2
+	exit 1
+fi
 
-  if [[ -n "${SUDO_USER:-}" ]] && [[ "$RUN_USER_SET" != "1" ]]; then
-    RUN_USER="$SUDO_USER"
-  fi
+APP_DIR="$SOURCE_DIR"
 
-  if [[ -n "${SUDO_USER:-}" ]] && [[ "$RUN_GROUP_SET" != "1" ]]; then
-    RUN_GROUP="$(id -gn "$SUDO_USER")"
-  fi
+# Set default user/group from SUDO_USER if available
+if [[ -n "${SUDO_USER:-}" ]] && [[ "$RUN_USER_SET" != "1" ]]; then
+	RUN_USER="$SUDO_USER"
 fi
 
-if [[ "$INSTALL_DEPS" == "1" ]]; then
-  export DEBIAN_FRONTEND=noninteractive
-  apt-get update -y
-  apt-get install -y python3 ca-certificates git
+if [[ -n "${SUDO_USER:-}" ]] && [[ "$RUN_GROUP_SET" != "1" ]]; then
+	RUN_GROUP="$(id -gn "$SUDO_USER")"
 fi
 
-if [[ "$RUN_USER" != "root" ]]; then
-  if ! getent group "$RUN_GROUP" >/dev/null 2>&1; then
-    groupadd --system "$RUN_GROUP"
-  fi
-  if ! id -u "$RUN_USER" >/dev/null 2>&1; then
-    useradd --system --home-dir "$APP_DIR" --create-home --shell /usr/sbin/nologin --gid "$RUN_GROUP" "$RUN_USER"
-  fi
+# Validate that we have a user set
+if [[ -z "$RUN_USER" ]]; then
+	echo "Error: Could not determine service user. Please run with sudo or specify --user" >&2
+	exit 1
 fi
 
-mkdir -p "$APP_DIR"
-
-if [[ "$DEPLOY_MODE" == "copy" ]]; then
-  CONFIG_BACKUP=""
-  if [[ "$OVERWRITE_CONFIG" != "1" && -f "$APP_DIR/config.json" ]]; then
-    CONFIG_BACKUP="$(mktemp)"
-    cp "$APP_DIR/config.json" "$CONFIG_BACKUP"
-  fi
-
-  tar -C "$SOURCE_DIR" \
-    --exclude='.git' \
-    --exclude='.DS_Store' \
-    --exclude='__pycache__' \
-    -cf - . | tar -C "$APP_DIR" -xf -
-
-  if [[ -n "$CONFIG_BACKUP" ]]; then
-    cp "$CONFIG_BACKUP" "$APP_DIR/config.json"
-    rm -f "$CONFIG_BACKUP"
-  fi
+if [[ -z "$RUN_GROUP" ]]; then
+	echo "Error: Could not determine service group. Please run with sudo or specify --group" >&2
+	exit 1
 fi
 
-mkdir -p "$APP_DIR/runtime"
-chmod +x "$APP_DIR/scripts/run_update_and_commit.sh" "$APP_DIR/scripts/install_debian.sh" "$APP_DIR/scripts/uninstall_debian.sh" || true
-
-if [[ "$RUN_USER" != "root" ]]; then
-  if [[ "$DEPLOY_MODE" == "in-place" ]]; then
-    chown -R "$RUN_USER:$RUN_GROUP" "$APP_DIR/runtime"
-  else
-    chown -R "$RUN_USER:$RUN_GROUP" "$APP_DIR"
-  fi
+if [[ "$INSTALL_DEPS" == "1" ]]; then
+	export DEBIAN_FRONTEND=noninteractive
+	apt-get update -y
+	apt-get install -y python3 ca-certificates git
 fi
 
-if [[ "$DEPLOY_MODE" == "copy" ]] && ! git -C "$APP_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
-  git -C "$APP_DIR" init
-fi
+# Ensure runtime directory exists with correct permissions
+mkdir -p "$APP_DIR/runtime"
+chmod +x "$APP_DIR/scripts/run_update_and_commit.sh" || true
+chown -R "$RUN_USER:$RUN_GROUP" "$APP_DIR/runtime"
 
+# Generate systemd service unit
 cat >"/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
 [Unit]
 Description=VMess Domain Rotator updater
@@ -164,6 +126,7 @@ WorkingDirectory=${APP_DIR}
 ExecStart=/bin/bash ${APP_DIR}/scripts/run_update_and_commit.sh ${APP_DIR}/config.json
 EOF
 
+# Generate systemd timer unit
 cat >"/etc/systemd/system/${SERVICE_NAME}.timer" <<EOF
 [Unit]
 Description=Run VMess Domain Rotator every ${INTERVAL}
@@ -179,14 +142,22 @@ Persistent=true
 WantedBy=timers.target
 EOF
 
+# Enable and start service
 systemctl daemon-reload
 systemctl enable --now "${SERVICE_NAME}.timer"
 systemctl start "${SERVICE_NAME}.service"
 
-echo "Installed successfully."
-echo "Deploy mode: ${DEPLOY_MODE}"
-echo "App dir: ${APP_DIR}"
-echo "Service: ${SERVICE_NAME}.service"
-echo "Timer: ${SERVICE_NAME}.timer"
-echo "Check status: systemctl status ${SERVICE_NAME}.timer"
-echo "View logs: journalctl -u ${SERVICE_NAME}.service -n 50 --no-pager"
+echo ""
+echo "✓ Installation complete!"
+echo ""
+echo "Configuration:"
+echo "  Working directory: ${APP_DIR}"
+echo "  Service user: ${RUN_USER}"
+echo "  Service group: ${RUN_GROUP}"
+echo "  Timer interval: ${INTERVAL}"
+echo ""
+echo "Commands:"
+echo "  Check status: systemctl status ${SERVICE_NAME}.timer"
+echo "  View logs:    journalctl -u ${SERVICE_NAME}.service -n 50 --no-pager"
+echo "  Manual run:   systemctl start ${SERVICE_NAME}.service"
+echo ""

+ 41 - 62
scripts/uninstall_debian.sh

@@ -2,92 +2,71 @@
 set -euo pipefail
 
 SERVICE_NAME="vmess-domain-rotator"
-APP_DIR="/opt/vmess-domain-rotator"
-KEEP_APP_DIR="0"
-REMOVE_USER="0"
-RUN_USER="vmessrotator"
 
 usage() {
-  cat <<'EOF'
-Usage: sudo bash scripts/uninstall_debian.sh [options]
+	cat <<'EOF'
+Usage: sudo bash scripts/uninstall_debian.sh
+
+This script will:
+- Stop and disable the systemd timer and service
+- Remove systemd unit files
+- Keep your git repository and all files intact
 
 Options:
-  --app-dir <path>         Install directory (default: /opt/vmess-domain-rotator)
-  --service-name <name>    Service base name (default: vmess-domain-rotator)
-  --keep-app-dir           Keep install directory and files
-  --remove-user <name>     Remove this service user after uninstall
-  -h, --help               Show help
+ --service-name <name> Service base name (default: vmess-domain-rotator)
+ -h, --help            Show help
 
 Examples:
-  sudo bash scripts/uninstall_debian.sh
-  sudo bash scripts/uninstall_debian.sh --keep-app-dir
-  sudo bash scripts/uninstall_debian.sh --remove-user vmessrotator
+ sudo bash scripts/uninstall_debian.sh
 EOF
 }
 
 while [[ $# -gt 0 ]]; do
-  case "$1" in
-    --app-dir)
-      APP_DIR="$2"
-      shift 2
-      ;;
-    --service-name)
-      SERVICE_NAME="$2"
-      shift 2
-      ;;
-    --keep-app-dir)
-      KEEP_APP_DIR="1"
-      shift
-      ;;
-    --remove-user)
-      REMOVE_USER="1"
-      RUN_USER="$2"
-      shift 2
-      ;;
-    -h|--help)
-      usage
-      exit 0
-      ;;
-    *)
-      echo "Unknown option: $1" >&2
-      usage
-      exit 1
-      ;;
-  esac
+	case "$1" in
+		--service-name)
+			SERVICE_NAME="$2"
+			shift 2
+			;;
+		-h|--help)
+			usage
+			exit 0
+			;;
+		*)
+			echo "Unknown option: $1" >&2
+			usage
+			exit 1
+			;;
+	esac
 done
 
 if [[ "$(id -u)" -ne 0 ]]; then
-  echo "Please run as root (use sudo)." >&2
-  exit 1
+	echo "Please run as root (use sudo)." >&2
+	exit 1
 fi
 
+# Stop and disable timer
 if systemctl list-unit-files | grep -q "^${SERVICE_NAME}.timer"; then
-  systemctl disable --now "${SERVICE_NAME}.timer" || true
+	echo "Stopping and disabling ${SERVICE_NAME}.timer..."
+	systemctl disable --now "${SERVICE_NAME}.timer" || true
 fi
 
+# Stop service if running
 if systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then
-  systemctl stop "${SERVICE_NAME}.service" || true
+	echo "Stopping ${SERVICE_NAME}.service..."
+	systemctl stop "${SERVICE_NAME}.service" || true
 fi
 
+# Remove systemd unit files
+echo "Removing systemd unit files..."
 rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
 rm -f "/etc/systemd/system/${SERVICE_NAME}.timer"
 
 systemctl daemon-reload
 systemctl reset-failed
 
-if [[ "$KEEP_APP_DIR" != "1" ]]; then
-  rm -rf "$APP_DIR"
-fi
-
-if [[ "$REMOVE_USER" == "1" ]]; then
-  if id -u "$RUN_USER" >/dev/null 2>&1; then
-    userdel "$RUN_USER" || true
-  fi
-fi
-
-echo "Uninstall completed."
-if [[ "$KEEP_APP_DIR" == "1" ]]; then
-  echo "Kept app directory: ${APP_DIR}"
-else
-  echo "Removed app directory: ${APP_DIR}"
-fi
+echo ""
+echo "✓ Uninstall complete!"
+echo ""
+echo "Note: Your git repository and all files have been preserved."
+echo "      To completely remove, manually delete the repository directory if needed."
+echo ""