Add S3 erasure swarm with CloudFront signed magnets.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Operators configure bucket and CloudFront domain with env credentials; deploy plans upload RS 4+2 shards and attach signed edge URLs to BGP swarm magnets. Agents fetch LAN, CloudFront, then C2. Forge panel adds test and IAM policy JSON.
This commit is contained in:
AetherForge
2026-06-07 10:05:21 -07:00
parent 40d46408ea
commit 0795c511ab
23 changed files with 885 additions and 75 deletions

View File

@@ -0,0 +1,63 @@
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
import AwsErasureSwarmPanel from './AwsErasureSwarmPanel';
vi.mock('../../api/client', () => ({
api: {
updateConfig: vi.fn().mockResolvedValue({}),
testErasureSwarm: vi.fn().mockResolvedValue({ ok: true }),
getErasureSwarmPolicyJSON: vi.fn().mockResolvedValue({ iam_policy: '{}', bucket_policy: '{}', env_keys: [] }),
},
}));
import { api } from '../../api/client';
const baseServer = {
public_url: '',
stats_retention_hours: 168,
build_retention_days: 7,
pool_reconnect_seconds: 30,
websocket_ping_seconds: 30,
max_agents: 500,
max_build_size_mb: 50,
log_agent_connections: false,
log_share_submissions: false,
log_pool_traffic: false,
strict_wallet_validation: true,
dashboard_subtitle: '',
open_firewall_on_start: false,
aws_s3_shard_bucket: 'b',
aws_cloudfront_domain: 'd.cf.net',
};
describe('AwsErasureSwarmPanel', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
cleanup();
});
it('renders bucket and CloudFront inputs', () => {
render(<AwsErasureSwarmPanel server={baseServer} onServerChange={() => {}} />);
expect(screen.getByDisplayValue('b')).toBeTruthy();
expect(screen.getByDisplayValue('d.cf.net')).toBeTruthy();
expect(screen.getByText(/AWS Erasure Swarm/i)).toBeTruthy();
});
it('calls test endpoint on Test connection', async () => {
render(<AwsErasureSwarmPanel server={baseServer} onServerChange={() => {}} />);
fireEvent.click(screen.getByRole('button', { name: /Test connection/i }));
await waitFor(() => {
expect(api.testErasureSwarm).toHaveBeenCalledWith({ s3_bucket: 'b', cloudfront_domain: 'd.cf.net' });
});
});
it('loads policy JSON on button click', async () => {
render(<AwsErasureSwarmPanel server={baseServer} onServerChange={() => {}} />);
fireEvent.click(screen.getByRole('button', { name: /Generate policy JSON/i }));
await waitFor(() => {
expect(api.getErasureSwarmPolicyJSON).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,87 @@
import { useState } from 'react';
import { api } from '../../api/client';
import type { ServerSettings } from '../../types';
import { HelpTip } from '../HelpTip';
type Props = {
server: ServerSettings;
onServerChange: (patch: Partial<ServerSettings>) => void;
};
export default function AwsErasureSwarmPanel({ server, onServerChange }: Props) {
const [testing, setTesting] = useState(false);
const [testMsg, setTestMsg] = useState('');
const [policyJSON, setPolicyJSON] = useState('');
const bucket = server.aws_s3_shard_bucket ?? '';
const cfDomain = server.aws_cloudfront_domain ?? '';
const saveFields = async (patch: Partial<ServerSettings>) => {
onServerChange(patch);
await api.updateConfig({ server: { ...server, ...patch } });
};
const runTest = async () => {
setTesting(true);
setTestMsg('');
try {
const res = await api.testErasureSwarm({ s3_bucket: bucket, cloudfront_domain: cfDomain });
setTestMsg(res.ok ? 'S3 head bucket OK — credentials and signing ready.' : (res.error || 'Test failed'));
} catch (e) {
setTestMsg(e instanceof Error ? e.message : 'Test failed');
} finally {
setTesting(false);
}
};
const loadPolicy = async () => {
setPolicyJSON('');
try {
const res = await api.getErasureSwarmPolicyJSON(bucket);
setPolicyJSON(JSON.stringify(res, null, 2));
} catch (e) {
setPolicyJSON(e instanceof Error ? e.message : 'Failed to load policy JSON');
}
};
return (
<div className="form-group card operator-deck-card" style={{ marginTop: '1rem' }}>
<h3 className="font-tech">AWS Erasure Swarm</h3>
<p className="form-hint">
Upload RS 4+2 shards to S3 on deploy plans; agents fetch LAN CloudFront C2.
Credentials via env: <code>AF_AWS_*</code>, <code>AF_CLOUDFRONT_*</code>.
<HelpTip field="aws_erasure_swarm" />
</p>
<label className="label">S3 shard bucket</label>
<input
className="input"
value={bucket}
onChange={(e) => onServerChange({ aws_s3_shard_bucket: e.target.value })}
onBlur={() => saveFields({ aws_s3_shard_bucket: bucket })}
placeholder="my-fleet-shards"
/>
<label className="label" style={{ marginTop: '0.5rem' }}>CloudFront domain</label>
<input
className="input"
value={cfDomain}
onChange={(e) => onServerChange({ aws_cloudfront_domain: e.target.value })}
onBlur={() => saveFields({ aws_cloudfront_domain: cfDomain })}
placeholder="d123abc.cloudfront.net"
/>
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.75rem', flexWrap: 'wrap' }}>
<button type="button" className="btn btn-secondary" disabled={testing} onClick={runTest}>
{testing ? 'Testing…' : 'Test connection'}
</button>
<button type="button" className="btn btn-secondary" onClick={loadPolicy}>
Generate policy JSON
</button>
</div>
{testMsg && <p className="form-hint" style={{ marginTop: '0.5rem' }}>{testMsg}</p>}
{policyJSON && (
<pre className="form-hint" style={{ marginTop: '0.5rem', maxHeight: '12rem', overflow: 'auto', fontSize: '0.75rem' }}>
{policyJSON}
</pre>
)}
</div>
);
}