diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66f07b8..04cf1bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,18 @@ # # The result is a DRAFT release: nothing becomes public until it is # reviewed and published by hand on GitHub. +# +# Dry run: tick "dry run" when starting it by hand. Everything is built and +# checked, the files stay on the run page as a workflow artifact, nothing is +# signed or published. Pull requests that change this workflow, the +# installer or .signpath/ get a dry run by themselves. +# +# Code signing (SignPath Foundation) is prepared but off until the +# repository has the variable SIGNPATH_ORGANIZATION_ID and the secret +# SIGNPATH_API_TOKEN; see .signpath/README.md. Without them the release is +# built unsigned, exactly as before. With them the programs are signed +# first, the installers are built from the signed programs and are then +# signed themselves: two signing requests to approve per release. name: Release on: @@ -17,13 +29,28 @@ on: version: description: 'Version without "v", e.g. 2.0.0' required: true + dry_run: + description: 'Dry run: build and check only, sign and publish nothing' + type: boolean + default: false + pull_request: + paths: + - '.github/workflows/release.yml' + - '.signpath/**' + - 'packaging/inno/**' permissions: contents: write + actions: read + +env: + DRY_RUN: ${{ github.event_name == 'pull_request' || inputs.dry_run == true }} jobs: release: runs-on: windows-latest + # Up to one hour of waiting for each of the two signing approvals. + timeout-minutes: 180 steps: - uses: actions/checkout@v5 - uses: actions/setup-go@v6 @@ -41,12 +68,23 @@ jobs: INPUT_VERSION: ${{ inputs.version }} run: | v="$INPUT_VERSION" + # A pull request has no version of its own: it builds the one in + # wails.json. + if [ -z "$v" ] && [ "$GITHUB_EVENT_NAME" = pull_request ]; then + v=$(sed -nE 's/.*"productVersion": "([^"]*)".*/\1/p' gui/wails.json) + fi [ -n "$v" ] || v="${GITHUB_REF_NAME#v}" if ! [[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then echo "::error::not a version: $v"; exit 1 fi + # numeric: what the file properties of every exe and installer + # carry, and what SignPath checks them against. + numeric=$(echo "$v" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/') echo "version=$v" >> "$GITHUB_OUTPUT" - echo "winver=$(echo "$v" | sed -E 's/^([0-9]+)\.([0-9]+)\.([0-9]+).*/\1.\2.\3.0/')" >> "$GITHUB_OUTPUT" + echo "winver=$numeric.0" >> "$GITHUB_OUTPUT" + echo "numeric=$numeric" >> "$GITHUB_OUTPUT" + echo "VERSION=$v" >> "$GITHUB_ENV" + echo "NUMERIC_VERSION=$numeric" >> "$GITHUB_ENV" - name: tests shell: bash @@ -64,11 +102,13 @@ jobs: run: | set -euo pipefail V="${{ steps.v.outputs.version }}" - W="${{ steps.v.outputs.winver }}" + N="${{ steps.v.outputs.numeric }}" LDFLAGS="-s -w -X github.com/burnersen/TimeSync/internal/version.Version=$V" + # Both exes carry the same product version text (1.2.3): SignPath + # compares it, and go-winres writes the text exactly as given. (cd cmd/ntp-sync && go run github.com/tc-hib/go-winres@v0.3.3 make --in winres/winres.json --out rsrc \ - --arch amd64,arm64 --product-version "$W" --file-version "$W") - sed -i -E "s/\"productVersion\": \"[^\"]*\"/\"productVersion\": \"${W%.0}\"/" gui/wails.json + --arch amd64,arm64 --product-version "$N" --file-version "$N") + sed -i -E "s/\"productVersion\": \"[^\"]*\"/\"productVersion\": \"$N\"/" gui/wails.json mkdir -p dist for arch in amd64 arm64; do label=x64; [ "$arch" = arm64 ] && label=arm64 @@ -80,34 +120,186 @@ jobs: cp LICENSE README.md THIRD_PARTY_NOTICES.md "$out/" done - # Code signing goes here once a certificate is available (for example - # through the SignPath Foundation for open source projects). Until - # then the files are unsigned and Windows SmartScreen will warn. + - name: check the file properties of the programs + run: | + $files = foreach ($label in 'x64', 'arm64') { + "dist/TimeSync-$env:VERSION-windows-$label/TimeSync.exe" + "dist/TimeSync-$env:VERSION-windows-$label/ntp-sync.exe" + } + ./.signpath/check-files.ps1 -ProductName TimeSync -ProductVersion $env:NUMERIC_VERSION -Path $files + + # --- code signing: on or off? ---------------------------------------- + - name: 'code signing: on or off?' + id: signing + env: + ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + HAS_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + run: | + $hasOrganization = -not [string]::IsNullOrWhiteSpace($env:ORGANIZATION_ID) + $hasToken = $env:HAS_TOKEN -eq 'true' + if ($env:DRY_RUN -eq 'true') { + $on = $false + Write-Host 'Dry run: nothing is signed.' + } elseif ($hasOrganization -ne $hasToken) { + Write-Host '::error::Code signing is half set up: SIGNPATH_ORGANIZATION_ID and SIGNPATH_API_TOKEN must both be set, or neither (see .signpath/README.md).' + exit 1 + } else { + $on = $hasOrganization -and $hasToken + Write-Host ($(if ($on) { 'Code signing is on.' } else { 'Code signing is off: this release is unsigned.' })) + } + "enabled=$($on.ToString().ToLower())" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 + + # --- code signing, round 1: the programs ----------------------------- + - name: 'code signing: collect the programs' + if: steps.signing.outputs.enabled == 'true' + run: | + foreach ($label in 'x64', 'arm64') { + New-Item -ItemType Directory "sign/programs/$label" -Force | Out-Null + Copy-Item "dist/TimeSync-$env:VERSION-windows-$label/*.exe" "sign/programs/$label/" + } + + - name: 'code signing: upload the programs' + id: unsigned-programs + if: steps.signing.outputs.enabled == 'true' + uses: actions/upload-artifact@v7 + with: + name: unsigned-programs + path: sign/programs + if-no-files-found: error + + - name: 'code signing: sign the programs (approve the request in SignPath)' + if: steps.signing.outputs.enabled == 'true' + uses: signpath/github-action-submit-signing-request@v3 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG || 'TimeSync' }} + signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG || 'release-signing' }} + artifact-configuration-slug: ${{ vars.SIGNPATH_PROGRAMS_CONFIGURATION_SLUG || 'programs' }} + github-artifact-id: ${{ steps.unsigned-programs.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: sign/programs-signed + parameters: | + version: "${{ steps.v.outputs.numeric }}" - - name: installers and zips + - name: 'code signing: take over the signed programs' + if: steps.signing.outputs.enabled == 'true' + run: | + $files = foreach ($label in 'x64', 'arm64') { + foreach ($exe in 'TimeSync.exe', 'ntp-sync.exe') { + $target = "dist/TimeSync-$env:VERSION-windows-$label/$exe" + Copy-Item "sign/programs-signed/$label/$exe" $target -Force + $target + } + } + ./.signpath/check-files.ps1 -ProductName TimeSync -ProductVersion $env:NUMERIC_VERSION -Path $files -Signed + + - name: installers shell: pwsh run: | $V = "${{ steps.v.outputs.version }}" + $N = "${{ steps.v.outputs.numeric }}" $iscc = (Get-Command iscc.exe -ErrorAction SilentlyContinue).Source if (-not $iscc) { $iscc = Get-ChildItem "C:\Program Files*\Inno Setup*\ISCC.exe" | Select-Object -First 1 -ExpandProperty FullName } if (-not $iscc) { Write-Host "::error::Inno Setup compiler not found"; exit 1 } foreach ($label in @("x64", "arm64")) { $src = "dist/TimeSync-$V-windows-$label" - & $iscc /Q "/DAppVersion=$V" "/DArch=$label" "/DSourceDir=$((Resolve-Path $src).Path)" "/O$((Resolve-Path dist).Path)" packaging/inno/TimeSync.iss + & $iscc /Q "/DAppVersion=$V" "/DNumericVersion=$N" "/DArch=$label" "/DSourceDir=$((Resolve-Path $src).Path)" "/O$((Resolve-Path dist).Path)" packaging/inno/TimeSync.iss if ($LASTEXITCODE -ne 0) { exit 1 } - Compress-Archive -Path $src -DestinationPath "dist/TimeSync-$V-windows-$label.zip" + } + $installers = foreach ($label in 'x64', 'arm64') { "dist/TimeSync-$V-setup-$label.exe" } + ./.signpath/check-files.ps1 -ProductName TimeSync -ProductVersion $N -Path $installers + + # --- code signing, round 2: the installers --------------------------- + - name: 'code signing: collect the installers' + if: steps.signing.outputs.enabled == 'true' + run: | + New-Item -ItemType Directory sign/installers -Force | Out-Null + Copy-Item dist/*-setup-*.exe sign/installers/ + + - name: 'code signing: upload the installers' + id: unsigned-installers + if: steps.signing.outputs.enabled == 'true' + uses: actions/upload-artifact@v7 + with: + name: unsigned-installers + path: sign/installers + if-no-files-found: error + + - name: 'code signing: sign the installers (approve the request in SignPath)' + if: steps.signing.outputs.enabled == 'true' + uses: signpath/github-action-submit-signing-request@v3 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG || 'TimeSync' }} + signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG || 'release-signing' }} + artifact-configuration-slug: ${{ vars.SIGNPATH_INSTALLERS_CONFIGURATION_SLUG || 'installers' }} + github-artifact-id: ${{ steps.unsigned-installers.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: sign/installers-signed + parameters: | + version: "${{ steps.v.outputs.version }}" + numeric: "${{ steps.v.outputs.numeric }}" + + - name: 'code signing: take over the signed installers' + if: steps.signing.outputs.enabled == 'true' + run: | + $files = foreach ($label in 'x64', 'arm64') { + $name = "TimeSync-$env:VERSION-setup-$label.exe" + Copy-Item "sign/installers-signed/$name" "dist/$name" -Force + "dist/$name" + } + ./.signpath/check-files.ps1 -ProductName TimeSync -ProductVersion $env:NUMERIC_VERSION -Path $files -Signed + + - name: zips and checksums + shell: pwsh + run: | + $V = "${{ steps.v.outputs.version }}" + foreach ($label in @("x64", "arm64")) { + Compress-Archive -Path "dist/TimeSync-$V-windows-$label" -DestinationPath "dist/TimeSync-$V-windows-$label.zip" } Get-ChildItem dist -File | ForEach-Object { "{0} {1}" -f (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower(), $_.Name } | Set-Content dist/SHA256SUMS.txt -Encoding ascii + Get-Content dist/SHA256SUMS.txt + + # The release page is the download page, so it links the code signing + # policy and says whether the files of this release are signed. + - name: release text + env: + SIGNED: ${{ steps.signing.outputs.enabled }} + run: | + $policy = "https://github.com/$env:GITHUB_REPOSITORY/blob/main/CODE_SIGNING.md" + if ($env:SIGNED -eq 'true') { + $footer = "The Windows files of this release are signed. Free code signing provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org). [Code signing policy]($policy)" + } else { + $footer = "The Windows files of this release are not code signed, so Windows SmartScreen may warn. [Code signing policy]($policy)" + } + $text = [IO.File]::ReadAllText("CHANGELOG.md") + "`n---`n`n$footer`n" + [IO.File]::WriteAllText("$env:RUNNER_TEMP/release-notes.md", $text, [Text.UTF8Encoding]::new($false)) + + - name: 'dry run: keep the files on the run page' + if: env.DRY_RUN == 'true' + uses: actions/upload-artifact@v7 + with: + name: release-dry-run + path: | + dist/*.zip + dist/*.exe + dist/SHA256SUMS.txt + retention-days: 7 - uses: softprops/action-gh-release@v2 + if: env.DRY_RUN != 'true' with: draft: true tag_name: v${{ steps.v.outputs.version }} target_commitish: ${{ github.sha }} name: TimeSync ${{ steps.v.outputs.version }} - body_path: CHANGELOG.md + body_path: ${{ runner.temp }}/release-notes.md files: | dist/*.zip dist/*.exe diff --git a/.signpath/README.md b/.signpath/README.md new file mode 100644 index 0000000..d8c83b7 --- /dev/null +++ b/.signpath/README.md @@ -0,0 +1,68 @@ +# Code signing with SignPath: prepared, off until switched on + +TimeSync has applied to the [SignPath Foundation](https://signpath.org) for free +code signing of its Windows files. Everything for it is prepared here and in +[`release.yml`](../.github/workflows/release.yml), but **it stays off** until it is +switched on below. Until then every release is built and published exactly as before, +unsigned, and its release text says so. + +| File | What it is | +| ---- | ---------- | +| [`../CODE_SIGNING.md`](../CODE_SIGNING.md) | The public code signing policy (required by SignPath Foundation): roles, what is signed, privacy | +| [`artifact-configuration-programs.xml`](artifact-configuration-programs.xml) | SignPath artifact configuration `programs`: the four programs (x64 and ARM64), first request of a release | +| [`artifact-configuration-installers.xml`](artifact-configuration-installers.xml) | SignPath artifact configuration `installers`: the two installers, built from the signed programs, second request | +| [`check-files.ps1`](check-files.ps1) | Run by every release build: checks product name and version of every file to be signed (SignPath enforces both), and with `-Signed` that the signatures are valid | + +## How the release workflow decides + +| Repository settings | What a release does | +| ------------------- | ------------------- | +| Neither `SIGNPATH_ORGANIZATION_ID` nor `SIGNPATH_API_TOKEN` set | Builds and publishes unsigned, as before | +| Both set | Uploads the files, sends a signing request to SignPath, **waits up to one hour for the approval**, downloads the signed files, checks their signatures and publishes them | +| Only one of them set | Stops with an error, so a half finished setup never ships unsigned files by surprise | + +**Dry run:** *Actions > Release > Run workflow* with "dry run" ticked builds and checks +everything, keeps the files on the run page as a workflow artifact, never signs and +publishes nothing. It also runs by itself for every pull request that changes the +release workflow or this folder. + +## Switching it on (after SignPath Foundation said yes) + +1. **Two-factor authentication** on GitHub and on SignPath for everybody in the team + (a condition of SignPath Foundation). +2. **In SignPath** (the Foundation sets up most of it with you): + - the project, slug `TimeSync` (another slug: set the variable `SIGNPATH_PROJECT_SLUG`), + - the trusted build system *GitHub.com* linked to the project, and the SignPath + GitHub App installed on this repository (SignPath shows the link), + - the artifact configuration from [`artifact-configuration-programs.xml`](artifact-configuration-programs.xml), slug `programs` (another slug: set `SIGNPATH_PROGRAMS_CONFIGURATION_SLUG`), + - the artifact configuration from [`artifact-configuration-installers.xml`](artifact-configuration-installers.xml), slug `installers` (another slug: set `SIGNPATH_INSTALLERS_CONFIGURATION_SLUG`), + - the signing policy `release-signing` with manual approval by the approver + (another slug: set `SIGNPATH_SIGNING_POLICY_SLUG`), + - a CI user with the *Submitter* role on that policy, and an API token for it. +3. **On GitHub:** *Settings > Secrets and variables > Actions* + - secret `SIGNPATH_API_TOKEN`: the API token of the CI user, + - variable `SIGNPATH_ORGANIZATION_ID`: the organization ID from SignPath. +4. **Recommended:** a ruleset that protects `main` and the `v*` tags from force pushes + and deletion (*Settings > Rules > Rulesets*). SignPath can require this in its + policies. +5. **Update the texts:** the status line at the top of + [`CODE_SIGNING.md`](../CODE_SIGNING.md) and the SmartScreen note in the README. +6. **Release as usual.** Approve the signing request in SignPath within one hour (the + link is in the run log and in the e-mail from SignPath), then the release is published + with signed files. + +| Variable / secret | Required | Default | +| ----------------- | -------- | ------- | +| `SIGNPATH_API_TOKEN` (secret) | yes | | +| `SIGNPATH_ORGANIZATION_ID` | yes | | +| `SIGNPATH_PROJECT_SLUG` | no | `TimeSync` | +| `SIGNPATH_SIGNING_POLICY_SLUG` | no | `release-signing` | +| `SIGNPATH_PROGRAMS_CONFIGURATION_SLUG` | no | `programs` | +| `SIGNPATH_INSTALLERS_CONFIGURATION_SLUG` | no | `installers` | + +## Taking it out again + +Never setting the secret and the variable is enough: nothing is signed then. To remove +it completely: delete this folder and `CODE_SIGNING.md`, remove the steps marked +`code signing` in `release.yml` (the file check step uses `check-files.ps1` from this +folder) and the links to `CODE_SIGNING.md` in the README. diff --git a/.signpath/artifact-configuration-installers.xml b/.signpath/artifact-configuration-installers.xml new file mode 100644 index 0000000..a193891 --- /dev/null +++ b/.signpath/artifact-configuration-installers.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/.signpath/artifact-configuration-programs.xml b/.signpath/artifact-configuration-programs.xml new file mode 100644 index 0000000..c72d97f --- /dev/null +++ b/.signpath/artifact-configuration-programs.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/.signpath/check-files.ps1 b/.signpath/check-files.ps1 new file mode 100644 index 0000000..02d81b1 --- /dev/null +++ b/.signpath/check-files.ps1 @@ -0,0 +1,61 @@ +# TimeSync (https://github.com/burnersen/TimeSync) +# Copyright (C) 2026 burnersen +# SPDX-License-Identifier: MIT +# +# Checks the Windows files a release is about to sign or publish: +# +# - every file exists, +# - its file properties carry exactly the product name and product version +# that the SignPath artifact configuration enforces (product-name, +# product-version). SignPath refuses a signing request whose files do not +# match, so this check runs on every release build, signed or not: a +# mismatch shows up long before the first signed release. +# - with -Signed: every file carries a valid Authenticode signature. +# +# Usage (from the release workflow): +# ./.signpath/check-files.ps1 -ProductName X -ProductVersion 1.2.3 -Path a.exe, b.exe [-Signed] + +param( + [Parameter(Mandatory)] [string[]] $Path, + [Parameter(Mandatory)] [string] $ProductName, + [Parameter(Mandatory)] [string] $ProductVersion, + [switch] $Signed +) + +$ErrorActionPreference = 'Stop' +$failed = 0 + +foreach ($file in $Path) { + if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { + Write-Host "::error::$file is missing" + $failed++ + continue + } + $info = (Get-Item -LiteralPath $file).VersionInfo + $name = "$($info.ProductName)".Trim() + $version = "$($info.ProductVersion)".Trim() + if ($name -ne $ProductName) { + Write-Host "::error::$file has product name '$name', expected '$ProductName'" + $failed++ + } + if ($version -ne $ProductVersion) { + Write-Host "::error::$file has product version '$version', expected '$ProductVersion'" + $failed++ + } + $line = "{0}: {1} {2}" -f $file, $name, $version + if ($Signed) { + $signature = Get-AuthenticodeSignature -LiteralPath $file + if ($signature.Status -ne 'Valid') { + Write-Host "::error::$file is not validly signed ($($signature.Status): $($signature.StatusMessage))" + $failed++ + } else { + $line += ", signed by $($signature.SignerCertificate.Subject)" + } + } + Write-Host $line +} + +if ($failed -gt 0) { + Write-Host "::error::$failed problem(s) with the release files" + exit 1 +} diff --git a/CODE_SIGNING.md b/CODE_SIGNING.md new file mode 100644 index 0000000..91c0d81 --- /dev/null +++ b/CODE_SIGNING.md @@ -0,0 +1,72 @@ +# Code signing policy + +Free code signing provided by [SignPath.io](https://about.signpath.io), +certificate by [SignPath Foundation](https://signpath.org). + +> **Status:** TimeSync has applied to the SignPath Foundation. Until the +> signing is active, the Windows files of a release are not signed. Every +> release says in its text whether its files are signed. + +## What is signed + +Only files built from the source code in this repository, by the +[release workflow](.github/workflows/release.yml) on GitHub-hosted runners: + +| File | What it is | +| ---- | ---------- | +| `TimeSync.exe` (x64, ARM64) | The window | +| `ntp-sync.exe` (x64, ARM64) | Command line tool and Windows service | +| `TimeSync--setup-x64.exe`, `-setup-arm64.exe` | The installers, built from the signed programs above | + +The `.zip` files contain the same signed programs. Nothing that was built +elsewhere, and nothing from other projects, is signed with this certificate. +Every signing request is approved by hand, release by release. + +The file properties of every signed file name the product `TimeSync` and the +version of the release; SignPath refuses to sign files that do not match. + +## Team roles + +| Role | Who | +| ---- | --- | +| Committers and reviewers | [burnersen](https://github.com/burnersen) | +| Approvers | [burnersen](https://github.com/burnersen) | + +Changes from anybody else (pull requests) are reviewed by a committer before +they are merged. All team members use multi-factor authentication for GitHub +and for SignPath. + +## Privacy policy + +This program will not transfer any information to other networked systems +unless specifically requested by the user or the person installing or +operating it. + +What TimeSync does on the network, and only for that: + +- It asks the **time servers in your settings** for the time (NTP or NTS). + The default servers are `time.cloudflare.com`, `ptbtime1.ptb.de`, + `ptbtime2.ptb.de` and `time.windows.com`; you can change them. +- Only if NTP is blocked, it reads the time from the `Date` header of the + **web servers in `httpsFallbackUrls`** (default: ptb.de, cloudflare.com, + google.com, microsoft.com). This is a `HEAD` request: nothing is sent but + the request itself. + +Like any network request, these reveal your IP address to the server asked. +There is no telemetry, no update check, no account and no advertising. The +diagnostics file stays on your PC until you pass it on yourself, and masks +your user name. + +## Verifying a download + +- **Signature:** right-click the file > *Properties* > *Digital Signatures*. + A signed release shows *SignPath Foundation*. +- **Checksums:** every release has a `SHA256SUMS.txt` + (`Get-FileHash ` in PowerShell). + +## Reporting a problem + +If you think a signed file of this project does something it should not, +please open an [issue](https://github.com/burnersen/TimeSync/issues). Signed +files that break these rules can also be reported to the +[SignPath Foundation](https://signpath.org). diff --git a/README.md b/README.md index 6c4e5fb..5d0b7ec 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ Edge WebView2 runtime, which is part of current Windows. > **Windows SmartScreen** may warn because the files are not yet digitally > signed. Choose "More info" → "Run anyway". The source code is all here, > and every release is built by GitHub Actions from this repository. +> Free code signing through the SignPath Foundation is applied for; see the +> [code signing policy](CODE_SIGNING.md). ## How it decides @@ -186,7 +188,8 @@ the program file is replaced. TimeSync contacts only the time servers in your settings and, if NTP is blocked, the web servers in `httpsFallbackUrls` (a `HEAD` request, nothing is sent but the request itself). There is no telemetry, no update check and -no account. The diagnostics file masks your user name. +no account. The diagnostics file masks your user name. The full privacy +policy is part of the [code signing policy](CODE_SIGNING.md#privacy-policy). ## Uninstall @@ -212,7 +215,10 @@ Releases are built by [`.github/workflows/release.yml`](.github/workflows/releas either when a `v*` tag is pushed or by hand (Actions → Release → "Run workflow", enter the version). The result is a draft release with installers, zip files and `SHA256SUMS.txt`; it becomes public only when it -is published on GitHub. Every push also leaves an unsigned test build of +is published on GitHub. Code signing is prepared in the same workflow and +switched on once SignPath Foundation approves it, see +[`.signpath/README.md`](.signpath/README.md). A dry run (tick "dry run") +builds and checks everything without publishing. Every push also leaves an unsigned test build of the x64 programs on its CI run page. Layout: `cmd/ntp-sync` (command line and service), `gui` (the window), @@ -223,3 +229,4 @@ Layout: `cmd/ntp-sync` (command line and service), `gui` (the window), ## License [MIT](LICENSE). Third-party components: [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). +Code signing policy: [CODE_SIGNING.md](CODE_SIGNING.md). diff --git a/packaging/inno/TimeSync.iss b/packaging/inno/TimeSync.iss index 94d0ade..4291296 100644 --- a/packaging/inno/TimeSync.iss +++ b/packaging/inno/TimeSync.iss @@ -1,7 +1,7 @@ ; Installer for TimeSync (Inno Setup 6). ; ; Built by .github/workflows/release.yml: -; ISCC /DAppVersion=2.0.0 /DArch=x64 /DSourceDir= TimeSync.iss +; ISCC /DAppVersion=2.0.0 /DNumericVersion=2.0.0 /DArch=x64 /DSourceDir= TimeSync.iss ; ; It installs TimeSync.exe and ntp-sync.exe to C:\Program Files\TimeSync ; and, if ticked, sets up the background service. Uninstalling removes the @@ -10,6 +10,11 @@ #ifndef AppVersion #define AppVersion "0.0.0" #endif +; The file properties of the installer: product name and version must match +; the programs inside, because code signing (SignPath) checks both. +#ifndef NumericVersion + #define NumericVersion AppVersion +#endif #ifndef Arch #define Arch "x64" #endif @@ -27,7 +32,12 @@ AppPublisher=burnersen AppPublisherURL=https://github.com/burnersen/TimeSync AppSupportURL=https://github.com/burnersen/TimeSync/issues AppUpdatesURL=https://github.com/burnersen/TimeSync/releases -VersionInfoVersion={#AppVersion} +VersionInfoVersion={#NumericVersion} +VersionInfoProductName=TimeSync +VersionInfoProductVersion={#NumericVersion} +VersionInfoProductTextVersion={#NumericVersion} +VersionInfoCompany=burnersen +VersionInfoDescription=TimeSync Setup ; The service binary must live where only administrators can change it. DefaultDirName={autopf}\TimeSync DisableDirPage=yes diff --git a/scripts/build.sh b/scripts/build.sh index 029d87d..fcd7396 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -20,7 +20,7 @@ mkdir -p "$DIST" # Icon, version and manifest for ntp-sync.exe. (cd "$ROOT/cmd/ntp-sync" && go run github.com/tc-hib/go-winres@v0.3.3 make \ --in winres/winres.json --out rsrc --arch amd64,arm64 \ - --product-version "$WINVER" --file-version "$WINVER") + --product-version "${WINVER%.0}" --file-version "${WINVER%.0}") # The window reads its version for the file properties from wails.json. python3 - "$ROOT/gui/wails.json" "$WINVER" <<'PY' 2>/dev/null || sed -i.bak -E "s/\"productVersion\": \"[^\"]*\"/\"productVersion\": \"${WINVER%.0}\"/" "$ROOT/gui/wails.json"