Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ on:
env:
TOOL_PROJ_PATH: ./src/ModVerify.CliApp/ModVerify.CliApp.csproj
CREATOR_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj
SIGNER_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/ApplicationManifestSigner/ApplicationManifestSigner.csproj
UPLOADER_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj
TOOL_EXE: ModVerify.exe
UPDATER_EXE: AnakinRaW.ExternalUpdater.exe
MANIFEST_CREATOR: AnakinRaW.ApplicationManifestCreator.dll
MANIFEST_SIGNER: AnakinRaW.ApplicationManifestSigner.dll
SFTP_UPLOADER: AnakinRaW.FtpUploader.dll
ORIGIN_BASE: https://republicatwar.com/downloads/ModVerify
ORIGIN_BASE_PART: downloads/ModVerify/
Expand Down Expand Up @@ -59,9 +61,12 @@ jobs:

deploy:
name: Deploy
# Deploy on push to main or manual trigger
# Stable deploys are gated to 'main'. Non-stable channels (beta, canary, etc.) can be
# workflow_dispatched from any branch.
if: |
(github.ref == 'refs/heads/main' && github.event_name == 'push') || github.event_name == 'workflow_dispatch'
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' &&
(github.event.inputs.branch != 'stable' || github.ref == 'refs/heads/main'))
needs: [pack]
runs-on: ubuntu-latest
steps:
Expand All @@ -70,6 +75,26 @@ jobs:
with:
fetch-depth: 0
submodules: recursive
- name: Verify embedded trust cert
shell: pwsh
run: |
$certPath = "src/ModVerify.CliApp/Resources/Certs/modverify-trust.cer"
if (-not (Test-Path $certPath)) {
Write-Error "$certPath is missing. Generate the production trust cert per docs/update-signing-setup.md and commit it before releasing."
exit 1
}
try {
$bytes = [IO.File]::ReadAllBytes($certPath)
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)
} catch {
Write-Error "Failed to load $certPath as an X.509 certificate: $_"
exit 1
}
if ($cert.HasPrivateKey) {
Write-Error "$certPath contains a private key. Only the public DER (.cer) is allowed; embedding a PFX would ship the signing key to every consumer."
exit 1
}
Write-Host "OK: $certPath is public-only ($($cert.Subject))."
- uses: actions/download-artifact@v8
with:
name: Binary Releases
Expand All @@ -82,6 +107,8 @@ jobs:
dotnet-version: 10.0.x
- name: Build Creator
run: dotnet build ${{env.CREATOR_PROJ_PATH}} --configuration Release --output ./dev
- name: Build Signer
run: dotnet build ${{env.SIGNER_PROJ_PATH}} --configuration Release --output ./dev
- name: Build Uploader
run: dotnet build ${{env.UPLOADER_PROJ_PATH}} --configuration Release --output ./dev
- name: Create binaries directory
Expand All @@ -92,6 +119,14 @@ jobs:
cp ./releases/net481/${{env.UPDATER_EXE}} ./deploy/
- name: Create Manifest
run: dotnet ./dev/${{env.MANIFEST_CREATOR}} -a deploy/${{env.TOOL_EXE}} --appDataFiles deploy/${{env.UPDATER_EXE}} --origin ${{env.ORIGIN_BASE}} -o ./deploy -b ${{env.BRANCH_NAME}}
- name: Decode signing pfx
shell: bash
run: echo "${{ secrets.UPDATER_SIGNING_PFX_B64 }}" | base64 -d > $RUNNER_TEMP/signing.pfx
- name: Sign Manifest
run: dotnet ./dev/${{env.MANIFEST_SIGNER}} --manifest ./deploy/manifest.json --pfx $RUNNER_TEMP/signing.pfx --password "${{ secrets.UPDATER_SIGNING_PFX_PASSWORD }}"
- name: Wipe pfx
if: always()
run: rm -f $RUNNER_TEMP/signing.pfx
- name: Upload Build
run: dotnet ./dev/${{env.SFTP_UPLOADER}} ftp --host $host --port $port -u ${{secrets.SFTP_USER}} -p ${{secrets.SFTP_PASSWORD}} --base $base_path -s $source
env:
Expand Down
7 changes: 7 additions & 0 deletions ModVerify.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
</Folder>
<Folder Name="/ModdingToolBase/DeployTools/">
<Project Path="modules/ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj" />
<Project Path="modules/ModdingToolBase/src/AnakinApps/ApplicationManifestSigner/ApplicationManifestSigner.csproj" />
<Project Path="modules/ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj" />
<Project Path="modules/ModdingToolBase/test/AnakinApps/ApplicationManifestSigner.Test/ApplicationManifestSigner.Test.csproj" />
</Folder>
<Folder Name="/ModdingToolBase/UpdateFrameworks/">
<Project Path="modules/ModdingToolBase/src/Updater/AppUpdaterFramework.Attributes/AppUpdaterFramework.Attributes.csproj" />
Expand All @@ -16,6 +18,11 @@
<Project Path="modules/ModdingToolBase/src/Updater/ExternalUpdater.App/ExternalUpdater.App.csproj" />
<Project Path="modules/ModdingToolBase/src/Updater/ExternalUpdater.Core/ExternalUpdater.Core.csproj" />
</Folder>
<Folder Name="/ModdingToolBase/UpdateFrameworks/test/">
<Project Path="modules/ModdingToolBase/src/Updater/AppUpdaterFramework.Signing.Testing/AppUpdaterFramework.Signing.Testing.csproj" />
<Project Path="modules/ModdingToolBase/test/Updater/AppUpdaterFramework.Manifest.Test/AppUpdaterFramework.Manifest.Test.csproj" />
<Project Path="modules/ModdingToolBase/test/Updater/AppUpdaterFramework.Test/AppUpdaterFramework.Test.csproj" />
</Folder>
<Folder Name="/PetroglyphTools/">
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem.Test/PG.StarWarsGame.Engine.FileSystem.Test.csproj" />
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/PG.StarWarsGame.Engine.FileSystem.csproj" />
Expand Down
99 changes: 88 additions & 11 deletions deploy-local.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Local deployment script for ModVerify to test the update feature.
# This script builds the application, creates an update manifest, and "deploys" it to a local directory.
# This script builds the application twice at different versions, creates an update manifest
# for the newer one, and stages an "installed" copy of the older one — so triggering the
# update flow against the local server actually finds an update.

