Commands are relative to the repository root.
- Windows 10 version 2004 (build 19041) or later for Windows builds
- glibc-based x64 or ARM64 Linux for Linux builds
- macOS 13+ with Xcode CLT for macOS NativeAOT and
.apppackaging - .NET SDK selected by
global.json - Visual Studio 2022/2026 Build Tools with Desktop C++ for NativeAOT linking
- WinApp CLI 0.6.0 for MSIX creation and validation
- A trusted code-signing certificate for distributable MSIX artifacts
Windows local sessions use ConPTY. Linux and macOS local sessions use the
bundled forkpty relay. The Avalonia shell, settings, renderer, and terminal
engines are shared.
CI workflows:
build-ghostty.yml— compilelibghostty-vtfor every RID and upload artifacts (optional cache; not required to develop).build-terminal.yml— restore natives from source, test, NativeAOT, Linux packages, macOS.app/zip, MSIX.
dotnet restore Devolutions.Terminal.slnx
dotnet build Devolutions.Terminal.slnx -c Debug
dotnet test Devolutions.Terminal.slnx -c Release
dotnet run --project src/Devolutions.TerminalWarnings are errors for production projects. The trim and NativeAOT analyzers run continuously rather than only during release packaging.
dotnet publish src/Devolutions.Terminal -c Release -r win-x64 --self-contained `
-o artifacts/native/win-x64
dotnet publish src/Devolutions.Terminal -c Release -r win-arm64 --self-contained `
-o artifacts/native/win-arm64Each publish directory contains:
Devolutions.Terminal.exe— GUI host and broker primarydt.exe— console command-line/broker client- Avalonia/Skia native dependencies
- bundled Noto Color Emoji fallback and its SIL OFL notice
MSIX staging additionally builds dt-shell-integration.exe and
Devolutions.Terminal.ShellExt.dll for the package architecture with the installed
MSVC/Windows SDK toolchain.
macOS NativeAOT app bundles (Darwin only):
dotnet publish src/Devolutions.Terminal -c Release -r osx-arm64 --self-contained \
-o artifacts/native/osx-arm64
MACOS_PUBLISH_DIR="$PWD/artifacts/native/osx-arm64" \
bash scripts/Build-MacOsPackage.sh osx-arm64 0.1.0 artifacts/packages
bash scripts/Test-MacOsPackage.sh osx-arm64 artifacts/packages/*.zip
bash scripts/Test-MacOsRuntime.sh artifacts/packagesThe staged Devolutions Terminal.app contains Devolutions.Terminal, dt,
dt-pty-host, libghostty-vt.dylib, Skia, HarfBuzz, Info.plist, and an
ad-hoc signed icon. Notarization, DMG, and Homebrew are not part of this gate.
Linux package formats:
scripts/Build-LinuxPackage.sh linux-x64 0.1.0 artifacts/packages all
scripts/Build-LinuxPackage.sh linux-arm64 0.1.0 artifacts/packages allThese commands publish NativeAOT executables and create reproducible tar, DEB,
RPM, and AppImage artifacts plus a SHA-256 manifest. Every format is staged from
one canonical filesystem root and includes Devolutions.Terminal, dt,
dt-pty-host, Ghostty, Skia, HarfBuzz, licenses, freedesktop metadata, icons,
the reversible integration helper, a sorted inventory, and an SPDX 2.3 SBOM.
Set SOURCE_DATE_EPOCH for release reproducibility. To reuse a separately
validated publish, set LINUX_PUBLISH_DIR.
ar, GNU tar, readelf, file, sha256sum, and Python 3 are required.
RPM builds additionally require rpmbuild; AppImage builds require
mksquashfs and a pinned, architecture-matched type-2 runtime in
APPIMAGE_RUNTIME_FILE; the builder never downloads a tool or runtime.
Validation of RPM and AppImage extraction requires rpm2cpio/cpio and
unsquashfs. The scripts fail before packaging with distribution-specific
installation guidance when a tool is absent.
Validate Linux desktop assets without launching the application:
scripts/Test-LinuxDesktopIntegration.sh
bash scripts/Test-LinuxPackagingMetadata.sh
bash scripts/Test-LinuxPackage.sh linux-x64 artifacts/packages/*-linux-x64.*
bash scripts/Test-LinuxArm64Runtime.sh artifacts/packagesThe test stages an install under artifacts, checks desktop/AppStream
metadata and all icon sizes, exercises a custom installed executable path,
simulates reversible protocol and xdg-terminal-exec registration, and verifies
uninstall removes every owned file. When installed, desktop-file-validate and
appstreamcli provide additional schema validation.
Test-LinuxArm64Runtime.sh must run on native Linux ARM64 (uname -m equal to
aarch64 or arm64) and deliberately rejects x64 and QEMU-based
cross-execution. It runs only non-UI gates: NativeAOT dt startup/parser,
Ghostty ABI and feed, the built-in engine, real forkpty lifecycle, broker
concurrency, Linux profile/XDG discovery, and package lifecycle under a
disposable root. CI uses the exact GitHub-hosted runner label
ubuntu-24.04-arm in job linux-arm64-hardware. If that label is unavailable
to a fork or plan, configure a native Linux ARM64 self-hosted runner and change
only runs-on; do not fall back to emulation because the script's architecture
guard is part of the release gate.
AppImage validation checks its ARM64 runtime, extracts its SquashFS payload
without mounting/FUSE, and executes the embedded dt. AppRun starts the GUI
host and is intentionally not executed in display-free CI; live AppImage UI
startup remains deferred to the final UI gate.
For package staging, the helper never updates host caches or configuration:
DESTDIR="$PWD/package-root" \
linux/Install-LinuxDesktopIntegration.sh install --prefix /usr
linux/Install-LinuxDesktopIntegration.sh uninstall \
--destdir "$PWD/package-root" --prefix /usrSmoke the x64 output:
.\scripts\Test-PublishedApp.ps1 `
-Executable .\artifacts\native\win-x64\Devolutions.Terminal.exeThe port accepts Windows Terminal's modern and legacy settings shapes, including comments and trailing commas. It resolves:
- Embedded defaults
- Generated PowerShell, cmd, WSL, SSH, and Visual Studio profiles
- Extension fragments
- User settings
Set WT_DOTNET_SETTINGS_PATH to test an existing file without replacing it:
$env:WT_DOTNET_SETTINGS_PATH = "$env:LOCALAPPDATA\Packages\Microsoft.Devolutions.Terminal_8wekyb3d8bbwe\LocalState\settings.json"
dotnet run --project src/Devolutions.TerminalUse a copy when evaluating editor saves. The editor canonicalizes comments and
whitespace while retaining unknown/local-layer data. Runtime state is stored in
state.json beside the selected settings file.
Create unsigned x64 and ARM64 packages:
.\src\Devolutions.Terminal.Package\Scripts\Build-Packages.ps1 -Version 0.1.0.0
$packages = Get-ChildItem .\artifacts\msix\packages\*.msix
.\src\Devolutions.Terminal.Package\Scripts\Test-Packages.ps1 `
-PackagePath $packages.FullNameDevelopment signing:
$password = Read-Host "Certificate password" -AsSecureString
.\src\Devolutions.Terminal.Package\Scripts\New-DevelopmentCertificate.ps1 `
-OutputDirectory .\artifacts\msix\certificates
.\src\Devolutions.Terminal.Package\Scripts\Sign-Packages.ps1 `
-PackageDirectory .\artifacts\msix\packages `
-CertificatePath .\artifacts\msix\certificates\Devolutions.Terminal.pfx `
-Version 0.1.0.0Build a WiX-based MSI package for the same published Windows outputs:
.\src\Devolutions.Terminal.Package\Scripts\Build-Msi.ps1 `
-Architectures x64,arm64 `
-Version 0.1.0.0 `
-OutputDirectory .\artifacts\msiThe MSI project is in src/Devolutions.Terminal.Installer and uses a fixed
UpgradeCode with per-machine install scope under ProgramFiles6432Folder.
Never commit PFX files, passwords, certificate private keys, or signed internal artifacts. CI produces unsigned packages unless a protected release environment injects signing credentials.
The release workflow in .github/workflows/build-terminal.yml publishes signed
Windows packages and the corresponding platform archives directly to GitHub
Releases without staging them in OneDrive. It builds unsigned per-architecture
MSIX and MSI packages for Windows x64 and ARM64, then signs them on the Linux
release runner with Devolutions psign-tool and Azure Artifact Signing
(Trusted Signing). The private key never lands on the runner. Signed Windows
packages are uploaded alongside Linux and macOS archives. The workflow is
intended for tag-based releases and for manual dispatch.
Required secrets:
ARTIFACT_SIGNING_ENDPOINTARTIFACT_SIGNING_ACCOUNT_NAMEARTIFACT_SIGNING_PROFILE_NAMEAZURE_TENANT_IDCODE_SIGNING_CLIENT_IDCODE_SIGNING_CLIENT_SECRET
Optional repository variable:
CODE_SIGNING_TIMESTAMP_SERVER(defaults tohttp://timestamp.acs.microsoft.com/)
psign-tool portable Artifact Signing signs the per-architecture .msix and
.msi files. The MSIX Publisher identity in Package.appxmanifest must
match the Artifact Signing certificate subject.
- Regenerate
compat/windows-terminal.jsonand review inventory changes. - Run the full Release solution tests with no failures or unconditional skips.
- Publish and launch-smoke NativeAOT x64.
- Cross-publish NativeAOT ARM64.
- Build and structurally validate both MSIX packages, including shell-helper PE architecture, SHA-256 manifests, COM/Explorer extensions, and notices.
- Sign and verify package publisher/identity/version in a protected environment.
- Install, launch
Devolutions.Terminal.exe, invokedt.exe, upgrade, and uninstall on clean x64 and ARM64 Windows VMs. - Run multi-tab/pane, settings, CLI forwarding, UIA, Unicode/CJK/emoji, VT, ConPTY cancellation, and long-output stress workflows.
- Compare startup time, working set, renderer allocations, and artifact size with the previous candidate.
- Review diagnostics and documentation for intentional platform differences.
- Build all four Linux formats for x64 and ARM64, run
scripts/Test-LinuxPackage.shover each artifact, compare a repeated x64 build's SHA-256 manifest, and runscripts/Test-LinuxDesktopIntegration.sh. The validator checks every ELF architecture/dependency/debug section, metadata, paths, modes, licenses, inventory/SBOM hashes, desktop/protocol declarations, and idempotent install/uninstall behavior without launching the UI. Thelinux-arm64-hardwarejob must then pass on the nativeubuntu-24.04-armrunner before release. - Verify Windows global-hotkey collision diagnostics, settings re-registration, named broker summon, current/mouse monitor placement, quake sizing, dropdown completion, and the native system menu. These are protected live-UI checks; unit/headless validation does not register a desktop shortcut or display a window.
- Public out-of-process ConPTY can filter or alter DCS/APC payloads on some Windows builds. Sixel works when the selected connection passes DCS bytes through unchanged.
- Avalonia 12 exposes the terminal as a readable UIA Document/Value provider, but does not provide a public bridge for native UIA TextPattern/TextPattern2 or LiveSetting events. Managed ranges and visible notification text remain available.
- Azure Cloud Shell requires a public-client application ID and host-provided device-code/tenant UI. No client secret is embedded.
- The development package identity and
dt.exealias can conflict with an installed Microsoft Windows Terminal alias; Windows alias settings choose the active provider. - The notification-area icon and minimize-to-area behavior work packaged and
unpackaged. MSIX builds include architecture-matched Explorer, jump-list, and
toast helpers. Packaged operations use
<package-family-name>!Terminal. Unpackaged system toasts are supported only whenWT_DOTNET_AUMIDnames an AUMID registered by a Start-menu shortcut andWT_DOTNET_TOAST_SHORTCUTpoints to that.lnk; the shortcut and COM/sparse-package registration must also name toast activatora3aeb121-45d9-4cd9-a278-4b43d19b95b1. Otherwise the diagnostic is explicit. Default-terminal registration still requires the unbundled OpenConsole handoff v3 proxy/stub and host, so only its versioned diagnostic boundary exists and no incomplete manifest extension is registered. - System-toast activation payloads contain no secrets and accept only protocol
version 1, a GUID notification id,
focus, anduse-anyor a positive window id before authenticated broker routing. - Linux portal calls are bounded to three seconds and fall back to
xdg-openornotify-send. Basic system notifications are supported; portal notification actions/activation are not. Tray availability is owned by the Avalonia desktop backend and has no reliable freedesktop capability probe. - Windows global summon uses
RegisterHotKeythrough a source-generatedLibraryImportboundary and reports collisions per binding. Public, reliable virtual-desktop movement is unavailable, so desktop movement is best-effort and diagnosed. Linux broker/manual summon and quake placement remain available, but cross-desktop shortcut registration is explicitly unsupported until a reflection-free interactive freedesktop GlobalShortcuts portal session provider is bundled. ToggleShaderEffectssupports only the bounded Skia retro/scanline pass selected byexperimental.retroTerminalEffect. Arbitrary custom HLSL fromexperimental.pixelShaderPathremains unsupported.xdg-terminal-execis preferred for an explicit per-user default-terminal choice. Debianupdate-alternativesis available only through an explicit, reversible administrator action. Other distro-specific default-terminal registries are unsupported.- Linux package creation and validation do not alter live protocol/default terminal choices. Those remain explicit, reversible helper actions after installation.
Assembly versions derive from VersionPrefix in Directory.Build.props.
Package versions use four numeric components. CI uses
0.1.<run-number>.0; release automation must set the final package version
explicitly and must never reuse a published MSIX version.