diff --git a/.github/workflows/normalize.yml b/.github/workflows/normalize.yml index 15fe9e31e8..525d417f60 100644 --- a/.github/workflows/normalize.yml +++ b/.github/workflows/normalize.yml @@ -24,19 +24,22 @@ jobs: go build -o xsr ./cmd/xraysubrefiner - name: Run + env: + XRAY_SUBREFINER_CONFIG: ${{ vars.XRAY_SUBREFINER_CONFIG }} run: | mkdir -p ./export - ./xsr -config config.yaml -out export + ./xsr -out export - name: Commit & push changes env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} run: | cd ./export git init git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git remote add origin https://github-action:$GITHUB_TOKEN@github.com/ircfspace/XrayRefiner.git + git remote add origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git git branch -M export git add . git commit -m "Update export" diff --git a/README.md b/README.md index 6fea7c0322..17e604ba9f 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ A ready-to-use workflow is included at `.github/workflows/normalize.yml`: - Triggers every hour (`cron: "0 * * * *"`). - Can be run manually via `workflow_dispatch`. - Builds the tool, runs it, and commits any changes to `export/` back to the repo. +- Reads the config from the GitHub Actions variable `XRAY_SUBREFINER_CONFIG`. + +To use GitHub variables, add `XRAY_SUBREFINER_CONFIG` in the repo or environment settings. It can contain either the YAML content itself or a file path that exists on the runner. + +The repository no longer ships a default `config.yaml`, so local runs must provide `-config` or set `XRAY_SUBREFINER_CONFIG`. ## Troubleshooting diff --git a/cmd/xraysubrefiner/main.go b/cmd/xraysubrefiner/main.go index 1e67b8b4f8..f09a78477b 100644 --- a/cmd/xraysubrefiner/main.go +++ b/cmd/xraysubrefiner/main.go @@ -20,6 +20,8 @@ import ( "gopkg.in/yaml.v3" ) +const configEnvVar = "XRAY_SUBREFINER_CONFIG" + type Subscription struct { Key string `yaml:"key"` URL string `yaml:"url"` @@ -51,12 +53,20 @@ func must(err error) { } func main() { - cfgPath := flag.String("config", "config.yaml", "path to config.yaml") + cfgPath := flag.String("config", "", "path to config.yaml") outDir := flag.String("out", "export", "output directory") timeout := flag.Duration("timeout", 20*time.Second, "HTTP client timeout") flag.Parse() - cfg, err := loadConfig(*cfgPath) + var explicitConfig bool + flag.Visit(func(f *flag.Flag) { + if f.Name == "config" { + explicitConfig = true + } + }) + + configSource := resolveConfigSource(explicitConfig, *cfgPath, os.Getenv(configEnvVar)) + cfg, err := loadConfig(configSource) must(err) client := &http.Client{Timeout: *timeout} @@ -130,11 +140,44 @@ func main() { } } -func loadConfig(path string) (*Config, error) { - b, err := os.ReadFile(path) - if err != nil { +func resolveConfigSource(explicitConfig bool, flagValue, envValue string) string { + flagValue = strings.TrimSpace(flagValue) + if explicitConfig && flagValue != "" { + return flagValue + } + if envValue != "" { + return envValue + } + return flagValue +} + +func loadConfig(source string) (*Config, error) { + source = strings.TrimSpace(source) + if source == "" { + return nil, fmt.Errorf("config source is empty") + } + + if looksLikeInlineYAML(source) { + return parseConfig([]byte(source)) + } + + if info, err := os.Stat(source); err == nil { + if info.IsDir() { + return nil, fmt.Errorf("config path %q is a directory", source) + } + b, err := os.ReadFile(source) + if err != nil { + return nil, err + } + return parseConfig(b) + } else if err != nil && !os.IsNotExist(err) { return nil, err } + + return nil, fmt.Errorf("config source %q not found", source) +} + +func parseConfig(b []byte) (*Config, error) { var cfg Config if err := yaml.Unmarshal(b, &cfg); err != nil { return nil, err @@ -148,6 +191,16 @@ func loadConfig(path string) (*Config, error) { return &cfg, nil } +func looksLikeInlineYAML(source string) bool { + if strings.Contains(source, "\n") || strings.Contains(source, "\r") { + return true + } + if strings.Contains(source, ":") || strings.Contains(source, "{") || strings.Contains(source, "[") { + return true + } + return false +} + func fetch(client *http.Client, rawurl string) ([]byte, error) { req, err := http.NewRequest("GET", rawurl, nil) if err != nil { diff --git a/cmd/xraysubrefiner/main_test.go b/cmd/xraysubrefiner/main_test.go new file mode 100644 index 0000000000..63dd1a1786 --- /dev/null +++ b/cmd/xraysubrefiner/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestResolveConfigSourcePrefersEnvValueWhenFlagUnset(t *testing.T) { + t.Setenv(configEnvVar, "allowed_schemes:\n - vless\n") + + got := resolveConfigSource(false, "", os.Getenv(configEnvVar)) + if got != "allowed_schemes:\n - vless\n" { + t.Fatalf("resolveConfigSource() = %q, want env content", got) + } +} + +func TestLoadConfigParsesRawYAMLContent(t *testing.T) { + cfg, err := loadConfig("allowed_schemes:\n - vless\nlite:\n n: 12\n") + if err != nil { + t.Fatalf("loadConfig(raw yaml) returned error: %v", err) + } + if len(cfg.AllowedSchemes) != 1 || cfg.AllowedSchemes[0] != "vless" { + t.Fatalf("unexpected allowed schemes: %#v", cfg.AllowedSchemes) + } + if cfg.Lite.N != 12 { + t.Fatalf("expected lite.n to be 12, got %d", cfg.Lite.N) + } +} + +func TestLoadConfigParsesGitHubVariableStyleMultilineYAML(t *testing.T) { + raw := "allowed_schemes:\n - vless\nsubscriptions:\n - key: \"location/FR\"\n url: \"https://example.com/subscription\"\n" + + cfg, err := loadConfig(raw) + if err != nil { + t.Fatalf("loadConfig(multiline yaml) returned error: %v", err) + } + if len(cfg.Subscriptions) != 1 || cfg.Subscriptions[0].Key != "location/FR" { + t.Fatalf("unexpected subscriptions: %#v", cfg.Subscriptions) + } +} + +func TestLoadConfigReadsFilePath(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.yaml") + content := []byte("allowed_schemes:\n - vmess\nsubscriptions: []\n") + if err := os.WriteFile(path, content, 0o644); err != nil { + t.Fatalf("write temp config: %v", err) + } + + cfg, err := loadConfig(path) + if err != nil { + t.Fatalf("loadConfig(file path) returned error: %v", err) + } + if len(cfg.AllowedSchemes) != 1 || cfg.AllowedSchemes[0] != "vmess" { + t.Fatalf("unexpected allowed schemes: %#v", cfg.AllowedSchemes) + } +} + +func TestLoadConfigRejectsEmptySource(t *testing.T) { + if _, err := loadConfig(""); err == nil { + t.Fatal("loadConfig(\"\") expected an error") + } +} diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 6f270620a8..0000000000 --- a/config.yaml +++ /dev/null @@ -1,115 +0,0 @@ -allowed_schemes: ["vless", "vmess", "ss", "trojan"] - -lite: - strategy: per_host - max_total: 100 - per_host_limit: 1 - n: 50 - -subscriptions: - - key: "mahsaXray" - url: "https://raw.githubusercontent.com/MahsaNetConfigTopic/config/refs/heads/main/xray_final.txt" - - key: "code3Vless" - url: "https://raw.githubusercontent.com/code3-dev/code3-dev/refs/heads/main/warp-in-vless#Warp-in-VLESS" - - key: "parvinXs" - url: "https://raw.githubusercontent.com/parvinxs/Submahsanetxsparvin/refs/heads/main/Sub.mahsa.xsparvin" - - key: "psgV6" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/main/lite/subscriptions/xray/normal/vless_ipv6" - - key: "amirAlter" - url: "https://sub.amiralter.com/config-lite" - - key: "darkProxy" - url: "https://raw.githubusercontent.com/darkvpnapp/CloudflarePlus/refs/heads/main/proxy" - - key: "psgMix" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/main/lite/subscriptions/xray/normal/mix" - - key: "hamedP" - url: "https://raw.githubusercontent.com/hamedp-71/Sub_Checker_Creator/refs/heads/main/final.txt" - - key: "soliSpirit" - url: "https://raw.githubusercontent.com/SoliSpirit/v2ray-configs/refs/heads/main/all_configs.txt" - - key: "matinGh" - url: "https://raw.githubusercontent.com/MatinGhanbari/v2ray-configs/main/subscriptions/v2ray/super-sub.txt" - - key: "daniSamadi" - url: "https://raw.githubusercontent.com/Danialsamadi/v2go/refs/heads/main/AllConfigsSub.txt" - - key: "gameFssociety" - url: "https://raw.githubusercontent.com/parvinxs/Game/refs/heads/main/Sub_game_fssociety" - - key: "goidaVpn" - url: "https://raw.githubusercontent.com/AvenCores/goida-vpn-configs/refs/heads/main/githubmirror/26.txt" - - key: "kobabi" - url: "https://raw.githubusercontent.com/liketolivefree/kobabi/main/sub.txt" - - key: "configForge" - url: "https://raw.githubusercontent.com/ShatakVPN/ConfigForge-V2Ray/main/configs/all.txt" - - key: "v2RayRoot" - url: "https://raw.githubusercontent.com/V2RayRoot/V2RayConfig/refs/heads/main/Config/vless.txt" - - key: "azIrani" - url: "https://raw.githubusercontent.com/ToolSeRF/AzIRANi/refs/heads/main/AllConfigs.txt" - - key: "begTemp" - url: "https://manager.begweb.com/api/sub/1" - - key: "Epodonios" - url: "https://raw.githubusercontent.com/Epodonios/v2ray-configs/refs/heads/main/Splitted-By-Protocol/ss.txt" - - key: "hectorSalamanca" - url: "https://chicken.hectorsalamanca.site:2096/Defyx/8pg34ec5ec91nmob" - - key: "mineral" - url: "https://raw.githubusercontent.com/LalatinaHub/Mineral/refs/heads/master/result/nodes" - - key: "awmirx" - url: "https://raw.githubusercontent.com/Awmiroosen/awmirx-v2ray/refs/heads/main/blob/main/v2-sub.txt" - - key: "vpnHub" - url: "https://raw.githubusercontent.com/itsyebekhe/persianvpnhub/refs/heads/export/base64" - - key: "donation" - url: "https://donate-api.defyxvpn.com/donate/subscription/xray" - - key: "fastlane" - url: "https://donate-api.defyxvpn.com/fastlane/all" - - key: "barryFar" - url: "https://raw.githubusercontent.com/barry-far/V2ray-Config/refs/heads/main/All_Configs_base64_Sub.txt" - - key: "aliLapro" - url: "https://raw.githubusercontent.com/ALIILAPRO/v2rayNG-Config/refs/heads/main/server.txt" - - key: "matinGhanbari" - url: "https://raw.githubusercontent.com/MatinGhanbari/v2ray-configs/refs/heads/main/subscriptions/v2ray/all_sub.txt" - - key: "hamedCode" - url: "https://raw.githubusercontent.com/hamedcode/port-based-v2ray-configs/refs/heads/main/sub/vless.txt" - - key: "mahdiBland" - url: "https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/Eternity.txt" - - key: "shatakVpn" - url: "https://raw.githubusercontent.com/ShatakVPN/ConfigForge-V2Ray/main/configs/all.txt" - - key: "justVisiting" - url: "https://raw.githubusercontent.com/justVisiting992/xray-Config-Collector/main/ss_iran.txt" - - key: "mahsaMci" - url: "https://raw.githubusercontent.com/mahsanet/MahsaFreeConfig/refs/heads/main/mci/sub_1.txt" - - key: "mahsaMtn" - url: "https://raw.githubusercontent.com/mahsanet/MahsaFreeConfig/refs/heads/main/mtn/sub_1.txt" - - key: "igareck" - url: "https://raw.githubusercontent.com/igareck/vpn-configs-for-russia/refs/heads/main/BLACK_VLESS_RUS_mobile.txt" - - key: "whoahaow" - url: "https://raw.githubusercontent.com/whoahaow/rjsxrd/refs/heads/main/githubmirror/bypass/bypass-all.txt" - - key: "shadowException" - url: "https://raw.githubusercontent.com/ShadowException/VPN/refs/heads/main/configs/VPN-cat" - - key: "whiteDns" - url: "https://raw.githubusercontent.com/iampedii/whitedns-sub/refs/heads/main/base64.txt" - - key: "masirSefid" - url: "https://raw.githubusercontent.com/masir-sefid/Sub/main/@Masir_Sefid.txt" - - key: "seyedNg" - url: "https://raw.githubusercontent.com/SeyedNG/proxy/refs/heads/main/configs/proxy_configs_tested.txt" - - key: "kavehDonation" - url: "https://kaveh.yebekhe.workers.dev/sub/donations" - - key: "ariataPanel" - url: "https://raw.githubusercontent.com/aristapanell-cell/AriataPanel/refs/heads/main/configs.txt/combined/ALL/ALL.txt" - -locations: - - key: "location/DE" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/DE" - - key: "location/TR" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/TR" - - key: "location/GB" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/GB" - - key: "location/US" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/US" - - key: "location/IR" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/IR" - - key: "location/RU" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/RU" - - key: "location/CN" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/CN" - - key: "location/FI" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/FI" - - key: "location/FR" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/FR" - - key: "location/SE" - url: "https://raw.githubusercontent.com/itsyebekhe/PSG/refs/heads/main/subscriptions/locations/base64/SE"