Skip to content

Commit a334555

Browse files
adaexclaude
andcommitted
feat: TCP sequential retry, deploy to sub branch, add AGENTS.md
- TCP retries now run in 3 sequential rounds with 3s buffer between each - Deploy target changed from main to sub branch - Commit message format: pure timestamp with seconds - Remove updated.txt from output - Add AGENTS.md with workflow conventions (CLAUDE.md symlink) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f07eba3 commit a334555

6 files changed

Lines changed: 60 additions & 35 deletions

File tree

.github/workflows/update.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ jobs:
4545
- name: Generate output
4646
run: npx tsx src/output.ts
4747

48-
- name: Deploy to main branch
48+
- name: Deploy to sub branch
4949
run: |
5050
cd output
5151
git init
5252
git config user.name "github-actions[bot]"
5353
git config user.email "github-actions[bot]@users.noreply.github.com"
5454
git add -A
55-
git commit -m "$(date -u '+%Y-%m-%d %H:%M') update"
56-
git push --force "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" HEAD:main
55+
git commit -m "$(date -u '+%Y-%m-%d %H:%M:%S')"
56+
git push --force "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" HEAD:sub

AGENTS.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Agents Guide
2+
3+
## Workflow
4+
5+
- Always ask before pushing to remote. Never push without user confirmation.
6+
- After pushing, monitor CI run status (`gh run watch`) and report the result to the user.
7+
- Commit messages and PR descriptions in English.
8+
9+
## Branch Strategy
10+
11+
- `dev` branch: source code, CI workflow triggers on push
12+
- `sub` branch: output files only (subscriptions), deployed by CI via force-push
13+
14+
## Code Style
15+
16+
- TypeScript, strict mode
17+
- No comments unless explaining a non-obvious WHY
18+
- Shared utilities go in `src/utils.ts`

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ Free Mihomo/Clash/sing-box subscription aggregator, updated hourly.
66

77
| Description | URL |
88
|-------------|-----|
9-
| Curated mihomo config | `https://raw.githubusercontent.com/imaex/free-sub/main/curated.yaml` |
10-
| Curated sing-box config | `https://raw.githubusercontent.com/imaex/free-sub/main/curated-singbox.json` |
11-
| Curated nodes only | `https://raw.githubusercontent.com/imaex/free-sub/main/curated-nodes.yaml` |
12-
| ACL4SSR config | `https://raw.githubusercontent.com/imaex/free-sub/main/acl4ssr.yaml` |
13-
| ACL4SSR nodes only | `https://raw.githubusercontent.com/imaex/free-sub/main/acl4ssr-nodes.yaml` |
14-
| freeSub config | `https://raw.githubusercontent.com/imaex/free-sub/main/freesub.yaml` |
15-
| freeSub nodes only | `https://raw.githubusercontent.com/imaex/free-sub/main/freesub-nodes.yaml` |
16-
| All nodes | `https://raw.githubusercontent.com/imaex/free-sub/main/all-nodes.yaml` |
9+
| Curated mihomo config | `https://raw.githubusercontent.com/imaex/free-sub/sub/curated.yaml` |
10+
| Curated sing-box config | `https://raw.githubusercontent.com/imaex/free-sub/sub/curated-singbox.json` |
11+
| Curated nodes only | `https://raw.githubusercontent.com/imaex/free-sub/sub/curated-nodes.yaml` |
12+
| ACL4SSR config | `https://raw.githubusercontent.com/imaex/free-sub/sub/acl4ssr.yaml` |
13+
| ACL4SSR nodes only | `https://raw.githubusercontent.com/imaex/free-sub/sub/acl4ssr-nodes.yaml` |
14+
| FreeSub config | `https://raw.githubusercontent.com/imaex/free-sub/sub/freesub.yaml` |
15+
| FreeSub nodes only | `https://raw.githubusercontent.com/imaex/free-sub/sub/freesub-nodes.yaml` |
16+
| All nodes | `https://raw.githubusercontent.com/imaex/free-sub/sub/all-nodes.yaml` |
1717

1818
CN mirror: replace `https://raw.githubusercontent.com/` with `https://gh-proxy.org/raw.githubusercontent.com/`
1919

