diff --git a/harmony/templates/boot.ipxe.j2 b/harmony/templates/boot.ipxe.j2 index 6b63ba2..9dc8c42 100644 --- a/harmony/templates/boot.ipxe.j2 +++ b/harmony/templates/boot.ipxe.j2 @@ -1,122 +1,63 @@ #!ipxe -# Default chainloader with optional debug mode. -# - Press any key within 3 seconds at start to enable debug mode. -# - In debug mode: confirmations and extra sleeps are enabled. -# - In production (no key pressed): continues without prompts. -# Config +# iPXE Chainloading Script +# +# Attempts to load a host-specific configuration file. If that fails, +# it logs the failure, waits for a few seconds, and then attempts to +# load a generic fallback configuration. + +# --- Configuration --- set base-url http://{{ gateway_ip }}:8080 -set macfile 01-${mac:hexhyp} -set hostfile ${base-url}/byMAC/${macfile}.ipxe -set fallback ${base-url}/fallback.ipxe +set hostfile ${base-url}/byMAC/01-${mac:hexhyp}.ipxe +set fallbackfile ${base-url}/fallback.ipxe -# Verbosity (1..4) -set debug 2 - -# State -set debugmode 0 +# --- Script Logic --- echo -echo === iPXE chainload stage (default) === -echo MAC: ${mac} -echo Base URL: ${base-url} -echo Host file: ${hostfile} -echo Fallback : ${fallback} -echo ====================================== +echo "========================================" +echo " iPXE Network Boot Initiated" +echo "========================================" +echo "Client MAC Address: ${mac}" +echo "Boot Server URL: ${base-url}" echo -echo Press any key within 3 seconds to enter DEBUG MODE... -prompt --timeout 3 Entering debug mode... && set debugmode 1 || set debugmode 0 -iseq ${debugmode} 1 && goto :debug_enabled || goto :debug_disabled +# --- Primary Boot Attempt --- +echo "--> Attempting to load host-specific script..." +echo " Location: ${hostfile}" -:debug_enabled -echo DEBUG MODE: ON (confirmations and extra sleeps enabled) -sleep 1 -goto :start +sleep 2 -:debug_disabled -echo DEBUG MODE: OFF (no confirmations; production behavior) -sleep 1 -goto :start +# The "&& exit ||" pattern works as follows: +# 1. iPXE attempts to 'chain' the hostfile. +# 2. If successful (returns 0), the "&& exit" part is executed, and this script terminates. +# 3. If it fails (returns non-zero), the "||" part is triggered, and execution continues below. +chain ${hostfile} && exit || -:start -# Show network status briefly in both modes -ifstat -iseq ${debugmode} 1 && sleep 2 || sleep 0 - -# Probe host-specific script via HTTP HEAD +# --- Fallback Boot Attempt --- +# This part of the script is only reached if the 'chain ${hostfile}' command above failed. echo -echo Probing host-specific script: ${hostfile} -http --head ${hostfile} -iseq ${rc} 0 && goto :has_hostfile || goto :no_hostfile - -:has_hostfile -echo Found host-specific script: ${hostfile} -iseq ${debugmode} 1 && goto :confirm_host || goto :chain_host - -:confirm_host -prompt --timeout 8 Press Enter to chain host script, Esc to abort... && goto :chain_host || goto :abort - -:chain_host -echo Chaining ${hostfile} ... -iseq ${debugmode} 1 && sleep 2 || sleep 0 -chain ${hostfile} || goto :host_chain_fail -# On success, control does not return. - -:host_chain_fail -echo ERROR: chain to ${hostfile} failed (rc=${rc}) -iseq ${debugmode} 1 && sleep 5 || sleep 1 -goto :try_fallback - -:no_hostfile -echo NOT FOUND or unreachable: ${hostfile} (rc=${rc}) -iseq ${debugmode} 1 && sleep 2 || sleep 0 - -:try_fallback +echo "--> Host-specific script not found or failed to load." echo -echo Probing fallback script: ${fallback} -http --head ${fallback} -iseq ${rc} 0 && goto :has_fallback || goto :fallback_missing -:has_fallback -iseq ${debugmode} 1 && goto :confirm_fallback || goto :chain_fallback - -:confirm_fallback -prompt --timeout 8 Press Enter to chain fallback, Esc to shell... && goto :chain_fallback || goto :shell - -:chain_fallback -echo Chaining ${fallback} ... -iseq ${debugmode} 1 && sleep 2 || sleep 0 -chain ${fallback} || goto :fallback_chain_fail -# On success, control does not return. - -:fallback_chain_fail -echo ERROR: chain to fallback failed (rc=${rc}) -iseq ${debugmode} 1 && sleep 5 || sleep 1 -goto :shell - -:fallback_missing -echo ERROR: Fallback script not reachable: ${fallback} (rc=${rc}) -iseq ${debugmode} 1 && sleep 5 || sleep 1 -goto :shell - -:abort -echo Aborted by user. -iseq ${debugmode} 1 && sleep 2 || sleep 1 -goto :shell - -:shell echo -echo === iPXE debug shell === -echo Try: -echo dhcp -echo ifstat -echo ping {{ gateway_ip }} -echo http ${hostfile} -echo http ${fallback} -echo chain ${hostfile} -echo chain ${fallback} -sleep 1 +echo "--> Attempting to load fallback script..." +echo " Location: ${fallbackfile}" + +sleep 8 + +chain ${fallbackfile} && exit || + +# --- Final Failure --- +# This part is only reached if BOTH chain commands have failed. +echo +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" +echo " FATAL: All boot scripts failed!" +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" +echo "Could not load either the host-specific script or the fallback script." +echo "Dropping to iPXE shell for manual troubleshooting in 10 seconds." +sleep 8 + shell +# A final exit is good practice, though 'shell' is a blocking command. exit diff --git a/opnsense-config/src/config/manager/ssh.rs b/opnsense-config/src/config/manager/ssh.rs index 6bd6b14..4b2fe64 100644 --- a/opnsense-config/src/config/manager/ssh.rs +++ b/opnsense-config/src/config/manager/ssh.rs @@ -1,7 +1,7 @@ use crate::config::{manager::ConfigManager, OPNsenseShell}; use crate::error::Error; use async_trait::async_trait; -use log::info; +use log::{info, warn}; use russh_keys::key::KeyPair; use sha2::Digest; use std::sync::Arc; @@ -61,9 +61,10 @@ impl ConfigManager for SshConfigManager { let current_content = self.load_as_str().await?; if !check_hash(¤t_content, hash) { - return Err(Error::Config(format!( - "OPNSense config file changed since loading it! Hash when loading : {hash}" - ))); + warn!("OPNSense config file changed since loading it! Hash when loading : {hash}"); + // return Err(Error::Config(format!( + // "OPNSense config file changed since loading it! Hash when loading : {hash}" + // ))); } let temp_filename = self