Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b57f266
Self-extracting bundle: trailer format, lazy extraction, setup comman…
davidfowl Feb 8, 2026
7286d4d
Add version check, update --self extraction, tests, and README docs
davidfowl Feb 8, 2026
e799ac3
Centralize bundle extraction into BundleService, simplify update, upd…
davidfowl Feb 8, 2026
8c70ea9
Fix DI factories to use BundleTrailer for bundle detection
davidfowl Feb 8, 2026
5627e33
Make BundleTrailer.TryRead not throw on IO errors
davidfowl Feb 8, 2026
35ded63
Use cross-process Mutex for bundle extraction locking
davidfowl Feb 8, 2026
bf06ed3
Update bundle spec: format versioning, Mutex, checksum verification
davidfowl Feb 8, 2026
b04eebf
Replace Mutex with file lock for cross-process extraction
davidfowl Feb 9, 2026
1b2925f
Update spec: file lock instead of Mutex
davidfowl Feb 9, 2026
b2428f1
Extract FileLock into a separate class
davidfowl Feb 9, 2026
468f405
Add retry loop to FileLock for Windows compatibility
davidfowl Feb 9, 2026
918f25f
Add debug logging throughout BundleService
davidfowl Feb 9, 2026
069d598
Rewrite FileLock based on NuGet ConcurrencyUtilities pattern
davidfowl Feb 9, 2026
3678b68
Simplify FileLock to IDisposable with async-only AcquireAsync
davidfowl Feb 9, 2026
bdc864f
Replace appended trailer with embedded resource for bundle payload
davidfowl Feb 9, 2026
e1aa8d0
Fix ConfigureAwait in CreateLayout, add BundleRuntimePath option to B…
davidfowl Feb 9, 2026
ca459df
Remove CLI from bundle layout - the native AOT binary IS the CLI
davidfowl Feb 9, 2026
1a483a9
Validate symlink targets during tar extraction to prevent path traversal
davidfowl Feb 9, 2026
181853b
Delete unused BundleTrailer.cs
davidfowl Feb 9, 2026
16fb6c6
Forward VersionSuffix to CLI publish in Bundle.proj
davidfowl Feb 9, 2026
66fe47b
Address review comments: timeout, permissions, dedup, hardening, dead…
davidfowl Feb 10, 2026
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
56 changes: 8 additions & 48 deletions .github/workflows/build-bundle.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Build Bundle (Reusable)

# This workflow creates the Aspire CLI bundle by:
# 1. Downloading the native CLI from the CLI archives workflow
# 2. Building/publishing managed components (Dashboard, NuGetHelper, AppHostServer)
# 3. Assembling everything into a bundle using CreateLayout
# 1. Building/publishing managed components (Dashboard, NuGetHelper, AppHostServer)
# 2. Creating the tar.gz archive from the layout
# 3. AOT-compiling the CLI with the archive as an embedded resource

on:
workflow_call:
Expand All @@ -21,56 +21,26 @@ jobs:
targets:
- rid: linux-x64
runner: 8-core-ubuntu-latest # Larger runner for bundle disk space
archive_ext: tar.gz
cli_exe: aspire
- rid: win-x64
runner: windows-latest
archive_ext: zip
cli_exe: aspire.exe
- rid: osx-arm64
runner: macos-latest
archive_ext: tar.gz
cli_exe: aspire

steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

# Download CLI archive from previous workflow - this avoids rebuilding native CLI
- name: Download CLI archive
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: cli-native-archives-${{ matrix.targets.rid }}
path: artifacts/packages

# Download RID-specific NuGet packages (for DCP)
- name: Download RID-specific NuGets
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: built-nugets-for-${{ matrix.targets.rid }}
path: artifacts/packages/Release/Shipping

# Extract CLI archive to expected location so CreateLayout can find it
- name: Extract CLI archive
shell: pwsh
run: |
$rid = "${{ matrix.targets.rid }}"
$ext = if ($rid -eq "win-x64") { "zip" } else { "tar.gz" }
$destDir = "artifacts/bin/Aspire.Cli/Release/net10.0/$rid/native"
New-Item -ItemType Directory -Path $destDir -Force | Out-Null

$archive = Get-ChildItem -Path artifacts/packages -Recurse -Filter "aspire-cli-$rid-*.$ext" | Select-Object -First 1
if (-not $archive) {
Write-Error "CLI archive not found for $rid"
exit 1
}
Write-Host "Extracting $($archive.FullName) to $destDir"

if ($ext -eq "zip") {
Expand-Archive -Path $archive.FullName -DestinationPath $destDir -Force
} else {
tar -xzf $archive.FullName -C $destDir
}
Get-ChildItem $destDir

