hero-init is a minimal, deterministic replacement for cloud-init, written in Rust. It focuses only on basic VM needs: metadata, networking, users & SSH keys, mounts, and controlled startup commands. No datasources, no magic—just fast, auditable init logic designed for custom images and infrastructure platforms.
Find a file
RawanMostafa08 030f844f2a
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
fix: re-apply mounts on reboot in initramfs case
2026-02-12 15:46:58 +02:00
.forgejo/workflows chore: release workflow 2026-02-12 13:06:58 +02:00
src fix: re-apply mounts on reboot in initramfs case 2026-02-12 15:46:58 +02:00
.gitignore Initial commit 2026-01-26 09:51:36 +00:00
Cargo.toml feat: handled rtnetlink case if no provider, added some return errors to avoid silent failures 2026-02-04 16:26:24 +02:00
example.yaml support env vars 2026-02-11 18:41:32 +02:00
README.md support env vars 2026-02-11 18:41:32 +02:00

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

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

  1. At boot time, hero-init searches for a block device labeled "SEED"
  2. The SEED device is mounted read-only
  3. Configuration is loaded from hero-init.yaml on the mounted device
  4. The tool checks if this is the first boot by comparing instance IDs
  5. 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
  6. 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 label
  • mount_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 hostname
  • write_instance_id(instance_id: &str) -> Result<()> - Persists instance ID
  • update_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 function
  • write_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 user
  • inject_ssh_keys(username: &str, keys: &[String]) -> Result<()> - Sets up SSH keys
  • add_sudo_rule(username: &str) -> Result<()> - Grants sudo access
  • apply(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 command
  • write_file_atomic(path: &Path, content: &str, mode: u32) -> Result<()> - Writes file atomically
  • apply(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 disk
  • save_state(state: &HeroState) -> Result<()> - Saves state to disk
  • is_module_complete(state: &HeroState, module: &str) -> bool - Checks if module is complete
  • mark_module_complete(state: &mut HeroState, module: &str) - Marks module as complete

Tool Functions

hero-init provides several utility functions across its modules:

  1. Device Discovery: Automatically finds and mounts the SEED device
  2. Configuration Parsing: Reads and validates YAML configuration
  3. Idempotent Operations: Ensures configurations are only applied once
  4. State Management: Tracks completed configuration modules
  5. Filesystem Mounting: Mounts devices with optional fstab persistence
  6. Error Handling: Comprehensive error handling with detailed logging
  7. Atomic Operations: File operations are atomic to prevent corruption

How to Configure

  1. Create a SEED device with a filesystem labeled "SEED"
  2. Create a hero-init.yaml file on the SEED device with your configuration
  3. Boot the system with the SEED device attached
  4. hero-init will automatically run and apply the configuration

Adding Configuration

To add configuration:

  1. Mount the SEED device on a running system:

    sudo mkdir -p /mnt/seed
    sudo mount /dev/disk/by-label/SEED /mnt/seed
    
  2. Edit or create /mnt/seed/hero-init.yaml with your desired configuration

  3. Unmount the device:

    sudo umount /mnt/seed
    
  4. Boot the target system with the SEED device attached

Seed Partition

The SEED partition must:

  1. Be a filesystem on a block device
  2. Have the label "SEED"
  3. Contain a hero-init.yaml file with valid configuration
  4. Be readable by the system

To create a SEED partition:

  1. Create a filesystem with the SEED label:

     dd if=/dev/zero of=seed.img bs=1M count=10
     mkfs.vfat -n SEED seed.img
    
  2. 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"