param(
# Version baked into the "already installed" copy. Must be lower than $ServerVersion
# so the updater treats the server build as newer.
[string]$InstalledVersion = "0.0.1-local",

# Version baked into the build that ends up on the local "server" / in the manifest.
[string]$ServerVersion = "0.0.2-local"
)

$ErrorActionPreference = "Stop"

Expand All @@ -13,28 +24,77 @@ $installDir = Join-Path $deployRoot "install"

$toolProj = Join-Path $root "src\ModVerify.CliApp\ModVerify.CliApp.csproj"
$creatorProj = Join-Path $root "modules\ModdingToolBase\src\AnakinApps\ApplicationManifestCreator\ApplicationManifestCreator.csproj"
$signerProj = Join-Path $root "modules\ModdingToolBase\src\AnakinApps\ApplicationManifestSigner\ApplicationManifestSigner.csproj"
$uploaderProj = Join-Path $root "modules\ModdingToolBase\src\AnakinApps\FtpUploader\FtpUploader.csproj"

$toolExe = "ModVerify.exe"
$updaterExe = "AnakinRaW.ExternalUpdater.exe"
$manifestCreatorDll = "AnakinRaW.ApplicationManifestCreator.dll"
$manifestSignerDll = "AnakinRaW.ApplicationManifestSigner.dll"
$uploaderDll = "AnakinRaW.FtpUploader.dll"

$devPfx = Join-Path $deployRoot "dev-signing.pfx"
$devCer = Join-Path $deployRoot "dev-trust.cer"
$devPwd = "devpass"

$versionJsonPath = Join-Path $root "version.json"
$versionJsonBackup = [IO.File]::ReadAllText($versionJsonPath)

function Set-NbgvVersion {
param([string]$Version)
$json = $versionJsonBackup | ConvertFrom-Json
$json.version = $Version
# publicReleaseRefSpec defaults the build to non-public; clearing it gives us a clean
# "X.Y.Z" InformationalVersion locally without the +gitHash height suffix making
# comparisons noisier than they need to be.
if ($json.PSObject.Properties.Name -contains 'publicReleaseRefSpec') {
$json.publicReleaseRefSpec = @()
}
($json | ConvertTo-Json -Depth 32) | Set-Content -Path $versionJsonPath -Encoding UTF8
}

