Skip to content

aspire-managed unified binary + native certificate management#14441

Draft
davidfowl wants to merge 14 commits intomainfrom
davidfowl/aspire-managed
Draft

aspire-managed unified binary + native certificate management#14441
davidfowl wants to merge 14 commits intomainfrom
davidfowl/aspire-managed

Conversation

@davidfowl
Copy link
Member

@davidfowl davidfowl commented Feb 11, 2026

Summary

Consolidates the Aspire bundle managed components into a single self-contained binary (aspire-managed) and moves HTTPS certificate management natively into the CLI.

What changed

1. aspire-managed unified binary (src/Aspire.Managed/)

Single self-contained executable replacing 3 separate managed binaries + shared .NET runtime:

aspire-managed dashboard [args...]   # Aspire Dashboard
aspire-managed server [args...]      # AppHost Server (RemoteHost)
aspire-managed nuget [args...]       # NuGet search/restore/layout
  • References Dashboard, RemoteHost, and NuGetHelper as project references
  • Publishes as single-file self-contained (no separate runtime/ directory needed)
  • Zero changes to the referenced projects dispatches to their entry points

2. Native certificate management (src/Aspire.Cli/Certificates/CertificateGeneration/)

Ported ASP.NET Core CertificateManager library directly into the native AOT CLI:

  • Vendored 8 files from aspnetcore/src/Shared/CertificateGeneration/
  • Replaced EventSource (AOT-incompatible) with ILogger
  • Removed statics: CertificateManager.Create(ILogger) factory + instance Log property
  • NativeCertificateToolRunner calls CertificateManager directly no subprocess spawn
  • Deleted BundleCertificateToolRunner and SdkCertificateToolRunner

3. Bundle layout simplification

Before: runtime/ + dashboard/ + aspire-server/ + tools/aspire-nuget/ + tools/dev-certs/ (~172 MB across 5 directories)

After: managed/aspire-managed (~65 MB single binary including .NET runtime)

Certificate management is native to the CLI no external tool needed.

4. CreateLayout + Bundle.proj updates

  • Simplified to build aspire-managed + copy DCP (no SDK download/extraction step)
  • Updated layout.json schema: managed replaces runtime, dashboard, apphostServer, nugetHelper, devCerts
  • ASPIRE_MANAGED_PATH replaces ASPIRE_DASHBOARD_PATH and ASPIRE_RUNTIME_PATH

Smoke test results

Scenario Result
aspire doctor Certificate check works (native CertificateManager)
aspire run (TestShop.AppHost) Full E2E: cert check, build, connect, dashboard
aspire-managed dashboard Dashboard starts and serves
aspire-managed server AppHost server starts
aspire-managed nuget search Returns packages from nuget.org
Build with TreatWarningsAsErrors 0 warnings, 0 errors

What is next

  • Cross-platform testing (Linux, macOS)
  • Size optimization (trimming aspire-managed)
  • Multi-platform build workflow

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14441

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14441"

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

🎬 CLI E2E Test Recordings

The following terminal recordings are available for commit a33d43c:

Test Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateEmptyAppHostProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateStartWaitAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
ResourcesCommandShowsRunningResources ▶️ View Recording

📹 Recordings uploaded automatically from CI run #21938346146

…dashboard, server, nuget, and runtime components

- Replace 3 separate framework-dependent publishes (Dashboard, RemoteHost, NuGetHelper) with single self-contained Aspire.Managed binary
- Remove bundled .NET runtime download — aspire-managed embeds the runtime
- Remove dev-certs DLL bundling — fall back to global dotnet dev-certs
- Simplify CreateLayout: single CopyManagedAsync replaces 4 copy methods + runtime download
- Simplify bundle layout: managed/ + dcp/ only (no runtime/, dashboard/, aspire-server/, tools/)
- Update all CLI process launching to use aspire-managed with subcommands (server, nuget, dashboard)
- Update DashboardEventHandlers to detect aspire-managed and inject dashboard subcommand
- Update localhive scripts with --skip-bundle and --native-aot support
Port the CertificateGeneration shared source from aspnetcore into
aspire-managed, eliminating the dependency on 'dotnet dev-certs' for
bundle mode certificate operations.

Changes:
- Copy 8 CertificateGeneration files from aspnetcore/src/Shared/ into
  src/Aspire.Managed/CertificateGeneration/ (vendored shared source)
- Add DevCertsCommand with full dotnet dev-certs parity: check-trust,
  check, trust, clean, import, export
- Add 'dev-certs' subcommand dispatch to aspire-managed Program.cs
- Recreate BundleCertificateToolRunner to invoke aspire-managed dev-certs
  instead of dotnet dev-certs
- Restore bundle vs SDK branching for ICertificateToolRunner in CLI
  Program.cs
