Publish Windows and Linux servers together
This commit is contained in:
+2
-1
@@ -75,7 +75,8 @@ bundle; its environment variables enable Developer ID signing and notarization.
|
|||||||
|
|
||||||
## Server publishing
|
## Server publishing
|
||||||
|
|
||||||
`dotnet/publish-server.ps1` produces locked self-contained Windows and Linux artifacts.
|
`dotnet/publish-server.ps1` produces locked self-contained Windows and Linux artifacts in one
|
||||||
|
command. Pass `-Runtime win-x64` or `-Runtime linux-x64` to publish only one target.
|
||||||
`Dockerfile`, Compose configuration, and systemd packaging use the managed server. Validate a
|
`Dockerfile`, Compose configuration, and systemd packaging use the managed server. Validate a
|
||||||
published binary with its TLS `--health-check`, preferably including `--expect-fingerprint`.
|
published binary with its TLS `--health-check`, preferably including `--expect-fingerprint`.
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -6,7 +6,9 @@ The .NET server preserves protocol v2 and the native schema/credentials. Publish
|
|||||||
self-contained executable (no installed .NET runtime required):
|
self-contained executable (no installed .NET runtime required):
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
# Publishes both win-x64 and linux-x64 by default.
|
||||||
./dotnet/publish-server.ps1
|
./dotnet/publish-server.ps1
|
||||||
|
# Publish just one target when needed.
|
||||||
./dotnet/publish-server.ps1 -Runtime linux-x64
|
./dotnet/publish-server.ps1 -Runtime linux-x64
|
||||||
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe --help
|
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe --help
|
||||||
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe account add Operator --admin --data-dir ./voicecat-data
|
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe account add Operator --admin --data-dir ./voicecat-data
|
||||||
@@ -52,8 +54,9 @@ backoff grows from one to thirty seconds. Success clears backoff but does not re
|
|||||||
tokens. State is bounded to 4096 keys; idle entries retire after ten minutes when full.
|
tokens. State is bounded to 4096 keys; idle entries retire after ten minutes when full.
|
||||||
Throttle and credential failures share the generic auth error. Limits are process-local.
|
Throttle and credential failures share the generic auth error. Limits are process-local.
|
||||||
|
|
||||||
The publish script uses separate per-RID lock files so deployment and development restore
|
The publish script produces Windows x64 and Linux x64 self-contained outputs in one command
|
||||||
graphs remain reproducible. Windows x64 and Linux x64 self-contained outputs are checked.
|
by default. It uses separate per-RID lock files so deployment and development restore graphs
|
||||||
|
remain reproducible. Pass `-Runtime` to publish only one target.
|
||||||
The Linux smoke starts the published ELF, validates TLS health and the pin, checks mode 0700
|
The Linux smoke starts the published ELF, validates TLS health and the pin, checks mode 0700
|
||||||
data creation, sends SIGTERM and requires a clean exit:
|
data creation, sends SIGTERM and requires a clean exit:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
param(
|
param(
|
||||||
[string]$Runtime = 'win-x64',
|
[string[]]$Runtime = @('win-x64', 'linux-x64'),
|
||||||
[string]$Output = "$PSScriptRoot/artifacts/server/$Runtime"
|
[string]$Output
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
dotnet publish "$PSScriptRoot/src/VoiceCat.Server/VoiceCat.Server.csproj" -c Release -r $Runtime --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false -p:RestoreLockedMode=true "-p:NuGetLockFilePath=packages.publish.$Runtime.lock.json" -o $Output
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'Managed server publish failed.' }
|
if ($Runtime.Count -eq 0) { throw 'At least one runtime must be specified.' }
|
||||||
|
if ($Output -and $Runtime.Count -ne 1) { throw '-Output can only be used when publishing one runtime.' }
|
||||||
|
|
||||||
|
foreach ($targetRuntime in $Runtime) {
|
||||||
|
$targetOutput = if ($Output) { $Output } else { "$PSScriptRoot/artifacts/server/$targetRuntime" }
|
||||||
|
if (-not $PSCmdlet.ShouldProcess($targetOutput, "Publish VoiceCat.Server for $targetRuntime")) { continue }
|
||||||
|
|
||||||
|
dotnet publish "$PSScriptRoot/src/VoiceCat.Server/VoiceCat.Server.csproj" -c Release -r $targetRuntime --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false -p:RestoreLockedMode=true "-p:NuGetLockFilePath=packages.publish.$targetRuntime.lock.json" -o $targetOutput
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Managed server publish failed for $targetRuntime." }
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.7, )",
|
"requested": "[10.0.12, )",
|
||||||
"resolved": "10.0.7",
|
"resolved": "10.0.12",
|
||||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||||
},
|
},
|
||||||
"Google.Protobuf": {
|
"Google.Protobuf": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.7, )",
|
"requested": "[10.0.12, )",
|
||||||
"resolved": "10.0.7",
|
"resolved": "10.0.12",
|
||||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||||
},
|
},
|
||||||
"Google.Protobuf": {
|
"Google.Protobuf": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.7, )",
|
"requested": "[10.0.12, )",
|
||||||
"resolved": "10.0.7",
|
"resolved": "10.0.12",
|
||||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net10.0/linux-x64": {}
|
"net10.0/linux-x64": {}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.7, )",
|
"requested": "[10.0.12, )",
|
||||||
"resolved": "10.0.7",
|
"resolved": "10.0.12",
|
||||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net10.0/win-x64": {}
|
"net10.0/win-x64": {}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.7, )",
|
"requested": "[10.0.12, )",
|
||||||
"resolved": "10.0.7",
|
"resolved": "10.0.12",
|
||||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||||
},
|
},
|
||||||
"SourceGear.sqlite3": {
|
"SourceGear.sqlite3": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.7, )",
|
"requested": "[10.0.12, )",
|
||||||
"resolved": "10.0.7",
|
"resolved": "10.0.12",
|
||||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||||
},
|
},
|
||||||
"SourceGear.sqlite3": {
|
"SourceGear.sqlite3": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace VoiceCat.Tests;
|
||||||
|
|
||||||
|
public class PublishServerScriptTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task DefaultPublishTargetsWindowsAndLinuxWhileRuntimeCanSelectOne()
|
||||||
|
{
|
||||||
|
string script = Path.Combine(FindRoot(), "dotnet", "publish-server.ps1");
|
||||||
|
|
||||||
|
string allTargets = await RunWhatIf(script);
|
||||||
|
Assert.Contains("win-x64", allTargets);
|
||||||
|
Assert.Contains("linux-x64", allTargets);
|
||||||
|
|
||||||
|
string linuxOnly = await RunWhatIf(script, "-Runtime", "linux-x64");
|
||||||
|
Assert.Contains("linux-x64", linuxOnly);
|
||||||
|
Assert.DoesNotContain("win-x64", linuxOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> RunWhatIf(string script, params string[] arguments)
|
||||||
|
{
|
||||||
|
string powerShell = OperatingSystem.IsWindows() ? "powershell.exe" : "pwsh";
|
||||||
|
var start = new ProcessStartInfo(powerShell)
|
||||||
|
{
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
};
|
||||||
|
foreach (string argument in new[] { "-NoProfile", "-NonInteractive", "-File", script, "-WhatIf" }.Concat(arguments))
|
||||||
|
start.ArgumentList.Add(argument);
|
||||||
|
|
||||||
|
using Process process = Process.Start(start) ?? throw new InvalidOperationException("Could not start PowerShell.");
|
||||||
|
Task<string> stdout = process.StandardOutput.ReadToEndAsync();
|
||||||
|
Task<string> stderr = process.StandardError.ReadToEndAsync();
|
||||||
|
await process.WaitForExitAsync();
|
||||||
|
string output = await stdout + await stderr;
|
||||||
|
Assert.True(process.ExitCode == 0, output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FindRoot()
|
||||||
|
{
|
||||||
|
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
||||||
|
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "CMakePresets.json")))
|
||||||
|
directory = directory.Parent;
|
||||||
|
return directory?.FullName ?? throw new DirectoryNotFoundException("Repository root not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user