Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

175 lines
5.5 KiB
Go

package agentextension
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
"github.com/tutti-os/tutti/packages/agent/daemon/httpx"
)
func downloadRuntimeBinary(
ctx context.Context,
client *http.Client,
artifact RuntimeBinaryArtifact,
destination string,
) (runtimeExecutableFingerprint, error) {
if err := validateRuntimeBinaryArtifacts([]RuntimeBinaryArtifact{artifact}); err != nil {
return runtimeExecutableFingerprint{}, err
}
if err := os.MkdirAll(filepath.Dir(destination), 0o700); err != nil {
return runtimeExecutableFingerprint{}, err
}
file, err := os.OpenFile(destination, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
return runtimeExecutableFingerprint{}, err
}
keep := false
defer func() {
_ = file.Close()
if !keep {
_ = os.Remove(destination)
}
}()
fingerprint, err := downloadRuntimeBinaryToFile(ctx, client, artifact, file)
if err != nil {
return runtimeExecutableFingerprint{}, err
}
if err := file.Close(); err != nil {
return runtimeExecutableFingerprint{}, err
}
if err := verifyRuntimeExecutableUnchanged(destination, fingerprint); err != nil {
return runtimeExecutableFingerprint{}, errors.New("runtime binary changed after download verification")
}
keep = true
return fingerprint, nil
}
func downloadRuntimeBinaryToFile(
ctx context.Context,
client *http.Client,
artifact RuntimeBinaryArtifact,
file *os.File,
) (runtimeExecutableFingerprint, error) {
const maxAttempts = 3
var lastErr error
for attempt := 1; attempt <= maxAttempts; attempt++ {
if attempt > 1 {
if err := file.Truncate(0); err != nil {
return runtimeExecutableFingerprint{}, err
}
if _, err := file.Seek(0, io.SeekStart); err != nil {
return runtimeExecutableFingerprint{}, err
}
timer := time.NewTimer(time.Duration(attempt-1) * 750 * time.Millisecond)
select {
case <-ctx.Done():
timer.Stop()
return runtimeExecutableFingerprint{}, ctx.Err()
case <-timer.C:
}
}
fingerprint, err := downloadRuntimeBinaryToFileOnce(ctx, client, artifact, file)
if err == nil {
return fingerprint, nil
}
lastErr = err
if !isTransientRuntimeDownloadError(err) || attempt == maxAttempts {
break
}
}
return runtimeExecutableFingerprint{}, lastErr
}
func downloadRuntimeBinaryToFileOnce(
ctx context.Context,
client *http.Client,
artifact RuntimeBinaryArtifact,
file *os.File,
) (runtimeExecutableFingerprint, error) {
if err := validateRuntimeBinaryArtifacts([]RuntimeBinaryArtifact{artifact}); err != nil {
return runtimeExecutableFingerprint{}, err
}
if file == nil {
return runtimeExecutableFingerprint{}, errors.New("runtime binary destination descriptor is required")
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, artifact.URL, nil)
if err != nil {
return runtimeExecutableFingerprint{}, err
}
request.Header.Set("Accept-Encoding", "identity")
if client == nil {
client = httpx.NewClient(15 * time.Minute)
}
client = httpsOnlyRedirectClient(client, errors.New("runtime binary download redirected away from HTTPS"))
response, err := client.Do(request)
if err != nil {
return runtimeExecutableFingerprint{}, err
}
defer response.Body.Close()
if response.Request == nil || response.Request.URL == nil || response.Request.URL.Scheme != "https" {
return runtimeExecutableFingerprint{}, errors.New("runtime binary download redirected away from HTTPS")
}
if response.StatusCode != http.StatusOK {
return runtimeExecutableFingerprint{}, fmt.Errorf("runtime binary download returned HTTP %d", response.StatusCode)
}
if response.ContentLength >= 0 && response.ContentLength != artifact.SizeBytes {
return runtimeExecutableFingerprint{}, errors.New("runtime binary Content-Length does not match signed metadata")
}
hash := sha256.New()
written, err := io.Copy(io.MultiWriter(file, hash), io.LimitReader(response.Body, artifact.SizeBytes+1))
if err != nil {
return runtimeExecutableFingerprint{}, err
}
if written != artifact.SizeBytes {
return runtimeExecutableFingerprint{}, errors.New("runtime binary size does not match signed metadata")
}
digest := hex.EncodeToString(hash.Sum(nil))
if digest != artifact.SHA256 {
return runtimeExecutableFingerprint{}, errors.New("runtime binary SHA-256 does not match signed metadata")
}
if err := file.Sync(); err != nil {
return runtimeExecutableFingerprint{}, err
}
if err := file.Chmod(0o700); err != nil {
return runtimeExecutableFingerprint{}, err
}
if err := file.Sync(); err != nil {
return runtimeExecutableFingerprint{}, err
}
expected := runtimeExecutableFingerprint{SHA256: artifact.SHA256, Size: artifact.SizeBytes}
info, err := file.Stat()
if err != nil || !isExecutableFileInfo(info) || info.Size() != artifact.SizeBytes {
return runtimeExecutableFingerprint{}, errors.New("runtime binary changed after download verification")
}
return expected, nil
}
func isTransientRuntimeDownloadError(err error) bool {
return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
}
func verifyRuntimeExecutableUnchanged(path string, expected runtimeExecutableFingerprint) error {
info, err := os.Lstat(path)
if err != nil || !isExecutableFileInfo(info) || info.Mode()&os.ModeSymlink != 0 {
return errors.New("runtime executable is not an ordinary executable file")
}
actual, err := fingerprintRuntimeExecutable(path)
if err != nil {
return err
}
if actual != expected || actual.SHA256 == "" {
return errors.New("runtime executable changed across verification boundary")
}
return nil
}