package runtime import ( "archive/tar" "archive/zip" "compress/gzip" "context" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "io" "io/fs" "log" "net/url" "os" "os/exec" "path/filepath" "strings" "time" "browser.local/run/protocol" ) const distributionArtifactChunkSize = 1024 * 1024 type packageConfigPayload struct { Kind string `json:"kind"` ServerInstanceID string `json:"serverInstanceId"` PluginID string `json:"pluginId"` RunEndpointID string `json:"runEndpointId,omitempty"` ProfileKey string `json:"profileKey,omitempty"` TargetOS string `json:"targetOs"` TargetArch string `json:"targetArch"` SecretRef string `json:"secretRef"` KeyGeneration int `json:"keyGeneration"` AuthKey string `json:"authKey"` } func (worker *Worker) executeDistributionBuild(ctx context.Context, assignment protocol.RunJobAssignment) LifecycleExecutionResult { if err := protocol.ValidateRunJobAssignment(assignment); err != nil { return lifecycleFailure("unsafe_distribution_build", err.Error()) } state, err := worker.registeredState() if err != nil { return distributionBuildFailure("build_unregistered", "Run worker is not registered") } input, err := worker.client.GetDistributionBuildInput(ctx, protocol.DistributionBuildInputRequest{ RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, JobID: assignment.JobID, LeaseToken: assignment.LeaseToken, Attempt: assignment.Attempt, }) if err != nil { return distributionBuildFailure("build_input_failed", "could not load authenticated build input") } if err := validateDistributionBuildInput(assignment, input); err != nil { return distributionBuildFailure("unsafe_build_input", err.Error()) } report := func(percent int, message string) error { state, err := worker.registeredState() if err != nil { return err } response, err := worker.client.UpdateJobProgress(ctx, protocol.RunJobProgressRequest{ RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, JobID: assignment.JobID, LeaseToken: assignment.LeaseToken, Attempt: assignment.Attempt, Progress: protocol.RunJobProgressReport{Percent: percent, Message: message}, Sequence: worker.nextProgressSequence(assignment.ProgressSequence), }) if err != nil { return err } if !response.Accepted { return fmt.Errorf("distribution build progress was not accepted") } assignment = response.Job return worker.journal.Store(assignment) } workspace := distributionBuildWorkspace(worker.cfg.WorkspaceRoot, input.PluginID, assignment.JobID) if err := os.RemoveAll(workspace); err != nil { return distributionBuildFailure("workspace_prepare_failed", "could not reset isolated build workspace") } if err := os.MkdirAll(workspace, 0o700); err != nil { return distributionBuildFailure("workspace_prepare_failed", "could not create isolated build workspace") } defer os.RemoveAll(workspace) if err := report(12, "git_sync: preparing approved source"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report source preparation") } sourceRoot, err := worker.prepareDistributionSource(ctx, workspace, input) if err != nil { return distributionBuildFailure("git_sync_failed", "approved source checkout failed") } if err := report(28, "env_check: validating Go build environment"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report environment check") } if err := fixedCommand(ctx, sourceRoot, nil, "go", "version"); err != nil { return distributionBuildFailure("env_check_failed", "Go build environment is unavailable") } configPath := "" if input.ComponentKind == "client-manager" { var err error configPath, err = writeDistributionConfig(sourceRoot, input, worker.clientPlatformURL()) if err != nil { return distributionBuildFailure("config_injection_failed", "could not inject scoped component configuration") } } if err := report(45, "deps_download: downloading Go modules"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report dependency download") } buildEnv := []string{"GOOS=" + input.TargetOS, "GOARCH=" + input.TargetArch, "CGO_ENABLED=0"} if err := fixedCommand(ctx, sourceRoot, buildEnv, "go", "mod", "download"); err != nil { return distributionBuildFailure("deps_download_failed", "Go module download failed") } if err := report(65, "build_compile: compiling target executable"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report compilation") } binaryPath := filepath.Join(workspace, input.OutputFilename) entry := "." if input.ComponentKind == "run" { entry = "./cmd/run" } ldflags := "-s -w" if input.ComponentKind == "run" { ldflags = buildRunLDFlags(input, distributionBuildPlatformURL(worker, input)) } if err := fixedCommand(ctx, sourceRoot, buildEnv, "go", "build", "-trimpath", "-ldflags", ldflags, "-o", binaryPath, entry); err != nil { return distributionBuildFailure("build_compile_failed", "Go compilation failed") } if err := report(82, "package_finalize: creating distribution archive"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report packaging") } if input.ComponentKind == "run" { payload, err := os.ReadFile(binaryPath) if err != nil { return distributionBuildFailure("package_finalize_failed", "run executable could not be read") } if err := worker.uploadDistributionArtifact(ctx, assignment, input.ArtifactID, payload); err != nil { return distributionBuildFailure("artifact_upload_failed", "distribution artifact upload failed") } if err := report(96, "package_finalize: artifact upload completed"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report artifact upload") } return LifecycleExecutionResult{ State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "package_finalize: build artifact available"}, ResultRef: "artifact://" + input.ArtifactID, Message: "distribution build completed", } } archivePath := filepath.Join(workspace, archiveFilename(input)) if err := createDistributionArchive(archivePath, input.PackageFormat, binaryPath, configPath); err != nil { return distributionBuildFailure("package_finalize_failed", "distribution archive creation failed") } payload, err := os.ReadFile(archivePath) if err != nil { return distributionBuildFailure("package_finalize_failed", "distribution archive could not be read") } if err := worker.uploadDistributionArtifact(ctx, assignment, input.ArtifactID, payload); err != nil { return distributionBuildFailure("artifact_upload_failed", "distribution artifact upload failed") } if err := report(96, "package_finalize: artifact upload completed"); err != nil { return distributionBuildFailure("progress_report_failed", "could not report artifact upload") } return LifecycleExecutionResult{ State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "package_finalize: build artifact available"}, ResultRef: "artifact://" + input.ArtifactID, Message: "distribution build completed", } } func (worker *Worker) prepareDistributionSource(ctx context.Context, workspace string, input protocol.DistributionBuildInputResponse) (string, error) { if input.ComponentKind == "run" { root, err := filepath.Abs(worker.cfg.BuildSourceRoot) if err != nil { return "", err } root, err = filepath.EvalSymlinks(root) if err != nil { return "", err } if _, err := os.Stat(filepath.Join(root, "go.mod")); err != nil { return "", err } isolatedSource := filepath.Join(workspace, "source") if err := copyDistributionSource(root, isolatedSource, workspace); err != nil { return "", err } if err := writeRunWorkspaceSeedConfig(isolatedSource, input.WorkspaceSeed); err != nil { return "", err } return isolatedSource, nil } checkout := filepath.Join(workspace, "source") if err := os.MkdirAll(checkout, 0o700); err != nil { return "", err } if err := fixedCommand(ctx, checkout, nil, "git", "init", "--quiet"); err != nil { return "", err } if err := fixedCommand(ctx, checkout, nil, "git", "remote", "add", "origin", input.RepositoryURL); err != nil { return "", err } if err := fixedCommand(ctx, checkout, nil, "git", "fetch", "--quiet", "--depth", "1", "origin", input.SourceRevision); err != nil { return "", err } if err := fixedCommand(ctx, checkout, nil, "git", "checkout", "--quiet", "--detach", "FETCH_HEAD"); err != nil { return "", err } return checkout, nil } func writeRunWorkspaceSeedConfig(sourceRoot string, encodedSeed string) error { encodedSeed = strings.TrimSpace(encodedSeed) if encodedSeed == "" { return nil } if _, err := base64.StdEncoding.DecodeString(encodedSeed); err != nil { return fmt.Errorf("workspace seed is invalid") } configDir := filepath.Join(sourceRoot, "config") if err := os.MkdirAll(configDir, 0o700); err != nil { return err } body := fmt.Sprintf("package config\n\nfunc init() { BuildWorkspaceSeed = %q }\n", encodedSeed) return os.WriteFile(filepath.Join(configDir, "workspace_seed_generated.go"), []byte(body), 0o600) } func copyDistributionSource(sourceRoot string, destinationRoot string, workspace string) error { workspace, err := filepath.Abs(workspace) if err != nil { return err } return filepath.WalkDir(sourceRoot, func(path string, entry fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } if path == workspace || strings.HasPrefix(path, workspace+string(filepath.Separator)) { if entry.IsDir() { return filepath.SkipDir } return nil } relative, err := filepath.Rel(sourceRoot, path) if err != nil { return err } if relative == "." { return os.MkdirAll(destinationRoot, 0o700) } if entry.Name() == ".git" && entry.IsDir() { return filepath.SkipDir } if entry.Type()&os.ModeSymlink != 0 { return fmt.Errorf("trusted build source contains a symbolic link") } destination := filepath.Join(destinationRoot, relative) if entry.IsDir() { return os.MkdirAll(destination, 0o700) } info, err := entry.Info() if err != nil { return err } if !info.Mode().IsRegular() { return nil } input, err := os.Open(path) if err != nil { return err } output, err := os.OpenFile(destination, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) if err != nil { input.Close() return err } _, copyErr := io.Copy(output, input) inputCloseErr := input.Close() closeErr := output.Close() if copyErr != nil { return copyErr } if inputCloseErr != nil { return inputCloseErr } return closeErr }) } func writeDistributionConfig(sourceRoot string, input protocol.DistributionBuildInputResponse, platformURL string) (string, error) { if input.ComponentKind == "client-manager" { content := fmt.Sprintf("server_url: %q\nserver_instance_id: %q\nscum_client_credential: %q\nscum_client_name: %q\nscum_client_version: %q\nscum_client_machine_label: %q\nftp_provider: 3\n", platformURL, input.ServerInstanceID, input.AuthKey, input.ProfileKey, "platform-build", "managed-client") if err := os.WriteFile(filepath.Join(sourceRoot, "config.yaml"), []byte(content), 0o600); err != nil { return "", err } return filepath.Join(sourceRoot, "config.yaml"), nil } return "", fmt.Errorf("run distributions do not use sidecar package config") } func distributionBuildPlatformURL(worker *Worker, input protocol.DistributionBuildInputResponse) string { if value := strings.TrimSpace(input.PlatformURL); value != "" { return value } return worker.clientPlatformURL() } func buildRunLDFlags(input protocol.DistributionBuildInputResponse, platformURL string) string { values := map[string]string{ "BuildMode": "worker", "BuildPlatformURL": platformURL, "BuildRunEndpointID": input.RunEndpointID, "BuildDisplayName": "Run-" + input.ServerInstanceID, "BuildRegistrationToken": input.AuthKey, "BuildServerInstanceID": input.ServerInstanceID, "BuildPluginID": input.PluginID, "BuildComponentKind": input.ComponentKind, "BuildComponentKey": input.ProfileKey, "BuildKeyGeneration": fmt.Sprint(input.KeyGeneration), "BuildVersion": input.TargetRelease, } flags := []string{"-s", "-w"} for _, name := range []string{"BuildMode", "BuildPlatformURL", "BuildRunEndpointID", "BuildDisplayName", "BuildRegistrationToken", "BuildServerInstanceID", "BuildPluginID", "BuildComponentKind", "BuildComponentKey", "BuildKeyGeneration", "BuildVersion"} { flags = append(flags, "-X", "browser.local/run/config."+name+"="+values[name]) } return strings.Join(flags, " ") } func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment protocol.RunJobAssignment, artifactID string, payload []byte) error { checksum := bytesChecksum(payload) state, err := worker.registeredState() if err != nil { return err } opened, err := worker.client.OpenArtifactTransfer(ctx, protocol.ArtifactTransferOpenRequest{ RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, ArtifactID: artifactID, Direction: "upload", OwnerKind: "job", OwnerID: assignment.JobID, SizeBytes: int64(len(payload)), ChunkSizeBytes: distributionArtifactChunkSize, Checksum: checksum, IdempotencyKey: "distribution-build:" + assignment.JobID, }) if err != nil { return err } received := map[int]bool{} for _, index := range opened.ReceivedChunkIndexes { received[index] = true } for index, offset := 0, 0; offset < len(payload); index, offset = index+1, offset+distributionArtifactChunkSize { if received[index] { continue } state, err := worker.registeredState() if err != nil { return err } end := offset + distributionArtifactChunkSize if end > len(payload) { end = len(payload) } chunk := payload[offset:end] if _, err := worker.client.UploadArtifactChunk(ctx, protocol.ArtifactChunkUploadRequest{ RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, TransferID: opened.TransferID, ArtifactID: artifactID, ChunkIndex: index, Offset: int64(offset), SizeBytes: len(chunk), Checksum: bytesChecksum(chunk), Payload: chunk, }); err != nil { return err } } state, err = worker.registeredState() if err != nil { return err } completed, err := worker.client.CompleteArtifactTransfer(ctx, protocol.ArtifactTransferCompleteRequest{ RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, TransferID: opened.TransferID, ArtifactID: artifactID, Checksum: checksum, SizeBytes: int64(len(payload)), }) if err != nil { return err } if !completed.Completed || completed.Artifact.State != "available" { return fmt.Errorf("artifact transfer did not complete") } return nil } func validateDistributionBuildInput(assignment protocol.RunJobAssignment, input protocol.DistributionBuildInputResponse) error { if input.JobID != assignment.JobID || input.ServerInstanceID != assignment.ServerInstanceID { return fmt.Errorf("build input scope does not match job") } if input.ComponentKind != "run" && input.ComponentKind != "client-manager" { return fmt.Errorf("build component kind is unsupported") } if !protocol.ValidLogicalFileKey(input.RunEndpointID) { return fmt.Errorf("generated Run endpoint identity is unsafe") } if input.ComponentKind == "client-manager" && input.RunEndpointID != assignment.RunEndpointID { return fmt.Errorf("client-manager build target does not match job") } if strings.TrimSpace(input.PluginID) == "" { return fmt.Errorf("build plugin id is required") } if input.ComponentKind == "client-manager" && !approvedHTTPSGitRepository(input.RepositoryURL) { return fmt.Errorf("client-manager repository is not approved") } if input.ComponentKind == "client-manager" && strings.TrimSpace(input.SourceRevision) == "" { return fmt.Errorf("client-manager source revision is required") } if input.TargetOS != "windows" && input.TargetOS != "linux" && input.TargetOS != "darwin" { return fmt.Errorf("target OS is unsupported") } if input.TargetArch != "amd64" && input.TargetArch != "arm64" { return fmt.Errorf("target architecture is unsupported") } if input.ComponentKind == "run" && !protocol.ValidLogicalFileKey(input.TargetRelease) { return fmt.Errorf("target release is unsafe") } if input.ComponentKind == "run" && input.PackageFormat != "raw-executable" { return fmt.Errorf("run package format must be raw-executable") } if input.ComponentKind == "run" && !validDistributionPlatformURL(input.PlatformURL) { return fmt.Errorf("run platform URL is invalid") } if input.ComponentKind == "run" && strings.TrimSpace(input.WorkspaceSeed) != "" { if _, err := base64.StdEncoding.DecodeString(strings.TrimSpace(input.WorkspaceSeed)); err != nil { return fmt.Errorf("run workspace seed is invalid") } } if input.ComponentKind == "client-manager" && input.PackageFormat != "zip" && input.PackageFormat != "tar.gz" { return fmt.Errorf("package format is unsupported") } if strings.TrimSpace(input.ArtifactID) == "" || strings.TrimSpace(input.OutputFilename) == "" || strings.TrimSpace(input.AuthKey) == "" { return fmt.Errorf("build input is incomplete") } return nil } func approvedHTTPSGitRepository(value string) bool { parsed, err := url.ParseRequestURI(strings.TrimSpace(value)) return err == nil && parsed.Scheme == "https" && parsed.Host != "" && parsed.User == nil && parsed.RawQuery == "" && parsed.Fragment == "" && strings.HasSuffix(parsed.Path, ".git") } func validDistributionPlatformURL(value string) bool { parsed, err := url.ParseRequestURI(strings.TrimSpace(value)) return err == nil && (parsed.Scheme == "https" || parsed.Scheme == "http") && parsed.Host != "" && parsed.User == nil && parsed.RawQuery == "" && parsed.Fragment == "" } func fixedCommand(ctx context.Context, dir string, extraEnv []string, name string, args ...string) error { startedAt := time.Now() commandLine := redactedDistributionCommandLine(name, args) log.Printf("RUN phase=distribution_build.command status=starting workdir=%s command=%s envKeys=%s", safeOptional(dir), commandLine, envKeysSummary(extraEnvMap(extraEnv), nil)) command := exec.CommandContext(ctx, name, args...) command.Dir = dir command.Env = append(os.Environ(), extraEnv...) command.Stdout = io.Discard command.Stderr = io.Discard if err := command.Run(); err != nil { log.Printf("RUN phase=distribution_build.command status=failed command=%s durationMs=%d error=%s", commandLine, time.Since(startedAt).Milliseconds(), RedactText(err.Error())) return err } log.Printf("RUN phase=distribution_build.command status=complete command=%s durationMs=%d", commandLine, time.Since(startedAt).Milliseconds()) return nil } func redactedDistributionCommandLine(name string, args []string) string { parts := append([]string{name}, args...) redacted := append([]string(nil), parts...) for index, part := range redacted { if part == "-ldflags" && index+1 < len(redacted) { redacted[index+1] = "[redacted-ldflags]" continue } if strings.Contains(part, "BuildRegistrationToken=") { redacted[index] = "[redacted-ldflags]" } } return redactedCommandLine(redacted) } func extraEnvMap(entries []string) map[string]string { if len(entries) == 0 { return nil } env := make(map[string]string, len(entries)) for _, entry := range entries { key, value, ok := strings.Cut(entry, "=") if !ok { key = entry value = "" } env[key] = value } return env } func createDistributionArchive(path string, format string, binaryPath string, configPath string) error { if format == "zip" { file, err := os.Create(path) if err != nil { return err } writer := zip.NewWriter(file) if err := addZipFile(writer, binaryPath); err != nil { writer.Close() file.Close() return err } if err := addZipFile(writer, configPath); err != nil { writer.Close() file.Close() return err } if err := writer.Close(); err != nil { file.Close() return err } return file.Close() } file, err := os.Create(path) if err != nil { return err } gzipWriter := gzip.NewWriter(file) tarWriter := tar.NewWriter(gzipWriter) if err := addTarFile(tarWriter, binaryPath); err != nil { tarWriter.Close() gzipWriter.Close() file.Close() return err } if err := addTarFile(tarWriter, configPath); err != nil { tarWriter.Close() gzipWriter.Close() file.Close() return err } if err := tarWriter.Close(); err != nil { gzipWriter.Close() file.Close() return err } if err := gzipWriter.Close(); err != nil { file.Close() return err } return file.Close() } func addZipFile(writer *zip.Writer, path string) error { body, err := os.ReadFile(path) if err != nil { return err } entry, err := writer.Create(filepath.Base(path)) if err != nil { return err } _, err = entry.Write(body) return err } func addTarFile(writer *tar.Writer, path string) error { info, err := os.Stat(path) if err != nil { return err } header := &tar.Header{Name: filepath.Base(path), Mode: 0o600, Size: info.Size()} if strings.HasSuffix(filepath.Base(path), ".exe") || filepath.Base(path) == "run" { header.Mode = 0o700 } if err := writer.WriteHeader(header); err != nil { return err } file, err := os.Open(path) if err != nil { return err } defer file.Close() _, err = io.Copy(writer, file) return err } func archiveFilename(input protocol.DistributionBuildInputResponse) string { base := "run-" + input.ServerInstanceID if input.ComponentKind == "client-manager" { base = input.ProfileKey + "-" + input.ServerInstanceID } if input.PackageFormat == "zip" { return base + ".zip" } return base + ".tar.gz" } func distributionBuildWorkspace(workspaceRoot string, pluginID string, jobID string) string { return filepath.Join(workspaceRoot, "distribution-builds", safeWorkspaceName(pluginID), safeWorkspaceName(jobID)) } func bytesChecksum(payload []byte) string { sum := sha256.Sum256(payload) return "sha256:" + hex.EncodeToString(sum[:]) } func safeWorkspaceName(value string) string { var builder strings.Builder for _, char := range value { if char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char >= '0' && char <= '9' || char == '-' || char == '_' { builder.WriteRune(char) } else { builder.WriteByte('-') } } return builder.String() } func distributionBuildFailure(code string, message string) LifecycleExecutionResult { return LifecycleExecutionResult{ State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: message}, Message: message, ErrorCode: code, } } func (worker *Worker) clientPlatformURL() string { if client, ok := worker.client.(interface{ BaseURL() string }); ok { return client.BaseURL() } return worker.cfg.PlatformURL }