diff --git a/README.md b/README.md index 1d2ff56..e6786a4 100644 --- a/README.md +++ b/README.md @@ -96,21 +96,15 @@ simpleci 提供集成适配器 `pkgs/simpleci/simplegit-hook.sh`。联合部署 显式使用该脚本,并配置: ```sh -export SIMPLECI_URL=http://simpleci:8095 -export SIMPLECI_HOOK_TOKEN=replace-with-a-shared-secret -export SIMPLECI_WORKFLOW=.github/workflows/deploy.yml # 可选 -simplegit daemon -hook-script /path/to/simplegit-hook.sh +simplegit daemon \ + -hook-script=/path/to/simplegit-hook.sh \ + -hook-arg=--url=http://simpleci:8095 \ + -hook-arg=--workflow=.github/workflows/deploy.yml \ + -hook-arg=-- ``` tag push 和分支删除不会触发构建;simpleci 不可用时通知最多等待 5 秒,且不影响 -push 的成功结果。simpleci 使用相同 secret 启动: - -```sh -simpleci -hook-token replace-with-a-shared-secret -``` - -`SIMPLECI_HOOK_TOKEN` 和 `-hook-token` 都为空时不启用鉴权,适合仅在受信任的 -本机或内部网络开发;生产环境应从同一个 Secret 分别注入两端。 +push 的成功结果。`POST /runs` 应只暴露在受信任的内部网络。 --- diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 98848f1..bae4580 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -33,6 +33,10 @@ func Run(args []string) { return nil }) fs.StringVar(&cfg.HookScript, "hook-script", "./hook.sh", "script run by every Git hook; receives hook type and owner/name as its first two arguments") + fs.Func("hook-arg", "fixed argument passed to -hook-script before hook type and owner/name (repeatable)", func(arg string) error { + cfg.HookArgs = append(cfg.HookArgs, arg) + return nil + }) fs.StringVar(&cfg.DBUri, "db", "sqlite://./db.sqlite", "private state DB URI (sqlite:// or postgres://) - backs PAT/SSH-key ACLs and repo metadata") fs.Parse(args) @@ -51,9 +55,6 @@ func Run(args []string) { app := NewApp(cfg.RepoRoot(), store) server := gitrpc.NewServer(&cfg, store) - if _, err := ensureHookTemplate(cfg.Root); err != nil { - log.Printf("simplegit: hook template: %v (hooks disabled)", err) - } if cfg.HookScript != "" { hookScript, err := resolveHookScript(cfg.HookScript) if err != nil { @@ -67,15 +68,10 @@ func Run(args []string) { log.Fatalf("simplegit: hook script %s is not executable", hookScript) } cfg.HookScript = hookScript - _ = os.Setenv("SIMPLEGIT_HOOK_SCRIPT", hookScript) - repoRoot, err := filepath.Abs(cfg.RepoRoot()) - if err != nil { - log.Fatalf("simplegit: resolve repository root: %v", err) - } - _ = os.Setenv("SIMPLEGIT_REPO_ROOT", repoRoot) - log.Printf("simplegit: Git hooks execute %s", hookScript) - } else { - _ = os.Unsetenv("SIMPLEGIT_HOOK_SCRIPT") + log.Printf("simplegit: Git hooks execute %s %s", hookScript, strings.Join(cfg.HookArgs, " ")) + } + if _, err := ensureHookTemplate(cfg.Root, cfg.HookScript, cfg.HookArgs); err != nil { + log.Printf("simplegit: hook template: %v (hooks disabled)", err) } // gitrpc mux: Connect-RPC (GitService + ManageService) + the binary content @@ -153,16 +149,18 @@ var gitHookNames = []string{ "p4-prepare-changelist", "p4-changelist", } -const hookWrapper = `#!/bin/sh -# Generated by simplegit. The operator controls hook behavior with -hook-script. -test -n "$SIMPLEGIT_HOOK_SCRIPT" || exit 0 +const hookWrapperTemplate = `#!/bin/sh +# Generated by simplegit. The operator controls behavior with -hook-script. repo_dir=$(pwd -P) -repo=${repo_dir#"$SIMPLEGIT_REPO_ROOT"/} -repo=${repo%%.git} -exec "$SIMPLEGIT_HOOK_SCRIPT" %s "$repo" "$@" +repo_name=${repo_dir##*/} +repo_name=${repo_name%%.git} +owner_dir=${repo_dir%%/*} +owner=${owner_dir##*/} +repo=$owner/$repo_name +exec %s %s "$repo" "$@" ` -func ensureHookTemplate(root string) (string, error) { +func ensureHookTemplate(root, script string, args []string) (string, error) { dir := filepath.Join(root, "template") hooksDir := filepath.Join(dir, "hooks") if err := os.MkdirAll(hooksDir, 0o755); err != nil { @@ -170,7 +168,15 @@ func ensureHookTemplate(root string) (string, error) { } for _, name := range gitHookNames { path := filepath.Join(hooksDir, name) - if err := os.WriteFile(path, []byte(fmt.Sprintf(hookWrapper, name)), 0o755); err != nil { + body := "#!/bin/sh\nexit 0\n" + if script != "" { + command := shellQuote(script) + for _, arg := range args { + command += " " + shellQuote(arg) + } + body = fmt.Sprintf(hookWrapperTemplate, command, shellQuote(name)) + } + if err := os.WriteFile(path, []byte(body), 0o755); err != nil { return "", fmt.Errorf("write hook %s: %w", name, err) } } @@ -180,6 +186,10 @@ func ensureHookTemplate(root string) (string, error) { return dir, nil } +func shellQuote(value string) string { + return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'" +} + func refreshRepositoryHooks(reposRoot, templateHooks string) error { if _, err := os.Stat(reposRoot); os.IsNotExist(err) { return nil diff --git a/common/config.go b/common/config.go index ec62538..2028b54 100644 --- a/common/config.go +++ b/common/config.go @@ -27,7 +27,9 @@ type Config struct { // The hook name is its first argument; Git's original args, stdin and // environment are preserved. Empty disables hook effects. HookScript string - DBUri string + // HookArgs are fixed CLI arguments placed before the hook type and repo. + HookArgs []string + DBUri string preparedDbUri string prepared atomic.Bool diff --git a/hook.sh b/hook.sh index 8bf604b..708a44c 100755 --- a/hook.sh +++ b/hook.sh @@ -1,8 +1,20 @@ #!/bin/sh -# Default standalone policy: append the hook invocation and its stdin to a log. -# Integrations can supply another script with simplegit daemon -hook-script. +# Default standalone policy. Optional CLI: --log=/path/to/hooks.log -- -log_file=${SIMPLEGIT_HOOK_LOG:-${SIMPLEGIT_REPO_ROOT%/repos}/hooks.log} +log_file= +while [ "$#" -gt 0 ]; do + case "$1" in + --log=*) log_file=${1#--log=}; shift ;; + --log) log_file=$2; shift 2 ;; + --) shift; break ;; + *) break ;; + esac +done +[ "$#" -ge 2 ] || exit 0 +if [ -z "$log_file" ]; then + repo_dir=$(pwd -P) + log_file=${repo_dir%/*/*}/hooks.log +fi { printf '%s\t%s\t%s' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" "$2" shift 2