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
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2
updates:
- package-ecosystem: nuget
directory: /src/ARSVIN
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:

jobs:
build-test:
name: Build and test
name: Build, test, and validate site
runs-on: windows-latest
steps:
- name: Checkout
Expand All @@ -23,6 +23,10 @@ jobs:
with:
dotnet-version: 8.0.x

- name: Validate public site
shell: pwsh
run: .\scripts\validate-public-site.ps1

- name: Restore Publisher
run: dotnet restore src/ARSVIN/ARSVIN.csproj

Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ permissions:

jobs:
analyze:
name: Analyze C#
name: Analyze C# solution
runs-on: windows-latest
steps:
- name: Checkout
Expand All @@ -31,10 +31,11 @@ jobs:
languages: csharp
build-mode: manual

- name: Build
run: |
dotnet restore src/ARSVIN/ARSVIN.csproj
dotnet build src/ARSVIN/ARSVIN.csproj -c Release --no-restore
- name: Restore solution
run: dotnet restore ARSVIN.sln

- name: Build solution
run: dotnet build ARSVIN.sln -c Release --no-restore

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
151 changes: 144 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ on:
push:
tags:
- 'v*.*.*'
pull_request:
branches: [ main ]
paths:
- '.github/workflows/release.yml'
- 'installer/**'
- 'scripts/publish-release.ps1'
- 'publish-win-x64.ps1'
- 'build.ps1'
- 'src/**'
- 'tests/**'
- 'ARSVIN.sln'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- 'global.json'
workflow_dispatch:
inputs:
version:
Expand All @@ -12,20 +26,23 @@ on:
default: '0.0.0-dev'

permissions:
contents: write
contents: read

concurrency:
group: release-${{ github.ref }}
group: release-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

env:
DOTNET_CLI_TELEMETRY_OPTOUT: '1'
DOTNET_NOLOGO: '1'

jobs:
release:
name: Portable EXEs and installer
package:
name: Validate portable EXEs and installer
runs-on: windows-latest
outputs:
version: ${{ steps.version.outputs.value }}
artifact_name: ${{ steps.version.outputs.artifact_name }}

steps:
- name: Checkout
Expand All @@ -47,19 +64,24 @@ jobs:
if ('${{ github.ref_type }}' -eq 'tag') {
$version = '${{ github.ref_name }}' -replace '^[vV]', ''
}
else {
elseif ('${{ github.event_name }}' -eq 'workflow_dispatch') {
$version = '${{ inputs.version }}'
if ([string]::IsNullOrWhiteSpace($version)) {
$version = '0.0.0-dev'
}
$version = $version -replace '^[vV]', ''
}
else {
$version = "0.0.0-pr.$env:GITHUB_RUN_NUMBER"
}

if ($version -notmatch '^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$') {
throw "Unsupported release version: $version"
}

$artifactName = "ARSVIN-$version-windows-x64"
"value=$version" >> $env:GITHUB_OUTPUT
"artifact_name=$artifactName" >> $env:GITHUB_OUTPUT
Write-Host "Release version: $version"

- name: Build and test
Expand Down Expand Up @@ -100,6 +122,103 @@ jobs:
throw "Inno Setup failed with exit code $LASTEXITCODE."
}

- name: Validate release artifacts
shell: pwsh
run: |
$releaseRoot = (Resolve-Path '.\artifacts\release').Path
$expected = @(
'ARSVIN-Publisher-win-x64.exe',
'ArSubsv-Subscriber-win-x64.exe',
'ARSVIN-Suite-Setup-win-x64.exe',
'ARSVIN-win-x64-portable.zip'
)

foreach ($name in $expected) {
$path = Join-Path $releaseRoot $name
if (-not (Test-Path $path -PathType Leaf)) {
throw "Expected release artifact was not found: $path"
}

if ((Get-Item $path).Length -le 0) {
throw "Release artifact is empty: $path"
}
}

Get-ChildItem $releaseRoot -File |
Select-Object Name, Length |
Sort-Object Name |
Format-Table -AutoSize

