Skip to content

Conversation

@antitree
Copy link
Contributor

@antitree antitree commented Dec 10, 2025

Summary

This PR adds support for the QEMU_ADDITIONAL_PACKAGES environment variable, which allows users to specify additional packages to include in the initramfs image during QEMU VM initialization. This complements the existing TESTING environment variable feature gate.

Changes

  • Added QEMU_ADDITIONAL_PACKAGES support in pkg/container/qemu_runner.go
  • Packages are added to the initramfs image build specification
  • Accepts comma-separated list of package names (e.g., hello-wolfi,nginx-stable,strace)
  • Packages are baked into the initramfs for immediate availability at boot
  • Input validation prevents injection attacks (regex: ^[a-zA-Z0-9_,.-]+$)
  • Cache invalidation: different package lists create separate cached initramfs files

How It Works

Without QEMU_ADDITIONAL_PACKAGES:

spec := apko_types.ImageConfiguration{
    Contents: apko_types.ImageContents{
        Packages: []string{"microvm-init"},
    },
}

With QEMU_ADDITIONAL_PACKAGES=hello-wolfi,strace:

spec := apko_types.ImageConfiguration{
    Contents: apko_types.ImageContents{
        Packages: []string{"microvm-init", "hello-wolfi", "strace"},
    },
}

Packages are installed into the initramfs during the apko build, so they're available immediately when the VM boots - no runtime apk add needed!

Usage

# Single package
QEMU_ADDITIONAL_PACKAGES=hello-wolfi melange build mypackage.yaml --runner qemu

# Multiple packages
QEMU_ADDITIONAL_PACKAGES=strace,gdb,tcpdump melange build mypackage.yaml --runner qemu

Use Cases

  • Testing/debugging: Install tools like strace, gdb, tcpdump for debugging builds
  • Runtime dependencies: Install packages needed during the build that aren't in the build environment
  • Development: Quick iteration without modifying build configurations\
  • Init.d hooks: Install packages that provide init.d scripts for custom initialization

Security

The implementation validates input to prevent shell injection attacks. Only alphanumeric characters, hyphens, underscores, commas, and dots are allowed in package names. Suspicious input is rejected with a warning.

Test Results

Verified that packages are successfully added to the initramfs:

2025/12/10 12:05:59 INFO qemu: QEMU_ADDITIONAL_PACKAGES env set to hello-wolfi, adding to initramfs
2025/12/10 12:06:00 INFO     packages:     [microvm-init hello-wolfi]
2025/12/10 12:06:02 INFO installing hello-wolfi (2.12.2-r2)

The package is installed in the initramfs and available at boot time ✅

antitree and others added 2 commits December 10, 2025 12:20
Add support for QEMU_ADDITIONAL_PACKAGES environment variable that allows
users to specify additional packages to install in the QEMU microVM during
initialization.

The variable accepts a comma-separated list of package names (e.g.,
"hello-wolfi,nginx-stable,strace") and passes them to microvm-init via
the kernel command line as melange.additional_packages=<list>.

Input validation prevents injection attacks by only allowing alphanumeric
characters, hyphens, underscores, commas, and dots. Invalid input is
rejected with a warning.

Usage:
  QEMU_ADDITIONAL_PACKAGES=hello-wolfi,strace melange build mypackage.yaml

Note: This requires a corresponding update to microvm-init package to
read and process the melange.additional_packages kernel parameter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update to use strings.SplitSeq() instead of strings.Split() for better
efficiency with the iterator pattern in Go 1.24+.

Addresses golangci-lint modernize check.
@antitree antitree requested a review from 89luca89 December 10, 2025 17:27

// Check for QEMU_ADDITIONAL_PACKAGES environment variable
// Add packages to the initramfs image so they're available during boot
if additionalPkgs, ok := os.LookupEnv("QEMU_ADDITIONAL_PACKAGES"); ok && additionalPkgs != "" {
Copy link
Member

@egibs egibs Dec 10, 2025

Choose a reason for hiding this comment

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

Would it be worth moving this to a new function (getAdditionalPackages) and writing a test to make sure this does what it's supposed to?

Copy link
Member

Choose a reason for hiding this comment

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

(and likely the new TESTING code as well although it's not doing nearly as much)

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree on the separate function and tests
Also I'd avoid shadowing variables in general

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On it. Added a new function and tests for it

// Use sanitized package list as cache key (replace commas and dots with dashes)
sanitized := strings.NewReplacer(",", "-", ".", "-").Replace(additionalPkgs)
if len(sanitized) > 32 {
sanitized = sanitized[:32] // Limit length for reasonable filenames
Copy link
Member

Choose a reason for hiding this comment

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

Can we foresee hitting the edge case where we install enough packages to make this consistent even though we're installing new/different packages after the cutoff?

Would something like a checksum work better?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree on checksum, makes it simpler and less likely to have strange chars in it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I think that smart and gets us out of dealing with the 4K cmdline problem I think

antitree and others added 2 commits December 11, 2025 09:03
…L_PACKAGES

Addresses PR feedback from @egibs and @89luca89:
- Extract getAdditionalPackages() function for parsing env var
- Extract getPackageCacheSuffix() function for cache key generation
- Use SHA256 hash instead of truncation to avoid collisions
- Add comprehensive test coverage for both functions
- Fix variable shadowing issue

Tests verify:
- Package parsing and validation
- Security (injection prevention)
- Cache suffix generation with SHA256
- Hash determinism and collision prevention

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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