Skip to content

Update Nodes

Update Nodes #212

Workflow file for this run

name: Update Nodes
on:
schedule:
- cron: '0 */6 * * *'
push:
paths:
- 'core/**'
- 'main.py'
- 'config/**'
- 'requirements.txt'
- 'tools/**'
- 'tests/**'
- '.github/workflows/update.yml'
workflow_dispatch:
inputs:
top_n:
description: 'verified.* 输出节点数(默认 300)'
required: false
default: '300'
test_limit:
description: '送入真测的最大节点数(默认 1500)'
required: false
default: '1500'
min_retain_ratio:
description: '缩水守门比例,0=关闭并强制写入本轮真测结果(默认 0)'
required: false
default: '0'
audit:
description: '同时跑订阅源审计'
type: boolean
default: false
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
concurrency:
group: update-nodes
cancel-in-progress: false
jobs:
update:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
- run: pip install -r requirements.txt
- name: Get sing-box version
id: sb-version
run: |
set -euo pipefail
VERSION=$(curl -fsSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/SagerNet/sing-box/releases/latest | jq -r '.tag_name')
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "::error::Failed to fetch sing-box version"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Latest sing-box: $VERSION"
- name: Cache sing-box binary
id: cache-sb
uses: actions/cache@v4
with:
path: ./sing-box
key: sing-box-${{ runner.os }}-${{ steps.sb-version.outputs.version }}
- name: Download sing-box
if: steps.cache-sb.outputs.cache-hit != 'true'
run: |
set -euo pipefail
VERSION="${{ steps.sb-version.outputs.version }}"
VER_NUM="${VERSION#v}"
curl -fsSL -L -o sb.tar.gz "https://github.com/SagerNet/sing-box/releases/download/${VERSION}/sing-box-${VER_NUM}-linux-amd64.tar.gz"
tar -xzf sb.tar.gz
mv "sing-box-${VER_NUM}-linux-amd64/sing-box" ./sing-box
chmod +x ./sing-box
- name: Audit sources
run: |
python tools/audit_sources.py --sources config/sources.yaml --output output/source_audit.json
- name: Validate code
run: |
python -m compileall -q main.py core tools tests
python tools/validate_config.py --sources config/sources.yaml --filter-rules config/filter_rules.yaml --scoring-rules 'config/scoring*.yaml'
python -m unittest -v tests.test_regressions
- name: Run pipeline
env:
AUTONODES_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
TOP_N="${{ github.event.inputs.top_n || '300' }}"
TEST_LIMIT="${{ github.event.inputs.test_limit || '1500' }}"
MIN_RETAIN_RATIO="${{ github.event.inputs.min_retain_ratio || '0' }}"
[[ "$TOP_N" =~ ^[0-9]+$ ]] || { echo "::error::top_n must be a non-negative integer"; exit 1; }
[[ "$TEST_LIMIT" =~ ^[0-9]+$ ]] || { echo "::error::test_limit must be a non-negative integer"; exit 1; }
[[ "$MIN_RETAIN_RATIO" =~ ^[0-9]+([.][0-9]+)?$ ]] || { echo "::error::min_retain_ratio must be a non-negative number"; exit 1; }
[ "$TOP_N" -le 1000 ] || { echo "::error::top_n must be <= 1000"; exit 1; }
[ "$TEST_LIMIT" -le 3000 ] || { echo "::error::test_limit must be <= 3000"; exit 1; }
python main.py \
--top-n "$TOP_N" \
--test-limit "$TEST_LIMIT" \
--min-retain-ratio "$MIN_RETAIN_RATIO" \
--repo "${{ github.repository }}" \
--branch "${{ github.event.repository.default_branch }}"
- name: Build health report
run: |
python tools/source_scores_report.py --output-dir output --output output/source_scores.md
python tools/scoring_profiles_report.py --profiles 'config/scoring*.yaml' --output output/scoring_profiles.md
python tools/suggest_source_cleanup.py --output-dir output --sources config/sources.yaml --output output/source_cleanup_suggestions.md --json-output output/source_cleanup_suggestions.json
python tools/health_report.py --output-dir output --verified-prefix verified --output output/health_report.json
python tools/daily_report.py --output-dir output --output output/daily_report.md
- name: Write job summary
if: always()
run: |
if [ -f output/stats.json ]; then
python tools/actions_summary.py \
--stats output/stats.json \
--repo "${{ github.repository }}" \
--branch "${{ github.event.repository.default_branch }}"
else
echo "# AutoNodes Run Summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "stats.json was not generated. Check previous steps." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Commit & push
run: |
git config user.email "actions@github.com"
git config user.name "GitHub Actions"
BRANCH="${{ github.event.repository.default_branch }}"
# 显式 fetch 一次确保 origin/$BRANCH 是最新的, 避免 stale shallow ref
git fetch --unshallow origin "$BRANCH" 2>/dev/null || git fetch origin "$BRANCH"
git rm -f output/top.* 2>/dev/null || true
git add output/ README.md 2>/dev/null || true
if git diff --staged --quiet; then
echo "no changes"
else
git commit -m "chore: update nodes $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M CST')"
for i in 1 2 3 4 5; do
if git pull --rebase --autostash --strategy-option=theirs origin "$BRANCH"; then
git push origin HEAD:"$BRANCH" && break
else
git rebase --abort 2>/dev/null || true
echo "::error::rebase failed; refusing to force push automatically"
exit 1
fi
if [ "$i" = "5" ]; then
echo "::error::git push failed after 5 attempts"
exit 1
fi
sleep 5
done
fi
- name: Upload debug artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: autonodes-debug
path: |
output/stats.json
output/source_audit.json
output/health_report.json
output/health_report.md
output/daily_report.md
output/source_scores.md
output/scoring_profiles.md
output/source_cleanup_suggestions.md
output/source_cleanup_suggestions.json
output/trend_history.json
output/*.converter.md
- name: Purge jsDelivr cache
run: |
set -euo pipefail
REPO="${{ github.repository }}"
BRANCH="${{ github.event.repository.default_branch }}"
failed=0
for f in verified.txt verified.yaml verified.json verified.urls verified.converter.md global.txt global.yaml global.json global.urls global.converter.md all.txt all.yaml all.json all.urls all.converter.md stats.json source_audit.json health_report.json health_report.md daily_report.md source_scores.md scoring_profiles.md source_cleanup_suggestions.md source_cleanup_suggestions.json trend_history.json geo_cache.json; do
code=$(curl -s -o /tmp/jsdelivr-purge.json -w '%{http_code}' "https://purge.jsdelivr.net/gh/${REPO}@${BRANCH}/output/${f}" || true)
if [ "$code" -ge 500 ] || [ "$code" = "000" ]; then
echo "::warning::jsDelivr purge failed for output/${f} HTTP ${code}"
failed=$((failed + 1))
else
echo "purged output/${f} HTTP ${code}"
fi
done
echo "jsDelivr purge warnings: ${failed}"