- name: Smoke-test silent installer
shell: pwsh
run: |
$setup = (Resolve-Path '.\artifacts\release\ARSVIN-Suite-Setup-win-x64.exe').Path
$installDir = Join-Path $env:RUNNER_TEMP 'ARSVIN-SmokeInstall'
$installLog = Join-Path $env:RUNNER_TEMP 'arsvin-install.log'
$uninstallLog = Join-Path $env:RUNNER_TEMP 'arsvin-uninstall.log'

if (Test-Path $installDir) {
Remove-Item $installDir -Recurse -Force
}
Remove-Item $installLog, $uninstallLog -Force -ErrorAction SilentlyContinue

$installArgs = @(
'/VERYSILENT',
'/SUPPRESSMSGBOXES',
'/NORESTART',
"/DIR=`"$installDir`"",
"/LOG=`"$installLog`""
)
$installProcess = Start-Process -FilePath $setup -ArgumentList $installArgs -Wait -PassThru

if (Test-Path $installLog) {
Get-Content $installLog
}
if ($installProcess.ExitCode -ne 0) {
throw "Silent installer failed with exit code $($installProcess.ExitCode)."
}

Write-Host 'Installed files:'
Get-ChildItem $installDir -Recurse -Force |
Select-Object FullName, Length |
Format-Table -AutoSize

$installedFiles = @(
'ARSVIN.exe',
'ArSubsv.exe',
'README.md',
'LICENSE.txt',
'VERSION.txt'
)

foreach ($name in $installedFiles) {
$path = Join-Path $installDir $name
if (-not (Test-Path $path -PathType Leaf)) {
throw "Installer smoke test could not find: $path"
}
}

$uninstaller = Get-ChildItem $installDir -Filter 'unins*.exe' -File |
Select-Object -First 1
if (-not $uninstaller) {
throw "Installer smoke test could not find an uninstaller in: $installDir"
}

