diff --git a/Cargo.lock b/Cargo.lock index 99822cb..befd87e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,48 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + [[package]] name = "assert_cmd" version = "2.0.17" @@ -485,6 +527,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bcrypt-pbkdf" version = "0.10.0" @@ -1659,6 +1710,7 @@ dependencies = [ name = "example-pxe" version = "0.1.0" dependencies = [ + "askama", "cidr", "env_logger", "harmony", diff --git a/examples/okd_pxe/Cargo.toml b/examples/okd_pxe/Cargo.toml index 04a70b0..609432e 100644 --- a/examples/okd_pxe/Cargo.toml +++ b/examples/okd_pxe/Cargo.toml @@ -16,3 +16,4 @@ harmony_macros = { path = "../../harmony_macros" } log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } +askama = "0.14.0" diff --git a/examples/okd_pxe/src/main.rs b/examples/okd_pxe/src/main.rs index b026fbf..f5cb6cb 100644 --- a/examples/okd_pxe/src/main.rs +++ b/examples/okd_pxe/src/main.rs @@ -1,5 +1,8 @@ mod topology; +use std::net::IpAddr; + +use askama::Template; use harmony::{ data::{FileContent, FilePath}, modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore}, @@ -13,7 +16,7 @@ use crate::topology::{get_inventory, get_topology}; async fn main() { let inventory = get_inventory(); let topology = get_topology().await; - let gateway_ip = topology.router.get_gateway(); + let gateway_ip = &topology.router.get_gateway(); let kickstart_filename = "inventory.kickstart"; let cluster_pubkey_filename = "cluster_ssh_key.pub"; @@ -33,160 +36,30 @@ async fn main() { files_to_serve: Url::LocalFolder("./data/pxe/okd/tftpboot/".to_string()), }), Box::new(StaticFilesHttpScore { - folder_to_serve: Some(Url::LocalFolder("./data/pxe/okd/http_files/".to_string())), + // TODO The current russh based copy is way too slow, check for a lib update or use scp + // when available + // + // For now just run : + // scp -r data/pxe/okd/http_files/* root@192.168.1.1:/usr/local/http/ + // + folder_to_serve: None, + // folder_to_serve: Some(Url::LocalFolder("./data/pxe/okd/http_files/".to_string())), files: vec![ FileContent { path: FilePath::Relative("boot.ipxe".to_string()), - content: format!( - "#!ipxe - -set base-url http://{gateway_ip}:8080 -set hostfile ${{base-url}}/byMAC/01-${{mac:hexhyp}}.ipxe - -chain ${{hostfile}} || chain ${{base-url}}/default.ipxe" - ), + content: BootIpxeTpl { gateway_ip }.to_string(), }, FileContent { path: FilePath::Relative(kickstart_filename.to_string()), - content: format!( - r#"# ================================================================= -# Harmony Discovery Agent - Kickstart File (inventory.kickstart) -# ================================================================= -# -# This Kickstart file configures the CentOS Stream 9 live environment. -# It does NOT install to disk. It sets up SSH for remote access -# and downloads and runs the harmony-inventory-agent. -# - -# --- System Configuration -lang en_US.UTF-8 -keyboard --xlayouts='us' -timezone America/New_York --isUtc - -# --- Network Configuration -# Ensure the network is activated using DHCP. -network --bootproto=dhcp --device=link --activate - -# --- Security Configuration -# Disable the firewall for this isolated provisioning network. -firewall --disabled -# Disable SELinux for simplicity in the live environment. -selinux --disabled -# Disable password-based root login for security. -rootpw --lock - -# --- Service Configuration -# Ensure the SSH daemon is enabled. -services --enabled="sshd" - -# We are running a live environment, so no disk partitioning. -# The 'liveimg' command would be used here if booting from a squashfs, -# but since we are booting from kernel/initrd, we just use the %post. - -# Do not run the graphical initial setup wizard. -firstboot --disable - -# --- Post-Boot Scripting -# This section runs after the live environment has booted into RAM. -%post --log=/root/ks-post.log - -echo "Harmony Kickstart: Post-boot script started." - -# 1. Configure SSH Access -# Create the .ssh directory and set correct permissions. -echo " - Setting up SSH authorized_keys..." -mkdir -p /root/.ssh -chmod 700 /root/.ssh - -# Download the public key and place it in authorized_keys. -curl -sSL "http://{gateway_ip}:8080/{cluster_pubkey_filename}" -o /root/.ssh/authorized_keys -chmod 600 /root/.ssh/authorized_keys - -# SELinux context is handled by 'selinux --disabled' above, -# but if SELinux were enabled, this would be essential: -# restorecon -R /root/.ssh - -# 2. Download the Harmony Inventory Agent -echo " - Downloading harmony-inventory-agent..." -curl -sSL "http://{gateway_ip}:8080/{harmony_inventory_agent}" -o /usr/local/bin/harmony-inventory-agent -chmod +x /usr/local/bin/harmony-inventory-agent - -# 3. Create a systemd service to run the agent persistently -echo " - Creating systemd service for the agent..." -cat > /etc/systemd/system/harmony-agent.service << EOF -[Unit] -Description=Harmony Inventory Agent -After=network-online.target -Wants=network-online.target - -[Service] -ExecStart=/usr/local/bin/harmony-inventory-agent -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target -EOF - -# 4. Enable and start the service -echo " - Enabling and starting harmony-agent.service..." -systemctl daemon-reload -systemctl enable --now harmony-agent.service - -echo "Harmony Kickstart: Post-boot script finished. The inventory agent is running." - -curl localhost:8080/inventory | tee -a /tmp/harmony_inventory.json - -%end - -# Do not automatically reboot or poweroff. -# The machine should remain running for inventory scraping."# - ), + content: InventoryKickstartTpl { + gateway_ip, + harmony_inventory_agent, + cluster_pubkey_filename, + }.to_string(), }, FileContent { - path: FilePath::Relative("default.ipxe".to_string()), - content: format!( - r#"#!ipxe - -# ================================================================= -# Harmony Discovery Agent - Default Boot Script (default.ipxe) -# ================================================================= -# -# This script boots the CentOS Stream live environment for the -# purpose of hardware inventory. It loads the kernel and initramfs -# directly and passes a Kickstart URL for full automation. -# - -# --- Configuration -# Set the base URL for where the CentOS kernel/initrd are hosted. -set os_base_url http://{gateway_ip}:8080/os/centos-stream-9 -# Set the URL for the Kickstart file. -set ks_url http://{gateway_ip}:8080/{kickstart_filename} - -# --- Boot Process -echo "Harmony: Starting automated node discovery..." -echo "Fetching kernel from ${{os_base_url}}/vmlinuz..." -kernel ${{os_base_url}}/vmlinuz - -echo "Fetching initramfs from ${{os_base_url}}/initrd.img..." -initrd ${{os_base_url}}/initrd.img - -echo "Configuring kernel boot arguments..." -# Kernel Arguments Explained: -# - initrd=initrd.img: Specifies the initial ramdisk to use. -# - inst.stage2: Points to the OS source. For a live boot, the base URL is sufficient. -# - inst.ks: CRITICAL: Points to our Kickstart file for automation. -# - ip=dhcp: Ensures the live environment configures its network. -# - console=...: Provides boot output on both serial and graphical consoles for debugging. -imgargs vmlinuz initrd=initrd.img inst.stage2=${{os_base_url}} inst.ks=${{ks_url}} ip=dhcp console=ttyS0,115200 console=tty1 - -echo "Booting into CentOS Stream 9 live environment..." -boot || goto failed - -:failed -echo "Boot failed. Dropping to iPXE shell." -shell"# - ), + path: FilePath::Relative("fallback.ipxe".to_string()), + content: FallbackIpxeTpl { gateway_ip, kickstart_filename}.to_string(), }, ], }), @@ -196,3 +69,24 @@ shell"# .await .unwrap(); } + +#[derive(Template)] +#[template(path = "boot.ipxe.j2")] +struct BootIpxeTpl<'a> { + gateway_ip: &'a IpAddr, +} + +#[derive(Template)] +#[template(path = "fallback.ipxe.j2")] +struct FallbackIpxeTpl<'a> { + gateway_ip: &'a IpAddr, + kickstart_filename: &'a str, +} + +#[derive(Template)] +#[template(path = "inventory.kickstart.j2")] +struct InventoryKickstartTpl<'a> { + gateway_ip: &'a IpAddr, + cluster_pubkey_filename: &'a str, + harmony_inventory_agent: &'a str, +} diff --git a/examples/okd_pxe/templates/boot.ipxe.j2 b/examples/okd_pxe/templates/boot.ipxe.j2 new file mode 100644 index 0000000..94ea07b --- /dev/null +++ b/examples/okd_pxe/templates/boot.ipxe.j2 @@ -0,0 +1,6 @@ +#!ipxe + +set base-url http://{{ gateway_ip }}:8080 +set hostfile ${base-url}/byMAC/01-${mac:hexhyp}.ipxe + +chain ${hostfile} || chain ${base-url}/fallback.ipxe diff --git a/examples/okd_pxe/templates/fallback.ipxe.j2 b/examples/okd_pxe/templates/fallback.ipxe.j2 new file mode 100644 index 0000000..5335e8f --- /dev/null +++ b/examples/okd_pxe/templates/fallback.ipxe.j2 @@ -0,0 +1,40 @@ +#!ipxe + +# ================================================================= +# Harmony Discovery Agent - Default Boot Script (default.ipxe) +# ================================================================= +# +# This script boots the CentOS Stream live environment for the +# purpose of hardware inventory. It loads the kernel and initramfs +# directly and passes a Kickstart URL for full automation. +# + +# --- Configuration +# Set the base URL for where the CentOS kernel/initrd are hosted. +set os_base_url http://{{gateway_ip}}:8080/os/centos-stream-9 +# Set the URL for the Kickstart file. +set ks_url http://{{ gateway_ip }}:8080/{{ kickstart_filename }} + +# --- Boot Process +echo "Harmony: Starting automated node discovery..." +echo "Fetching kernel from ${os_base_url}/vmlinuz..." +kernel ${os_base_url}/vmlinuz + +echo "Fetching initramfs from ${os_base_url}/initrd.img..." +initrd ${os_base_url}/initrd.img + +echo "Configuring kernel boot arguments..." +# Kernel Arguments Explained: +# - initrd=initrd.img: Specifies the initial ramdisk to use. +# - inst.stage2: Points to the OS source. For a live boot, the base URL is sufficient. +# - inst.ks: CRITICAL: Points to our Kickstart file for automation. +# - ip=dhcp: Ensures the live environment configures its network. +# - console=...: Provides boot output on both serial and graphical consoles for debugging. +imgargs vmlinuz initrd=initrd.img inst.stage2=${os_base_url} inst.ks=${ks_url} ip=dhcp console=ttyS0,115200 console=tty1 + +echo "Booting into CentOS Stream 9 live environment..." +boot || goto failed + +:failed +echo "Boot failed. Dropping to iPXE shell." +shell diff --git a/examples/okd_pxe/templates/inventory.kickstart.j2 b/examples/okd_pxe/templates/inventory.kickstart.j2 new file mode 100644 index 0000000..7bdd66e --- /dev/null +++ b/examples/okd_pxe/templates/inventory.kickstart.j2 @@ -0,0 +1,92 @@ +# ================================================================= +# Harmony Discovery Agent - Kickstart File (inventory.kickstart) +# ================================================================= +# +# This Kickstart file configures the CentOS Stream 9 live environment. +# It does NOT install to disk. It sets up SSH for remote access +# and downloads and runs the harmony-inventory-agent. +# + +# --- System Configuration +lang en_US.UTF-8 +keyboard --xlayouts='us' +timezone America/New_York --isUtc + +# --- Network Configuration +# Ensure the network is activated using DHCP. +network --bootproto=dhcp --device=link --activate + +# --- Security Configuration +# Disable the firewall for this isolated provisioning network. +firewall --disabled +# Disable SELinux for simplicity in the live environment. +selinux --disabled +# Disable password-based root login for security. +rootpw --lock + +# --- Service Configuration +# Ensure the SSH daemon is enabled. +services --enabled="sshd" + +# We are running a live environment, so no disk partitioning. +# The 'liveimg' command would be used here if booting from a squashfs, +# but since we are booting from kernel/initrd, we just use the %post. + +# Do not run the graphical initial setup wizard. +firstboot --disable + +# --- Post-Boot Scripting +# This section runs after the live environment has booted into RAM. +%post --log=/root/ks-post.log + +echo "Harmony Kickstart: Post-boot script started." + +# 1. Configure SSH Access +# Create the .ssh directory and set correct permissions. +echo " - Setting up SSH authorized_keys..." +mkdir -p /root/.ssh +chmod 700 /root/.ssh + +# Download the public key and place it in authorized_keys. +curl -sSL "http://{{ gateway_ip }}:8080/{{ cluster_pubkey_filename }}" -o /root/.ssh/authorized_keys +chmod 600 /root/.ssh/authorized_keys + +# SELinux context is handled by 'selinux --disabled' above, +# but if SELinux were enabled, this would be essential: +# restorecon -R /root/.ssh + +# 2. Download the Harmony Inventory Agent +echo " - Downloading harmony-inventory-agent..." +curl -sSL "http://{{ gateway_ip }}:8080/{{ harmony_inventory_agent }}" -o /usr/local/bin/harmony-inventory-agent +chmod +x /usr/local/bin/harmony-inventory-agent + +# 3. Create a systemd service to run the agent persistently +echo " - Creating systemd service for the agent..." +cat > /etc/systemd/system/harmony-agent.service << EOF +[Unit] +Description=Harmony Inventory Agent +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/local/bin/harmony-inventory-agent +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +# 4. Enable and start the service +echo " - Enabling and starting harmony-agent.service..." +systemctl daemon-reload +systemctl enable --now harmony-agent.service + +echo "Harmony Kickstart: Post-boot script finished. The inventory agent is running." + +curl localhost:8080/inventory | tee -a /tmp/harmony_inventory.json + +%end + +# Do not automatically reboot or poweroff. +# The machine should remain running for inventory scraping.