mod topology; use harmony::{ data::{FileContent, FilePath}, modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore}, score::Score, topology::{HAClusterTopology, Url}, }; use crate::topology::{get_inventory, get_topology}; #[tokio::main] async fn main() { let inventory = get_inventory(); let topology = get_topology().await; let gateway_ip = topology.router.get_gateway(); let kickstart_filename = "inventory.kickstart"; let cluster_pubkey_filename = "cluster_ssh_key.pub"; let harmony_inventory_agent = "harmony_inventory_agent"; // TODO this should be a single IPXEScore instead of having the user do this step by step let scores: Vec>> = vec![ Box::new(DhcpScore { host_binding: vec![], next_server: Some(topology.router.get_gateway()), boot_filename: None, filename: Some("undionly.kpxe".to_string()), filename64: Some("ipxe.efi".to_string()), filenameipxe: Some(format!("http://{gateway_ip}:8080/boot.ipxe").to_string()), }), Box::new(TftpScore { 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())), 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" ), }, 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."# ), }, 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"# ), }, ], }), ]; harmony_cli::run(inventory, topology, scores, None) .await .unwrap(); }