Files
qortal-DevNet-scripts/setup-devnet-windows.ps1

492 lines
17 KiB
PowerShell

param()
$ErrorActionPreference = "Stop"
$mainUrl = "https://cloud.qortal.org/s/devnet_download/download/qortal-DevNet-MAIN.7z"
$dbUrl = "https://cloud.qortal.org/s/QortalDevNetDatabaseLatest/download/db-DevNet-LATEST.7z"
$ipScriptUrl = "https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/add-public-ip-to-settings-windows.ps1"
$defaultSettingsUrl = "https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/default-settings.json"
$startValidationUrl = "https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/start-windows.ps1"
$qortScriptUrl = "https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/qort"
$dependenciesUrl = "https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/setup-dependencies-windows.ps1"
$stopScriptUrl = "https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/stop-windows.ps1"
$publishShareToken = "PublishDevNetIPsHere"
$publishWebDavUrl = "https://cloud.qortal.org/public.php/webdav"
$homeDir = $env:USERPROFILE
$baseDir = $homeDir
$devnetDir = ""
$stateFile = ""
$downloadDir = ""
$mainArchive = ""
$dbArchive = ""
$defaultSettingsLocal = ""
$ipScriptLocal = ""
$startValidationLocal = ""
$qortScriptLocal = ""
$dependenciesLocal = ""
$stopScriptLocal = ""
$portStartDefault = 23391
$portStart = $null
$listenPort = $null
function Write-Log {
param([string]$Message)
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "[$ts] $Message"
}
function Read-HostTimeout {
param(
[string]$Prompt,
[int]$Seconds,
[string]$DefaultValue = ""
)
Write-Host -NoNewline $Prompt
if ($Host.Name -eq "ConsoleHost" -and $Host.UI.RawUI) {
$raw = $Host.UI.RawUI
$buffer = ""
$deadline = (Get-Date).AddSeconds($Seconds)
while ((Get-Date) -lt $deadline) {
if ($raw.KeyAvailable) {
$key = $raw.ReadKey("NoEcho,IncludeKeyDown")
if ($key.VirtualKeyCode -eq 13) {
Write-Host ""
return $buffer
}
if ($key.VirtualKeyCode -eq 8) {
if ($buffer.Length -gt 0) {
$buffer = $buffer.Substring(0, $buffer.Length - 1)
Write-Host -NoNewline "`b `b"
}
continue
}
$buffer += $key.Character
Write-Host -NoNewline $key.Character
} else {
Start-Sleep -Milliseconds 50
}
}
Write-Host ""
return $DefaultValue
}
if ([Console]::IsInputRedirected) {
Write-Host ""
return $DefaultValue
}
$task = [System.Threading.Tasks.Task[string]]::Run([Func[string]]{ [Console]::ReadLine() })
try {
if ($task.Wait($Seconds * 1000)) {
return $task.Result
}
} catch {
Write-Host ""
return $DefaultValue
}
Write-Host ""
return $DefaultValue
}
function Add-PathIfMissing {
param([string]$Dir)
if ([string]::IsNullOrWhiteSpace($Dir)) {
return
}
if (-not (Test-Path $Dir)) {
return
}
$pathParts = $env:Path -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
if ($pathParts -notcontains $Dir) {
$env:Path = ($pathParts + $Dir) -join ';'
}
}
function Find-7Zip {
$candidates = @()
$programFiles = $env:ProgramFiles
if (-not $programFiles) {
$programFiles = [Environment]::GetFolderPath("ProgramFiles")
}
$programFilesX86 = ${env:ProgramFiles(x86)}
if (-not $programFilesX86) {
$programFilesX86 = [Environment]::GetFolderPath("ProgramFilesX86")
}
foreach ($base in @($programFiles, $programFilesX86)) {
if ($base) {
$candidates += (Join-Path -Path $base -ChildPath "7-Zip\7z.exe")
}
}
foreach ($candidate in $candidates) {
if ($candidate -and (Test-Path $candidate)) {
return $candidate
}
}
return $null
}
function Find-JavaBin {
$roots = @()
$programFiles = $env:ProgramFiles
if (-not $programFiles) {
$programFiles = [Environment]::GetFolderPath("ProgramFiles")
}
$programFilesX86 = ${env:ProgramFiles(x86)}
if (-not $programFilesX86) {
$programFilesX86 = [Environment]::GetFolderPath("ProgramFilesX86")
}
foreach ($base in @($programFiles, $programFilesX86)) {
if ($base) {
$roots += (Join-Path -Path $base -ChildPath "Eclipse Adoptium")
$roots += (Join-Path -Path $base -ChildPath "Java")
}
}
$roots = $roots | Where-Object { $_ -and (Test-Path $_) }
foreach ($root in $roots) {
$found = Get-ChildItem -Path $root -Recurse -Filter "java.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\bin\\java\.exe$' } |
Select-Object -ExpandProperty FullName -First 1
if ($found) {
return $found
}
}
return $null
}
function Ensure-Dependencies {
$missing = @()
if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) {
$missing += "7z"
}
if (-not (Get-Command java -ErrorAction SilentlyContinue)) {
$missing += "java"
}
if ($missing.Count -eq 0) {
return
}
Write-Log "Missing dependencies detected: $($missing -join ', ')"
New-Item -ItemType Directory -Force -Path $downloadDir | Out-Null
$localFallback = Join-Path $PSScriptRoot "setup-dependencies-windows.ps1"
if (Test-Path $localFallback) {
$depScript = $localFallback
} else {
Invoke-WebRequest -Uri $dependenciesUrl -OutFile $dependenciesLocal
$depScript = $dependenciesLocal
}
& powershell -ExecutionPolicy Bypass -File $depScript
$sevenZipExe = Find-7Zip
if ($sevenZipExe) {
Add-PathIfMissing -Dir (Split-Path -Parent $sevenZipExe)
}
$javaExe = Find-JavaBin
if ($javaExe) {
Add-PathIfMissing -Dir (Split-Path -Parent $javaExe)
}
if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) {
throw "Dependencies are still missing after setup. Please install manually."
}
if (-not (Get-Command java -ErrorAction SilentlyContinue)) {
throw "Java is still missing after setup. Please install Java 11+ manually."
}
}
$useDefaultBase = Read-HostTimeout "Use default base path $homeDir? [Y/n] (auto-continue in 20s): " 20 ""
switch -Regex ($useDefaultBase) {
'^(n|no)$' {
$baseDirInput = Read-HostTimeout "Enter the base path to install into (auto-continue in 20s): " 20 ""
if ([string]::IsNullOrWhiteSpace($baseDirInput)) {
throw "Base path cannot be empty."
}
$baseDir = $baseDirInput
}
default {
$baseDir = $homeDir
}
}
$devnetDir = Join-Path $baseDir "qortal-DevNet"
$stateFile = Join-Path $baseDir "qortal-DevNet.setup.state"
$downloadDir = Join-Path $baseDir "qortal-DevNet-downloads"
$mainArchive = Join-Path $downloadDir "qortal-DevNet-MAIN.7z"
$dbArchive = Join-Path $downloadDir "db-DevNet-LATEST.7z"
$defaultSettingsLocal = Join-Path $downloadDir "default-settings.json"
$ipScriptLocal = Join-Path $downloadDir "add-public-ip-to-settings-windows.ps1"
$startValidationLocal = Join-Path $downloadDir "start-windows.ps1"
$qortScriptLocal = Join-Path $downloadDir "qort"
$dependenciesLocal = Join-Path $downloadDir "setup-dependencies-windows.ps1"
$stopScriptLocal = Join-Path $downloadDir "stop-windows.ps1"
Ensure-Dependencies
$useDefaultPorts = Read-HostTimeout "Use default port range $portStartDefault-$($portStartDefault + 3)? [Y/n] (auto-continue in 20s): " 20 ""
switch -Regex ($useDefaultPorts) {
'^(n|no)$' {
$portStartInput = Read-HostTimeout "Enter the starting port for the range (auto-continue in 20s): " 20 ""
if ([string]::IsNullOrWhiteSpace($portStartInput)) {
throw "Port start cannot be empty."
}
if (-not ($portStartInput -match '^\d+$')) {
throw "Port start must be a number."
}
$portStart = [int]$portStartInput
}
default {
$portStart = $portStartDefault
}
}
if ($portStart -lt 1024 -or $portStart -gt 65532) {
throw "Port start must be between 1024 and 65532."
}
$listenPort = $portStart + 1
function Step-Done {
param([string]$Name)
if (-not (Test-Path $stateFile)) { return $false }
return (Select-String -Path $stateFile -SimpleMatch $Name -Quiet)
}
function Mark-Done {
param([string]$Name)
Add-Content -Path $stateFile -Value $Name
}
function Ensure-File {
param([string]$Url, [string]$Dest)
if (Test-Path $Dest) {
$item = Get-Item $Dest
if ($item.Length -gt 0) {
Write-Log "Using existing $([System.IO.Path]::GetFileName($Dest))"
return
}
}
Invoke-WebRequest -Uri $Url -OutFile $Dest
}
function Unblock-IfPresent {
param([string]$Path)
if (Test-Path $Path) {
Unblock-File -Path $Path -ErrorAction SilentlyContinue
}
}
function Port-In-Use {
param([int]$Port)
if (Get-Command Get-NetTCPConnection -ErrorAction SilentlyContinue) {
return (Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue) -ne $null
}
$netstat = netstat -ano 2>$null
if ($LASTEXITCODE -ne 0 -or -not $netstat) {
return $null
}
return $netstat -match "[:.]$Port\s+LISTENING"
}
$portRangeInUse = $false
$portCheckUnknown = $false
foreach ($p in @($portStart, $portStart + 1, $portStart + 2, $portStart + 3)) {
$inUse = Port-In-Use -Port $p
if ($inUse -eq $true) {
$portRangeInUse = $true
break
}
if ($inUse -eq $null) {
$portCheckUnknown = $true
}
}
if ($portRangeInUse) {
Write-Log "Port range $portStart-$($portStart + 3) appears in use; shifting by 10000"
$portStart = $portStart + 10000
if ($portStart -gt 65532) {
throw "Port range exceeds 65535 after shifting."
}
$listenPort = $portStart + 1
} elseif ($portCheckUnknown) {
Write-Log "Warning: unable to detect if ports are in use; continuing with selected range."
}
function Get-PublicIp {
$urls = @(
"https://api.ipify.org",
"https://ipv4.icanhazip.com",
"https://checkip.amazonaws.com",
"https://ifconfig.me",
"https://canhazip.com"
)
foreach ($url in $urls) {
try {
$ip = (Invoke-WebRequest -Uri $url -TimeoutSec 8 -Headers @{ "User-Agent" = "Mozilla/5.0" }).Content.Trim()
if ($ip -match '^\d{1,3}(\.\d{1,3}){3}$') {
return $ip
}
} catch {
continue
}
}
return $null
}
function Publish-PublicIp {
param([string]$Ip, [int]$Port, [string]$HostName, [string]$TempFile)
$content = "$Ip`:$Port"
Set-Content -Path $TempFile -Value $content -Encoding ASCII
$remoteName = "$HostName.txt"
$url = "$publishWebDavUrl/$remoteName"
$sec = New-Object System.Security.SecureString
$cred = New-Object System.Management.Automation.PSCredential($publishShareToken, $sec)
try {
Invoke-WebRequest -Uri $url -Method Put -InFile $TempFile -Credential $cred | Out-Null
return $true
} catch {
$altName = "$HostName-$((Get-Date).ToUniversalTime().ToString('yyyyMMdd_HHmmss')).txt"
$altUrl = "$publishWebDavUrl/$altName"
try {
Invoke-WebRequest -Uri $altUrl -Method Put -InFile $TempFile -Credential $cred | Out-Null
return $true
} catch {
return $false
}
}
}
if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) {
throw "7z not found. Please install 7-Zip and ensure it is in PATH."
}
Write-Log "Step 1/7: Download files..."
New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null
Ensure-File -Url $mainUrl -Dest $mainArchive
Ensure-File -Url $dbUrl -Dest $dbArchive
Ensure-File -Url $defaultSettingsUrl -Dest $defaultSettingsLocal
Ensure-File -Url $ipScriptUrl -Dest $ipScriptLocal
Ensure-File -Url $startValidationUrl -Dest $startValidationLocal
Ensure-File -Url $qortScriptUrl -Dest $qortScriptLocal
Ensure-File -Url $stopScriptUrl -Dest $stopScriptLocal
Unblock-IfPresent -Path $ipScriptLocal
Unblock-IfPresent -Path $startValidationLocal
Unblock-IfPresent -Path $stopScriptLocal
if (-not (Step-Done "01-download-files")) {
Mark-Done "01-download-files"
}
if (-not (Step-Done "02-prepare-devnet-dir")) {
Write-Log "Step 2/7: Prepare devnet directory..."
if (Test-Path $devnetDir) {
$backupDir = Join-Path $homeDir "backup-qortal-DevNet"
if (Test-Path $backupDir) {
$backupDir = "$backupDir-$(Get-Date -Format yyyyMMdd_HHmmss)"
}
Write-Log "Existing $devnetDir found; moving to $backupDir"
Move-Item -Path $devnetDir -Destination $backupDir
}
Write-Log "Creating $devnetDir"
New-Item -ItemType Directory -Path $devnetDir -Force | Out-Null
Mark-Done "02-prepare-devnet-dir"
}
if (-not (Step-Done "03-extract-main")) {
Write-Log "Step 3/7: Extract devnet main archive..."
& 7z x $mainArchive "-o$devnetDir" | Out-Null
$tempDir = Join-Path $devnetDir "temp"
if (Test-Path $tempDir) {
Write-Log "Normalizing devnet main files from temp/ into $devnetDir"
Copy-Item -Path (Join-Path $tempDir "*") -Destination $devnetDir -Recurse -Force
Remove-Item -Path $tempDir -Recurse -Force
}
Mark-Done "03-extract-main"
}
if (-not (Step-Done "04-extract-db")) {
Write-Log "Step 4/7: Extract devnet DB archive..."
$dbPath = Join-Path $devnetDir "db"
if (Test-Path $dbPath) {
$dbBackup = "$dbPath.bak-$(Get-Date -Format yyyyMMdd_HHmmss)"
Write-Log "Existing db folder found; moving to $dbBackup"
Move-Item -Path $dbPath -Destination $dbBackup
}
& 7z x $dbArchive "-o$devnetDir" | Out-Null
if (-not (Test-Path $dbPath)) {
throw "Expected db/ folder in devnet DB archive, but it was not found."
}
Mark-Done "04-extract-db"
}
if (-not (Step-Done "05-configure-settings")) {
Write-Log "Step 5/7: Configure settings..."
$settingsPath = Join-Path $devnetDir "settings.json"
if (Test-Path $settingsPath) {
$settingsBackup = Join-Path $devnetDir "backup-settings.json"
if (Test-Path $settingsBackup) {
$settingsBackup = "$settingsBackup-$(Get-Date -Format yyyyMMdd_HHmmss)"
}
Write-Log "Existing settings.json found; moving to $settingsBackup"
Move-Item -Path $settingsPath -Destination $settingsBackup
}
Copy-Item -Path $defaultSettingsLocal -Destination $settingsPath -Force
$ipScriptPath = Join-Path $devnetDir "add-public-ip-to-settings.ps1"
Copy-Item -Path $ipScriptLocal -Destination $ipScriptPath -Force
Unblock-IfPresent -Path $ipScriptPath
$env:QORTAL_PORT_START = $portStart.ToString()
Write-Log "Running public IP script..."
& powershell -ExecutionPolicy Bypass -File $ipScriptPath
Mark-Done "05-configure-settings"
}
if (-not (Step-Done "06-publish-public-ip")) {
Write-Log "Step 6/7: Publish public IP..."
$hostName = $env:COMPUTERNAME
$publishTmp = Join-Path $downloadDir "devnet-public-ip.txt"
$publicIp = Get-PublicIp
if ($publicIp) {
if (Publish-PublicIp -Ip $publicIp -Port $listenPort -HostName $hostName -TempFile $publishTmp) {
Write-Log "Published $publicIp`:$listenPort for $hostName"
} else {
Write-Log "Warning: failed to publish public IP to Nextcloud share."
}
} else {
Write-Log "Warning: could not determine public IP to publish."
}
Mark-Done "06-publish-public-ip"
}
if (-not (Step-Done "07-install-start-script")) {
Write-Log "Step 7/7: Install start/stop scripts..."
$startValidationPath = Join-Path $devnetDir "start-windows.ps1"
Copy-Item -Path $startValidationLocal -Destination $startValidationPath -Force
$startPath = Join-Path $devnetDir "start.ps1"
if (Test-Path $startPath) {
$startBackup = "$startPath.bak-$(Get-Date -Format yyyyMMdd_HHmmss)"
Move-Item -Path $startPath -Destination $startBackup
Write-Log "Backed up existing start.ps1 to $startBackup"
}
Copy-Item -Path $startValidationPath -Destination $startPath -Force
Unblock-IfPresent -Path $startPath
Unblock-IfPresent -Path $startValidationPath
$stopPath = Join-Path $devnetDir "stop.ps1"
if (Test-Path $stopPath) {
$stopBackup = "$stopPath.bak-$(Get-Date -Format yyyyMMdd_HHmmss)"
Move-Item -Path $stopPath -Destination $stopBackup
Write-Log "Backed up existing stop.ps1 to $stopBackup"
}
Copy-Item -Path $stopScriptLocal -Destination $stopPath -Force
$stopScriptPath = Join-Path $devnetDir "stop-windows.ps1"
Copy-Item -Path $stopScriptLocal -Destination $stopScriptPath -Force
Unblock-IfPresent -Path $stopPath
Unblock-IfPresent -Path $stopScriptPath
$qortPath = Join-Path $devnetDir "qort"
Copy-Item -Path $qortScriptLocal -Destination $qortPath -Force
Mark-Done "07-install-start-script"
}
Write-Log "Done."