- Rust 100%
|
All checks were successful
CI / test (push) Successful in 1m8s
CI / build (push) Successful in 1m15s
CI / format (push) Successful in 47s
CI / lint (push) Successful in 55s
CI / test (pull_request) Successful in 1m4s
CI / build (pull_request) Successful in 1m11s
CI / format (pull_request) Successful in 47s
CI / lint (pull_request) Successful in 56s
Create Release / build (linux-amd64-musl, true, x86_64-unknown-linux-musl) (push) Successful in 29s
|
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| .gitignore | ||
| Cargo.toml | ||
| example.yaml | ||
| README.md | ||
hero-init
hero-init is a system initialization tool designed to configure Linux systems at boot time using a SEED device. It reads configuration from a YAML file on a mounted SEED device and applies various system configurations including network settings, user accounts, hostname, and custom commands.
Table of Contents
- Overview
- Features
- How It Works
- Configuration
- Module Functionalities
- Tool Functions
- How to Configure
- Adding Configuration
- Seed Partition
- Example Configuration
Overview
hero-init is a Rust-based system initialization tool that runs at boot time to configure a Linux system based on a configuration file stored on a SEED device. The SEED device is a specially labeled storage device that contains the configuration needed to initialize the system.
The tool is designed to be idempotent, meaning it can be run multiple times without causing issues. It tracks which configuration modules have been applied using a state file to prevent re-applying configurations on subsequent boots.
Features
- Automatic discovery and mounting of SEED device
- Network configuration (static IPs, DHCP, routes, DNS)
- User account management (creation, SSH keys, sudo access)
- Hostname and hosts file configuration
- Filesystem mount management with optional fstab persistence
- Persistent environment variable configuration via
/etc/environment - Custom command execution with environment variables
- Idempotent operation with state tracking
- Support for multiple network providers (netplan, ifupdown)
- Comprehensive logging
How It Works
- At boot time, hero-init searches for a block device labeled "SEED"
- The SEED device is mounted read-only
- Configuration is loaded from
hero-init.yamlon the mounted device - The tool checks if this is the first boot by comparing instance IDs
- If it's the first boot, configuration modules are applied in order:
- Metadata (hostname, instance ID)
- Network configuration
- User account creation
- Filesystem mounts
- Environment variables
- Custom commands execution
- Each module's completion is tracked in a state file to prevent re-application
Configuration
Configuration Structure
The configuration file (hero-init.yaml) uses YAML format and contains the following top-level sections:
instance_id: "unique-identifier"
hostname: "system-hostname"
network:
# Network configuration
users:
- # User configurations
mounts:
- # Filesystem mounts
env:
# Global environment variables
runcmd:
- # Custom commands
Metadata Configuration
The metadata section defines system identification:
instance_id: "hero-001" # Unique identifier for this instance
hostname: "hero-vm" # System hostname
Network Configuration
The network section configures network interfaces:
network:
provider: netplan # or ifupdown
interfaces:
- name: ens3
mac: "52:54:00:12:34:56"
dhcp4: false
addresses:
- 10.0.2.100/24
routes:
- to: default
via: 10.0.2.2
nameservers:
addresses:
- 10.0.2.3
- 8.8.8.8
search:
- hero.local
Important: Network configuration files are written but not applied automatically. You must add the appropriate network commands to the runcmd section to apply the network configuration:
For netplan provider:
runcmd:
- "netplan apply"
For ifupdown provider:
runcmd:
- "systemctl restart networking"
User Configuration
The users section defines user accounts to create:
users:
- name: john
ssh_authorized_keys:
- "ssh-ed25519 AAAAC.... john@hero-test"
groups:
- sudo
sudo: true
Mounts Configuration
The mounts section defines filesystems to mount at boot:
mounts:
- device: /dev/vdc1
path: /mnt/data
fs_type: ext4
options: noatime
persist: true
- device: /dev/vdd1
path: /mnt/backup
fs_type: xfs
| Field | Required | Default | Description |
|---|---|---|---|
device |
yes | — | Block device path (e.g. /dev/sdb1) |
path |
yes | — | Mount point (e.g. /mnt/data) |
fs_type |
no | ext4 |
Filesystem type |
options |
no | defaults |
Mount options (comma-separated) |
persist |
no | false |
If true, an entry is added to /etc/fstab |
Initramfs note: When running from initramfs with a root prefix, mount paths are automatically prefixed so filesystems are mounted under the real root. The fstab entries always use the unprefixed (logical) paths so they remain correct after switch_root.
Environment Variables
The env section defines environment variables that are persisted to /etc/environment on the system. These variables are available to all users and processes after boot.
env:
LANG: "en_US.UTF-8"
EDITOR: "vim"
Existing variables in /etc/environment are preserved. If a variable already exists, its value is updated. New variables are appended.
This is independent of the per-command env in runcmd, which only sets variables for that specific command's execution.
Run Commands
The runcmd section allows execution of custom commands:
env:
LANG: "en_US.UTF-8"
EDITOR: "vim"
runcmd:
- "echo 'Hello from hero-init' >> /tmp/test1.txt"
- cmd: "echo $MY_VAR > /tmp/test2.txt"
env:
MY_VAR: "190"
Note: The top-level env section writes variables to /etc/environment for system-wide persistence. The per-command env in runcmd only sets variables for that specific command.
Module Functionalities
Discovery Module
The discovery module is responsible for finding and mounting the SEED device:
- Searches for block devices with the label "SEED" in
/dev/disk/by-label - Mounts the device read-only to
/run/hero-init/seed - Provides functions for device capacity detection and partition management
Key functions:
find_seed_device(label: &str) -> Option<PathBuf>- Finds a device by labelmount_seed(device: PathBuf, target: &Path) -> io::Result<()>- Mounts the device
Metadata Module
The metadata module handles system identification configuration:
- Sets the system hostname using both file update and system call
- Writes the instance ID to persistent storage
- Updates the hosts file with the new hostname
Key functions:
set_hostname(hostname: &str) -> Result<()>- Sets system hostnamewrite_instance_id(instance_id: &str) -> Result<()>- Persists instance IDupdate_hosts_file(hostname: &str) -> Result<()>- Updates /etc/hosts
Network Module
The network module configures network interfaces:
- Supports both netplan and ifupdown providers
- Configures static IPs, DHCP, routes, and DNS settings
- Generates appropriate configuration files based on provider
- Writes network configuration files (manual application required via runcmd)
Key functions:
apply(network: &Network) -> Result<()>- Main network configuration functionwrite_network_config(config_yaml: &str, provider: NetworkConfigType) -> io::Result<()>- Writes network config file
Users Module
The users module manages user accounts:
- Creates system users with specified shells and groups
- Sets up SSH authorized keys for passwordless authentication
- Configures sudo access for users
- Sets proper file permissions and ownership
Key functions:
add_system_user(username: &str, shell: &str, groups: &[&str]) -> Result<()>- Creates a userinject_ssh_keys(username: &str, keys: &[String]) -> Result<()>- Sets up SSH keysadd_sudo_rule(username: &str) -> Result<()>- Grants sudo accessapply(users: &[User]) -> Result<()>- Main user configuration function
Mounts Module
The mounts module handles filesystem mounting:
- Mounts filesystems specified in the configuration
- Supports custom filesystem types and mount options
- Optionally persists mounts to
/etc/fstab(skipping duplicates) - Handles initramfs root-prefix mode by mounting under the real root
Key functions:
apply(mounts: &[Mount], paths: &Paths) -> Result<()>- Main mount configuration function
Environment Module
The environment module handles persistent environment variable configuration:
- Writes environment variables to
/etc/environment - Preserves existing variables not specified in the configuration
- Updates existing variables if they are redefined
Key functions:
apply(env_vars: &HashMap<String, String>, paths: &Paths) -> Result<()>- Writes env vars to/etc/environment
Execution Module
The execution module handles custom command execution:
- Runs shell commands with optional environment variables
- Provides atomic file writing functionality
- Executes commands in the order specified in configuration
Key functions:
run_cmd(command: &str, env_vars: HashMap<String, String>) -> Result<()>- Executes a commandwrite_file_atomic(path: &Path, content: &str, mode: u32) -> Result<()>- Writes file atomicallyapply(commands: &[RunCommand]) -> Result<()>- Main execution function
State Module
The state module tracks which configuration modules have been applied:
- Loads and saves state to
/var/lib/hero-init/state - Prevents re-application of configurations on subsequent boots
- Uses a YAML file with a set of completed module names
Key functions:
load_state() -> Result<HeroState>- Loads state from disksave_state(state: &HeroState) -> Result<()>- Saves state to diskis_module_complete(state: &HeroState, module: &str) -> bool- Checks if module is completemark_module_complete(state: &mut HeroState, module: &str)- Marks module as complete
Tool Functions
hero-init provides several utility functions across its modules:
- Device Discovery: Automatically finds and mounts the SEED device
- Configuration Parsing: Reads and validates YAML configuration
- Idempotent Operations: Ensures configurations are only applied once
- State Management: Tracks completed configuration modules
- Filesystem Mounting: Mounts devices with optional fstab persistence
- Error Handling: Comprehensive error handling with detailed logging
- Atomic Operations: File operations are atomic to prevent corruption
How to Configure
- Create a SEED device with a filesystem labeled "SEED"
- Create a
hero-init.yamlfile on the SEED device with your configuration - Boot the system with the SEED device attached
- hero-init will automatically run and apply the configuration
Adding Configuration
To add configuration:
-
Mount the SEED device on a running system:
sudo mkdir -p /mnt/seed sudo mount /dev/disk/by-label/SEED /mnt/seed -
Edit or create
/mnt/seed/hero-init.yamlwith your desired configuration -
Unmount the device:
sudo umount /mnt/seed -
Boot the target system with the SEED device attached
Seed Partition
The SEED partition must:
- Be a filesystem on a block device
- Have the label "SEED"
- Contain a
hero-init.yamlfile with valid configuration - Be readable by the system
To create a SEED partition:
-
Create a filesystem with the SEED label:
dd if=/dev/zero of=seed.img bs=1M count=10 mkfs.vfat -n SEED seed.img -
Mount and populate with configuration:
sudo mount seed.img /mnt # Add hero-init.yaml sudo umount /mnt/seed
Example Configuration
See example.yaml for a complete configuration example:
instance_id: "hero-001"
hostname: "hero-vm"
network:
provider: netplan
interfaces:
- name: ens3
mac: "52:54:00:12:34:56"
dhcp4: false
addresses:
- 10.0.2.100/24
routes:
- to: default
via: 10.0.2.2
nameservers:
addresses:
- 10.0.2.3
- 8.8.8.8
search:
- hero.local
users:
- name: john
ssh_authorized_keys:
- "ssh-ed25519 AAAAC.... john@hero-test"
groups:
- sudo
sudo: true
mounts:
- device: /dev/vdc1
path: /mnt/data
fs_type: ext4
options: noatime
persist: true
- device: /dev/vdd1
path: /mnt/backup
fs_type: xfs
env:
NUM: "80"
STRING: "heroinit"
runcmd:
- "echo 'Hello from hero-init' >> /tmp/test1.txt"
- cmd: "echo $MY_VAR > /tmp/test2.txt"
env:
MY_VAR: "190"