exec-sandbox 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. exec_sandbox-0.1.0/.editorconfig +18 -0
  2. exec_sandbox-0.1.0/.github/actions/build-guest-agent/action.yml +56 -0
  3. exec_sandbox-0.1.0/.github/actions/build-gvproxy-wrapper/action.yml +29 -0
  4. exec_sandbox-0.1.0/.github/actions/build-vm-images/action.yml +64 -0
  5. exec_sandbox-0.1.0/.github/workflows/release.yml +265 -0
  6. exec_sandbox-0.1.0/.github/workflows/test.yml +387 -0
  7. exec_sandbox-0.1.0/.gitignore +272 -0
  8. exec_sandbox-0.1.0/CLAUDE.md +3 -0
  9. exec_sandbox-0.1.0/LICENSE +202 -0
  10. exec_sandbox-0.1.0/Makefile +125 -0
  11. exec_sandbox-0.1.0/PKG-INFO +455 -0
  12. exec_sandbox-0.1.0/README.md +402 -0
  13. exec_sandbox-0.1.0/SECURITY.md +21 -0
  14. exec_sandbox-0.1.0/catalogs/npm_top_10k.json +10002 -0
  15. exec_sandbox-0.1.0/catalogs/pypi_top_10k.json +10002 -0
  16. exec_sandbox-0.1.0/cicd/version.sh +39 -0
  17. exec_sandbox-0.1.0/guest-agent/.gitignore +20 -0
  18. exec_sandbox-0.1.0/guest-agent/Cargo.lock +467 -0
  19. exec_sandbox-0.1.0/guest-agent/Cargo.toml +25 -0
  20. exec_sandbox-0.1.0/guest-agent/Makefile +26 -0
  21. exec_sandbox-0.1.0/guest-agent/src/main.rs +1214 -0
  22. exec_sandbox-0.1.0/gvproxy-wrapper/.gitignore +30 -0
  23. exec_sandbox-0.1.0/gvproxy-wrapper/Makefile +41 -0
  24. exec_sandbox-0.1.0/gvproxy-wrapper/go.mod +29 -0
  25. exec_sandbox-0.1.0/gvproxy-wrapper/go.sum +88 -0
  26. exec_sandbox-0.1.0/gvproxy-wrapper/main.go +123 -0
  27. exec_sandbox-0.1.0/gvproxy-wrapper/test_wrapper.sh +51 -0
  28. exec_sandbox-0.1.0/images/minimal-init.sh +157 -0
  29. exec_sandbox-0.1.0/pyproject.toml +185 -0
  30. exec_sandbox-0.1.0/scripts/build-guest-agent.sh +164 -0
  31. exec_sandbox-0.1.0/scripts/build-images.sh +89 -0
  32. exec_sandbox-0.1.0/scripts/build-initramfs.sh +144 -0
  33. exec_sandbox-0.1.0/scripts/build-qcow2.sh +457 -0
  34. exec_sandbox-0.1.0/scripts/build_package_catalogs.py +129 -0
  35. exec_sandbox-0.1.0/scripts/ci-diagnose.sh +212 -0
  36. exec_sandbox-0.1.0/scripts/extract-kernel.sh +131 -0
  37. exec_sandbox-0.1.0/src/exec_sandbox/__init__.py +83 -0
  38. exec_sandbox-0.1.0/src/exec_sandbox/_imports.py +22 -0
  39. exec_sandbox-0.1.0/src/exec_sandbox/_logging.py +13 -0
  40. exec_sandbox-0.1.0/src/exec_sandbox/asset_downloader.py +496 -0
  41. exec_sandbox-0.1.0/src/exec_sandbox/assets.py +275 -0
  42. exec_sandbox-0.1.0/src/exec_sandbox/cgroup.py +447 -0
  43. exec_sandbox-0.1.0/src/exec_sandbox/config.py +196 -0
  44. exec_sandbox-0.1.0/src/exec_sandbox/constants.py +184 -0
  45. exec_sandbox-0.1.0/src/exec_sandbox/dns_filter.py +152 -0
  46. exec_sandbox-0.1.0/src/exec_sandbox/exceptions.py +159 -0
  47. exec_sandbox-0.1.0/src/exec_sandbox/guest_agent_protocol.py +179 -0
  48. exec_sandbox-0.1.0/src/exec_sandbox/guest_channel.py +728 -0
  49. exec_sandbox-0.1.0/src/exec_sandbox/models.py +38 -0
  50. exec_sandbox-0.1.0/src/exec_sandbox/package_validator.py +131 -0
  51. exec_sandbox-0.1.0/src/exec_sandbox/permission_utils.py +610 -0
  52. exec_sandbox-0.1.0/src/exec_sandbox/platform_utils.py +279 -0
  53. exec_sandbox-0.1.0/src/exec_sandbox/py.typed +0 -0
  54. exec_sandbox-0.1.0/src/exec_sandbox/qmp_client.py +384 -0
  55. exec_sandbox-0.1.0/src/exec_sandbox/resource_cleanup.py +267 -0
  56. exec_sandbox-0.1.0/src/exec_sandbox/scheduler.py +442 -0
  57. exec_sandbox-0.1.0/src/exec_sandbox/settings.py +61 -0
  58. exec_sandbox-0.1.0/src/exec_sandbox/snapshot_manager.py +998 -0
  59. exec_sandbox-0.1.0/src/exec_sandbox/socket_auth.py +251 -0
  60. exec_sandbox-0.1.0/src/exec_sandbox/subprocess_utils.py +130 -0
  61. exec_sandbox-0.1.0/src/exec_sandbox/vm_manager.py +2608 -0
  62. exec_sandbox-0.1.0/src/exec_sandbox/vm_working_directory.py +221 -0
  63. exec_sandbox-0.1.0/src/exec_sandbox/warm_vm_pool.py +615 -0
  64. exec_sandbox-0.1.0/stubs/backports/__init__.pyi +0 -0
  65. exec_sandbox-0.1.0/stubs/backports/zstd.pyi +8 -0
  66. exec_sandbox-0.1.0/tests/__init__.py +1 -0
  67. exec_sandbox-0.1.0/tests/conftest.py +192 -0
  68. exec_sandbox-0.1.0/tests/test_asset_downloader.py +690 -0
  69. exec_sandbox-0.1.0/tests/test_cgroup.py +1214 -0
  70. exec_sandbox-0.1.0/tests/test_config.py +348 -0
  71. exec_sandbox-0.1.0/tests/test_dns_filter.py +111 -0
  72. exec_sandbox-0.1.0/tests/test_dns_filtering.py +451 -0
  73. exec_sandbox-0.1.0/tests/test_env_var_security.py +316 -0
  74. exec_sandbox-0.1.0/tests/test_execution_edge_cases.py +790 -0
  75. exec_sandbox-0.1.0/tests/test_guest_agent_protocol.py +591 -0
  76. exec_sandbox-0.1.0/tests/test_guest_channel.py +324 -0
  77. exec_sandbox-0.1.0/tests/test_image_tools.py +178 -0
  78. exec_sandbox-0.1.0/tests/test_memory_optimization.py +730 -0
  79. exec_sandbox-0.1.0/tests/test_package_validator.py +147 -0
  80. exec_sandbox-0.1.0/tests/test_payload_integrity.py +645 -0
  81. exec_sandbox-0.1.0/tests/test_permission_utils.py +616 -0
  82. exec_sandbox-0.1.0/tests/test_pid1_zombie_reaping.py +290 -0
  83. exec_sandbox-0.1.0/tests/test_platform_utils.py +423 -0
  84. exec_sandbox-0.1.0/tests/test_resource_cleanup.py +369 -0
  85. exec_sandbox-0.1.0/tests/test_rng_quality.py +497 -0
  86. exec_sandbox-0.1.0/tests/test_scheduler.py +437 -0
  87. exec_sandbox-0.1.0/tests/test_snapshot_manager.py +1243 -0
  88. exec_sandbox-0.1.0/tests/test_socket_auth.py +756 -0
  89. exec_sandbox-0.1.0/tests/test_subprocess_utils.py +245 -0
  90. exec_sandbox-0.1.0/tests/test_vm_manager.py +606 -0
  91. exec_sandbox-0.1.0/tests/test_vm_working_directory.py +251 -0
  92. exec_sandbox-0.1.0/tests/test_warm_vm_pool.py +1366 -0
  93. exec_sandbox-0.1.0/uv.lock +2460 -0
