From 01c2fd3292a31466aade45b7ecd41b7419b4bcf8 Mon Sep 17 00:00:00 2001 From: yuanzhen869 Date: Mon, 23 Mar 2026 15:27:56 +0800 Subject: [PATCH] Add scheduled subset sync workflow --- .gitea/workflows/sync-subset.yml | 20 ++++++ README.md | 10 ++- scripts/build_singbox_rules.py | 103 +++++++++++++++++++++++++++++++ scripts/sync_subset.sh | 26 +++++--- 4 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 .gitea/workflows/sync-subset.yml create mode 100644 scripts/build_singbox_rules.py diff --git a/.gitea/workflows/sync-subset.yml b/.gitea/workflows/sync-subset.yml new file mode 100644 index 0000000..fb7dd29 --- /dev/null +++ b/.gitea/workflows/sync-subset.yml @@ -0,0 +1,20 @@ +name: sync-ios-rule-subset +on: + workflow_dispatch: + schedule: + - cron: '0 */12 * * *' +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Sync subset from upstream + env: + GITEA_REPO_OWNER: yuanzhen869 + GITEA_REPO_NAME: ios-rule-script-subset + GITEA_SYNC_USERNAME: yuanzhen869 + GITEA_SYNC_EMAIL: yuanzhen869@gmail.com + GITEA_SYNC_TOKEN: ${{ secrets.SYNC_TOKEN }} + run: | + bash ./scripts/sync_subset.sh diff --git a/README.md b/README.md index 6bec0db..90ffb5d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ 这个仓库只同步 `blackmatrix7/ios_rule_script` 里当前本地 `surgio` 需要的少量 Surge 规则文件,不做全量镜像。 +当前 Gitea 仓库地址: + +- [yuanzhen869/ios-rule-script-subset](https://git.halonice.com/yuanzhen869/ios-rule-script-subset) + 当前同步目标: - `rule/Surge/Lan.list` <- `rule/Surge/Lan/Lan.list` @@ -17,6 +21,10 @@ - [sync_subset.sh](/Users/yuan/Desktop/workspaces/docker/ios-rule-script-subset/scripts/sync_subset.sh) +本地同步凭据放在未提交的: + +- [`.sync.env`](/Users/yuan/Desktop/workspaces/docker/ios-rule-script-subset/.sync.env) + 这个项目给本地 `surgio` 用作远程规则源: -- `https://git.halonice.com/admin/ios-rule-script-subset/raw/branch/main/rule/Surge` +- `https://git.halonice.com/yuanzhen869/ios-rule-script-subset/raw/branch/main/rule/Surge` diff --git a/scripts/build_singbox_rules.py b/scripts/build_singbox_rules.py new file mode 100644 index 0000000..6e10046 --- /dev/null +++ b/scripts/build_singbox_rules.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SURGE_DIR = ROOT / "rule" / "Surge" +SINGBOX_DIR = ROOT / "rule" / "sing-box" + +SINGBOX_DIR.mkdir(parents=True, exist_ok=True) + +UPSTREAM_TARGETS = { + "Lan": "Lan.list", + "Apple": "Apple.list", + "OpenAI": "OpenAI.list", + "Gemini": "Gemini.list", + "Claude": "Claude.list", + "China": "China.list", + "ChinaIPs": "ChinaIPs.list", + "Proxy": "Proxy.list", +} + +FIELD_MAP = { + "DOMAIN": "domain", + "DOMAIN-SUFFIX": "domain_suffix", + "DOMAIN-KEYWORD": "domain_keyword", + "DOMAIN-REGEX": "domain_regex", + "IP-CIDR": "ip_cidr", + "IP-CIDR6": "ip_cidr", + "PROCESS-NAME": "process_name", +} + + +def build_rule_set(entries: dict[str, list[str]]) -> dict: + rules = [] + for field in ("domain", "domain_suffix", "domain_keyword", "domain_regex", "ip_cidr", "process_name"): + values = entries.get(field, []) + if values: + rules.append({field: values}) + return {"version": 3, "rules": rules} + + +def parse_surge_list(path: Path) -> dict: + entries: dict[str, list[str]] = {} + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.strip() + if not line or line.startswith("#"): + continue + parts = [part.strip() for part in line.split(",")] + if len(parts) < 2: + continue + kind = parts[0].upper() + field = FIELD_MAP.get(kind) + if not field: + continue + value = parts[1] + if not value: + continue + entries.setdefault(field, []) + if value not in entries[field]: + entries[field].append(value) + return build_rule_set(entries) + + +MANUAL_RULESETS = { + "ManualBackHome": {"version": 3, "rules": [{"ip_cidr": ["192.168.10.0/24"]}]}, + "ManualReject": {"version": 3, "rules": [{"domain": ["www.axure.com"]}]}, + "ManualAI": {"version": 3, "rules": [{"domain_keyword": ["macked"]}]}, + "ManualDirect": { + "version": 3, + "rules": [ + { + "domain_suffix": [ + "umeng.com", + "umsns.com", + "umindex.com", + "nice.com", + "apple.com", + "alicdn.com", + "qujiangkeji.com", + "banxueketang.com", + "doubj.cn", + "local", + ] + } + ], + }, +} + + +for name, filename in UPSTREAM_TARGETS.items(): + data = parse_surge_list(SURGE_DIR / filename) + (SINGBOX_DIR / f"{name}.json").write_text( + json.dumps(data, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + +for name, payload in MANUAL_RULESETS.items(): + (SINGBOX_DIR / f"{name}.json").write_text( + json.dumps(payload, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) diff --git a/scripts/sync_subset.sh b/scripts/sync_subset.sh index 0e153f4..6205541 100755 --- a/scripts/sync_subset.sh +++ b/scripts/sync_subset.sh @@ -3,28 +3,34 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SURGE_DIR="$ROOT_DIR/rule/Surge" +SINGBOX_DIR="$ROOT_DIR/rule/sing-box" UPSTREAM_BASE="https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge" GITEA_ENV_FILE="$ROOT_DIR/../gitea/.env" LOCAL_SYNC_ENV_FILE="$ROOT_DIR/.sync.env" -if [[ ! -f "$GITEA_ENV_FILE" ]]; then - echo "missing Gitea env file: $GITEA_ENV_FILE" >&2 - exit 1 +if [[ -f "$GITEA_ENV_FILE" ]]; then + source "$GITEA_ENV_FILE" fi -source "$GITEA_ENV_FILE" - if [[ -f "$LOCAL_SYNC_ENV_FILE" ]]; then source "$LOCAL_SYNC_ENV_FILE" fi -GITEA_REPO_OWNER="${GITEA_REPO_OWNER:-admin}" +GITEA_REPO_OWNER="${GITEA_REPO_OWNER:-yuanzhen869}" GITEA_REPO_NAME="${GITEA_REPO_NAME:-ios-rule-script-subset}" -GITEA_SYNC_USERNAME="${GITEA_SYNC_USERNAME:-$GITEA_ADMIN_USERNAME}" -GITEA_SYNC_PASSWORD="${GITEA_SYNC_PASSWORD:-${GITEA_SYNC_TOKEN:-$GITEA_ADMIN_PASSWORD}}" +GITEA_SYNC_USERNAME="${GITEA_SYNC_USERNAME:-${GITEA_ADMIN_USERNAME:-yuanzhen869}}" +GITEA_SYNC_EMAIL="${GITEA_SYNC_EMAIL:-${GITEA_ADMIN_EMAIL:-yuanzhen869@gmail.com}}" +GITEA_SYNC_PASSWORD="${GITEA_SYNC_PASSWORD:-${GITEA_SYNC_TOKEN:-${GITEA_ADMIN_PASSWORD:-}}}" + +if [[ -z "${GITEA_SYNC_PASSWORD}" ]]; then + echo "missing push credential: set GITEA_SYNC_TOKEN or GITEA_ADMIN_PASSWORD" >&2 + exit 1 +fi + GITEA_REMOTE_URL="https://${GITEA_SYNC_USERNAME}:${GITEA_SYNC_PASSWORD}@git.halonice.com/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}.git" mkdir -p "$SURGE_DIR" +mkdir -p "$SINGBOX_DIR" while IFS='|' read -r target src; do [[ -z "$target" ]] && continue @@ -42,6 +48,8 @@ ChinaIPs.list|ChinaIPs/ChinaIPs.list Proxy.list|Proxy/Proxy.list EOF +python3 "$ROOT_DIR/scripts/build_singbox_rules.py" + cd "$ROOT_DIR" if [[ ! -d .git ]]; then @@ -58,6 +66,6 @@ if git diff --cached --quiet; then exit 0 fi -git -c user.name="$GITEA_ADMIN_USERNAME" -c user.email="$GITEA_ADMIN_EMAIL" commit -m "Sync subset from blackmatrix7/ios_rule_script" >/dev/null +git -c user.name="$GITEA_SYNC_USERNAME" -c user.email="$GITEA_SYNC_EMAIL" commit -m "Sync subset from blackmatrix7/ios_rule_script" >/dev/null git push origin main