Skip to content
Merged
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
164 changes: 164 additions & 0 deletions .github/workflows/release-signpath.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: Signed release (SignPath Foundation)

on:
workflow_dispatch:
inputs:
tag:
description: Existing release tag on main (for example v0.17.0-rc.4)
required: true
type: string

permissions:
contents: read

concurrency:
group: signpath-${{ inputs.tag }}
cancel-in-progress: false

jobs:
build:
if: github.repository == 'almuleev/AMSignal' && github.ref == 'refs/heads/main'
runs-on: windows-latest
timeout-minutes: 40
outputs:
version: ${{ steps.source.outputs.version }}
commit: ${{ steps.source.outputs.commit }}
artifact_id: ${{ steps.unsigned.outputs.artifact-id }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Verify configuration and immutable release source
id: source
shell: pwsh
env:
REQUESTED_TAG: ${{ inputs.tag }}
SIGNPATH_ENABLED: ${{ vars.SIGNPATH_ENABLED }}
SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }}
SIGNPATH_POLICY_SLUG: ${{ vars.SIGNPATH_POLICY_SLUG }}
SIGNPATH_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_CONFIGURATION_SLUG }}
run: |
$ErrorActionPreference = 'Stop'
if ($env:SIGNPATH_ENABLED -ne 'true') { throw 'SignPath is not activated. See docs/CODE_SIGNING.md.' }
foreach ($name in @('SIGNPATH_ORGANIZATION_ID','SIGNPATH_PROJECT_SLUG','SIGNPATH_POLICY_SLUG','SIGNPATH_CONFIGURATION_SLUG')) {
if (-not [Environment]::GetEnvironmentVariable($name)) { throw "Missing repository variable: $name" }
}
if ($env:REQUESTED_TAG -notmatch '^v(\d+\.\d+\.\d+(?:-[A-Za-z0-9]+(?:[.-][A-Za-z0-9]+)*)?)$') { throw 'Invalid release tag.' }
$version = $Matches[1]
$ref = 'refs/tags/' + $env:REQUESTED_TAG
$commit = git rev-parse --verify ($ref + '^{commit}')
if ($LASTEXITCODE -ne 0) { throw 'Release tag does not exist.' }
git merge-base --is-ancestor $commit origin/main
if ($LASTEXITCODE -ne 0) { throw 'Release source is not part of main.' }
git checkout --detach $commit
if ($LASTEXITCODE -ne 0) { throw 'Cannot check out release source.' }
if (-not (Test-Path docs/signpath-artifact.xml)) { throw 'This tag predates the signing integration. Use a new release tag.' }
"version=$version" >> $env:GITHUB_OUTPUT
"commit=$commit" >> $env:GITHUB_OUTPUT
- uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2
with:
msystem: UCRT64
update: true
install: >-
make
git
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-binutils
- name: Core and GUI regressions
shell: msys2 {0}
run: make test test-gui CXX=g++
- name: Production GUI and CLI
shell: msys2 {0}
run: |
powershell.exe -NoProfile -ExecutionPolicy Bypass -File ./build_cli.ps1 || exit $?
powershell.exe -NoProfile -ExecutionPolicy Bypass -File ./build_gui.ps1 || exit $?
- name: Check metadata and stage only project executables
shell: pwsh
env:
RELEASE_VERSION: ${{ steps.source.outputs.version }}
run: |
./verify_release_binaries.ps1 -InputDirectory . -Version $env:RELEASE_VERSION
./tests/release_signing.ps1 -InputDirectory . -Version $env:RELEASE_VERSION
New-Item -ItemType Directory dist/signpath-input | Out-Null
Copy-Item -LiteralPath "AMSignal-${env:RELEASE_VERSION}-x64.exe", 'lvm_reader.exe' -Destination dist/signpath-input
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
id: unsigned
with:
name: signpath-input
path: dist/signpath-input/*
if-no-files-found: error
retention-days: 7

sign:
needs: build
runs-on: windows-latest
timeout-minutes: 75
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ needs.build.outputs.commit }}
persist-credentials: false
- name: Submit request and wait for maintainer approval in SignPath
uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
signing-policy-slug: ${{ vars.SIGNPATH_POLICY_SLUG }}
artifact-configuration-slug: ${{ vars.SIGNPATH_CONFIGURATION_SLUG }}
github-artifact-id: ${{ needs.build.outputs.artifact_id }}
wait-for-completion: true
wait-for-completion-timeout-in-seconds: 3600
output-artifact-directory: signed
parameters: |
version: ${{ toJSON(needs.build.outputs.version) }}
- name: Verify signatures, then package and hash the signed bytes
shell: pwsh
env:
RELEASE_VERSION: ${{ needs.build.outputs.version }}
run: |
./package_release.ps1 -InputDirectory signed -Version $env:RELEASE_VERSION -OutputDir dist/signed/package -RequireSignature -Zip
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: verified-signed-release
path: |
dist/signed/*.zip
dist/signed/*.sha256
dist/signed/package/*.exe
if-no-files-found: error
retention-days: 30

draft-release:
needs: [build, sign]
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: verified-signed-release
path: release-files
- name: Create a draft with verified signed assets
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
$ErrorActionPreference = 'Stop'
$existing = gh release view $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY --json tagName 2>$null
if ($LASTEXITCODE -eq 0) { throw 'Release already exists. Its assets will not be overwritten.' }
$assets = @(Get-ChildItem release-files -Recurse -File | ForEach-Object FullName)
$notes = @('Signed Windows GUI and CLI; SHA-256 checksums attached.', '',
'Code signing policy: https://github.com/almuleev/AMSignal/blob/main/docs/CODE_SIGNING.md', '',
'Review and complete the release notes before publishing this draft.')
[IO.File]::WriteAllLines('release-notes.md', $notes)
$arguments = @('release','create',$env:RELEASE_TAG,'--repo',$env:GITHUB_REPOSITORY,
'--verify-tag','--draft','--title',"AMSignal $env:RELEASE_TAG",'--notes-file','release-notes.md')
if ($env:RELEASE_TAG.Contains('-')) { $arguments += '--prerelease' }
& gh @arguments @assets
if ($LASTEXITCODE -ne 0) { throw 'Could not create release draft.' }
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@

## Быстрая карта

- Бесплатная подпись (2026-10-14): release-signpath.yml собирает только чистый
tag из main на GitHub-hosted Windows, передаёт два собственных EXE в SignPath
и ждёт ручного одобрения. Подключение Foundation ещё не одобрено; не объявляй
текущие EXE подписанными. AM_version.rc/build_version_resource.ps1 добавляют
одинаковые ProductName/ProductVersion/FileVersion в GUI и CLI, включая Make.
verify_release_binaries.ps1 требует Valid Authenticode, SignPath Foundation,
timestamp и общий сертификат; package_release.ps1 -RequireSignature берёт
только проверенные EXE из InputDirectory и считает SHA-256 после подписи.
Старые выпуски не перезаписываются, новый создаётся только draft. Политика,
настройки и ограничения — docs/CODE_SIGNING.md; заявка —
docs/SIGNPATH_APPLICATION.md, XML — docs/signpath-artifact.xml.
tests/release_signing.ps1: 25 проверок метаданных, отказа unsigned, версий,
безопасных путей, ZIP и контрольных сумм. Численный код не менялся.

- Предварительный выпуск `v0.17.0-rc.3` (2026-10-14) объединяет накопленные
исправления просмотра, AMSOIDX2/склейку, адаптивные RAM/диск ресурсы и аудит
крайних случаев. Полное двуязычное описание — `docs/releases/v0.17.0-rc.3.md`;
Expand Down
1 change: 1 addition & 0 deletions AM_logo.rc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
101 ICON "AM_logo.ico"
#include "AM_version.rc"
41 changes: 41 additions & 0 deletions AM_version.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <winver.h>
#ifndef AM_VERSION_STRING
#define AM_VERSION_STRING "dev"
#endif
#ifndef AM_VERSION_NUMBERS
#define AM_VERSION_NUMBERS 0,0,0,0
#endif
#ifndef AM_ORIGINAL_FILENAME
#define AM_ORIGINAL_FILENAME "AMSignal.exe"
#endif
1 VERSIONINFO
FILEVERSION AM_VERSION_NUMBERS
PRODUCTVERSION AM_VERSION_NUMBERS
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "AMSignal contributors"
#ifdef AM_CLI
VALUE "FileDescription", "AMSignal command-line signal analysis"
#else
VALUE "FileDescription", "AMSignal desktop signal analysis"
#endif
VALUE "FileVersion", AM_VERSION_STRING
VALUE "ProductName", "AMSignal"
VALUE "ProductVersion", AM_VERSION_STRING
VALUE "OriginalFilename", AM_ORIGINAL_FILENAME
VALUE "LegalCopyright", "AMSignal contributors; GNU GPLv3"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1200
END
END
24 changes: 19 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ GUI_TEST_OBJECT := .build/make_gui/gui_regression.o
BENCH_BIN := tests/perf_benchmark.exe
GUI_FLAGS := $(CPPFLAGS) $(CXXFLAGS) -DAPP_VERSION_W=L\"$(VERSION)\" -I.
GUI_RES := AM_logo.o
VERSION_NUMBERS := $(shell printf '%s' '$(RELEASE_VERSION)' | sed -nE 's/^([0-9]+)\.([0-9]+)\.([0-9]+)([.+-].*)?$$/\1,\2,\3,0/p')
ifeq ($(VERSION_NUMBERS),)
VERSION_NUMBERS := 0,0,0,0
endif

ifeq ($(OS),Windows_NT)
BIN := $(TARGET).exe
TEST_BIN := tests/run_tests.exe
GUI_BIN := AMSignal-$(RELEASE_VERSION)-x64.exe
CLI_RES := .build/cli/version.o
else
BIN := $(TARGET)
TEST_BIN := tests/run_tests
Expand All @@ -41,8 +46,15 @@ all: $(BIN)
main.o: FORCE
FORCE:

$(BIN): $(APP_OBJ)
$(CXX) $(CXXFLAGS) -o $@ $(APP_OBJ) $(LDFLAGS)
$(BIN): $(APP_OBJ) $(CLI_RES)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

ifeq ($(OS),Windows_NT)
$(CLI_RES): AM_version.rc FORCE
mkdir -p .build/cli
printf '%s\n' '#define AM_VERSION_STRING "$(RELEASE_VERSION)"' '#define AM_VERSION_NUMBERS $(VERSION_NUMBERS)' '#define AM_CLI 1' '#define AM_ORIGINAL_FILENAME "lvm_reader.exe"' '#include "AM_version.rc"' > .build/cli/version.rc
$(WINDRES) -I. -O coff -i .build/cli/version.rc -o $@
endif

%.o: %.cpp $(HDRS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
Expand Down Expand Up @@ -87,11 +99,13 @@ $(TEST_BIN): tests/run_tests.cpp tests/recording_overview_tests.cpp tests/record
# Win32 import libraries. On Windows you can also run: powershell ./build_gui.ps1
gui: $(GUI_BIN)

$(GUI_RES): AM_logo.rc AM_logo.ico
$(WINDRES) -O coff -i $< -o $@
$(GUI_RES): AM_logo.rc AM_version.rc AM_logo.ico FORCE
mkdir -p .build
printf '%s\n' '#define AM_VERSION_STRING "$(RELEASE_VERSION)"' '#define AM_VERSION_NUMBERS $(VERSION_NUMBERS)' '#define AM_ORIGINAL_FILENAME "$(GUI_BIN)"' '#include "AM_logo.rc"' > .build/logo-version.rc
$(WINDRES) -I. -O coff -i .build/logo-version.rc -o $@

$(GUI_BIN): $(GUI_OBJECTS) $(GUI_RES)
$(CXX) -municode -mwindows -o $@ $^ $(LDFLAGS) -lcomdlg32 -lgdi32 -luser32 -lgdiplus -lcomctl32 -luxtheme -ladvapi32 -lshell32 -lole32 -luuid

clean:
rm -f $(APP_OBJ) $(BIN) $(TEST_BIN) $(GUI_BIN) $(GUI_RES) $(GUI_OBJECTS) $(GUI_OBJECTS:.o=.d) $(GUI_TEST_OBJECT) $(GUI_TEST_OBJECT:.o=.d) $(BENCH_BIN) tests/gui_regression.exe
rm -f $(APP_OBJ) $(BIN) $(TEST_BIN) $(GUI_BIN) $(GUI_RES) $(CLI_RES) $(GUI_OBJECTS) $(GUI_OBJECTS:.o=.d) $(GUI_TEST_OBJECT) $(GUI_TEST_OBJECT:.o=.d) $(BENCH_BIN) tests/gui_regression.exe
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ The project includes two front ends built on one shared parsing and analysis cor
The interface screenshots above show the real application UI.
The graph previews below were generated from the bundled sample dataset in [`lvm_files_for_tests`](lvm_files_for_tests).

## Code signing policy

Free SignPath Foundation signing integration is prepared; enrollment and the
first signed release are pending. Current downloads are not automatically signed.
See the [code signing policy and activation steps](docs/CODE_SIGNING.md).

## Quick Start

### For end users
Expand Down
6 changes: 6 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ handled separately by filling with the channel mean, without shifting channels.
The screenshots above show the real application interface.
The graph previews below are based on the bundled sample input files from [`lvm_files_for_tests`](lvm_files_for_tests).

## Code signing policy

Free SignPath Foundation signing integration is prepared; provider acceptance
and the first signed release are pending. See the [code signing policy](docs/CODE_SIGNING.md).
Existing downloads must not be assumed to have a trusted signature.

## Quick Start

### Download a ready-to-run build
Expand Down
6 changes: 6 additions & 0 deletions README_RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ FFT принимает неравномерные и округлённые ме
Скриншоты выше показывают реальный интерфейс программы.
Превью графиков ниже построены по реальным данным из папки [`lvm_files_for_tests`](lvm_files_for_tests).

## Code signing policy / Подпись программы

Подготовлена бесплатная интеграция SignPath Foundation. Одобрение проекта и
первый подписанный выпуск ещё требуются. [Политика подписи](docs/CODE_SIGNING.md)
описывает подключение и проверку; прежние скачивания не становятся подписанными автоматически.

## Быстрый старт

### Готовая сборка
Expand Down
5 changes: 4 additions & 1 deletion build_cli.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ if (-not (Get-Command g++ -ErrorAction SilentlyContinue)) {
$version = git -c core.excludesFile= describe --tags --always --dirty 2>$null
if (-not $version) { $version = 'dev' }
$versionDefine = '-DAPP_VERSION=\"' + $version + '\"'
& g++ -std=c++17 -O2 -Wall -Wextra -finput-charset=UTF-8 -static $versionDefine -o lvm_reader.exe main.cpp lvm_parser.cpp recording_source.cpp recording_overview.cpp overview_worker.cpp disk_cache.cpp scan_index_cache.cpp data_io.cpp filter_engine.cpp spectrum_worker.cpp fft.cpp analysis.cpp disk_spectrum.cpp frf_stream.cpp frf_analysis.cpp -lshell32
. (Join-Path $PSScriptRoot 'build_version_resource.ps1')
$resourceObject = Join-Path $PSScriptRoot '.build/cli/version.o'
Build-VersionResource -Version $version -OriginalFilename 'lvm_reader.exe' -OutputPath $resourceObject -Cli
& g++ -std=c++17 -O2 -Wall -Wextra -finput-charset=UTF-8 -static $versionDefine -o lvm_reader.exe main.cpp lvm_parser.cpp recording_source.cpp recording_overview.cpp overview_worker.cpp disk_cache.cpp scan_index_cache.cpp data_io.cpp filter_engine.cpp spectrum_worker.cpp fft.cpp analysis.cpp disk_spectrum.cpp frf_stream.cpp frf_analysis.cpp $resourceObject -lshell32
if ($LASTEXITCODE -ne 0) { throw "CLI build failed (exit $LASTEXITCODE)." }
Write-Host "Built lvm_reader.exe ($version)"
4 changes: 2 additions & 2 deletions build_gui.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ if ($Test) {
$outName = Join-Path ([IO.Path]::GetFullPath($OutputDirectory)) $outName
}
$resourceObject = Join-Path $objectDir 'AM_logo_res.o'
& windres -O coff -i AM_logo.rc -o $resourceObject
if ($LASTEXITCODE -ne 0) { throw 'Resource compilation failed.' }
. (Join-Path $PSScriptRoot 'build_version_resource.ps1')
Build-VersionResource -Version $version -OriginalFilename "AMSignal-$releaseVersion-x64.exe" -OutputPath $resourceObject
& g++ -municode -static -mwindows -o $outName $resourceObject @objects @libraries
}
if ($LASTEXITCODE -ne 0) { throw "Link failed: $outName (exit $LASTEXITCODE)." }
Expand Down
27 changes: 27 additions & 0 deletions build_version_resource.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Shared Windows metadata for the two production executables. No runtime code.
function Build-VersionResource {
param([string]$Version, [string]$OriginalFilename, [string]$OutputPath, [switch]$Cli)
$releaseVersion = $Version -replace '^v', ''
if ($releaseVersion -notmatch '^[A-Za-z0-9][A-Za-z0-9.+-]{0,127}$' -or
$OriginalFilename -notmatch '^[A-Za-z0-9][A-Za-z0-9_.+-]*\.exe$') {
throw 'Unsafe executable version or filename.'
}
$numbers = '0,0,0,0'
if ($releaseVersion -match '^(\d+)\.(\d+)\.(\d+)(?:[.+-]|$)') {
$parts = @($Matches[1], $Matches[2], $Matches[3])
foreach ($part in $parts) { if ([uint64]$part -gt 65535) { throw 'Windows version component exceeds 65535.' } }
$numbers = ($parts -join ',') + ',0'
}
New-Item -ItemType Directory -Force -Path (Split-Path $OutputPath -Parent) | Out-Null
$resource = if ($Cli) { 'AM_version.rc' } else { 'AM_logo.rc' }
# Write literal C strings once; avoid quoting through PowerShell -> windres -> cpp.
$lines = @(('#define AM_VERSION_STRING "' + $releaseVersion + '"'),
('#define AM_VERSION_NUMBERS ' + $numbers),
('#define AM_ORIGINAL_FILENAME "' + $OriginalFilename + '"'))
if ($Cli) { $lines += '#define AM_CLI 1' }
$lines += '#include "' + $resource + '"'
$generated = $OutputPath + '.rc'
[IO.File]::WriteAllLines($generated, $lines, [Text.Encoding]::ASCII)
& windres -I $PSScriptRoot -O coff -i $generated -o $OutputPath
if ($LASTEXITCODE -ne 0) { throw "Version resource compilation failed ($LASTEXITCODE)." }
}
Loading
Loading