feat: add pre and post command
This commit is contained in:
+14
-1
@@ -46,6 +46,8 @@ type AgentTask struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
PreCommand string `json:"pre_command"`
|
||||
PostCommand string `json:"post_command"`
|
||||
Schedule string `json:"schedule"`
|
||||
Cron string `json:"cron"`
|
||||
Timeout int `json:"timeout"`
|
||||
@@ -69,6 +71,14 @@ func (t *AgentTask) GetCommand() string {
|
||||
return t.Command
|
||||
}
|
||||
|
||||
func (t *AgentTask) GetPreCommand() string {
|
||||
return t.PreCommand
|
||||
}
|
||||
|
||||
func (t *AgentTask) GetPostCommand() string {
|
||||
return t.PostCommand
|
||||
}
|
||||
|
||||
func (t *AgentTask) GetTimeout() int {
|
||||
return t.Timeout
|
||||
}
|
||||
@@ -527,7 +537,9 @@ func (a *Agent) handleExecute(data json.RawMessage) {
|
||||
TaskID: task.ID,
|
||||
LogID: req.LogID,
|
||||
Name: task.Name,
|
||||
Command: task.Command,
|
||||
Command: task.Command,
|
||||
PreCommand: task.PreCommand,
|
||||
PostCommand: task.PostCommand,
|
||||
WorkDir: task.WorkDir,
|
||||
Envs: executor.ParseEnvVars(envs),
|
||||
Secrets: req.Secrets,
|
||||
@@ -690,6 +702,7 @@ func (a *Agent) updateTasks(tasks []AgentTask) {
|
||||
for id, task := range newTasks {
|
||||
oldTask, exists := a.tasks[id]
|
||||
if !exists || oldTask.Schedule != task.Schedule || oldTask.Command != task.Command ||
|
||||
oldTask.PreCommand != task.PreCommand || oldTask.PostCommand != task.PostCommand ||
|
||||
oldTask.Enabled != task.Enabled || oldTask.Timeout != task.Timeout ||
|
||||
oldTask.WorkDir != task.WorkDir || oldTask.Envs != task.Envs ||
|
||||
oldTask.RandomRange != task.RandomRange {
|
||||
|
||||
+61
-33
@@ -20,14 +20,14 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SourceType string
|
||||
SourceURL string
|
||||
TargetPath string
|
||||
Branch string
|
||||
Path string
|
||||
SingleFile bool
|
||||
Proxy string
|
||||
ProxyURL string
|
||||
SourceType string
|
||||
SourceURL string
|
||||
TargetPath string
|
||||
Branch string
|
||||
Path string
|
||||
SingleFile bool
|
||||
Proxy string
|
||||
ProxyURL string
|
||||
AuthToken string
|
||||
HttpProxy string
|
||||
WhitelistPaths string // Comma or vertical line separated paths to preserve or filter (whitelist)
|
||||
@@ -38,6 +38,8 @@ type Config struct {
|
||||
TaskLanguages string
|
||||
TaskTimeout int
|
||||
CommentToTask string
|
||||
PreCommand string
|
||||
PostCommand string
|
||||
}
|
||||
|
||||
func Run(args []string) {
|
||||
@@ -62,9 +64,11 @@ func Run(args []string) {
|
||||
fs.StringVar(&cfg.TaskID, "repo-task-id", "", "Original Task ID")
|
||||
fs.IntVar(&cfg.TaskTimeout, "task-timeout", 30, "Task timeout (minutes)")
|
||||
fs.StringVar(&cfg.CommentToTask, "commenttotask", "false", "Compatible with QL format script comment parsing (true/false)")
|
||||
fs.StringVar(&cfg.PreCommand, "pre-command", "", "Default pre-command for discovered tasks")
|
||||
fs.StringVar(&cfg.PostCommand, "post-command", "", "Default post-command for discovered tasks")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
|
||||
// 处理 $SCRIPTS_DIR$ 代号替换
|
||||
if strings.Contains(cfg.TargetPath, "$SCRIPTS_DIR$") {
|
||||
scriptsDir := os.Getenv("BH_SCRIPTS_DIR")
|
||||
@@ -86,6 +90,19 @@ func Run(args []string) {
|
||||
syncURL(cfg)
|
||||
}
|
||||
|
||||
// 执行前置指令
|
||||
if cfg.PreCommand != "" {
|
||||
fmt.Printf("[准备] 执行同步前指令: %s\n", cfg.PreCommand)
|
||||
// 计算当前仓库真实的物理路径
|
||||
repoDir := getActualRepoDir(cfg)
|
||||
fmt.Printf("[准备] 工作目录: %s\n", repoDir)
|
||||
fmt.Printf("[准备] 注入环境变量: CURR_REPO_DIR=%s\n", repoDir)
|
||||
|
||||
shell, shellArgs := utils.GetShellCommand(cfg.PreCommand)
|
||||
envs := append(os.Environ(), "CURR_REPO_DIR="+repoDir)
|
||||
runCmd(append([]string{shell}, shellArgs...), repoDir, envs)
|
||||
}
|
||||
|
||||
// 执行脚本过滤(仅限 git 模式,url 加载通常为单文件,暂不处理过滤)
|
||||
if cfg.SourceType == "git" {
|
||||
fmt.Printf("[3/3] 正在执行脚本过滤与文件清理...\n")
|
||||
@@ -98,11 +115,34 @@ func Run(args []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行后置指令
|
||||
if cfg.PostCommand != "" {
|
||||
fmt.Printf("[收尾] 执行同步后指令: %s\n", cfg.PostCommand)
|
||||
|
||||
// 计算当前仓库真实的物理路径
|
||||
repoDir := getActualRepoDir(cfg)
|
||||
fmt.Printf("[收尾] 工作目录: %s\n", repoDir)
|
||||
fmt.Printf("[收尾] 注入环境变量: CURR_REPO_DIR=%s\n", repoDir)
|
||||
|
||||
shell, shellArgs := utils.GetShellCommand(cfg.PostCommand)
|
||||
envs := append(os.Environ(), "CURR_REPO_DIR="+repoDir)
|
||||
runCmd(append([]string{shell}, shellArgs...), repoDir, envs)
|
||||
}
|
||||
|
||||
fmt.Println("\n========================================")
|
||||
fmt.Println(" 仓库同步任务完成 ")
|
||||
fmt.Println("========================================")
|
||||
}
|
||||
|
||||
func getActualRepoDir(cfg Config) string {
|
||||
if cfg.SourceType == "git" {
|
||||
repoName := utils.GetRepoIdentifier(cfg.SourceURL, cfg.Branch)
|
||||
return filepath.Join(cfg.TargetPath, repoName)
|
||||
}
|
||||
return cfg.TargetPath
|
||||
}
|
||||
|
||||
func notifyMainServerToSyncRepoTasks(repoID string, upsertedIDs []string, deletedIDs []string) {
|
||||
appCfg := services.GetConfig()
|
||||
if appCfg != nil {
|
||||
@@ -174,7 +214,7 @@ func syncGit(cfg Config) {
|
||||
if pathExists(gitDir) {
|
||||
fmt.Println("检测到已存在仓库,正在更新...")
|
||||
runCmd([]string{"git", "fetch", "--all"}, dest, env)
|
||||
|
||||
|
||||
targetBranch := cfg.Branch
|
||||
if targetBranch != "" {
|
||||
// 如果切换了分支,或者当前分支偏离,强制切换并对齐远程
|
||||
@@ -205,7 +245,7 @@ func syncGit(cfg Config) {
|
||||
fmt.Println("提示: 请清空目标目录或指定一个新目录")
|
||||
os.Exit(1)
|
||||
}
|
||||
// If dest exists but is empty now, git clone might still complain if the directory itself exists?
|
||||
// If dest exists but is empty now, git clone might still complain if the directory itself exists?
|
||||
// No, git clone works if dir is empty.
|
||||
|
||||
cloneCmd := []string{"git", "clone", "--depth", "1"}
|
||||
@@ -306,7 +346,7 @@ func buildProxyURL(url string, proxyType string, proxyURL string) string {
|
||||
if proxyType == "" || proxyType == "none" {
|
||||
return url
|
||||
}
|
||||
|
||||
|
||||
// 如果 URL 已经包含明显的代理前缀 (如用户手动填写的 http://ghproxy.com/...)
|
||||
// 则跳过内置代理逻辑
|
||||
if strings.Contains(url, "googo.win") || (proxyType == "custom" && strings.HasPrefix(url, proxyURL)) {
|
||||
@@ -392,8 +432,6 @@ func isRawFileURL(url string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
|
||||
var ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[a-zA-Z]")
|
||||
|
||||
type cleanWriter struct {
|
||||
@@ -454,11 +492,11 @@ func runCmd(args []string, dir string, env []string) {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = env
|
||||
|
||||
|
||||
cw := &cleanWriter{out: os.Stdout}
|
||||
cmd.Stdout = cw
|
||||
cmd.Stderr = cw
|
||||
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
cw.Flush()
|
||||
fmt.Printf("命令执行失败: %v\n", err)
|
||||
@@ -601,18 +639,7 @@ func filterFiles(cfg Config) {
|
||||
return
|
||||
}
|
||||
|
||||
dest := cfg.TargetPath
|
||||
// If the dest appended a repo name in syncGit, we need to find it.
|
||||
// However, BuildRepoCommand already passes the abs path which might already be the specific repo dir.
|
||||
// We'll walk from cfg.TargetPath.
|
||||
|
||||
gitDir := filepath.Join(dest, ".git")
|
||||
if isDir(dest) && !pathExists(gitDir) {
|
||||
repoName := utils.GetRepoIdentifier(cfg.SourceURL, cfg.Branch)
|
||||
if pathExists(filepath.Join(dest, repoName)) {
|
||||
dest = filepath.Join(dest, repoName)
|
||||
}
|
||||
}
|
||||
dest := getActualRepoDir(cfg)
|
||||
|
||||
fmt.Printf("开始执行脚本过滤: %s\n", dest)
|
||||
|
||||
@@ -623,7 +650,7 @@ func filterFiles(cfg Config) {
|
||||
|
||||
// We'll collect files to delete to avoid modifying while walking if possible.
|
||||
// But os.RemoveAll is fine.
|
||||
|
||||
|
||||
count := 0
|
||||
filepath.Walk(dest, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -704,7 +731,7 @@ func splitKeywords(s string) []string {
|
||||
} else {
|
||||
parts = []string{s}
|
||||
}
|
||||
|
||||
|
||||
var res []string
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
@@ -761,13 +788,15 @@ func cleanEmptyDirs(root string) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
// Actually we need to do this recursively or multiple times.
|
||||
// A simpler way:
|
||||
entries, _ := os.ReadDir(root)
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
if entry.Name() == ".git" { continue }
|
||||
if entry.Name() == ".git" {
|
||||
continue
|
||||
}
|
||||
dirPath := filepath.Join(root, entry.Name())
|
||||
cleanEmptyDirs(dirPath)
|
||||
// Check if now empty
|
||||
@@ -778,4 +807,3 @@ func cleanEmptyDirs(root string) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Command string `json:"command"`
|
||||
PreCommand string `json:"pre_command"`
|
||||
PostCommand string `json:"post_command"`
|
||||
Tags string `json:"tags"`
|
||||
Type string `json:"type"`
|
||||
Config string `json:"config"`
|
||||
@@ -119,12 +121,12 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
||||
if sourceID != "" {
|
||||
task = tc.taskService.GetTaskBySourceID(sourceID)
|
||||
if task != nil {
|
||||
task = tc.taskService.UpdateTask(task.ID, req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, true, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange, sourceID, req.PinType)
|
||||
task = tc.taskService.UpdateTask(task.ID, req.Name, req.Command, req.PreCommand, req.PostCommand, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, true, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange, sourceID, req.PinType)
|
||||
}
|
||||
}
|
||||
|
||||
if task == nil {
|
||||
task = tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange, sourceID, req.PinType)
|
||||
task = tc.taskService.CreateTask(req.Name, req.Command, req.PreCommand, req.PostCommand, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange, sourceID, req.PinType)
|
||||
}
|
||||
|
||||
// 如果是 Agent 任务,通知 Agent;否则添加到本地 cron
|
||||
@@ -225,6 +227,8 @@ func (tc *TaskController) UpdateTask(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
PreCommand string `json:"pre_command"`
|
||||
PostCommand string `json:"post_command"`
|
||||
Tags string `json:"tags"`
|
||||
Type string `json:"type"`
|
||||
Config string `json:"config"`
|
||||
@@ -274,7 +278,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) {
|
||||
sourceID = oldTask.SourceID
|
||||
}
|
||||
|
||||
task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Enabled, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange, sourceID, req.PinType)
|
||||
task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.PreCommand, req.PostCommand, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Enabled, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange, sourceID, req.PinType)
|
||||
if task == nil {
|
||||
utils.NotFound(c, "任务不存在")
|
||||
return
|
||||
|
||||
@@ -107,7 +107,9 @@ func (m *CronManager) AddTask(task CronTask) error {
|
||||
return &ExecutionRequest{
|
||||
TaskID: taskID,
|
||||
Name: name,
|
||||
Command: cmd,
|
||||
Command: cmd,
|
||||
PreCommand: task.GetPreCommand(),
|
||||
PostCommand: task.GetPostCommand(),
|
||||
Type: TaskTypeCron,
|
||||
Timeout: timeout,
|
||||
WorkDir: workDir,
|
||||
|
||||
@@ -21,6 +21,8 @@ type Task interface {
|
||||
GetID() string
|
||||
GetName() string
|
||||
GetCommand() string
|
||||
GetPreCommand() string
|
||||
GetPostCommand() string
|
||||
GetTimeout() int
|
||||
GetWorkDir() string
|
||||
GetEnvs() string
|
||||
@@ -40,8 +42,10 @@ type CronTask interface {
|
||||
|
||||
// Request 任务执行请求
|
||||
type Request struct {
|
||||
Command string
|
||||
WorkDir string
|
||||
Command string
|
||||
PreCommand string
|
||||
PostCommand string
|
||||
WorkDir string
|
||||
Envs []string
|
||||
Timeout int // 任务超时时间(分钟)
|
||||
Languages []map[string]string
|
||||
@@ -121,6 +125,19 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer
|
||||
req.UseMise = false
|
||||
}
|
||||
|
||||
// 组合指令(如果存在前置或后置指令)
|
||||
if req.PreCommand != "" || req.PostCommand != "" {
|
||||
finalCmd := ""
|
||||
if req.PreCommand != "" {
|
||||
finalCmd += req.PreCommand + "\n"
|
||||
}
|
||||
finalCmd += req.Command
|
||||
if req.PostCommand != "" {
|
||||
finalCmd += "\n" + req.PostCommand
|
||||
}
|
||||
req.Command = finalCmd
|
||||
}
|
||||
|
||||
// 1. 执行前钩子
|
||||
var logID string
|
||||
if hooks != nil {
|
||||
|
||||
@@ -66,8 +66,10 @@ type ExecutionRequest struct {
|
||||
LogID string // 日志 ID
|
||||
Name string // 任务名称
|
||||
Type TaskType // 任务类型
|
||||
Command string // 命令
|
||||
WorkDir string // 工作目录
|
||||
Command string // 命令
|
||||
PreCommand string // 前置命令
|
||||
PostCommand string // 后置命令
|
||||
WorkDir string // 工作目录
|
||||
Envs []string // 环境变量
|
||||
Secrets []string // 需要脱敏的密码
|
||||
Timeout int // 超时时间(分钟)
|
||||
@@ -204,7 +206,9 @@ func NewScheduler(config SchedulerConfig, handler SchedulerEventHandler) *Schedu
|
||||
executor: func(ctx context.Context, req *ExecutionRequest, stdout, stderr io.Writer) (*Result, error) {
|
||||
hooks := &schedulerHooksAdapter{handler: handler, req: req}
|
||||
return ExecuteWithHooks(ctx, Request{
|
||||
Command: req.Command,
|
||||
Command: req.Command,
|
||||
PreCommand: req.PreCommand,
|
||||
PostCommand: req.PostCommand,
|
||||
WorkDir: req.WorkDir,
|
||||
Envs: req.Envs,
|
||||
Timeout: req.Timeout,
|
||||
|
||||
@@ -50,11 +50,13 @@ func (AgentToken) TableName() string {
|
||||
type AgentTask struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
Schedule string `json:"schedule"`
|
||||
Timeout int `json:"timeout"`
|
||||
WorkDir string `json:"work_dir"`
|
||||
Envs string `json:"envs"`
|
||||
Command string `json:"command"`
|
||||
PreCommand string `json:"pre_command"`
|
||||
PostCommand string `json:"post_command"`
|
||||
Schedule string `json:"schedule"`
|
||||
Timeout int `json:"timeout"`
|
||||
WorkDir string `json:"work_dir"`
|
||||
Envs string `json:"envs"`
|
||||
Languages []map[string]string `json:"languages"`
|
||||
RandomRange int `json:"random_range"`
|
||||
Secrets []string `json:"secrets"`
|
||||
@@ -73,6 +75,14 @@ func (t AgentTask) GetCommand() string {
|
||||
return t.Command
|
||||
}
|
||||
|
||||
func (t AgentTask) GetPreCommand() string {
|
||||
return t.PreCommand
|
||||
}
|
||||
|
||||
func (t AgentTask) GetPostCommand() string {
|
||||
return t.PostCommand
|
||||
}
|
||||
|
||||
func (t AgentTask) GetSchedule() string {
|
||||
return t.Schedule
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ type Task struct {
|
||||
Remark string `json:"remark" gorm:"size:255;default:''"`
|
||||
PinType string `json:"pin_type" gorm:"size:20;default:none;index"` // 置顶类型: constant.PinTypeNone, constant.PinTypeTop
|
||||
Command BigText `json:"command"` // 普通任务的命令
|
||||
PreCommand BigText `json:"pre_command"` // 执行前的命令
|
||||
PostCommand BigText `json:"post_command"` // 执行后的命令
|
||||
Tags string `json:"tags" gorm:"size:255;default:''"` // 标签,逗号分隔
|
||||
Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo
|
||||
TriggerType string `json:"trigger_type" gorm:"size:25;default:'cron'"` // 触发类型: constant.TriggerTypeCron, constant.TriggerTypeBaihuStartup
|
||||
@@ -123,6 +125,14 @@ func (t *Task) GetCommand() string {
|
||||
return string(t.Command)
|
||||
}
|
||||
|
||||
func (t *Task) GetPreCommand() string {
|
||||
return string(t.PreCommand)
|
||||
}
|
||||
|
||||
func (t *Task) GetPostCommand() string {
|
||||
return string(t.PostCommand)
|
||||
}
|
||||
|
||||
func (t *Task) GetTimeout() int {
|
||||
return t.Timeout
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ type TaskVO struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
PreCommand string `json:"pre_command"`
|
||||
PostCommand string `json:"post_command"`
|
||||
Tags string `json:"tags"`
|
||||
Type string `json:"type"`
|
||||
TriggerType string `json:"trigger_type"`
|
||||
@@ -44,6 +46,8 @@ func ToTaskVO(task *models.Task) *TaskVO {
|
||||
ID: task.ID,
|
||||
Name: task.Name,
|
||||
Command: string(task.Command),
|
||||
PreCommand: string(task.PreCommand),
|
||||
PostCommand: string(task.PostCommand),
|
||||
Tags: task.Tags,
|
||||
Type: task.Type,
|
||||
TriggerType: task.TriggerType,
|
||||
|
||||
@@ -314,13 +314,13 @@ func (s *AgentService) Heartbeat(token, ip, version, buildTime, hostname, osType
|
||||
|
||||
// GetTasks 获取 Agent 的任务列表
|
||||
func (s *AgentService) GetTasks(agentID string) []models.AgentTask {
|
||||
var tasks []models.Task
|
||||
database.DB.Where("agent_id = ? AND enabled = ?", agentID, true).Find(&tasks)
|
||||
var tasksList []models.Task
|
||||
database.DB.Where("agent_id = ? AND enabled = ?", agentID, true).Find(&tasksList)
|
||||
|
||||
result := make([]models.AgentTask, len(tasks))
|
||||
result := make([]models.AgentTask, len(tasksList))
|
||||
envService := NewEnvService()
|
||||
|
||||
for i, task := range tasks {
|
||||
for i, task := range tasksList {
|
||||
// 加载环境配置
|
||||
var envVars []string
|
||||
|
||||
@@ -343,14 +343,29 @@ func (s *AgentService) GetTasks(agentID string) []models.AgentTask {
|
||||
}
|
||||
|
||||
envVarsStr := executor.FormatEnvVars(envVars)
|
||||
|
||||
command := string(task.Command)
|
||||
preCommand := string(task.PreCommand)
|
||||
postCommand := string(task.PostCommand)
|
||||
workDir := task.WorkDir
|
||||
|
||||
// 仓库同步任务特殊处理:将配置转换为 reposync 命令行
|
||||
if task.Type == constant.TaskTypeRepo {
|
||||
command, workDir = tasks.BuildRepoCommand(&task)
|
||||
// 仓库任务的前置/后置命令已作为参数传给 reposync 内部处理,此处清空防止重复执行
|
||||
preCommand = ""
|
||||
postCommand = ""
|
||||
}
|
||||
|
||||
result[i] = models.AgentTask{
|
||||
ID: task.ID,
|
||||
Name: task.Name,
|
||||
Command: string(task.Command),
|
||||
Command: command,
|
||||
PreCommand: preCommand,
|
||||
PostCommand: postCommand,
|
||||
Schedule: task.Schedule,
|
||||
Timeout: task.Timeout,
|
||||
WorkDir: task.WorkDir,
|
||||
WorkDir: workDir,
|
||||
Envs: envVarsStr,
|
||||
Languages: []map[string]string(task.Languages),
|
||||
RandomRange: task.RandomRange,
|
||||
|
||||
@@ -404,7 +404,9 @@ func (es *ExecutorService) HandleTaskRetry(task *models.Task, req *executor.Exec
|
||||
return &executor.ExecutionRequest{
|
||||
TaskID: req.TaskID,
|
||||
Name: latestTask.Name,
|
||||
Command: string(latestTask.Command),
|
||||
Command: string(latestTask.Command),
|
||||
PreCommand: string(latestTask.PreCommand),
|
||||
PostCommand: string(latestTask.PostCommand),
|
||||
WorkDir: latestTask.WorkDir,
|
||||
Envs: newEnvs,
|
||||
Secrets: newSecrets,
|
||||
@@ -454,6 +456,8 @@ func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor.
|
||||
|
||||
// 解析路径变量 (如 $SCRIPTS_DIR$)
|
||||
req.Command = es.ResolvePath(req.Command)
|
||||
req.PreCommand = es.ResolvePath(req.PreCommand)
|
||||
req.PostCommand = es.ResolvePath(req.PostCommand)
|
||||
req.WorkDir = es.ResolvePath(req.WorkDir)
|
||||
|
||||
task := es.taskService.GetTaskByID(taskID)
|
||||
@@ -478,10 +482,14 @@ func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor.
|
||||
req.Command = cmd
|
||||
req.WorkDir = workDir
|
||||
req.UseMise = false // 仓库同步任务不使用 mise,由系统原生执行
|
||||
// 仓库任务的前置/后置命令已作为参数传给 reposync 内部处理,此处清空防止重复执行
|
||||
req.PreCommand = ""
|
||||
req.PostCommand = ""
|
||||
|
||||
// 强制脱敏并更新数据库日志
|
||||
masks := append([]string{}, req.Secrets...)
|
||||
masks = append(masks, utils.GetSystemSecrets()...)
|
||||
|
||||
|
||||
// 补充仓库特有的 AuthToken
|
||||
var repoCfg models.RepoConfig
|
||||
if err := json.Unmarshal([]byte(task.Config), &repoCfg); err == nil && repoCfg.AuthToken != "" {
|
||||
@@ -489,17 +497,20 @@ func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor.
|
||||
}
|
||||
|
||||
maskedCmd := utils.MaskSecrets(req.Command, masks)
|
||||
|
||||
|
||||
// 更新数据库中的任务日志命令内容
|
||||
if req.LogID != "" {
|
||||
es.taskLogService.UpdateLogCommand(req.LogID, maskedCmd)
|
||||
}
|
||||
|
||||
|
||||
// 在控制台打印最终执行的脱敏命令
|
||||
logger.Infof("[Executor] 仓库同步最终执行命令: %s", maskedCmd)
|
||||
}
|
||||
}
|
||||
|
||||
// 组合指令逻辑已移至 executor.ExecuteWithHooks 中,此处不再处理
|
||||
// 以避免指令被重复组合。
|
||||
|
||||
// 远程任务
|
||||
if task.AgentID != nil && *task.AgentID != "" {
|
||||
// 将请求中已包含的环境变量(已合并)传递给 Agent
|
||||
@@ -651,7 +662,9 @@ func (es *ExecutorService) ExecuteTask(taskID string, extraEnvs []string) *execu
|
||||
req := &executor.ExecutionRequest{
|
||||
TaskID: task.ID,
|
||||
Name: task.Name,
|
||||
Command: string(task.Command),
|
||||
Command: string(task.Command),
|
||||
PreCommand: string(task.PreCommand),
|
||||
PostCommand: string(task.PostCommand),
|
||||
WorkDir: task.WorkDir,
|
||||
Envs: envs,
|
||||
Secrets: secrets,
|
||||
@@ -1039,6 +1052,11 @@ func (es *ExecutorService) HandleAgentResult(result *models.AgentTaskResult) err
|
||||
|
||||
// BuildRepoCommand 构建仓库同步任务的命令
|
||||
func (es *ExecutorService) BuildRepoCommand(task *models.Task) (string, string) {
|
||||
return BuildRepoCommand(task)
|
||||
}
|
||||
|
||||
// BuildRepoCommand 构建仓库同步任务的命令(独立函数,方便 AgentService 调用)
|
||||
func BuildRepoCommand(task *models.Task) (string, string) {
|
||||
var config models.RepoConfig
|
||||
if err := json.Unmarshal([]byte(task.Config), &config); err != nil {
|
||||
return "", ""
|
||||
@@ -1107,6 +1125,12 @@ func (es *ExecutorService) BuildRepoCommand(task *models.Task) (string, string)
|
||||
if config.Extensions != "" {
|
||||
args = append(args, "--extensions", config.Extensions)
|
||||
}
|
||||
if string(task.PreCommand) != "" {
|
||||
args = append(args, "--pre-command", string(task.PreCommand))
|
||||
}
|
||||
if string(task.PostCommand) != "" {
|
||||
args = append(args, "--post-command", string(task.PostCommand))
|
||||
}
|
||||
|
||||
// 传递任务 ID,以便 reposync 内部直接处理脚本注册并输出日志
|
||||
args = append(args, "--task-id", task.ID)
|
||||
|
||||
@@ -24,7 +24,7 @@ func (ts *TaskService) GetTaskBySourceID(sourceID string) *models.Task {
|
||||
return &task
|
||||
}
|
||||
|
||||
func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, workDir, cleanConfig, envs, taskType, config string, agentID *string, languages models.TaskLanguages, triggerType string, tags string, retryCount int, retryInterval int, randomRange int, sourceID string, pinType string) *models.Task {
|
||||
func (ts *TaskService) CreateTask(name, command, preCommand, postCommand, schedule string, timeout int, workDir, cleanConfig, envs, taskType, config string, agentID *string, languages models.TaskLanguages, triggerType string, tags string, retryCount int, retryInterval int, randomRange int, sourceID string, pinType string) *models.Task {
|
||||
if taskType == "" {
|
||||
taskType = "task"
|
||||
}
|
||||
@@ -38,6 +38,8 @@ func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, w
|
||||
ID: utils.GenerateID(),
|
||||
Name: name,
|
||||
Command: models.BigText(command),
|
||||
PreCommand: models.BigText(preCommand),
|
||||
PostCommand: models.BigText(postCommand),
|
||||
PinType: pinType,
|
||||
Tags: tags,
|
||||
Type: taskType,
|
||||
@@ -122,7 +124,7 @@ func (ts *TaskService) GetTaskByID(id string) *models.Task {
|
||||
return &task
|
||||
}
|
||||
|
||||
func (ts *TaskService) UpdateTask(id string, name, command, schedule string, timeout int, workDir, cleanConfig, envs string, enabled bool, taskType, config string, agentID *string, languages models.TaskLanguages, triggerType string, tags string, retryCount int, retryInterval int, randomRange int, sourceID string, pinType string) *models.Task {
|
||||
func (ts *TaskService) UpdateTask(id string, name, command, preCommand, postCommand, schedule string, timeout int, workDir, cleanConfig, envs string, enabled bool, taskType, config string, agentID *string, languages models.TaskLanguages, triggerType string, tags string, retryCount int, retryInterval int, randomRange int, sourceID string, pinType string) *models.Task {
|
||||
var task models.Task
|
||||
res := database.DB.Where("id = ?", id).Limit(1).Find(&task)
|
||||
if res.Error != nil || res.RowsAffected == 0 {
|
||||
@@ -130,6 +132,8 @@ func (ts *TaskService) UpdateTask(id string, name, command, schedule string, tim
|
||||
}
|
||||
task.Name = name
|
||||
task.Command = models.BigText(command)
|
||||
task.PreCommand = models.BigText(preCommand)
|
||||
task.PostCommand = models.BigText(postCommand)
|
||||
task.PinType = pinType
|
||||
task.Tags = tags
|
||||
task.Schedule = schedule
|
||||
@@ -159,6 +163,7 @@ func (ts *TaskService) UpdateTask(id string, name, command, schedule string, tim
|
||||
"CleanConfig", "Envs", "Enabled", "AgentID", "Languages",
|
||||
"RetryCount", "RetryInterval", "RandomRange", "Type",
|
||||
"TriggerType", "Config", "SourceID", "PinType",
|
||||
"PreCommand", "PostCommand",
|
||||
).Updates(&task)
|
||||
|
||||
return &task
|
||||
|
||||
+22
-1
@@ -1,6 +1,10 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetRepoIdentifier 返回根据仓库URL和分支生成的作者_仓库名标识符
|
||||
func GetRepoIdentifier(url string, branch string) string {
|
||||
@@ -45,3 +49,20 @@ func GetRepoIdentifier(url string, branch string) string {
|
||||
identifier = strings.ReplaceAll(identifier, ".", "_")
|
||||
return identifier
|
||||
}
|
||||
|
||||
// GetActualRepoDir 返回仓库真实的物理目录
|
||||
func GetActualRepoDir(targetPath, sourceURL, branch, sourceType string) string {
|
||||
repoDir := targetPath
|
||||
if sourceType == "git" && sourceURL != "" {
|
||||
repoName := GetRepoIdentifier(sourceURL, branch)
|
||||
// 检查 targetPath 是否已存在且是 Git 仓库
|
||||
gitDir := filepath.Join(repoDir, ".git")
|
||||
if info, err := os.Stat(repoDir); err == nil && info.IsDir() {
|
||||
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
|
||||
// 只有当目标目录存在但不是 Git 仓库时,才追加仓库名
|
||||
repoDir = filepath.Join(repoDir, repoName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return repoDir
|
||||
}
|
||||
|
||||
@@ -349,6 +349,8 @@ export interface Task {
|
||||
name: string
|
||||
remark: string
|
||||
command: string
|
||||
pre_command: string
|
||||
post_command: string
|
||||
tags: string
|
||||
type: string
|
||||
trigger_type: string
|
||||
|
||||
@@ -117,6 +117,12 @@ export function parseBaihuCommand(command: string): ParsedRepoResult | null {
|
||||
console.error('Parse task-langs failed', e)
|
||||
}
|
||||
break
|
||||
case '--pre-command':
|
||||
task.pre_command = value
|
||||
break
|
||||
case '--post-command':
|
||||
task.post_command = value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,8 @@ function submitBaihuImport() {
|
||||
repoConfig.value = { ...repoConfig.value, ...result.repoConfig }
|
||||
if (result.task.name) form.value.name = result.task.name
|
||||
if (result.task.timeout) form.value.timeout = result.task.timeout
|
||||
if (result.task.pre_command) form.value.pre_command = result.task.pre_command
|
||||
if (result.task.post_command) form.value.post_command = result.task.post_command
|
||||
|
||||
if (result.task.languages) {
|
||||
selectedLangs.value = result.task.languages.map(l => ({
|
||||
@@ -308,6 +310,8 @@ watch(() => props.open, async (val: boolean) => {
|
||||
random_range: props.task?.random_range ?? 0,
|
||||
timeout: props.task?.timeout ?? 30,
|
||||
pin_type: props.task?.pin_type ?? 'none',
|
||||
pre_command: props.task?.pre_command ?? '',
|
||||
post_command: props.task?.post_command ?? '',
|
||||
...props.task
|
||||
}
|
||||
// 解析清理配置
|
||||
@@ -440,12 +444,12 @@ async function save() {
|
||||
<Dialog :open="open" @update:open="emit('update:open', $event)">
|
||||
<DialogContent class="max-w-[95vw] sm:max-w-[700px] xl:max-w-[950px] p-0 overflow-hidden border-none bg-background shadow-2xl transition-all duration-300" style="text-rendering: optimizeLegibility;" @openAutoFocus.prevent>
|
||||
<div class="flex flex-col max-h-[85vh]">
|
||||
<DialogHeader class="px-5 sm:px-6 pr-10 pt-6 pb-2 shrink-0">
|
||||
<DialogHeader class="px-5 sm:px-6 pr-20 pt-6 pb-2 shrink-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-2">
|
||||
<DialogTitle class="text-xl font-bold whitespace-nowrap">
|
||||
{{ isEdit ? '编辑仓库同步' : '新建仓库同步' }}
|
||||
</DialogTitle>
|
||||
<div v-if="!isEdit" class="flex flex-wrap items-center gap-2">
|
||||
<div v-if="!isEdit" class="flex flex-wrap items-center gap-2 sm:mr-4">
|
||||
<Button variant="outline" size="sm" @click="importFromBaihu" class="flex-1 sm:flex-initial h-8 gap-1.5 bg-primary/5 hover:bg-primary/10 border-primary/20 hover:border-primary/40 text-primary px-3">
|
||||
<Terminal class="w-3.5 h-3.5" />
|
||||
<span class="text-xs">Baihu 命令导入</span>
|
||||
@@ -573,6 +577,15 @@ async function save() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3 mt-4">
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">前置脚本</Label>
|
||||
<div class="sm:col-span-3 relative"><Input v-model="form.pre_command" placeholder="同步前运行的指令 (可选)" :class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:bg-background/50 pr-10', form.pre_command ? 'font-mono text-sm tracking-tight font-medium' : 'text-[11px] font-normal')" /><Zap class="absolute right-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground opacity-40 pointer-events-none" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3">
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">后置脚本</Label>
|
||||
<div class="sm:col-span-3 relative"><Input v-model="form.post_command" placeholder="同步后运行的指令 (可选)" :class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:bg-background/50 pr-10', form.post_command ? 'font-mono text-sm tracking-tight font-medium' : 'text-[11px] font-normal')" /><Zap class="absolute right-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground opacity-40 pointer-events-none" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -923,10 +936,10 @@ async function save() {
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">示例命令</Label>
|
||||
<button class="text-[10px] text-primary hover:underline font-medium" @click="baihuCommandInput = 'baihu reposync --source-url \'https://github.com/example/repo.git\' --branch \'main\' --blacklist \'test|dev\''">填入示例</button>
|
||||
<button class="text-[10px] text-primary hover:underline font-medium" @click="baihuCommandInput = 'baihu reposync --source-url \'https://github.com/example/repo.git\' --branch \'main\' --blacklist \'test|dev\' --pre-command \'npm install\' --post-command \'echo done\''">填入示例</button>
|
||||
</div>
|
||||
<div class="p-3 rounded-lg bg-muted/40 font-mono text-[11px] text-muted-foreground/70 border border-muted/20 leading-relaxed break-all">
|
||||
baihu reposync --source-url 'https://...' --branch 'main' --blacklist '...'
|
||||
baihu reposync --source-url 'https://...' --branch 'main' --blacklist '...' --pre-command '...' --post-command '...'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -241,6 +241,8 @@ watch(() => props.open, async (val: boolean) => {
|
||||
random_range: props.task?.random_range ?? 0,
|
||||
timeout: props.task?.timeout ?? 30,
|
||||
pin_type: props.task?.pin_type ?? 'none',
|
||||
pre_command: props.task?.pre_command ?? '',
|
||||
post_command: props.task?.post_command ?? '',
|
||||
...props.task
|
||||
}
|
||||
// 解析清理配置
|
||||
@@ -576,9 +578,17 @@ async function save() {
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3">
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">执行命令</Label>
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">前置指令</Label>
|
||||
<div class="sm:col-span-3 relative"><Input v-model="form.pre_command" placeholder="执行主命令前运行的指令 (可选)" :class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:bg-background/50 pr-10', form.pre_command ? 'font-mono text-sm tracking-tight font-medium' : 'text-[11px] font-normal')" /><Zap class="absolute right-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground opacity-40 pointer-events-none" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3">
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">核心命令</Label>
|
||||
<div class="sm:col-span-3 relative"><Input v-model="form.command" placeholder="例如: python main.py --args" :class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:bg-background/50 pr-10', form.command ? 'font-mono text-sm tracking-tight font-medium' : 'text-[11px] font-normal')" /><Terminal class="absolute right-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground opacity-40 pointer-events-none" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3">
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">后置指令</Label>
|
||||
<div class="sm:col-span-3 relative"><Input v-model="form.post_command" placeholder="主命令执行后运行的指令 (可选)" :class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:bg-background/50 pr-10', form.post_command ? 'font-mono text-sm tracking-tight font-medium' : 'text-[11px] font-normal')" /><Zap class="absolute right-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground opacity-40 pointer-events-none" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3">
|
||||
<Label class="sm:text-right text-xs text-foreground/70 uppercase tracking-wider font-bold">工作目录</Label>
|
||||
<div class="sm:col-span-3"><DirTreeSelect v-if="selectedAgentId === 'local'" v-model="currentWorkDir" class="h-9" /><Input v-else v-model="currentWorkDir" placeholder="任务运行路径(留空取 Agent 默认值)" :class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:bg-background/50', currentWorkDir ? 'font-mono text-sm tracking-tight font-medium' : 'text-[11px] font-normal')" /></div>
|
||||
|
||||
Reference in New Issue
Block a user