igneum/relay/clients/send.ps1
igneum-labs 3b0cca530b Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network)
New Vercel project igneum-relay from relay/: one function (api/relay.mjs) over Neon tables relay_items and relay_machines,
files in Vercel Blob store igneum-relay (50 MB client uploads, 4 MB through the function), phone-first web page at /r/<token>/
with the site tokens. Mac CLI tools/relay.mjs (feed, read, drop, task, run, watch, inbox, machines, role, name).
Windows clients send.bat/send.ps1 and the igneum-agent (registers hostname, role, GPUs, WSL, nvcc; runs queued PowerShell
scripts, posts results, reboot-continue via scheduled task + RunOnce), bash twins send.sh and agent.sh (verified live),
playbooks for WSL setup, prover setup, prove-block, miner v4, one-click placeholder. make-clients.sh bakes the secrets
into a zip; the repo copies hold placeholders. Screenshots under docs/design/relay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 10:04:30 +00:00

111 lines
5.6 KiB
PowerShell

# Igneum relay client (Windows PowerShell 5.1 or later). Driven by send.bat; the agent and the playbooks call it too.
# send.ps1 "<text>" post a note to everyone
# send.ps1 <file> post a file (up to 50 MB, straight to Vercel Blob)
# send.ps1 result "<text>" [-TaskId N] [-File path] post a result (what a task produced)
# send.ps1 inbox print the unread tasks for this machine and mark them read
# send.ps1 peek print them without marking read
# send.ps1 get <id> print an item; download its file into the current folder
# send.ps1 done <id> mark a task done
# -To PC1 / -Title "..." / -Machine NAME override the defaults (machine = this PC's name on the relay, else its hostname)
# The URL, key and token below are written in by make-clients.sh. They are the secret; keep this file off shared drives.
param(
[Parameter(Position = 0)] [string] $A,
[Parameter(Position = 1)] [string] $B,
[string] $To = 'all',
[string] $Title = '',
[string] $Machine = '',
[long] $TaskId = 0,
[string] $File = '',
[string] $Kind = ''
)
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$RelayUrl = '__RELAY_URL__'
$RelayKey = '__RELAY_KEY__'
$RelayToken = '__RELAY_TOKEN__'
$Base = "$RelayUrl/r/$RelayToken/api"
$Headers = @{ 'x-igneum-key' = $RelayKey }
$StateDir = Join-Path $env:LOCALAPPDATA 'igneum-relay'
function Get-MachineName {
if ($Machine) { return $Machine }
if ($env:RELAY_MACHINE) { return $env:RELAY_MACHINE }
$f = Join-Path $StateDir 'machine.txt'
if (Test-Path $f) { $n = (Get-Content $f -Raw).Trim(); if ($n) { return $n } }
return $env:COMPUTERNAME
}
function Invoke-Relay([string] $Method, [string] $Fn, $Body) {
$uri = "$Base/$Fn"
if ($Method -eq 'GET') { return Invoke-RestMethod -Uri $uri -Headers $Headers -TimeoutSec 60 }
$bytes = [Text.Encoding]::UTF8.GetBytes((ConvertTo-Json $Body -Depth 8 -Compress))
return Invoke-RestMethod -Method Post -Uri $uri -Headers $Headers -ContentType 'application/json; charset=utf-8' -Body $bytes -TimeoutSec 120
}
function Send-RelayFile([string] $Path) {
$Path = (Resolve-Path $Path).Path
$name = Split-Path $Path -Leaf
$size = (Get-Item $Path).Length
$t = Invoke-Relay 'POST' 'upload' @{ name = $name; size = $size }
if ($size -gt $t.max) { throw "$name is $size bytes, over the $($t.max) byte cap" }
$h = @{ 'authorization' = "Bearer $($t.token)"; 'x-api-version' = "$($t.api_version)"; 'x-add-random-suffix' = '1'; 'x-content-type' = 'application/octet-stream' }
$r = Invoke-RestMethod -Method Put -Uri $t.put_url -Headers $h -ContentType 'application/octet-stream' -InFile $Path -TimeoutSec 600
if (-not $r.url) { throw "blob upload failed: $($r | ConvertTo-Json -Compress)" }
return @{ file_name = $name; file_url = $r.url; size = $size }
}
function Post-Item([hashtable] $o) {
$o['from'] = Get-MachineName
if (-not $o.ContainsKey('to')) { $o['to'] = $To }
if ($Title -and -not $o['title']) { $o['title'] = $Title }
if ($TaskId) { $o['task_id'] = $TaskId }
if ($File) { $f = Send-RelayFile $File; foreach ($k in $f.Keys) { $o[$k] = $f[$k] } }
$r = Invoke-Relay 'POST' 'drop' $o
Write-Host ("sent #" + $r.id + " (" + $r.kind + ") from " + $o['from'])
return $r
}
function Show-Item($it) {
Write-Host ("===== #" + $it.id + " " + $it.kind + " from " + $it.from + " to " + $it.to + " at " + $it.ts + ($(if ($it.title) { " | " + $it.title } else { "" })) + " =====")
if ($it.body) { Write-Output $it.body }
if ($it.has_file) { Write-Host ("file: " + $it.file_name + " (" + $it.size + " bytes); send.bat get " + $it.id + " downloads it") }
}
switch -Regex ($A) {
'^(?i)inbox$' {
$j = Invoke-RestMethod -Uri "$Base/inbox?machine=$([Uri]::EscapeDataString((Get-MachineName)))&ack=1" -Headers $Headers -TimeoutSec 60
if (-not $j.items) { Write-Host ("nothing waiting for " + (Get-MachineName)); break }
foreach ($it in $j.items) { Show-Item $it }
break
}
'^(?i)peek$' {
$j = Invoke-RestMethod -Uri "$Base/inbox?machine=$([Uri]::EscapeDataString((Get-MachineName)))" -Headers $Headers -TimeoutSec 60
if (-not $j.items) { Write-Host ("nothing waiting for " + (Get-MachineName)); break }
foreach ($it in $j.items) { Show-Item $it }
break
}
'^(?i)get$' {
if (-not $B) { throw 'get <id>' }
$it = (Invoke-RestMethod -Uri "$Base/item?id=$B" -Headers $Headers -TimeoutSec 60).item
Show-Item $it
if ($it.has_file) {
$out = Join-Path (Get-Location) ($it.id.ToString() + '-' + $it.file_name)
Invoke-WebRequest -Uri "$Base/file?id=$($it.id)" -Headers $Headers -OutFile $out -UseBasicParsing -TimeoutSec 600
Write-Host "downloaded: $out"
}
break
}
'^(?i)done$' { if (-not $B) { throw 'done <id>' }; Invoke-Relay 'POST' 'done' @{ id = [long]$B } | Out-Null; Write-Host "#$B done"; break }
'^(?i)result$' {
if (-not $B -and -not $File) { throw 'result "<text>" [-TaskId N] [-File path]' }
Post-Item @{ kind = 'result'; body = "$B"; title = $Title } | Out-Null
break
}
default {
if (-not $A) { throw 'send.bat "<text>" | send.bat <file> | send.bat inbox | send.bat result "<text>" | send.bat get <id> | send.bat done <id>' }
if (Test-Path -LiteralPath $A -PathType Leaf) {
$f = Send-RelayFile $A
$o = @{ kind = 'file'; body = "$B"; title = $(if ($Title) { $Title } else { $f.file_name }) }
foreach ($k in $f.Keys) { $o[$k] = $f[$k] }
Post-Item $o | Out-Null
} else {
Post-Item @{ kind = $(if ($Kind) { $Kind } else { 'text' }); body = "$A"; title = $Title } | Out-Null
}
}
}