diff --git a/src-tauri/client-cli/src/brand.rs b/src-tauri/client-cli/src/brand.rs index 3883327cb..4cf96e55e 100644 --- a/src-tauri/client-cli/src/brand.rs +++ b/src-tauri/client-cli/src/brand.rs @@ -3,7 +3,12 @@ //! Shown when invoked with no arguments or with `--help`. //! Suppressed for `--version` (which must stay grep-friendly). //! -//! Two assets: +//! The logo is emitted on non-Windows platforms only. Its art uses +//! fine-grained Unicode block glyphs (eighths/quadrant blocks) that many +//! Windows console fonts can't render, so Windows shows just the +//! copyright + version line. +//! +//! Two assets (non-Windows): //! - assets/logo-color.ansi -- ANSI block-character art, //! used when stdout is an interactive TTY //! - assets/logo-mono.txt -- plain ASCII fallback (no ANSI), @@ -11,16 +16,20 @@ //! //! Both assets are embedded at compile time via `include_str!`. +#[cfg(not(windows))] use owo_colors::{OwoColorize, Stream}; +#[cfg(not(windows))] const LOGO_COLOR: &str = include_str!("../assets/logo-color.ansi"); +#[cfg(not(windows))] const LOGO_MONO: &str = include_str!("../assets/logo-mono.txt"); const COPYRIGHT: &str = "Copyright (C) 2026 Defguard Sp. z o.o."; -/// Print logo + copyright + project name/version to stdout. Picks -/// the colored variant on an interactive TTY, the mono fallback +/// Print logo + copyright + project name/version to stdout. Picks the +/// colored logo variant on an interactive TTY and the mono fallback /// otherwise (so `defguard-cli --help | cat` stays clean ASCII). +#[cfg(not(windows))] pub fn print_banner() { // owo-colors' supports-colors detection drives the choice: if // stdout supports color, emit the ANSI variant; otherwise mono. @@ -38,13 +47,22 @@ pub fn print_banner() { let project = common::version_string("defguard-cli"); if use_color { - let p = project.bright_yellow().bold().to_string(); - let c = COPYRIGHT.dimmed().to_string(); - println!(" {p}"); - println!(" {c}"); + println!(" {}", project.bright_yellow().bold()); + println!(" {}", COPYRIGHT.dimmed()); } else { println!(" {project}"); println!(" {COPYRIGHT}"); } println!(); } + +/// Print copyright + project name/version to stdout. The logo is skipped +/// on Windows: its art relies on Unicode block glyphs that many Windows +/// console fonts can't render, and the console may not interpret ANSI. +#[cfg(windows)] +pub fn print_banner() { + let project = common::version_string("defguard-cli"); + println!(" {project}"); + println!(" {COPYRIGHT}"); + println!(); +} diff --git a/src-tauri/resources-windows/fragments/cli.wxs b/src-tauri/resources-windows/fragments/cli.wxs new file mode 100644 index 000000000..a77d0b484 --- /dev/null +++ b/src-tauri/resources-windows/fragments/cli.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src-tauri/resources-windows/scripts/Sign-Binaries.ps1 b/src-tauri/resources-windows/scripts/Sign-Binaries.ps1 new file mode 100644 index 000000000..5321d4240 --- /dev/null +++ b/src-tauri/resources-windows/scripts/Sign-Binaries.ps1 @@ -0,0 +1,55 @@ +$ErrorActionPreference = 'Stop' + +$tauriConfigPath = Join-Path $PSScriptRoot '..\..\tauri.conf.json' +$tauriConfig = Get-Content $tauriConfigPath -Raw | ConvertFrom-Json +$windowsConfig = $tauriConfig.bundle.windows + +$thumbprint = $windowsConfig.certificateThumbprint + +$timestampUrl = if ($env:DEFGUARD_WINDOWS_TIMESTAMP_URL) { + $env:DEFGUARD_WINDOWS_TIMESTAMP_URL +} else { + $windowsConfig.timestampUrl +} + +$digestAlgorithm = if ($windowsConfig.digestAlgorithm) { + $windowsConfig.digestAlgorithm.ToUpperInvariant() +} else { + 'SHA256' +} + +if (-not $thumbprint) { + throw 'Windows certificate thumbprint is not configured.' +} + +# Resolve signtool to a plain path string. Get-Command returns a +# CommandInfo (use .Source); the Windows Kits fallback returns a +# FileInfo (use .FullName). +$signtoolPath = (Get-Command signtool.exe -ErrorAction SilentlyContinue).Source +if (-not $signtoolPath) { + $signtoolPath = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' -Recurse -Filter signtool.exe | + Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } | + Sort-Object FullName -Descending | + Select-Object -First 1 -ExpandProperty FullName +} + +if (-not $signtoolPath) { + throw 'signtool.exe was not found.' +} + +$binaries = @( + 'target\release\defguard-cli.exe', + 'target\release\defguard-service.exe' +) + +foreach ($binary in $binaries) { + if (-not (Test-Path $binary)) { + throw "Binary not found: $binary" + } + + Write-Host "Signing $binary" + & $signtoolPath sign /sha1 $thumbprint /fd $digestAlgorithm /tr $timestampUrl /td $digestAlgorithm $binary + if ($LASTEXITCODE -ne 0) { + throw "Failed to sign $binary" + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index da3a94ad9..23f210085 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -30,10 +30,12 @@ "dialogImagePath": "./resources-windows/msi/side_banner.png", "fragmentPaths": [ "./resources-windows/fragments/service.wxs", + "./resources-windows/fragments/cli.wxs", "./resources-windows/fragments/provisioning.wxs" ], "componentRefs": [ "DefguardServiceFragment", + "DefguardCliFragment", "ProvisioningScriptFragment" ], "template": "./resources-windows/msi/main.wxs" diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index e7d92d08f..1644c0a1c 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -1,4 +1,10 @@ { + "build": { + "beforeBundleCommand": { + "cwd": ".", + "script": "powershell -ExecutionPolicy Bypass -File ./resources-windows/scripts/Sign-Binaries.ps1" + } + }, "bundle": { "targets": ["msi"], "resources": [