Files
minerops/agents/windows/Enroll-MinerOpsAgent.ps1
T

324 lines
8.8 KiB
PowerShell

#Requires -RunAsAdministrator
[CmdletBinding()]
param()
$ErrorActionPreference = "Stop"
$Target = "C:\MinerOpsAgent"
$ConfigPath = Join-Path $Target "agent-config.json"
$PendingConfigPath = Join-Path $Target "agent-config.json.pending"
$RecoveryConfigPath = Join-Path $Target "agent-config.recovery.json"
$EnrollmentPath = Join-Path $PSScriptRoot "enrollment.json"
$Installer = Join-Path $PSScriptRoot "install.ps1"
$RequiredFiles = @(
"install.ps1",
"MinerOpsAgentService.ps1",
"MinerOpsAgentTray.ps1",
"MinerOpsAgentGUI.ps1",
"MinerOpsApi3EnrichmentPoller.ps1",
"MinerOpsPoolPoller.ps1",
"MinerOpsPoolPollerTask.ps1",
"EnrichOneMiner.ps1",
"MinerOpsControl.ps1",
"VERSION",
"enrollment.json"
)
function Protect-MinerOpsCredentialFile {
param(
[Parameter(Mandatory = $true)]
[string] $Path
)
$acl = New-Object System.Security.AccessControl.FileSecurity
$acl.SetAccessRuleProtection($true, $false)
$identities = @(
(New-Object System.Security.Principal.SecurityIdentifier("S-1-5-18")),
(New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")),
[System.Security.Principal.WindowsIdentity]::GetCurrent().User
)
foreach ($identity in $identities) {
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$identity,
[System.Security.AccessControl.FileSystemRights]::FullControl,
[System.Security.AccessControl.InheritanceFlags]::None,
[System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow
)
$acl.AddAccessRule($rule)
}
Set-Acl -LiteralPath $Path -AclObject $acl
}
function ConvertFrom-MinerOpsSecureString {
param(
[Parameter(Mandatory = $true)]
[Security.SecureString] $SecureValue
)
$pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR(
$SecureValue
)
try {
return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer)
}
finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer)
}
}
# Validate the complete package before modifying the Agent host or consuming
# the single-use enrollment token.
foreach ($name in $RequiredFiles) {
$source = Join-Path $PSScriptRoot $name
if (!(Test-Path -LiteralPath $source -PathType Leaf)) {
throw "Enrollment package is missing $name"
}
}
if (Test-Path -LiteralPath $ConfigPath) {
throw @"
C:\MinerOpsAgent\agent-config.json already exists.
This enrollment bootstrapper is for a new Agent identity and will not overwrite
an existing identity. Use the normal MinerOps upgrade procedure instead.
"@
}
foreach ($recoveryPath in @($PendingConfigPath, $RecoveryConfigPath)) {
if (Test-Path -LiteralPath $recoveryPath) {
throw @"
A previous enrollment recovery file exists at:
$recoveryPath
Do not create or redeem another enrollment until that recovery state has been
reviewed.
"@
}
}
$enrollment = Get-Content -LiteralPath $EnrollmentPath -Raw |
ConvertFrom-Json
$HubUrl = ([string] $enrollment.hub_url).TrimEnd("/")
$EnrollmentToken = ([string] $enrollment.token).Trim()
$parsedHub = $null
if (
-not [Uri]::TryCreate(
$HubUrl,
[UriKind]::Absolute,
[ref] $parsedHub
) -or
$parsedHub.Scheme -notin @("http", "https")
) {
throw "enrollment.json contains an invalid MinerOps Hub URL."
}
if (
$EnrollmentToken.Length -lt 32 -or
$EnrollmentToken.Length -gt 256
) {
throw "enrollment.json contains an invalid enrollment token."
}
Write-Host ""
Write-Host "MinerOps Windows Agent enrollment"
Write-Host "Hub: $HubUrl"
Write-Host ""
Write-Host "API3 credentials stay only on this Windows Agent."
Write-Host "Leave the account blank if API3 features are not being configured yet."
Write-Host ""
$Api3Account = (Read-Host "WhatsMiner API3 account").Trim()
$Api3PasswordPlain = ""
if ($Api3Account) {
$Api3PasswordSecure = Read-Host `
"WhatsMiner API3 password" `
-AsSecureString
$Api3PasswordPlain = ConvertFrom-MinerOpsSecureString `
-SecureValue $Api3PasswordSecure
}
$Redeemed = $false
$ConfigPersisted = $false
try {
New-Item -ItemType Directory -Force -Path $Target | Out-Null
# Create protected empty files BEFORE token redemption. If persistence is
# not possible, fail before consuming the single-use token.
[IO.File]::WriteAllText($PendingConfigPath, "")
Protect-MinerOpsCredentialFile -Path $PendingConfigPath
[IO.File]::WriteAllText($RecoveryConfigPath, "")
Protect-MinerOpsCredentialFile -Path $RecoveryConfigPath
$requestBody = @{
token = $EnrollmentToken
} | ConvertTo-Json -Compress
Write-Host ""
Write-Host "Redeeming one-time MinerOps enrollment..."
$response = Invoke-RestMethod `
-Method Post `
-Uri "$HubUrl/api/agent/enroll" `
-ContentType "application/json" `
-Body $requestBody `
-TimeoutSec 30
$Redeemed = $true
if (-not $response.data) {
throw "MinerOps enrollment returned no Agent data."
}
$agent = $response.data
foreach ($required in @(
"agent_id",
"agent_secret",
"site_id",
"site_name",
"agent_zone_id",
"agent_zone_name"
)) {
if (-not ([string] $agent.$required).Trim()) {
throw "MinerOps enrollment response is missing $required."
}
}
$ranges = @()
foreach ($cidr in @($agent.ip_ranges)) {
$network = ([string] $cidr).Trim()
if (-not $network) {
continue
}
$ranges += [pscustomobject]@{
name = "$($agent.agent_zone_name) - $network"
cidr = $network
enabled = $true
api_port = 4028
api3_port = 4433
}
}
$config = [ordered]@{
hub_url = $HubUrl
agent_id = [string] $agent.agent_id
site_id = [string] $agent.site_id
agent_zone_id = [string] $agent.agent_zone_id
site_name = [string] $agent.site_name
agent_secret = [string] $agent.agent_secret
poll_interval_seconds = 120
ip_ranges = @($ranges)
api3_account = $Api3Account
api3_password = $Api3PasswordPlain
api3_port = 4433
}
$configJson = $config | ConvertTo-Json -Depth 20
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
# Write a protected recovery copy first. If anything after redemption
# fails, the permanent Agent credential is not lost with the consumed token.
[IO.File]::WriteAllText(
$RecoveryConfigPath,
$configJson + [Environment]::NewLine,
$utf8NoBom
)
[IO.File]::WriteAllText(
$PendingConfigPath,
$configJson + [Environment]::NewLine,
$utf8NoBom
)
Move-Item `
-LiteralPath $PendingConfigPath `
-Destination $ConfigPath
$ConfigPersisted = $true
Remove-Item -LiteralPath $RecoveryConfigPath -Force
# The extracted bootstrap token is no longer needed once permanent
# credentials have been persisted locally.
Remove-Item -LiteralPath $EnrollmentPath -Force
$EnrollmentToken = $null
$requestBody = $null
$Api3PasswordPlain = $null
Write-Host "Permanent Agent identity saved locally."
Write-Host "Installing MinerOps Agent runtime..."
& $Installer
$mainTask = Get-ScheduledTask `
-TaskName "MinerOpsAgent" `
-ErrorAction Stop
if ([string] $mainTask.State -ne "Running") {
Start-ScheduledTask -TaskName "MinerOpsAgent" -ErrorAction Stop
}
Write-Host ""
Write-Host "MinerOps Agent enrollment completed."
Write-Host "Agent ID: $($agent.agent_id)"
Write-Host "Site: $($agent.site_name)"
Write-Host "Agent Zone: $($agent.agent_zone_name)"
Write-Host ""
Write-Host "Delete the downloaded ZIP and extracted setup folder after verification."
}
catch {
if (-not $Redeemed) {
foreach ($temporaryPath in @(
$PendingConfigPath,
$RecoveryConfigPath
)) {
if (Test-Path -LiteralPath $temporaryPath) {
Remove-Item `
-LiteralPath $temporaryPath `
-Force `
-ErrorAction SilentlyContinue
}
}
}
elseif ($ConfigPersisted) {
Write-Warning @"
The enrollment token was consumed and the permanent Agent identity WAS saved
to C:\MinerOpsAgent\agent-config.json.
Do not create a replacement enrollment. Correct the installation problem and
rerun install.ps1 from this package.
"@
}
else {
Write-Warning @"
The enrollment token was consumed.
A protected credential recovery file may exist at:
C:\MinerOpsAgent\agent-config.recovery.json
Do not create or redeem another enrollment until that recovery file has been
reviewed.
"@
}
throw
}