From 9ba230f1909a207e4c64a571dd79f5374edb3047 Mon Sep 17 00:00:00 2001 From: Refringe Date: Wed, 14 Feb 2024 01:35:01 -0500 Subject: [PATCH] Build Scripts Got to a point where the server and modules are being compiled correctly. Launcher script needs... something. It's failing to build and I don't know why. TODO: - Fix Launcher Build Script - Combine sub-builds into output directory - Compress output directory - Upload release to public folder - Adapt build script to work with a dynamic tag value - Update script to only run _any_ builds when the tag exists in all three - Write drone configuration version of the dockerfile/script - Testing within the Drone env --- .drone.yml | 20 +++++ .vscode/settings.json | 5 ++ Dockerfile | 31 ++++++++ LICENSE | 21 ++++++ README.md | 86 +++++++++++++++++++++ build.code-workspace | 12 +++ project-trigger.yml | 19 +++++ project/build.ps1 | 27 +++++++ project/build_launcher.ps1 | 62 ++++++++++++++++ project/build_modules.ps1 | 148 +++++++++++++++++++++++++++++++++++++ project/build_server.ps1 | 90 ++++++++++++++++++++++ 11 files changed, 521 insertions(+) create mode 100644 .drone.yml create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.code-workspace create mode 100644 project-trigger.yml create mode 100644 project/build.ps1 create mode 100644 project/build_launcher.ps1 create mode 100644 project/build_modules.ps1 create mode 100644 project/build_server.ps1 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..0982a40 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,20 @@ +kind: pipeline +type: docker +name: windows-build + +platform: + os: windows + arch: amd64 + +trigger: + event: + - tag + +steps: +- name: build-and-package + image: mcr.microsoft.com/windows/servercore:ltsc2019 + commands: + - powershell -File project/build-script.ps1 + environment: + GITEA_TOKEN: + from_secret: gitea_token diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d55f9be --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "[markdown]": { + "editor.defaultFormatter": null + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..70c3973 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +## This image is configured just as the drone runner image but expected to be run locally for +## rapid development and testing. It is not intended to be used in production. + +# Start with the .NET 6.0 SDK Windows Server Core base image +FROM mcr.microsoft.com/dotnet/sdk:6.0-windowsservercore-ltsc2022 + +# Use PowerShell +SHELL ["powershell", "-Command"] + +# Install Chocolatey package manager +RUN Set-ExecutionPolicy Bypass -Scope Process -Force; \ + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; \ + iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + +# Use Chocolatey to install Node.js and Git +RUN choco install nodejs --version=20.10.0 -y +RUN choco install git -y + +# Log Versions +RUN node --version +RUN npm --version +RUN git --version + +# Copy build scripts into the container +COPY project/build.ps1 /Code/project/ +COPY project/build_server.ps1 /Code/project/ +COPY project/build_modules.ps1 /Code/project/ +COPY project/build_launcher.ps1 /Code/project/ + +# Set the working directory to /Code +WORKDIR /Code diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..32d0efa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Tyler Brownell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c958459 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# SPT Build Project - CI/CD Process + +This document outlines the Continuous Integration and Continuous Deployment (CI/CD) setup for the `SPT-AKI/Build` project, which automates the build and release process for three interconnected repositories: `SPT-AKI/Modules`, `SPT-AKI/Server`, and `SPT-AKI/Launcher`. The process is orchestrated using Drone CI with Gitea and relies on a Windows Docker runner to execute PowerShell scripts for building and packaging the projects. + +## Project Repositories + +Each of the three project repositories (`SPT-AKI/Modules`, `SPT-AKI/Server`, `SPT-AKI/Launcher`) requires a `.drone.yml` file configured to trigger a build in this `SPT-AKI/Build` repository using the Drone downstream plugin upon a new tag push (e.g., `v3.8.0`). The contents of the `.drone.yml` file can be found in `project-trigger.yml`. Note that the file must be present and named `.drone.yml` to trigger the build process. + +### Build Process + +This repository's `.drone.yml` initiates the CI/CD process by running a PowerShell script `build.ps1` on a Windows Docker runner. The PowerShell script performs the following actions: + +1. Checks if the passed in tag exists in all three project repositories. +1. Clones the tagged commits of each repository. +1. Builds each project. +1. Combines and compresses the build files into a release file. +1. Copies the release file to a web-accessible location. +1. Release notifications (creates a Gitea release, sends a Discord notification, etc.) + +## Drone Runner Configuration + +Drone CI Runner Requirements: +- Windows Server 2022 Host +- Docker Community Edition (CE) + +### Install Docker CE + +Docker CE needs to be installed (not Docker Desktop). The following steps outline the installation process for Windows Server 2022: + +To install Docker CE on Windows Server 2022, follow these steps: + +1. Open `Windows Server Manager` +1. Select `Manage` +1. Select `Add Roles and Features` +1. Click `Next` on the `Before You Begin` page +1. Select `Role-based or feature-based installation` +1. Select the name of the server where the feature will be installed and click `Next` +1. Select `Hyper-V` and click `Next` +1. Select `Containers` and click `Next` +1. Click `Install` on the `Confirm installation selections` page +1. Click `Close` on the `Installation progress` page +1. Open a PowerShell terminal (as admin) and run the following commands to install Docker CE: + +```powershell +# Download install script +Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/microsoft/Windows-Containers/Main/helpful_tools/Install-DockerCE/install-docker-ce.ps1" -o install-docker-ce.ps1 + +# Run install script +.\install-docker-ce.ps1 + +# Test Docker installation +Get-Service Docker +``` + +### Run the Runner + +Use the command below to start the Drone CI Runner. But first... + - Replace the `DRONE_RPC_HOST` value with the host of the Drone server that should be connected to for builds. + - Replace the `DRONE_RPC_SECRET` value with the Drone server secret. + - Replace the `DRONE_RUNNER_NAME` value with a unique name for the runner. + - Replace the `DRONE_UI_PASSWORD` value with a password to access the web runner UI. + - Adjust `DRONE_RUNNER_CAPACITY` to the number of builds that should be allowed to run at once. + +```powershell +docker run --detach --volume=//./pipe/docker_engine://./pipe/docker_engine --env=DRONE_RPC_PROTO=https --env=DRONE_RPC_HOST=example.com --env=DRONE_RPC_SECRET=secret --env=DRONE_RUNNER_CAPACITY=2 --env=DRONE_RUNNER_NAME=example --env=DRONE_UI_DISABLE=false --env=DRONE_UI_USERNAME=root --env=DRONE_UI_PASSWORD=password --publish=3000:3000 --restart=always --name=runner drone/drone-runner-docker:latest +``` + +## Module Requirements + +To build the Modules project, a link to a private repository is required for the build process. The link is stored as a secret in the Drone CI/CD environment. The secret is named `MODULE_DOMAIN` and is used to download files from the private repository. + +## Testing Build Process + +To test the build process, the following commands can be used to build the Docker image and run the container on the Windows Server 2022 host. Be sure to replace the `MODULE_DOMAIN` environment variable with a valid value. + +```powershell +# Clone the repository and move inside the project directory +git clone https://dev.sp-tarkov.com/SPT-AKI/Build.git C:\Code\Build +cd C:\Code\Build + +# Build the Docker image +docker build -t spt-build-environment . + +# Test the build process +docker run --rm -v "C:\Code\Build:C:\Code" -e MODULE_DOMAIN="https://example.com" spt-build-environment powershell -File C:\Code\project\build.ps1 +``` diff --git a/build.code-workspace b/build.code-workspace new file mode 100644 index 0000000..366c758 --- /dev/null +++ b/build.code-workspace @@ -0,0 +1,12 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "[markdown]": { + "editor.defaultFormatter": null + } + } +} diff --git a/project-trigger.yml b/project-trigger.yml new file mode 100644 index 0000000..a23581b --- /dev/null +++ b/project-trigger.yml @@ -0,0 +1,19 @@ +kind: pipeline +type: docker +name: trigger-spt-build + +trigger: + event: + - tag + +steps: +- name: trigger-build-check + image: plugins/downstream + settings: + repositories: + - SPT-AKI/Build + server: https://drone.sp-tarkov.com + token: + from_secret: gitea_token + when: + event: tag diff --git a/project/build.ps1 b/project/build.ps1 new file mode 100644 index 0000000..f9608ce --- /dev/null +++ b/project/build.ps1 @@ -0,0 +1,27 @@ +Write-Output " » Beginning SPT Build Process" + +# Check for required environment variables +if ([string]::IsNullOrWhiteSpace($env:MODULE_DOMAIN)) { + Write-Output " » FAIL: The MODULE_DOMAIN environment variable can not be empty." + exit 1 # Fail the build +} +$MODULE_DOMAIN = $env:MODULE_DOMAIN + +# TODO: This is dynamic, based on the incoming commit information. +$RELEASE_TAG = "3.8.0-BE" + +# TODO: Validate that the tag exists in all three repositories before continuing the build. + +#$BEPINEX_RELEASE = "https://github.com/BepInEx/BepInEx/releases/download/v5.4.21/BepInEx_x64_5.4.21.0.zip" + +$OUTPUT_DIR = ".\output" + +if (Test-Path -Path $OUTPUT_DIR) { + Write-Output " » Removing Previous Output Directory" + Remove-Item -Recurse -Force $OUTPUT_DIR +} + +# Build the projects +pwsh .\project\build_server.ps1 $RELEASE_TAG +pwsh .\project\build_modules.ps1 $RELEASE_TAG $MODULE_DOMAIN +pwsh .\project\build_launcher.ps1 $RELEASE_TAG diff --git a/project/build_launcher.ps1 b/project/build_launcher.ps1 new file mode 100644 index 0000000..be9582d --- /dev/null +++ b/project/build_launcher.ps1 @@ -0,0 +1,62 @@ +Param( + [Parameter(Mandatory = $true)] + [string] $RELEASE_TAG +) + +Write-Output " » Building Launcher" + +# Set directories +$DIR_ABS = (Get-Location).Path +$DIR = "$DIR_ABS\Launcher" +$DIR_PROJECT = "$DIR\project" +$DIR_BUILD = "$DIR_PROJECT\build" + +# Remove the output folder if it already exists +if (Test-Path -Path $DIR) { + Write-Output " » Removing Previous Build Directory" + Remove-Item -Recurse -Force $DIR +} + +# Pull down the server project, at the tag, with no history +Write-Output " » Cloning Launcher Project" +$REPO = "https://dev.sp-tarkov.com/SPT-AKI/Launcher.git" +try { + $processInfo = New-Object System.Diagnostics.ProcessStartInfo + $processInfo.FileName = "git" + $processInfo.Arguments = "clone $REPO --branch $RELEASE_TAG --depth 1 `"$DIR`"" + $processInfo.RedirectStandardError = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.WaitForExit() + + $stdout = $process.StandardOutput.ReadToEnd() + $stderr = $process.StandardError.ReadToEnd() + + Write-Output $stdout + if ($process.ExitCode -ne 0) { + throw "git clone command failed with exit code $($process.ExitCode). Output: $stderr" + } +} +catch { + $errorMessage = " » FAIL: Error Executing git clone: $_" + Write-Error $errorMessage + exit 1 # Fail the build +} + +# Create the any necessary subdirectories +New-Item -Path $DIR_BUILD -ItemType Directory -Force + +Set-Location $DIR_PROJECT + +Write-Output " » Installing .NET Dependencies" +dotnet restore + +Write-Output " » Running Build Task" +dotnet build + +Write-Output "⚡ Launcher Built ⚡" diff --git a/project/build_modules.ps1 b/project/build_modules.ps1 new file mode 100644 index 0000000..123fbb9 --- /dev/null +++ b/project/build_modules.ps1 @@ -0,0 +1,148 @@ +Param( + [Parameter(Mandatory = $true)] + [string] $RELEASE_TAG, + + [Parameter(Mandatory = $true)] + [string] $MODULE_DOMAIN +) + +Write-Output " » Building Modules" + +# Set directorys +$DIR_ABS = (Get-Location).Path +$DIR = "$DIR_ABS\Modules" +$DIR_PROJECT = "$DIR\project" +$DIR_BUILD = "$DIR_PROJECT\build" +$DIR_MANAGED = "$DIR_PROJECT\Shared\Managed" + +# Remove the output folder if it already exists +if (Test-Path -Path $DIR) { + Write-Output " » Removing Previous Build Directory" + Remove-Item -Recurse -Force $DIR +} + +# Pull down the server project, at the tag, with no history +Write-Output " » Cloning Modules Project" +$REPO = "https://dev.sp-tarkov.com/SPT-AKI/Modules.git" +try { + $processInfo = New-Object System.Diagnostics.ProcessStartInfo + $processInfo.FileName = "git" + $processInfo.Arguments = "clone $REPO --branch $RELEASE_TAG --depth 1 `"$DIR`"" + $processInfo.RedirectStandardError = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.WaitForExit() + + $stdout = $process.StandardOutput.ReadToEnd() + $stderr = $process.StandardError.ReadToEnd() + + Write-Output $stdout + if ($process.ExitCode -ne 0) { + throw "git clone command failed with exit code $($process.ExitCode). Output: $stderr" + } +} +catch { + $errorMessage = " » FAIL: Error Executing git clone: $_" + Write-Error $errorMessage + exit 1 # Fail the build +} + +# Create the any necessary subdirectories +New-Item -Path $DIR_BUILD -ItemType Directory -Force +New-Item -Path $DIR_MANAGED -ItemType Directory -Force + +Set-Location $DIR + +Write-Output " » Fetching Required Client Version" +$coreJsonPath = "$DIR_ABS\Server\project\build\Aki_Data\Server\configs\core.json" +if (-not (Test-Path -Path $coreJsonPath) -or (Get-Item -Path $coreJsonPath).Length -eq 0) { + Write-Output " » FAIL: core.json does not exist or is empty." + exit 1 # Fail the build +} +try { + # Attempt to read and parse the core.json file + $RELEASE_METADATA_RAW = Get-Content -Path $coreJsonPath -ErrorAction Stop + $RELEASE_METADATA = $RELEASE_METADATA_RAW | ConvertFrom-Json -ErrorAction Stop + $CLIENT_VERSION = $RELEASE_METADATA.compatibleTarkovVersion.Split('.') | Select-Object -Last 1 + + # Check if the $CLIENT_VERSION is valid + if ([string]::IsNullOrWhiteSpace($CLIENT_VERSION)) { + throw "Invalid or missing 'compatibleTarkovVersion' in core.json." + } +} +catch { + Write-Error " » FAIL: Error fetching or parsing core.json: $_" + exit 1 # Fail the build +} +Write-Output " » Client Version: $CLIENT_VERSION fetched successfully." + +# Download the module files +Write-Output " » Downloading Client Modules" +$DOWNLOAD_PATH = "$DIR_MANAGED\$CLIENT_VERSION.zip" +$DOWNLOAD_URL = "$MODULE_DOMAIN/$CLIENT_VERSION.zip" +try { + Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $DOWNLOAD_PATH -UseBasicParsing -ErrorAction Stop + if (-not (Test-Path -Path $DOWNLOAD_PATH) -or (Get-Item -Path $DOWNLOAD_PATH).Length -eq 0) { + throw "The module download does not exist or is empty." + } +} +catch { + Write-Error " » FAIL: Unable to download the module. Error: $_" + exit 1 # Fail the build +} +Write-Output " » Download successful: $DOWNLOAD_PATH" + +Write-Output " » Extracting Client Modules" +try { + Expand-Archive -Path $DOWNLOAD_PATH -DestinationPath $DIR_MANAGED -Force -ErrorAction Stop + Write-Output " » Client Modules extracted to $DIR_MANAGED" +} +catch { + Write-Error " » FAIL: Error extracting client modules: $_" + exit 1 # Fail the build +} + +# Delete the modules archive now that it's been uncompressed +try { + Remove-Item -Path $DOWNLOAD_PATH -Force -ErrorAction Stop + Write-Output " » Client Modules Archive Deleted" +} +catch { + Write-Warning " » Failed to delete ZIP file: $_" + exit 1 # Fail the build +} + +Set-Location $DIR_PROJECT + +Write-Output " » Installing .NET Dependencies" +try { + $restoreResult = dotnet restore + if ($LASTEXITCODE -ne 0) { + throw "dotnet restore failed with exit code $LASTEXITCODE" + } + Write-Output $restoreResult +} +catch { + Write-Error " » FAIL: Error executing dotnet restore: $_" + exit 1 # Fail the build +} + +Write-Output " » Running Build Task" +try { + $buildResult = dotnet build + if ($LASTEXITCODE -ne 0) { + throw "dotnet build failed with exit code $LASTEXITCODE" + } + Write-Output $buildResult +} +catch { + Write-Error " » FAIL: Error executing dotnet build: $_" + exit 1 # Fail the build +} + +Write-Output "⚡ Modules Built ⚡" diff --git a/project/build_server.ps1 b/project/build_server.ps1 new file mode 100644 index 0000000..0736320 --- /dev/null +++ b/project/build_server.ps1 @@ -0,0 +1,90 @@ +Param( + [Parameter(Mandatory = $true)] + [string] $RELEASE_TAG +) + +Write-Output " » Building Server" + +# Set directorys +$DIR_ABS = (Get-Location).Path +$DIR = "$DIR_ABS\Server" +$DIR_PROJECT = "$DIR\project" +$DIR_BUILD = "$DIR_PROJECT\build" + +# Remove the output folder if it already exists +if (Test-Path -Path $DIR) { + Write-Output " » Removing Previous Build Directory" + Remove-Item -Recurse -Force $DIR +} + +# Pull down the server project, at the tag, with no history +Write-Output " » Cloning Server Project" +$REPO = "https://dev.sp-tarkov.com/SPT-AKI/Server.git" +try { + $processInfo = New-Object System.Diagnostics.ProcessStartInfo + $processInfo.FileName = "git" + $processInfo.Arguments = "clone $REPO --branch $RELEASE_TAG --depth 1 `"$DIR`"" + $processInfo.RedirectStandardError = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.WaitForExit() + + $stdout = $process.StandardOutput.ReadToEnd() + $stderr = $process.StandardError.ReadToEnd() + + Write-Output $stdout + if ($process.ExitCode -ne 0) { + throw "git clone command failed with exit code $($process.ExitCode). Output: $stderr" + } +} +catch { + $errorMessage = " » FAIL: Error Executing git clone: $_" + Write-Error $errorMessage + exit 1 # Fail the build +} + +# Create the any necessary subdirectories +New-Item -Path $DIR_BUILD -ItemType Directory -Force + +# Ensure we are in the correct directory +Set-Location $DIR + +# Pull down the LFS files +git lfs fetch +git lfs pull + +# Set the build type based on whether the tag matches the release regex or not. +# A tag in the format of `1.2.3` will be considered a release build, while anything else will be considered debug. +$BUILD_TYPE_REGEX = '^(v?\d+\.\d+\.\d+)$' +if ($RELEASE_TAG -match $BUILD_TYPE_REGEX) { + $BUILD_TYPE = "release" +} +else { + $BUILD_TYPE = "debug" +} +Write-Output " » Build Type: $BUILD_TYPE" + +Set-Location $DIR_PROJECT + +Write-Output " » Installing NPM Dependencies" +try { + npm install +} catch { + Write-Error " » npm install failed: $_" + exit 1 +} + +Write-Output " » Running Build Task" +try { + npm run build:$BUILD_TYPE +} catch { + Write-Error " » npm run build failed: $_" + exit 1 +} + +Write-Output "⚡ Server Built ⚡"