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
|
||||
|
||||
`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
|
||||
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):
|
||||
|
||||
```powershell
|
||||
# Publishes both win-x64 and linux-x64 by default.
|
||||
./dotnet/publish-server.ps1
|
||||
# Publish just one target when needed.
|
||||
./dotnet/publish-server.ps1 -Runtime linux-x64
|
||||
./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
|
||||
@@ -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.
|
||||
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
|
||||
graphs remain reproducible. Windows x64 and Linux x64 self-contained outputs are checked.
|
||||
The publish script produces Windows x64 and Linux x64 self-contained outputs in one command
|
||||
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
|
||||
data creation, sends SIGTERM and requires a clean exit:
|
||||
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[string]$Runtime = 'win-x64',
|
||||
[string]$Output = "$PSScriptRoot/artifacts/server/$Runtime"
|
||||
[string[]]$Runtime = @('win-x64', 'linux-x64'),
|
||||
[string]$Output
|
||||
)
|
||||
$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": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
||||
"requested": "[10.0.12, )",
|
||||
"resolved": "10.0.12",
|
||||
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||
},
|
||||
"Google.Protobuf": {
|
||||
"type": "Transitive",
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
||||
"requested": "[10.0.12, )",
|
||||
"resolved": "10.0.12",
|
||||
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||
},
|
||||
"Google.Protobuf": {
|
||||
"type": "Transitive",
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
||||
"requested": "[10.0.12, )",
|
||||
"resolved": "10.0.12",
|
||||
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||
}
|
||||
},
|
||||
"net10.0/linux-x64": {}
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
||||
"requested": "[10.0.12, )",
|
||||
"resolved": "10.0.12",
|
||||
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||
}
|
||||
},
|
||||
"net10.0/win-x64": {}
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
||||
"requested": "[10.0.12, )",
|
||||
"resolved": "10.0.12",
|
||||
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||
},
|
||||
"SourceGear.sqlite3": {
|
||||
"type": "Direct",
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
|
||||
"requested": "[10.0.12, )",
|
||||
"resolved": "10.0.12",
|
||||
"contentHash": "xi+BDjFpW+Sb+MHFHaH6Y/gV9I8BluFwRXc1QyCdoZbIK26eNiBeFuMTe/FMwc33G1wdHCyDg7CVTmb8OdQrMQ=="
|
||||
},
|
||||
"SourceGear.sqlite3": {
|
||||
"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