@@ -22,4 +22,4 @@ CN mirror: replace `https://raw.githubusercontent.com/` with `https://gh-proxy.o
2222
1. Fetch 25 sources hourly, normalize node names, deduplicate by `type|server|port`
2323
2. TCP connectivity test (5s timeout, 3 retries) to filter dead nodes
2424
3. Curated category sorted by speed / multiplier / loss rate, top-N per country (HK 100, US 50, others 20)
25-
4. Generate full configs, node lists and sing-box format, push to main branch
25+
4. Generate full configs, node lists and sing-box format, push to sub branch

src/fetch.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ function buildNodesOnly(proxies: Proxy[]): { proxies: Proxy[] } {
171171
const TCP_TIMEOUT = 5000;
172172
const TCP_CONCURRENCY = 50;
173173
const TCP_RETRIES = 3;
174+
const TCP_RETRY_DELAY = 3000;
174175

175176
function tcpCheckOnce(host: string, port: number): Promise<boolean> {
176177
return new Promise((resolve) => {
@@ -181,14 +182,21 @@ function tcpCheckOnce(host: string, port: number): Promise<boolean> {
181182
});
182183
}
183184

184-
async function tcpCheck(host: string, port: number): Promise<boolean> {
185-
try {
186-
return await Promise.any(
187-
Array.from({ length: TCP_RETRIES }, () => tcpCheckOnce(host, port)),
188-
);
189-
} catch {
190-
return false;
185+
async function tcpBatch(endpoints: [string, { host: string; port: number }][]): Promise<Set<string>> {
186+
const aliveSet = new Set<string>();
187+
let tested = 0;
188+
for (let i = 0; i < endpoints.length; i += TCP_CONCURRENCY) {
189+
const batch = endpoints.slice(i, i + TCP_CONCURRENCY);
190+
const results = await Promise.all(batch.map(async ([key, ep]) => ({ key, ok: await tcpCheckOnce(ep.host, ep.port) })));
191+
for (const r of results) {
192+
if (r.ok) aliveSet.add(r.key);
193+
}
194+
tested += batch.length;
195+
const pct = Math.round((tested / endpoints.length) * 100);
196+
process.stdout.write(`\r ${tested}/${endpoints.length} (${pct}%) 存活: ${aliveSet.size}`);
191197
}
198+
process.stdout.write('\n');
199+
return aliveSet;
192200
}
193201

194202
async function tcpFilterAll(proxies: Proxy[]): Promise<Set<string>> {
@@ -198,24 +206,25 @@ async function tcpFilterAll(proxies: Proxy[]): Promise<Set<string>> {
198206
if (!uniqueEndpoints.has(key)) uniqueEndpoints.set(key, { host: p.server, port: p.port });
199207
}
200208

201-
const endpoints = [...uniqueEndpoints.entries()];
209+
const allEndpoints = [...uniqueEndpoints.entries()];
202210
const aliveSet = new Set<string>();
203-
let tested = 0;
211+
let remaining = allEndpoints;
204212

205-
console.log(`\nTCP 连通性测试 (${endpoints.length} 个唯一端点)...`);
213+
console.log(`\nTCP 连通性测试 (${allEndpoints.length} 个唯一端点, ${TCP_RETRIES})...`);
206214

207-
for (let i = 0; i < endpoints.length; i += TCP_CONCURRENCY) {
208-
const batch = endpoints.slice(i, i + TCP_CONCURRENCY);
209-
const results = await Promise.all(batch.map(async ([key, ep]) => ({ key, ok: await tcpCheck(ep.host, ep.port) })));
210-
for (const r of results) {
211-
if (r.ok) aliveSet.add(r.key);
215+
for (let round = 1; round <= TCP_RETRIES; round++) {
216+
if (remaining.length === 0) break;
217+
if (round > 1) {
218+
console.log(` 等待 ${TCP_RETRY_DELAY / 1000}s 后重试...`);
219+
await new Promise(r => setTimeout(r, TCP_RETRY_DELAY));
212220
}
213-
tested += batch.length;
214-
const pct = Math.round((tested / endpoints.length) * 100);
215-
process.stdout.write(`\r TCP: ${tested}/${endpoints.length} (${pct}%) 存活: ${aliveSet.size}`);
221+
console.log(` 第 ${round} 轮 (${remaining.length} 个端点):`);
222+
const roundAlive = await tcpBatch(remaining);
223+
for (const key of roundAlive) aliveSet.add(key);
224+
remaining = remaining.filter(([key]) => !roundAlive.has(key));
216225
}
217-
process.stdout.write('\n');
218-
console.log(`TCP 连通率: ${aliveSet.size}/${endpoints.length} (${Math.round((aliveSet.size / endpoints.length) * 100)}%)`);
226+
227+
console.log(`TCP 连通率: ${aliveSet.size}/${allEndpoints.length} (${Math.round((aliveSet.size / allEndpoints.length) * 100)}%)`);
219228
return aliveSet;
220229
}
221230

src/output.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,6 @@ function main() {
421421
// all nodes
422422
writeYaml(path.join(OUTPUT_DIR, 'all-nodes.yaml'), { proxies: allProxies });
423423
console.log(`已写入 output/all-nodes.yaml (${allProxies.length} 节点)`);
424-
425-
// timestamp
426-
fs.writeFileSync(path.join(OUTPUT_DIR, 'updated.txt'), new Date().toISOString(), 'utf-8');
427424
}
428425

429426
main();

0 commit comments

Comments
 (0)