Skip to content

anaconda: Add experimental anaconda-based installation#202

Draft
cgwalters wants to merge 2 commits intomainfrom
feature/anaconda-integration
Draft

anaconda: Add experimental anaconda-based installation#202
cgwalters wants to merge 2 commits intomainfrom
feature/anaconda-integration

Conversation

@cgwalters
Copy link
Collaborator

Implement bootc container installation using anaconda as the installation
engine with kickstart processing. Uses the ephemeral VM infrastructure to
run anaconda in an isolated environment with proper access to block devices
and container storage.

Key implementation details:

  • User must provide kickstart file with partitioning and locale settings
  • bcvk injects ostreecontainer directive with --transport=containers-storage
  • Inject %pre script to configure container storage with host overlay
  • Inject %post script running 'bootc switch --mutate-in-place' to repoint
    the installed system to the registry image (for bootc upgrade to work)
  • Handle SSH exit code 255 as success when VM powers off after installation
  • Share disk creation logic via qemu_img::create_disk()

Options:

  • --kickstart (-k): Required kickstart file path
  • --target-imgref: Registry image for bootc origin (defaults to image arg)
  • --no-repoint: Skip %post repointing if user handles it themselves

The anaconda installer container (localhost/anaconda-bootc) is based on
fedora-bootc with anaconda-tui installed. Build instructions in
containers/anaconda-bootc/.

The --cache=never flag (added in 50e7601 to avoid FD exhaustion) has
a side effect: the guest kernel returns ENODEV for mmap() syscalls on
virtiofs files. This breaks a surprising number of userspace tools:

  - ldconfig uses mmap to scan ELF headers, so it can't populate
    /etc/ld.so.cache
  - ctypes.util.find_library() parses ldconfig output, so it returns
    None for all libraries
  - pyudev uses find_library() to load libudev, so it fails with
    "ImportError: No library named udev"
  - anaconda uses pyudev for device discovery, so it fails to start

The --allow-mmap flag tells virtiofsd to negotiate FUSE_DIRECT_IO_ALLOW_MMAP
with the kernel (added in Linux 6.2 / FUSE 7.38), which permits mmap even
when DIRECT_IO is enabled. This gives us the best of both worlds:

  - No FD exhaustion (still using --cache=never)
  - mmap works (ldconfig, pyudev, anaconda all function)

Assisted-by: OpenCode (claude-sonnet-4-20250514)
Implement bootc container installation using anaconda as the installation
engine with kickstart processing. Uses the ephemeral VM infrastructure to
run anaconda in an isolated environment with proper access to block devices
and container storage.

Key implementation details:

- User must provide kickstart file with partitioning and locale settings
- bcvk injects ostreecontainer directive with --transport=containers-storage
- Inject %pre script to configure container storage with host overlay
- Inject %post script running 'bootc switch --mutate-in-place' to repoint
  the installed system to the registry image (for bootc upgrade to work)
- Handle SSH exit code 255 as success when VM powers off after installation
- Share disk creation logic via qemu_img::create_disk()

Options:
- --kickstart (-k): Required kickstart file path
- --target-imgref: Registry image for bootc origin (defaults to image arg)
- --no-repoint: Skip %post repointing if user handles it themselves

The anaconda installer container (localhost/anaconda-bootc) is based on
fedora-bootc with anaconda-tui installed. Build instructions in
containers/anaconda-bootc/.

Assisted-by: OpenCode (Claude sonnet-4-20250514)
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an experimental anaconda-based installation method for bootc images, including a new Dockerfile, documentation, and integration tests. While the code is well-structured, there are significant security concerns regarding how user-supplied image names and references are handled when generating Kickstart files. Specifically, the code is vulnerable to both Command Injection in the %post script and Kickstart Directive Injection via the image argument. These should be addressed by properly quoting and validating all user-supplied strings. Additionally, a minor issue was noted regarding a hardcoded value in the generated kickstart script, which could be made dynamic for better flexibility.

set -euo pipefail
# Set up container storage with host overlay access
mkdir -p /var/lib/containers
mount -t tmpfs -o size=10485760k tmpfs /var/lib/containers

Choose a reason for hiding this comment

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

medium

The size of the tmpfs for /var/lib/containers is hardcoded to 10GB. This might not be sufficient for all installation scenarios and is inconsistent with the dynamic sizing used for the /var/tmp tmpfs in generate_install_script. Consider making this size dynamic, for example, by basing it on the disk or image size.

return utils::parse_size(size_str);
}
let image_size = images::get_image_size(&self.image)?;
let min_4gb = 4u64 * 1024 * 1024 * 1024;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This needs a rationale and should be const


/// Disk size to create (e.g. 10G, 5120M)
#[clap(long)]
pub disk_size: Option<String>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should use shared size parser

let mut ostreecontainer_added = false;
let mut pre_added = false;

for line in user_kickstart.lines() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should factor out a mod kickstart

// Before first section, add ostreecontainer and our %pre
if !ostreecontainer_added {
result.push_str(&format!(
"ostreecontainer --transport=containers-storage --url={}\n",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Detect if target anaconda has the bootc verb?

additionalimagestores = ["/run/virtiofs-mnt-hoststorage"]
EOF

cat > /etc/containers/policy.json << 'EOF'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This shouldn't be necessary by default

Ok(result)
}

/// Generate the %pre section that sets up container storage
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we can maybe move this to the Anaconda container image as a systemd unit.

Comment on lines +251 to +253
let mut file = std::fs::File::create(&path).context("Failed to create kickstart file")?;
file.write_all(content.as_bytes())?;
file.sync_all()?;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

std::fs::write

[ -e "$TARGET_DISK" ] || {{ echo "ERROR: Target disk not found"; exit 1; }}

export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
anaconda --text --noninteractive --kickstart "$KS_PATH" || ANACONDA_EXIT=$?
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's interesting, does anaconda itself not run via a systemd unit by default? Needs investigation.

rm -rf /var/lib/containers
ln -sr /var/tmp/containers /var/lib/containers

AIS=/run/virtiofs-mnt-hoststorage
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should not duplicate the %pre content

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.

1 participant