@@ -0,0 +1,18 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ end_of_line = lf
9
+ indent_style = space
10
+ insert_final_newline = true
11
+ trim_trailing_whitespace = true
12
+
13
+ [Makefile]
14
+ indent_size = 4
15
+ indent_style = tab
16
+
17
+ [*.py]
18
+ indent_size = 4
@@ -0,0 +1,56 @@
1
+ name: Build guest-agent
2
+ description: Build guest-agent Rust binary for specified architecture
3
+
4
+ inputs:
5
+ arch:
6
+ description: Target architecture (x86_64 or aarch64)
7
+ required: true
8
+
9
+ runs:
10
+ using: composite
11
+ steps:
12
+ - name: Set up QEMU
13
+ uses: docker/setup-qemu-action@v3
14
+
15
+ - name: Set up Docker Buildx
16
+ uses: docker/setup-buildx-action@v3
17
+
18
+ - name: Expose GitHub Actions cache variables
19
+ uses: actions/github-script@v7
20
+ with:
21
+ script: |
22
+ core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL'] || '');
23
+ core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'] || '');
24
+ core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'] || '');
25
+
26
+ # Try to restore from latest run on same branch (hash-based caching in build script will skip if unchanged)
27
+ - name: Get latest run ID
28
+ id: latest-run
29
+ shell: bash
30
+ run: |
31
+ RUN_ID=$(gh run list \
32
+ --workflow=test.yml \
33
+ --branch="${{ github.ref_name }}" \
34
+ --limit=100 \
35
+ --json databaseId,conclusion \
36
+ --jq '[.[] | select(.conclusion == "success" or .conclusion == "failure")] | .[0].databaseId // empty')
37
+ echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
38
+ env:
39
+ GH_TOKEN: ${{ github.token }}
40
+
41
+ - name: Download previous build
42
+ if: steps.latest-run.outputs.run_id != ''
43
+ uses: actions/download-artifact@v4
44
+ with:
45
+ name: guest-agent-${{ inputs.arch == 'x86_64' && 'x64' || 'arm64' }}
46
+ path: images/dist/
47
+ github-token: ${{ github.token }}
48
+ run-id: ${{ steps.latest-run.outputs.run_id }}
49
+ continue-on-error: true
50
+
51
+ - name: Build guest-agent
52
+ shell: bash
53
+ env:
54
+ BUILDX_CACHE_FROM: type=gha,ignore-error=true
55
+ BUILDX_CACHE_TO: type=gha,mode=max,ignore-error=true
56
+ run: ./scripts/build-guest-agent.sh ${{ inputs.arch }}
@@ -0,0 +1,29 @@
1
+ name: Build gvproxy-wrapper
2
+ description: Build gvproxy-wrapper Go binary
3
+
4
+ inputs:
5
+ all-platforms:
6
+ description: Build for all platforms (linux/darwin × amd64/arm64)
7
+ required: false
8
+ default: "false"
9
+
10
+ runs:
11
+ using: composite
12
+ steps:
13
+ - name: Install Go
14
+ uses: actions/setup-go@v5
15
+ with:
16
+ go-version: "1.24"
17
+ cache-dependency-path: gvproxy-wrapper/go.sum
18
+
19
+ - name: Build (current platform)
20
+ if: inputs.all-platforms == 'false'
21
+ working-directory: gvproxy-wrapper
22
+ shell: bash
23
+ run: make build
24
+
25
+ - name: Build (all platforms)
26
+ if: inputs.all-platforms == 'true'
27
+ working-directory: gvproxy-wrapper
28
+ shell: bash
29
+ run: make build-all
@@ -0,0 +1,64 @@
1
+ name: Build VM images
2
+ description: Build kernel and qcow2 images for specified architecture
3
+
4
+ inputs:
5
+ arch:
6
+ description: Target architecture (x86_64 or aarch64)
7
+ required: true
8
+ variant:
9
+ description: Image variant (python, node, raw, or all)
10
+ required: false
11
+ default: "all"
12
+
13
+ runs:
14
+ using: composite
15
+ steps:
16
+ - name: Set up QEMU
17
+ uses: docker/setup-qemu-action@v3
18
+
19
+ - name: Set up Docker Buildx
20
+ uses: docker/setup-buildx-action@v3
21
+
22
+ - name: Expose GitHub Actions cache variables
23
+ uses: actions/github-script@v7
24
+ with:
25
+ script: |
26
+ core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL'] || '');
27
+ core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'] || '');
28
+ core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'] || '');
29
+
30
+ # Try to restore from latest run on same branch (hash-based caching in build script will skip if unchanged)
31
+ - name: Get latest run ID
32
+ id: latest-run
33
+ shell: bash
34
+ run: |
35
+ RUN_ID=$(gh run list \
36
+ --workflow=test.yml \
37
+ --branch="${{ github.ref_name }}" \
38
+ --limit=100 \
39
+ --json databaseId,conclusion \
40
+ --jq '[.[] | select(.conclusion == "success" or .conclusion == "failure")] | .[0].databaseId // empty')
41
+ echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
42
+ env:
43
+ GH_TOKEN: ${{ github.token }}
44
+
45
+ - name: Download previous build
46
+ if: steps.latest-run.outputs.run_id != ''
47
+ uses: actions/download-artifact@v4
48
+ with:
49
+ name: vm-images-${{ inputs.arch == 'x86_64' && 'x64' || 'arm64' }}
50
+ path: images/dist/
51
+ github-token: ${{ github.token }}
52
+ run-id: ${{ steps.latest-run.outputs.run_id }}
53
+ continue-on-error: true
54
+
55
+ - name: Extract kernel
56
+ shell: bash
57
+ run: ./scripts/extract-kernel.sh ${{ inputs.arch }}
58
+
59
+ - name: Build qcow2
60
+ shell: bash
61
+ env:
62
+ BUILDX_CACHE_FROM: type=gha,ignore-error=true
63
+ BUILDX_CACHE_TO: type=gha,mode=max,ignore-error=true
64
+ run: ./scripts/build-qcow2.sh ${{ inputs.variant }} ${{ inputs.arch }}
@@ -0,0 +1,265 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ concurrency:
9
+ group: release-${{ github.ref }}
10
+ cancel-in-progress: false
11
+
12
+ permissions:
13
+ actions: read # Required for artifact caching (gh run list)
14
+ attestations: write # Required for GitHub attestations
15
+ contents: write # Required for release asset upload
16
+ id-token: write # Required for PyPI trusted publishing + Sigstore signing
17
+
18
+ jobs:
19
+ # ==========================================================================
20
+ # Python Package
21
+ # ==========================================================================
22
+
23
+ python-package:
24
+ name: Python Package
25
+ runs-on: linux-arm64-ubuntu2404-small-private
26
+ timeout-minutes: 10
27
+ steps:
28
+ - uses: actions/checkout@v6.0.1
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v7.1.6
32
+ with:
33
+ enable-cache: true
34
+ cache-dependency-glob: uv.lock
35
+
36
+ - name: Set up Python
37
+ uses: actions/setup-python@v6.1.0
38
+ with:
39
+ python-version: "3.12"
40
+
41
+ - name: Install dependencies
42
+ run: make install-deps
43
+
44
+ - name: Inject version from git tag
45
+ run: |
46
+ VERSION=$(./cicd/version.sh -g .)
47
+ VERSION_FULL=$(./cicd/version.sh -g . -c -m)
48
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
49
+ echo "VERSION_FULL=$VERSION_FULL" >> $GITHUB_ENV
50
+ echo "Injecting version: $VERSION (full: $VERSION_FULL)"
51
+ sed -i "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
52
+
53
+ - name: Build package
54
+ run: uv build
55
+
56
+ - name: Verify build artifacts
57
+ run: |
58
+ ls -la dist/
59
+ test -f dist/exec_sandbox-*.whl
60
+ test -f dist/exec_sandbox-*.tar.gz
61
+ uv run twine check dist/*
62
+
63
+ - name: Attest build provenance
64
+ uses: actions/attest-build-provenance@v3.1.0
65
+ with:
66
+ subject-path: dist/*
67
+
68
+ - name: Upload build artifacts
69
+ uses: actions/upload-artifact@v4
70
+ with:
71
+ name: python-dist
72
+ path: dist/
73
+ retention-days: 30
74
+
75
+ publish-testpypi:
76
+ name: Publish to TestPyPI
77
+ needs: python-package
78
+ runs-on: linux-x64-ubuntu2404-small-private # x64 required: pypi-publish action is amd64 only
79
+ timeout-minutes: 10
80
+ environment:
81
+ name: testpypi
82
+ url: https://test.pypi.org/p/exec_sandbox
83
+ permissions:
84
+ id-token: write
85
+ steps:
86
+ - name: Download build artifacts
87
+ uses: actions/download-artifact@v4
88
+ with:
89
+ name: python-dist
90
+ path: dist/
91
+
92
+ - name: Publish to TestPyPI
93
+ uses: pypa/gh-action-pypi-publish@release/v1
94
+ with:
95
+ repository-url: https://test.pypi.org/legacy/
96
+ attestations: true
97
+ print-hash: true
98
+
99
+ publish-pypi:
100
+ name: Publish to PyPI
101
+ needs: [python-package, publish-testpypi]
102
+ runs-on: linux-x64-ubuntu2404-small-private # x64 required: pypi-publish action is amd64 only
103
+ timeout-minutes: 10
104
+ environment:
105
+ name: pypi
106
+ url: https://pypi.org/p/exec_sandbox
107
+ permissions:
108
+ id-token: write
109
+ steps:
110
+ - name: Download build artifacts
111
+ uses: actions/download-artifact@v4
112
+ with:
113
+ name: python-dist
114
+ path: dist/
115
+
116
+ - name: Publish to PyPI
117
+ uses: pypa/gh-action-pypi-publish@release/v1
118
+ with:
119
+ attestations: true
120
+ print-hash: true
121
+
122
+ # ==========================================================================
123
+ # Binary Builds
124
+ # ==========================================================================
125
+
126
+ gvproxy-wrapper:
127
+ name: gvproxy-wrapper
128
+ runs-on: ubuntu-latest
129
+ steps:
130
+ - uses: actions/checkout@v6.0.1
131
+
132
+ - name: Build gvproxy-wrapper (all platforms)
133
+ uses: ./.github/actions/build-gvproxy-wrapper
134
+ with:
135
+ all-platforms: "true"
136
+
137
+ - name: Upload artifacts
138
+ uses: actions/upload-artifact@v4
139
+ with:
140
+ name: gvproxy-wrapper
141
+ path: gvproxy-wrapper/bin/gvproxy-wrapper-*
142
+
143
+ guest-agent:
144
+ name: guest-agent (${{ matrix.arch }})
145
+ runs-on: ubuntu-latest
146
+ strategy:
147
+ matrix:
148
+ arch: [x86_64, aarch64]
149
+ steps:
150
+ - uses: actions/checkout@v6.0.1
151
+
152
+ - name: Build guest-agent
153
+ uses: ./.github/actions/build-guest-agent
154
+ with:
155
+ arch: ${{ matrix.arch }}
156
+
157
+ - name: Upload artifact
158
+ uses: actions/upload-artifact@v4
159
+ with:
160
+ name: guest-agent-${{ matrix.arch }}
161
+ path: images/dist/guest-agent-linux-${{ matrix.arch }}
162
+
163
+ # ==========================================================================
164
+ # VM Images
165
+ # ==========================================================================
166
+
167
+ vm-images:
168
+ name: VM Images (${{ matrix.arch }})
169
+ runs-on: ubuntu-latest
170
+ needs: [guest-agent]
171
+ strategy:
172
+ matrix:
173
+ arch: [x86_64, aarch64]
174
+ steps:
175
+ - uses: actions/checkout@v6.0.1
176
+
177
+ - name: Download guest-agent
178
+ uses: actions/download-artifact@v4
179
+ with:
180
+ name: guest-agent-${{ matrix.arch }}
181
+ path: images/dist/
182
+
183
+ - name: Build VM images
184
+ uses: ./.github/actions/build-vm-images
185
+ with:
186
+ arch: ${{ matrix.arch }}
187
+
188
+ - name: Upload artifacts
189
+ uses: actions/upload-artifact@v4
190
+ with:
191
+ name: vm-images-${{ matrix.arch }}
192
+ path: |
193
+ images/dist/vmlinuz-${{ matrix.arch }}
194
+ images/dist/initramfs-${{ matrix.arch }}
195
+ images/dist/*-${{ matrix.arch }}.qcow2
196
+
197
+ # ==========================================================================
198
+ # GitHub Release
199
+ # ==========================================================================
200
+
201
+ release:
202
+ name: Create GitHub Release
203
+ runs-on: ubuntu-latest
204
+ needs: [publish-pypi, gvproxy-wrapper, guest-agent, vm-images]
205
+ permissions:
206
+ contents: write
207
+ steps:
208
+ - uses: actions/checkout@v6.0.1
209
+
210
+ - name: Download all artifacts
211
+ uses: actions/download-artifact@v4
212
+ with:
213
+ path: artifacts/
214
+
215
+ - name: Prepare release files
216
+ run: |
217
+ mkdir -p release
218
+
219
+ # Python package
220
+ cp artifacts/python-dist/*.whl release/
221
+ cp artifacts/python-dist/*.tar.gz release/
222
+
223
+ # gvproxy-wrapper
224
+ cp artifacts/gvproxy-wrapper/* release/
225
+
226
+ # guest-agent
227
+ cp artifacts/guest-agent-x86_64/* release/
228
+ cp artifacts/guest-agent-aarch64/* release/
229
+
230
+ # VM images (kernel + initramfs + qcow2)
231
+ cp artifacts/vm-images-x86_64/* release/
232
+ cp artifacts/vm-images-aarch64/* release/
233
+
234
+ # Compress large files
235
+ cd release
236
+ for f in *.qcow2 vmlinuz-* initramfs-*; do
237
+ zstd -19 --rm "$f"
238
+ done
239
+
240
+ # Generate checksums
241
+ sha256sum * > SHA256SUMS
242
+
243
+ ls -lh
244
+
245
+ - name: Create GitHub Release
246
+ uses: softprops/action-gh-release@v2.5.0
247
+ with:
248
+ files: release/*
249
+ generate_release_notes: true
250
+ fail_on_unmatched_files: true
251
+
252
+ - name: Generate job summary
253
+ run: |
254
+ echo "## Release Summary" >> $GITHUB_STEP_SUMMARY
255
+ echo "" >> $GITHUB_STEP_SUMMARY
256
+ echo "### Published To" >> $GITHUB_STEP_SUMMARY
257
+ echo "- [TestPyPI](https://test.pypi.org/p/exec_sandbox)" >> $GITHUB_STEP_SUMMARY
258
+ echo "- [PyPI](https://pypi.org/p/exec_sandbox)" >> $GITHUB_STEP_SUMMARY
259
+ echo "" >> $GITHUB_STEP_SUMMARY
260
+ echo "### Artifacts" >> $GITHUB_STEP_SUMMARY
261
+ echo "- Python wheel + sdist" >> $GITHUB_STEP_SUMMARY
262
+ echo "- gvproxy-wrapper (4 platforms)" >> $GITHUB_STEP_SUMMARY
263
+ echo "- guest-agent (2 architectures)" >> $GITHUB_STEP_SUMMARY
264
+ echo "- Kernel + initramfs (2 architectures)" >> $GITHUB_STEP_SUMMARY
265
+ echo "- qcow2 images (6 variants)" >> $GITHUB_STEP_SUMMARY