mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
126 lines
3.5 KiB
Go
126 lines
3.5 KiB
Go
//go:build linux
|
|
|
|
package agent
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/workspace"
|
|
)
|
|
|
|
type PreparedBinaryUsageError struct {
|
|
Operation string
|
|
Reason string
|
|
}
|
|
|
|
func (err *PreparedBinaryUsageError) Error() string {
|
|
return fmt.Sprintf("prepared agent binary %s: %s", err.Operation, err.Reason)
|
|
}
|
|
|
|
// PreparedBinary owns a build-only workspace. Each successful Start lease keeps
|
|
// that workspace alive; Agent workspaces own their own config, logs, and process tracking.
|
|
type PreparedBinary struct {
|
|
workspace *workspace.Workspace
|
|
binaryPath string
|
|
mu sync.Mutex
|
|
consumers int
|
|
closed bool
|
|
}
|
|
|
|
func PrepareBinary(ctx context.Context, sourceDir string) (*PreparedBinary, error) {
|
|
if err := validateSourceDir(sourceDir); err != nil {
|
|
return nil, &PreparedBinaryUsageError{Operation: "prepare", Reason: err.Error()}
|
|
}
|
|
workspaceRoot, err := workspace.New(context.WithoutCancel(ctx))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create prepared agent workspace: %w", err)
|
|
}
|
|
binaryPath, err := workspaceRoot.Build(ctx, agentBuildSpec(sourceDir))
|
|
if err != nil {
|
|
return nil, closePreparedWorkspace(workspaceRoot, err)
|
|
}
|
|
if err := exposePreparedBinary(workspaceRoot.Root(), binaryPath); err != nil {
|
|
return nil, closePreparedWorkspace(workspaceRoot, err)
|
|
}
|
|
return &PreparedBinary{workspace: workspaceRoot, binaryPath: binaryPath}, nil
|
|
}
|
|
|
|
func closePreparedWorkspace(workspaceRoot *workspace.Workspace, cause error) error {
|
|
return fmt.Errorf("prepare agent binary: %w", closeError(cause, workspaceRoot.Close()))
|
|
}
|
|
|
|
func exposePreparedBinary(root, binaryPath string) error {
|
|
for _, path := range []string{root, filepath.Dir(binaryPath)} {
|
|
if err := os.Chmod(path, 0o755); err != nil {
|
|
return fmt.Errorf("make prepared agent binary executable: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (prepared *PreparedBinary) BinaryPath() string {
|
|
if prepared == nil {
|
|
return ""
|
|
}
|
|
prepared.mu.Lock()
|
|
defer prepared.mu.Unlock()
|
|
return prepared.binaryPath
|
|
}
|
|
|
|
func (prepared *PreparedBinary) WorkspaceRoot() string {
|
|
if prepared == nil {
|
|
return ""
|
|
}
|
|
prepared.mu.Lock()
|
|
defer prepared.mu.Unlock()
|
|
return prepared.workspace.Root()
|
|
}
|
|
|
|
func (prepared *PreparedBinary) acquire() (string, func(), error) {
|
|
if prepared == nil {
|
|
return "", nil, &PreparedBinaryUsageError{Operation: "start", Reason: "is nil"}
|
|
}
|
|
prepared.mu.Lock()
|
|
defer prepared.mu.Unlock()
|
|
if prepared.workspace == nil || prepared.binaryPath == "" {
|
|
return "", nil, &PreparedBinaryUsageError{Operation: "start", Reason: "is uninitialized"}
|
|
}
|
|
if prepared.closed {
|
|
return "", nil, &PreparedBinaryUsageError{Operation: "start", Reason: "is closed"}
|
|
}
|
|
prepared.consumers++
|
|
return prepared.binaryPath, prepared.release, nil
|
|
}
|
|
|
|
func (prepared *PreparedBinary) release() {
|
|
prepared.mu.Lock()
|
|
defer prepared.mu.Unlock()
|
|
prepared.consumers--
|
|
}
|
|
|
|
func (prepared *PreparedBinary) Close() error {
|
|
if prepared == nil {
|
|
return &PreparedBinaryUsageError{Operation: "close", Reason: "is nil"}
|
|
}
|
|
prepared.mu.Lock()
|
|
defer prepared.mu.Unlock()
|
|
if prepared.workspace == nil || prepared.binaryPath == "" {
|
|
return &PreparedBinaryUsageError{Operation: "close", Reason: "is uninitialized"}
|
|
}
|
|
if prepared.closed {
|
|
return nil
|
|
}
|
|
if prepared.consumers != 0 {
|
|
return &PreparedBinaryUsageError{Operation: "close", Reason: "has active consumers"}
|
|
}
|
|
if err := prepared.workspace.Close(); err != nil {
|
|
return fmt.Errorf("close prepared agent workspace: %w", err)
|
|
}
|
|
prepared.closed = true
|
|
return nil
|
|
}
|