MSI upgrade/data preservation + service data dir + CI toolchain fixes #4

Merged
gsadmin merged 3 commits from development into main 2026-09-02 13:28:07 +00:00
4 changed files with 71 additions and 15 deletions
+5 -1
View File
@@ -33,6 +33,10 @@ jobs:
packages: write
env:
# Use exactly the toolchain provided by setup-go; never auto-download a
# different Go toolchain (a `go install ...@latest` otherwise pulled a
# newer toolchain that mismatched and broke the build).
GOTOOLCHAIN: local
# Optional EXTERNAL registry override. Leave these unset to publish to the
# Gitea instance's own built-in container registry (the default below).
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }}
@@ -147,7 +151,7 @@ jobs:
VHM: ${{ steps.ver.outputs.vhm }}
run: |
set -euo pipefail
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@v1.7.0
GV="$(go env GOPATH)/bin/goversioninfo"
"$GV" -64 -ver-major="$VY" -ver-minor="$VM" -ver-patch="$VD" -ver-build="$VHM" \
-product-version="$VY.$VM.$VD" -o cmd/orchestrad/resource_windows_amd64.syso versioninfo.json
+12
View File
@@ -32,6 +32,18 @@ type program struct {
// Start is called by the service manager (or by Run when interactive). It
// launches the server without blocking.
func (p *program) Start(s service.Service) error {
// When launched by a service manager the process inherits the manager's
// working directory (e.g. C:\Windows\System32 on Windows), which would put a
// relative ORCHESTRAD_DATA_PATH (default ./data) in the wrong place. Pin the
// working directory to the executable's directory so data lands next to the
// installed binary. Interactive runs (foreground / container) keep the
// caller's working directory and any absolute data path they set.
if !service.Interactive() {
if exe, err := os.Executable(); err == nil {
_ = os.Chdir(filepath.Dir(exe))
}
}
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
p.done = make(chan error, 1)
+52 -13
View File
@@ -1,14 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
OrchestrAD Windows installer (WiX v4+ / built with WiX 7).
OrchestrAD Windows installer (WiX v4+ / built with WiX 5).
Installs orchestrad.exe to "Program Files\OrchestrAD" and registers +
starts the Windows service by invoking the binary's own idempotent
`initialize` command. Uninstall runs `remove` (idempotent stop + delete)
before the files are deleted, so no orphaned service is left behind.
Installs orchestrad.exe to "Program Files\OrchestrAD" and registers + starts
the Windows service by invoking the binary's own idempotent `initialize`
command. Uninstall runs `remove` (idempotent stop + delete) before the files
are deleted, so no orphaned service is left behind.
Upgrades update only the binaries: the runtime data directory
(INSTALLDIR\data, which holds the SQLite database) is created by the app, not
by the installer, so MSI never tracks or deletes it. On upgrade the old
product is removed first (service stopped + deleted, old binary removed) and
the new product then installs the new binary and re-initializes the service,
which migrates the preserved database on start. The install location and
version are recorded in the registry, and the install directory is reused on
upgrade.
Build:
wix build installer/OrchestrAD.wxs \
wix build -arch x64 installer/OrchestrAD.wxs \
-d Version=<x.y.z> -d BinDir=<dir containing orchestrad.exe> \
-d IconPath=<path to orchestrad.ico> -o OrchestrAD.msi
-->
@@ -23,25 +32,51 @@
<SummaryInformation Description="OrchestrAD - Active Directory Rule Automation Platform" />
<!-- Block downgrades; replace older/equal on upgrade. -->
<MajorUpgrade DowngradeErrorMessage="A newer version of OrchestrAD is already installed." />
<!-- Replace older/equal on upgrade; block downgrades. The default schedule
(afterInstallValidate) removes the old product first, so the old
service is stopped and deleted and the old binary removed before the
new binary installs and re-initializes the service. Only
installer-tracked files are touched; the runtime data directory
(the database) is never removed. -->
<MajorUpgrade
DowngradeErrorMessage="A newer version of OrchestrAD is already installed." />
<MediaTemplate EmbedCab="yes" />
<!-- Add/Remove Programs metadata. -->
<!-- Add/Remove Programs metadata. ProductVersion is recorded by MSI
automatically; surface it in ARP alongside the icon. -->
<Icon Id="OrchestradIcon" SourceFile="$(IconPath)" />
<Property Id="ARPPRODUCTICON" Value="OrchestradIcon" />
<Property Id="ARPNOMODIFY" Value="1" />
<!-- Install tree: Program Files\OrchestrAD -->
<!-- On upgrade/repair, reuse the previously recorded install directory so
the binary, service, and data stay in one place across versions. On a
fresh install this search finds nothing and the default below is used. -->
<Property Id="INSTALLDIR">
<RegistrySearch Id="FindInstallDir" Root="HKLM"
Key="Software\Grace Solutions\OrchestrAD"
Name="InstallDir" Type="directory" />
</Property>
<!-- Default install tree: Program Files\OrchestrAD -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="OrchestrAD" />
<Directory Id="INSTALLDIR" Name="OrchestrAD" />
</StandardDirectory>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<ComponentGroup Id="ProductComponents" Directory="INSTALLDIR">
<!-- The application binary. -->
<Component Id="OrchestradExe" Bitness="always64">
<File Id="OrchestradExe" Name="orchestrad.exe" Source="$(BinDir)/orchestrad.exe" KeyPath="yes" />
</Component>
<!-- Record the install location and product version so upgrades reuse the
directory and external tooling can discover both. -->
<Component Id="RegistryEntries" Bitness="always64" Guid="7C1E2A64-9B3D-4E1A-9F2B-2D0E6C8A5B10">
<RegistryKey Root="HKLM" Key="Software\Grace Solutions\OrchestrAD">
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
<RegistryValue Name="Version" Type="string" Value="$(Version)" />
</RegistryKey>
</Component>
</ComponentGroup>
<Feature Id="Main" Title="OrchestrAD" Level="1">
@@ -52,6 +87,7 @@
Service lifecycle via the binary's own idempotent CLI. Deferred, no-impersonate
custom actions run as LocalSystem, which can create/delete services.
- Install/upgrade: after the files land, run `initialize` (install + start).
The service migrates the existing database on start, so upgrades keep data.
- Uninstall: before the files are removed, run `remove` (stop + delete).
-->
<CustomAction Id="InitializeService"
@@ -69,7 +105,10 @@
Return="ignore" />
<InstallExecuteSequence>
<!-- Remove the service before its binary is deleted, only when uninstalling. -->
<!-- Remove the service before its binary is deleted. This runs both on a
plain uninstall and on the old product's removal during an upgrade
(REMOVE=ALL in both), so the upgrade stops the old service before the
new binary installs and re-initializes it. -->
<Custom Action="RemoveService" Before="RemoveFiles" Condition="REMOVE=&quot;ALL&quot;" />
<!-- Install and start the service after files are present, when not uninstalling. -->
<Custom Action="InitializeService" After="InstallFiles" Condition="NOT REMOVE=&quot;ALL&quot;" />
+2 -1
View File
@@ -114,7 +114,8 @@ if ($BuildWindows) {
$GoVersionInfo = Join-Path (Join-Path $env:USERPROFILE "go\bin") "goversioninfo.exe"
if (-not (Test-Path $GoVersionInfo)) {
Write-Host " Installing goversioninfo..." -ForegroundColor Yellow
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest
$env:GOTOOLCHAIN = "local"
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@v1.7.0
}
$vp = $Version -split '\.'
Push-Location $BackendDir