492 lines
17 KiB
PowerShell
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."
|