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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user