$uninstallArgs = @(
'/VERYSILENT',
'/SUPPRESSMSGBOXES',
'/NORESTART',
"/LOG=`"$uninstallLog`""
)
$uninstallProcess = Start-Process -FilePath $uninstaller.FullName -ArgumentList $uninstallArgs -Wait -PassThru

if (Test-Path $uninstallLog) {
Get-Content $uninstallLog
}
if ($uninstallProcess.ExitCode -ne 0) {
throw "Silent uninstaller failed with exit code $($uninstallProcess.ExitCode)."
}

- name: Generate SHA-256 checksums
shell: pwsh
run: |
Expand All @@ -118,21 +237,39 @@ jobs:
[System.Text.UTF8Encoding]::new($false)
)

if ($lines.Count -ne 4) {
throw "Expected four checksummed release artifacts, found $($lines.Count)."
}

Get-Content (Join-Path $releaseRoot 'SHA256SUMS.txt')

- name: Upload release workflow artifact
uses: actions/upload-artifact@v7
with:
name: ARSVIN-${{ steps.version.outputs.value }}-windows-x64
name: ${{ steps.version.outputs.artifact_name }}
path: |
artifacts/release/*.exe
artifacts/release/*.zip
artifacts/release/SHA256SUMS.txt
if-no-files-found: error
retention-days: 30

release:
name: Publish GitHub Release
if: github.ref_type == 'tag'
needs: package
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download validated release artifact
uses: actions/download-artifact@v8
with:
name: ${{ needs.package.outputs.artifact_name }}
path: artifacts/release

- name: Create GitHub Release
if: github.ref_type == 'tag'
uses: softprops/action-gh-release@v3
with:
generate_release_notes: true
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ The tools are intentionally separated. Publisher evidence proves what the PC gen

## Release downloads

Every tagged release builds reproducible Windows x64 artifacts:
Every tagged release builds validated Windows x64 artifacts with stable filenames:

| Artifact | Use |
|---|---|
Expand Down
9 changes: 8 additions & 1 deletion THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Third-Party Notices

This file summarizes third-party components that ARSVIN depends on directly or expects users to install for live packet capture and transmission.
This file summarizes third-party components that ARSVIN depends on directly, includes in self-contained releases, or expects users to install for live packet capture and transmission.

## SharpPcap

Expand All @@ -22,3 +22,10 @@ Npcap is not bundled in this repository or silently installed by the ARSVIN inst
## .NET and WPF

ARSVIN is built with .NET and WPF. Self-contained release binaries include applicable .NET runtime components. See Microsoft documentation and license terms for those components.

## Inno Setup

- Purpose: Build tooling and installer runtime used to create `ARSVIN-Suite-Setup-win-x64.exe`
- Project: https://jrsoftware.org/isinfo.php

Inno Setup is not required to run the portable applications. Its license and third-party component notices apply to the generated installer as described by the Inno Setup project.
40 changes: 28 additions & 12 deletions docs/build-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ The script restores, builds, and tests:

External command exit codes are checked, so CI and local builds stop immediately when a `dotnet` command fails.

## Validate the public site

```powershell
.\scripts\validate-public-site.ps1
```

The validator checks required landing-page files, local asset references, JSON-LD blocks, web-manifest icons, sitemap canonical URL, release download filenames, and the sitemap reference in `robots.txt`.

## Build portable release artifacts

```powershell
Expand Down Expand Up @@ -91,12 +99,29 @@ The installer:
- offers an optional Publisher desktop shortcut,
- includes an uninstaller,
- preserves Apache-2.0 and third-party notices,
- warns when Npcap is not detected.
- warns interactively when Npcap is not detected,
- supports unattended installation and removal without an Npcap message box.

## GitHub Actions release flow

Workflow: `.github/workflows/release.yml`

### Pull-request validation

When release tooling, installer definitions, project files, or build configuration change, the workflow runs on the pull request and:

1. restores, builds, and tests the solution,
2. publishes both portable single-file applications,
3. creates the portable suite ZIP,
4. compiles the Inno Setup installer,
5. checks all expected artifact names and non-empty files,
6. silently installs the suite into a temporary directory,
7. verifies Publisher, Subscriber, documentation, version file, and uninstaller,
8. silently uninstalls the temporary installation,
9. generates checksums and uploads a private workflow artifact.

A pull-request run never creates a public GitHub Release.

### Tagged release

Create and push a semantic version tag:
Expand All @@ -106,16 +131,7 @@ git tag v0.3.0
git push origin v0.3.0
```

The workflow then:

1. restores, builds, and tests the solution,
2. publishes both self-contained single-file applications,
3. creates the portable suite ZIP,
4. installs Inno Setup on the Windows runner,
5. compiles the suite installer,
6. generates `SHA256SUMS.txt`,
7. uploads the files as a workflow artifact,
8. creates a GitHub Release and attaches all public artifacts.
The workflow repeats the validated packaging path, downloads the validated workflow artifact in a separate least-privilege release job, and publishes all public files to a GitHub Release.

### Manual artifact build

Expand Down Expand Up @@ -151,7 +167,7 @@ When a trusted code-signing certificate becomes available, sign the portable exe

Before tagging a public release:

- Confirm `main` CI and CodeQL are green.
- Confirm `main` CI, release validation, and CodeQL are green.
- Test Publisher dry run.
- Test live publishing only on an isolated lab link.
- Test Subscriber live capture and PCAP import.
Expand Down
7 changes: 4 additions & 3 deletions installer/ARSVIN.iss
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,15 @@ end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep = ssPostInstall) and (not NpcapInstalled()) then
if (CurStep = ssPostInstall) and (not WizardSilent) and (not NpcapInstalled()) then
begin
MsgBox(
SuppressibleMsgBox(
'ARSVIN was installed successfully.' + #13#10 + #13#10 +
'Live IEC 61850 Sampled Values capture and transmission require Npcap. ' +
'Install Npcap separately from its official website before using live network features.',
mbInformation,
MB_OK
MB_OK,
IDOK
);
end;
end;
Loading
Loading