# Build bundle directly via MSBuild - skips native CLI build, uses pre-extracted CLI
# Build bundle: managed projects → tar.gz → AOT compile CLI with embedded payload
- name: Build bundle
shell: pwsh
run: |
Expand All @@ -79,7 +49,6 @@ jobs:
/restore `
/p:Configuration=Release `
/p:TargetRid=${{ matrix.targets.rid }} `
/p:SkipNativeBuild=true `
/p:ContinuousIntegrationBuild=true `
/bl:${{ github.workspace }}/artifacts/log/Release/Bundle.binlog `
${{ inputs.versionOverrideArg }}
Expand All @@ -89,19 +58,10 @@ jobs:
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: aspire-bundle-${{ matrix.targets.rid }}
path: artifacts/bundle/${{ matrix.targets.rid }}
path: artifacts/bin/Aspire.Cli/Release/net10.0/${{ matrix.targets.rid }}/native/${{ matrix.targets.cli_exe }}
retention-days: 15
if-no-files-found: error

- name: Upload bundle archive
if: success()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: aspire-bundle-archive-${{ matrix.targets.rid }}
path: artifacts/bundle/*.${{ matrix.targets.archive_ext }}
retention-days: 15
if-no-files-found: warn

- name: Upload logs
if: always()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
Expand Down
163 changes: 20 additions & 143 deletions .github/workflows/polyglot-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,54 +37,11 @@ jobs:
name: built-nugets-for-linux-x64
path: ${{ github.workspace }}/artifacts/nugets-rid

- name: Debug - List all downloaded artifacts
- name: Verify bundle artifact
run: |
echo "=== DEBUG: Full artifact tree ==="
echo "Working directory: $(pwd)"
echo "GITHUB_WORKSPACE: ${{ github.workspace }}"
echo ""
echo "=== Setting execute permissions on bundle executables ==="
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire || echo "aspire not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/runtime/dotnet || echo "dotnet not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/aspire-nuget/aspire-nuget || echo "aspire-nuget not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/dev-certs/aspire-dev-certs || echo "aspire-dev-certs not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dashboard/aspire-dashboard || echo "Dashboard not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire-server/aspire-server || echo "RemoteHost not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dcp/dcp || echo "dcp not found"
echo ""
echo "=== artifacts/ directory ==="
ls -la ${{ github.workspace }}/artifacts/ || echo "artifacts/ does not exist"
echo ""
echo "=== artifacts/bundle/ directory ==="
ls -la ${{ github.workspace }}/artifacts/bundle/ || echo "artifacts/bundle/ does not exist"
echo ""
echo "=== artifacts/bundle/ recursive (first 3 levels) ==="
find ${{ github.workspace }}/artifacts/bundle -maxdepth 3 -type f 2>/dev/null | head -50 || echo "No files found"
find ${{ github.workspace }}/artifacts/bundle -maxdepth 3 -type d 2>/dev/null || echo "No directories found"
echo ""
echo "=== Check for aspire CLI ==="
ls -la ${{ github.workspace }}/artifacts/bundle/aspire 2>/dev/null || echo "aspire CLI not found at expected path"
find ${{ github.workspace }}/artifacts -name "aspire" -type f 2>/dev/null || echo "aspire CLI not found anywhere"
echo ""
echo "=== Check for runtime/dotnet ==="
ls -la ${{ github.workspace }}/artifacts/bundle/runtime/dotnet 2>/dev/null || echo "runtime/dotnet not found at expected path"
find ${{ github.workspace }}/artifacts -name "dotnet" -type f 2>/dev/null || echo "dotnet not found anywhere"
echo ""
echo "=== Check for key directories ==="
for dir in runtime dashboard dcp aspire-server tools; do
if [ -d "${{ github.workspace }}/artifacts/bundle/$dir" ]; then
echo "✓ $dir/ exists"
ls -la "${{ github.workspace }}/artifacts/bundle/$dir" | head -5
else
echo "✗ $dir/ MISSING"
fi
done
echo ""
echo "=== artifacts/nugets/ sample ==="
find ${{ github.workspace }}/artifacts/nugets -name "*.nupkg" 2>/dev/null | head -10 || echo "No nupkg files found"
echo ""
echo "=== artifacts/nugets-rid/ sample ==="
find ${{ github.workspace }}/artifacts/nugets-rid -name "*.nupkg" 2>/dev/null | head -10 || echo "No nupkg files found"
echo "=== Verifying self-extracting binary ==="
ls -la ${{ github.workspace }}/artifacts/bundle/aspire || { echo "ERROR: aspire binary not found"; exit 1; }
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire

- name: Build Python validation image
run: |
Expand Down Expand Up @@ -125,31 +82,11 @@ jobs:
name: built-nugets-for-linux-x64
path: ${{ github.workspace }}/artifacts/nugets-rid

- name: Debug - List all downloaded artifacts
- name: Verify bundle artifact
run: |
echo "=== DEBUG: Go validation - artifact tree ==="
echo "=== Setting execute permissions on bundle executables ==="
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire || echo "aspire not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/runtime/dotnet || echo "dotnet not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/aspire-nuget/aspire-nuget || echo "aspire-nuget not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/dev-certs/aspire-dev-certs || echo "aspire-dev-certs not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dashboard/aspire-dashboard || echo "Dashboard not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire-server/aspire-server || echo "RemoteHost not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dcp/dcp || echo "dcp not found"
echo ""
echo "=== artifacts/bundle/ ==="
ls -la ${{ github.workspace }}/artifacts/bundle/ || echo "bundle/ does not exist"
echo ""
echo "=== Bundle structure check ==="
for dir in runtime dashboard dcp aspire-server; do
if [ -d "${{ github.workspace }}/artifacts/bundle/$dir" ]; then
echo "✓ $dir/"
else
echo "✗ $dir/ MISSING"
fi
done
ls -la ${{ github.workspace }}/artifacts/bundle/aspire 2>/dev/null || echo "aspire CLI MISSING"
ls -la ${{ github.workspace }}/artifacts/bundle/runtime/dotnet 2>/dev/null || echo "runtime/dotnet MISSING"
echo "=== Verifying self-extracting binary ==="
ls -la ${{ github.workspace }}/artifacts/bundle/aspire || { echo "ERROR: aspire binary not found"; exit 1; }
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire

- name: Build Go validation image
run: |
Expand Down Expand Up @@ -190,31 +127,11 @@ jobs:
name: built-nugets-for-linux-x64
path: ${{ github.workspace }}/artifacts/nugets-rid

- name: Debug - List all downloaded artifacts
- name: Verify bundle artifact
run: |
echo "=== DEBUG: Java validation - artifact tree ==="
echo "=== Setting execute permissions on bundle executables ==="
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire || echo "aspire not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/runtime/dotnet || echo "dotnet not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/aspire-nuget/aspire-nuget || echo "aspire-nuget not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/dev-certs/aspire-dev-certs || echo "aspire-dev-certs not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dashboard/aspire-dashboard || echo "Dashboard not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire-server/aspire-server || echo "RemoteHost not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dcp/dcp || echo "dcp not found"
echo ""
echo "=== artifacts/bundle/ ==="
ls -la ${{ github.workspace }}/artifacts/bundle/ || echo "bundle/ does not exist"
echo ""
echo "=== Bundle structure check ==="
for dir in runtime dashboard dcp aspire-server; do
if [ -d "${{ github.workspace }}/artifacts/bundle/$dir" ]; then
echo "✓ $dir/"
else
echo "✗ $dir/ MISSING"
fi
done
ls -la ${{ github.workspace }}/artifacts/bundle/aspire 2>/dev/null || echo "aspire CLI MISSING"
ls -la ${{ github.workspace }}/artifacts/bundle/runtime/dotnet 2>/dev/null || echo "runtime/dotnet MISSING"
echo "=== Verifying self-extracting binary ==="
ls -la ${{ github.workspace }}/artifacts/bundle/aspire || { echo "ERROR: aspire binary not found"; exit 1; }
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire

- name: Build Java validation image
run: |
Expand Down Expand Up @@ -257,31 +174,11 @@ jobs:
name: built-nugets-for-linux-x64
path: ${{ github.workspace }}/artifacts/nugets-rid

- name: Debug - List all downloaded artifacts
- name: Verify bundle artifact
run: |
echo "=== DEBUG: Rust validation - artifact tree ==="
echo "=== Setting execute permissions on bundle executables ==="
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire || echo "aspire not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/runtime/dotnet || echo "dotnet not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/aspire-nuget/aspire-nuget || echo "aspire-nuget not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/dev-certs/aspire-dev-certs || echo "aspire-dev-certs not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dashboard/aspire-dashboard || echo "Dashboard not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire-server/aspire-server || echo "RemoteHost not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dcp/dcp || echo "dcp not found"
echo ""
echo "=== artifacts/bundle/ ==="
ls -la ${{ github.workspace }}/artifacts/bundle/ || echo "bundle/ does not exist"
echo ""
echo "=== Bundle structure check ==="
for dir in runtime dashboard dcp aspire-server; do
if [ -d "${{ github.workspace }}/artifacts/bundle/$dir" ]; then
echo "✓ $dir/"
else
echo "✗ $dir/ MISSING"
fi
done
ls -la ${{ github.workspace }}/artifacts/bundle/aspire 2>/dev/null || echo "aspire CLI MISSING"
ls -la ${{ github.workspace }}/artifacts/bundle/runtime/dotnet 2>/dev/null || echo "runtime/dotnet MISSING"
echo "=== Verifying self-extracting binary ==="
ls -la ${{ github.workspace }}/artifacts/bundle/aspire || { echo "ERROR: aspire binary not found"; exit 1; }
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire

- name: Build Rust validation image
run: |
Expand Down Expand Up @@ -322,31 +219,11 @@ jobs:
name: built-nugets-for-linux-x64
path: ${{ github.workspace }}/artifacts/nugets-rid

- name: Debug - List all downloaded artifacts
- name: Verify bundle artifact
run: |
echo "=== DEBUG: TypeScript validation - artifact tree ==="
echo "=== Setting execute permissions on bundle executables ==="
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire || echo "aspire not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/runtime/dotnet || echo "dotnet not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/aspire-nuget/aspire-nuget || echo "aspire-nuget not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/tools/dev-certs/aspire-dev-certs || echo "aspire-dev-certs not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dashboard/aspire-dashboard || echo "Dashboard not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire-server/aspire-server || echo "RemoteHost not found"
chmod +x ${{ github.workspace }}/artifacts/bundle/dcp/dcp || echo "dcp not found"
echo ""
echo "=== artifacts/bundle/ ==="
ls -la ${{ github.workspace }}/artifacts/bundle/ || echo "bundle/ does not exist"
echo ""
echo "=== Bundle structure check ==="
for dir in runtime dashboard dcp aspire-server; do
if [ -d "${{ github.workspace }}/artifacts/bundle/$dir" ]; then
echo "✓ $dir/"
else
echo "✗ $dir/ MISSING"
fi
done
ls -la ${{ github.workspace }}/artifacts/bundle/aspire 2>/dev/null || echo "aspire CLI MISSING"
ls -la ${{ github.workspace }}/artifacts/bundle/runtime/dotnet 2>/dev/null || echo "runtime/dotnet MISSING"
echo "=== Verifying self-extracting binary ==="
ls -la ${{ github.workspace }}/artifacts/bundle/aspire || { echo "ERROR: aspire binary not found"; exit 1; }
chmod +x ${{ github.workspace }}/artifacts/bundle/aspire

- name: Build TypeScript validation image
run: |
Expand Down
25 changes: 3 additions & 22 deletions .github/workflows/polyglot-validation/Dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# -v /var/run/docker.sock:/var/run/docker.sock \
# polyglot-go
#
# Note: Expects bundle and NuGet artifacts to be pre-downloaded to /workspace/artifacts/
# Note: Expects self-extracting binary and NuGet artifacts to be pre-downloaded to /workspace/artifacts/
#
FROM mcr.microsoft.com/devcontainers/go:1-trixie

Expand All @@ -22,8 +22,6 @@ RUN apt-get update && apt-get install -y \
jq \
&& rm -rf /var/lib/apt/lists/*

# Note: .NET SDK is NOT required - the bundle includes the .NET runtime

# Pre-configure Aspire CLI path
ENV PATH="/root/.aspire/bin:${PATH}"

Expand All @@ -33,28 +31,11 @@ COPY setup-local-cli.sh /scripts/setup-local-cli.sh
COPY test-go.sh /scripts/test-go.sh
RUN chmod +x /scripts/setup-local-cli.sh /scripts/test-go.sh

# Entrypoint: Set up Aspire CLI from bundle, enable polyglot, run validation
# Note: ASPIRE_LAYOUT_PATH must be exported before running any aspire commands
# Entrypoint: Set up Aspire CLI, enable polyglot, run validation
# Bundle extraction happens lazily on first command that needs the layout
ENTRYPOINT ["/bin/bash", "-c", "\
set -e && \
echo '=== ENTRYPOINT DEBUG ===' && \
echo 'Starting Docker entrypoint...' && \
echo 'PWD:' $(pwd) && \
echo '' && \
echo '=== Running setup-local-cli.sh ===' && \
/scripts/setup-local-cli.sh && \
echo '' && \
echo '=== Post-setup: Setting ASPIRE_LAYOUT_PATH ===' && \
export ASPIRE_LAYOUT_PATH=/workspace/artifacts/bundle && \
echo 'ASPIRE_LAYOUT_PATH=' $ASPIRE_LAYOUT_PATH && \
echo '' && \
echo '=== Verifying CLI with layout path ===' && \
echo 'Running: aspire --version' && \
aspire --version && \
echo '' && \
echo '=== Enabling polyglot support ===' && \
aspire config set features:polyglotSupportEnabled true --global && \
echo '' && \
echo '=== Running validation ===' && \
/scripts/test-go.sh \
"]
Loading
Loading