Guide to Pre-Boot Configuration Injection for Migrated VMs
hyper2kvm provides powerful configuration injection capabilities that allow you to customize VM settings before first boot. Unlike traditional cloud-init approaches that run on boot, these injectors work offline using libguestfs to modify the VM disk image directly.
Why Pre-Boot Injection?
hyper2kvm supports five configuration injectors:
Inject network configuration files for systemd-networkd or NetworkManager without cloud-init.
.network, .netdev files).nmconnection files)network_config_inject:
# systemd-networkd files
network_files:
- name: eth0 # Creates /etc/systemd/network/10-eth0.network
type: network # "network" or "netdev"
priority: 10 # File priority (default: 50)
content: |
[Match]
Name=eth0
[Network]
Address=192.168.1.100/24
Gateway=192.168.1.1
DNS=8.8.8.8
DNS=8.8.4.4
- name: br0
type: netdev # Virtual network device
priority: 10
content: |
[NetDev]
Name=br0
Kind=bridge
[Bridge]
STP=yes
# NetworkManager connection files
nm_connections:
- name: Production # Creates /etc/NetworkManager/system-connections/Production.nmconnection
content: |
[connection]
id=Production
type=ethernet
interface-name=eth0
autoconnect=true
[ipv4]
method=manual
address1=192.168.1.100/24,192.168.1.1
dns=8.8.8.8;8.8.4.4;
[ipv6]
method=disabled
# Optional: Enable services
enable_networkd: true # Enable systemd-networkd.service
enable_network_manager: true # Enable NetworkManager.service
network_config_inject:
network_files:
- name: eth0
type: network
priority: 10
content: |
[Match]
Name=eth0
[Network]
Address=10.0.1.50/24
Gateway=10.0.1.1
DNS=10.0.1.1
Domains=example.com
enable_networkd: true
network_config_inject:
network_files:
# Create bond interface
- name: bond0
type: netdev
priority: 10
content: |
[NetDev]
Name=bond0
Kind=bond
[Bond]
Mode=802.3ad
LACPTransmitRate=fast
# Bond configuration
- name: bond0
type: network
priority: 11
content: |
[Match]
Name=bond0
[Network]
Address=192.168.1.100/24
Gateway=192.168.1.1
# Slave interfaces
- name: eth0
type: network
priority: 12
content: |
[Match]
Name=eth0
[Network]
Bond=bond0
- name: eth1
type: network
priority: 12
content: |
[Match]
Name=eth1
[Network]
Bond=bond0
enable_networkd: true
network_config_inject:
network_files:
# Bridge device
- name: br-vlan100
type: netdev
priority: 10
content: |
[NetDev]
Name=br-vlan100
Kind=bridge
# VLAN device
- name: vlan100
type: netdev
priority: 11
content: |
[NetDev]
Name=vlan100
Kind=vlan
[VLAN]
Id=100
# Physical interface
- name: eth0
type: network
priority: 20
content: |
[Match]
Name=eth0
[Network]
VLAN=vlan100
# VLAN to bridge
- name: vlan100
type: network
priority: 21
content: |
[Match]
Name=vlan100
[Network]
Bridge=br-vlan100
# Bridge IP
- name: br-vlan100
type: network
priority: 22
content: |
[Match]
Name=br-vlan100
[Network]
Address=192.168.100.10/24
enable_networkd: true
network_config_inject:
nm_connections:
- name: Management
content: |
[connection]
id=Management
type=ethernet
interface-name=eno1
autoconnect=true
autoconnect-priority=10
[ethernet]
mac-address=52:54:00:aa:bb:cc
[ipv4]
method=manual
address1=10.10.10.50/24,10.10.10.1
dns=10.10.10.1;
dns-search=mgmt.example.com;
[ipv6]
method=link-local
enable_network_manager: true
/etc/systemd/network/
10-eth0.network, 50-default.network/etc/NetworkManager/system-connections/
0600Set the system hostname before first boot.
hostname_config:
hostname: production-web-01 # Simple hostname
# OR
fqdn: web01.production.example.com # Fully qualified domain name
hostname_config:
hostname: database-server
hostname_config:
fqdn: app01.datacenter.example.com
/etc/hostname/etc/hosts with FQDN mappingCreate users, set passwords, and configure SSH keys before first boot.
user_config:
users:
- username: deploy
uid: 1001 # Optional: specific UID
gid: 1001 # Optional: specific GID
groups: # Additional groups
- wheel
- docker
shell: /bin/bash
home: /home/deploy # Default: /home/{username}
comment: "Deployment User" # GECOS field
password_hash: "$6$rounds=4096$..." # Hashed password (see below)
ssh_authorized_keys:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... user@host"
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... backup@host"
sudo: true # Add to sudoers
sudo_nopasswd: true # Passwordless sudo
- username: appuser
groups: [users, appgroup]
shell: /bin/bash
ssh_authorized_keys:
- "ssh-rsa AAAAB3... deploy-key"
Generate password hashes with mkpasswd:
# Install mkpasswd (Fedora/RHEL)
sudo dnf install whois
# Generate SHA-512 password hash
mkpasswd -m sha-512 MySecurePassword
# Output:
# $6$rounds=656000$xyz...$abc...
Or with Python:
import crypt
import secrets
# Generate salt
salt = crypt.mksalt(crypt.METHOD_SHA512)
# Hash password
password_hash = crypt.crypt("MySecurePassword", salt)
print(password_hash)
user_config:
users:
- username: deploy
groups: [wheel, docker]
shell: /bin/bash
ssh_authorized_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFoo... deploy@cicd"
sudo: true
sudo_nopasswd: true
user_config:
users:
# Admin user
- username: admin
uid: 2000
groups: [wheel]
password_hash: "$6$rounds=656000$salt$hash"
sudo: true
sudo_nopasswd: false
# Application user
- username: webapp
uid: 3000
shell: /bin/bash
groups: [www-data]
comment: "Web Application User"
# Service account
- username: monitoring
uid: 4000
shell: /sbin/nologin
groups: [monitoring]
ssh_authorized_keys:
- "ssh-rsa AAAAB3... monitoring@nagios"
/etc/passwd, /etc/shadow, /etc/group.ssh/authorized_keys with mode 0600/etc/sudoers.d/{username}Enable or disable systemd services before first boot.
service_config:
enable:
- sshd # Enable SSH daemon
- firewalld # Enable firewall
- chronyd # Enable NTP
- NetworkManager # Enable NetworkManager
disable:
- bluetooth # Disable Bluetooth
- cups # Disable printing
- avahi-daemon # Disable mDNS
service_config:
enable:
- sshd
- firewalld
- chronyd
- rsyslog
disable:
- bluetooth
- cups
- ModemManager
service_config:
enable:
- sshd
- docker
- containerd
disable:
- firewalld # Using iptables directly
- NetworkManager # Using systemd-networkd
- bluetooth
- cups
- avahi-daemon
/etc/systemd/system/multi-user.target.wants/.service, .socket, .timer unitsExecute custom scripts automatically on first boot via systemd.
firstboot_config:
# Single script (simple form)
script: |
#!/bin/bash
echo "First boot setup"
systemctl restart NetworkManager
# OR: Multiple scripts with ordering
scripts:
- name: network-setup
order: 10 # Lower runs first
content: |
#!/bin/bash
echo "Configuring network..."
nmcli connection reload
- name: app-init
order: 20
content: |
#!/bin/bash
echo "Initializing application..."
/opt/app/initialize.sh
- name: cleanup
order: 90
content: |
#!/bin/bash
echo "Final cleanup..."
systemctl disable hyper2kvm-firstboot.service
# Service configuration
service_name: hyper2kvm-firstboot # Default
keep_enabled: false # Disable service after first run
# Systemd service customization (optional)
service:
Description: "Custom first boot setup"
After: network-online.target
RequiresMountsFor: [/data, /opt/app]
Environment:
- "APP_ENV=production"
- "DEBUG=0"
firstboot_config:
script: |
#!/bin/bash
systemctl restart NetworkManager
nmcli connection reload
firstboot_config:
scripts:
- name: mount-storage
order: 10
content: |
#!/bin/bash
set -e
echo "Mounting storage..."
mount /dev/vdb1 /data
- name: start-services
order: 20
content: |
#!/bin/bash
set -e
echo "Starting services..."
systemctl start docker
systemctl start nginx
- name: notify-complete
order: 90
content: |
#!/bin/bash
curl -X POST https://api.example.com/vm-ready \
-d "hostname=$(hostname)" \
-d "status=ready"
service:
After: "network-online.target docker.service"
Environment:
- "DEPLOYMENT_ID="
firstboot_config:
scripts:
- name: security-hardening
order: 5
content: |
#!/bin/bash
# Apply security baseline
chmod 0700 /root
systemctl mask debug-shell.service
- name: monitoring-setup
order: 15
content: |
#!/bin/bash
# Configure monitoring agent
/opt/monitoring/register.sh --datacenter=dc1
systemctl enable monitoring-agent
systemctl start monitoring-agent
- name: app-deployment
order: 20
content: |
#!/bin/bash
# Pull latest application
docker pull registry.internal/app:latest
docker-compose up -d
- name: cleanup-and-disable
order: 99
content: |
#!/bin/bash
# Clean up firstboot artifacts
rm -rf /var/lib/hyper2kvm-firstboot
systemctl disable hyper2kvm-firstboot.service
service:
Description: "Production First Boot Setup"
After: "network-online.target docker.service"
RequiresMountsFor: /data
Environment:
- "ENV=production"
- "REGION=us-east-1"
Scripts execute in order (by order field, lower first):
/var/log/hyper2kvm-firstboot.logkeep_enabled: true)The injector creates:
/usr/local/lib/hyper2kvm-firstboot//etc/systemd/system/{service_name}.service/etc/systemd/system/multi-user.target.wants/Combining all injectors for a production web server:
# Production web server configuration
hostname_config:
fqdn: web01.production.example.com
user_config:
users:
- username: deploy
groups: [wheel, docker]
shell: /bin/bash
ssh_authorized_keys:
- "ssh-ed25519 AAAAC3... deploy@cicd"
sudo: true
sudo_nopasswd: true
- username: webapp
uid: 5000
groups: [webapp]
shell: /bin/bash
home: /opt/webapp
network_config_inject:
network_files:
- name: eth0
type: network
priority: 10
content: |
[Match]
Name=eth0
[Network]
Address=10.0.1.50/24
Gateway=10.0.1.1
DNS=10.0.1.1
DNS=8.8.8.8
Domains=production.example.com
enable_networkd: true
service_config:
enable:
- sshd
- firewalld
- docker
- nginx
disable:
- bluetooth
- cups
firstboot_config:
scripts:
- name: firewall-rules
order: 10
content: |
#!/bin/bash
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
- name: deploy-app
order: 20
content: |
#!/bin/bash
cd /opt/webapp
docker-compose pull
docker-compose up -d
- name: register-monitoring
order: 30
content: |
#!/bin/bash
curl -X POST https://monitoring.example.com/register \
-d "host=$(hostname -f)" \
-d "ip=$(ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')"
service:
After: "network-online.target docker.service nginx.service"
Environment:
- "ENVIRONMENT=production"
nologin shellssudo_nopasswd: false for human usersset -e or proper error checksAlways test configuration injection with --dry-run first:
sudo python -m hyper2kvm local \
--vmdk /path/to/vm.vmdk \
--manifest /path/to/config.yaml \
--dry-run
Check logs for:
systemctl status systemd-networkd
systemctl status NetworkManager
ls -la /etc/systemd/network/
ls -la /etc/NetworkManager/system-connections/
journalctl -u systemd-networkd
journalctl -u NetworkManager
grep username /etc/passwd
ls -la /home/username/.ssh/
# Should be: drwx------ .ssh/
# Should be: -rw------- authorized_keys
journalctl -u sshd
systemctl list-unit-files | grep firstboot
systemctl status hyper2kvm-firstboot.service
cat /var/log/hyper2kvm-firstboot.log
journalctl -u hyper2kvm-firstboot.service
systemctl start hyper2kvm-firstboot.service