- Suppress SYSLIB0057/IDE1006 warnings for vendored aspnetcore code
@davidfowl davidfowl force-pushed the davidfowl/aspire-managed branch from 61b8f76 to 4ca1a0a Compare February 11, 2026 15:54
…ntSource

- Move 8 CertificateGeneration files from Aspire.Managed to Aspire.Cli/Certificates/CertificateGeneration/
- Replace CertificateManagerEventSource (AOT-incompatible) with CertificateManagerLogger backed by ILogger
- Add NativeCertificateToolRunner that calls CertificateManager.Instance directly (no subprocess)
- Delete BundleCertificateToolRunner and SdkCertificateToolRunner (subprocess spawners)
- Remove dev-certs command and CertificateGeneration from aspire-managed
- Clean up aspire-managed csproj warning suppressions
- Replace static Instance property with Create(ILogger) factory method
- Make Log an instance property initialized from constructor ILogger
- Add ILogger parameter to CertificateManager and subclass constructors
- NativeCertificateToolRunner takes CertificateManager via DI
- Register CertificateManager as singleton in Program.cs
@davidfowl davidfowl changed the title POC: aspire-managed unified bootstrapper aspire-managed unified binary + native certificate management Feb 11, 2026

namespace Microsoft.AspNetCore.Certificates.Generation;

internal abstract class CertificateManager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uggh do we really want to dupe all this code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not ideal. The alternatives are:

  1. Keep spawning dotnet dev-certs - requires SDK on the machine, adds ~2s startup latency per check, and the subprocess approach is fragile
  2. Reference aspnetcore's shared source via submodule/source package - they don't ship this as a package, it's internal shared source
  3. Vendor it (current approach) - one-time copy, ~2,700 lines, stable code that rarely changes

The vendored code is the same approach aspnetcore uses internally (they share it across projects via shared source). The code is very stable - last meaningful change was the V6 cert version bump. Happy to discuss alternatives if you have ideas.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have plans to replace this with our own implementation as soon as @danegsta and @DamianEdwards are ready.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I planned on going the vendor approach as the basis of our implementation for the same reasons you outlined. I figure we go with a few implementation details that'll make it easier for us to keep up-to-date with the upstream code to take any fixes:

First we'll make minimal changes to CertificateManager to support generating with our assigned OID and to create a CA capable root certificate, all Aspire specific extensions of the base logic (generating intermediate certificate, leaf certificate, etc.) will go into a derived class. For the OS specific implementations we'll make them partial classes so that we can similarly separate the common logic we'll share with ASP.NET Core from our specific features.

We won't ever be able to naively copy and paste upstream changes, but we can minimize the pain as much as possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main source of drift from CertificateManager in our current plan is that we'll implement a full CA chain (root, intermediate, leaf certificates) vs. the ASP.NET Core model of a single self-signed root. There's an approved reference design we have to start with, but I don't doubt that we'll need to get approval for some tweaks to make things make sense for Aspire as the approved reference design is very aggressive for how long certificates are valid which will be a pain to integrate with persistent containers.

<DotNetSdkCurrentVersionForTesting>9.0.306</DotNetSdkCurrentVersionForTesting>
<!-- .NET SDK version to include in the bundle for running managed components -->
<BundleRuntimeVersion>10.0.102</BundleRuntimeVersion>
<!-- BundleRuntimeVersion removed: aspire-managed is self-contained, no separate runtime needed -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

eng/Bundle.proj Outdated
<BundleVersion Condition="'$(BundleVersion)' == ''">$(VersionPrefix)-dev</BundleVersion>


<!-- VersionSuffix must be forwarded explicitly because Exec invocations are outer MSBuild processes,
Copy link
Member

@eerhardt eerhardt Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following this reasoning. Does someone manually overwrite VersionSuffix when building?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified the comment. The _VersionSuffixArg is used when Bundle.proj invokes dotnet publish via <Exec> - MSBuild properties from the outer process don't flow to child dotnet invocations, so we need to pass /p:VersionSuffix=... explicitly. In CI, VersionSuffix comes from the pipeline (e.g. pr.14441.g0f27cc2). Locally it's empty so BundleVersion falls back to -dev.


var dashboardPath = _layout.GetDashboardPath();
if (dashboardPath is not null)
// Set ASPIRE_DASHBOARD_PATH to the aspire-managed exe (DashboardEventHandlers will detect it)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to set this env var?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI launches the AppHost as a child process. The AppHost needs to know where the dashboard binary is so it can start it via DCP. The env var is the existing mechanism - DcpOptions reads ASPIRE_DASHBOARD_PATH and passes it to DashboardEventHandlers.AddDashboardResource(). This was already the pattern before this PR (the CLI set it to the dashboard exe in the old layout). Updated the comment to be clearer about the purpose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants