feat: refact syncrepo using go

This commit is contained in:
engigu
2026-02-27 11:35:55 +08:00
parent 54409696c4
commit 578e020f0d
10 changed files with 370 additions and 342 deletions
+1 -1
View File
@@ -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
```
---
+1 -1
View File
@@ -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:
+322
View File
@@ -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
}
-331
View File
@@ -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()
+2 -4
View File
@@ -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
+1 -1
View File
@@ -52,4 +52,4 @@ echo "[entrypoint][Nodejs] npm: $(npm --version) at $(which npm)"
# 启动应用
# ============================
cd /app
exec ./baihu
exec ./baihu server
+3
View File
@@ -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
+9
View File
@@ -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=
+8 -2
View File
@@ -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 加载环境变量
+23 -2
View File
@@ -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)
}
}