324 lines
8.8 KiB
PowerShell
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
|
|
}
|