feat: build runtime releases from pinned Git branch commits

This commit is contained in:
2026-10-14 14:25:27 +08:00
parent 9a71772771
commit 20635ffaec
23 changed files with 999 additions and 31 deletions
+217
View File
@@ -0,0 +1,217 @@
package api
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"errors"
"io"
"net/url"
"os"
"os/exec"
"path"
"regexp"
"strings"
"syscall"
"time"
)
type releaseManifest struct {
SchemaVersion int `json:"schema_version"`
ReleaseID string `json:"release_id"`
Version string `json:"version"`
DeploymentSchema int `json:"deployment_schema"`
Platforms []string `json:"platforms"`
}
type releaseInfo struct {
releaseManifest
Repository string `json:"source_repository"`
Commit string `json:"source_commit"`
}
var errReleaseIncompatible = errors.New("release layout or platform is incompatible")
var releaseCommit = regexp.MustCompile(`^[0-9a-f]{40}$`)
var releaseID = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`)
const releaseLimit = 32 << 20
func releaseSource() (string, string, error) {
repository := os.Getenv("DSH_RELEASE_REPOSITORY")
if repository == "" {
repository = "https://git.misaka.ren/m1saka/dsh-web.git"
}
branch := os.Getenv("DSH_RELEASE_BRANCH")
if branch == "" {
branch = "dockerfile"
}
u, err := url.Parse(repository)
if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || strings.ContainsAny(repository, "\r\n") || !regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]*$`).MatchString(branch) || strings.Contains(branch, "..") {
return "", "", errors.New("invalid trusted release source configuration")
}
return repository, branch, nil
}
type boundedReleaseBuffer struct{ bytes.Buffer }
func (b *boundedReleaseBuffer) Write(p []byte) (int, error) {
if b.Len()+len(p) > releaseLimit {
return 0, errors.New("release exceeds size limit")
}
return b.Buffer.Write(p)
}
func gitReleaseCommand(ctx context.Context, dir string, args ...string) ([]byte, error) {
gitArgs := append([]string{"-c", "credential.helper=", "-c", "http.followRedirects=false", "-c", "core.hooksPath=/dev/null", "-c", "protocol.allow=never", "-c", "protocol.https.allow=always", "-c", "fetch.unpackLimit=1"}, args...)
// The inherited file-size limit also bounds Git's on-disk pack, not just stdout.
cmd := exec.CommandContext(ctx, "/bin/sh", append([]string{"-c", "ulimit -f 32768; exec git \"$@\"", "git"}, gitArgs...)...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Cancel = func() error { return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) }
cmd.Dir = dir
cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null", "GIT_TERMINAL_PROMPT=0", "GIT_LFS_SKIP_SMUDGE=1"}
cmd.WaitDelay = time.Second
var out boundedReleaseBuffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, errors.New("trusted release Git operation failed")
}
return out.Bytes(), nil
}
var releaseFetchSlot = make(chan struct{}, 1)
func readRelease(ctx context.Context, repository, ref string) (releaseInfo, io.Reader, error) {
var info releaseInfo
ctx, cancel := context.WithTimeout(ctx, 45*time.Second)
defer cancel()
select {
case releaseFetchSlot <- struct{}{}:
defer func() { <-releaseFetchSlot }()
case <-ctx.Done():
return info, nil, ctx.Err()
}
dir, err := os.MkdirTemp("", "dsh-release-")
if err != nil {
return info, nil, err
}
defer os.RemoveAll(dir)
if _, err = gitReleaseCommand(ctx, dir, "init", "--quiet"); err != nil {
return info, nil, err
}
if _, err = gitReleaseCommand(ctx, dir, "fetch", "--quiet", "--depth=1", "--no-tags", repository, ref); err != nil {
return info, nil, err
}
commit, err := gitReleaseCommand(ctx, dir, "rev-parse", "FETCH_HEAD^{commit}")
if err != nil {
return info, nil, err
}
info.Commit = strings.TrimSpace(string(commit))
info.Repository = repository
if !releaseCommit.MatchString(info.Commit) || (releaseCommit.MatchString(ref) && info.Commit != ref) {
return info, nil, errors.New("release commit mismatch")
}
archive, err := gitReleaseCommand(ctx, dir, "archive", "--format=tar", info.Commit, "--", "release.json", "image")
if err != nil {
return info, nil, err
}
return parseReleaseArchive(info, archive)
}
func parseReleaseArchive(info releaseInfo, archive []byte) (releaseInfo, io.Reader, error) {
var manifest []byte
tr := tar.NewReader(bytes.NewReader(archive))
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return info, nil, err
}
if h.Name == "release.json" && h.Typeflag == tar.TypeReg {
if h.Size > 16384 {
return info, nil, errors.New("release manifest too large")
}
manifest, err = io.ReadAll(tr)
if err != nil {
return info, nil, err
}
}
}
dec := json.NewDecoder(bytes.NewReader(manifest))
dec.DisallowUnknownFields()
if dec.Decode(&info.releaseManifest) != nil || dec.Decode(new(any)) != io.EOF {
return info, nil, errors.New("invalid release manifest")
}
m := info.releaseManifest
compatible := false
for _, p := range m.Platforms {
if p == imagePlatform {
compatible = true
}
}
if m.SchemaVersion != 1 || !releaseID.MatchString(m.ReleaseID) || !validBuildVersion(m.Version) || m.DeploymentSchema < 1 || len(m.Platforms) == 0 {
return info, nil, errors.New("unsupported release manifest or platform")
}
if m.DeploymentSchema != 7 || !compatible {
return info, nil, errReleaseIncompatible
}
var out bytes.Buffer
tw := tar.NewWriter(&out)
tr = tar.NewReader(bytes.NewReader(archive))
dockerfile := false
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return info, nil, err
}
name := h.Name
if !strings.HasPrefix(name, "image/") {
continue
}
name = strings.TrimPrefix(name, "image/")
if name == "" {
continue
}
if path.IsAbs(name) || path.Clean(strings.TrimSuffix(name, "/")) != strings.TrimSuffix(name, "/") || name == ".." || strings.HasPrefix(name, "../") {
return info, nil, errors.New("unsafe release context path")
}
excluded := false
for _, part := range strings.Split(name, "/") {
if part == ".git" || part == ".env" || strings.HasPrefix(part, ".env.") || part == ".netrc" || part == ".npmrc" || part == ".ssh" || part == ".git-credentials" {
excluded = true
}
}
if excluded {
continue
}
if h.Typeflag != tar.TypeReg && h.Typeflag != tar.TypeDir {
return info, nil, errors.New("release context links and special files are not supported")
}
h.Name = name
h.PAXRecords = nil
h.Uname = ""
h.Gname = ""
h.Uid = 0
h.Gid = 0
if name == "Dockerfile" && h.Typeflag == tar.TypeReg {
dockerfile = true
}
if err = tw.WriteHeader(h); err != nil {
return info, nil, err
}
if _, err = io.Copy(tw, tr); err != nil {
return info, nil, err
}
}
if !dockerfile {
return info, nil, errors.New("release context Dockerfile missing")
}
if err := tw.Close(); err != nil {
return info, nil, err
}
return info, bytes.NewReader(out.Bytes()), nil
}