diff --git a/automatic/nexus-repository-migrator/Readme.md b/automatic/nexus-repository-migrator/Readme.md new file mode 100644 index 00000000000..a689bc7d6f7 --- /dev/null +++ b/automatic/nexus-repository-migrator/Readme.md @@ -0,0 +1,52 @@ +# Nexus Repository Migrator + +## Features + +In release 3.71.0, Sonatype Nexus Repository began using H2 as its default embedded database. +OrientDB is now in Extended Maintenance as defined in our [Sunsetting documentation](https://help.sonatype.com/en/sonatype-sunsetting-information.html). +Those wishing to upgrade to version 3.71.0 or above will need to migrate to either an H2 or PostgreSQL database. +See our detailed [help topic on Upgrading to 3.71.0 and beyond](https://help.sonatype.com/en/upgrading-to-nexus-repository-3-71-0-and-beyond.html). + +This package contains the migrator tool to migrate a Sonatype Nexus Repository 3.70.1 database from OrientDb to H2 (available to all) or PostgreSQL (available to Pro license holders). + +You can either install the package and run the migrator by following the instructions in Notes, or run the script `ConvertFrom-NexusOrientDb` in the tools directory. + +If you have upgraded to the `nexus-repository` package version 3.71.0.6 and your installation is failing to run, you can run the `Repair-Nexus371FailedUpgrade` script in the tools directory. + +## Package Parameters + +This package supports the following parameters: + +* `/Migrate` - Triggers migration of your existing Sonatype Nexus install(s) from OrientDb to a H2 database. +* `/FQDN` - The fully-qualified domain name that matches the subject you are using for your Nexus instance SSL certificate. + +You can pass parameters as follows: + +`choco install nexus-repository-migrator --parameters="/Migrate /FQDN=nexus.example.com"` + +## Notes + +**Please note that using the scripts contained within is entirely at the user's own risk. Read them before you run them!** + +For more details, including how to migrate to PostgreSQL, see the [Migrating To A New Database article](https://help.sonatype.com/en/migrating-to-a-new-database.html). + +### Migration Environment Prerequisite Requirements + +The checklist below covers requirements for a successful database migration: + +- If you are migrating from OrientDB to H2 or PostgreSQL, your OrientDB-based instance must be on the latest 3.70.x version. +- You must have JRE 8 or JRE 11 available. The Chocolatey `nexus-repository` 3.70.1.6 package contains this already. +- You must have at least 16GB RAM available. +- You must have a database backup in a clean working location. +- The migrator utility requires three times the disk space (minimum 10 GB) as your $data-dir/db directory. + +### Migrating From OrientDB to H2 + +1. Perform a full backup using normal backup procedures. +1. Copy the backup to a clean working location on a different filesystem so that any extraction doesn’t impact the existing production system. +1. Shut down Nexus Repository. +1. Run the following command from the clean working location containing your database backup. You may include any of the optional parameters listed in the section below when running this command. + `java -Xmx16G -Xms16G -XX:+UseG1GC -XX:MaxDirectMemorySize=28672M -jar nexus-db-migrator-*.jar --migration_type=h2` +1. Copy the resultant nexus.mv.db file to your $data-dir/db directory. +1. Edit the $data-dir/etc/nexus.properties file and add the following line: `nexus.datastore.enabled=true` +1. Start Nexus Repository \ No newline at end of file diff --git a/automatic/nexus-repository-migrator/nexus-repository-migrator.nuspec b/automatic/nexus-repository-migrator/nexus-repository-migrator.nuspec new file mode 100644 index 00000000000..50aa57465e1 --- /dev/null +++ b/automatic/nexus-repository-migrator/nexus-repository-migrator.nuspec @@ -0,0 +1,28 @@ + + + + nexus-repository-migrator + Nexus Repository Migrator + 3.70.2.1 + Sonatype + chocolatey-community, jpruskin + Migrator for the Sonatype Nexus Repository database. + + https://help.sonatype.com/en/orientdb-downloads.html + https://github.com/sonatype/nexus-public + https://help.sonatype.com/en/migrating-to-a-new-database.html + nexus-repository-migrator utility freeware cross-platform sonatype nexus-repository + Sonatype + http://www.eclipse.org/legal/epl-v10.html + false + https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@f459e946d7b1926ee89c2b415ec8507dffe99218/icons/nexus-repository.png + https://help.sonatype.com/repomanager3/release-notes + + + + + + + + + diff --git a/automatic/nexus-repository-migrator/tools/ConvertFrom-NexusOrientDb.ps1 b/automatic/nexus-repository-migrator/tools/ConvertFrom-NexusOrientDb.ps1 new file mode 100644 index 00000000000..e4aa5eb2822 --- /dev/null +++ b/automatic/nexus-repository-migrator/tools/ConvertFrom-NexusOrientDb.ps1 @@ -0,0 +1,114 @@ +<# + .Synopsis + Migrates Sonatype Nexus Repository 3.70.1-02 to the H2 database type + + .Example + .\ConvertFrom-NexusOrientDb.ps1 + + .Example + .\ConvertFrom-NexusOrientDb.ps1 -JavaPath .\path\to\java.exe +#> +[CmdletBinding(SupportsShouldProcess)] +param( + # A path to a Java.exe file, major version 8 or 11. + [string]$JavaPath, + + # The hostname of the machine used by Nexus, if using an SSL certificate. + [string]$Hostname, + + # Forces migration even if the test doesn't think it is required. + [switch]$Force +) + +Import-Module $PSScriptRoot\nexus-helpers.ps1 -Verbose:$false + +foreach ($Service in Get-NexusRepositoryServiceInstall) { + if ($Force -or (Test-NexusMigratorRequired -DataDir (Split-Path $Service.DataFolder) -ProgramDir $Service.ProgramFolder)) { + Write-Host "Preparing for database migration..." + $MigrationFiles = Join-Path $env:Temp "dbmigration-$($Service.ServiceName)" + $MigratorDownload = @{FileFullPath = Join-Path $PSScriptRoot "nexus-db-migrator.jar"} + $PreMigrationIssues = @() + + if (Test-NexusMigratorFreeSpaceProblem -Drive $MigrationFiles.Split(':')[0] -DatabaseFolder (Join-Path $Service.DataFolder "db")) { + $PreMigrationIssues += "Cannot migrate database with available disk space" + } + + if (Test-NexusMigratorMemoryProblem) { + $PreMigrationIssues += "Cannot migrate database with available memory" + } + + # Test if contained JRE is compatible, otherwise check JAVA_HOME, otherwise... + $JavaPath = @( + $JavaPath + Join-Path $Service.ProgramFolder "\jre\bin\java.exe" + Convert-Path $env:JAVA_HOME\bin\java.exe -ErrorAction SilentlyContinue + Convert-Path $env:ProgramFiles\*\jre-*\bin\java.exe -ErrorAction SilentlyContinue + ) | Where-Object { + $_ -and + (Test-Path $_) -and + (([version](Get-ItemProperty -Path $_).VersionInfo.ProductVersion).Major -in 8, 11) + } | Select-Object -First 1 + + if (-not $JavaPath) { + $PreMigrationIssues += "Nexus Migrator requires JRE 8 or JRE 11. Please provide a -JavaPath to a compatible version." + } + + if ($PreMigrationIssues) { + Write-Error -Message ($PreMigrationIssues -join "`n") -ErrorAction Stop + } + + $ServiceState = (Get-Service $Service.ServiceName).Status + + New-NexusOrientDbBackup -DataDir (Split-Path $Service.DataFolder) -ServiceName $Service.ServiceName -DestinationPath $MigrationFiles + + try { + Push-Location $MigrationFiles + Write-Host "Migrating database from 'OrientDb' to 'H2'" + # We could adjust the memory values to work on more systems, + # but these are the recommended arguments from Sonatype. + $JavaArgs = @( + "-Xmx16G" + "-Xms16G" + "-XX:+UseG1GC" + "-XX:MaxDirectMemorySize=28672M" + "-jar" + $MigratorDownload.FileFullPath + "--migration_type=h2" + "--yes" + ) + & $JavaPath @JavaArgs + + if ($LASTEXITCODE -ne 0) { + throw "Migration of the database failed." + } + + if ($PSCmdlet.ShouldProcess("$MigrationFiles\nexus.mv.db", "Moving migrated database")) { + Copy-Item -Path $MigrationFiles\nexus.mv.db -Destination (Join-Path $Service.DataFolder "db") + } + $NexusConfigFile = Join-Path $Service.DataFolder "etc\nexus.properties" + if ((Get-NexusConfiguration -Path $NexusConfigFile).'nexus.datastore.enabled' -ne 'true' -and $PSCmdlet.ShouldProcess($NexusConfigFile, 'Updating Datastore Configuration')) { + (Get-Content $NexusConfigFile) | Where-Object {$_ -notmatch '^\W*nexus\.datastore\.enabled='} | Set-Content $NexusConfigFile + Add-Content -Path $NexusConfigFile -Value "nexus.datastore.enabled=true" + } + + if ($ServiceState -eq 'Running') { + try { + Write-Host "Restarting '$($Service.ServiceName)'" + Restart-Service $Service.ServiceName + # The post-migration launch can take significantly longer than normal + Wait-NexusAvailability -Hostname $Hostname -Config $NexusConfigFile -Timeout 15 -ErrorAction Stop + } catch { + Write-Error -Message (@( + "The Nexus service '$($Service.ServiceName)' was restarted, but did not recover." + "Check the logs at '$(Join-Path $Service.DataFolder "log")'" + ) -join "`n") + } + } + } finally { + Pop-Location + Remove-Item $MigrationFiles -Recurse + } + } else { + Write-Warning "'$($Service.DataFolder)' was not migrated" + } +} \ No newline at end of file diff --git a/automatic/nexus-repository-migrator/tools/Repair-Nexus371FailedUpgrade.ps1 b/automatic/nexus-repository-migrator/tools/Repair-Nexus371FailedUpgrade.ps1 new file mode 100644 index 00000000000..1e291227b89 --- /dev/null +++ b/automatic/nexus-repository-migrator/tools/Repair-Nexus371FailedUpgrade.ps1 @@ -0,0 +1,91 @@ +<# + .Synopsis + Repairs a broken upgrade of Sonatype Nexus Repository OSS + + .Example + .\Repair-Nexus371FailedUpgrade.ps1 + # Repairs the Nexus install, ending with a migrated database and version 3.71.0.6 + + .Example + .\Repair-Nexus371FailedUpgrade.ps1 -Rollback + # Repairs the Nexus install, ending with an unmodified database and version 3.70.1.2 +#> +[CmdletBinding(DefaultParameterSetName="Upgrade")] +param( + # If selected, rolls the install back to 3.70.x instead of upgrading. + [Parameter(ParameterSetName="Repair")] + [Alias("Repair")] + [switch]$Rollback, + + # Temporary Path for extracting files + [string]$ExtractPath = (Join-Path $env:Temp "nexus") +) +$ProgressPreference = "SilentlyContinue" +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot\nexus-helpers.ps1 -Verbose:$false + +# Test if Nexus JRE matches +$JavaPath = @( + Join-Path $env:ProgramData "\nexus\jre\bin\java.exe" + Convert-Path $env:JAVA_HOME\bin\java.exe + Convert-Path $env:ProgramFiles\*\jre-*\bin\java.exe +) | Where-Object { + $_ -and + (Test-Path $_) -and + (([version](Get-ItemProperty -Path $_).VersionInfo.ProductVersion).Major -in 8, 11) +} | Select-Object -First 1 + +# Download Java if it's missing, via the old version of Nexus Repository +if (-not $JavaPath -and -not ($JavaPath = Convert-Path "$ExtractPath\nexus-3.70.1-02\jre\bin\java.exe" -ErrorAction SilentlyContinue)) { + Invoke-WebRequest -Uri https://sonatype-download.global.ssl.fastly.net/repository/downloads-prod-group/3/nexus-3.70.1-02-win64.zip -OutFile "$env:Temp\nexus-3.70.1-02.zip" + Expand-Archive -Path "$env:Temp\nexus-3.70.1-02.zip" -DestinationPath $ExtractPath + $JavaPath = Convert-Path "$ExtractPath\nexus-3.70.1-02\jre\bin\java.exe" + Remove-Item "$env:Temp\nexus-3.70.1-02.zip" +} + +$BackupLocation = "$PSScriptRoot\$(New-Guid)" +Backup-NexusSSL -BackupLocation $BackupLocation + +# Get the "installed" and package version of Nexus +$NexusVersion = Get-NexusVersion +$null = [System.Reflection.Assembly]::LoadFrom("$env:ChocolateyInstall\choco.exe") +$LatestPackageVersion = [Chocolatey.NugetVersionExtensions]::ToNormalizedStringChecked( + "$($NexusVersion -replace '-','.')" +) + +if ((Get-Service nexus).Status -ne 'running') { + if ($PSCmdlet.ParameterSetName -eq 'Upgrade') { + & $PSScriptRoot\ConvertFrom-NexusOrientDb.ps1 -JavaPath $JavaPath -Force + } + + # The combined installation of old and new Nexus Java versions and supports + # causes the install to become un-startable. We'll replace it. + Remove-Item $env:ProgramData\nexus -Recurse + + switch ($PSCmdlet.ParameterSetName) { + "Repair" { + Copy-Item -Path "$ExtractPath\nexus-3.70.1-02\" -Destination "$env:ProgramData\nexus" -Recurse + } + "Upgrade" { + # Download the matching version of Nexus + Invoke-WebRequest -Uri https://sonatype-download.global.ssl.fastly.net/repository/downloads-prod-group/3/nexus-$($NexusVersion)-win64.zip -OutFile "$env:Temp\nexus-$($NexusVersion)-win64.zip" + Expand-Archive -Path "$env:Temp\nexus-$($NexusVersion)-win64.zip" -DestinationPath $ExtractPath -Force + Copy-Item -Path "$ExtractPath\nexus-$NexusVersion\" -Destination "$env:ProgramData\nexus" -Recurse + Remove-Item "$env:Temp\nexus-$($NexusVersion)-win64.zip" + + # Ensure the package is in a good state, from CCR as we know the local repository isn't running. + choco upgrade nexus-repository --version $LatestPackageVersion --confirm --no-progress --source https://community.chocolatey.org/api/v2/ --skip-powershell + } + } +} else { + Write-Warning "The service is already running. You don't need to repair." +} + +try { + Restore-NexusSSL -BackupLocation $BackupLocation +} finally { + Remove-Item $BackupLocation -Recurse +} + +Restart-Service nexus \ No newline at end of file diff --git a/automatic/nexus-repository-migrator/tools/chocolateyInstall.ps1 b/automatic/nexus-repository-migrator/tools/chocolateyInstall.ps1 new file mode 100644 index 00000000000..4e643da1ea4 --- /dev/null +++ b/automatic/nexus-repository-migrator/tools/chocolateyInstall.ps1 @@ -0,0 +1,27 @@ +$ErrorActionPreference = 'Stop' +$toolsDir = Split-Path $MyInvocation.MyCommand.Definition +$PackageParameters = Get-PackageParameters + +$MigratorDownload = @{ + PackageName = $env:ChocolateyPackageName + Url = 'https://download.sonatype.com/nexus/nxrm3-migrator/nexus-db-migrator-3.70.2-01.jar' + FileFullPath = Join-Path $toolsDir "nexus-db-migrator.jar" + Checksum = '69aebd05589a730af54d7b2cacba7d7bb5ea1f13dccd5e0d7eec002837068df0' + ChecksumType = 'SHA256' +} +Get-ChocolateyWebFile @MigratorDownload + +$PowerShellScript = @{ + packageName = $env:ChocolateyPackageName + PsFileFullPath = Join-Path $toolsDir "ConvertFrom-NexusOrientDb.ps1" +} +Install-ChocolateyPowershellCommand @PowerShellScript + +if ($PackageParameters.Migrate) { + $ConvertArguments = @{} + if ($PackageParameters.FQDN) { + $ConvertArguments.Hostname = $PackageParameters.FQDN + } + & $toolsDir\ConvertFrom-NexusOrientDb.ps1 @ConvertArguments -ErrorAction Stop + Write-Host "You may now uninstall this package with 'choco uninstall $($env:ChocolateyPackageName) --confirm'." +} diff --git a/automatic/nexus-repository-migrator/tools/chocolateyUninstall.ps1 b/automatic/nexus-repository-migrator/tools/chocolateyUninstall.ps1 new file mode 100644 index 00000000000..53ebbdd72dc --- /dev/null +++ b/automatic/nexus-repository-migrator/tools/chocolateyUninstall.ps1 @@ -0,0 +1,4 @@ +$ErrorActionPreference = 'Stop' +$toolsDir = Split-Path $MyInvocation.MyCommand.Definition + +Uninstall-BinFile -Name "ConvertFrom-NexusOrientDb" -Path $toolsDir\ConvertFrom-NexusOrientDb.ps1 \ No newline at end of file diff --git a/automatic/nexus-repository-migrator/update.ps1 b/automatic/nexus-repository-migrator/update.ps1 new file mode 100644 index 00000000000..5061d336b94 --- /dev/null +++ b/automatic/nexus-repository-migrator/update.ps1 @@ -0,0 +1,39 @@ +Import-Module Chocolatey-AU + +function global:au_GetLatest { + $R = Invoke-WebRequest -Uri https://help.sonatype.com/en/orientdb-downloads.html -UseBasicParsing + $MigratorUrl = $R.Links.href | Where-Object { + $_ -like 'https://download.sonatype.com/nexus/nxrm3-migrator/nexus-db-migrator-3.70.*.jar' + } + + if ($MigratorUrl.Count -ne 1) { + throw "Found $($MigratorUrl.Count) URLs that look like the migrator:`n $($MigratorUrl -join "`n ")" + } + + if ($MigratorUrl -match "/nexus-db-migrator-(?3\.70\.\d+-\d+)\.jar$") { + $NexusMigratorVersion = $Matches.Version + } + + $null = [System.Reflection.Assembly]::LoadFrom("$env:ChocolateyInstall\choco.exe") + $LatestPackageVersion = [Chocolatey.NugetVersionExtensions]::ToNormalizedStringChecked( + "$($NexusMigratorVersion -replace '-','.')" + ) + + @{ + NexusVersion = $NexusMigratorVersion + Version = $LatestPackageVersion + URL = $MigratorUrl + SHA256 = (Invoke-RestMethod "$MigratorUrl.sha256" -UseBasicParsing).Trim() + } +} + +function global:au_SearchReplace { + @{ + ".\tools\chocolateyInstall.ps1" = @{ + "(^\s*url\s*=\s*)('.*')" = "`$1'$($Latest.URL)'" + "(^\s*checksum\s*=\s*)('.*')" = "`$1'$($Latest.SHA256)'" + } + } +} + +update -ChecksumFor none \ No newline at end of file diff --git a/automatic/nexus-repository/Readme.md b/automatic/nexus-repository/Readme.md index 2a2cffc17d2..2c1d8dee092 100644 --- a/automatic/nexus-repository/Readme.md +++ b/automatic/nexus-repository/Readme.md @@ -22,10 +22,10 @@ Nexus product does not have a built-in web gallery for components. This package supports the following parameters: -* `/Fqdn` - The fqdn that matches the subject you are using for your Nexus instance SSL certificate. -* `/Port` - Specify what port Nexus should listen on. Defaults to `8081`. -* `/BackupSslConfig` - Ensures that the ssl configuration survives an upgrade. -* `/BackupLocation` - Species the path to backup ssl configuration to during upgrade. Defaults to `~/NexusSSLBackup`. +* `/Fqdn` - The fully-qualified domain name that matches the subject you are using for your Nexus instance SSL certificate. +* `/Port` - Specify what port Nexus should listen on. Defaults to `8081`, or whatever was previously configured. +* `/BackupSslConfig` - Ensures that the SSL configuration survives an upgrade, if it's otherwise undetected. +* `/BackupLocation` - Specifies the path to backup the SSL configuration to during upgrade. Defaults to `~/NexusSSLBackup`. You can pass parameters as follows: @@ -33,6 +33,14 @@ You can pass parameters as follows: ## Notes +- **ATTENTION BREAKING CHANGE FOR UPGRADES FROM VERSIONS BEFORE 3.71.0.6** + Nexus has upgraded the bundled version of Java, and sunset the OrientDb database type. + This means that you will need to migrate your database from OrientDb, if you are using it. + See [here](https://help.sonatype.com/en/orient-3-70-java-8-or-11.html) for further details, + or check out the nexus-repository-migrator package. + Versions after this will also overwrite rather than merge the program directory, i.e. `$env:ProgramFiles/nexus`, + and back-up HTTPS configuration and the keystore from there if it is configured. + - **ATTENTION BREAKING CHANGE FOR UPGRADES FROM VERSIONS BEFORE 3.3.2.02** Nexus no longer provided a setup.exe for installing Nexus Repository 3.x on Windows. If you previously installed this with a package that used a setup.exe, you must manually uninstall it first (use choco uninstall nexus-repository if you used Chocolatey to install it). Once you are on 3.3.2.02 or later, upgrades will work smoothly. diff --git a/automatic/nexus-repository/nexus-repository.nuspec b/automatic/nexus-repository/nexus-repository.nuspec index 354107d4f76..c9ac12ab526 100644 --- a/automatic/nexus-repository/nexus-repository.nuspec +++ b/automatic/nexus-repository/nexus-repository.nuspec @@ -29,10 +29,10 @@ Nexus product does not have a built-in web gallery for components. This package supports the following parameters: -* `/Fqdn` - The fqdn that matches the subject you are using for your Nexus instance SSL certificate. -* `/Port` - Specify what port Nexus should listen on. Defaults to `8081`. -* `/BackupSslConfig` - Ensures that the ssl configuration survives an upgrade. -* `/BackupLocation` - Species the path to backup ssl configuration to during upgrade. Defaults to `~/NexusSSLBackup`. +* `/Fqdn` - The fully-qualified domain name that matches the subject you are using for your Nexus instance SSL certificate. +* `/Port` - Specify what port Nexus should listen on. Defaults to `8081`, or whatever was previously configured. +* `/BackupSslConfig` - Ensures that the SSL configuration survives an upgrade, if it's otherwise undetected. +* `/BackupLocation` - Specifies the path to backup the SSL configuration to during upgrade. Defaults to `~/NexusSSLBackup`. You can pass parameters as follows: @@ -40,6 +40,14 @@ You can pass parameters as follows: ## Notes +- **ATTENTION BREAKING CHANGE FOR UPGRADES FROM VERSIONS BEFORE 3.71.0.6** + Nexus has upgraded the bundled version of Java, and sunset the OrientDb database type. + This means that you will need to migrate your database from OrientDb, if you are using it. + See [here](https://help.sonatype.com/en/orient-3-70-java-8-or-11.html) for further details, + or check out the nexus-repository-migrator package. + Versions after this will also overwrite rather than merge the program directory, i.e. `$env:ProgramFiles/nexus`, + and back-up HTTPS configuration and the keystore from there if it is configured. + - **ATTENTION BREAKING CHANGE FOR UPGRADES FROM VERSIONS BEFORE 3.3.2.02** Nexus no longer provided a setup.exe for installing Nexus Repository 3.x on Windows. If you previously installed this with a package that used a setup.exe, you must manually uninstall it first (use choco uninstall nexus-repository if you used Chocolatey to install it). Once you are on 3.3.2.02 or later, upgrades will work smoothly. diff --git a/automatic/nexus-repository/tools/chocolateyinstall.ps1 b/automatic/nexus-repository/tools/chocolateyinstall.ps1 index d9db82f91d8..300f6e96aab 100644 --- a/automatic/nexus-repository/tools/chocolateyinstall.ps1 +++ b/automatic/nexus-repository/tools/chocolateyinstall.ps1 @@ -16,6 +16,7 @@ $ServiceName = 'nexus' # Handle Package Parameters $pp = Get-PackageParameters +$CurrentConfig = Get-NexusConfiguration -Path $NexusConfigFile -ErrorAction SilentlyContinue $Hostname = if ($pp.ContainsKey("Fqdn")) { $pp["Fqdn"] @@ -25,7 +26,14 @@ $Hostname = if ($pp.ContainsKey("Fqdn")) { $NexusPort = if ($pp.ContainsKey("Port")) { $pp["Port"] - Write-Host "/Port was used, Nexus will listen on port $($NexusPort)." + Write-Host "/Port was used, Nexus will listen on port $($PP['Port'])." +} elseif ($CurrentConfig.'application-port-ssl' -gt 0) { + $CurrentConfig.'application-port-ssl' + $SslConfigured = $true + Write-Host "Nexus is configured to use application-port-ssl, Nexus will listen on port $($CurrentConfig.'application-port-ssl')" +} elseif ($CurrentConfig.'application-port' -gt 0) { + $CurrentConfig.'application-port' + Write-Host "Nexus is configured to use application-port, Nexus will listen on port $($CurrentConfig.'application-port')" } else { "8081" } @@ -34,12 +42,23 @@ if (Test-Path "$env:ProgramFiles\nexus\bin") { throw "Previous version of Nexus 3 installed by setup.exe is present, please uninstall before running this package." } +if (Test-NexusMigratorRequired -DataDir $TargetDataFolder -ProgramDir $TargetFolder) { + Write-Error (@( + "This upgrade will fail if you do not migrate your database from OrientDb." + "You can do this with the nexus-repository-migrator package, or by following" + "Sonatype's instructions. For details on using the migrator package, see:" + " https://community.chocolatey.org/packages/nexus-repository-migrator#description" + "For details, see: https://help.sonatype.com/en/orient-3-70-java-8-or-11.html" + ) -join "`n") +} + if ((Get-Service $ServiceName -ErrorAction SilentlyContinue)) { - Write-Warning "Nexus web app is already present, shutting it down so that we can upgrade it." + $CurrentlyInstalledVersion = Get-NexusVersion + Write-Warning "Nexus web app $($CurrentlyInstalledVersion) is already present, shutting it down so that we can upgrade it." Get-Service $ServiceName | Stop-Service -Force } -if ($pp.ContainsKey("BackupSslConfig")) { +if ($pp.ContainsKey("BackupSslConfig") -or $SslConfigured) { if ($pp.ContainsKey("BackupLocation")) { Backup-NexusSSL -BackupLocation $pp["BackupLocation"] } else { @@ -64,10 +83,9 @@ Install-ChocolateyZipPackage @PackageArgs Write-Host "Copying files to '$TargetFolder' with overwrite" if (Test-Path "$TargetFolder") { - Copy-Item "$ExtractFolder\$nexusversionedfolder\*" "$TargetFolder" -Force -Recurse -} else { - Copy-Item "$ExtractFolder\$nexusversionedfolder" "$TargetFolder" -Force -Recurse + Remove-Item "$TargetFolder" -Force -Recurse } +Copy-Item "$ExtractFolder\$nexusversionedfolder" "$TargetFolder" -Force -Recurse # Create the Nexus data directory, if it doesn't exist if (!(Test-Path "$TargetDataFolder")) { @@ -87,7 +105,7 @@ $processArgs = @{ $null = Start-ChocolateyProcessAsAdmin @processArgs -if ($pp.ContainsKey("BackupSslConfig")) { +if ($pp.ContainsKey("BackupSslConfig") -or $SslConfigured) { if ($pp.ContainsKey("BackupLocation")) { Restore-NexusSSL -BackupLocation $pp['BackupLocation'] } else { @@ -96,7 +114,7 @@ if ($pp.ContainsKey("BackupSslConfig")) { } # Update Port in Configuration before starting the service -if ($NexusPort -ne '8081') { +if ($NexusPort -ne '8081' -and -not $SslConfigured) { if (Test-Path "$NexusConfigFile") { Write-Host "Configuring Nexus to listen on port $NexusPort." (Get-Content "$NexusConfigFile") -replace "^#\s*application-port=.*$", "application-port=$NexusPort" | @@ -108,7 +126,7 @@ if ($NexusPort -ne '8081') { # Start the service, and wait for the site to become available if ((Start-Service $ServiceName -PassThru).Status -eq 'Running') { - Wait-NexusAvailability -Hostname $Hostname -Port $NexusPort -Config $NexusConfigFile -SSL:$pp.ContainsKey("BackupSslConfig") + Wait-NexusAvailability -Hostname $Hostname -Config $NexusConfigFile -Timeout 15 } else { Write-Warning "The Nexus Repository service ($ServiceName) did not start." } diff --git a/automatic/nexus-repository/tools/helpers.ps1 b/automatic/nexus-repository/tools/helpers.ps1 index 5996e6a7aa3..93348d33c47 100644 --- a/automatic/nexus-repository/tools/helpers.ps1 +++ b/automatic/nexus-repository/tools/helpers.ps1 @@ -6,7 +6,6 @@ function Backup-NexusSSL { ) begin { if (-not (Test-Path $BackupLocation)) { - Write-Host "Creating SSL Backup location" $null = New-Item $BackupLocation -ItemType Directory } } @@ -39,34 +38,111 @@ function Restore-NexusSSL { if (Test-Path "$BackupLocation\jetty-https.xml") { Copy-Item "$BackupLocation\jetty-https.xml" "$env:ProgramData\nexus\etc\jetty" } + } +} - Write-Host "Nexus is now available with the restored SSL configuration" +function Get-NexusCertificateDomain { + param( + [string]$DataDir = (Join-Path $env:ProgramData "sonatype-work"), + + [string]$ProgramDir = (Join-Path $env:ProgramData "nexus") + ) + $Config = Get-NexusConfiguration -Path $DataDir\nexus3\etc\nexus.properties + if ($Config.'nexus-args'.Split(',') -contains '${jetty.etc}/jetty-https.xml') { + [xml]$HttpsConfig = Get-Content $ProgramDir\etc\jetty\jetty-https.xml + $KeyToolPath = Join-Path $ProgramDir "jre/bin/keytool.exe" + $KeyStorePath = Join-Path (Join-Path $ProgramDir "etc/ssl") $HttpsConfig.SelectSingleNode("//Set[@name='KeyStorePath']").'#text' + $KeyStorePassword = $HttpsConfig.SelectSingleNode("//Set[@name='KeyStorePassword']").'#text' + + if ((Test-Path $KeyToolPath) -and (Test-Path $KeyStorePath)) { + # Running in a job, as otherwise KeyTool fails when run without input + Start-Job { + $KeyToolOutput = $using:KeyStorePassword | & "$using:KeyToolPath" -list -v -keystore "$using:KeyStorePath" -J"-Duser.language=en" 2>$null + if ($KeyToolOutput -join "`n" -match "(?smi)Certificate\[1\]:\nOwner: CN=(?.+?)\n") { + $Matches.Domain + } + } | Receive-Job -Wait + } } } -function Wait-NexusAvailability { +function Get-NexusUri { param( - [Parameter(Mandatory = $true)] - [string]$Hostname, + [Parameter()] + [string]$DataDir = (Join-Path $env:ProgramData "sonatype-work"), - [Parameter(Mandatory = $true)] - [uint16]$Port, + [Parameter()] + [string]$ProgramDir = (Join-Path $env:ProgramData "nexus"), - [Parameter(Mandatory = $true)] - [Alias("Config")] - [string]$NexusConfigFile, + [Parameter()] + [string]$ConfigPath = $( + if (Test-Path $DataDir/nexus3/etc/nexus.properties) { + "$DataDir/nexus3/etc/nexus.properties" + } elseif (Test-Path $ProgramDir/etc/nexus-default.properties) { + "$ProgramDir/etc/nexus-default.properties" + } + ), - [switch]$SSL + [string]$HostnameOverride ) - # Even though windows reports service is ready - web url will not respond until Nexus is actually ready to serve content - # We need to use this method to collect the port number so we can properly test the website has returned OK. - $nexusScheme, $portConfigLine = if ($SSL) { - # This is to combat Package Internalizer's over-enthusiastic URL matching - ('http' + 's'), 'application-port-ssl' - } else { - 'http', 'application-port' + $Scheme, $Hostname, $Port, $Path = if (Test-Path $ConfigPath) { + $Config = Get-NexusConfiguration -Path $ConfigPath -ErrorAction SilentlyContinue + + if ($Config.'application-port-ssl' -gt 0) { + # This is to combat Package Internalizer's over-enthusiastic URL matching + ('http' + 's') + if ($CertDomain = Get-NexusCertificateDomain -ConfigPath $ConfigPath) { + if (-not $script:OverriddenDomains) {$script:OverriddenDomains = @{}} + if ($CertDomain -notmatch '^\*') { + $CertDomain + } elseif ($CertDomain -match '^\*' -and $HostnameOverride -like $CertDomain) { + ($script:OverriddenDomains[$CertDomain] = $HostnameOverride) + } elseif ($CertDomain -match '^\*') { + while ($script:OverriddenDomains[$CertDomain] -notlike $CertDomain) { + $script:OverriddenDomains[$CertDomain] = Read-Host "Please provide the FQDN for Nexus matching the '$($CertDomain)' certificate" + } + $script:OverriddenDomains[$CertDomain] + } + } else { + Write-Warning "Could not figure out SSL configuration for $($env:ComputerName)" + $env:ComputerName + } + $Config.'application-port-ssl' + } elseif ($Config.'application-port' -gt 0) { + 'http' + if ($HostnameOverride) { + $HostnameOverride + } else { + "localhost" + } + $Config.'application-port' + } + + $Config.'nexus-context-path' } + # Set defaults if still not present + if (-not $Hostname) {$Hostname = "localhost"} + if (-not $Scheme) {$Scheme = 'http'} + if (-not $Port) {$Port = '8081'} + + "$($Scheme)://$($Hostname):$($Port)$($Path)" +} + +function Wait-NexusAvailability { + param( + # The hostname in use + [Parameter()] + [string]$Hostname, + + # The path to the config file + [Parameter(Mandatory)] + [Alias("Config")] + [string]$NexusConfigFile = (Join-Path $env:ProgramData "\sonatype-work\nexus3\etc\nexus.properties"), + + # Configurable timeout in minutes + [uint16]$Timeout = 3 + ) # As the service is started, this should be present momentarily $Timer = [System.Diagnostics.Stopwatch]::StartNew() while (-not ($ConfigPresent = Test-Path $NexusConfigFile) -and $Timer.Elapsed.TotalSeconds -le 60) { @@ -74,23 +150,10 @@ function Wait-NexusAvailability { Start-Sleep -Seconds 5 } - if ($ConfigPresent) { - $nexusPort = (Get-Content $NexusConfigFile | Where-Object { - $_ -match $portConfigLine - }).Split('=')[-1] - - $nexusPath = (Get-Content $NexusConfigFile | Where-Object { - $_ -match "nexus-context-path" - }).Split("=")[-1] - } else { - Write-Warning "Expected Nexus Config file '$($NexusConfigFile)' is not present." - $nexusPath, $nexusPort = '/', $Port - } - - $NexusUri = "$($nexusScheme)://$($hostname):$($nexusPort)$($nexusPath)" + $NexusUri = Get-NexusUri -Hostname $Hostname Write-Host "Waiting on Nexus Web UI to be available at '$($NexusUri)'" - while ($Response.StatusCode -ne '200' -and $Timer.Elapsed.TotalMinutes -lt 3) { + while ($Response.StatusCode -ne '200' -and $Timer.Elapsed.TotalMinutes -lt $Timeout) { try { $Response = Invoke-WebRequest -Uri $NexusUri -UseBasicParsing } catch { @@ -102,6 +165,359 @@ function Wait-NexusAvailability { if ($Response.StatusCode -eq '200') { Write-Host "Nexus is ready!" } else { - Write-Error "Nexus did not respond to requests at '$($NexusUri)' within 3 minutes of the service being started." + Write-Error "Nexus did not respond to requests at '$($NexusUri)' within $($Timeout) minutes of the service being started." + } +} + +function Get-NexusConfiguration { + <# + .Synopsis + Returns a list of settings configured in nexus.properties + + .Example + Get-NexusConfiguration + # Returns all properties and values + + .Example + (Get-NexusConfiguration).'application-port-ssl' + # Returns the value for a single property, 'application-port-ssl' + #> + [CmdletBinding()] + param( + $Path = (Join-Path $env:ProgramData "\sonatype-work\nexus3\etc\nexus.properties") + ) + Get-Content $Path | Where-Object { + $_ -and $_ -notmatch "^\W*#" + } | ConvertFrom-StringData +} + +function Get-NexusVersion { + <# + .Synopsis + Returns the currently installed version of Sonatype Nexus Repository + + .Example + Get-NexusVersion + #> + [CmdletBinding()] + param( + [string]$ProgramDir = (Join-Path $env:ProgramData 'nexus') + ) + # This isn't a very appropriate file to target. + ([xml](Get-Content (Join-Path $ProgramDir "\.install4j\i4jparams.conf") -ErrorAction SilentlyContinue)).config.general.applicationVersion +} + +function Test-NexusDatabaseType { + <# + .Synopsis + Checks the currently configured database type in use by Nexus + + .Example + Test-NexusDatabaseType -is "OrientDb" + #> + [OutputType([bool])] + [CmdletBinding()] + param( + [ValidateSet("OrientDb","H2","PostgreSQL")] + [Parameter(Mandatory)] + [Alias('is','eq')] + [string]$Type, + [string]$ProgramDir = (Join-Path $env:ProgramData 'nexus'), + [string]$DataDir = (Join-Path $env:ProgramData 'sonatype-work') + ) + $JdbcUrl = if ($env:NEXUS_DATASTORE_NEXUS_JDBCURL) { + $env:NEXUS_DATASTORE_NEXUS_JDBCURL + } elseif ((Test-Path "$DataDir\etc\fabric\nexus-store.properties") -and ($Properties = Select-String -Path "$DataDir\etc\fabric\nexus-store.properties" -Pattern '^jdbcUrl=(?.+)$')) { + $Properties.Matches.Groups[1].Value + } elseif ($Properties = Select-String -Path "$ProgramDir\bin\nexus.vmoptions" -Pattern '^(?!#)\W*-Dnexus\.datastore\.nexus\.jdbcUrl=(?.+)') { + $Properties.Matches.Groups[1].Value + } + + $FoundType = if ($JdbcUrl) { + if ($JdbcUrl -match 'jdbc\\?:h2') { + "H2" + } elseif ($JdbcUrl -match 'jdbc\\?:postgresql') { + "PostgreSQL" + } + } else { + if (Test-Path $DataDir\nexus3\db\nexus.mv.db) { + "H2" + } else { + "OrientDb" + } + } + + $Type -eq $FoundType +} + +function New-NexusOrientDbBackup { + <# + .Synopsis + Produces a nearly-authentic backup file for all specified database types + + .Example + New-NexusOrientDbBackup + # Backs up all the databases needed for a migration. + + .Example + New-NexusOrientDbBackup -IncludedDatabases 'config' -DestinationPath $PWD + # Backs up the config database to the present working directory. + #> + [CmdletBinding(SupportsShouldProcess)] + param( + [string]$DestinationPath = (Join-Path $env:TEMP 'sonatype-backup'), + [string]$DataDir = (Join-Path $env:ProgramData 'sonatype-work'), + [string]$BaseName = "-$(Get-Date -Format 'yyyy-MM-dd-HH-mm-ss')-$(Get-NexusVersion).bak", + [string[]]$IncludedDatabases = @( + # "analytics" + "component" + "config" + "security" + ), + [string]$ServiceName = "nexus", + [switch]$PassThru + ) + if (($Service = Get-Service $ServiceName -ErrorAction SilentlyContinue).Status -ne 'Stopped') { + if ($PSCmdlet.ShouldProcess($ServiceName, "Stopping Service")) { + Stop-Service $ServiceName + } + } + try { + if (-not (Test-Path $DestinationPath) -and $PSCmdlet.ShouldProcess($DestinationPath, "Ensuring Folder")) { + $null = New-Item -Path $DestinationPath -ItemType Directory -Force + } + $PreviousProgress, $ProgressPreference = $ProgressPreference, "SilentlyContinue" + foreach ($Database in $IncludedDatabases) { + Push-Location (Join-Path $DataDir "nexus3/db/$Database") + $ArchiveArgs = @{ + Path = Get-ChildItem -Path (Join-Path $DataDir "nexus3/db/$Database/*") -Exclude "cache.stt" -File + DestinationPath = Join-Path $DestinationPath "$($Database.ToLower())$BaseName.zip" + CompressionLevel = 'NoCompression' # Not efficient, but sufficient for this migration. + } + if ($PSCmdlet.ShouldProcess($Database, "Creating Backup of Database")) { + Compress-ArchiveCompat @ArchiveArgs + Rename-Item $ArchiveArgs.DestinationPath -NewName "$($Database.ToLower())$BaseName" -PassThru:$PassThru + } + Pop-Location + } + } finally { + $ProgressPreference = $PreviousProgress + if ($Service.Status -eq 'Running') { + if ($PSCmdlet.ShouldProcess($ServiceName, "Starting Service")) { + Start-Service $ServiceName + } + } } } + +function Compress-ArchiveCompat { + <# + .Synopsis + A drop in for creating an archive on PowerShell versions that don't have Compress-Archive + + .Notes + Specifically written to be able to create Nexus database backups, + this probably shouldn't be used for other stuff. + + .Example + $ArchiveArgs = @{ + Path = Get-ChildItem (Join-Path $DataDir "nexus3/db/$Database/*") -File -Recurse + DestinationPath = Join-Path $DestinationPath "SomeName.zip" + CompressionLevel = 'NoCompression' + } + Compress-Archive @ArchiveArgs + #> + [CmdletBinding()] + param( + [Parameter(Mandatory, ValueFromPipeline)] + [System.IO.FileInfo[]]$Path, + + [Parameter(Mandatory)] + [string]$DestinationPath, + + [ValidateSet("Fastest", "NoCompression", "Optimal")] + [string]$CompressionLevel = "Optimal" + ) + begin { + Add-Type -Assembly System.IO.Compression, System.IO.Compression.FileSystem + if (-not (Test-Path (Split-Path $DestinationPath))) {$null = mkdir (Split-Path $DestinationPath) -Force} + $Archive = [System.IO.Compression.ZipFile]::Open($DestinationPath, [System.IO.Compression.ZipArchiveMode]::Update) + } + process { + foreach ($File in $Path) { + $null = [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile( + $Archive, + $File.FullName, + "$((Resolve-Path -Path $File.FullName -Relative).TrimStart('\.'))", + [System.IO.Compression.CompressionLevel]::$CompressionLevel + ) + } + } + end { + $Archive.Dispose() + } +} + +function Test-NexusMigratorRequired { + <# + .Synopsis + Tests if we need to migrate this system's database + + .Example + Test-NexusMigratorRequired -DataDir $TargetDataFolder -ProgramDir $TargetFolder + # Returns if a migration is required, based on the existing version and database. + #> + [OutputType([Nullable[bool]])] + [CmdletBinding()] + param( + # The installed version of Nexus + $CurrentVersion = (Get-NexusVersion -ProgramDir $ProgramDir), + + # The only version of Nexus we should be upgrading from + $RequiredVersion = "3.70.1", + + # Nexus' working directory + $DataDir = (Join-Path $env:ProgramData 'sonatype-work'), + + # Nexus' program directory + $ProgramDir = (Join-Path $env:ProgramData 'nexus') + ) + if ($CurrentVersion) { + [version]$CurrentDeNexusVersion = $CurrentVersion -replace '-\d+$' + + if ($CurrentDeNexusVersion -lt $RequiredVersion) { + Write-Error (@( + "Please upgrade nexus-repository to version 3.70.1-02 before upgrading further." + "You can do this by running 'choco upgrade nexus-repository --version 3.70.1.2 --confirm'" + "You will then need to migrate your database from OrientDb to H2 or PostgreSQL." + "For more details, please see: https://help.sonatype.com/en/orient-pre-3-70-java-8-or-11.html" + ) -join "`n") + throw "Package cannot upgrade from '$($CurrentVersion)'" + } elseif ($CurrentDeNexusVersion -eq $RequiredVersion) { + # We will upgrade if we are on OrientDb, otherwise leave it alone. + Test-NexusDatabaseType -Type "OrientDb" -DataDir $DataDir -ProgramDir $ProgramDir + } elseif ($CurrentDeNexusVersion -gt $RequiredVersion) { + Write-Verbose "Detected Nexus version '$($CurrentVersion)'. Will not attempt to upgrade." + } + } +} + +function Test-NexusMigratorFreeSpaceProblem { + <# + .Synopsis + Tests to see if we don't have more free space than we think we require. + + .Example + Test-NexusMigratorFreeSpaceProblem -FreeSpace 1GB -RequiredSpace 5GB + # Should be a problem, because 1GB is less than 5GB + + .Example + Test-NexusMigratorFreeSpaceProblem -Drive C -DatabaseFolder $env:ProgramData\sonatype-work\nexus3\db + # Will test for the values found. + #> + [OutputType([bool])] + [CmdletBinding()] + param( + # The name of the drive we care about. + [Parameter(Mandatory, ParameterSetName="Location")] + [string]$Drive, + + # The folder the database lives in + [Parameter(Mandatory, ParameterSetName="Location")] + [string]$DatabaseFolder, + + # This is the free space on the drive that we'll be manipulating the migration files. + [Parameter(Mandatory, ParameterSetName="Values")] + $FreeSpace = (Get-PSDrive $Drive).Free, + + # The migrator requires 3 times the size of the database files, or 10GB - whichever is larger. + [Parameter(Mandatory, ParameterSetName="Values")] + $RequiredSpace = $( + [Math]::Max( + (3 * (Get-ChildItem -Path $DatabaseFolder -Recurse | Measure-Object Length -Sum).Sum), + 10GB + ) + ) + ) + $Result = $FreeSpace -lt $RequiredSpace + + if ($Result) { + Write-Error -Message (@( + "The Sonatype Nexus Database Migrator requires at least $([Math]::Round(($RequiredSpace / 1GB), 2))GB free space." + "There is only $([Math]::Floor($FreeSpace / 1GB))GB free. For more details, please see: " + "https://help.sonatype.com/en/orient-3-70-java-8-or-11.html#considerations-before-migrating-252166" + ) -join "`n") + } + + return $Result +} + +function Test-NexusMigratorMemoryProblem { + <# + .Synopsis + Tests to see if we don't have enough memory for the migration. + + .Example + Test-NexusMigratorMemoryProblem -Memory 10GB -Requirement 5GB + # Should be fine, no problem. + + .Example + Test-NexusMigratorMemoryProblem + # Tests the expected values against the existing memory on this system. + #> + [OutputType([bool])] + [CmdletBinding()] + param( + # The current amount of memory, in bytes. + $Memory = (Get-CimInstance Win32_PhysicalMemory | Measure-Object Capacity -Sum).Sum, + + # The migrator requires 16GB of memory. + $Requirement = 16GB + ) + $Result = $Memory -lt $Requirement + + if ($Result) { + Write-Error -Message (@( + "The Sonatype Nexus Database Migrator requires at least $($Requirement / 1GB)GB of memory." + "There is only $($Memory/1GB)GB available. For more details, please see: " + "https://help.sonatype.com/en/orient-3-70-java-8-or-11.html#considerations-before-migrating-252166" + ) -join "`n") + } + + return $Result +} + +function Get-NexusRepositoryServiceInstall { + <# + .Synopsis + If found, returns the name of the Nexus service and the install and data directories it uses. + + .Example + Get-NexusRepositoryInstallValues + #> + [CmdletBinding()] + param( + # By default, we assume there is only one service. This searches for all installed services. + [switch]$AllResults + ) + # If you have a lot of services, searching them all may take longer - + # so we can stop searching when we find the first service matching nexus.exe. + $ResultCount = @{} + if (-not $AllResults) {$ResultCount.First = 1} + + $NexusService = Get-ChildItem HKLM:\System\CurrentControlSet\Services\ | Where-Object { + ($ImagePath = Get-ItemProperty -Path $_.PSPath -Name ImagePath -ErrorAction SilentlyContinue) -and + $ImagePath.ImagePath.Trim('"''').EndsWith('\nexus.exe') + } | Select-Object @ResultCount + + foreach ($Service in $NexusService) { + $ServiceName = $Service.PSChildName + $TargetFolder = (Get-ItemProperty -Path $Service.PSPath).ImagePath.Trim('"''') | Split-Path | Split-Path + $DataFolder = Convert-Path (Join-Path $TargetFolder "$((Get-Content $TargetFolder\bin\nexus.vmoptions) -match '^-Dkaraf.data=(?.+)$' -replace '^-Dkaraf.data=')") + [PSCustomObject]@{ + ServiceName = $ServiceName + ProgramFolder = $TargetFolder + DataFolder = $DataFolder + } + } +} \ No newline at end of file diff --git a/automatic/nexus-repository/_update.ps1 b/automatic/nexus-repository/update.ps1 similarity index 100% rename from automatic/nexus-repository/_update.ps1 rename to automatic/nexus-repository/update.ps1