try {

# 1. Clean and Create directories
if (Test-Path $deployRoot) { Remove-Item -Recurse -Force $deployRoot }
New-Item -ItemType Directory -Path $stagingDir | Out-Null
New-Item -ItemType Directory -Path $serverDir | Out-Null
New-Item -ItemType Directory -Path $installDir | Out-Null

Write-Host "--- Building ModVerify (net481) ---" -ForegroundColor Cyan
dotnet build $toolProj --configuration Release -f net481 --output "$deployRoot\bin\tool" /p:DebugType=None /p:DebugSymbols=false
Write-Host "--- Building ModVerify (net481) @ installed v$InstalledVersion ---" -ForegroundColor Cyan
Set-NbgvVersion -Version $InstalledVersion
dotnet build $toolProj --configuration Release -f net481 --output "$deployRoot\bin\install" /p:DebugType=None /p:DebugSymbols=false /p:LocalDeploy=true

Write-Host "--- Building ModVerify (net481) @ server v$ServerVersion ---" -ForegroundColor Cyan
Set-NbgvVersion -Version $ServerVersion
dotnet build $toolProj --configuration Release -f net481 --output "$deployRoot\bin\tool" /p:DebugType=None /p:DebugSymbols=false /p:LocalDeploy=true

Write-Host "--- Building Manifest Creator ---" -ForegroundColor Cyan
dotnet build $creatorProj --configuration Release --output "$deployRoot\bin\creator"

Write-Host "--- Building Manifest Signer ---" -ForegroundColor Cyan
dotnet build $signerProj --configuration Release --output "$deployRoot\bin\signer"

Write-Host "--- Building Local Uploader ---" -ForegroundColor Cyan
dotnet build $uploaderProj --configuration Release --output "$deployRoot\bin\uploader"

Write-Host "--- Generating dev signing cert ---" -ForegroundColor Cyan
$curve = [System.Security.Cryptography.ECCurve]::CreateFromFriendlyName("nistP256")
$ecdsa = [System.Security.Cryptography.ECDsa]::Create($curve)
$req = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new(
"CN=ModVerify Dev Signing",
$ecdsa,
[System.Security.Cryptography.HashAlgorithmName]::SHA256)
$cert = $req.CreateSelfSigned(
[DateTimeOffset]::UtcNow.AddDays(-1),
[DateTimeOffset]::UtcNow.AddYears(10))
[IO.File]::WriteAllBytes($devPfx, $cert.Export(
[System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $devPwd))
[IO.File]::WriteAllBytes($devCer, $cert.Export(
[System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
$cert.Dispose()
$ecdsa.Dispose()

# 2. Prepare staging
Write-Host "--- Preparing Staging ---" -ForegroundColor Cyan
Copy-Item "$deployRoot\bin\tool\$toolExe" $stagingDir
Expand All @@ -56,20 +116,37 @@ dotnet "$deployRoot\bin\creator\$manifestCreatorDll" `
-o "$stagingDir" `
-b "beta"

Write-Host "--- Signing Manifest ---" -ForegroundColor Cyan
dotnet "$deployRoot\bin\signer\$manifestSignerDll" `
--manifest "$stagingDir\manifest.json" `
--pfx $devPfx `
--password $devPwd

# 4. "Deploy" to server using the local uploader
Write-Host "--- Deploying to Local Server ---" -ForegroundColor Cyan
dotnet "$deployRoot\bin\uploader\$uploaderDll" local --base "$serverDir" --source "$stagingDir"

# 5. Setup a "test" installation
Write-Host "--- Setting up Test Installation ---" -ForegroundColor Cyan
Copy-Item "$deployRoot\bin\tool\*" $installDir -Recurse
# 5. Setup a "test" installation — uses the older-version build so the updater sees the
# staged server build as an upgrade.
Write-Host "--- Setting up Test Installation (v$InstalledVersion) ---" -ForegroundColor Cyan
Copy-Item "$deployRoot\bin\install\*" $installDir -Recurse

Write-Host "`nLocal deployment complete!" -ForegroundColor Green
Write-Host "Server directory: $serverDir"
Write-Host "Installed version: $InstalledVersion"
Write-Host "Server version: $ServerVersion"
Write-Host "Server directory: $serverDir"
Write-Host "Install directory: $installDir"
Write-Host "`nTo test the update:"
Write-Host "1. (Optional) Modify the version in version.json and run this script again to 'push' a new version to the local server."
Write-Host "2. Run ModVerify from the install directory with the following command:"
Write-Host "1. Run ModVerify from the install directory with the following command:"
Write-Host " cd '$installDir'"
Write-Host " .\ModVerify.exe updateApplication --updateManifestUrl '$serverUri'"
Write-Host "`n Note: You can also specify a different branch using --updateBranch if needed."
Write-Host " .\ModVerify.exe updateApplication --updateBranch beta --updateServerUrl '$serverUri'"
Write-Host "`n Note: --updateServerUrl takes a server base URL and resolves to <server>/<branch>/manifest.json."
Write-Host " Use --updateManifestUrl instead if you want to point directly at a full manifest URL."
Write-Host "`n2. To re-test, just rerun this script — every run produces v$InstalledVersion installed against v$ServerVersion on the server."
Write-Host " Override with -InstalledVersion / -ServerVersion to exercise other version transitions."

}
finally {
# Always restore version.json verbatim (bytes-in == bytes-out), even if a build step above failed.
[IO.File]::WriteAllText($versionJsonPath, $versionJsonBackup)
}
Loading
Loading