Skip to content

Commit

Permalink
Merge pull request #246 from chocolatey/offlineBootstrap
Browse files Browse the repository at this point in the history
(#86) Adds Offline Preparation Script
  • Loading branch information
ryanrichter94 authored May 14, 2024
2 parents f23d10f + 8b4666e commit 6498db7
Show file tree
Hide file tree
Showing 19 changed files with 668 additions and 323 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.nupkg
*.zip
/scripts/ChocolateyInstall.ps1
Chocolatey.License.xml
*/bcrypt.net.0.1.0/*
59 changes: 43 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,62 @@ This document outlines how to help with development of the Chocolatey Quickstart

## Development

When looking to make a change ensure any working branch is taken from `develop`. You can do this with the following:
When looking to make a change ensure any working branch is taken from the tip of `main`. You can do this with the following:

```powershell
git checkout develop
git fetch upstream
git rebase upstream/develop
git push origin
git checkout -b $NewBranchName
$ChocolateyUpstream = ((git remote -v) -match "github.com/chocolatey/choco-quickstart-scripts.git \(fetch\)$" -split "\t")[0]
git fetch $ChocolateyUpstream
git checkout -b $NewBranchName $ChocolateyUpstream/main
```

## Testing
### Development Testing

Test your changes before raising a Pull Request to merge your changes. In order to set things up for testing do the following:
You must test your changes before submitting a PR.

1. Set `$env:CHOCO_QSG_DEVELOP = $true`
1. The first step of the Guide will need amended to fetch from the `develop` branch:
You should test on a clean, supported operating system.

> NB: To save time in repeated testing from a clean environment, you can run the OfflineInstallPreparation script in your repository and copy the files directory before copying.
To test the quickstart environment:

1. Copy the repository directory over to `C:\choco-setup\files\` on the test machine. You do not need to copy the `.git` directory.
1. Open an elevated Windows PowerShell console.
1. Run `C:\choco-setup\files\Start-C4bSetup.ps1`, and continue through the guide steps as detailed in `README.md`.
1. Run `C:\choco-setup\files\Start-C4bVerification.ps1` and check that all tests pass.

## Testing a PR

Changes in a PR must be tested before merging. In order to set things up for testing do the following in an elevated Windows PowerShell terminal:

1. Set `$env:CHOCO_QSG_BRANCH` to the PR ID or Branch Name to download.
1. Run Quickstart Guide as documented, in the same session.

Example:

```powershell
$env:CHOCO_QSG_BRANCH = "< Insert PR ID or Upstream BranchName Here >"
Set-ExecutionPolicy Bypass -Scope Process -Force
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::tls12
$QuickStart = 'https://raw.githubusercontent.com/chocolatey/choco-quickstart-scripts/develop/Start-C4bSetup.ps1'
$Script = [System.Net.Webclient]::new().DownloadString($QuickStart)
$sb = [ScriptBlock]::Create($Script)
& $sb
Invoke-RestMethod "https://ch0.co/qsg-go" | Invoke-Expression
```

1. Perform each step of the Quickstart Guide, and make sure the changes you have attempted to make work appropriately.
1. Run `Start-C4bVerification.ps1` and check that all tests pass.
1. If everything looks OK, push your branch and create your Pull Request.

## SSL Certificates for testing
## Creating a PR

Push your branch to a fork or repository, and create a new PR [here](https://github.com/chocolatey/choco-quickstart-scripts/compare).

You should fill out the issue template as much as possible.

Reach out to Stephen, and he can generate a Let's Encrypt certificate for you.
### Rebasing Your Branch

If something else has been merged since you created your branch, you should rebase your branch onto the new tip of `main`. If you'd already pushed the branch, you may need to force-push the new history over the upstream version. You can do this as follows:

```powershell
$ChocolateyUpstream = ((git remote -v) -match "github.com/chocolatey/choco-quickstart-scripts.git \(fetch\)$" -split "\t")[0]
git fetch $ChocolateyUpstream
git rebase $ChocolateyUpstream\main --autostash
```
147 changes: 147 additions & 0 deletions OfflineInstallPreparation.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<#
.Synopsis
Prepares the repository for an offline deployment.
.Description
These scripts can be run from a network without access to the internet,
but it needs to prepare packages to be run offline.
This script downloads and internalizes packages for such usage.
.Notes
This must be run on a Windows system with access to the internet because
it uses Chocolatey for Business' Package Internalizer.
.Notes
Instead of using this script, you can internalize all required packages manually,
zip them, and drop them in the files directory as shown below.
.Example
.\OfflineInstallPreparation.ps1 -LicensePath C:\ProgramData\chocolatey\license\chocolatey.license.xml
#>
[CmdletBinding()]
param(
[ValidateScript({
if (-not (Test-Path (Convert-Path $_))) {
throw "License file does not exist at '$($_)'. Please provide a valid -LicensePath"
}
try {
[xml]$License = Get-Content $_
$Expiry = Get-Date $License.license.expiration
if (-not $Expiry -or $Expiry -lt (Get-Date)) {throw}
} catch {
throw "License '$($_)' is not valid.$(if ($Expiry) {" It expired at '$($Expiry)'."})"
}
$true
})]
[string]$LicensePath = "C:\ProgramData\chocolatey\license\chocolatey.license.xml",

[string]$WorkingDirectory = $(Join-Path $env:Temp "choco-offline")
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$LicensePath = Convert-Path $LicensePath

. $PSScriptRoot\scripts\Get-Helpers.ps1

$ChocoInstallScript = Join-Path $PSScriptRoot "scripts\ChocolateyInstall.ps1"
if (-not (Test-Path $ChocoInstallScript)) {
Invoke-WebRequest -Uri 'https://chocolatey.org/install.ps1' -OutFile $ChocoInstallScript
}

$Signature = Get-AuthenticodeSignature -FilePath $ChocoInstallScript

if ($Signature.Status -eq 'Valid' -and $Signature.SignerCertificate.Subject -eq 'CN="Chocolatey Software, Inc.", O="Chocolatey Software, Inc.", L=Topeka, S=Kansas, C=US') {
if (-not (Get-Command choco.exe -ErrorAction SilentlyContinue)) {
if (Test-Path $PSScriptRoot\files\chocolatey.*.nupkg) {
$env:ChocolateyDownloadUrl = (Convert-Path $PSScriptRoot\files\chocolatey.*.nupkg)[0]
}
& $ChocoInstallScript
}
} else {
Write-Error "ChocolateyInstall.ps1 script signature is not valid. Please investigate." -ErrorAction Stop
}

# Initialize environment, ensure Chocolatey For Business, etc.
$Licensed = ($($(choco.exe)[0] -match "^Chocolatey (?<Version>\S+)\s*(?<LicenseType>Business)?$") -and $Matches.LicenseType)
$InstalledLicensePath = "$env:ChocolateyInstall\license\chocolatey.license.xml"
if (-not $Licensed) {
if (-not (Test-Path $InstalledLicensePath)) {
if (-not (Test-Path $env:ChocolateyInstall\license)) {
$null = New-Item $env:ChocolateyInstall\license -ItemType Directory
}
Copy-Item $LicensePath $InstalledLicensePath -Force
}
$ExtensionSource = if (Test-Path $PSScriptRoot\files\chocolatey.extension.*.nupkg) {
Convert-Path $PSScriptRoot\files\
} else {
'https://licensedpackages.chocolatey.org/api/v2/'
}
choco install chocolatey.extension --source $ExtensionSource --params="'/NoContextMenu'" --confirm
}

# Download each set of packages to the output directories
$PackageWorkingDirectory = Join-Path $WorkingDirectory "Packages"
if (-not (Test-Path $PackageWorkingDirectory)) {
$null = New-Item -Path $PackageWorkingDirectory -ItemType Directory -Force
}
foreach ($Package in (Get-Content $PSScriptRoot\files\chocolatey.json | ConvertFrom-Json).packages) {
$ChocoArgs = @(
"download", "$($Package.Name)"
"--output-directory", $PackageWorkingDirectory
"--ignore-dependencies"
)
$ChocoArgs += switch ($Package.psobject.properties.name) {
"Version" { "--version=$($Package.Version)" }
"Args" { $Package.Args }
}
if ($Package.Internalize -or $Package.PSObject.Properties.Name -notcontains "Internalize") {
$ChocoArgs += "--internalize" # Default to internalizing
}

try {
if (-not (Get-ChocolateyPackageMetadata -Path $PackageWorkingDirectory -Id $Package.Name) -and -not (Get-ChocolateyPackageMetadata -Path "$PSScriptRoot\files\" -Id $Package.Name)) {
Write-Host "Downloading '$($Package.Name)'"

while ((Get-ChildItem $PackageWorkingDirectory -Filter *.nupkg).Where{$_.CreationTime -gt (Get-Date).AddMinutes(-1)}.Count -gt 5) {
Write-Verbose "Slowing down for a minute, in order to not trigger rate-limiting..."
Start-Sleep -Seconds 5
}

choco @ChocoArgs
}
} catch {
throw $_
}
}
Move-Item -Path $PackageWorkingDirectory\*.nupkg -Destination $PSScriptRoot\files\

# Jenkins Plugins
$PluginsWorkingDirectory = Join-Path $WorkingDirectory "JenkinsPlugins"
if (-not (Test-Path $PluginsWorkingDirectory)) {
$null = New-Item -Path $PluginsWorkingDirectory -ItemType Directory -Force
}
if (Test-Path $PSScriptRoot\files\JenkinsPlugins.zip) {
Expand-Archive -Path $PSScriptRoot\files\JenkinsPlugins.zip -DestinationPath $PluginsWorkingDirectory -Force
}
$ProgressPreference = "Ignore"
foreach ($Plugin in (Get-Content $PSScriptRoot\files\jenkins.json | ConvertFrom-Json).plugins) {
$RestArgs = @{
Uri = "https://updates.jenkins-ci.org/latest/$($Plugin.Name).hpi"
OutFile = Join-Path $PluginsWorkingDirectory "$($Plugin.Name).hpi"
}
if ($Plugin.Version -and $Plugin.Version -ne 'latest') {
$RestArgs.Uri = "https://updates.jenkins-ci.org/download/plugins/$($Plugin.Name)/$($Plugin.Version)/$($Plugin.Name).hpi"
}
if (-not (Test-Path $RestArgs.OutFile)) {
Invoke-WebRequest @RestArgs -UseBasicParsing
}
}
Compress-Archive -Path $PluginsWorkingDirectory\* -Destination $PSScriptRoot\files\JenkinsPlugins.zip -Force

# BCryptDll
$null = Get-BcryptDll

# License
if ($LicensePath -ne "$PSScriptRoot\files\chocolatey.license.xml") {
Copy-Item -Path (Convert-Path $LicensePath) -Destination $PSScriptRoot\files\chocolatey.license.xml
}
45 changes: 35 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,18 @@ Below are the minimum requirements for setting up your C4B server via this guide

> :warning:**DISCLAIMER**: This guide utilizes code from a GitHub repository, namely: [choco-quickstart-scripts](https://github.com/chocolatey/choco-quickstart-scripts). Though we explain what each script does in drop-down boxes, please do your due diligence to review this code and ensure it meets your Organizational requirements.
> :memo:**Offline Install**: If your C4B server does not have unrestricted access to the internet, you can download the `choco-quickstart-scripts` repository to a Windows machine that is connected to the internet and run `OfflineInstallPreparation.ps1`. This will use Chocolatey to save all of the required assets into the repository folder, which can then be transferred to the target C4B server.
### Step 1: Begin C4B Setup

> :exclamation:**[IMPORTANT]** All commands should be run from an **elevated** PowerShell window (and **not ISE**), by opening your PowerShell console with the `Run as Administrator` option.
> :exclamation:**[IMPORTANT]** All commands must be run from an **elevated** Windows PowerShell window (and **not ISE**), by opening your PowerShell console with the `Run as Administrator` option.
1. Open a PowerShell console with the `Run as Administrator` option, and paste and run the following code:
1. Open a Windows PowerShell console with the `Run as Administrator` option, and paste and run the following code:

```powershell
Set-ExecutionPolicy Bypass -Scope Process -Force
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::tls12
$QuickStart = 'https://raw.githubusercontent.com/chocolatey/choco-quickstart-scripts/main/Start-C4bSetup.ps1'
$Script = [System.Net.Webclient]::new().DownloadString($QuickStart)
$sb = [ScriptBlock]::Create($Script)
& $sb
Invoke-RestMethod https://ch0.co/qsg-go | Invoke-Expression
```
> <details>
Expand All @@ -97,9 +96,11 @@ Below are the minimum requirements for setting up your C4B server via this guide
> </ul>
> </details>
> :memo:**Offline Install**: You can now copy the `C:\choco-setup\` directory to any computer to continue the installation. To zip up that directory, run `Compress-Archive -Path C:\choco-setup\files\* -DestinationPath C:\choco-setup\C4B-Files.zip`. Move the archive to your new machine, and run `Expand-Archive -Path /path/to/C4B-Files.zip -DestinationPath C:\choco-setup\files -Force`. You should then run `Set-Location "$env:SystemDrive\choco-setup\files"; .\Start-C4bSetup.ps1`, and continue with the guide.
### Step 2: Nexus Setup
1. In the same **elevated** PowerShell console as above, paste and run the following code:
1. In the same **elevated** Windows PowerShell console as above, paste and run the following code:
```powershell
Set-Location "$env:SystemDrive\choco-setup\files"
Expand All @@ -121,7 +122,7 @@ Below are the minimum requirements for setting up your C4B server via this guide
> </ul>
> </details>
### Step 3: CCM Setup
### Step 3: Chocolatey Central Management Setup
1. In the same PowerShell Administrator console as above, paste and run the following code:
Expand All @@ -135,7 +136,7 @@ Below are the minimum requirements for setting up your C4B server via this guide
> <ul class="list-style-type-disc">
> <li>Installs MS SQL Express and SQL Server Management Studio (SSMS)</li>
> <li>Creates "ChocolateyManagement" database, and adds appropriate `ChocoUser` permissions</li>
> <li>Installs all 3 CCM packages (database, service, web), with correct parameters</li>
> <li>Installs all 3 Chocolatey Central Management packages (database, service, web), with correct parameters</li>
> <li>Outputs data to a JSON file to pass between scripts</li>
> </ul>
> </details>
Expand Down Expand Up @@ -208,7 +209,31 @@ Below are the minimum requirements for setting up your C4B server via this guide
> :mag: **FYI**: A `Readme.html` file will now be generated on your desktop. This file contains login information for all 3 web portals (CCM, Nexus, and Jenkins). This `Readme.html`, along with all 3 web portals, will automatically be opened in your browser.
### Step 6: Setting up Endpoints
### Step 6: Verification
1. In the same **elevated** PowerShell console as above, paste and run the following code:
```powershell
Set-Location "$env:SystemDrive\choco-setup\files"
.\Start-C4bVerification.ps1 -Fqdn '<Your expected fqdn here>'
```
If you expect services to be available at `chocoserver.yourcompany.com`, then your command would look like: `.\Start-C4bVerification.ps1 -Fqdn 'chocoserver.yourcompany.com'`
> <details>
> <summary><strong>What does this script do? (click to expand)</strong></summary>
> <ul class="list-style-type-disc">
> <li>Verifies Nexus Repository installation</li>
> <li>Verifies Central Management installation</li>
> <li>Verifies Jenkins installation</li>
> <li>Ensures system firewall is configured</li>
> <li>Ensures Windows Features are installed</li>
> <li>Ensures services are correctly configured</li>
> <li>Ensured README is created</li>
> </ul>
> </details>
### Step 7: Setting up Endpoints
1. Find the `Register-C4bEndpoint.ps1` script in the `choco-setup\files\scripts\` directory on your C4B Server. Copy this script to your client endpoint.
Expand Down
26 changes: 17 additions & 9 deletions Set-SslSecurity.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,19 @@ process {
}

if (-not $CertificateDnsName) {
$matcher = 'CN\s?=\s?[^,\s]+'
$matcher = 'CN\s?=\s?(?<Subject>[^,\s]+)'
$null = $Certificate.Subject -match $matcher
$SubjectWithoutCn = $matches[0] -replace 'CN=', ''
}
$SubjectWithoutCn = if ($Matches.Subject.StartsWith('*')) {
# This is a wildcard cert, we need to prompt for the intended CertificateDnsName
while ($CertificateDnsName -notlike $Matches.Subject) {
$CertificateDnsName = Read-Host -Prompt "$(if ($CertificateDnsName) {"'$($CertificateDnsName)' is not a subdomain of '$($Matches.Subject)'. "})Please provide an FQDN to use with the certificate '$($Matches.Subject)'"
}
$CertificateDnsName
}
else {
$Matches.Subject
}
}
else {
$SubjectWithoutCn = $CertificateDnsName
}
Expand All @@ -111,15 +120,15 @@ process {
Copy-CertToStore -Certificate $Certificate

# Generate Nexus keystore
New-NexusCert -Thumbprint $Certificate.Thumbprint
$null = New-NexusCert -Thumbprint $Certificate.Thumbprint

# Add firewall rule for Nexus
netsh advfirewall firewall add rule name="Nexus-8443" dir=in action=allow protocol=tcp localport=8443

Write-Verbose "Starting up Nexus"
Start-Service nexus

Write-Warning "Waiting to give Nexus time to start up"
Write-Warning "Waiting to give Nexus time to start up on 'https://${SubjectWithoutCn}:8443'"
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::tls12
do {
$response = try {
Expand Down Expand Up @@ -202,6 +211,9 @@ process {
$chocoArgs = @('apikey', "--source='$RepositoryUrl'", "--api-key='$NuGetApiKey'")
& choco @chocoArgs

# Reset the NuGet v3 cache, such that it doesn't capture localhost as the FQDN
Remove-NexusRepositoryFolder -RepositoryName ChocolateyInternal -Name v3

Update-JsonFile -Path "$env:SystemDrive\choco-setup\logs\nexus.json" -Properties @{
NexusUri = "https://$($SubjectWithoutCn):8443"
NexusRepo = $RepositoryUrl
Expand Down Expand Up @@ -329,10 +341,6 @@ Invoke-Expression (`$downloader.DownloadString("http://`$(`$HostName):80/Import-
}

end {

# Hand back the created/found certificate to the caller.
$Certificate

Write-Host 'Writing README to Desktop; this file contains login information for all C4B services.'
New-QuickstartReadme

Expand Down
Loading

0 comments on commit 6498db7

Please sign in to comment.