add binary build workflow and hook policy
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
name: Build binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
suffix: ""
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
suffix: ""
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
suffix: .exe
|
||||
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
run: go build -trimpath -ldflags="-s -w" -o "dist/simplegit-${GOOS}-${GOARCH}${{ matrix.suffix }}" ./cmd
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: simplegit-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: dist/simplegit-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.suffix }}
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -31,6 +31,7 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /out/simplegit /app/simplegit
|
||||
COPY simplegit/hook.sh /app/hook.sh
|
||||
EXPOSE 8080 2222
|
||||
ENTRYPOINT ["/app/simplegit"]
|
||||
# Identity/JWT are verified in-process; no -auth-url. The Ed25519 signing key
|
||||
|
||||
@@ -89,6 +89,29 @@ hook.sh <hook-type> <owner/name> [Git hook 原始参数...]
|
||||
`-hook-script=/path/to/production-hook.sh` 替换。simplegit 不解释或转发 hook
|
||||
事件,具体效果完全由该脚本负责。
|
||||
|
||||
仓库自带的 `hook.sh` 只向 `<root>/hooks.log` 追加 hook 类型、仓库、参数和 stdin,
|
||||
不访问任何外部服务,因此 standalone simplegit 不依赖 CI。
|
||||
|
||||
simpleci 提供集成适配器 `pkgs/simpleci/simplegit-hook.sh`。联合部署让 simplegit
|
||||
显式使用该脚本,并配置:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
tag push 和分支删除不会触发构建;simpleci 不可用时通知最多等待 5 秒,且不影响
|
||||
push 的成功结果。simpleci 使用相同 secret 启动:
|
||||
|
||||
```sh
|
||||
simpleci -hook-token replace-with-a-shared-secret
|
||||
```
|
||||
|
||||
`SIMPLECI_HOOK_TOKEN` 和 `-hook-token` 都为空时不启用鉴权,适合仅在受信任的
|
||||
本机或内部网络开发;生产环境应从同一个 Secret 分别注入两端。
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var gitHookNames = []string{
|
||||
"pre-receive", "update", "post-receive", "post-update", "proc-receive",
|
||||
"push-to-checkout", "reference-transaction", "pre-applypatch",
|
||||
"post-applypatch", "pre-commit", "pre-merge-commit", "prepare-commit-msg",
|
||||
"commit-msg", "post-commit", "pre-rebase", "post-checkout", "post-merge",
|
||||
"pre-push", "pre-auto-gc", "post-rewrite", "sendemail-validate",
|
||||
"fsmonitor-watchman", "p4-pre-submit", "p4-post-changelist",
|
||||
"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
|
||||
repo_dir=$(pwd -P)
|
||||
repo=${repo_dir#"$SIMPLEGIT_REPO_ROOT"/}
|
||||
repo=${repo%%.git}
|
||||
exec "$SIMPLEGIT_HOOK_SCRIPT" %s "$repo" "$@"
|
||||
`
|
||||
|
||||
// EnsureHookTemplate creates the git-init template used for new repositories.
|
||||
// All hook entrypoints delegate to one environment-specific external script.
|
||||
func EnsureHookTemplate(root string) (string, error) {
|
||||
dir := filepath.Join(root, "template")
|
||||
hooksDir := filepath.Join(dir, "hooks")
|
||||
if err := os.MkdirAll(hooksDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("create hook template: %w", err)
|
||||
}
|
||||
for _, name := range gitHookNames {
|
||||
path := filepath.Join(hooksDir, name)
|
||||
if err := os.WriteFile(path, []byte(fmt.Sprintf(hookWrapper, name)), 0o755); err != nil {
|
||||
return "", fmt.Errorf("write hook %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
if err := refreshRepositoryHooks(filepath.Join(root, "repos"), hooksDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// refreshRepositoryHooks updates repositories created with an older template.
|
||||
func refreshRepositoryHooks(reposRoot, templateHooks string) error {
|
||||
if _, err := os.Stat(reposRoot); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return filepath.WalkDir(reposRoot, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !entry.IsDir() || !strings.HasSuffix(entry.Name(), ".git") {
|
||||
return nil
|
||||
}
|
||||
dst := filepath.Join(path, "hooks")
|
||||
if err := os.MkdirAll(dst, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range gitHookNames {
|
||||
body, err := os.ReadFile(filepath.Join(templateHooks, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dst, name), body, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return filepath.SkipDir
|
||||
})
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnsureHookTemplatePassesTypeAndRepo(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
dir, err := EnsureHookTemplate(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := os.ReadFile(filepath.Join(dir, "hooks", "post-receive"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
text := string(body)
|
||||
for _, want := range []string{`post-receive "$repo" "$@"`, `repo=${repo_dir#"$SIMPLEGIT_REPO_ROOT"/}`, `repo=${repo%.git}`} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Errorf("generated hook missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureHookTemplateRefreshesExistingRepos(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
hook := filepath.Join(root, "repos", "alice", "demo.git", "hooks", "post-receive")
|
||||
if err := os.MkdirAll(filepath.Dir(hook), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(hook, []byte("old"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := EnsureHookTemplate(root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := os.ReadFile(hook)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(body), `post-receive "$repo"`) {
|
||||
t.Fatalf("existing hook was not refreshed: %s", body)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Default simplegit hook policy. Deployments can replace it with -hook-script.
|
||||
# $1 is the Git hook type, $2 is the owner/name repository, remaining arguments
|
||||
# are the original Git hook arguments. Original stdin and GIT_* env are intact.
|
||||
# Default standalone policy: append the hook invocation and its stdin to a log.
|
||||
# Integrations can supply another script with simplegit daemon -hook-script.
|
||||
|
||||
log_file=${SIMPLEGIT_HOOK_LOG:-${SIMPLEGIT_REPO_ROOT%/repos}/hooks.log}
|
||||
{
|
||||
printf '%s\t%s\t%s' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" "$2"
|
||||
shift 2
|
||||
for arg in "$@"; do
|
||||
printf '\t%s' "$arg"
|
||||
done
|
||||
printf '\n'
|
||||
sed 's/^/stdin\t/'
|
||||
} >>"$log_file" 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user