package forge import ( "crypto/sha256" "database/sql" "encoding/hex" "fmt" "io" "os" "os/exec" "path/filepath" "time" "forge-mesh/internal/api/types" "github.com/google/uuid" ) var targets = []struct { OS string Arch string }{ {"linux", "amd64"}, {"linux", "arm64"}, } // Pipeline builds and optionally signs agent binaries. type Pipeline struct { db *sql.DB artifactsDir string signingKey *KeyPair agentMainPath string version string } func NewPipeline(db *sql.DB, artifactsDir, signingKeyPath, agentMainPath, version string) (*Pipeline, error) { key, err := LoadOrCreateKey(signingKeyPath) if err != nil { return nil, err } return &Pipeline{ db: db, artifactsDir: artifactsDir, signingKey: key, agentMainPath: agentMainPath, version: version, }, nil } // BuildAll cross-compiles agent for linux amd64/arm64, signs, and stores artifacts. func (p *Pipeline) BuildAll(public bool) ([]types.Build, error) { var builds []types.Build for _, tgt := range targets { b, err := p.buildOne(tgt.OS, tgt.Arch, public) if err != nil { return nil, err } builds = append(builds, *b) } return builds, nil } func (p *Pipeline) buildOne(osName, arch string, public bool) (*types.Build, error) { outName := fmt.Sprintf("forge-mesh-agent-%s-%s", osName, arch) outPath := filepath.Join(p.artifactsDir, outName) cmd := exec.Command("go", "build", "-trimpath", "-ldflags=-s -w", "-o", outPath, p.agentMainPath, ) cmd.Env = append(os.Environ(), "GOOS="+osName, "GOARCH="+arch, "CGO_ENABLED=0", ) if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("build %s/%s: %w\n%s", osName, arch, err, out) } data, err := os.ReadFile(outPath) if err != nil { return nil, err } sum := sha256.Sum256(data) checksum := hex.EncodeToString(sum[:]) sig := p.signingKey.Sign(data) build := types.Build{ ID: uuid.NewString(), OS: osName, Arch: arch, Version: p.version, Checksum: checksum, Signature: sig, Public: public, Path: outPath, CreatedAt: time.Now().UTC(), } if err := p.saveBuild(&build); err != nil { return nil, err } return &build, nil } func (p *Pipeline) saveBuild(b *types.Build) error { pub := 0 if b.Public { pub = 1 } _, err := p.db.Exec(` INSERT INTO builds (id, os, arch, version, checksum, signature, public, path, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, b.ID, b.OS, b.Arch, b.Version, b.Checksum, b.Signature, pub, b.Path, b.CreatedAt.Format(time.RFC3339)) return err } // LatestPublic returns the newest public build for os/arch. func LatestPublic(db *sql.DB, osName, arch string) (*types.Build, error) { row := db.QueryRow(` SELECT id, os, arch, version, checksum, signature, public, path, created_at FROM builds WHERE public = 1 AND os = ? AND arch = ? ORDER BY created_at DESC LIMIT 1 `, osName, arch) var b types.Build var pub int var path sql.NullString var createdAt string err := row.Scan(&b.ID, &b.OS, &b.Arch, &b.Version, &b.Checksum, &b.Signature, &pub, &path, &createdAt) if err != nil { return nil, err } b.Public = pub == 1 if path.Valid { b.Path = path.String } b.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) return &b, nil } // GetBuild loads a build by ID. func GetBuild(db *sql.DB, id string) (*types.Build, error) { row := db.QueryRow(` SELECT id, os, arch, version, checksum, signature, public, path, created_at FROM builds WHERE id = ? `, id) var b types.Build var pub int var path sql.NullString var createdAt string err := row.Scan(&b.ID, &b.OS, &b.Arch, &b.Version, &b.Checksum, &b.Signature, &pub, &path, &createdAt) if err != nil { return nil, err } b.Public = pub == 1 if path.Valid { b.Path = path.String } b.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) return &b, nil } // PublicKey returns the pipeline signing public key hex. func (p *Pipeline) PublicKey() string { return p.signingKey.PublicKeyHex() } // CopyArtifact streams a build artifact to w. func CopyArtifact(path string, w io.Writer) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() _, err = io.Copy(w, f) return err }