From 578e020f0d3e5dc51b05ea2eff46a3463fe91260 Mon Sep 17 00:00:00 2001 From: engigu Date: Fri, 27 Feb 2026 11:35:55 +0800 Subject: [PATCH] feat: refact syncrepo using go --- CHANGELOG.md | 2 +- Makefile | 2 +- cmd/reposync/reposync.go | 322 +++++++++++++++++++ custom/sync.py | 331 -------------------- docker/Dockerfile | 6 +- docker/docker-entrypoint.sh | 2 +- go.mod | 3 + go.sum | 9 + internal/services/tasks/executor_service.go | 10 +- main.go | 25 +- 10 files changed, 370 insertions(+), 342 deletions(-) create mode 100644 cmd/reposync/reposync.go delete mode 100644 custom/sync.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 794382d..b712f42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ export PATH="~/.local/share/mise/bin:~/.local/share/mise/shims:$PATH" ```bash tar -xzvf baihu-linux-amd64.tar.gz chmod +x baihu-linux-amd64 -./baihu-linux-amd64 +./baihu-linux-amd64 server ``` --- diff --git a/Makefile b/Makefile index 082dcd4..92b2581 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ clean: run: @mkdir -p bin $(GOBUILD) -o $(BINARY) main.go - ./$(BINARY) + ./$(BINARY) server # Development run with hot reload (both frontend and backend) dev: diff --git a/cmd/reposync/reposync.go b/cmd/reposync/reposync.go new file mode 100644 index 0000000..cbe0ee3 --- /dev/null +++ b/cmd/reposync/reposync.go @@ -0,0 +1,322 @@ +package reposync + +import ( + "flag" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +type Config struct { + SourceType string + SourceURL string + TargetPath string + Branch string + Path string + SingleFile bool + Proxy string + ProxyURL string + AuthToken string + HttpProxy string +} + +func Run(args []string) { + fs := flag.NewFlagSet("reposync", flag.ExitOnError) + var cfg Config + fs.StringVar(&cfg.SourceType, "source-type", "git", "Source type: git or url") + fs.StringVar(&cfg.SourceURL, "source-url", "", "Source url") + fs.StringVar(&cfg.TargetPath, "target-path", "", "Target path") + fs.StringVar(&cfg.Branch, "branch", "", "Branch") + fs.StringVar(&cfg.Path, "path", "", "Path for sparse checkout") + fs.BoolVar(&cfg.SingleFile, "single-file", false, "Single file mode") + fs.StringVar(&cfg.Proxy, "proxy", "none", "Proxy type") + fs.StringVar(&cfg.ProxyURL, "proxy-url", "", "Custom proxy url") + fs.StringVar(&cfg.AuthToken, "auth-token", "", "Auth token") + fs.StringVar(&cfg.HttpProxy, "http-proxy", "", "Http proxy") + + fs.Parse(args) + + if cfg.SourceURL == "" || cfg.TargetPath == "" { + fmt.Println("Error: --source-url and --target-path are required") + os.Exit(1) + } + + fmt.Printf("Arguments: %v\n", args) + + if cfg.SourceType == "git" { + syncGit(cfg) + } else { + syncURL(cfg) + } +} + +func syncGit(cfg Config) { + env := os.Environ() + + if isRawFileURL(cfg.SourceURL) { + fmt.Println("Raw file URL detected, switching to URL mode") + syncURL(cfg) + return + } + + if cfg.HttpProxy != "" { + env = append(env, "http_proxy="+cfg.HttpProxy, "https_proxy="+cfg.HttpProxy) + } + + repoURL := buildProxyURL(cfg.SourceURL, cfg.Proxy, cfg.ProxyURL) + if cfg.AuthToken != "" && strings.HasPrefix(repoURL, "https://") { + repoURL = strings.Replace(repoURL, "https://", "https://"+cfg.AuthToken+"@", 1) + } + + dest := cfg.TargetPath + + if cfg.Path != "" && cfg.SingleFile { + syncGitFile(cfg, repoURL, env) + return + } + + gitDir := filepath.Join(dest, ".git") + if isDir(dest) && !pathExists(gitDir) { + repoName := getRepoName(cfg.SourceURL) + dest = filepath.Join(dest, repoName) + fmt.Printf("Appending repo name to target path: %s\n", dest) + gitDir = filepath.Join(dest, ".git") + } + + if pathExists(gitDir) { + fmt.Println("Executing git pull") + if cfg.Branch != "" { + runCmd([]string{"git", "checkout", cfg.Branch}, dest, env) + } + runCmd([]string{"git", "pull"}, dest, env) + } else { + fmt.Println("Executing git clone") + parentDir := filepath.Dir(dest) + if parentDir != "" { + os.MkdirAll(parentDir, 0755) + } + + if pathExists(dest) && !isDirEmpty(dest) { + fmt.Printf("Error: Target dir '%s' is not empty.\n", dest) + os.Exit(1) + } + + cloneCmd := []string{"git", "clone", "--depth", "1"} + if cfg.Branch != "" { + cloneCmd = append(cloneCmd, "-b", cfg.Branch) + } + + if cfg.Path != "" { + cloneCmd = append(cloneCmd, "--filter=blob:none", "--no-checkout", repoURL, dest) + runCmd(cloneCmd, "", env) + runCmd([]string{"git", "sparse-checkout", "init", "--cone"}, dest, env) + runCmd([]string{"git", "sparse-checkout", "set", cfg.Path}, dest, env) + runCmd([]string{"git", "checkout"}, dest, env) + } else { + cloneCmd = append(cloneCmd, repoURL, dest) + runCmd(cloneCmd, "", env) + } + } + fmt.Println("Sync completed") +} + +func syncURL(cfg Config) { + downloadURL := buildProxyURL(cfg.SourceURL, cfg.Proxy, cfg.ProxyURL) + dest := cfg.TargetPath + + if isDir(dest) || strings.HasSuffix(dest, string(os.PathSeparator)) || strings.HasSuffix(dest, "/") { + urlPath := strings.Split(cfg.SourceURL, "?")[0] + filename := filepath.Base(urlPath) + if filename == "" { + filename = "downloaded_file" + } + dest = filepath.Join(dest, filename) + fmt.Printf("Target file: %s\n", dest) + } + + downloadFile(downloadURL, dest, cfg.AuthToken) +} + +func syncGitFile(cfg Config, repoURL string, env []string) { + sourceURL := cfg.SourceURL + filePath := cfg.Path + dest := cfg.TargetPath + + if isDir(dest) || strings.HasSuffix(dest, string(os.PathSeparator)) || strings.HasSuffix(dest, "/") { + filename := filepath.Base(filePath) + dest = filepath.Join(dest, filename) + fmt.Printf("Auto corrected path to: %s\n", dest) + } + + branch := cfg.Branch + if branch == "" { + branch = getRemoteDefaultBranch(repoURL, env) + } + + cleanURL := strings.TrimSuffix(cfg.SourceURL, ".git") + rawURL := "" + + if strings.Contains(sourceURL, "github.com") { + base := strings.Replace(strings.TrimSuffix(cfg.SourceURL, ".git"), "github.com", "raw.githubusercontent.com", 1) + rawURL = fmt.Sprintf("%s/%s/%s", base, branch, filePath) + } else if strings.Contains(sourceURL, "gitlab.com") { + rawURL = fmt.Sprintf("%s/-/raw/%s/%s", cleanURL, branch, filePath) + } else if strings.Contains(sourceURL, "gitee.com") { + rawURL = fmt.Sprintf("%s/raw/%s/%s", cleanURL, branch, filePath) + } else { + rawURL = fmt.Sprintf("%s/raw/%s/%s", cleanURL, branch, filePath) + } + + rawURL = buildProxyURL(rawURL, cfg.Proxy, cfg.ProxyURL) + downloadFile(rawURL, dest, cfg.AuthToken) +} + +func getRemoteDefaultBranch(repoURL string, env []string) string { + fmt.Printf("Detecting remote default branch: %s\n", repoURL) + cmd := exec.Command("git", "ls-remote", "--symref", repoURL, "HEAD") + cmd.Env = env + out, err := cmd.Output() + if err == nil { + lines := strings.Split(string(out), "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) >= 2 && parts[0] == "ref:" && strings.Contains(parts[1], "refs/heads/") { + branch := strings.TrimPrefix(parts[1], "refs/heads/") + fmt.Printf("Detected branch: %s\n", branch) + return branch + } + } + } + fmt.Println("Failed to detect, using 'main'") + return "main" +} + +func buildProxyURL(url string, proxyType string, proxyURL string) string { + if proxyType == "" || proxyType == "none" { + return url + } + base := "" + if proxyType == "ghproxy" { + base = "https://gh-proxy.com/" + } else if proxyType == "mirror" { + base = "https://mirror.ghproxy.com/" + } else if proxyType == "custom" && proxyURL != "" { + base = strings.TrimSuffix(proxyURL, "/") + "/" + } + + if base != "" && strings.HasPrefix(url, "http") { + return base + url + } + return url +} + +func downloadFile(url, dest, authToken string) { + fmt.Printf("Downloading: %s\n", url) + fmt.Printf("To: %s\n", dest) + + parentDir := filepath.Dir(dest) + if parentDir != "" { + os.MkdirAll(parentDir, 0755) + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Printf("Download prep failed: %v\n", err) + os.Exit(1) + } + if authToken != "" { + req.Header.Set("Authorization", "token "+authToken) + } + req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; reposync)") + + client := &http.Client{Timeout: 300 * time.Second} + resp, err := client.Do(req) + if err != nil { + fmt.Printf("Download request failed: %v\n", err) + os.Exit(1) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + fmt.Printf("Download failed, HTTP code: %d\n", resp.StatusCode) + os.Exit(1) + } + + out, err := os.Create(dest) + if err != nil { + fmt.Printf("Failed to create file: %v\n", err) + os.Exit(1) + } + defer out.Close() + + n, err := io.Copy(out, resp.Body) + if err != nil { + fmt.Printf("Failed to write data: %v\n", err) + os.Exit(1) + } + + fmt.Printf("File size: %d bytes\n", n) + fmt.Println("Download completed") +} + +func isRawFileURL(url string) bool { + rawPatterns := []string{ + "raw.githubusercontent.com", + "/raw/", + "/-/raw/", + "/blob/", + } + for _, p := range rawPatterns { + if strings.Contains(url, p) { + return true + } + } + return false +} + +func getRepoName(url string) string { + u := strings.TrimSuffix(url, "/") + u = strings.TrimSuffix(u, ".git") + return filepath.Base(u) +} + +func runCmd(args []string, dir string, env []string) { + fmt.Printf(">> %s\n", strings.Join(args, " ")) + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = dir + cmd.Env = env + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Printf("Command failed: %v\n", err) + os.Exit(1) + } +} + +func isDir(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} + +func pathExists(path string) bool { + _, err := os.Stat(path) + return err == nil || !os.IsNotExist(err) +} + +func isDirEmpty(path string) bool { + f, err := os.Open(path) + if err != nil { + return false + } + defer f.Close() + _, err = f.Readdirnames(1) + return err == io.EOF +} diff --git a/custom/sync.py b/custom/sync.py deleted file mode 100644 index ca9b387..0000000 --- a/custom/sync.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import argparse -import os -import subprocess -import sys -import urllib.error -import urllib.request -from typing import TYPE_CHECKING, Protocol, cast - -# 仅在类型检查时导入,避免运行时依赖 -if TYPE_CHECKING: - from http.client import HTTPResponse - - -class SyncArgs(Protocol): - """用于类型检查的参数协议,映射 argparse 的解析结果""" - source_type: str - source_url: str - target_path: str - branch: str | None - path: str | None - single_file: bool - proxy: str | None - proxy_url: str | None - auth_token: str | None - http_proxy: str | None - - -def run( - cmd: list[str], - env: dict[str, str] | None = None, - cwd: str | None = None, - capture_output: bool = False -) -> str | None: - """ - 执行系统命令。 - - Args: - cmd: 命令列表 - env: 环境变量字典 - cwd: 当前工作目录 - capture_output: 是否捕获输出。 - """ - if not capture_output: - print(">>", " ".join(cmd)) - - if capture_output: - # 捕获模式:不打印到屏幕,返回 stdout - result = subprocess.run( - cmd, - cwd=cwd, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - timeout=30, - encoding="utf-8", - errors="ignore" - ) - else: - # 直通模式:直接打印到屏幕 - result = subprocess.run( - cmd, - cwd=cwd, - env=env, - stdout=sys.stdout, - stderr=sys.stderr, - ) - - if result.returncode != 0: - if capture_output: - return None - sys.exit(result.returncode) - - if capture_output: - return str(result.stdout).strip() - - return str(result) - - -def get_remote_default_branch(repo_url: str, env: dict[str, str]) -> str: - """检测远程仓库的默认分支名称(通常是 main 或 master)。""" - print(f"正在检测远程仓库默认分支: {repo_url}") - cmd = ["git", "ls-remote", "--symref", repo_url, "HEAD"] - output = run(cmd, env=env, capture_output=True) - - if isinstance(output, str): - for line in output.splitlines(): - parts = line.split() - if len(parts) >= 2 and parts[0] == "ref:" and "refs/heads/" in parts[1]: - branch = parts[1].removeprefix("refs/heads/") - print(f"检测到默认分支: {branch}") - return branch - - print("无法检测到默认分支,回退使用 'main'") - return "main" - - -def build_proxy_url(url: str, proxy_type: str | None, proxy_url: str | None) -> str: - """根据配置构建带有代理前缀的 URL。""" - if not proxy_type or proxy_type == "none": - return url - - proxy_base = "" - if proxy_type == "ghproxy": - proxy_base = "https://gh-proxy.com/" - elif proxy_type == "mirror": - proxy_base = "https://mirror.ghproxy.com/" - elif proxy_type == "custom" and proxy_url: - proxy_base = proxy_url.rstrip("/") + "/" - - if proxy_base and url.startswith("http"): - return proxy_base + url - - return url - - -def _download_file(url: str, dest: str, auth_token: str | None) -> None: - """ - 内部通用下载函数,处理请求构建、Token 认证和文件写入。 - """ - print(f"下载地址: {url}") - print(f"目标路径: {dest}") - - parent_dir = os.path.dirname(dest) - if parent_dir: - os.makedirs(parent_dir, exist_ok=True) - - req = urllib.request.Request(url) - if auth_token: - req.add_header("Authorization", f"token {auth_token}") - req.add_header("User-Agent", "Mozilla/5.0 (compatible; sync.py)") - - try: - with cast("HTTPResponse", urllib.request.urlopen(req, timeout=300)) as response: - content: bytes = response.read() - with open(dest, "wb") as f: - _ = f.write(content) - - print(f"文件大小: {len(content)} 字节") - print("下载完成") - except urllib.error.HTTPError as e: - print(f"下载失败, HTTP 状态码: {e.code}") - sys.exit(1) - except urllib.error.URLError as e: - print(f"下载失败: {e.reason}") - sys.exit(1) - - -def sync_git_file(args: SyncArgs, repo_url: str, env: dict[str, str]) -> None: - """ - 从 Git 仓库同步单个文件(通过构造 Raw URL 下载)。 - """ - source_url = args.source_url - file_path = args.path or "" - dest = args.target_path - - # 如果目标是目录,自动拼接文件名 - if os.path.isdir(dest) or dest.endswith(os.sep) or (os.altsep and dest.endswith(os.altsep)): - filename = os.path.basename(file_path) - dest = os.path.join(dest, filename) - print(f"检测到目标路径为目录 '{args.target_path}',自动修正为: '{dest}'") - - branch = args.branch or get_remote_default_branch(repo_url, env) - - # 构建 raw 文件 URL - # GitHub: https://github.com/user/repo -> https://raw.githubusercontent.com/user/repo/branch/path - # GitLab: https://gitlab.com/user/repo -> https://gitlab.com/user/repo/-/raw/branch/path - # Gitee: https://gitee.com/user/repo -> https://gitee.com/user/repo/raw/branch/path - - clean_url = args.source_url.rstrip(".git") - raw_url = "" - - if "github.com" in source_url: - base = args.source_url.replace("github.com", "raw.githubusercontent.com").rstrip(".git") - raw_url = f"{base}/{branch}/{file_path}" - elif "gitlab.com" in source_url: - raw_url = f"{clean_url}/-/raw/{branch}/{file_path}" - elif "gitee.com" in source_url: - raw_url = f"{clean_url}/raw/{branch}/{file_path}" - else: - # 通用策略:尝试 GitHub 风格 - raw_url = f"{clean_url}/raw/{branch}/{file_path}" - - raw_url = build_proxy_url(raw_url, args.proxy, args.proxy_url) - - # 调用通用下载函数 - _download_file(raw_url, dest, args.auth_token) - - -def is_raw_file_url(url: str) -> bool: - """判断 URL 是否已经是 Raw 文件链接。""" - raw_patterns = [ - "raw.githubusercontent.com", - "/raw/", - "/-/raw/", - "/blob/", - ] - return any(pattern in url for pattern in raw_patterns) - - -def get_repo_name(url: str) -> str: - """从 Git URL 中提取仓库名称。""" - url_stripped = url.rstrip("/").rstrip(".git") - return os.path.basename(url_stripped) - - -def sync_git(args: SyncArgs) -> None: - """处理 Git 类型的同步逻辑(Clone, Pull 或 Sparse Checkout)。""" - env = os.environ.copy() - - if is_raw_file_url(args.source_url): - print("检测到 raw 文件 URL,自动切换到 URL 下载模式") - sync_url(args) - return - - if args.http_proxy: - env["http_proxy"] = args.http_proxy - env["https_proxy"] = args.http_proxy - - repo_url = build_proxy_url(args.source_url, args.proxy, args.proxy_url) - - if args.auth_token and repo_url.startswith("https://"): - repo_url = repo_url.replace("https://", f"https://{args.auth_token}@") - - dest = args.target_path - - # 单文件模式:使用 Raw URL 下载 - if args.path and args.single_file: - sync_git_file(args, repo_url, env) - return - - # 自动追加仓库名逻辑 - git_dir = os.path.join(dest, ".git") - if os.path.isdir(dest) and not os.path.exists(git_dir): - repo_name = get_repo_name(args.source_url) - dest = os.path.join(dest, repo_name) - print(f"目标路径自动追加仓库名: {dest}") - git_dir = os.path.join(dest, ".git") - - if os.path.exists(git_dir): - print("检测到已存在仓库,执行 git pull") - if args.branch: - _ = run(["git", "checkout", args.branch], cwd=dest, env=env) - _ = run(["git", "pull"], cwd=dest, env=env) - else: - print("执行 git clone") - parent_dir = os.path.dirname(dest) - if parent_dir: - os.makedirs(parent_dir, exist_ok=True) - - if os.path.exists(dest) and os.listdir(dest): - print(f"错误: 目标目录 '{dest}' 已存在且不为空,无法执行 git clone") - print("提示: 请清空目标目录或指定一个新目录") - sys.exit(1) - - clone_cmd = ["git", "clone", "--depth", "1"] - - if args.branch: - clone_cmd.extend(["-b", args.branch]) - - # 稀疏检出 (Sparse Checkout) - if args.path: - clone_cmd.extend(["--filter=blob:none", "--no-checkout", repo_url, dest]) - _ = run(clone_cmd, env=env) - _ = run(["git", "sparse-checkout", "init", "--cone"], cwd=dest, env=env) - _ = run(["git", "sparse-checkout", "set", args.path], cwd=dest, env=env) - _ = run(["git", "checkout"], cwd=dest, env=env) - else: - # 普通 Clone - clone_cmd.extend([repo_url, dest]) - _ = run(clone_cmd, env=env) - - print("同步完成") - - -def sync_url(args: SyncArgs) -> None: - """处理普通 URL 文件下载逻辑。""" - download_url = build_proxy_url(args.source_url, args.proxy, args.proxy_url) - print(f"下载地址: {download_url}") - - dest = args.target_path - - if os.path.isdir(dest) or dest.endswith("/"): - url_path = args.source_url.split("?")[0] - filename = os.path.basename(url_path) or "downloaded_file" - dest = os.path.join(dest, filename) - print(f"目标文件: {dest}") - - # 调用通用下载函数 - _download_file(download_url, dest, args.auth_token) - - -def main() -> None: - parser = argparse.ArgumentParser(description="仓库/文件同步工具") - - _ = parser.add_argument("--source-type", choices=["git", "url"], default="git", - help="源类型: git(Git仓库) 或 url(URL下载)") - _ = parser.add_argument("--source-url", required=True, - help="源地址(Git仓库URL或文件URL)") - _ = parser.add_argument("--target-path", required=True, - help="目标路径") - _ = parser.add_argument("--branch", - help="Git 分支名(仅 git 类型有效)") - _ = parser.add_argument("--path", - help="仅拉取指定文件或目录(仅 git 类型有效)") - _ = parser.add_argument("--single-file", action="store_true", - help="单文件模式,直接下载指定文件而非 sparse-checkout(需配合 --path 使用)") - _ = parser.add_argument("--proxy", choices=["none", "ghproxy", "mirror", "custom"], default="none", - help="代理类型") - _ = parser.add_argument("--proxy-url", - help="自定义代理地址(仅 proxy=custom 时有效)") - _ = parser.add_argument("--auth-token", - help="认证 Token(用于私有仓库)") - _ = parser.add_argument("--http-proxy", - help="HTTP 代理(如 http://127.0.0.1:7890)") - - # 使用 cast 将 Namespace 转换为 SyncArgs 协议,满足静态类型检查 - args = cast(SyncArgs, cast(object, parser.parse_args())) - - print("参数:", " ".join(sys.argv[1:])) - - if args.source_type == "git": - sync_git(args) - else: - sync_url(args) - - -if __name__ == "__main__": - main() diff --git a/docker/Dockerfile b/docker/Dockerfile index cc59d7b..6e7eb04 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -135,14 +135,12 @@ COPY --from=backend-builder /app/baihu . COPY --from=backend-builder /app/configs ./configs COPY docker/docker-entrypoint.sh . -# Copy sync.py to /opt -COPY custom/sync.py /opt/sync.py + # Copy agent binaries to /opt/agent COPY --from=agent-builder /opt/agent /opt/agent -RUN chmod +x /opt/sync.py \ - && chmod +x docker-entrypoint.sh \ +RUN chmod +x docker-entrypoint.sh \ && echo "set encoding=utf-8" >> /etc/vim/vimrc EXPOSE 8052 diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index a5b5b4d..d5c6079 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -52,4 +52,4 @@ echo "[entrypoint][Nodejs] npm: $(npm --version) at $(which npm)" # 启动应用 # ============================ cd /app -exec ./baihu \ No newline at end of file +exec ./baihu server \ No newline at end of file diff --git a/go.mod b/go.mod index cf2e5a7..63558b8 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/gofrs/flock v0.13.0 // indirect github.com/gohugoio/hugo v0.149.1 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.6 // indirect @@ -66,6 +67,8 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.9.2 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/tdewolff/parse/v2 v2.8.3 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect diff --git a/go.sum b/go.sum index 99cd507..83e3d61 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhD github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -145,6 +146,8 @@ github.com/hairyhenderson/go-codeowners v0.7.0 h1:s0W4wF8bdsBEjTWzwzSlsatSthWtTA github.com/hairyhenderson/go-codeowners v0.7.0/go.mod h1:wUlNgQ3QjqC4z8DnM5nnCYVq/icpqXJyJOukKx5U8/Q= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -241,6 +244,7 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -251,6 +255,10 @@ github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -294,6 +302,7 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= diff --git a/internal/services/tasks/executor_service.go b/internal/services/tasks/executor_service.go index cf5d95d..45efab2 100644 --- a/internal/services/tasks/executor_service.go +++ b/internal/services/tasks/executor_service.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "os" "path/filepath" "strings" "sync" @@ -819,8 +820,13 @@ func (es *ExecutorService) BuildRepoCommand(task *models.Task) (string, string) } absTargetPath, _ := filepath.Abs(targetPath) + exePath, err := os.Executable() + if err != nil { + exePath = "baihu" // Fallback if executable path can't be found + } + args := []string{ - "/opt/sync.py", + "reposync", "--source-type", config.SourceType, "--source-url", config.SourceURL, "--target-path", absTargetPath, @@ -844,7 +850,7 @@ func (es *ExecutorService) BuildRepoCommand(task *models.Task) (string, string) args = append(args, "--auth-token", config.AuthToken) } - return "python3 " + strings.Join(args, " "), "/opt" + return exePath + " " + strings.Join(args, " "), filepath.Dir(exePath) } // loadEnvVars 加载环境变量 diff --git a/main.go b/main.go index 519e34a..65c9934 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,28 @@ package main -import "github.com/engigu/baihu-panel/internal/bootstrap" +import ( + "fmt" + "os" + + "github.com/engigu/baihu-panel/cmd/reposync" + "github.com/engigu/baihu-panel/internal/bootstrap" +) func main() { - bootstrap.New().Run() + if len(os.Args) < 2 { + bootstrap.New().Run() + return + } + + cmd := os.Args[1] + switch cmd { + case "server": + bootstrap.New().Run() + case "reposync": + reposync.Run(os.Args[2:]) + default: + fmt.Printf("Unknown command: %s\n", cmd) + fmt.Println("Available commands: server, reposync") + os.Exit(1) + } }