Compare commits
	
		
			30 Commits
		
	
	
		
			3ca31179d0
			...
			269f13ae9b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 269f13ae9b | |||
| ec277bc13d | |||
| a9f8cd16ea | |||
| c542a935e3 | |||
| 0395d11e98 | |||
| 05e7b8075c | |||
| b857412151 | |||
| 7bb3602ab8 | |||
| 78b80c2169 | |||
| 0876f4e4f0 | |||
| 6ac0e095a3 | |||
| ff2efc0a66 | |||
|  | f180cc4c80 | ||
| 8cc7adf196 | |||
| a1ab5d40fb | |||
| 6c92dd24f7 | |||
| c805d7e018 | |||
| b33615b969 | |||
| 0f59f29ac4 | |||
| 361f240762 | |||
| 57c3b01e66 | |||
| 94ddf027dd | |||
| 06a2be4496 | |||
| e2a09efdee | |||
| 2618441de3 | |||
| da6610c625 | |||
| e956772593 | |||
| 27c51e0ec5 | |||
| 597dcbc848 | |||
| a53e8552e9 | 
							
								
								
									
										744
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										744
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -14,7 +14,7 @@ members = [ | |||||||
|   "harmony_composer", |   "harmony_composer", | ||||||
|   "harmony_inventory_agent", |   "harmony_inventory_agent", | ||||||
|   "harmony_secret_derive", |   "harmony_secret_derive", | ||||||
|   "harmony_secret", |   "harmony_secret", "adr/agent_discovery/mdns", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [workspace.package] | [workspace.package] | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								adr/agent_discovery/mdns/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								adr/agent_discovery/mdns/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | [package] | ||||||
|  | name = "mdns" | ||||||
|  | edition = "2024" | ||||||
|  | version.workspace = true | ||||||
|  | readme.workspace = true | ||||||
|  | license.workspace = true | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | mdns-sd = "0.14" | ||||||
|  | tokio = { version = "1", features = ["full"] } | ||||||
|  | futures = "0.3" | ||||||
|  | dmidecode = "0.2" # For getting the motherboard ID on the agent | ||||||
|  | log.workspace=true | ||||||
|  | env_logger.workspace=true | ||||||
|  | clap = { version = "4.5.46", features = ["derive"] } | ||||||
|  | get_if_addrs = "0.5.3" | ||||||
|  | local-ip-address = "0.6.5" | ||||||
							
								
								
									
										60
									
								
								adr/agent_discovery/mdns/src/advertise.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								adr/agent_discovery/mdns/src/advertise.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | // harmony-agent/src/main.rs
 | ||||||
|  | 
 | ||||||
|  | use log::info; | ||||||
|  | use mdns_sd::{ServiceDaemon, ServiceInfo}; | ||||||
|  | use std::collections::HashMap; | ||||||
|  | 
 | ||||||
|  | use crate::SERVICE_TYPE; | ||||||
|  | 
 | ||||||
|  | // The service we are advertising.
 | ||||||
|  | const SERVICE_PORT: u16 = 43210; // A port for the service. It needs one, even if unused.
 | ||||||
|  | 
 | ||||||
|  | pub async fn advertise() { | ||||||
|  |     info!("Starting Harmony Agent..."); | ||||||
|  | 
 | ||||||
|  |     // Get a unique ID for this machine.
 | ||||||
|  |     let motherboard_id = "some motherboard id"; | ||||||
|  |     let instance_name = format!("harmony-agent-{}", motherboard_id); | ||||||
|  |     info!("This agent's instance name: {}", instance_name); | ||||||
|  |     info!("Advertising with ID: {}", motherboard_id); | ||||||
|  | 
 | ||||||
|  |     // Create a new mDNS daemon.
 | ||||||
|  |     let mdns = ServiceDaemon::new().expect("Failed to create mDNS daemon"); | ||||||
|  | 
 | ||||||
|  |     // Create a TXT record HashMap to hold our metadata.
 | ||||||
|  |     let mut properties = HashMap::new(); | ||||||
|  |     properties.insert("id".to_string(), motherboard_id.to_string()); | ||||||
|  |     properties.insert("version".to_string(), "1.0".to_string()); | ||||||
|  | 
 | ||||||
|  |     // Create the service information.
 | ||||||
|  |     // The instance name should be unique on the network.
 | ||||||
|  |     let local_ip = local_ip_address::local_ip().unwrap(); | ||||||
|  |     let service_info = ServiceInfo::new( | ||||||
|  |         SERVICE_TYPE, | ||||||
|  |         &instance_name, | ||||||
|  |         "harmony-host.local.", // A hostname for the service
 | ||||||
|  |         local_ip, | ||||||
|  |         // "0.0.0.0",
 | ||||||
|  |         SERVICE_PORT, | ||||||
|  |         Some(properties), | ||||||
|  |     ) | ||||||
|  |     .expect("Failed to create service info"); | ||||||
|  | 
 | ||||||
|  |     // Register our service with the daemon.
 | ||||||
|  |     mdns.register(service_info) | ||||||
|  |         .expect("Failed to register service"); | ||||||
|  | 
 | ||||||
|  |     info!( | ||||||
|  |         "Service '{}' registered and now being advertised.", | ||||||
|  |         instance_name | ||||||
|  |     ); | ||||||
|  |     info!("Agent is running. Press Ctrl+C to exit."); | ||||||
|  | 
 | ||||||
|  |     for iface in get_if_addrs::get_if_addrs().unwrap() { | ||||||
|  |         println!("{:#?}", iface); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Keep the agent running indefinitely.
 | ||||||
|  |     tokio::signal::ctrl_c().await.unwrap(); | ||||||
|  |     info!("Shutting down agent."); | ||||||
|  | } | ||||||
							
								
								
									
										110
									
								
								adr/agent_discovery/mdns/src/discover.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								adr/agent_discovery/mdns/src/discover.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | |||||||
|  | use log::debug; | ||||||
|  | use mdns_sd::{ServiceDaemon, ServiceEvent}; | ||||||
|  | 
 | ||||||
|  | use crate::SERVICE_TYPE; | ||||||
|  | 
 | ||||||
|  | pub async fn discover() { | ||||||
|  |     println!("Starting Harmony Master and browsing for agents..."); | ||||||
|  | 
 | ||||||
|  |     // Create a new mDNS daemon.
 | ||||||
|  |     let mdns = ServiceDaemon::new().expect("Failed to create mDNS daemon"); | ||||||
|  | 
 | ||||||
|  |     // Start browsing for the service type.
 | ||||||
|  |     // The receiver will be a stream of events.
 | ||||||
|  |     let receiver = mdns.browse(SERVICE_TYPE).expect("Failed to browse"); | ||||||
|  | 
 | ||||||
|  |     println!( | ||||||
|  |         "Listening for mDNS events for '{}'. Press Ctrl+C to exit.", | ||||||
|  |         SERVICE_TYPE | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     std::thread::spawn(move || { | ||||||
|  |         while let Ok(event) = receiver.recv() { | ||||||
|  |             match event { | ||||||
|  |                 ServiceEvent::ServiceData(resolved) => { | ||||||
|  |                     println!("Resolved a new service: {}", resolved.fullname); | ||||||
|  |                 } | ||||||
|  |                 other_event => { | ||||||
|  |                     println!("Received other event: {:?}", &other_event); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Gracefully shutdown the daemon.
 | ||||||
|  |     std::thread::sleep(std::time::Duration::from_secs(1000000)); | ||||||
|  |     mdns.shutdown().unwrap(); | ||||||
|  | 
 | ||||||
|  |     // Process events as they come in.
 | ||||||
|  |     // while let Ok(event) = receiver.recv_async().await {
 | ||||||
|  |     //     debug!("Received event {event:?}");
 | ||||||
|  |     //     // match event {
 | ||||||
|  |     //     //     ServiceEvent::ServiceFound(svc_type, fullname) => {
 | ||||||
|  |     //     //         println!("\n--- Agent Discovered ---");
 | ||||||
|  |     //     //         println!("  Service Name: {}", fullname());
 | ||||||
|  |     //     //         // You can now resolve this service to get its IP, port, and TXT records
 | ||||||
|  |     //     //         // The resolve operation is a separate network call.
 | ||||||
|  |     //     //         let receiver = mdns.browse(info.get_fullname()).unwrap();
 | ||||||
|  |     //     //         if let Ok(resolve_event) = receiver.recv_timeout(Duration::from_secs(2)) {
 | ||||||
|  |     //     //              if let ServiceEvent::ServiceResolved(info) = resolve_event {
 | ||||||
|  |     //     //                 let ip = info.get_addresses().iter().next().unwrap();
 | ||||||
|  |     //     //                 let port = info.get_port();
 | ||||||
|  |     //     //                 let motherboard_id = info.get_property("id").map_or("N/A", |v| v.val_str());
 | ||||||
|  |     //     //
 | ||||||
|  |     //     //                 println!("  IP: {}:{}", ip, port);
 | ||||||
|  |     //     //                 println!("  Motherboard ID: {}", motherboard_id);
 | ||||||
|  |     //     //                 println!("------------------------");
 | ||||||
|  |     //     //
 | ||||||
|  |     //     //                 // TODO: Add this agent to your central list of discovered hosts.
 | ||||||
|  |     //     //              }
 | ||||||
|  |     //     //         } else {
 | ||||||
|  |     //     //             println!("Could not resolve service '{}' in time.", info.get_fullname());
 | ||||||
|  |     //     //         }
 | ||||||
|  |     //     //     }
 | ||||||
|  |     //     //     ServiceEvent::ServiceRemoved(info) => {
 | ||||||
|  |     //     //         println!("\n--- Agent Removed ---");
 | ||||||
|  |     //     //         println!("  Service Name: {}", info.get_fullname());
 | ||||||
|  |     //     //         println!("---------------------");
 | ||||||
|  |     //     //         // TODO: Remove this agent from your list.
 | ||||||
|  |     //     //     }
 | ||||||
|  |     //     //     _ => {
 | ||||||
|  |     //     //         // We don't care about other event types for this example
 | ||||||
|  |     //     //     }
 | ||||||
|  |     //     // }
 | ||||||
|  |     // }
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn discover_example() { | ||||||
|  |     use mdns_sd::{ServiceDaemon, ServiceEvent}; | ||||||
|  | 
 | ||||||
|  |     // Create a daemon
 | ||||||
|  |     let mdns = ServiceDaemon::new().expect("Failed to create daemon"); | ||||||
|  | 
 | ||||||
|  |     // Use recently added `ServiceEvent::ServiceData`.
 | ||||||
|  |     mdns.use_service_data(true) | ||||||
|  |         .expect("Failed to use ServiceData"); | ||||||
|  | 
 | ||||||
|  |     // Browse for a service type.
 | ||||||
|  |     let service_type = "_mdns-sd-my-test._udp.local."; | ||||||
|  |     let receiver = mdns.browse(service_type).expect("Failed to browse"); | ||||||
|  | 
 | ||||||
|  |     // Receive the browse events in sync or async. Here is
 | ||||||
|  |     // an example of using a thread. Users can call `receiver.recv_async().await`
 | ||||||
|  |     // if running in async environment.
 | ||||||
|  |     std::thread::spawn(move || { | ||||||
|  |         while let Ok(event) = receiver.recv() { | ||||||
|  |             match event { | ||||||
|  |                 ServiceEvent::ServiceData(resolved) => { | ||||||
|  |                     println!("Resolved a new service: {}", resolved.fullname); | ||||||
|  |                 } | ||||||
|  |                 other_event => { | ||||||
|  |                     println!("Received other event: {:?}", &other_event); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Gracefully shutdown the daemon.
 | ||||||
|  |     std::thread::sleep(std::time::Duration::from_secs(1)); | ||||||
|  |     mdns.shutdown().unwrap(); | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								adr/agent_discovery/mdns/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								adr/agent_discovery/mdns/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | use clap::{Parser, ValueEnum}; | ||||||
|  | 
 | ||||||
|  | mod advertise; | ||||||
|  | mod discover; | ||||||
|  | 
 | ||||||
|  | #[derive(Parser, Debug)] | ||||||
|  | #[command(version, about, long_about = None)] | ||||||
|  | struct Args { | ||||||
|  |     #[arg(value_enum)] | ||||||
|  |     profile: Profiles, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] | ||||||
|  | enum Profiles { | ||||||
|  |     Advertise, | ||||||
|  |     Discover, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // The service type we are looking for.
 | ||||||
|  | const SERVICE_TYPE: &str = "_harmony._tcp.local."; | ||||||
|  | 
 | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() { | ||||||
|  |     env_logger::init(); | ||||||
|  |     let args = Args::parse(); | ||||||
|  | 
 | ||||||
|  |     match args.profile { | ||||||
|  |         Profiles::Advertise => advertise::advertise().await, | ||||||
|  |         Profiles::Discover => discover::discover().await, | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								check.sh
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								check.sh
									
									
									
									
									
								
							| @ -1,6 +1,7 @@ | |||||||
| #!/bin/sh | #!/bin/sh | ||||||
| set -e | set -e | ||||||
| 
 | 
 | ||||||
|  | rustc --version | ||||||
| cargo check --all-targets --all-features --keep-going | cargo check --all-targets --all-features --keep-going | ||||||
| cargo fmt --check | cargo fmt --check | ||||||
| cargo clippy | cargo clippy | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								data/pxe/okd/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								data/pxe/okd/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | Here lies all the data files required for an OKD cluster PXE boot setup. | ||||||
|  | 
 | ||||||
|  | This inclues ISO files, binary boot files, ipxe, etc. | ||||||
|  | 
 | ||||||
|  | TODO as of august 2025 : | ||||||
|  | 
 | ||||||
|  | - `harmony_inventory_agent` should be downloaded from official releases, this embedded version is practical for now though | ||||||
|  | - The cluster ssh key should be generated and handled by harmony with the private key saved in a secret store | ||||||
							
								
								
									
										9
									
								
								data/pxe/okd/http_files/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								data/pxe/okd/http_files/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | harmony_inventory_agent filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9 filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9/images filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9/initrd.img filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9/vmlinuz filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9/images/efiboot.img filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9/images/install.img filter=lfs diff=lfs merge=lfs -text | ||||||
|  | os/centos-stream-9/images/pxeboot filter=lfs diff=lfs merge=lfs -text | ||||||
							
								
								
									
										1
									
								
								data/pxe/okd/http_files/cluster_ssh_key.pub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/pxe/okd/http_files/cluster_ssh_key.pub
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2 | ||||||
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/harmony_inventory_agent
									 (Stored with Git LFS)
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/harmony_inventory_agent
									 (Stored with Git LFS)
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/images/efiboot.img
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/images/efiboot.img
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/images/install.img
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/images/install.img
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/images/pxeboot/vmlinuz
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/images/pxeboot/vmlinuz
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/initrd.img
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/initrd.img
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/vmlinuz
									 (Stored with Git LFS)
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/http_files/os/centos-stream-9/vmlinuz
									 (Stored with Git LFS)
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/tftpboot/ipxe.efi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/tftpboot/ipxe.efi
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								data/pxe/okd/tftpboot/undionly.kpxe
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/pxe/okd/tftpboot/undionly.kpxe
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										108
									
								
								docs/pxe_test/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								docs/pxe_test/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | # OPNsense PXE Lab Environment | ||||||
|  | 
 | ||||||
|  | This project contains a script to automatically set up a virtual lab environment for testing PXE boot services managed by an OPNsense firewall. | ||||||
|  | 
 | ||||||
|  | ## Overview | ||||||
|  | 
 | ||||||
|  | The `pxe_vm_lab_setup.sh` script will create the following resources using libvirt/KVM: | ||||||
|  | 
 | ||||||
|  | 1.  **A Virtual Network**: An isolated network named `harmonylan` (`virbr1`) for the lab. | ||||||
|  | 2.  **Two Virtual Machines**: | ||||||
|  |     *   `opnsense-pxe`: A firewall VM that will act as the gateway and PXE server. | ||||||
|  |     *   `pxe-node-1`: A client VM configured to boot from the network. | ||||||
|  | 
 | ||||||
|  | ## Prerequisites | ||||||
|  | 
 | ||||||
|  | Ensure you have the following software installed on your Arch Linux host: | ||||||
|  | 
 | ||||||
|  | *   `libvirt` | ||||||
|  | *   `qemu` | ||||||
|  | *   `virt-install` (from the `virt-install` package) | ||||||
|  | *   `curl` | ||||||
|  | *   `bzip2` | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | 
 | ||||||
|  | ### 1. Create the Environment | ||||||
|  | 
 | ||||||
|  | Run the `up` command to download the necessary images and create the network and VMs. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | sudo ./pxe_vm_lab_setup.sh up | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### 2. Install and Configure OPNsense | ||||||
|  | 
 | ||||||
|  | The OPNsense VM is created but the OS needs to be installed manually via the console. | ||||||
|  | 
 | ||||||
|  | 1.  **Connect to the VM console**: | ||||||
|  |     ```bash | ||||||
|  |     sudo virsh console opnsense-pxe | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | 2.  **Log in as the installer**: | ||||||
|  |     *   Username: `installer` | ||||||
|  |     *   Password: `opnsense` | ||||||
|  | 
 | ||||||
|  | 3.  **Follow the on-screen installation wizard**. When prompted to assign network interfaces (`WAN` and `LAN`): | ||||||
|  |     *   Find the MAC address for the `harmonylan` interface by running this command in another terminal: | ||||||
|  |         ```bash | ||||||
|  |         virsh domiflist opnsense-pxe | ||||||
|  |         # Example output: | ||||||
|  |         # Interface   Type      Source       Model    MAC | ||||||
|  |         # --------------------------------------------------------- | ||||||
|  |         # vnet18      network   default      virtio   52:54:00:b5:c4:6d | ||||||
|  |         # vnet19      network   harmonylan   virtio   52:54:00:21:f9:ba | ||||||
|  |         ``` | ||||||
|  |     *   Assign the interface connected to `harmonylan` (e.g., `vtnet1` with MAC `52:54:00:21:f9:ba`) as your **LAN**. | ||||||
|  |     *   Assign the other interface as your **WAN**. | ||||||
|  | 
 | ||||||
|  | 4.  After the installation is complete, **shut down** the VM from the console menu. | ||||||
|  | 
 | ||||||
|  | 5.  **Detach the installation media** by editing the VM's configuration: | ||||||
|  |     ```bash | ||||||
|  |     sudo virsh edit opnsense-pxe | ||||||
|  |     ``` | ||||||
|  |     Find and **delete** the entire `<disk>` block corresponding to the `.img` file (the one with `<target ... bus='usb'/>`). | ||||||
|  | 
 | ||||||
|  | 6.  **Start the VM** to boot into the newly installed system: | ||||||
|  |     ```bash | ||||||
|  |     sudo virsh start opnsense-pxe | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | ### 3. Connect to OPNsense from Your Host | ||||||
|  | 
 | ||||||
|  | To configure OPNsense, you need to connect your host to the `harmonylan` network. | ||||||
|  | 
 | ||||||
|  | 1.  By default, OPNsense configures its LAN interface with the IP `192.168.1.1`. | ||||||
|  | 2.  Assign a compatible IP address to your host's `virbr1` bridge interface: | ||||||
|  |     ```bash | ||||||
|  |     sudo ip addr add 192.168.1.5/24 dev virbr1 | ||||||
|  |     ``` | ||||||
|  | 3.  You can now access the OPNsense VM from your host: | ||||||
|  |     *   **SSH**: `ssh root@192.168.1.1` (password: `opnsense`) | ||||||
|  |     *   **Web UI**: `https://192.168.1.1` | ||||||
|  | 
 | ||||||
|  | ### 4. Configure PXE Services with Harmony | ||||||
|  | 
 | ||||||
|  | With connectivity established, you can now use Harmony to configure the OPNsense firewall for PXE booting. Point your Harmony OPNsense scores to the firewall using these details: | ||||||
|  | 
 | ||||||
|  | *   **Hostname/IP**: `192.168.1.1` | ||||||
|  | *   **Credentials**: `root` / `opnsense` | ||||||
|  | 
 | ||||||
|  | ### 5. Boot the PXE Client | ||||||
|  | 
 | ||||||
|  | Once your Harmony configuration has been applied and OPNsense is serving DHCP/TFTP, start the client VM. It will automatically attempt to boot from the network. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | sudo virsh start pxe-node-1 | ||||||
|  | sudo virsh console pxe-node-1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Cleanup | ||||||
|  | 
 | ||||||
|  | To destroy all VMs and networks created by the script, run the `clean` command: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | sudo ./pxe_vm_lab_setup.sh clean | ||||||
|  | ``` | ||||||
							
								
								
									
										191
									
								
								docs/pxe_test/pxe_vm_lab_setup.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										191
									
								
								docs/pxe_test/pxe_vm_lab_setup.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,191 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | set -euo pipefail | ||||||
|  | 
 | ||||||
|  | # --- Configuration --- | ||||||
|  | LAB_DIR="/var/lib/harmony_pxe_test" | ||||||
|  | IMG_DIR="${LAB_DIR}/images" | ||||||
|  | STATE_DIR="${LAB_DIR}/state" | ||||||
|  | VM_OPN="opnsense-pxe" | ||||||
|  | VM_PXE="pxe-node-1" | ||||||
|  | NET_HARMONYLAN="harmonylan" | ||||||
|  | 
 | ||||||
|  | # Network settings for the isolated LAN | ||||||
|  | VLAN_CIDR="192.168.150.0/24" | ||||||
|  | VLAN_GW="192.168.150.1" | ||||||
|  | VLAN_MASK="255.255.255.0" | ||||||
|  | 
 | ||||||
|  | # VM Specifications | ||||||
|  | RAM_OPN="2048" | ||||||
|  | VCPUS_OPN="2" | ||||||
|  | DISK_OPN_GB="10" | ||||||
|  | OS_VARIANT_OPN="freebsd14.0" # Updated to a more recent FreeBSD variant | ||||||
|  | 
 | ||||||
|  | RAM_PXE="4096" | ||||||
|  | VCPUS_PXE="2" | ||||||
|  | DISK_PXE_GB="40" | ||||||
|  | OS_VARIANT_LINUX="centos-stream9" | ||||||
|  | 
 | ||||||
|  | OPN_IMG_URL="https://mirror.ams1.nl.leaseweb.net/opnsense/releases/25.7/OPNsense-25.7-serial-amd64.img.bz2" | ||||||
|  | OPN_IMG_PATH="${IMG_DIR}/OPNsense-25.7-serial-amd64.img" | ||||||
|  | CENTOS_ISO_URL="https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/os/images/boot.iso" | ||||||
|  | CENTOS_ISO_PATH="${IMG_DIR}/CentOS-Stream-9-latest-boot.iso" | ||||||
|  | 
 | ||||||
|  | CONNECT_URI="qemu:///system" | ||||||
|  | 
 | ||||||
|  | download_if_missing() { | ||||||
|  |   local url="$1" | ||||||
|  |   local dest="$2" | ||||||
|  |   if [[ ! -f "$dest" ]]; then | ||||||
|  |     echo "Downloading $url to $dest" | ||||||
|  |     mkdir -p "$(dirname "$dest")" | ||||||
|  |     local tmp | ||||||
|  |     tmp="$(mktemp)" | ||||||
|  |     curl -L --progress-bar "$url" -o "$tmp" | ||||||
|  |     case "$url" in | ||||||
|  |       *.bz2) bunzip2 -c "$tmp" > "$dest" && rm -f "$tmp" ;; | ||||||
|  |       *) mv "$tmp" "$dest" ;; | ||||||
|  |     esac | ||||||
|  |   else | ||||||
|  |     echo "Already present: $dest" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Ensures a libvirt network is defined and active | ||||||
|  | ensure_network() { | ||||||
|  |   local net_name="$1" | ||||||
|  |   local net_xml_path="$2" | ||||||
|  |   if virsh --connect "${CONNECT_URI}" net-info "${net_name}" >/dev/null 2>&1; then | ||||||
|  |     echo "Network ${net_name} already exists." | ||||||
|  |   else | ||||||
|  |     echo "Defining network ${net_name} from ${net_xml_path}" | ||||||
|  |     virsh --connect "${CONNECT_URI}" net-define "${net_xml_path}" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if ! virsh --connect "${CONNECT_URI}" net-info "${net_name}" | grep "Active: *yes"; then | ||||||
|  |     echo "Starting network ${net_name}..." | ||||||
|  |     virsh --connect "${CONNECT_URI}" net-start "${net_name}" | ||||||
|  |     virsh --connect "${CONNECT_URI}" net-autostart "${net_name}" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Destroys a VM completely | ||||||
|  | destroy_vm() { | ||||||
|  |   local vm_name="$1" | ||||||
|  |   if virsh --connect "${CONNECT_URI}" dominfo "$vm_name" >/dev/null 2>&1; then | ||||||
|  |     echo "Destroying and undefining VM: ${vm_name}" | ||||||
|  |     virsh --connect "${CONNECT_URI}" destroy "$vm_name" || true | ||||||
|  |     virsh --connect "${CONNECT_URI}" undefine "$vm_name" --nvram | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Destroys a libvirt network | ||||||
|  | destroy_network() { | ||||||
|  |   local net_name="$1" | ||||||
|  |   if virsh --connect "${CONNECT_URI}" net-info "$net_name" >/dev/null 2>&1; then | ||||||
|  |     echo "Destroying and undefining network: ${net_name}" | ||||||
|  |     virsh --connect "${CONNECT_URI}" net-destroy "$net_name" || true | ||||||
|  |     virsh --connect "${CONNECT_URI}" net-undefine "$net_name" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # --- Main Logic --- | ||||||
|  | create_lab_environment() { | ||||||
|  |   # Create network definition files | ||||||
|  |   cat > "${STATE_DIR}/default.xml" <<EOF | ||||||
|  | <network> | ||||||
|  |   <name>default</name> | ||||||
|  |   <forward mode='nat'/> | ||||||
|  |   <bridge name='virbr0' stp='on' delay='0'/> | ||||||
|  |   <ip address='192.168.122.1' netmask='255.255.255.0'> | ||||||
|  |     <dhcp> | ||||||
|  |       <range start='192.168.122.100' end='192.168.122.200'/> | ||||||
|  |     </dhcp> | ||||||
|  |   </ip> | ||||||
|  | </network> | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  |   cat > "${STATE_DIR}/${NET_HARMONYLAN}.xml" <<EOF | ||||||
|  | <network> | ||||||
|  |   <name>${NET_HARMONYLAN}</name> | ||||||
|  |   <bridge name='virbr1' stp='on' delay='0'/> | ||||||
|  | </network> | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  |   # Ensure both networks exist and are active | ||||||
|  |   ensure_network "default" "${STATE_DIR}/default.xml" | ||||||
|  |   ensure_network "${NET_HARMONYLAN}" "${STATE_DIR}/${NET_HARMONYLAN}.xml" | ||||||
|  | 
 | ||||||
|  |   # --- Create OPNsense VM (MODIFIED SECTION) --- | ||||||
|  |   local disk_opn="${IMG_DIR}/${VM_OPN}.qcow2" | ||||||
|  |   if [[ ! -f "$disk_opn" ]]; then | ||||||
|  |     qemu-img create -f qcow2 "$disk_opn" "${DISK_OPN_GB}G" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   echo "Creating OPNsense VM using serial image..." | ||||||
|  |   virt-install \ | ||||||
|  |     --connect "${CONNECT_URI}" \ | ||||||
|  |     --name "${VM_OPN}" \ | ||||||
|  |     --ram "${RAM_OPN}" \ | ||||||
|  |     --vcpus "${VCPUS_OPN}" \ | ||||||
|  |     --cpu host-passthrough \ | ||||||
|  |     --os-variant "${OS_VARIANT_OPN}" \ | ||||||
|  |     --graphics none \ | ||||||
|  |     --noautoconsole \ | ||||||
|  |     --disk path="${disk_opn}",device=disk,bus=virtio,boot.order=1 \ | ||||||
|  |     --disk path="${OPN_IMG_PATH}",device=disk,bus=usb,readonly=on,boot.order=2 \ | ||||||
|  |     --network network=default,model=virtio \ | ||||||
|  |     --network network="${NET_HARMONYLAN}",model=virtio \ | ||||||
|  |     --boot uefi,menu=on | ||||||
|  | 
 | ||||||
|  |   echo "OPNsense VM created. Connect with: sudo virsh console ${VM_OPN}" | ||||||
|  |   echo "The VM will boot from the serial installation image." | ||||||
|  |   echo "Login with user 'installer' and password 'opnsense' to start the installation." | ||||||
|  |   echo "Install onto the VirtIO disk (vtbd0)." | ||||||
|  |   echo "After installation, shutdown the VM, then run 'sudo virsh edit ${VM_OPN}' and remove the USB disk block to boot from the installed system." | ||||||
|  | 
 | ||||||
|  |   # --- Create PXE Client VM --- | ||||||
|  |   local disk_pxe="${IMG_DIR}/${VM_PXE}.qcow2" | ||||||
|  |   if [[ ! -f "$disk_pxe" ]]; then | ||||||
|  |     qemu-img create -f qcow2 "$disk_pxe" "${DISK_PXE_GB}G" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   echo "Creating PXE client VM..." | ||||||
|  |   virt-install \ | ||||||
|  |     --connect "${CONNECT_URI}" \ | ||||||
|  |     --name "${VM_PXE}" \ | ||||||
|  |     --ram "${RAM_PXE}" \ | ||||||
|  |     --vcpus "${VCPUS_PXE}" \ | ||||||
|  |     --cpu host-passthrough \ | ||||||
|  |     --os-variant "${OS_VARIANT_LINUX}" \ | ||||||
|  |     --graphics none \ | ||||||
|  |     --noautoconsole \ | ||||||
|  |     --disk path="${disk_pxe}",format=qcow2,bus=virtio \ | ||||||
|  |     --network network="${NET_HARMONYLAN}",model=virtio \ | ||||||
|  |     --pxe \ | ||||||
|  |     --boot uefi,menu=on | ||||||
|  | 
 | ||||||
|  |   echo "PXE VM created. It will attempt to netboot on ${NET_HARMONYLAN}." | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # --- Script Entrypoint --- | ||||||
|  | case "${1:-}" in | ||||||
|  |   up) | ||||||
|  |     mkdir -p "${IMG_DIR}" "${STATE_DIR}" | ||||||
|  |     download_if_missing "$OPN_IMG_URL" "$OPN_IMG_PATH" | ||||||
|  |     download_if_missing "$CENTOS_ISO_URL" "$CENTOS_ISO_PATH" | ||||||
|  |     create_lab_environment | ||||||
|  |     echo "Lab setup complete. Use 'sudo virsh list --all' to see VMs." | ||||||
|  |     ;; | ||||||
|  |   clean) | ||||||
|  |     destroy_vm "${VM_PXE}" | ||||||
|  |     destroy_vm "${VM_OPN}" | ||||||
|  |     destroy_network "${NET_HARMONYLAN}" | ||||||
|  |     # Optionally destroy the default network if you want a full reset | ||||||
|  |     # destroy_network "default" | ||||||
|  |     echo "Cleanup complete." | ||||||
|  |     ;; | ||||||
|  |   *) | ||||||
|  |     echo "Usage: sudo $0 {up|clean}" | ||||||
|  |     exit 1 | ||||||
|  |     ;; | ||||||
|  | esac | ||||||
| @ -1,6 +1,9 @@ | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     modules::dummy::{ErrorScore, PanicScore, SuccessScore}, |     modules::{ | ||||||
|  |         dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||||
|  |         inventory::DiscoverInventoryAgentScore, | ||||||
|  |     }, | ||||||
|     topology::LocalhostTopology, |     topology::LocalhostTopology, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -13,6 +16,9 @@ async fn main() { | |||||||
|             Box::new(SuccessScore {}), |             Box::new(SuccessScore {}), | ||||||
|             Box::new(ErrorScore {}), |             Box::new(ErrorScore {}), | ||||||
|             Box::new(PanicScore {}), |             Box::new(PanicScore {}), | ||||||
|  |             Box::new(DiscoverInventoryAgentScore { | ||||||
|  |                 discovery_timeout: Some(10), | ||||||
|  |             }), | ||||||
|         ], |         ], | ||||||
|         None, |         None, | ||||||
|     ) |     ) | ||||||
|  | |||||||
| @ -125,9 +125,12 @@ async fn main() { | |||||||
|         harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology); |         harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology); | ||||||
| 
 | 
 | ||||||
|     let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string())); |     let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string())); | ||||||
|     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( |     let http_score = StaticFilesHttpScore { | ||||||
|         "./data/watchguard/pxe-http-files".to_string(), |         folder_to_serve: Some(Url::LocalFolder( | ||||||
|     )); |             "./data/watchguard/pxe-http-files".to_string(), | ||||||
|  |         )), | ||||||
|  |         files: vec![], | ||||||
|  |     }; | ||||||
|     let ipxe_score = IpxeScore::new(); |     let ipxe_score = IpxeScore::new(); | ||||||
| 
 | 
 | ||||||
|     harmony_tui::run( |     harmony_tui::run( | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								examples/okd_pxe/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								examples/okd_pxe/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | [package] | ||||||
|  | name = "example-pxe" | ||||||
|  | edition = "2024" | ||||||
|  | version.workspace = true | ||||||
|  | readme.workspace = true | ||||||
|  | license.workspace = true | ||||||
|  | publish = false | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | harmony = { path = "../../harmony" } | ||||||
|  | harmony_cli = { path = "../../harmony_cli" } | ||||||
|  | harmony_types = { path = "../../harmony_types" } | ||||||
|  | harmony_secret = { path = "../../harmony_secret" } | ||||||
|  | harmony_secret_derive = { path = "../../harmony_secret_derive" } | ||||||
|  | cidr = { workspace = true } | ||||||
|  | tokio = { workspace = true } | ||||||
|  | harmony_macros = { path = "../../harmony_macros" } | ||||||
|  | log = { workspace = true } | ||||||
|  | env_logger = { workspace = true } | ||||||
|  | url = { workspace = true } | ||||||
|  | serde.workspace = true | ||||||
							
								
								
									
										24
									
								
								examples/okd_pxe/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								examples/okd_pxe/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | mod topology; | ||||||
|  | 
 | ||||||
|  | use crate::topology::{get_inventory, get_topology}; | ||||||
|  | use harmony::modules::okd::ipxe::OkdIpxeScore; | ||||||
|  | 
 | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() { | ||||||
|  |     let inventory = get_inventory(); | ||||||
|  |     let topology = get_topology().await; | ||||||
|  | 
 | ||||||
|  |     let kickstart_filename = "inventory.kickstart".to_string(); | ||||||
|  |     let cluster_pubkey_filename = "cluster_ssh_key.pub".to_string(); | ||||||
|  |     let harmony_inventory_agent = "harmony_inventory_agent".to_string(); | ||||||
|  | 
 | ||||||
|  |     let ipxe_score = OkdIpxeScore { | ||||||
|  |         kickstart_filename, | ||||||
|  |         harmony_inventory_agent, | ||||||
|  |         cluster_pubkey_filename, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     harmony_cli::run(inventory, topology, vec![Box::new(ipxe_score)], None) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								examples/okd_pxe/src/topology.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								examples/okd_pxe/src/topology.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | |||||||
|  | use cidr::Ipv4Cidr; | ||||||
|  | use harmony::{ | ||||||
|  |     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, | ||||||
|  |     infra::opnsense::OPNSenseManagementInterface, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     topology::{HAClusterTopology, LogicalHost, UnmanagedRouter}, | ||||||
|  | }; | ||||||
|  | use harmony_macros::{ip, ipv4}; | ||||||
|  | use harmony_secret::{Secret, SecretManager}; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | use std::{net::IpAddr, sync::Arc}; | ||||||
|  | 
 | ||||||
|  | #[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] | ||||||
|  | struct OPNSenseFirewallConfig { | ||||||
|  |     username: String, | ||||||
|  |     password: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn get_topology() -> HAClusterTopology { | ||||||
|  |     let firewall = harmony::topology::LogicalHost { | ||||||
|  |         ip: ip!("192.168.1.1"), | ||||||
|  |         name: String::from("opnsense-1"), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let config = SecretManager::get::<OPNSenseFirewallConfig>().await; | ||||||
|  |     let config = config.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let opnsense = Arc::new( | ||||||
|  |         harmony::infra::opnsense::OPNSenseFirewall::new( | ||||||
|  |             firewall, | ||||||
|  |             None, | ||||||
|  |             &config.username, | ||||||
|  |             &config.password, | ||||||
|  |         ) | ||||||
|  |         .await, | ||||||
|  |     ); | ||||||
|  |     let lan_subnet = ipv4!("192.168.1.0"); | ||||||
|  |     let gateway_ipv4 = ipv4!("192.168.1.1"); | ||||||
|  |     let gateway_ip = IpAddr::V4(gateway_ipv4); | ||||||
|  |     harmony::topology::HAClusterTopology { | ||||||
|  |         domain_name: "demo.harmony.mcd".to_string(), | ||||||
|  |         router: Arc::new(UnmanagedRouter::new( | ||||||
|  |             gateway_ip, | ||||||
|  |             Ipv4Cidr::new(lan_subnet, 24).unwrap(), | ||||||
|  |         )), | ||||||
|  |         load_balancer: opnsense.clone(), | ||||||
|  |         firewall: opnsense.clone(), | ||||||
|  |         tftp_server: opnsense.clone(), | ||||||
|  |         http_server: opnsense.clone(), | ||||||
|  |         dhcp_server: opnsense.clone(), | ||||||
|  |         dns_server: opnsense.clone(), | ||||||
|  |         control_plane: vec![LogicalHost { | ||||||
|  |             ip: ip!("10.100.8.20"), | ||||||
|  |             name: "cp0".to_string(), | ||||||
|  |         }], | ||||||
|  |         bootstrap_host: LogicalHost { | ||||||
|  |             ip: ip!("10.100.8.20"), | ||||||
|  |             name: "cp0".to_string(), | ||||||
|  |         }, | ||||||
|  |         workers: vec![], | ||||||
|  |         switch: vec![], | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn get_inventory() -> Inventory { | ||||||
|  |     Inventory { | ||||||
|  |         location: Location::new( | ||||||
|  |             "Some virtual machine or maybe a physical machine if you're cool".to_string(), | ||||||
|  |             "testopnsense".to_string(), | ||||||
|  |         ), | ||||||
|  |         switch: SwitchGroup::from([]), | ||||||
|  |         firewall: FirewallGroup::from([PhysicalHost::empty(HostCategory::Firewall) | ||||||
|  |             .management(Arc::new(OPNSenseManagementInterface::new()))]), | ||||||
|  |         storage_host: vec![], | ||||||
|  |         worker_host: vec![], | ||||||
|  |         control_plane_host: vec![], | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								examples/okd_pxe/ssh_example_key
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/okd_pxe/ssh_example_key
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | -----BEGIN OPENSSH PRIVATE KEY----- | ||||||
|  | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW | ||||||
|  | QyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHAAAAJikacCNpGnA | ||||||
|  | jQAAAAtzc2gtZWQyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHA | ||||||
|  | AAAECiiKk4V6Q5cVs6axDM4sjAzZn/QCZLQekmYQXS9XbEYxx6bDylvC68cVpjKfEFtLQJ | ||||||
|  | /dOFi6PVS2vsIOqPDJIcAAAAEGplYW5nYWJAbGlsaWFuZTIBAgMEBQ== | ||||||
|  | -----END OPENSSH PRIVATE KEY----- | ||||||
							
								
								
									
										1
									
								
								examples/okd_pxe/ssh_example_key.pub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/okd_pxe/ssh_example_key.pub
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2 | ||||||
| @ -80,9 +80,12 @@ async fn main() { | |||||||
|     let load_balancer_score = OKDLoadBalancerScore::new(&topology); |     let load_balancer_score = OKDLoadBalancerScore::new(&topology); | ||||||
| 
 | 
 | ||||||
|     let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string())); |     let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string())); | ||||||
|     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( |     let http_score = StaticFilesHttpScore { | ||||||
|         "./data/watchguard/pxe-http-files".to_string(), |         folder_to_serve: Some(Url::LocalFolder( | ||||||
|     )); |             "./data/watchguard/pxe-http-files".to_string(), | ||||||
|  |         )), | ||||||
|  |         files: vec![], | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     harmony_tui::run( |     harmony_tui::run( | ||||||
|         inventory, |         inventory, | ||||||
|  | |||||||
| @ -11,8 +11,7 @@ testing = [] | |||||||
| [dependencies] | [dependencies] | ||||||
| rand = "0.9" | rand = "0.9" | ||||||
| hex = "0.4" | hex = "0.4" | ||||||
| libredfish = "0.1.1" | reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features = false } | ||||||
| reqwest = { version = "0.11", features = ["blocking", "json"] } |  | ||||||
| russh = "0.45.0" | russh = "0.45.0" | ||||||
| rust-ipmi = "0.1.1" | rust-ipmi = "0.1.1" | ||||||
| semver = "1.0.23" | semver = "1.0.23" | ||||||
| @ -67,7 +66,9 @@ bollard.workspace = true | |||||||
| tar.workspace = true | tar.workspace = true | ||||||
| base64.workspace = true | base64.workspace = true | ||||||
| once_cell = "1.21.3" | once_cell = "1.21.3" | ||||||
| harmony-secret-derive = { version = "0.1.0", path = "../harmony_secret_derive" } | harmony_inventory_agent = { path = "../harmony_inventory_agent" } | ||||||
|  | harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" } | ||||||
|  | askama = "0.14.0" | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| pretty_assertions.workspace = true | pretty_assertions.workspace = true | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								harmony/src/domain/data/file.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								harmony/src/domain/data/file.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct FileContent { | ||||||
|  |     pub path: FilePath, | ||||||
|  |     pub content: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub enum FilePath { | ||||||
|  |     Relative(String), | ||||||
|  |     Absolute(String), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for FilePath { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         match self { | ||||||
|  |             FilePath::Relative(path) => f.write_fmt(format_args!("./{path}")), | ||||||
|  |             FilePath::Absolute(path) => f.write_fmt(format_args!("/{path}")), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,4 +1,6 @@ | |||||||
|  | mod file; | ||||||
| mod id; | mod id; | ||||||
| mod version; | mod version; | ||||||
|  | pub use file::*; | ||||||
| pub use id::*; | pub use id::*; | ||||||
| pub use version::*; | pub use version::*; | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| use log::debug; |  | ||||||
| use once_cell::sync::Lazy; | use once_cell::sync::Lazy; | ||||||
| use tokio::sync::broadcast; | use std::{collections::HashMap, sync::Mutex}; | ||||||
| 
 | 
 | ||||||
| use crate::modules::application::ApplicationFeatureStatus; | use crate::modules::application::ApplicationFeatureStatus; | ||||||
| 
 | 
 | ||||||
| @ -40,43 +39,46 @@ pub enum HarmonyEvent { | |||||||
|     }, |     }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static HARMONY_EVENT_BUS: Lazy<broadcast::Sender<HarmonyEvent>> = Lazy::new(|| { | type Subscriber = Box<dyn Fn(&HarmonyEvent) + Send + Sync>; | ||||||
|     // TODO: Adjust channel capacity
 |  | ||||||
|     let (tx, _rx) = broadcast::channel(100); |  | ||||||
|     tx |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| pub fn instrument(event: HarmonyEvent) -> Result<(), &'static str> { | static SUBSCRIBERS: Lazy<Mutex<HashMap<String, Subscriber>>> = | ||||||
|     if cfg!(any(test, feature = "testing")) { |     Lazy::new(|| Mutex::new(HashMap::new())); | ||||||
|         let _ = event; // Suppress the "unused variable" warning for `event`
 |  | ||||||
|         Ok(()) |  | ||||||
|     } else { |  | ||||||
|         match HARMONY_EVENT_BUS.send(event) { |  | ||||||
|             Ok(_) => Ok(()), |  | ||||||
|             Err(_) => Err("send error: no subscribers"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| pub async fn subscribe<F, Fut>(name: &str, mut handler: F) | /// Subscribes a listener to all instrumentation events.
 | ||||||
|  | ///
 | ||||||
|  | /// Simply provide a unique name and a closure to run when an event happens.
 | ||||||
|  | ///
 | ||||||
|  | /// # Example
 | ||||||
|  | /// ```
 | ||||||
|  | /// use harmony::instrumentation;
 | ||||||
|  | /// instrumentation::subscribe("my_logger", |event| {
 | ||||||
|  | ///   println!("Event occurred: {:?}", event);
 | ||||||
|  | /// });
 | ||||||
|  | /// ```
 | ||||||
|  | pub fn subscribe<F>(name: &str, callback: F) | ||||||
| where | where | ||||||
|     F: FnMut(HarmonyEvent) -> Fut + Send + 'static, |     F: Fn(&HarmonyEvent) + Send + Sync + 'static, | ||||||
|     Fut: Future<Output = bool> + Send, |  | ||||||
| { | { | ||||||
|     let mut rx = HARMONY_EVENT_BUS.subscribe(); |     let mut subs = SUBSCRIBERS.lock().unwrap(); | ||||||
|     debug!("[{name}] Service started. Listening for events..."); |     subs.insert(name.to_string(), Box::new(callback)); | ||||||
|     loop { | } | ||||||
|         match rx.recv().await { | 
 | ||||||
|             Ok(event) => { | /// Instruments an event, notifying all subscribers.
 | ||||||
|                 if !handler(event).await { | ///
 | ||||||
|                     debug!("[{name}] Handler requested exit."); | /// This will call every closure that was registered with `subscribe`.
 | ||||||
|                     break; | ///
 | ||||||
|                 } | /// # Example
 | ||||||
|             } | /// ```
 | ||||||
|             Err(broadcast::error::RecvError::Lagged(n)) => { | /// use harmony::instrumentation;
 | ||||||
|                 debug!("[{name}] Lagged behind by {n} messages."); | /// use harmony::instrumentation::HarmonyEvent;
 | ||||||
|             } | /// instrumentation::instrument(HarmonyEvent::HarmonyStarted);
 | ||||||
|             Err(_) => break, | /// ```
 | ||||||
|         } | pub fn instrument(event: HarmonyEvent) -> Result<(), &'static str> { | ||||||
|     } |     let subs = SUBSCRIBERS.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |     for callback in subs.values() { | ||||||
|  |         callback(&event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(()) | ||||||
| } | } | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ pub enum InterpretName { | |||||||
|     Lamp, |     Lamp, | ||||||
|     ApplicationMonitoring, |     ApplicationMonitoring, | ||||||
|     K8sPrometheusCrdAlerting, |     K8sPrometheusCrdAlerting, | ||||||
|  |     DiscoverInventoryAgent, | ||||||
|     CephClusterHealth, |     CephClusterHealth, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -59,6 +60,7 @@ impl std::fmt::Display for InterpretName { | |||||||
|             InterpretName::Lamp => f.write_str("LAMP"), |             InterpretName::Lamp => f.write_str("LAMP"), | ||||||
|             InterpretName::ApplicationMonitoring => f.write_str("ApplicationMonitoring"), |             InterpretName::ApplicationMonitoring => f.write_str("ApplicationMonitoring"), | ||||||
|             InterpretName::K8sPrometheusCrdAlerting => f.write_str("K8sPrometheusCrdAlerting"), |             InterpretName::K8sPrometheusCrdAlerting => f.write_str("K8sPrometheusCrdAlerting"), | ||||||
|  |             InterpretName::DiscoverInventoryAgent => f.write_str("DiscoverInventoryAgent"), | ||||||
|             InterpretName::CephClusterHealth => f.write_str("CephClusterHealth"), |             InterpretName::CephClusterHealth => f.write_str("CephClusterHealth"), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -74,6 +74,7 @@ impl<T: Topology> Maestro<T> { | |||||||
| 
 | 
 | ||||||
|     fn is_topology_initialized(&self) -> bool { |     fn is_topology_initialized(&self) -> bool { | ||||||
|         self.topology_state.status == TopologyStatus::Success |         self.topology_state.status == TopologyStatus::Success | ||||||
|  |             || self.topology_state.status == TopologyStatus::Noop | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> { |     pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> { | ||||||
|  | |||||||
| @ -1,9 +1,12 @@ | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use harmony_macros::ip; | use harmony_macros::ip; | ||||||
| use harmony_types::net::MacAddress; | use harmony_types::net::MacAddress; | ||||||
|  | use log::debug; | ||||||
| use log::info; | use log::info; | ||||||
| 
 | 
 | ||||||
|  | use crate::data::FileContent; | ||||||
| use crate::executors::ExecutorError; | use crate::executors::ExecutorError; | ||||||
|  | use crate::topology::PxeOptions; | ||||||
| 
 | 
 | ||||||
| use super::DHCPStaticEntry; | use super::DHCPStaticEntry; | ||||||
| use super::DhcpServer; | use super::DhcpServer; | ||||||
| @ -49,9 +52,10 @@ impl Topology for HAClusterTopology { | |||||||
|         "HAClusterTopology" |         "HAClusterTopology" | ||||||
|     } |     } | ||||||
|     async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> { |     async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> { | ||||||
|         todo!( |         debug!( | ||||||
|             "ensure_ready, not entirely sure what it should do here, probably something like verify that the hosts are reachable and all services are up and ready." |             "ensure_ready, not entirely sure what it should do here, probably something like verify that the hosts are reachable and all services are up and ready." | ||||||
|         ) |         ); | ||||||
|  |         Ok(PreparationOutcome::Noop) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -153,12 +157,10 @@ impl DhcpServer for HAClusterTopology { | |||||||
|     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> { |     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> { | ||||||
|         self.dhcp_server.list_static_mappings().await |         self.dhcp_server.list_static_mappings().await | ||||||
|     } |     } | ||||||
|     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { |     async fn set_pxe_options(&self, options: PxeOptions) -> Result<(), ExecutorError> { | ||||||
|         self.dhcp_server.set_next_server(ip).await |         self.dhcp_server.set_pxe_options(options).await | ||||||
|     } |  | ||||||
|     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         self.dhcp_server.set_boot_filename(boot_filename).await |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
|         self.dhcp_server.get_ip() |         self.dhcp_server.get_ip() | ||||||
|     } |     } | ||||||
| @ -168,16 +170,6 @@ impl DhcpServer for HAClusterTopology { | |||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|         self.dhcp_server.commit_config().await |         self.dhcp_server.commit_config().await | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         self.dhcp_server.set_filename(filename).await |  | ||||||
|     } |  | ||||||
|     async fn set_filename64(&self, filename64: &str) -> Result<(), ExecutorError> { |  | ||||||
|         self.dhcp_server.set_filename64(filename64).await |  | ||||||
|     } |  | ||||||
|     async fn set_filenameipxe(&self, filenameipxe: &str) -> Result<(), ExecutorError> { |  | ||||||
|         self.dhcp_server.set_filenameipxe(filenameipxe).await |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| @ -221,17 +213,21 @@ impl HttpServer for HAClusterTopology { | |||||||
|         self.http_server.serve_files(url).await |         self.http_server.serve_files(url).await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError> { | ||||||
|  |         self.http_server.serve_file_content(file).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         self.http_server.get_ip() | ||||||
|     } |     } | ||||||
|     async fn ensure_initialized(&self) -> Result<(), ExecutorError> { |     async fn ensure_initialized(&self) -> Result<(), ExecutorError> { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         self.http_server.ensure_initialized().await | ||||||
|     } |     } | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         self.http_server.commit_config().await | ||||||
|     } |     } | ||||||
|     async fn reload_restart(&self) -> Result<(), ExecutorError> { |     async fn reload_restart(&self) -> Result<(), ExecutorError> { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         self.http_server.reload_restart().await | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -299,19 +295,7 @@ impl DhcpServer for DummyInfra { | |||||||
|     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> { |     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||||
|     } |     } | ||||||
|     async fn set_next_server(&self, _ip: IpAddress) -> Result<(), ExecutorError> { |     async fn set_pxe_options(&self, _options: PxeOptions) -> Result<(), ExecutorError> { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |  | ||||||
|     } |  | ||||||
|     async fn set_boot_filename(&self, _boot_filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |  | ||||||
|     } |  | ||||||
|     async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |  | ||||||
|     } |  | ||||||
|     async fn set_filename64(&self, _filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |  | ||||||
|     } |  | ||||||
|     async fn set_filenameipxe(&self, _filenameipxe: &str) -> Result<(), ExecutorError> { |  | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||||
|     } |     } | ||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
| @ -381,6 +365,9 @@ impl HttpServer for DummyInfra { | |||||||
|     async fn serve_files(&self, _url: &Url) -> Result<(), ExecutorError> { |     async fn serve_files(&self, _url: &Url) -> Result<(), ExecutorError> { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||||
|     } |     } | ||||||
|  |     async fn serve_file_content(&self, _file: &FileContent) -> Result<(), ExecutorError> { | ||||||
|  |         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||||
|  |     } | ||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) |         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use crate::executors::ExecutorError; | use crate::{data::FileContent, executors::ExecutorError}; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| 
 | 
 | ||||||
| use super::{IpAddress, Url}; | use super::{IpAddress, Url}; | ||||||
| @ -6,6 +6,7 @@ use super::{IpAddress, Url}; | |||||||
| #[async_trait] | #[async_trait] | ||||||
| pub trait HttpServer: Send + Sync { | pub trait HttpServer: Send + Sync { | ||||||
|     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError>; |     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError>; | ||||||
|  |     async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError>; | ||||||
|     fn get_ip(&self) -> IpAddress; |     fn get_ip(&self) -> IpAddress; | ||||||
| 
 | 
 | ||||||
|     // async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError>;
 |     // async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError>;
 | ||||||
|  | |||||||
| @ -185,7 +185,10 @@ impl K8sClient { | |||||||
|                 if let Some(s) = status.status { |                 if let Some(s) = status.status { | ||||||
|                     let mut stdout_buf = String::new(); |                     let mut stdout_buf = String::new(); | ||||||
|                     if let Some(mut stdout) = process.stdout().take() { |                     if let Some(mut stdout) = process.stdout().take() { | ||||||
|                         stdout.read_to_string(&mut stdout_buf).await; |                         stdout | ||||||
|  |                             .read_to_string(&mut stdout_buf) | ||||||
|  |                             .await | ||||||
|  |                             .map_err(|e| format!("Failed to get status stdout {e}"))?; | ||||||
|                     } |                     } | ||||||
|                     debug!("Status: {} - {:?}", s, status.details); |                     debug!("Status: {} - {:?}", s, status.details); | ||||||
|                     if s == "Success" { |                     if s == "Success" { | ||||||
|  | |||||||
| @ -46,16 +46,19 @@ pub trait K8sclient: Send + Sync { | |||||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, String>; |     async fn k8s_client(&self) -> Result<Arc<K8sClient>, String>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub struct PxeOptions { | ||||||
|  |     pub ipxe_filename: String, | ||||||
|  |     pub bios_filename: String, | ||||||
|  |     pub efi_filename: String, | ||||||
|  |     pub tftp_ip: Option<IpAddress>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| pub trait DhcpServer: Send + Sync + std::fmt::Debug { | pub trait DhcpServer: Send + Sync + std::fmt::Debug { | ||||||
|     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; |     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; | ||||||
|     async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; |     async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; | ||||||
|     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; |     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; | ||||||
|     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError>; |     async fn set_pxe_options(&self, pxe_options: PxeOptions) -> Result<(), ExecutorError>; | ||||||
|     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError>; |  | ||||||
|     async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>; |  | ||||||
|     async fn set_filename64(&self, filename64: &str) -> Result<(), ExecutorError>; |  | ||||||
|     async fn set_filenameipxe(&self, filenameipxe: &str) -> Result<(), ExecutorError>; |  | ||||||
|     fn get_ip(&self) -> IpAddress; |     fn get_ip(&self) -> IpAddress; | ||||||
|     fn get_host(&self) -> LogicalHost; |     fn get_host(&self) -> LogicalHost; | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError>; |     async fn commit_config(&self) -> Result<(), ExecutorError>; | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use harmony_types::net::MacAddress; | use harmony_types::net::MacAddress; | ||||||
| use log::debug; | use log::info; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     executors::ExecutorError, |     executors::ExecutorError, | ||||||
|     topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost}, |     topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost, PxeOptions}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::OPNSenseFirewall; | use super::OPNSenseFirewall; | ||||||
| @ -26,7 +26,7 @@ impl DhcpServer for OPNSenseFirewall { | |||||||
|                 .unwrap(); |                 .unwrap(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         debug!("Registered {:?}", entry); |         info!("Registered {:?}", entry); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -46,57 +46,25 @@ impl DhcpServer for OPNSenseFirewall { | |||||||
|         self.host.clone() |         self.host.clone() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { |     async fn set_pxe_options(&self, options: PxeOptions) -> Result<(), ExecutorError> { | ||||||
|         let ipv4 = match ip { |         let mut writable_opnsense = self.opnsense_config.write().await; | ||||||
|             std::net::IpAddr::V4(ipv4_addr) => ipv4_addr, |         let PxeOptions { | ||||||
|             std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"), |             ipxe_filename, | ||||||
|         }; |             bios_filename, | ||||||
|         { |             efi_filename, | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |             tftp_ip, | ||||||
|             writable_opnsense.dhcp().set_next_server(ipv4); |         } = options; | ||||||
|             debug!("OPNsense dhcp server set next server {ipv4}"); |         writable_opnsense | ||||||
|         } |             .dhcp() | ||||||
| 
 |             .set_pxe_options( | ||||||
|         Ok(()) |                 tftp_ip.map(|i| i.to_string()), | ||||||
|     } |                 bios_filename, | ||||||
| 
 |                 efi_filename, | ||||||
|     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> { |                 ipxe_filename, | ||||||
|         { |             ) | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |             .await | ||||||
|             writable_opnsense.dhcp().set_boot_filename(boot_filename); |             .map_err(|dhcp_error| { | ||||||
|             debug!("OPNsense dhcp server set boot filename {boot_filename}"); |                 ExecutorError::UnexpectedError(format!("Failed to set_pxe_options : {dhcp_error}")) | ||||||
|         } |             }) | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         { |  | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|             writable_opnsense.dhcp().set_filename(filename); |  | ||||||
|             debug!("OPNsense dhcp server set filename {filename}"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn set_filename64(&self, filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         { |  | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|             writable_opnsense.dhcp().set_filename64(filename); |  | ||||||
|             debug!("OPNsense dhcp server set filename {filename}"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn set_filenameipxe(&self, filenameipxe: &str) -> Result<(), ExecutorError> { |  | ||||||
|         { |  | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|             writable_opnsense.dhcp().set_filenameipxe(filenameipxe); |  | ||||||
|             debug!("OPNsense dhcp server set filenameipxe {filenameipxe}"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,23 +2,23 @@ use async_trait::async_trait; | |||||||
| use log::info; | use log::info; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|  |     data::FileContent, | ||||||
|     executors::ExecutorError, |     executors::ExecutorError, | ||||||
|     topology::{HttpServer, IpAddress, Url}, |     topology::{HttpServer, IpAddress, Url}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::OPNSenseFirewall; | use super::OPNSenseFirewall; | ||||||
|  | const OPNSENSE_HTTP_ROOT_PATH: &str = "/usr/local/http"; | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl HttpServer for OPNSenseFirewall { | impl HttpServer for OPNSenseFirewall { | ||||||
|     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError> { |     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError> { | ||||||
|         let http_root_path = "/usr/local/http"; |  | ||||||
| 
 |  | ||||||
|         let config = self.opnsense_config.read().await; |         let config = self.opnsense_config.read().await; | ||||||
|         info!("Uploading files from url {url} to {http_root_path}"); |         info!("Uploading files from url {url} to {OPNSENSE_HTTP_ROOT_PATH}"); | ||||||
|         match url { |         match url { | ||||||
|             Url::LocalFolder(path) => { |             Url::LocalFolder(path) => { | ||||||
|                 config |                 config | ||||||
|                     .upload_files(path, http_root_path) |                     .upload_files(path, OPNSENSE_HTTP_ROOT_PATH) | ||||||
|                     .await |                     .await | ||||||
|                     .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; |                     .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; | ||||||
|             } |             } | ||||||
| @ -27,8 +27,29 @@ impl HttpServer for OPNSenseFirewall { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError> { | ||||||
|  |         let path = match &file.path { | ||||||
|  |             crate::data::FilePath::Relative(path) => { | ||||||
|  |                 format!("{OPNSENSE_HTTP_ROOT_PATH}/{}", path.to_string()) | ||||||
|  |             } | ||||||
|  |             crate::data::FilePath::Absolute(path) => { | ||||||
|  |                 return Err(ExecutorError::ConfigurationError(format!( | ||||||
|  |                     "Cannot serve file from http server with absolute path : {path}" | ||||||
|  |                 ))); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         let config = self.opnsense_config.read().await; | ||||||
|  |         info!("Uploading file content to {}", path); | ||||||
|  |         config | ||||||
|  |             .upload_file_content(&path, &file.content) | ||||||
|  |             .await | ||||||
|  |             .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
|         todo!(); |         OPNSenseFirewall::get_ip(self) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ impl TftpServer for OPNSenseFirewall { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
|         todo!() |         OPNSenseFirewall::get_ip(self) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError> { |     async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError> { | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ use crate::{ | |||||||
|     domain::{data::Version, interpret::InterpretStatus}, |     domain::{data::Version, interpret::InterpretStatus}, | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, Topology}, |     topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, PxeOptions, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::domain::score::Score; | use crate::domain::score::Score; | ||||||
| @ -98,69 +98,14 @@ impl DhcpInterpret { | |||||||
|         _inventory: &Inventory, |         _inventory: &Inventory, | ||||||
|         dhcp_server: &D, |         dhcp_server: &D, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         let next_server_outcome = match self.score.next_server { |         let pxe_options = PxeOptions { | ||||||
|             Some(next_server) => { |             ipxe_filename: self.score.filenameipxe.clone().unwrap_or_default(), | ||||||
|                 dhcp_server.set_next_server(next_server).await?; |             bios_filename: self.score.filename.clone().unwrap_or_default(), | ||||||
|                 Outcome::new( |             efi_filename: self.score.filename64.clone().unwrap_or_default(), | ||||||
|                     InterpretStatus::SUCCESS, |             tftp_ip: self.score.next_server, | ||||||
|                     format!("Dhcp Interpret Set next boot to {next_server}"), |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|             None => Outcome::noop(), |  | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let boot_filename_outcome = match &self.score.boot_filename { |         dhcp_server.set_pxe_options(pxe_options).await?; | ||||||
|             Some(boot_filename) => { |  | ||||||
|                 dhcp_server.set_boot_filename(boot_filename).await?; |  | ||||||
|                 Outcome::new( |  | ||||||
|                     InterpretStatus::SUCCESS, |  | ||||||
|                     format!("Dhcp Interpret Set boot filename to {boot_filename}"), |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|             None => Outcome::noop(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let filename_outcome = match &self.score.filename { |  | ||||||
|             Some(filename) => { |  | ||||||
|                 dhcp_server.set_filename(filename).await?; |  | ||||||
|                 Outcome::new( |  | ||||||
|                     InterpretStatus::SUCCESS, |  | ||||||
|                     format!("Dhcp Interpret Set filename to {filename}"), |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|             None => Outcome::noop(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let filename64_outcome = match &self.score.filename64 { |  | ||||||
|             Some(filename64) => { |  | ||||||
|                 dhcp_server.set_filename64(filename64).await?; |  | ||||||
|                 Outcome::new( |  | ||||||
|                     InterpretStatus::SUCCESS, |  | ||||||
|                     format!("Dhcp Interpret Set filename64 to {filename64}"), |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|             None => Outcome::noop(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let filenameipxe_outcome = match &self.score.filenameipxe { |  | ||||||
|             Some(filenameipxe) => { |  | ||||||
|                 dhcp_server.set_filenameipxe(filenameipxe).await?; |  | ||||||
|                 Outcome::new( |  | ||||||
|                     InterpretStatus::SUCCESS, |  | ||||||
|                     format!("Dhcp Interpret Set filenameipxe to {filenameipxe}"), |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|             None => Outcome::noop(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         if next_server_outcome.status == InterpretStatus::NOOP |  | ||||||
|             && boot_filename_outcome.status == InterpretStatus::NOOP |  | ||||||
|             && filename_outcome.status == InterpretStatus::NOOP |  | ||||||
|             && filename64_outcome.status == InterpretStatus::NOOP |  | ||||||
|             && filenameipxe_outcome.status == InterpretStatus::NOOP |  | ||||||
|         { |  | ||||||
|             return Ok(Outcome::noop()); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::new( | ||||||
|             InterpretStatus::SUCCESS, |             InterpretStatus::SUCCESS, | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ use derive_new::new; | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     data::{Id, Version}, |     data::{FileContent, Id, Version}, | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     score::Score, |     score::Score, | ||||||
| @ -23,7 +23,8 @@ use crate::{ | |||||||
| /// ```
 | /// ```
 | ||||||
| #[derive(Debug, new, Clone, Serialize)] | #[derive(Debug, new, Clone, Serialize)] | ||||||
| pub struct StaticFilesHttpScore { | pub struct StaticFilesHttpScore { | ||||||
|     files_to_serve: Url, |     pub folder_to_serve: Option<Url>, | ||||||
|  |     pub files: Vec<FileContent>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + HttpServer> Score<T> for StaticFilesHttpScore { | impl<T: Topology + HttpServer> Score<T> for StaticFilesHttpScore { | ||||||
| @ -50,12 +51,25 @@ impl<T: Topology + HttpServer> Interpret<T> for StaticFilesHttpInterpret { | |||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         http_server.ensure_initialized().await?; |         http_server.ensure_initialized().await?; | ||||||
|         // http_server.set_ip(topology.router.get_gateway()).await?;
 |         // http_server.set_ip(topology.router.get_gateway()).await?;
 | ||||||
|         http_server.serve_files(&self.score.files_to_serve).await?; |         if let Some(folder) = self.score.folder_to_serve.as_ref() { | ||||||
|  |             http_server.serve_files(folder).await?; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for f in self.score.files.iter() { | ||||||
|  |             http_server.serve_file_content(&f).await? | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         http_server.commit_config().await?; |         http_server.commit_config().await?; | ||||||
|         http_server.reload_restart().await?; |         http_server.reload_restart().await?; | ||||||
|         Ok(Outcome::success(format!( |         Ok(Outcome::success(format!( | ||||||
|             "Http Server running and serving files from {}", |             "Http Server running and serving files from folder {:?} and content for {}", | ||||||
|             self.score.files_to_serve |             self.score.folder_to_serve, | ||||||
|  |             self.score | ||||||
|  |                 .files | ||||||
|  |                 .iter() | ||||||
|  |                 .map(|f| f.path.to_string()) | ||||||
|  |                 .collect::<Vec<String>>() | ||||||
|  |                 .join(",") | ||||||
|         ))) |         ))) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										72
									
								
								harmony/src/modules/inventory/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								harmony/src/modules/inventory/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use harmony_inventory_agent::local_presence::DiscoveryEvent; | ||||||
|  | use log::info; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     data::{Id, Version}, | ||||||
|  |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     score::Score, | ||||||
|  |     topology::Topology, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// This launches an harmony_inventory_agent discovery process
 | ||||||
|  | /// This will allow us to register/update hosts running harmony_inventory_agent
 | ||||||
|  | /// from LAN in the Harmony inventory
 | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct DiscoverInventoryAgentScore { | ||||||
|  |     pub discovery_timeout: Option<u64>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology> Score<T> for DiscoverInventoryAgentScore { | ||||||
|  |     fn name(&self) -> String { | ||||||
|  |         "DiscoverInventoryAgentScore".to_string() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|  |         Box::new(DiscoverInventoryAgentInterpret { | ||||||
|  |             score: self.clone(), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | struct DiscoverInventoryAgentInterpret { | ||||||
|  |     score: DiscoverInventoryAgentScore, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl<T: Topology> Interpret<T> for DiscoverInventoryAgentInterpret { | ||||||
|  |     async fn execute( | ||||||
|  |         &self, | ||||||
|  |         inventory: &Inventory, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         harmony_inventory_agent::local_presence::discover_agents( | ||||||
|  |             self.score.discovery_timeout, | ||||||
|  |             on_discover_event, | ||||||
|  |         ); | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_name(&self) -> InterpretName { | ||||||
|  |         InterpretName::DiscoverInventoryAgent | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_version(&self) -> Version { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_status(&self) -> InterpretStatus { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_children(&self) -> Vec<Id> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn on_discover_event(event: &DiscoveryEvent) { | ||||||
|  |     info!("got discovery event {event:?}"); | ||||||
|  | } | ||||||
| @ -5,6 +5,7 @@ pub mod dns; | |||||||
| pub mod dummy; | pub mod dummy; | ||||||
| pub mod helm; | pub mod helm; | ||||||
| pub mod http; | pub mod http; | ||||||
|  | pub mod inventory; | ||||||
| pub mod ipxe; | pub mod ipxe; | ||||||
| pub mod k3d; | pub mod k3d; | ||||||
| pub mod k8s; | pub mod k8s; | ||||||
|  | |||||||
| @ -37,18 +37,6 @@ pub struct NtfyInterpret { | |||||||
|     pub score: NtfyScore, |     pub score: NtfyScore, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, EnumString, Display)] |  | ||||||
| enum NtfyAccessMode { |  | ||||||
|     #[strum(serialize = "read-write", serialize = "rw")] |  | ||||||
|     ReadWrite, |  | ||||||
|     #[strum(serialize = "read-only", serialize = "ro", serialize = "read")] |  | ||||||
|     ReadOnly, |  | ||||||
|     #[strum(serialize = "write-only", serialize = "wo", serialize = "write")] |  | ||||||
|     WriteOnly, |  | ||||||
|     #[strum(serialize = "deny", serialize = "none")] |  | ||||||
|     Deny, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, EnumString, Display)] | #[derive(Debug, EnumString, Display)] | ||||||
| enum NtfyRole { | enum NtfyRole { | ||||||
|     #[strum(serialize = "user")] |     #[strum(serialize = "user")] | ||||||
|  | |||||||
							
								
								
									
										148
									
								
								harmony/src/modules/okd/ipxe.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								harmony/src/modules/okd/ipxe.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | |||||||
|  | use askama::Template; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use derive_new::new; | ||||||
|  | use serde::Serialize; | ||||||
|  | use std::net::IpAddr; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     data::{FileContent, FilePath, Id, Version}, | ||||||
|  |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore}, | ||||||
|  |     score::Score, | ||||||
|  |     topology::{DhcpServer, HttpServer, Router, TftpServer, Topology, Url}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, new, Clone, Serialize)] | ||||||
|  | pub struct OkdIpxeScore { | ||||||
|  |     pub kickstart_filename: String, | ||||||
|  |     pub harmony_inventory_agent: String, | ||||||
|  |     pub cluster_pubkey_filename: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology + DhcpServer + TftpServer + HttpServer + Router> Score<T> for OkdIpxeScore { | ||||||
|  |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|  |         Box::new(IpxeInterpret::new(self.clone())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> String { | ||||||
|  |         "OkdIpxeScore".to_string() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, new, Clone)] | ||||||
|  | pub struct IpxeInterpret { | ||||||
|  |     score: OkdIpxeScore, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl<T: Topology + DhcpServer + TftpServer + HttpServer + Router> Interpret<T> for IpxeInterpret { | ||||||
|  |     async fn execute( | ||||||
|  |         &self, | ||||||
|  |         inventory: &Inventory, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         let gateway_ip = topology.get_gateway(); | ||||||
|  | 
 | ||||||
|  |         let scores: Vec<Box<dyn Score<T>>> = vec![ | ||||||
|  |             Box::new(DhcpScore { | ||||||
|  |                 host_binding: vec![], | ||||||
|  |                 next_server: Some(topology.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 { | ||||||
|  |                 // 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: BootIpxeTpl { | ||||||
|  |                             gateway_ip: &gateway_ip, | ||||||
|  |                         } | ||||||
|  |                         .to_string(), | ||||||
|  |                     }, | ||||||
|  |                     FileContent { | ||||||
|  |                         path: FilePath::Relative(self.score.kickstart_filename.clone()), | ||||||
|  |                         content: InventoryKickstartTpl { | ||||||
|  |                             gateway_ip: &gateway_ip, | ||||||
|  |                             harmony_inventory_agent: &self.score.harmony_inventory_agent, | ||||||
|  |                             cluster_pubkey_filename: &self.score.cluster_pubkey_filename, | ||||||
|  |                         } | ||||||
|  |                         .to_string(), | ||||||
|  |                     }, | ||||||
|  |                     FileContent { | ||||||
|  |                         path: FilePath::Relative("fallback.ipxe".to_string()), | ||||||
|  |                         content: FallbackIpxeTpl { | ||||||
|  |                             gateway_ip: &gateway_ip, | ||||||
|  |                             kickstart_filename: &self.score.kickstart_filename, | ||||||
|  |                         } | ||||||
|  |                         .to_string(), | ||||||
|  |                     }, | ||||||
|  |                 ], | ||||||
|  |             }), | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         for score in scores { | ||||||
|  |             let result = score.interpret(inventory, topology).await; | ||||||
|  |             match result { | ||||||
|  |                 Ok(outcome) => match outcome.status { | ||||||
|  |                     InterpretStatus::SUCCESS => continue, | ||||||
|  |                     InterpretStatus::NOOP => continue, | ||||||
|  |                     _ => return Err(InterpretError::new(outcome.message)), | ||||||
|  |                 }, | ||||||
|  |                 Err(e) => return Err(e), | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(Outcome::success("Ipxe installed".to_string())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_name(&self) -> InterpretName { | ||||||
|  |         InterpretName::Ipxe | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_version(&self) -> Version { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_status(&self) -> InterpretStatus { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_children(&self) -> Vec<Id> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[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, | ||||||
|  | } | ||||||
| @ -2,5 +2,6 @@ pub mod bootstrap_dhcp; | |||||||
| pub mod bootstrap_load_balancer; | pub mod bootstrap_load_balancer; | ||||||
| pub mod dhcp; | pub mod dhcp; | ||||||
| pub mod dns; | pub mod dns; | ||||||
|  | pub mod ipxe; | ||||||
| pub mod load_balancer; | pub mod load_balancer; | ||||||
| pub mod upgrade; | pub mod upgrade; | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| use std::{ | use std::{ | ||||||
|     process::Command, |  | ||||||
|     sync::Arc, |     sync::Arc, | ||||||
|     time::{Duration, Instant}, |     time::{Duration, Instant}, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ use crate::{ | |||||||
| 
 | 
 | ||||||
| #[derive(Debug, new, Clone, Serialize)] | #[derive(Debug, new, Clone, Serialize)] | ||||||
| pub struct TftpScore { | pub struct TftpScore { | ||||||
|     files_to_serve: Url, |     pub files_to_serve: Url, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + TftpServer + Router> Score<T> for TftpScore { | impl<T: Topology + TftpServer + Router> Score<T> for TftpScore { | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								harmony/templates/boot.ipxe.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								harmony/templates/boot.ipxe.j2
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
							
								
								
									
										40
									
								
								harmony/templates/fallback.ipxe.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								harmony/templates/fallback.ipxe.j2
									
									
									
									
									
										Normal file
									
								
							| @ -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.sshd 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 | ||||||
							
								
								
									
										129
									
								
								harmony/templates/inventory.kickstart.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								harmony/templates/inventory.kickstart.j2
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | |||||||
|  | # --- Pre-Boot Scripting (The Main Goal) --- | ||||||
|  | # This section runs after the live environment has booted into RAM. | ||||||
|  | # It sets up SSH and downloads/runs the harmony-inventory-agent. | ||||||
|  | %pre --log=/root/ks-pre.log | ||||||
|  | 
 | ||||||
|  | echo "Harmony Kickstart: Pre-boot script started." | ||||||
|  | 
 | ||||||
|  | # 1. Configure SSH Access for Root | ||||||
|  | # Create the .ssh directory and set correct permissions. | ||||||
|  | echo "  - Setting up SSH authorized_keys for root..." | ||||||
|  | mkdir -p /root/.ssh | ||||||
|  | chmod 700 /root/.ssh | ||||||
|  | 
 | ||||||
|  | # Download the public key from the provisioning server. | ||||||
|  | # The -sS flags make curl silent but show errors. -L follows redirects. | ||||||
|  | curl -vSL "http://{{ gateway_ip }}:8080/{{ cluster_pubkey_filename }}" -o /root/.ssh/authorized_keys | ||||||
|  | if [ $? -ne 0 ]; then | ||||||
|  |     echo "  - ERROR: Failed to download SSH public key." | ||||||
|  | else | ||||||
|  |     echo "  - SSH key downloaded successfully." | ||||||
|  |     chmod 600 /root/.ssh/authorized_keys | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # 2. Download the Harmony Inventory Agent | ||||||
|  | echo "  - Downloading harmony-inventory-agent..." | ||||||
|  | curl -vSL "http://{{ gateway_ip }}:8080/{{ harmony_inventory_agent }}" -o /usr/bin/harmony-inventory-agent | ||||||
|  | if [ $? -ne 0 ]; then | ||||||
|  |     echo "  - ERROR: Failed to download harmony_inventory_agent." | ||||||
|  | else | ||||||
|  |     echo "  - Agent binary downloaded successfully." | ||||||
|  |     chmod +x /usr/bin/harmony-inventory-agent | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # 3. Create a systemd service to run the agent persistently. | ||||||
|  | # This is the most robust method to ensure the agent stays running. | ||||||
|  | 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] | ||||||
|  | Type=simple | ||||||
|  | ExecStart=/usr/bin/harmony-inventory-agent | ||||||
|  | Restart=on-failure | ||||||
|  | RestartSec=5 | ||||||
|  | Environment="RUST_LOG=info" | ||||||
|  | 
 | ||||||
|  | [Install] | ||||||
|  | WantedBy=multi-user.target | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | # 4. Enable and start the service | ||||||
|  | # The 'systemctl' commands will work correctly within the chroot environment of the %pre script. | ||||||
|  | echo "  - Enabling and starting harmony-agent.service..." | ||||||
|  | systemctl daemon-reload | ||||||
|  | systemctl enable --now harmony-agent.service | ||||||
|  | 
 | ||||||
|  | # Check if the service started correctly | ||||||
|  | systemctl is-active --quiet harmony-agent.service | ||||||
|  | if [ $? -eq 0 ]; then | ||||||
|  |     echo "  - Harmony Inventory Agent service is now running." | ||||||
|  | else | ||||||
|  |     echo "  - ERROR: Harmony Inventory Agent service failed to start." | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | echo "Harmony Kickstart: Pre-boot script finished. The machine is ready for inventory." | ||||||
|  | 
 | ||||||
|  | echo "Running cat - to pause system indefinitely" | ||||||
|  | cat - | ||||||
|  | 
 | ||||||
|  | %end | ||||||
|  | 
 | ||||||
|  | # ================================================================= | ||||||
|  | #  Harmony Discovery Agent - Kickstart File (NON-INSTALL, LIVE BOOT) | ||||||
|  | # ================================================================= | ||||||
|  | # | ||||||
|  | # This file achieves a fully automated, non-interactive boot into a | ||||||
|  | # live CentOS environment. It does NOT install to disk. | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | # --- Automation and Interaction Control --- | ||||||
|  | # Perform the installation in command-line mode. This is critical for | ||||||
|  | # preventing Anaconda from starting a UI and halting for input. | ||||||
|  | cmdline | ||||||
|  | 
 | ||||||
|  | # Accept the End User License Agreement to prevent a prompt. | ||||||
|  | eula --agreed | ||||||
|  | 
 | ||||||
|  | # --- Core System Configuration (Required by Anaconda) --- | ||||||
|  | # Set keyboard and language. These are mandatory. | ||||||
|  | keyboard --vckeymap=us --xlayouts='us' | ||||||
|  | lang en_US.UTF-8 | ||||||
|  | 
 | ||||||
|  | # Configure networking. This is essential for the %post script to work. | ||||||
|  | # The --activate flag ensures this device is brought up in the installer environment. | ||||||
|  | network --bootproto=dhcp --device=link --activate | ||||||
|  | 
 | ||||||
|  | # Set a locked root password. This is a mandatory command. | ||||||
|  | rootpw --lock | ||||||
|  | 
 | ||||||
|  | # Set the timezone. This is a mandatory command. | ||||||
|  | timezone UTC | ||||||
|  | 
 | ||||||
|  | # --- Disable Installation-Specific Features --- | ||||||
|  | # CRITICAL: Do not install a bootloader. The --disabled flag prevents | ||||||
|  | # this step and avoids errors about where to install it. | ||||||
|  | bootloader --disabled | ||||||
|  | 
 | ||||||
|  | # CRITICAL: Ignore all disks. This prevents Anaconda from stopping at the | ||||||
|  | # "Installation Destination" screen asking where to install. | ||||||
|  | # ignoredisk --drives /dev/sda | ||||||
|  | 
 | ||||||
|  | # Do not run the Initial Setup wizard on first boot. | ||||||
|  | firstboot --disable | ||||||
|  | 
 | ||||||
|  | # --- Package Selection --- | ||||||
|  | # We are not installing, so this section can be minimal. | ||||||
|  | # An empty %packages section is valid and ensures no time is wasted | ||||||
|  | # resolving dependencies for an installation that will not happen. | ||||||
|  | %packages | ||||||
|  | %end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # IMPORTANT: Do not include a final action command like 'reboot' or 'poweroff'. | ||||||
|  | # The default action is 'halt', which in cmdline mode will leave the system | ||||||
|  | # running in the live environment with the agent active, which is the desired state. | ||||||
| @ -7,19 +7,11 @@ use harmony::{ | |||||||
| }; | }; | ||||||
| use log::{error, info, log_enabled}; | use log::{error, info, log_enabled}; | ||||||
| use std::io::Write; | use std::io::Write; | ||||||
| use std::sync::{Arc, Mutex}; | use std::sync::Mutex; | ||||||
| 
 | 
 | ||||||
| pub fn init() -> tokio::task::JoinHandle<()> { | pub fn init() { | ||||||
|     configure_logger(); |     configure_logger(); | ||||||
|     let handle = tokio::spawn(handle_events()); |     handle_events(); | ||||||
| 
 |  | ||||||
|     loop { |  | ||||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     handle |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn configure_logger() { | fn configure_logger() { | ||||||
| @ -86,119 +78,114 @@ fn configure_logger() { | |||||||
|         .init(); |         .init(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn handle_events() { | fn handle_events() { | ||||||
|     let preparing_topology = Arc::new(Mutex::new(false)); |     let preparing_topology = Mutex::new(false); | ||||||
|     let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None)); |     let current_score: Mutex<Option<String>> = Mutex::new(None); | ||||||
| 
 | 
 | ||||||
|     instrumentation::subscribe("Harmony CLI Logger", { |     instrumentation::subscribe("Harmony CLI Logger", { | ||||||
|         move |event| { |         move |event| { | ||||||
|             let preparing_topology = Arc::clone(&preparing_topology); |             let mut preparing_topology = preparing_topology.lock().unwrap(); | ||||||
|             let current_score = Arc::clone(¤t_score); |             let mut current_score = current_score.lock().unwrap(); | ||||||
| 
 | 
 | ||||||
|             async move { |             match event { | ||||||
|                 let mut preparing_topology = preparing_topology.lock().unwrap(); |                 HarmonyEvent::HarmonyStarted => {} | ||||||
|                 let mut current_score = current_score.lock().unwrap(); |                 HarmonyEvent::HarmonyFinished => { | ||||||
| 
 |                     let emoji = crate::theme::EMOJI_HARMONY.to_string(); | ||||||
|                 match event { |                     info!(emoji = emoji.as_str(); "Harmony completed"); | ||||||
|                     HarmonyEvent::HarmonyStarted => {} |  | ||||||
|                     HarmonyEvent::HarmonyFinished => { |  | ||||||
|                         let emoji = crate::theme::EMOJI_HARMONY.to_string(); |  | ||||||
|                         info!(emoji = emoji.as_str(); "Harmony completed"); |  | ||||||
|                         return false; |  | ||||||
|                     } |  | ||||||
|                     HarmonyEvent::TopologyStateChanged { |  | ||||||
|                         topology, |  | ||||||
|                         status, |  | ||||||
|                         message, |  | ||||||
|                     } => match status { |  | ||||||
|                         TopologyStatus::Queued => {} |  | ||||||
|                         TopologyStatus::Preparing => { |  | ||||||
|                             let emoji = format!("{}", style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow()); |  | ||||||
|                             info!(emoji = emoji.as_str(); "Preparing environment: {topology}..."); |  | ||||||
|                             (*preparing_topology) = true; |  | ||||||
|                         } |  | ||||||
|                         TopologyStatus::Success => { |  | ||||||
|                             (*preparing_topology) = false; |  | ||||||
|                             if let Some(message) = message { |  | ||||||
|                                 info!(status = "finished"; "{message}"); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         TopologyStatus::Noop => { |  | ||||||
|                             (*preparing_topology) = false; |  | ||||||
|                             if let Some(message) = message { |  | ||||||
|                                 info!(status = "skipped"; "{message}"); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         TopologyStatus::Error => { |  | ||||||
|                             (*preparing_topology) = false; |  | ||||||
|                             if let Some(message) = message { |  | ||||||
|                                 error!(status = "failed"; "{message}"); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     HarmonyEvent::InterpretExecutionStarted { |  | ||||||
|                         execution_id: _, |  | ||||||
|                         topology: _, |  | ||||||
|                         interpret: _, |  | ||||||
|                         score, |  | ||||||
|                         message, |  | ||||||
|                     } => { |  | ||||||
|                         if *preparing_topology || current_score.is_some() { |  | ||||||
|                             info!("{message}"); |  | ||||||
|                         } else { |  | ||||||
|                             (*current_score) = Some(score.clone()); |  | ||||||
|                             let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue()); |  | ||||||
|                             info!(emoji = emoji.as_str(); "Interpreting score: {score}..."); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     HarmonyEvent::InterpretExecutionFinished { |  | ||||||
|                         execution_id: _, |  | ||||||
|                         topology: _, |  | ||||||
|                         interpret: _, |  | ||||||
|                         score, |  | ||||||
|                         outcome, |  | ||||||
|                     } => { |  | ||||||
|                         if current_score.is_some() && current_score.clone().unwrap() == score { |  | ||||||
|                             (*current_score) = None; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         match outcome { |  | ||||||
|                             Ok(outcome) => match outcome.status { |  | ||||||
|                                 harmony::interpret::InterpretStatus::SUCCESS => { |  | ||||||
|                                     info!(status = "finished"; "{}", outcome.message); |  | ||||||
|                                 } |  | ||||||
|                                 harmony::interpret::InterpretStatus::NOOP => { |  | ||||||
|                                     info!(status = "skipped"; "{}", outcome.message); |  | ||||||
|                                 } |  | ||||||
|                                 _ => { |  | ||||||
|                                     error!(status = "failed"; "{}", outcome.message); |  | ||||||
|                                 } |  | ||||||
|                             }, |  | ||||||
|                             Err(err) => { |  | ||||||
|                                 error!(status = "failed"; "{}", err); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     HarmonyEvent::ApplicationFeatureStateChanged { |  | ||||||
|                         topology: _, |  | ||||||
|                         application, |  | ||||||
|                         feature, |  | ||||||
|                         status, |  | ||||||
|                     } => match status { |  | ||||||
|                         ApplicationFeatureStatus::Installing => { |  | ||||||
|                             info!("Installing feature '{}' for '{}'...", feature, application); |  | ||||||
|                         } |  | ||||||
|                         ApplicationFeatureStatus::Installed => { |  | ||||||
|                             info!(status = "finished"; "Feature '{}' installed", feature); |  | ||||||
|                         } |  | ||||||
|                         ApplicationFeatureStatus::Failed { details } => { |  | ||||||
|                             error!(status = "failed"; "Feature '{}' installation failed: {}", feature, details); |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                 } |                 } | ||||||
|                 true |                 HarmonyEvent::TopologyStateChanged { | ||||||
|  |                     topology, | ||||||
|  |                     status, | ||||||
|  |                     message, | ||||||
|  |                 } => match status { | ||||||
|  |                     TopologyStatus::Queued => {} | ||||||
|  |                     TopologyStatus::Preparing => { | ||||||
|  |                         let emoji = format!( | ||||||
|  |                             "{}", | ||||||
|  |                             style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow() | ||||||
|  |                         ); | ||||||
|  |                         info!(emoji = emoji.as_str(); "Preparing environment: {topology}..."); | ||||||
|  |                         (*preparing_topology) = true; | ||||||
|  |                     } | ||||||
|  |                     TopologyStatus::Success => { | ||||||
|  |                         (*preparing_topology) = false; | ||||||
|  |                         if let Some(message) = message { | ||||||
|  |                             info!(status = "finished"; "{message}"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     TopologyStatus::Noop => { | ||||||
|  |                         (*preparing_topology) = false; | ||||||
|  |                         if let Some(message) = message { | ||||||
|  |                             info!(status = "skipped"; "{message}"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     TopologyStatus::Error => { | ||||||
|  |                         (*preparing_topology) = false; | ||||||
|  |                         if let Some(message) = message { | ||||||
|  |                             error!(status = "failed"; "{message}"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 HarmonyEvent::InterpretExecutionStarted { | ||||||
|  |                     execution_id: _, | ||||||
|  |                     topology: _, | ||||||
|  |                     interpret: _, | ||||||
|  |                     score, | ||||||
|  |                     message, | ||||||
|  |                 } => { | ||||||
|  |                     if *preparing_topology || current_score.is_some() { | ||||||
|  |                         info!("{message}"); | ||||||
|  |                     } else { | ||||||
|  |                         (*current_score) = Some(score.clone()); | ||||||
|  |                         let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue()); | ||||||
|  |                         info!(emoji = emoji.as_str(); "Interpreting score: {score}..."); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 HarmonyEvent::InterpretExecutionFinished { | ||||||
|  |                     execution_id: _, | ||||||
|  |                     topology: _, | ||||||
|  |                     interpret: _, | ||||||
|  |                     score, | ||||||
|  |                     outcome, | ||||||
|  |                 } => { | ||||||
|  |                     if current_score.is_some() && ¤t_score.clone().unwrap() == score { | ||||||
|  |                         (*current_score) = None; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     match outcome { | ||||||
|  |                         Ok(outcome) => match outcome.status { | ||||||
|  |                             harmony::interpret::InterpretStatus::SUCCESS => { | ||||||
|  |                                 info!(status = "finished"; "{}", outcome.message); | ||||||
|  |                             } | ||||||
|  |                             harmony::interpret::InterpretStatus::NOOP => { | ||||||
|  |                                 info!(status = "skipped"; "{}", outcome.message); | ||||||
|  |                             } | ||||||
|  |                             _ => { | ||||||
|  |                                 error!(status = "failed"; "{}", outcome.message); | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         Err(err) => { | ||||||
|  |                             error!(status = "failed"; "{err}"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 HarmonyEvent::ApplicationFeatureStateChanged { | ||||||
|  |                     topology: _, | ||||||
|  |                     application, | ||||||
|  |                     feature, | ||||||
|  |                     status, | ||||||
|  |                 } => match status { | ||||||
|  |                     ApplicationFeatureStatus::Installing => { | ||||||
|  |                         info!("Installing feature '{feature}' for '{application}'..."); | ||||||
|  |                     } | ||||||
|  |                     ApplicationFeatureStatus::Installed => { | ||||||
|  |                         info!(status = "finished"; "Feature '{feature}' installed"); | ||||||
|  |                     } | ||||||
|  |                     ApplicationFeatureStatus::Failed { details } => { | ||||||
|  |                         error!(status = "failed"; "Feature '{feature}' installation failed: {details}"); | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }) |     }); | ||||||
|     .await; |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -115,7 +115,7 @@ pub async fn run_cli<T: Topology + Send + Sync + 'static>( | |||||||
|     scores: Vec<Box<dyn Score<T>>>, |     scores: Vec<Box<dyn Score<T>>>, | ||||||
|     args: Args, |     args: Args, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     let cli_logger_handle = cli_logger::init(); |     cli_logger::init(); | ||||||
| 
 | 
 | ||||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); |     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||||
|     maestro.register_all(scores); |     maestro.register_all(scores); | ||||||
| @ -123,7 +123,6 @@ pub async fn run_cli<T: Topology + Send + Sync + 'static>( | |||||||
|     let result = init(maestro, args).await; |     let result = init(maestro, args).await; | ||||||
| 
 | 
 | ||||||
|     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); |     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); | ||||||
|     let _ = tokio::try_join!(cli_logger_handle); |  | ||||||
|     result |     result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,82 +1,66 @@ | |||||||
| use harmony_cli::progress::{IndicatifProgressTracker, ProgressTracker}; | use harmony_cli::progress::{IndicatifProgressTracker, ProgressTracker}; | ||||||
| use indicatif::MultiProgress; | use indicatif::MultiProgress; | ||||||
| use std::sync::Arc; |  | ||||||
| 
 | 
 | ||||||
| use crate::instrumentation::{self, HarmonyComposerEvent}; | use crate::instrumentation::{self, HarmonyComposerEvent}; | ||||||
| 
 | 
 | ||||||
| pub fn init() -> tokio::task::JoinHandle<()> { | pub fn init() { | ||||||
|     configure_logger(); |     configure_logger(); | ||||||
|     let handle = tokio::spawn(handle_events()); |     handle_events(); | ||||||
| 
 |  | ||||||
|     loop { |  | ||||||
|         if instrumentation::instrument(HarmonyComposerEvent::HarmonyComposerStarted).is_ok() { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     handle |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn configure_logger() { | fn configure_logger() { | ||||||
|     env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); |     env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn handle_events() { | pub fn handle_events() { | ||||||
|     let progress_tracker = Arc::new(IndicatifProgressTracker::new(MultiProgress::new())); |     let progress_tracker = IndicatifProgressTracker::new(MultiProgress::new()); | ||||||
| 
 | 
 | ||||||
|     const SETUP_SECTION: &str = "project-initialization"; |     const SETUP_SECTION: &str = "project-initialization"; | ||||||
|     const COMPILTATION_TASK: &str = "compilation"; |     const COMPILTATION_TASK: &str = "compilation"; | ||||||
|     const PROGRESS_DEPLOYMENT: &str = "deployment"; |     const PROGRESS_DEPLOYMENT: &str = "deployment"; | ||||||
| 
 | 
 | ||||||
|     instrumentation::subscribe("Harmony Composer Logger", { |     instrumentation::subscribe("Harmony Composer Logger", { | ||||||
|         move |event| { |         move |event| match event { | ||||||
|             let progress_tracker = Arc::clone(&progress_tracker); |             HarmonyComposerEvent::HarmonyComposerStarted => {} | ||||||
| 
 |             HarmonyComposerEvent::ProjectInitializationStarted => { | ||||||
|             async move { |                 progress_tracker.add_section( | ||||||
|                 match event { |                     SETUP_SECTION, | ||||||
|                     HarmonyComposerEvent::HarmonyComposerStarted => {} |                     &format!( | ||||||
|                     HarmonyComposerEvent::ProjectInitializationStarted => { |                         "{} Initializing Harmony project...", | ||||||
|                         progress_tracker.add_section( |                         harmony_cli::theme::EMOJI_HARMONY, | ||||||
|                             SETUP_SECTION, |                     ), | ||||||
|                             &format!( |                 ); | ||||||
|                                 "{} Initializing Harmony project...", |  | ||||||
|                                 harmony_cli::theme::EMOJI_HARMONY, |  | ||||||
|                             ), |  | ||||||
|                         ); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::ProjectInitialized => {} |  | ||||||
|                     HarmonyComposerEvent::ProjectCompilationStarted { details } => { |  | ||||||
|                         progress_tracker.add_task(SETUP_SECTION, COMPILTATION_TASK, &details); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::ProjectCompiled => { |  | ||||||
|                         progress_tracker.finish_task(COMPILTATION_TASK, "project compiled"); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::ProjectCompilationFailed { details } => { |  | ||||||
|                         progress_tracker.fail_task(COMPILTATION_TASK, &format!("failed to compile project:\n{details}")); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::DeploymentStarted { target, profile } => { |  | ||||||
|                         progress_tracker.add_section( |  | ||||||
|                             PROGRESS_DEPLOYMENT, |  | ||||||
|                             &format!( |  | ||||||
|                                 "\n{} Deploying project on target '{target}' with profile '{profile}'...\n", |  | ||||||
|                                 harmony_cli::theme::EMOJI_DEPLOY, |  | ||||||
|                             ), |  | ||||||
|                         ); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::DeploymentCompleted => { |  | ||||||
|                         progress_tracker.clear(); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::DeploymentFailed { details } => { |  | ||||||
|                         progress_tracker.add_task(PROGRESS_DEPLOYMENT, "deployment-failed", ""); |  | ||||||
|                         progress_tracker.fail_task("deployment-failed", &details); |  | ||||||
|                     }, |  | ||||||
|                     HarmonyComposerEvent::Shutdown => { |  | ||||||
|                         return false; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 true |  | ||||||
|             } |             } | ||||||
|  |             HarmonyComposerEvent::ProjectInitialized => {} | ||||||
|  |             HarmonyComposerEvent::ProjectCompilationStarted { details } => { | ||||||
|  |                 progress_tracker.add_task(SETUP_SECTION, COMPILTATION_TASK, details); | ||||||
|  |             } | ||||||
|  |             HarmonyComposerEvent::ProjectCompiled => { | ||||||
|  |                 progress_tracker.finish_task(COMPILTATION_TASK, "project compiled"); | ||||||
|  |             } | ||||||
|  |             HarmonyComposerEvent::ProjectCompilationFailed { details } => { | ||||||
|  |                 progress_tracker.fail_task( | ||||||
|  |                     COMPILTATION_TASK, | ||||||
|  |                     &format!("failed to compile project:\n{details}"), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |             HarmonyComposerEvent::DeploymentStarted { target, profile } => { | ||||||
|  |                 progress_tracker.add_section( | ||||||
|  |                     PROGRESS_DEPLOYMENT, | ||||||
|  |                     &format!( | ||||||
|  |                         "\n{} Deploying project on target '{target}' with profile '{profile}'...\n", | ||||||
|  |                         harmony_cli::theme::EMOJI_DEPLOY, | ||||||
|  |                     ), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |             HarmonyComposerEvent::DeploymentCompleted => { | ||||||
|  |                 progress_tracker.clear(); | ||||||
|  |             } | ||||||
|  |             HarmonyComposerEvent::DeploymentFailed { details } => { | ||||||
|  |                 progress_tracker.add_task(PROGRESS_DEPLOYMENT, "deployment-failed", ""); | ||||||
|  |                 progress_tracker.fail_task("deployment-failed", details); | ||||||
|  |             } | ||||||
|  |             HarmonyComposerEvent::Shutdown => {} | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
|     .await |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| use log::debug; |  | ||||||
| use once_cell::sync::Lazy; | use once_cell::sync::Lazy; | ||||||
| use tokio::sync::broadcast; | use std::{collections::HashMap, sync::Mutex}; | ||||||
| 
 | 
 | ||||||
| use crate::{HarmonyProfile, HarmonyTarget}; | use crate::{HarmonyProfile, HarmonyTarget}; | ||||||
| 
 | 
 | ||||||
| @ -27,48 +26,43 @@ pub enum HarmonyComposerEvent { | |||||||
|     Shutdown, |     Shutdown, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static HARMONY_COMPOSER_EVENT_BUS: Lazy<broadcast::Sender<HarmonyComposerEvent>> = | type Subscriber = Box<dyn Fn(&HarmonyComposerEvent) + Send + Sync>; | ||||||
|     Lazy::new(|| { |  | ||||||
|         // TODO: Adjust channel capacity
 |  | ||||||
|         let (tx, _rx) = broadcast::channel(16); |  | ||||||
|         tx |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
| pub fn instrument(event: HarmonyComposerEvent) -> Result<(), &'static str> { | static SUBSCRIBERS: Lazy<Mutex<HashMap<String, Subscriber>>> = | ||||||
|     #[cfg(not(test))] |     Lazy::new(|| Mutex::new(HashMap::new())); | ||||||
|     { |  | ||||||
|         match HARMONY_COMPOSER_EVENT_BUS.send(event) { |  | ||||||
|             Ok(_) => Ok(()), |  | ||||||
|             Err(_) => Err("send error: no subscribers"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     #[cfg(test)] | /// Subscribes a listener to all instrumentation events.
 | ||||||
|     { | ///
 | ||||||
|         let _ = event; // Suppress the "unused variable" warning for `event`
 | /// Simply provide a unique name and a closure to run when an event happens.
 | ||||||
|         Ok(()) | ///
 | ||||||
|     } | /// # Example
 | ||||||
| } | /// ```
 | ||||||
| 
 | /// instrumentation::subscribe("my_logger", |event| {
 | ||||||
| pub async fn subscribe<F, Fut>(name: &str, mut handler: F) | ///   println!("Event occurred: {:?}", event);
 | ||||||
|  | /// });
 | ||||||
|  | /// ```
 | ||||||
|  | pub fn subscribe<F>(name: &str, callback: F) | ||||||
| where | where | ||||||
|     F: FnMut(HarmonyComposerEvent) -> Fut + Send + 'static, |     F: Fn(&HarmonyComposerEvent) + Send + Sync + 'static, | ||||||
|     Fut: Future<Output = bool> + Send, |  | ||||||
| { | { | ||||||
|     let mut rx = HARMONY_COMPOSER_EVENT_BUS.subscribe(); |     let mut subs = SUBSCRIBERS.lock().unwrap(); | ||||||
|     debug!("[{name}] Service started. Listening for events..."); |     subs.insert(name.to_string(), Box::new(callback)); | ||||||
|     loop { | } | ||||||
|         match rx.recv().await { | 
 | ||||||
|             Ok(event) => { | /// Instruments an event, notifying all subscribers.
 | ||||||
|                 if !handler(event).await { | ///
 | ||||||
|                     debug!("[{name}] Handler requested exit."); | /// This will call every closure that was registered with `subscribe`.
 | ||||||
|                     break; | ///
 | ||||||
|                 } | /// # Example
 | ||||||
|             } | /// ```
 | ||||||
|             Err(broadcast::error::RecvError::Lagged(n)) => { | /// instrumentation::instrument(HarmonyEvent::HarmonyStarted);
 | ||||||
|                 debug!("[{name}] Lagged behind by {n} messages."); | /// ```
 | ||||||
|             } | pub fn instrument(event: HarmonyComposerEvent) -> Result<(), &'static str> { | ||||||
|             Err(_) => break, |     let subs = SUBSCRIBERS.lock().unwrap(); | ||||||
|         } | 
 | ||||||
|     } |     for callback in subs.values() { | ||||||
|  |         callback(&event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(()) | ||||||
| } | } | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ impl std::fmt::Display for HarmonyProfile { | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     let hc_logger_handle = harmony_composer_logger::init(); |     harmony_composer_logger::init(); | ||||||
|     let cli_args = GlobalArgs::parse(); |     let cli_args = GlobalArgs::parse(); | ||||||
| 
 | 
 | ||||||
|     let harmony_path = Path::new(&cli_args.harmony_path) |     let harmony_path = Path::new(&cli_args.harmony_path) | ||||||
| @ -199,8 +199,6 @@ async fn main() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     instrumentation::instrument(HarmonyComposerEvent::Shutdown).unwrap(); |     instrumentation::instrument(HarmonyComposerEvent::Shutdown).unwrap(); | ||||||
| 
 |  | ||||||
|     let _ = tokio::try_join!(hc_logger_handle); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, clap::ValueEnum)] | #[derive(Clone, Debug, clap::ValueEnum)] | ||||||
|  | |||||||
| @ -10,3 +10,8 @@ serde.workspace = true | |||||||
| serde_json.workspace = true | serde_json.workspace = true | ||||||
| log.workspace = true | log.workspace = true | ||||||
| env_logger.workspace = true | env_logger.workspace = true | ||||||
|  | tokio.workspace = true | ||||||
|  | thiserror.workspace = true | ||||||
|  | # mdns-sd = "0.14.1" | ||||||
|  | mdns-sd = { git = "https://github.com/jggc/mdns-sd.git", branch = "patch-1" } | ||||||
|  | local-ip-address = "0.6.5" | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								harmony_inventory_agent/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								harmony_inventory_agent/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | ## Compiling : | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | cargo build -p harmony_inventory_agent --release --target x86_64-unknown-linux-musl | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | This will create a statically linked binary that can run on pretty much any x86_64 system. | ||||||
|  | 
 | ||||||
|  | This requires installation of the target | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | rustup target add x86_64-unknown-linux-musl | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And installation of the musl tools too. | ||||||
|  | 
 | ||||||
|  | On Archlinux, they can be installed with : | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | pacman -S musl | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| @ -1,4 +1,4 @@ | |||||||
| use log::debug; | use log::{debug, warn}; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| use std::fs; | use std::fs; | ||||||
| @ -104,48 +104,58 @@ impl PhysicalHost { | |||||||
| 
 | 
 | ||||||
|     fn all_tools_available() -> Result<(), String> { |     fn all_tools_available() -> Result<(), String> { | ||||||
|         let required_tools = [ |         let required_tools = [ | ||||||
|             ("lsblk", "--version"), |             ("lsblk", Some("--version")), | ||||||
|             ("lspci", "--version"), |             ("lspci", Some("--version")), | ||||||
|             ("lsmod", "--version"), |             ("lsmod", None), | ||||||
|             ("dmidecode", "--version"), |             ("dmidecode", Some("--version")), | ||||||
|             ("smartctl", "--version"), |             ("smartctl", Some("--version")), | ||||||
|             ("ip", "route"), // No version flag available
 |             ("ip", Some("route")), // No version flag available
 | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         let mut missing_tools = Vec::new(); |         let mut missing_tools = Vec::new(); | ||||||
| 
 | 
 | ||||||
|  |         debug!("Looking for required_tools {required_tools:?}"); | ||||||
|         for (tool, tool_arg) in required_tools.iter() { |         for (tool, tool_arg) in required_tools.iter() { | ||||||
|             // First check if tool exists in PATH using which(1)
 |             // First check if tool exists in PATH using which(1)
 | ||||||
|             let exists = if let Ok(output) = Command::new("which").arg(tool).output() { |             let mut exists = if let Ok(output) = Command::new("which").arg(tool).output() { | ||||||
|                 output.status.success() |                 output.status.success() | ||||||
|             } else { |             } else { | ||||||
|                 // Fallback: manual PATH search if which(1) is unavailable
 |                 false | ||||||
|                 if let Ok(path_var) = std::env::var("PATH") { |  | ||||||
|                     path_var.split(':').any(|dir| { |  | ||||||
|                         let tool_path = std::path::Path::new(dir).join(tool); |  | ||||||
|                         tool_path.exists() && Self::is_executable(&tool_path) |  | ||||||
|                     }) |  | ||||||
|                 } else { |  | ||||||
|                     false |  | ||||||
|                 } |  | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             if !exists { |             if !exists { | ||||||
|  |                 // Fallback: manual PATH search if which(1) is unavailable
 | ||||||
|  |                 debug!("Looking for {tool} in path"); | ||||||
|  |                 if let Ok(path_var) = std::env::var("PATH") { | ||||||
|  |                     debug!("PATH is {path_var}"); | ||||||
|  |                     exists = path_var.split(':').any(|dir| { | ||||||
|  |                         let tool_path = std::path::Path::new(dir).join(tool); | ||||||
|  |                         tool_path.exists() && Self::is_executable(&tool_path) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if !exists { | ||||||
|  |                 warn!("Unable to find tool {tool} from PATH"); | ||||||
|                 missing_tools.push(*tool); |                 missing_tools.push(*tool); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Verify tool is functional by checking version/help output
 |             // Verify tool is functional by checking version/help output
 | ||||||
|             let mut cmd = Command::new(tool); |             let mut cmd = Command::new(tool); | ||||||
|             cmd.arg(tool_arg); |             if let Some(tool_arg) = tool_arg { | ||||||
|  |                 cmd.arg(tool_arg); | ||||||
|  |             } | ||||||
|             cmd.stdout(std::process::Stdio::null()); |             cmd.stdout(std::process::Stdio::null()); | ||||||
|             cmd.stderr(std::process::Stdio::null()); |             cmd.stderr(std::process::Stdio::null()); | ||||||
| 
 | 
 | ||||||
|             if let Ok(status) = cmd.status() { |             if let Ok(status) = cmd.status() { | ||||||
|                 if !status.success() { |                 if !status.success() { | ||||||
|  |                     warn!("Unable to test {tool} status failed"); | ||||||
|                     missing_tools.push(*tool); |                     missing_tools.push(*tool); | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|  |                 warn!("Unable to test {tool}"); | ||||||
|                 missing_tools.push(*tool); |                 missing_tools.push(*tool); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -167,6 +177,7 @@ impl PhysicalHost { | |||||||
| 
 | 
 | ||||||
|     #[cfg(unix)] |     #[cfg(unix)] | ||||||
|     fn is_executable(path: &std::path::Path) -> bool { |     fn is_executable(path: &std::path::Path) -> bool { | ||||||
|  |         debug!("Checking if {} is executable", path.to_string_lossy()); | ||||||
|         use std::os::unix::fs::PermissionsExt; |         use std::os::unix::fs::PermissionsExt; | ||||||
| 
 | 
 | ||||||
|         match std::fs::metadata(path) { |         match std::fs::metadata(path) { | ||||||
| @ -285,11 +296,11 @@ impl PhysicalHost { | |||||||
|             if device_path.exists() { |             if device_path.exists() { | ||||||
|                 if drive.model.is_empty() { |                 if drive.model.is_empty() { | ||||||
|                     drive.model = Self::read_sysfs_string(&device_path.join("device/model")) |                     drive.model = Self::read_sysfs_string(&device_path.join("device/model")) | ||||||
|                         .map_err(|e| format!("Failed to read model for {}: {}", name, e))?; |                         .unwrap_or(format!("Failed to read model for {}", name)); | ||||||
|                 } |                 } | ||||||
|                 if drive.serial.is_empty() { |                 if drive.serial.is_empty() { | ||||||
|                     drive.serial = Self::read_sysfs_string(&device_path.join("device/serial")) |                     drive.serial = Self::read_sysfs_string(&device_path.join("device/serial")) | ||||||
|                         .map_err(|e| format!("Failed to read serial for {}: {}", name, e))?; |                         .unwrap_or(format!("Failed to read serial for {}", name)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -655,6 +666,10 @@ impl PhysicalHost { | |||||||
|             Ok("IDE".to_string()) |             Ok("IDE".to_string()) | ||||||
|         } else if device_name.starts_with("vd") { |         } else if device_name.starts_with("vd") { | ||||||
|             Ok("VirtIO".to_string()) |             Ok("VirtIO".to_string()) | ||||||
|  |         } else if device_name.starts_with("sr") { | ||||||
|  |             Ok("CDROM".to_string()) | ||||||
|  |         } else if device_name.starts_with("zram") { | ||||||
|  |             Ok("Ramdisk".to_string()) | ||||||
|         } else { |         } else { | ||||||
|             // Try to determine from device path
 |             // Try to determine from device path
 | ||||||
|             let subsystem = Self::read_sysfs_string(&device_path.join("device/subsystem"))?; |             let subsystem = Self::read_sysfs_string(&device_path.join("device/subsystem"))?; | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								harmony_inventory_agent/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								harmony_inventory_agent/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | mod hwinfo; | ||||||
|  | pub mod local_presence; | ||||||
							
								
								
									
										90
									
								
								harmony_inventory_agent/src/local_presence/advertise.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								harmony_inventory_agent/src/local_presence/advertise.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | |||||||
|  | use log::{debug, error, info, warn}; | ||||||
|  | use mdns_sd::{ServiceDaemon, ServiceInfo}; | ||||||
|  | use std::collections::HashMap; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     hwinfo::PhysicalHost, | ||||||
|  |     local_presence::{PresenceError, SERVICE_NAME, VERSION}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// Advertises the agent's presence on the local network.
 | ||||||
|  | ///
 | ||||||
|  | /// This function is synchronous and non-blocking. It spawns a background Tokio task
 | ||||||
|  | /// to handle the mDNS advertisement for the lifetime of the application.
 | ||||||
|  | pub fn advertise(service_port: u16) -> Result<(), PresenceError> { | ||||||
|  |     let host_id = match PhysicalHost::gather() { | ||||||
|  |         Ok(host) => Some(host.host_uuid), | ||||||
|  |         Err(e) => { | ||||||
|  |             error!("Could not build physical host, harmony presence id will be unavailable : {e}"); | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let instance_name = format!( | ||||||
|  |         "inventory-agent-{}", | ||||||
|  |         host_id.clone().unwrap_or("unknown".to_string()) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     let spawned_msg = format!("Spawned local presence advertisement task for '{instance_name}'."); | ||||||
|  | 
 | ||||||
|  |     tokio::spawn(async move { | ||||||
|  |         info!( | ||||||
|  |             "Local presence task started. Advertising as '{}'.", | ||||||
|  |             instance_name | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         // The ServiceDaemon must live for the entire duration of the advertisement.
 | ||||||
|  |         // If it's dropped, the advertisement stops.
 | ||||||
|  |         let mdns = match ServiceDaemon::new() { | ||||||
|  |             Ok(daemon) => daemon, | ||||||
|  |             Err(e) => { | ||||||
|  |                 warn!("Failed to create mDNS daemon: {}. Task shutting down.", e); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         let mut props = HashMap::new(); | ||||||
|  |         if let Some(host_id) = host_id { | ||||||
|  |             props.insert("id".to_string(), host_id); | ||||||
|  |         } | ||||||
|  |         props.insert("version".to_string(), VERSION.to_string()); | ||||||
|  | 
 | ||||||
|  |         let local_ip: Box<dyn mdns_sd::AsIpAddrs> = match local_ip_address::local_ip() { | ||||||
|  |             Ok(ip) => Box::new(ip), | ||||||
|  |             Err(e) => { | ||||||
|  |                 error!( | ||||||
|  |                     "Could not figure out local ip, mdns will have to try to figure it out by itself : {e}" | ||||||
|  |                 ); | ||||||
|  |                 Box::new(()) | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         debug!("Using local ip {:?}", local_ip.as_ip_addrs()); | ||||||
|  | 
 | ||||||
|  |         let service_info = ServiceInfo::new( | ||||||
|  |             SERVICE_NAME, | ||||||
|  |             &instance_name, | ||||||
|  |             &format!("{}.local.", instance_name), | ||||||
|  |             local_ip, | ||||||
|  |             service_port, | ||||||
|  |             Some(props), | ||||||
|  |         ) | ||||||
|  |         .expect("ServiceInfo creation should not fail with valid inputs"); | ||||||
|  | 
 | ||||||
|  |         // The registration handle must also be kept alive.
 | ||||||
|  |         let _registration_handle = match mdns.register(service_info) { | ||||||
|  |             Ok(handle) => { | ||||||
|  |                 info!("Service successfully registered on the local network."); | ||||||
|  |                 handle | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 warn!("Failed to register service: {}. Task shutting down.", e); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     info!("{spawned_msg}"); | ||||||
|  | 
 | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								harmony_inventory_agent/src/local_presence/discover.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								harmony_inventory_agent/src/local_presence/discover.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | use mdns_sd::{ServiceDaemon, ServiceEvent}; | ||||||
|  | 
 | ||||||
|  | use crate::local_presence::SERVICE_NAME; | ||||||
|  | 
 | ||||||
|  | pub type DiscoveryEvent = ServiceEvent; | ||||||
|  | 
 | ||||||
|  | pub fn discover_agents(timeout: Option<u64>, on_event: fn(&DiscoveryEvent)) { | ||||||
|  |     // Create a new mDNS daemon.
 | ||||||
|  |     let mdns = ServiceDaemon::new().expect("Failed to create mDNS daemon"); | ||||||
|  | 
 | ||||||
|  |     // Start browsing for the service type.
 | ||||||
|  |     // The receiver will be a stream of events.
 | ||||||
|  |     let receiver = mdns.browse(SERVICE_NAME).expect("Failed to browse"); | ||||||
|  | 
 | ||||||
|  |     std::thread::spawn(move || { | ||||||
|  |         while let Ok(event) = receiver.recv() { | ||||||
|  |             on_event(&event); | ||||||
|  |             match event { | ||||||
|  |                 ServiceEvent::ServiceResolved(resolved) => { | ||||||
|  |                     println!("Resolved a new service: {}", resolved.fullname); | ||||||
|  |                 } | ||||||
|  |                 other_event => { | ||||||
|  |                     println!("Received other event: {:?}", &other_event); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if let Some(timeout) = timeout { | ||||||
|  |         // Gracefully shutdown the daemon.
 | ||||||
|  |         std::thread::sleep(std::time::Duration::from_secs(timeout)); | ||||||
|  |         mdns.shutdown().unwrap(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								harmony_inventory_agent/src/local_presence/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								harmony_inventory_agent/src/local_presence/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | mod discover; | ||||||
|  | pub use discover::*; | ||||||
|  | mod advertise; | ||||||
|  | pub use advertise::*; | ||||||
|  | 
 | ||||||
|  | pub const SERVICE_NAME: &str = "_harmony._tcp.local."; | ||||||
|  | const VERSION: &str = env!("CARGO_PKG_VERSION"); | ||||||
|  | 
 | ||||||
|  | // A specific error type for our module enhances clarity and usability.
 | ||||||
|  | #[derive(thiserror::Error, Debug)] | ||||||
|  | pub enum PresenceError { | ||||||
|  |     #[error("Failed to create mDNS daemon")] | ||||||
|  |     DaemonCreationFailed(#[from] mdns_sd::Error), | ||||||
|  |     #[error("The shutdown signal has already been sent")] | ||||||
|  |     ShutdownFailed, | ||||||
|  | } | ||||||
| @ -1,9 +1,12 @@ | |||||||
| // src/main.rs
 | // src/main.rs
 | ||||||
| use actix_web::{App, HttpServer, Responder, get}; | use actix_web::{App, HttpServer, Responder, get}; | ||||||
| use hwinfo::PhysicalHost; | use log::error; | ||||||
| use std::env; | use std::env; | ||||||
| 
 | 
 | ||||||
|  | use crate::hwinfo::PhysicalHost; | ||||||
|  | 
 | ||||||
| mod hwinfo; | mod hwinfo; | ||||||
|  | mod local_presence; | ||||||
| 
 | 
 | ||||||
| #[get("/inventory")] | #[get("/inventory")] | ||||||
| async fn inventory() -> impl Responder { | async fn inventory() -> impl Responder { | ||||||
| @ -26,10 +29,17 @@ async fn main() -> std::io::Result<()> { | |||||||
|     env_logger::init(); |     env_logger::init(); | ||||||
| 
 | 
 | ||||||
|     let port = env::var("HARMONY_INVENTORY_AGENT_PORT").unwrap_or_else(|_| "8080".to_string()); |     let port = env::var("HARMONY_INVENTORY_AGENT_PORT").unwrap_or_else(|_| "8080".to_string()); | ||||||
|  |     let port = port | ||||||
|  |         .parse::<u16>() | ||||||
|  |         .expect(&format!("Invalid port number, cannot parse to u16 {port}")); | ||||||
|     let bind_addr = format!("0.0.0.0:{}", port); |     let bind_addr = format!("0.0.0.0:{}", port); | ||||||
| 
 | 
 | ||||||
|     log::info!("Starting inventory agent on {}", bind_addr); |     log::info!("Starting inventory agent on {}", bind_addr); | ||||||
| 
 | 
 | ||||||
|  |     if let Err(e) = local_presence::advertise(port) { | ||||||
|  |         error!("Could not start advertise local presence : {e}"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     HttpServer::new(|| App::new().service(inventory)) |     HttpServer::new(|| App::new().service(inventory)) | ||||||
|         .bind(&bind_addr)? |         .bind(&bind_addr)? | ||||||
|         .run() |         .run() | ||||||
|  | |||||||
| @ -1,19 +1,20 @@ | |||||||
| [package] | [package] | ||||||
| name = "harmony-secret" | name = "harmony_secret" | ||||||
| edition = "2024" | edition = "2024" | ||||||
| version.workspace = true | version.workspace = true | ||||||
| readme.workspace = true | readme.workspace = true | ||||||
| license.workspace = true | license.workspace = true | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| harmony-secret-derive = { version = "0.1.0", path = "../harmony_secret_derive" } | harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" } | ||||||
| serde = { version = "1.0.209", features = ["derive", "rc"] } | serde = { version = "1.0.209", features = ["derive", "rc"] } | ||||||
| serde_json = "1.0.127" | serde_json = "1.0.127" | ||||||
| thiserror.workspace = true | thiserror.workspace = true | ||||||
| lazy_static.workspace = true | lazy_static.workspace = true | ||||||
| directories.workspace = true | directories.workspace = true | ||||||
| log.workspace = true | log.workspace = true | ||||||
| infisical = "0.0.2" | # infisical = "0.0.2" | ||||||
|  | infisical = { git = "https://github.com/jggc/rust-sdk.git", branch = "patch-1" } | ||||||
| tokio.workspace = true | tokio.workspace = true | ||||||
| async-trait.workspace = true | async-trait.workspace = true | ||||||
| http.workspace = true | http.workspace = true | ||||||
|  | |||||||
| @ -126,6 +126,7 @@ impl SecretManager { | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use super::*; |     use super::*; | ||||||
|  |     #[cfg(secrete2etest)] | ||||||
|     use pretty_assertions::assert_eq; |     use pretty_assertions::assert_eq; | ||||||
|     use serde::{Deserialize, Serialize}; |     use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -70,7 +70,6 @@ mod tests { | |||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn test_set_and_get_raw_successfully() { |     async fn test_set_and_get_raw_successfully() { | ||||||
|         let dir = tempdir().unwrap(); |         let dir = tempdir().unwrap(); | ||||||
|         let store = LocalFileSecretStore::default(); |  | ||||||
|         let ns = "test-ns"; |         let ns = "test-ns"; | ||||||
|         let key = "test-key"; |         let key = "test-key"; | ||||||
|         let value = b"{\"data\":\"test-value\"}"; |         let value = b"{\"data\":\"test-value\"}"; | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| [package] | [package] | ||||||
| name = "harmony-secret-derive" | name = "harmony_secret_derive" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2024" | edition = "2024" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,10 +10,10 @@ pub fn derive_secret(input: TokenStream) -> TokenStream { | |||||||
| 
 | 
 | ||||||
|     // The key for the secret will be the stringified name of the struct itself.
 |     // The key for the secret will be the stringified name of the struct itself.
 | ||||||
|     // e.g., `struct OKDClusterSecret` becomes key `"OKDClusterSecret"`.
 |     // e.g., `struct OKDClusterSecret` becomes key `"OKDClusterSecret"`.
 | ||||||
|     let key = struct_ident.to_string(); |     let key = struct_ident.to_string(); // TODO: Utiliser path complet de la struct
 | ||||||
| 
 | 
 | ||||||
|     // Find the path to the `harmony_secret` crate.
 |     // Find the path to the `harmony_secret` crate.
 | ||||||
|     let secret_crate_path = match crate_name("harmony-secret") { |     let secret_crate_path = match crate_name("harmony_secret") { | ||||||
|         Ok(FoundCrate::Itself) => quote!(crate), |         Ok(FoundCrate::Itself) => quote!(crate), | ||||||
|         Ok(FoundCrate::Name(name)) => { |         Ok(FoundCrate::Name(name)) => { | ||||||
|             let ident = Ident::new(&name, proc_macro2::Span::call_site()); |             let ident = Ident::new(&name, proc_macro2::Span::call_site()); | ||||||
|  | |||||||
| @ -94,13 +94,9 @@ async fn init_instrumentation() -> tokio::task::JoinHandle<()> { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn handle_harmony_events() { | async fn handle_harmony_events() { | ||||||
|     instrumentation::subscribe("Harmony TUI Logger", async |event| { |     instrumentation::subscribe("Harmony TUI Logger", |_| { | ||||||
|         if let HarmonyEvent::HarmonyFinished = event { |         // TODO: Display events in the TUI
 | ||||||
|             return false; |     }); | ||||||
|         }; |  | ||||||
|         true |  | ||||||
|     }) |  | ||||||
|     .await; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct HarmonyTUI<T: Topology> { | pub struct HarmonyTUI<T: Topology> { | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ async-trait = { workspace = true } | |||||||
| tokio = { workspace = true } | tokio = { workspace = true } | ||||||
| octocrab = "0.44.0" | octocrab = "0.44.0" | ||||||
| regex = "1.11.1" | regex = "1.11.1" | ||||||
| reqwest = { version = "0.12", features = ["stream"]  } | reqwest = { version = "0.12", features = ["stream", "rustls-tls", "http2"], default-features = false } | ||||||
| url.workspace = true | url.workspace = true | ||||||
| sha2 = "0.10.8" | sha2 = "0.10.8" | ||||||
| futures-util = "0.3.31" | futures-util = "0.3.31" | ||||||
|  | |||||||
| @ -30,15 +30,15 @@ pub struct CaddyGeneral { | |||||||
|     #[yaserde(rename = "TlsDnsApiKey")] |     #[yaserde(rename = "TlsDnsApiKey")] | ||||||
|     pub tls_dns_api_key: MaybeString, |     pub tls_dns_api_key: MaybeString, | ||||||
|     #[yaserde(rename = "TlsDnsSecretApiKey")] |     #[yaserde(rename = "TlsDnsSecretApiKey")] | ||||||
|     pub tls_dns_secret_api_key: MaybeString, |     pub tls_dns_secret_api_key: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsOptionalField1")] |     #[yaserde(rename = "TlsDnsOptionalField1")] | ||||||
|     pub tls_dns_optional_field1: MaybeString, |     pub tls_dns_optional_field1: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsOptionalField2")] |     #[yaserde(rename = "TlsDnsOptionalField2")] | ||||||
|     pub tls_dns_optional_field2: MaybeString, |     pub tls_dns_optional_field2: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsOptionalField3")] |     #[yaserde(rename = "TlsDnsOptionalField3")] | ||||||
|     pub tls_dns_optional_field3: MaybeString, |     pub tls_dns_optional_field3: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsOptionalField4")] |     #[yaserde(rename = "TlsDnsOptionalField4")] | ||||||
|     pub tls_dns_optional_field4: MaybeString, |     pub tls_dns_optional_field4: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsPropagationTimeout")] |     #[yaserde(rename = "TlsDnsPropagationTimeout")] | ||||||
|     pub tls_dns_propagation_timeout: Option<MaybeString>, |     pub tls_dns_propagation_timeout: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsPropagationTimeoutPeriod")] |     #[yaserde(rename = "TlsDnsPropagationTimeoutPeriod")] | ||||||
| @ -47,6 +47,8 @@ pub struct CaddyGeneral { | |||||||
|     pub tls_dns_propagation_delay: Option<MaybeString>, |     pub tls_dns_propagation_delay: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "TlsDnsPropagationResolvers")] |     #[yaserde(rename = "TlsDnsPropagationResolvers")] | ||||||
|     pub tls_dns_propagation_resolvers: MaybeString, |     pub tls_dns_propagation_resolvers: MaybeString, | ||||||
|  |     #[yaserde(rename = "TlsDnsEchDomain")] | ||||||
|  |     pub tls_dns_ech_domain: Option<MaybeString>, | ||||||
|     pub accesslist: MaybeString, |     pub accesslist: MaybeString, | ||||||
|     #[yaserde(rename = "DisableSuperuser")] |     #[yaserde(rename = "DisableSuperuser")] | ||||||
|     pub disable_superuser: Option<i32>, |     pub disable_superuser: Option<i32>, | ||||||
| @ -56,6 +58,10 @@ pub struct CaddyGeneral { | |||||||
|     pub http_version: Option<MaybeString>, |     pub http_version: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "HttpVersions")] |     #[yaserde(rename = "HttpVersions")] | ||||||
|     pub http_versions: Option<MaybeString>, |     pub http_versions: Option<MaybeString>, | ||||||
|  |     pub timeout_read_body: Option<MaybeString>, | ||||||
|  |     pub timeout_read_header: Option<MaybeString>, | ||||||
|  |     pub timeout_write: Option<MaybeString>, | ||||||
|  |     pub timeout_idle: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "LogCredentials")] |     #[yaserde(rename = "LogCredentials")] | ||||||
|     pub log_credentials: MaybeString, |     pub log_credentials: MaybeString, | ||||||
|     #[yaserde(rename = "LogAccessPlain")] |     #[yaserde(rename = "LogAccessPlain")] | ||||||
|  | |||||||
							
								
								
									
										113
									
								
								opnsense-config-xml/src/data/dnsmasq.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								opnsense-config-xml/src/data/dnsmasq.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | |||||||
|  | use yaserde::{MaybeString, RawXml}; | ||||||
|  | use yaserde_derive::{YaDeserialize, YaSerialize}; | ||||||
|  | 
 | ||||||
|  | // This is the top-level struct that represents the entire <dnsmasq> element.
 | ||||||
|  | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
|  | pub struct DnsMasq { | ||||||
|  |     #[yaserde(attribute = true)] | ||||||
|  |     pub version: String, | ||||||
|  |     #[yaserde(attribute = true)] | ||||||
|  |     pub persisted_at: Option<String>, | ||||||
|  | 
 | ||||||
|  |     pub enable: u8, | ||||||
|  |     pub regdhcp: u8, | ||||||
|  |     pub regdhcpstatic: u8, | ||||||
|  |     pub dhcpfirst: u8, | ||||||
|  |     pub strict_order: u8, | ||||||
|  |     pub domain_needed: u8, | ||||||
|  |     pub no_private_reverse: u8, | ||||||
|  |     pub no_resolv: Option<u8>, | ||||||
|  |     pub log_queries: u8, | ||||||
|  |     pub no_hosts: u8, | ||||||
|  |     pub strictbind: u8, | ||||||
|  |     pub dnssec: u8, | ||||||
|  |     pub regdhcpdomain: MaybeString, | ||||||
|  |     pub interface: Option<String>, | ||||||
|  |     pub port: Option<u32>, | ||||||
|  |     pub dns_forward_max: MaybeString, | ||||||
|  |     pub cache_size: MaybeString, | ||||||
|  |     pub local_ttl: MaybeString, | ||||||
|  |     pub add_mac: Option<MaybeString>, | ||||||
|  |     pub add_subnet: Option<u8>, | ||||||
|  |     pub strip_subnet: Option<u8>, | ||||||
|  |     pub no_ident: Option<u8>, | ||||||
|  |     pub dhcp: Option<Dhcp>, | ||||||
|  |     pub dhcp_ranges: Vec<DhcpRange>, | ||||||
|  |     pub dhcp_options: Vec<DhcpOptions>, | ||||||
|  |     pub dhcp_boot: Vec<DhcpBoot>, | ||||||
|  |     pub dhcp_tags: Vec<RawXml>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Represents the <dhcp> element and its nested fields.
 | ||||||
|  | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
|  | #[yaserde(rename = "dhcp")] | ||||||
|  | pub struct Dhcp { | ||||||
|  |     pub no_interface: MaybeString, | ||||||
|  |     pub fqdn: u8, | ||||||
|  |     pub domain: MaybeString, | ||||||
|  |     pub local: Option<MaybeString>, | ||||||
|  |     pub lease_max: MaybeString, | ||||||
|  |     pub authoritative: u8, | ||||||
|  |     pub default_fw_rules: u8, | ||||||
|  |     pub reply_delay: MaybeString, | ||||||
|  |     pub enable_ra: u8, | ||||||
|  |     pub nosync: u8, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Represents a single <dhcp_ranges> element.
 | ||||||
|  | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
|  | #[yaserde(rename = "dhcp_ranges")] | ||||||
|  | pub struct DhcpRange { | ||||||
|  |     #[yaserde(attribute = true)] | ||||||
|  |     pub uuid: Option<String>, | ||||||
|  |     pub interface: Option<String>, | ||||||
|  |     pub set_tag: Option<MaybeString>, | ||||||
|  |     pub start_addr: Option<String>, | ||||||
|  |     pub end_addr: Option<String>, | ||||||
|  |     pub subnet_mask: Option<MaybeString>, | ||||||
|  |     pub constructor: Option<MaybeString>, | ||||||
|  |     pub mode: Option<MaybeString>, | ||||||
|  |     pub prefix_len: Option<MaybeString>, | ||||||
|  |     pub lease_time: Option<MaybeString>, | ||||||
|  |     pub domain_type: Option<String>, | ||||||
|  |     pub domain: Option<MaybeString>, | ||||||
|  |     pub nosync: Option<u8>, | ||||||
|  |     pub ra_mode: Option<MaybeString>, | ||||||
|  |     pub ra_priority: Option<MaybeString>, | ||||||
|  |     pub ra_mtu: Option<MaybeString>, | ||||||
|  |     pub ra_interval: Option<MaybeString>, | ||||||
|  |     pub ra_router_lifetime: Option<MaybeString>, | ||||||
|  |     pub description: Option<MaybeString>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Represents a single <dhcp_boot> element.
 | ||||||
|  | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
|  | #[yaserde(rename = "dhcp_boot")] | ||||||
|  | pub struct DhcpBoot { | ||||||
|  |     #[yaserde(attribute = true)] | ||||||
|  |     pub uuid: String, | ||||||
|  |     pub interface: MaybeString, | ||||||
|  |     pub tag: MaybeString, | ||||||
|  |     pub filename: Option<String>, | ||||||
|  |     pub servername: String, | ||||||
|  |     pub address: String, | ||||||
|  |     pub description: Option<String>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Represents a single <dhcp_options> element.
 | ||||||
|  | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
|  | #[yaserde(rename = "dhcp_options")] | ||||||
|  | pub struct DhcpOptions { | ||||||
|  |     #[yaserde(attribute = true)] | ||||||
|  |     pub uuid: String, | ||||||
|  |     #[yaserde(rename = "type")] | ||||||
|  |     pub _type: String, | ||||||
|  |     pub option: MaybeString, | ||||||
|  |     pub option6: MaybeString, | ||||||
|  |     pub interface: MaybeString, | ||||||
|  |     pub tag: MaybeString, | ||||||
|  |     pub set_tag: MaybeString, | ||||||
|  |     pub value: String, | ||||||
|  |     pub force: Option<u8>, | ||||||
|  |     pub description: MaybeString, | ||||||
|  | } | ||||||
| @ -8,10 +8,12 @@ pub struct Interface { | |||||||
|     #[yaserde(rename = "if")] |     #[yaserde(rename = "if")] | ||||||
|     pub physical_interface_name: String, |     pub physical_interface_name: String, | ||||||
|     pub descr: Option<MaybeString>, |     pub descr: Option<MaybeString>, | ||||||
|  |     pub mtu: Option<MaybeString>, | ||||||
|     pub enable: MaybeString, |     pub enable: MaybeString, | ||||||
|     pub lock: Option<MaybeString>, |     pub lock: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "spoofmac")] |     #[yaserde(rename = "spoofmac")] | ||||||
|     pub spoof_mac: Option<MaybeString>, |     pub spoof_mac: Option<MaybeString>, | ||||||
|  |     pub mss: Option<MaybeString>, | ||||||
|     pub ipaddr: Option<MaybeString>, |     pub ipaddr: Option<MaybeString>, | ||||||
|     pub dhcphostname: Option<MaybeString>, |     pub dhcphostname: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "alias-address")] |     #[yaserde(rename = "alias-address")] | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| mod caddy; | mod caddy; | ||||||
| mod dhcpd; | mod dhcpd; | ||||||
|  | pub mod dnsmasq; | ||||||
| mod haproxy; | mod haproxy; | ||||||
| mod interfaces; | mod interfaces; | ||||||
| mod opnsense; | mod opnsense; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | use crate::dnsmasq::DnsMasq; | ||||||
| use crate::HAProxy; | use crate::HAProxy; | ||||||
| use crate::{data::dhcpd::DhcpInterface, xml_utils::to_xml_str}; | use crate::{data::dhcpd::DhcpInterface, xml_utils::to_xml_str}; | ||||||
| use log::error; | use log::error; | ||||||
| @ -22,7 +23,7 @@ pub struct OPNsense { | |||||||
|     pub load_balancer: Option<LoadBalancer>, |     pub load_balancer: Option<LoadBalancer>, | ||||||
|     pub rrd: Option<RawXml>, |     pub rrd: Option<RawXml>, | ||||||
|     pub ntpd: Ntpd, |     pub ntpd: Ntpd, | ||||||
|     pub widgets: Widgets, |     pub widgets: Option<Widgets>, | ||||||
|     pub revision: Revision, |     pub revision: Revision, | ||||||
|     #[yaserde(rename = "OPNsense")] |     #[yaserde(rename = "OPNsense")] | ||||||
|     pub opnsense: OPNsenseXmlSection, |     pub opnsense: OPNsenseXmlSection, | ||||||
| @ -45,7 +46,7 @@ pub struct OPNsense { | |||||||
|     #[yaserde(rename = "Pischem")] |     #[yaserde(rename = "Pischem")] | ||||||
|     pub pischem: Option<Pischem>, |     pub pischem: Option<Pischem>, | ||||||
|     pub ifgroups: Ifgroups, |     pub ifgroups: Ifgroups, | ||||||
|     pub dnsmasq: Option<RawXml>, |     pub dnsmasq: Option<DnsMasq>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<String> for OPNsense { | impl From<String> for OPNsense { | ||||||
| @ -165,9 +166,9 @@ pub struct Sysctl { | |||||||
| 
 | 
 | ||||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
| pub struct SysctlItem { | pub struct SysctlItem { | ||||||
|     pub descr: MaybeString, |     pub descr: Option<MaybeString>, | ||||||
|     pub tunable: String, |     pub tunable: Option<String>, | ||||||
|     pub value: MaybeString, |     pub value: Option<MaybeString>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
| @ -182,8 +183,8 @@ pub struct System { | |||||||
|     pub domain: String, |     pub domain: String, | ||||||
|     pub group: Vec<Group>, |     pub group: Vec<Group>, | ||||||
|     pub user: Vec<User>, |     pub user: Vec<User>, | ||||||
|     pub nextuid: u32, |     pub nextuid: Option<u32>, | ||||||
|     pub nextgid: u32, |     pub nextgid: Option<u32>, | ||||||
|     pub timezone: String, |     pub timezone: String, | ||||||
|     pub timeservers: String, |     pub timeservers: String, | ||||||
|     pub webgui: WebGui, |     pub webgui: WebGui, | ||||||
| @ -242,6 +243,7 @@ pub struct Ssh { | |||||||
|     pub passwordauth: u8, |     pub passwordauth: u8, | ||||||
|     pub keysig: MaybeString, |     pub keysig: MaybeString, | ||||||
|     pub permitrootlogin: u8, |     pub permitrootlogin: u8, | ||||||
|  |     pub rekeylimit: Option<MaybeString>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
| @ -271,6 +273,7 @@ pub struct Group { | |||||||
|     pub member: Vec<u32>, |     pub member: Vec<u32>, | ||||||
|     #[yaserde(rename = "priv")] |     #[yaserde(rename = "priv")] | ||||||
|     pub priv_field: String, |     pub priv_field: String, | ||||||
|  |     pub source_networks: Option<MaybeString>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
| @ -1506,7 +1509,7 @@ pub struct Vlans { | |||||||
| 
 | 
 | ||||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
| pub struct Bridges { | pub struct Bridges { | ||||||
|     pub bridged: MaybeString, |     pub bridged: Option<MaybeString>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ russh-sftp = "2.0.6" | |||||||
| serde_json = "1.0.133" | serde_json = "1.0.133" | ||||||
| tokio-util = { version = "0.7.13", features = ["codec"] } | tokio-util = { version = "0.7.13", features = ["codec"] } | ||||||
| tokio-stream = "0.1.17" | tokio-stream = "0.1.17" | ||||||
|  | uuid.workspace = true | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| pretty_assertions.workspace = true | pretty_assertions.workspace = true | ||||||
|  | |||||||
| @ -4,8 +4,8 @@ use crate::{ | |||||||
|     config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, |     config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, | ||||||
|     error::Error, |     error::Error, | ||||||
|     modules::{ |     modules::{ | ||||||
|         caddy::CaddyConfig, dhcp::DhcpConfig, dns::DnsConfig, load_balancer::LoadBalancerConfig, |         caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::DnsConfig, | ||||||
|         tftp::TftpConfig, |         dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| use log::{debug, info, trace, warn}; | use log::{debug, info, trace, warn}; | ||||||
| @ -43,23 +43,27 @@ impl Config { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn dhcp(&mut self) -> DhcpConfig { |     pub fn dhcp_legacy_isc(&mut self) -> DhcpConfigLegacyISC<'_> { | ||||||
|         DhcpConfig::new(&mut self.opnsense, self.shell.clone()) |         DhcpConfigLegacyISC::new(&mut self.opnsense, self.shell.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn dns(&mut self) -> DnsConfig { |     pub fn dhcp(&mut self) -> DhcpConfigDnsMasq<'_> { | ||||||
|  |         DhcpConfigDnsMasq::new(&mut self.opnsense, self.shell.clone()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn dns(&mut self) -> DnsConfig<'_> { | ||||||
|         DnsConfig::new(&mut self.opnsense) |         DnsConfig::new(&mut self.opnsense) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn tftp(&mut self) -> TftpConfig { |     pub fn tftp(&mut self) -> TftpConfig<'_> { | ||||||
|         TftpConfig::new(&mut self.opnsense, self.shell.clone()) |         TftpConfig::new(&mut self.opnsense, self.shell.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn caddy(&mut self) -> CaddyConfig { |     pub fn caddy(&mut self) -> CaddyConfig<'_> { | ||||||
|         CaddyConfig::new(&mut self.opnsense, self.shell.clone()) |         CaddyConfig::new(&mut self.opnsense, self.shell.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn load_balancer(&mut self) -> LoadBalancerConfig { |     pub fn load_balancer(&mut self) -> LoadBalancerConfig<'_> { | ||||||
|         LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone()) |         LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -67,6 +71,10 @@ impl Config { | |||||||
|         self.shell.upload_folder(source, destination).await |         self.shell.upload_folder(source, destination).await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub async fn upload_file_content(&self, path: &str, content: &str) -> Result<String, Error> { | ||||||
|  |         self.shell.write_content_to_file(content, path).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Checks in config file if system.firmware.plugins csv field contains the specified package
 |     /// Checks in config file if system.firmware.plugins csv field contains the specified package
 | ||||||
|     /// name.
 |     /// name.
 | ||||||
|     ///
 |     ///
 | ||||||
| @ -200,7 +208,7 @@ impl Config { | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use crate::config::{DummyOPNSenseShell, LocalFileConfigManager}; |     use crate::config::{DummyOPNSenseShell, LocalFileConfigManager}; | ||||||
|     use crate::modules::dhcp::DhcpConfig; |     use crate::modules::dhcp_legacy::DhcpConfigLegacyISC; | ||||||
|     use std::fs; |     use std::fs; | ||||||
|     use std::net::Ipv4Addr; |     use std::net::Ipv4Addr; | ||||||
| 
 | 
 | ||||||
| @ -215,6 +223,9 @@ mod tests { | |||||||
|             "src/tests/data/config-vm-test.xml", |             "src/tests/data/config-vm-test.xml", | ||||||
|             "src/tests/data/config-structure.xml", |             "src/tests/data/config-structure.xml", | ||||||
|             "src/tests/data/config-full-1.xml", |             "src/tests/data/config-full-1.xml", | ||||||
|  |             "src/tests/data/config-full-ncd0.xml", | ||||||
|  |             "src/tests/data/config-full-25.7.xml", | ||||||
|  |             "src/tests/data/config-full-25.7-dummy-dnsmasq-options.xml", | ||||||
|         ] { |         ] { | ||||||
|             let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |             let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); | ||||||
|             test_file_path.push(path); |             test_file_path.push(path); | ||||||
| @ -257,7 +268,7 @@ mod tests { | |||||||
| 
 | 
 | ||||||
|         println!("Config {:?}", config); |         println!("Config {:?}", config); | ||||||
| 
 | 
 | ||||||
|         let mut dhcp_config = DhcpConfig::new(&mut config.opnsense, shell); |         let mut dhcp_config = DhcpConfigLegacyISC::new(&mut config.opnsense, shell); | ||||||
|         dhcp_config |         dhcp_config | ||||||
|             .add_static_mapping( |             .add_static_mapping( | ||||||
|                 "00:00:00:00:00:00", |                 "00:00:00:00:00:00", | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ use crate::Error; | |||||||
| pub trait OPNsenseShell: std::fmt::Debug + Send + Sync { | pub trait OPNsenseShell: std::fmt::Debug + Send + Sync { | ||||||
|     async fn exec(&self, command: &str) -> Result<String, Error>; |     async fn exec(&self, command: &str) -> Result<String, Error>; | ||||||
|     async fn write_content_to_temp_file(&self, content: &str) -> Result<String, Error>; |     async fn write_content_to_temp_file(&self, content: &str) -> Result<String, Error>; | ||||||
|  |     async fn write_content_to_file(&self, content: &str, filename: &str) -> Result<String, Error>; | ||||||
|     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error>; |     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -25,6 +26,14 @@ impl OPNsenseShell for DummyOPNSenseShell { | |||||||
|     async fn write_content_to_temp_file(&self, _content: &str) -> Result<String, Error> { |     async fn write_content_to_temp_file(&self, _content: &str) -> Result<String, Error> { | ||||||
|         unimplemented!("This is a dummy implementation"); |         unimplemented!("This is a dummy implementation"); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async fn write_content_to_file( | ||||||
|  |         &self, | ||||||
|  |         _content: &str, | ||||||
|  |         _filename: &str, | ||||||
|  |     ) -> Result<String, Error> { | ||||||
|  |         unimplemented!("This is a dummy implementation"); | ||||||
|  |     } | ||||||
|     async fn upload_folder(&self, _source: &str, _destination: &str) -> Result<String, Error> { |     async fn upload_folder(&self, _source: &str, _destination: &str) -> Result<String, Error> { | ||||||
|         unimplemented!("This is a dummy implementation"); |         unimplemented!("This is a dummy implementation"); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| use std::{ | use std::{ | ||||||
|     net::IpAddr, |     net::IpAddr, | ||||||
|  |     path::Path, | ||||||
|     sync::Arc, |     sync::Arc, | ||||||
|     time::{SystemTime, UNIX_EPOCH}, |     time::{SystemTime, UNIX_EPOCH}, | ||||||
| }; | }; | ||||||
| @ -44,6 +45,10 @@ impl OPNsenseShell for SshOPNSenseShell { | |||||||
|                 .unwrap() |                 .unwrap() | ||||||
|                 .as_millis() |                 .as_millis() | ||||||
|         ); |         ); | ||||||
|  |         self.write_content_to_file(content, &temp_filename).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn write_content_to_file(&self, content: &str, filename: &str) -> Result<String, Error> { | ||||||
|         let channel = self.get_ssh_channel().await?; |         let channel = self.get_ssh_channel().await?; | ||||||
|         channel |         channel | ||||||
|             .request_subsystem(true, "sftp") |             .request_subsystem(true, "sftp") | ||||||
| @ -53,10 +58,18 @@ impl OPNsenseShell for SshOPNSenseShell { | |||||||
|             .await |             .await | ||||||
|             .expect("Should acquire sftp subsystem"); |             .expect("Should acquire sftp subsystem"); | ||||||
| 
 | 
 | ||||||
|         let mut file = sftp.create(&temp_filename).await.unwrap(); |         if let Some(parent) = Path::new(filename).parent() { | ||||||
|  |             if let Some(parent_str) = parent.to_str() { | ||||||
|  |                 if !parent_str.is_empty() { | ||||||
|  |                     self.ensure_remote_dir_exists(&sftp, parent_str).await?; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mut file = sftp.create(filename).await.unwrap(); | ||||||
|         file.write_all(content.as_bytes()).await?; |         file.write_all(content.as_bytes()).await?; | ||||||
| 
 | 
 | ||||||
|         Ok(temp_filename) |         Ok(filename.to_string()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error> { |     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error> { | ||||||
| @ -69,10 +82,7 @@ impl OPNsenseShell for SshOPNSenseShell { | |||||||
|             .await |             .await | ||||||
|             .expect("Should acquire sftp subsystem"); |             .expect("Should acquire sftp subsystem"); | ||||||
| 
 | 
 | ||||||
|         if !sftp.try_exists(destination).await? { |         self.ensure_remote_dir_exists(&sftp, destination).await?; | ||||||
|             info!("Creating remote directory {destination}"); |  | ||||||
|             sftp.create_dir(destination).await?; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         info!("Reading local directory {source}"); |         info!("Reading local directory {source}"); | ||||||
|         let mut entries = read_dir(source).await?; |         let mut entries = read_dir(source).await?; | ||||||
| @ -149,6 +159,14 @@ impl SshOPNSenseShell { | |||||||
|         wait_for_completion(&mut channel).await |         wait_for_completion(&mut channel).await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async fn ensure_remote_dir_exists(&self, sftp: &SftpSession, path: &str) -> Result<(), Error> { | ||||||
|  |         if !sftp.try_exists(path).await? { | ||||||
|  |             info!("Creating remote directory {path}"); | ||||||
|  |             sftp.create_dir(path).await?; | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc<Config>) -> Self { |     pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc<Config>) -> Self { | ||||||
|         info!("Initializing SshOPNSenseShell on host {host:?}"); |         info!("Initializing SshOPNSenseShell on host {host:?}"); | ||||||
|         Self { |         Self { | ||||||
|  | |||||||
| @ -1,19 +1,3 @@ | |||||||
| use log::info; |  | ||||||
| use opnsense_config_xml::MaybeString; |  | ||||||
| use opnsense_config_xml::StaticMap; |  | ||||||
| use std::net::Ipv4Addr; |  | ||||||
| use std::sync::Arc; |  | ||||||
| 
 |  | ||||||
| use opnsense_config_xml::OPNsense; |  | ||||||
| 
 |  | ||||||
| use crate::config::OPNsenseShell; |  | ||||||
| use crate::Error; |  | ||||||
| 
 |  | ||||||
| pub struct DhcpConfig<'a> { |  | ||||||
|     opnsense: &'a mut OPNsense, |  | ||||||
|     opnsense_shell: Arc<dyn OPNsenseShell>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub enum DhcpError { | pub enum DhcpError { | ||||||
|     InvalidMacAddress(String), |     InvalidMacAddress(String), | ||||||
| @ -21,6 +5,7 @@ pub enum DhcpError { | |||||||
|     IpAddressAlreadyMapped(String), |     IpAddressAlreadyMapped(String), | ||||||
|     MacAddressAlreadyMapped(String), |     MacAddressAlreadyMapped(String), | ||||||
|     IpAddressOutOfRange(String), |     IpAddressOutOfRange(String), | ||||||
|  |     Configuration(String), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl std::fmt::Display for DhcpError { | impl std::fmt::Display for DhcpError { | ||||||
| @ -37,158 +22,9 @@ impl std::fmt::Display for DhcpError { | |||||||
|             DhcpError::IpAddressOutOfRange(ip) => { |             DhcpError::IpAddressOutOfRange(ip) => { | ||||||
|                 write!(f, "IP address {} is out of interface range", ip) |                 write!(f, "IP address {} is out of interface range", ip) | ||||||
|             } |             } | ||||||
|  |             DhcpError::Configuration(msg) => f.write_str(&msg), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl std::error::Error for DhcpError {} | impl std::error::Error for DhcpError {} | ||||||
| 
 |  | ||||||
| impl<'a> DhcpConfig<'a> { |  | ||||||
|     pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self { |  | ||||||
|         Self { |  | ||||||
|             opnsense, |  | ||||||
|             opnsense_shell, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn remove_static_mapping(&mut self, mac: &str) { |  | ||||||
|         let lan_dhcpd = self.get_lan_dhcpd(); |  | ||||||
|         lan_dhcpd |  | ||||||
|             .staticmaps |  | ||||||
|             .retain(|static_entry| static_entry.mac != mac); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface { |  | ||||||
|         &mut self |  | ||||||
|             .opnsense |  | ||||||
|             .dhcpd |  | ||||||
|             .elements |  | ||||||
|             .iter_mut() |  | ||||||
|             .find(|(name, _config)| name == "lan") |  | ||||||
|             .expect("Interface lan should have dhcpd activated") |  | ||||||
|             .1 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn add_static_mapping( |  | ||||||
|         &mut self, |  | ||||||
|         mac: &str, |  | ||||||
|         ipaddr: Ipv4Addr, |  | ||||||
|         hostname: &str, |  | ||||||
|     ) -> Result<(), DhcpError> { |  | ||||||
|         let mac = mac.to_string(); |  | ||||||
|         let hostname = hostname.to_string(); |  | ||||||
|         let lan_dhcpd = self.get_lan_dhcpd(); |  | ||||||
|         let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps; |  | ||||||
| 
 |  | ||||||
|         if !Self::is_valid_mac(&mac) { |  | ||||||
|             return Err(DhcpError::InvalidMacAddress(mac)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // TODO validate that address is in subnet range
 |  | ||||||
| 
 |  | ||||||
|         if existing_mappings.iter().any(|m| { |  | ||||||
|             m.ipaddr |  | ||||||
|                 .parse::<Ipv4Addr>() |  | ||||||
|                 .expect("Mapping contains invalid ipv4") |  | ||||||
|                 == ipaddr |  | ||||||
|                 && m.mac == mac |  | ||||||
|         }) { |  | ||||||
|             info!("Mapping already exists for {} [{}], skipping", ipaddr, mac); |  | ||||||
|             return Ok(()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if existing_mappings.iter().any(|m| { |  | ||||||
|             m.ipaddr |  | ||||||
|                 .parse::<Ipv4Addr>() |  | ||||||
|                 .expect("Mapping contains invalid ipv4") |  | ||||||
|                 == ipaddr |  | ||||||
|         }) { |  | ||||||
|             return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string())); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if existing_mappings.iter().any(|m| m.mac == mac) { |  | ||||||
|             return Err(DhcpError::MacAddressAlreadyMapped(mac)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let static_map = StaticMap { |  | ||||||
|             mac, |  | ||||||
|             ipaddr: ipaddr.to_string(), |  | ||||||
|             hostname, |  | ||||||
|             descr: Default::default(), |  | ||||||
|             winsserver: Default::default(), |  | ||||||
|             dnsserver: Default::default(), |  | ||||||
|             ntpserver: Default::default(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         existing_mappings.push(static_map); |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn is_valid_mac(mac: &str) -> bool { |  | ||||||
|         let parts: Vec<&str> = mac.split(':').collect(); |  | ||||||
|         if parts.len() != 6 { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         parts |  | ||||||
|             .iter() |  | ||||||
|             .all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit())) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_static_mappings(&self) -> Result<Vec<StaticMap>, Error> { |  | ||||||
|         let list_static_output = self |  | ||||||
|             .opnsense_shell |  | ||||||
|             .exec("configctl dhcpd list static") |  | ||||||
|             .await?; |  | ||||||
| 
 |  | ||||||
|         let value: serde_json::Value = serde_json::from_str(&list_static_output) |  | ||||||
|             .unwrap_or_else(|_| panic!("Got invalid json from configctl {list_static_output}")); |  | ||||||
|         let static_maps = value["dhcpd"] |  | ||||||
|             .as_array() |  | ||||||
|             .ok_or(Error::Command(format!( |  | ||||||
|                 "Invalid DHCP data from configctl command, got {list_static_output}" |  | ||||||
|             )))? |  | ||||||
|             .iter() |  | ||||||
|             .map(|entry| StaticMap { |  | ||||||
|                 mac: entry["mac"].as_str().unwrap_or_default().to_string(), |  | ||||||
|                 ipaddr: entry["ipaddr"].as_str().unwrap_or_default().to_string(), |  | ||||||
|                 hostname: entry["hostname"].as_str().unwrap_or_default().to_string(), |  | ||||||
|                 descr: entry["descr"].as_str().map(MaybeString::from), |  | ||||||
|                 winsserver: MaybeString::default(), |  | ||||||
|                 dnsserver: MaybeString::default(), |  | ||||||
|                 ntpserver: MaybeString::default(), |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
| 
 |  | ||||||
|         Ok(static_maps) |  | ||||||
|     } |  | ||||||
|     pub fn enable_netboot(&mut self) { |  | ||||||
|         self.get_lan_dhcpd().netboot = Some(1); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn set_next_server(&mut self, ip: Ipv4Addr) { |  | ||||||
|         self.enable_netboot(); |  | ||||||
|         self.get_lan_dhcpd().nextserver = Some(ip.to_string()); |  | ||||||
|         self.get_lan_dhcpd().tftp = Some(ip.to_string()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn set_boot_filename(&mut self, boot_filename: &str) { |  | ||||||
|         self.enable_netboot(); |  | ||||||
|         self.get_lan_dhcpd().bootfilename = Some(boot_filename.to_string()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn set_filename(&mut self, filename: &str) { |  | ||||||
|         self.enable_netboot(); |  | ||||||
|         self.get_lan_dhcpd().filename = Some(filename.to_string()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn set_filename64(&mut self, filename64: &str) { |  | ||||||
|         self.enable_netboot(); |  | ||||||
|         self.get_lan_dhcpd().filename64 = Some(filename64.to_string()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn set_filenameipxe(&mut self, filenameipxe: &str) { |  | ||||||
|         self.enable_netboot(); |  | ||||||
|         self.get_lan_dhcpd().filenameipxe = Some(filenameipxe.to_string()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										166
									
								
								opnsense-config/src/modules/dhcp_legacy.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								opnsense-config/src/modules/dhcp_legacy.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,166 @@ | |||||||
|  | use crate::modules::dhcp::DhcpError; | ||||||
|  | use log::info; | ||||||
|  | use opnsense_config_xml::MaybeString; | ||||||
|  | use opnsense_config_xml::StaticMap; | ||||||
|  | use std::net::Ipv4Addr; | ||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
|  | use opnsense_config_xml::OPNsense; | ||||||
|  | 
 | ||||||
|  | use crate::config::OPNsenseShell; | ||||||
|  | use crate::Error; | ||||||
|  | 
 | ||||||
|  | pub struct DhcpConfigLegacyISC<'a> { | ||||||
|  |     opnsense: &'a mut OPNsense, | ||||||
|  |     opnsense_shell: Arc<dyn OPNsenseShell>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> DhcpConfigLegacyISC<'a> { | ||||||
|  |     pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self { | ||||||
|  |         Self { | ||||||
|  |             opnsense, | ||||||
|  |             opnsense_shell, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn remove_static_mapping(&mut self, mac: &str) { | ||||||
|  |         let lan_dhcpd = self.get_lan_dhcpd(); | ||||||
|  |         lan_dhcpd | ||||||
|  |             .staticmaps | ||||||
|  |             .retain(|static_entry| static_entry.mac != mac); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface { | ||||||
|  |         &mut self | ||||||
|  |             .opnsense | ||||||
|  |             .dhcpd | ||||||
|  |             .elements | ||||||
|  |             .iter_mut() | ||||||
|  |             .find(|(name, _config)| name == "lan") | ||||||
|  |             .expect("Interface lan should have dhcpd activated") | ||||||
|  |             .1 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn add_static_mapping( | ||||||
|  |         &mut self, | ||||||
|  |         mac: &str, | ||||||
|  |         ipaddr: Ipv4Addr, | ||||||
|  |         hostname: &str, | ||||||
|  |     ) -> Result<(), DhcpError> { | ||||||
|  |         let mac = mac.to_string(); | ||||||
|  |         let hostname = hostname.to_string(); | ||||||
|  |         let lan_dhcpd = self.get_lan_dhcpd(); | ||||||
|  |         let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps; | ||||||
|  | 
 | ||||||
|  |         if !Self::is_valid_mac(&mac) { | ||||||
|  |             return Err(DhcpError::InvalidMacAddress(mac)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // TODO validate that address is in subnet range
 | ||||||
|  | 
 | ||||||
|  |         if existing_mappings.iter().any(|m| { | ||||||
|  |             m.ipaddr | ||||||
|  |                 .parse::<Ipv4Addr>() | ||||||
|  |                 .expect("Mapping contains invalid ipv4") | ||||||
|  |                 == ipaddr | ||||||
|  |                 && m.mac == mac | ||||||
|  |         }) { | ||||||
|  |             info!("Mapping already exists for {} [{}], skipping", ipaddr, mac); | ||||||
|  |             return Ok(()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if existing_mappings.iter().any(|m| { | ||||||
|  |             m.ipaddr | ||||||
|  |                 .parse::<Ipv4Addr>() | ||||||
|  |                 .expect("Mapping contains invalid ipv4") | ||||||
|  |                 == ipaddr | ||||||
|  |         }) { | ||||||
|  |             return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string())); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if existing_mappings.iter().any(|m| m.mac == mac) { | ||||||
|  |             return Err(DhcpError::MacAddressAlreadyMapped(mac)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let static_map = StaticMap { | ||||||
|  |             mac, | ||||||
|  |             ipaddr: ipaddr.to_string(), | ||||||
|  |             hostname, | ||||||
|  |             descr: Default::default(), | ||||||
|  |             winsserver: Default::default(), | ||||||
|  |             dnsserver: Default::default(), | ||||||
|  |             ntpserver: Default::default(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         existing_mappings.push(static_map); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn is_valid_mac(mac: &str) -> bool { | ||||||
|  |         let parts: Vec<&str> = mac.split(':').collect(); | ||||||
|  |         if parts.len() != 6 { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         parts | ||||||
|  |             .iter() | ||||||
|  |             .all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_static_mappings(&self) -> Result<Vec<StaticMap>, Error> { | ||||||
|  |         let list_static_output = self | ||||||
|  |             .opnsense_shell | ||||||
|  |             .exec("configctl dhcpd list static") | ||||||
|  |             .await?; | ||||||
|  | 
 | ||||||
|  |         let value: serde_json::Value = serde_json::from_str(&list_static_output) | ||||||
|  |             .unwrap_or_else(|_| panic!("Got invalid json from configctl {list_static_output}")); | ||||||
|  |         let static_maps = value["dhcpd"] | ||||||
|  |             .as_array() | ||||||
|  |             .ok_or(Error::Command(format!( | ||||||
|  |                 "Invalid DHCP data from configctl command, got {list_static_output}" | ||||||
|  |             )))? | ||||||
|  |             .iter() | ||||||
|  |             .map(|entry| StaticMap { | ||||||
|  |                 mac: entry["mac"].as_str().unwrap_or_default().to_string(), | ||||||
|  |                 ipaddr: entry["ipaddr"].as_str().unwrap_or_default().to_string(), | ||||||
|  |                 hostname: entry["hostname"].as_str().unwrap_or_default().to_string(), | ||||||
|  |                 descr: entry["descr"].as_str().map(MaybeString::from), | ||||||
|  |                 winsserver: MaybeString::default(), | ||||||
|  |                 dnsserver: MaybeString::default(), | ||||||
|  |                 ntpserver: MaybeString::default(), | ||||||
|  |             }) | ||||||
|  |             .collect(); | ||||||
|  | 
 | ||||||
|  |         Ok(static_maps) | ||||||
|  |     } | ||||||
|  |     pub fn enable_netboot(&mut self) { | ||||||
|  |         self.get_lan_dhcpd().netboot = Some(1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_next_server(&mut self, ip: Ipv4Addr) { | ||||||
|  |         self.enable_netboot(); | ||||||
|  |         self.get_lan_dhcpd().nextserver = Some(ip.to_string()); | ||||||
|  |         self.get_lan_dhcpd().tftp = Some(ip.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_boot_filename(&mut self, boot_filename: &str) { | ||||||
|  |         self.enable_netboot(); | ||||||
|  |         self.get_lan_dhcpd().bootfilename = Some(boot_filename.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_filename(&mut self, filename: &str) { | ||||||
|  |         self.enable_netboot(); | ||||||
|  |         self.get_lan_dhcpd().filename = Some(filename.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_filename64(&mut self, filename64: &str) { | ||||||
|  |         self.enable_netboot(); | ||||||
|  |         self.get_lan_dhcpd().filename64 = Some(filename64.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_filenameipxe(&mut self, filenameipxe: &str) { | ||||||
|  |         self.enable_netboot(); | ||||||
|  |         self.get_lan_dhcpd().filenameipxe = Some(filenameipxe.to_string()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										187
									
								
								opnsense-config/src/modules/dnsmasq.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								opnsense-config/src/modules/dnsmasq.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | |||||||
|  | // dnsmasq.rs
 | ||||||
|  | use crate::modules::dhcp::DhcpError; | ||||||
|  | use log::{debug, info}; | ||||||
|  | use opnsense_config_xml::{MaybeString, StaticMap}; | ||||||
|  | use std::net::Ipv4Addr; | ||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
|  | use opnsense_config_xml::OPNsense; | ||||||
|  | 
 | ||||||
|  | use crate::config::OPNsenseShell; | ||||||
|  | use crate::Error; | ||||||
|  | 
 | ||||||
|  | pub struct DhcpConfigDnsMasq<'a> { | ||||||
|  |     opnsense: &'a mut OPNsense, | ||||||
|  |     opnsense_shell: Arc<dyn OPNsenseShell>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const DNS_MASQ_PXE_CONFIG_FILE: &str = "/usr/local/etc/dnsmasq.conf.d/pxe.conf"; | ||||||
|  | 
 | ||||||
|  | impl<'a> DhcpConfigDnsMasq<'a> { | ||||||
|  |     pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self { | ||||||
|  |         Self { | ||||||
|  |             opnsense, | ||||||
|  |             opnsense_shell, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Removes a static mapping by its MAC address.
 | ||||||
|  |     /// Static mappings are stored in the <dhcpd> section of the config, shared with the ISC module.
 | ||||||
|  |     pub fn remove_static_mapping(&mut self, mac: &str) { | ||||||
|  |         let lan_dhcpd = self.get_lan_dhcpd(); | ||||||
|  |         lan_dhcpd | ||||||
|  |             .staticmaps | ||||||
|  |             .retain(|static_entry| static_entry.mac != mac); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Retrieves a mutable reference to the LAN interface's DHCP configuration.
 | ||||||
|  |     /// This is located in the shared <dhcpd> section of the config.
 | ||||||
|  |     fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface { | ||||||
|  |         &mut self | ||||||
|  |             .opnsense | ||||||
|  |             .dhcpd | ||||||
|  |             .elements | ||||||
|  |             .iter_mut() | ||||||
|  |             .find(|(name, _config)| name == "lan") | ||||||
|  |             .expect("Interface lan should have dhcpd activated") | ||||||
|  |             .1 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Adds a new static DHCP mapping.
 | ||||||
|  |     /// Validates the MAC address and checks for existing mappings to prevent conflicts.
 | ||||||
|  |     pub fn add_static_mapping( | ||||||
|  |         &mut self, | ||||||
|  |         mac: &str, | ||||||
|  |         ipaddr: Ipv4Addr, | ||||||
|  |         hostname: &str, | ||||||
|  |     ) -> Result<(), DhcpError> { | ||||||
|  |         let mac = mac.to_string(); | ||||||
|  |         let hostname = hostname.to_string(); | ||||||
|  |         let lan_dhcpd = self.get_lan_dhcpd(); | ||||||
|  |         let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps; | ||||||
|  | 
 | ||||||
|  |         if !Self::is_valid_mac(&mac) { | ||||||
|  |             return Err(DhcpError::InvalidMacAddress(mac)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // TODO: Validate that the IP address is within a configured DHCP range.
 | ||||||
|  | 
 | ||||||
|  |         if existing_mappings | ||||||
|  |             .iter() | ||||||
|  |             .any(|m| m.ipaddr == ipaddr.to_string() && m.mac == mac) | ||||||
|  |         { | ||||||
|  |             info!("Mapping already exists for {} [{}], skipping", ipaddr, mac); | ||||||
|  |             return Ok(()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if existing_mappings | ||||||
|  |             .iter() | ||||||
|  |             .any(|m| m.ipaddr == ipaddr.to_string()) | ||||||
|  |         { | ||||||
|  |             return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string())); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if existing_mappings.iter().any(|m| m.mac == mac) { | ||||||
|  |             return Err(DhcpError::MacAddressAlreadyMapped(mac)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let static_map = StaticMap { | ||||||
|  |             mac, | ||||||
|  |             ipaddr: ipaddr.to_string(), | ||||||
|  |             hostname: hostname, | ||||||
|  |             ..Default::default() | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         existing_mappings.push(static_map); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Helper function to validate a MAC address format.
 | ||||||
|  |     fn is_valid_mac(mac: &str) -> bool { | ||||||
|  |         let parts: Vec<&str> = mac.split(':').collect(); | ||||||
|  |         if parts.len() != 6 { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         parts | ||||||
|  |             .iter() | ||||||
|  |             .all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Retrieves the list of current static mappings by shelling out to `configctl`.
 | ||||||
|  |     /// This provides the real-time state from the running system.
 | ||||||
|  |     pub async fn get_static_mappings(&self) -> Result<Vec<StaticMap>, Error> { | ||||||
|  |         let list_static_output = self | ||||||
|  |             .opnsense_shell | ||||||
|  |             .exec("configctl dhcpd list static") | ||||||
|  |             .await?; | ||||||
|  | 
 | ||||||
|  |         let value: serde_json::Value = serde_json::from_str(&list_static_output) | ||||||
|  |             .unwrap_or_else(|_| panic!("Got invalid json from configctl {list_static_output}")); | ||||||
|  |         let static_maps = value["dhcpd"] | ||||||
|  |             .as_array() | ||||||
|  |             .ok_or(Error::Command(format!( | ||||||
|  |                 "Invalid DHCP data from configctl command, got {list_static_output}" | ||||||
|  |             )))? | ||||||
|  |             .iter() | ||||||
|  |             .map(|entry| StaticMap { | ||||||
|  |                 mac: entry["mac"].as_str().unwrap_or_default().to_string(), | ||||||
|  |                 ipaddr: entry["ipaddr"].as_str().unwrap_or_default().to_string(), | ||||||
|  |                 hostname: entry["hostname"].as_str().unwrap_or_default().to_string(), | ||||||
|  |                 descr: entry["descr"].as_str().map(MaybeString::from), | ||||||
|  |                 ..Default::default() | ||||||
|  |             }) | ||||||
|  |             .collect(); | ||||||
|  | 
 | ||||||
|  |         Ok(static_maps) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn set_pxe_options( | ||||||
|  |         &self, | ||||||
|  |         tftp_ip: Option<String>, | ||||||
|  |         bios_filename: String, | ||||||
|  |         efi_filename: String, | ||||||
|  |         ipxe_filename: String, | ||||||
|  |     ) -> Result<(), DhcpError> { | ||||||
|  |         // As of writing this opnsense does not support negative tags, and the dnsmasq config is a
 | ||||||
|  |         // bit complicated anyways. So we are writing directly a dnsmasq config file to
 | ||||||
|  |         // /usr/local/etc/dnsmasq.conf.d
 | ||||||
|  |         let tftp_str = tftp_ip.map_or(String::new(), |i| format!(",{i},{i}")); | ||||||
|  | 
 | ||||||
|  |         let config = format!( | ||||||
|  |             " | ||||||
|  | # Add tag ipxe to dhcp requests with user class (77) = iPXE | ||||||
|  | dhcp-match=set:ipxe,77,iPXE | ||||||
|  | # Add tag bios to dhcp requests with arch (93) = 0 | ||||||
|  | dhcp-match=set:bios,93,0 | ||||||
|  | # Add tag efi to dhcp requests with arch (93) = 7 | ||||||
|  | dhcp-match=set:efi,93,7 | ||||||
|  | 
 | ||||||
|  | # Provide ipxe efi file to uefi but NOT ipxe clients | ||||||
|  | dhcp-boot=tag:efi,tag:!ipxe,{efi_filename}{tftp_str} | ||||||
|  | 
 | ||||||
|  | # Provide ipxe boot script to ipxe clients | ||||||
|  | dhcp-boot=tag:ipxe,{ipxe_filename}{tftp_str} | ||||||
|  | 
 | ||||||
|  | # Provide undionly to legacy bios clients | ||||||
|  | dhcp-boot=tag:bios,{bios_filename}{tftp_str} | ||||||
|  | " | ||||||
|  |         ); | ||||||
|  |         info!("Writing configuration file to {DNS_MASQ_PXE_CONFIG_FILE}"); | ||||||
|  |         debug!("Content:\n{config}"); | ||||||
|  |         self.opnsense_shell | ||||||
|  |             .write_content_to_file(&config, DNS_MASQ_PXE_CONFIG_FILE) | ||||||
|  |             .await | ||||||
|  |             .map_err(|e| { | ||||||
|  |                 DhcpError::Configuration(format!( | ||||||
|  |                     "Could not configure pxe for dhcp because of : {e}" | ||||||
|  |                 )) | ||||||
|  |             })?; | ||||||
|  | 
 | ||||||
|  |         info!("Restarting dnsmasq to apply changes"); | ||||||
|  |         self.opnsense_shell | ||||||
|  |             .exec("configctl dnsmasq restart") | ||||||
|  |             .await | ||||||
|  |             .map_err(|e| DhcpError::Configuration(format!("Restarting dnsmasq failed : {e}")))?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,5 +1,7 @@ | |||||||
| pub mod caddy; | pub mod caddy; | ||||||
| pub mod dhcp; | pub mod dhcp; | ||||||
|  | pub mod dhcp_legacy; | ||||||
| pub mod dns; | pub mod dns; | ||||||
|  | pub mod dnsmasq; | ||||||
| pub mod load_balancer; | pub mod load_balancer; | ||||||
| pub mod tftp; | pub mod tftp; | ||||||
|  | |||||||
| @ -0,0 +1,896 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <opnsense> | ||||||
|  |   <theme>opnsense</theme> | ||||||
|  |   <sysctl> | ||||||
|  |     <item/> | ||||||
|  |   </sysctl> | ||||||
|  |   <system> | ||||||
|  |     <serialspeed>115200</serialspeed> | ||||||
|  |     <primaryconsole>serial</primaryconsole> | ||||||
|  |     <optimization>normal</optimization> | ||||||
|  |     <hostname>OPNsense</hostname> | ||||||
|  |     <domain>testpxe.harmony.mcd</domain> | ||||||
|  |     <group> | ||||||
|  |       <name>admins</name> | ||||||
|  |       <description>System Administrators</description> | ||||||
|  |       <scope>system</scope> | ||||||
|  |       <gid>1999</gid> | ||||||
|  |       <member>0</member> | ||||||
|  |       <priv>page-all</priv> | ||||||
|  |       <source_networks/> | ||||||
|  |     </group> | ||||||
|  |     <user> | ||||||
|  |       <name>root</name> | ||||||
|  |       <descr>System Administrator</descr> | ||||||
|  |       <scope>system</scope> | ||||||
|  |       <password>$2y$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS</password> | ||||||
|  |       <pwd_changed_at/> | ||||||
|  |       <uid>0</uid> | ||||||
|  |       <disabled>0</disabled> | ||||||
|  |       <landing_page/> | ||||||
|  |       <comment/> | ||||||
|  |       <email/> | ||||||
|  |       <apikeys/> | ||||||
|  |       <priv/> | ||||||
|  |       <language/> | ||||||
|  |       <expires/> | ||||||
|  |       <authorizedkeys/> | ||||||
|  |       <dashboard/> | ||||||
|  |       <otp_seed/> | ||||||
|  |       <shell/> | ||||||
|  |     </user> | ||||||
|  |     <timezone>Etc/UTC</timezone> | ||||||
|  |     <timeservers>0.opnsense.pool.ntp.org 1.opnsense.pool.ntp.org 2.opnsense.pool.ntp.org 3.opnsense.pool.ntp.org</timeservers> | ||||||
|  |     <webgui> | ||||||
|  |       <protocol>https</protocol> | ||||||
|  |       <ssl-certref>68a72b6f7f776</ssl-certref> | ||||||
|  |       <port/> | ||||||
|  |       <ssl-ciphers/> | ||||||
|  |       <interfaces/> | ||||||
|  |       <compression/> | ||||||
|  |     </webgui> | ||||||
|  |     <usevirtualterminal>1</usevirtualterminal> | ||||||
|  |     <disablenatreflection>yes</disablenatreflection> | ||||||
|  |     <disableconsolemenu>1</disableconsolemenu> | ||||||
|  |     <disablevlanhwfilter>1</disablevlanhwfilter> | ||||||
|  |     <disablechecksumoffloading>1</disablechecksumoffloading> | ||||||
|  |     <disablesegmentationoffloading>1</disablesegmentationoffloading> | ||||||
|  |     <disablelargereceiveoffloading>1</disablelargereceiveoffloading> | ||||||
|  |     <ipv6allow>1</ipv6allow> | ||||||
|  |     <powerd_ac_mode>hadp</powerd_ac_mode> | ||||||
|  |     <powerd_battery_mode>hadp</powerd_battery_mode> | ||||||
|  |     <powerd_normal_mode>hadp</powerd_normal_mode> | ||||||
|  |     <bogons> | ||||||
|  |       <interval>monthly</interval> | ||||||
|  |     </bogons> | ||||||
|  |     <pf_share_forward>1</pf_share_forward> | ||||||
|  |     <lb_use_sticky>1</lb_use_sticky> | ||||||
|  |     <ssh> | ||||||
|  |       <group>admins</group> | ||||||
|  |       <noauto>1</noauto> | ||||||
|  |       <interfaces/> | ||||||
|  |       <kex/> | ||||||
|  |       <ciphers/> | ||||||
|  |       <macs/> | ||||||
|  |       <keys/> | ||||||
|  |       <enabled>enabled</enabled> | ||||||
|  |       <passwordauth>1</passwordauth> | ||||||
|  |       <keysig/> | ||||||
|  |       <permitrootlogin>1</permitrootlogin> | ||||||
|  |       <rekeylimit/> | ||||||
|  |     </ssh> | ||||||
|  |     <rrdbackup>-1</rrdbackup> | ||||||
|  |     <netflowbackup>-1</netflowbackup> | ||||||
|  |     <firmware version="1.0.1"> | ||||||
|  |       <mirror/> | ||||||
|  |       <flavour/> | ||||||
|  |       <plugins>os-tftp</plugins> | ||||||
|  |       <type/> | ||||||
|  |       <subscription/> | ||||||
|  |       <reboot>0</reboot> | ||||||
|  |     </firmware> | ||||||
|  |     <language>en_US</language> | ||||||
|  |     <dnsserver/> | ||||||
|  |     <dnsallowoverride>1</dnsallowoverride> | ||||||
|  |     <dnsallowoverride_exclude/> | ||||||
|  |   </system> | ||||||
|  |   <interfaces> | ||||||
|  |     <wan> | ||||||
|  |       <if>vtnet0</if> | ||||||
|  |       <mtu/> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <spoofmac/> | ||||||
|  |       <mss/> | ||||||
|  |       <ipaddr>dhcp</ipaddr> | ||||||
|  |       <dhcphostname/> | ||||||
|  |       <blockpriv>0</blockpriv> | ||||||
|  |       <blockbogons>1</blockbogons> | ||||||
|  |       <subnet/> | ||||||
|  |       <ipaddrv6>dhcp6</ipaddrv6> | ||||||
|  |       <dhcp6-ia-pd-len>0</dhcp6-ia-pd-len> | ||||||
|  |       <gateway/> | ||||||
|  |       <media/> | ||||||
|  |       <mediaopt/> | ||||||
|  |     </wan> | ||||||
|  |     <lan> | ||||||
|  |       <if>vtnet1</if> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <ipaddr>192.168.1.1</ipaddr> | ||||||
|  |       <subnet>24</subnet> | ||||||
|  |       <ipaddrv6>track6</ipaddrv6> | ||||||
|  |       <subnetv6>64</subnetv6> | ||||||
|  |       <media/> | ||||||
|  |       <mediaopt/> | ||||||
|  |       <track6-interface>wan</track6-interface> | ||||||
|  |       <track6-prefix-id>0</track6-prefix-id> | ||||||
|  |     </lan> | ||||||
|  |     <lo0> | ||||||
|  |       <internal_dynamic>1</internal_dynamic> | ||||||
|  |       <if>lo0</if> | ||||||
|  |       <descr>Loopback</descr> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <ipaddr>127.0.0.1</ipaddr> | ||||||
|  |       <type>none</type> | ||||||
|  |       <virtual>1</virtual> | ||||||
|  |       <subnet>8</subnet> | ||||||
|  |       <ipaddrv6>::1</ipaddrv6> | ||||||
|  |       <subnetv6>128</subnetv6> | ||||||
|  |     </lo0> | ||||||
|  |   </interfaces> | ||||||
|  |   <dhcpd/> | ||||||
|  |   <snmpd> | ||||||
|  |     <syslocation/> | ||||||
|  |     <syscontact/> | ||||||
|  |     <rocommunity>public</rocommunity> | ||||||
|  |   </snmpd> | ||||||
|  |   <syslog/> | ||||||
|  |   <nat> | ||||||
|  |     <outbound> | ||||||
|  |       <mode>automatic</mode> | ||||||
|  |     </outbound> | ||||||
|  |   </nat> | ||||||
|  |   <filter> | ||||||
|  |     <rule> | ||||||
|  |       <type>pass</type> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <ipprotocol>inet</ipprotocol> | ||||||
|  |       <descr>Default allow LAN to any rule</descr> | ||||||
|  |       <source> | ||||||
|  |         <network>lan</network> | ||||||
|  |       </source> | ||||||
|  |       <destination> | ||||||
|  |         <any/> | ||||||
|  |       </destination> | ||||||
|  |     </rule> | ||||||
|  |     <rule> | ||||||
|  |       <type>pass</type> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <ipprotocol>inet6</ipprotocol> | ||||||
|  |       <descr>Default allow LAN IPv6 to any rule</descr> | ||||||
|  |       <source> | ||||||
|  |         <network>lan</network> | ||||||
|  |       </source> | ||||||
|  |       <destination> | ||||||
|  |         <any/> | ||||||
|  |       </destination> | ||||||
|  |     </rule> | ||||||
|  |   </filter> | ||||||
|  |   <rrd> | ||||||
|  |     <enable/> | ||||||
|  |   </rrd> | ||||||
|  |   <ntpd> | ||||||
|  |     <prefer>0.opnsense.pool.ntp.org</prefer> | ||||||
|  |   </ntpd> | ||||||
|  |   <revision> | ||||||
|  |     <username>root@192.168.1.5</username> | ||||||
|  |     <description>/api/dnsmasq/settings/set made changes</description> | ||||||
|  |     <time>1755800176.40</time> | ||||||
|  |   </revision> | ||||||
|  |   <OPNsense> | ||||||
|  |     <captiveportal version="1.0.4"> | ||||||
|  |       <zones/> | ||||||
|  |       <templates/> | ||||||
|  |     </captiveportal> | ||||||
|  |     <cron version="1.0.4"> | ||||||
|  |       <jobs/> | ||||||
|  |     </cron> | ||||||
|  |     <Netflow version="1.0.1"> | ||||||
|  |       <capture> | ||||||
|  |         <interfaces/> | ||||||
|  |         <egress_only/> | ||||||
|  |         <version>v9</version> | ||||||
|  |         <targets/> | ||||||
|  |       </capture> | ||||||
|  |       <collect> | ||||||
|  |         <enable>0</enable> | ||||||
|  |       </collect> | ||||||
|  |       <activeTimeout>1800</activeTimeout> | ||||||
|  |       <inactiveTimeout>15</inactiveTimeout> | ||||||
|  |     </Netflow> | ||||||
|  |     <Firewall> | ||||||
|  |       <Lvtemplate version="0.0.1"> | ||||||
|  |         <templates/> | ||||||
|  |       </Lvtemplate> | ||||||
|  |       <Category version="1.0.0"> | ||||||
|  |         <categories/> | ||||||
|  |       </Category> | ||||||
|  |       <Filter version="1.0.4"> | ||||||
|  |         <rules/> | ||||||
|  |         <snatrules/> | ||||||
|  |         <npt/> | ||||||
|  |         <onetoone/> | ||||||
|  |       </Filter> | ||||||
|  |       <Alias version="1.0.1"> | ||||||
|  |         <geoip> | ||||||
|  |           <url/> | ||||||
|  |         </geoip> | ||||||
|  |         <aliases/> | ||||||
|  |       </Alias> | ||||||
|  |     </Firewall> | ||||||
|  |     <IDS version="1.1.0"> | ||||||
|  |       <rules/> | ||||||
|  |       <policies/> | ||||||
|  |       <userDefinedRules/> | ||||||
|  |       <files/> | ||||||
|  |       <fileTags/> | ||||||
|  |       <general> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <ips>0</ips> | ||||||
|  |         <promisc>0</promisc> | ||||||
|  |         <interfaces>wan</interfaces> | ||||||
|  |         <homenet>192.168.0.0/16,10.0.0.0/8,172.16.0.0/12</homenet> | ||||||
|  |         <defaultPacketSize/> | ||||||
|  |         <UpdateCron/> | ||||||
|  |         <AlertLogrotate>W0D23</AlertLogrotate> | ||||||
|  |         <AlertSaveLogs>4</AlertSaveLogs> | ||||||
|  |         <MPMAlgo/> | ||||||
|  |         <detect> | ||||||
|  |           <Profile/> | ||||||
|  |           <toclient_groups/> | ||||||
|  |           <toserver_groups/> | ||||||
|  |         </detect> | ||||||
|  |         <syslog>0</syslog> | ||||||
|  |         <syslog_eve>0</syslog_eve> | ||||||
|  |         <LogPayload>0</LogPayload> | ||||||
|  |         <verbosity/> | ||||||
|  |         <eveLog> | ||||||
|  |           <http> | ||||||
|  |             <enable>0</enable> | ||||||
|  |             <extended>0</extended> | ||||||
|  |             <dumpAllHeaders/> | ||||||
|  |           </http> | ||||||
|  |           <tls> | ||||||
|  |             <enable>0</enable> | ||||||
|  |             <extended>0</extended> | ||||||
|  |             <sessionResumption>0</sessionResumption> | ||||||
|  |             <custom/> | ||||||
|  |           </tls> | ||||||
|  |         </eveLog> | ||||||
|  |       </general> | ||||||
|  |     </IDS> | ||||||
|  |     <IPsec version="1.0.4"> | ||||||
|  |       <general> | ||||||
|  |         <enabled/> | ||||||
|  |         <preferred_oldsa>0</preferred_oldsa> | ||||||
|  |         <disablevpnrules>0</disablevpnrules> | ||||||
|  |         <passthrough_networks/> | ||||||
|  |         <user_source/> | ||||||
|  |         <local_group/> | ||||||
|  |       </general> | ||||||
|  |       <keyPairs/> | ||||||
|  |       <preSharedKeys/> | ||||||
|  |       <charon> | ||||||
|  |         <max_ikev1_exchanges/> | ||||||
|  |         <threads>16</threads> | ||||||
|  |         <ikesa_table_size>32</ikesa_table_size> | ||||||
|  |         <ikesa_table_segments>4</ikesa_table_segments> | ||||||
|  |         <init_limit_half_open>1000</init_limit_half_open> | ||||||
|  |         <ignore_acquire_ts>1</ignore_acquire_ts> | ||||||
|  |         <install_routes>0</install_routes> | ||||||
|  |         <cisco_unity>0</cisco_unity> | ||||||
|  |         <make_before_break>0</make_before_break> | ||||||
|  |         <retransmit_tries/> | ||||||
|  |         <retransmit_timeout/> | ||||||
|  |         <retransmit_base/> | ||||||
|  |         <retransmit_jitter/> | ||||||
|  |         <retransmit_limit/> | ||||||
|  |         <syslog> | ||||||
|  |           <daemon> | ||||||
|  |             <ike_name>1</ike_name> | ||||||
|  |             <log_level>0</log_level> | ||||||
|  |             <app>1</app> | ||||||
|  |             <asn>1</asn> | ||||||
|  |             <cfg>1</cfg> | ||||||
|  |             <chd>1</chd> | ||||||
|  |             <dmn>1</dmn> | ||||||
|  |             <enc>1</enc> | ||||||
|  |             <esp>1</esp> | ||||||
|  |             <ike>1</ike> | ||||||
|  |             <imc>1</imc> | ||||||
|  |             <imv>1</imv> | ||||||
|  |             <job>1</job> | ||||||
|  |             <knl>1</knl> | ||||||
|  |             <lib>1</lib> | ||||||
|  |             <mgr>1</mgr> | ||||||
|  |             <net>1</net> | ||||||
|  |             <pts>1</pts> | ||||||
|  |             <tls>1</tls> | ||||||
|  |             <tnc>1</tnc> | ||||||
|  |           </daemon> | ||||||
|  |         </syslog> | ||||||
|  |         <plugins> | ||||||
|  |           <attr> | ||||||
|  |             <subnet/> | ||||||
|  |             <split-include/> | ||||||
|  |             <x_28674/> | ||||||
|  |             <x_28675/> | ||||||
|  |             <x_28672/> | ||||||
|  |             <x_28673>0</x_28673> | ||||||
|  |             <x_28679/> | ||||||
|  |             <dns/> | ||||||
|  |             <nbns/> | ||||||
|  |           </attr> | ||||||
|  |           <eap-radius> | ||||||
|  |             <servers/> | ||||||
|  |             <accounting>0</accounting> | ||||||
|  |             <class_group>0</class_group> | ||||||
|  |           </eap-radius> | ||||||
|  |           <xauth-pam> | ||||||
|  |             <pam_service>ipsec</pam_service> | ||||||
|  |             <session>0</session> | ||||||
|  |             <trim_email>1</trim_email> | ||||||
|  |           </xauth-pam> | ||||||
|  |         </plugins> | ||||||
|  |       </charon> | ||||||
|  |     </IPsec> | ||||||
|  |     <Interfaces> | ||||||
|  |       <vxlans version="1.0.2"/> | ||||||
|  |       <loopbacks version="1.0.0"/> | ||||||
|  |       <neighbors version="1.0.0"/> | ||||||
|  |     </Interfaces> | ||||||
|  |     <Kea> | ||||||
|  |       <dhcp4 version="1.0.4" persisted_at="1755786069.95"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <manual_config>0</manual_config> | ||||||
|  |           <interfaces/> | ||||||
|  |           <valid_lifetime>4000</valid_lifetime> | ||||||
|  |           <fwrules>1</fwrules> | ||||||
|  |           <dhcp_socket_type>raw</dhcp_socket_type> | ||||||
|  |         </general> | ||||||
|  |         <ha> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <this_server_name/> | ||||||
|  |           <max_unacked_clients>2</max_unacked_clients> | ||||||
|  |         </ha> | ||||||
|  |         <subnets/> | ||||||
|  |         <reservations/> | ||||||
|  |         <ha_peers/> | ||||||
|  |       </dhcp4> | ||||||
|  |       <ctrl_agent version="0.0.1" persisted_at="1755786069.95"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <http_host>127.0.0.1</http_host> | ||||||
|  |           <http_port>8000</http_port> | ||||||
|  |         </general> | ||||||
|  |       </ctrl_agent> | ||||||
|  |       <dhcp6 version="1.0.0" persisted_at="1755786069.95"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <manual_config>0</manual_config> | ||||||
|  |           <interfaces/> | ||||||
|  |           <valid_lifetime>4000</valid_lifetime> | ||||||
|  |           <fwrules>1</fwrules> | ||||||
|  |         </general> | ||||||
|  |         <ha> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <this_server_name/> | ||||||
|  |           <max_unacked_clients>2</max_unacked_clients> | ||||||
|  |         </ha> | ||||||
|  |         <subnets/> | ||||||
|  |         <reservations/> | ||||||
|  |         <pd_pools/> | ||||||
|  |         <ha_peers/> | ||||||
|  |       </dhcp6> | ||||||
|  |     </Kea> | ||||||
|  |     <monit version="1.0.13"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <interval>120</interval> | ||||||
|  |         <startdelay>120</startdelay> | ||||||
|  |         <mailserver>127.0.0.1</mailserver> | ||||||
|  |         <port>25</port> | ||||||
|  |         <username/> | ||||||
|  |         <password/> | ||||||
|  |         <ssl>0</ssl> | ||||||
|  |         <sslversion>auto</sslversion> | ||||||
|  |         <sslverify>1</sslverify> | ||||||
|  |         <logfile/> | ||||||
|  |         <statefile/> | ||||||
|  |         <eventqueuePath/> | ||||||
|  |         <eventqueueSlots/> | ||||||
|  |         <httpdEnabled>0</httpdEnabled> | ||||||
|  |         <httpdUsername>root</httpdUsername> | ||||||
|  |         <httpdPassword/> | ||||||
|  |         <httpdPort>2812</httpdPort> | ||||||
|  |         <httpdAllow/> | ||||||
|  |         <mmonitUrl/> | ||||||
|  |         <mmonitTimeout>5</mmonitTimeout> | ||||||
|  |         <mmonitRegisterCredentials>1</mmonitRegisterCredentials> | ||||||
|  |       </general> | ||||||
|  |       <alert uuid="ce8ca7d9-66ab-41d5-acea-598f4803e8ba"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <recipient>root@localhost.local</recipient> | ||||||
|  |         <noton>0</noton> | ||||||
|  |         <events/> | ||||||
|  |         <format/> | ||||||
|  |         <reminder/> | ||||||
|  |         <description/> | ||||||
|  |       </alert> | ||||||
|  |       <service uuid="dc3b9298-4a56-4c45-bd61-be2fdb103383"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <name>$HOST</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>system</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path/> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>cfed35dc-f74b-417d-9ed9-682c5de96495,f961277a-07f1-49a4-90ee-bb15738d9ebb,30b2cce2-f650-4e44-a3e2-ee53886cda3f,3c86136f-35a4-4126-865b-82732c6542d9</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="b4d5bdb4-206d-4afe-8d86-377ffbbdb2ec"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <name>RootFs</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>filesystem</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>fbb8dfe2-b9ad-4730-a0f3-41d7ecda6289</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="f96e3cbb-6c98-4d20-8337-bab717d4ab54"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <name>carp_status_change</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>custom</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/usr/local/opnsense/scripts/OPNsense/Monit/carp_status</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>11ceca8a-dff8-45e0-9dc5-ed80dc4b3947</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="69bbd4d5-3a50-42a7-ab64-050450504038"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <name>gateway_alert</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>custom</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>fad1f465-4a92-4b93-be66-59d7059b8779</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <test uuid="2bd5d8c0-6a4a-430b-b953-34214a107ccf"> | ||||||
|  |         <name>Ping</name> | ||||||
|  |         <type>NetworkPing</type> | ||||||
|  |         <condition>failed ping</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="0f06ffff-9bfa-463d-b75e-f7195cd8dcab"> | ||||||
|  |         <name>NetworkLink</name> | ||||||
|  |         <type>NetworkInterface</type> | ||||||
|  |         <condition>failed link</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="79b119ce-10e0-4a6a-bd1a-b0be371d0fd7"> | ||||||
|  |         <name>NetworkSaturation</name> | ||||||
|  |         <type>NetworkInterface</type> | ||||||
|  |         <condition>saturation is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="cfed35dc-f74b-417d-9ed9-682c5de96495"> | ||||||
|  |         <name>MemoryUsage</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>memory usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="f961277a-07f1-49a4-90ee-bb15738d9ebb"> | ||||||
|  |         <name>CPUUsage</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>cpu usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="30b2cce2-f650-4e44-a3e2-ee53886cda3f"> | ||||||
|  |         <name>LoadAvg1</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (1min) is greater than 4</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="3c86136f-35a4-4126-865b-82732c6542d9"> | ||||||
|  |         <name>LoadAvg5</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (5min) is greater than 3</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="27e91c6f-3e8e-4570-bb3a-27f46dd301a7"> | ||||||
|  |         <name>LoadAvg15</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (15min) is greater than 2</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="fbb8dfe2-b9ad-4730-a0f3-41d7ecda6289"> | ||||||
|  |         <name>SpaceUsage</name> | ||||||
|  |         <type>SpaceUsage</type> | ||||||
|  |         <condition>space usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="11ceca8a-dff8-45e0-9dc5-ed80dc4b3947"> | ||||||
|  |         <name>ChangedStatus</name> | ||||||
|  |         <type>ProgramStatus</type> | ||||||
|  |         <condition>changed status</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="fad1f465-4a92-4b93-be66-59d7059b8779"> | ||||||
|  |         <name>NonZeroStatus</name> | ||||||
|  |         <type>ProgramStatus</type> | ||||||
|  |         <condition>status != 0</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |     </monit> | ||||||
|  |     <OpenVPNExport version="0.0.1"> | ||||||
|  |       <servers/> | ||||||
|  |     </OpenVPNExport> | ||||||
|  |     <Syslog version="1.0.2"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <loglocal>1</loglocal> | ||||||
|  |         <maxpreserve>31</maxpreserve> | ||||||
|  |         <maxfilesize/> | ||||||
|  |       </general> | ||||||
|  |       <destinations/> | ||||||
|  |     </Syslog> | ||||||
|  |     <TrafficShaper version="1.0.3" persisted_at="1755786069.77"> | ||||||
|  |       <pipes/> | ||||||
|  |       <queues/> | ||||||
|  |       <rules/> | ||||||
|  |     </TrafficShaper> | ||||||
|  |     <unboundplus version="1.0.12"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <port>53</port> | ||||||
|  |         <stats>0</stats> | ||||||
|  |         <active_interface/> | ||||||
|  |         <dnssec>0</dnssec> | ||||||
|  |         <dns64>0</dns64> | ||||||
|  |         <dns64prefix/> | ||||||
|  |         <noarecords>0</noarecords> | ||||||
|  |         <regdhcp>0</regdhcp> | ||||||
|  |         <regdhcpdomain/> | ||||||
|  |         <regdhcpstatic>0</regdhcpstatic> | ||||||
|  |         <noreglladdr6>0</noreglladdr6> | ||||||
|  |         <noregrecords>0</noregrecords> | ||||||
|  |         <txtsupport>0</txtsupport> | ||||||
|  |         <cacheflush>0</cacheflush> | ||||||
|  |         <local_zone_type>transparent</local_zone_type> | ||||||
|  |         <outgoing_interface/> | ||||||
|  |         <enable_wpad>0</enable_wpad> | ||||||
|  |       </general> | ||||||
|  |       <advanced> | ||||||
|  |         <hideidentity>0</hideidentity> | ||||||
|  |         <hideversion>0</hideversion> | ||||||
|  |         <prefetch>0</prefetch> | ||||||
|  |         <prefetchkey>0</prefetchkey> | ||||||
|  |         <dnssecstripped>0</dnssecstripped> | ||||||
|  |         <aggressivensec>1</aggressivensec> | ||||||
|  |         <serveexpired>0</serveexpired> | ||||||
|  |         <serveexpiredreplyttl/> | ||||||
|  |         <serveexpiredttl/> | ||||||
|  |         <serveexpiredttlreset>0</serveexpiredttlreset> | ||||||
|  |         <serveexpiredclienttimeout/> | ||||||
|  |         <qnameminstrict>0</qnameminstrict> | ||||||
|  |         <extendedstatistics>0</extendedstatistics> | ||||||
|  |         <logqueries>0</logqueries> | ||||||
|  |         <logreplies>0</logreplies> | ||||||
|  |         <logtagqueryreply>0</logtagqueryreply> | ||||||
|  |         <logservfail>0</logservfail> | ||||||
|  |         <loglocalactions>0</loglocalactions> | ||||||
|  |         <logverbosity>1</logverbosity> | ||||||
|  |         <valloglevel>0</valloglevel> | ||||||
|  |         <privatedomain/> | ||||||
|  |         <privateaddress>0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,169.254.0.0/16,172.16.0.0/12,192.0.2.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,233.252.0.0/24,::1/128,2001:db8::/32,fc00::/8,fd00::/8,fe80::/10</privateaddress> | ||||||
|  |         <insecuredomain/> | ||||||
|  |         <msgcachesize/> | ||||||
|  |         <rrsetcachesize/> | ||||||
|  |         <outgoingnumtcp/> | ||||||
|  |         <incomingnumtcp/> | ||||||
|  |         <numqueriesperthread/> | ||||||
|  |         <outgoingrange/> | ||||||
|  |         <jostletimeout/> | ||||||
|  |         <discardtimeout/> | ||||||
|  |         <cachemaxttl/> | ||||||
|  |         <cachemaxnegativettl/> | ||||||
|  |         <cacheminttl/> | ||||||
|  |         <infrahostttl/> | ||||||
|  |         <infrakeepprobing>0</infrakeepprobing> | ||||||
|  |         <infracachenumhosts/> | ||||||
|  |         <unwantedreplythreshold/> | ||||||
|  |       </advanced> | ||||||
|  |       <acls> | ||||||
|  |         <default_action>allow</default_action> | ||||||
|  |       </acls> | ||||||
|  |       <dnsbl> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <safesearch>0</safesearch> | ||||||
|  |         <type/> | ||||||
|  |         <lists/> | ||||||
|  |         <whitelists/> | ||||||
|  |         <blocklists/> | ||||||
|  |         <wildcards/> | ||||||
|  |         <address/> | ||||||
|  |         <nxdomain>0</nxdomain> | ||||||
|  |       </dnsbl> | ||||||
|  |       <forwarding> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |       </forwarding> | ||||||
|  |       <dots/> | ||||||
|  |       <hosts/> | ||||||
|  |       <aliases/> | ||||||
|  |     </unboundplus> | ||||||
|  |     <DHCRelay version="1.0.1" persisted_at="1755786069.97"/> | ||||||
|  |     <trust> | ||||||
|  |       <general version="1.0.1" persisted_at="1755786070.08"> | ||||||
|  |         <store_intermediate_certs>0</store_intermediate_certs> | ||||||
|  |         <install_crls>0</install_crls> | ||||||
|  |         <fetch_crls>0</fetch_crls> | ||||||
|  |         <enable_legacy_sect>1</enable_legacy_sect> | ||||||
|  |         <enable_config_constraints>0</enable_config_constraints> | ||||||
|  |         <CipherString/> | ||||||
|  |         <Ciphersuites/> | ||||||
|  |         <SignatureAlgorithms/> | ||||||
|  |         <groups/> | ||||||
|  |         <MinProtocol/> | ||||||
|  |         <MinProtocol_DTLS/> | ||||||
|  |       </general> | ||||||
|  |     </trust> | ||||||
|  |     <tftp> | ||||||
|  |       <general version="0.0.1"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <listen>192.168.1.1</listen> | ||||||
|  |       </general> | ||||||
|  |     </tftp> | ||||||
|  |     <wireguard> | ||||||
|  |       <general version="0.0.1"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |       </general> | ||||||
|  |       <server version="1.0.0"> | ||||||
|  |         <servers/> | ||||||
|  |       </server> | ||||||
|  |       <client version="1.0.0"> | ||||||
|  |         <clients/> | ||||||
|  |       </client> | ||||||
|  |     </wireguard> | ||||||
|  |     <Swanctl version="1.0.0"> | ||||||
|  |       <Connections/> | ||||||
|  |       <locals/> | ||||||
|  |       <remotes/> | ||||||
|  |       <children/> | ||||||
|  |       <Pools/> | ||||||
|  |       <VTIs/> | ||||||
|  |       <SPDs/> | ||||||
|  |     </Swanctl> | ||||||
|  |     <OpenVPN version="1.0.1"> | ||||||
|  |       <Overwrites/> | ||||||
|  |       <Instances/> | ||||||
|  |       <StaticKeys/> | ||||||
|  |     </OpenVPN> | ||||||
|  |     <Gateways version="1.0.0" persisted_at="1755786217.76"/> | ||||||
|  |   </OPNsense> | ||||||
|  |   <staticroutes version="1.0.0"/> | ||||||
|  |   <ca/> | ||||||
|  |   <cert> | ||||||
|  |     <refid>68a72b6f7f776</refid> | ||||||
|  |     <descr>Web GUI TLS certificate</descr> | ||||||
|  |     <crt>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQlBxZ0F3SUJBZ0lVUkZqWUQ0Z1U0bzRNZGdiN2pIc29KNU9GVGFnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhHakFZQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVgpNQk1HQTFVRUNBd01XblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyCkJnTlZCQW9NSkU5UVRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWlRBZUZ3MHkKTlRBNE1qRXhOREl4TXpaYUZ3MHlOakE1TWpJeE5ESXhNelphTUlHR01Sb3dHQVlEVlFRRERCRlBVRTV6Wlc1egpaUzVwYm5SbGNtNWhiREVMTUFrR0ExVUVCaE1DVGt3eEZUQVRCZ05WQkFnTURGcDFhV1F0U0c5c2JHRnVaREVWCk1CTUdBMVVFQnd3TVRXbGtaR1ZzYUdGeWJtbHpNUzB3S3dZRFZRUUtEQ1JQVUU1elpXNXpaU0J6Wld4bUxYTnAKWjI1bFpDQjNaV0lnWTJWeWRHbG1hV05oZEdVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFDbENkeFJ3ZWJQQkxvYlVORnYvL2t3TEdKWExweDl6OFFHV2lyWTNpamVDeUxDQ0FwczBLaE1adTNRClhkczMranppbDRnSE96L0hvUEo5Z0xxMy9FYnR4cE9ENWkvQzZwbXc3SGM1M2tTQ3JCK2tlWUFnVWZ1aDU3MzAKZyt3cGc5RDQzaHFBNzF1L3F0ZC95eitnTVJnTWdZMndEK3ZWQWRrdGxVSWlmN2piTmR1RDRGMmdkL0gwbzljWApEUm5zMzNNQVptTkZwajN4QWFwQi9RWnhKV1JMZ1J5K1A5MWcyZEZFNzhNaWY4ZTRNSCtrU29ndzIwVG1JbmpzCitKdEVTc0xQZmx2eUZUa0lkTVdFbURWOG1HUk5hNXVoYXlEbVNEUU9xV0NUTlZHV3ZVWjZTRnJRZ1Q1MDBEdXgKWnRtYlhGdEVqRzlIaGd5SW5QT0dKbWYzTWVzS3dYclVNMW1BenVCRVBFR0lwOTc3UTY2SitpTDYzWTUvTTB3aAphMGVVNGppNTVRQnJOQjlaWjJsa080bGU2TXdmZm50c29JakMrVDh5RW5tbW5nQTlTdWNPRW9CcFFhd3cvRGhOCmtSNGk4TUptR1JNdmpLazlHVzZ3Z2VNVThJVDhKZDRjTmJOVzdFSGpzV08xN1luTVhIMEUxOVZqa2d1R3dIODAKZ3ZROGtzTmV4WVA3WWo0b0VycnRKbWVhWU8wbFVkV0tGektNdS8va0UvNG5HK0h4emlRUnA5QmdyTURNYks4ZgpkM29mY2tqZFZTTW9Vc1FJaWlmdTFMK1I4V1Y3K3hsTzdTWS80dGk3Y28zcjNXRTYyVlE4Vk9QMVphcStWRFpvClNIMVRCa0lTSU5paVJFRzhZSDQvRHJwNWZ2dHBPcERBRGN1TGdDNDJHcExmS1pwVEtRSURBUUFCbzRJQmREQ0MKQVhBd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0JrQXdOQVlKWUlaSUFZYjRRZ0VOQkNjVwpKVTlRVG5ObGJuTmxJRWRsYm1WeVlYUmxaQ0JUWlhKMlpYSWdRMlZ5ZEdsbWFXTmhkR1V3SFFZRFZSME9CQllFCkZIdUVQK05yYlorZWdMdWZVSUFKaUo2M1c4SDFNSUd3QmdOVkhTTUVnYWd3Z2FXaGdZeWtnWWt3Z1lZeEdqQVkKQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVk1CTUdBMVVFQ0F3TQpXblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyQmdOVkJBb01KRTlRClRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWllJVVJGallENGdVNG80TWRnYjcKakhzb0o1T0ZUYWd3SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01Bc0dBMVVkRHdRRQpBd0lGb0RBY0JnTlZIUkVFRlRBVGdoRlBVRTV6Wlc1elpTNXBiblJsY201aGJEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBZ0VBV2JzM2MwSXYwcEd3Y0wvUmRlbnBiZVJHQ3FsODY0V1ZITEtMZzJIR3BkKytJdmRFcHJEZkZ3SCsKdHdOd2VrZTlXUEtYa20vUkZDWE5DQmVLNjkxeURVWCtCNUJOMjMvSks5N1lzRVdtMURIV3FvSDE1WmdqelZ0QQp2d2JmbnRQdlhCWU1wV2ZQY0Zua0hjN3pxUjI3RzBEZHFUeGg2TjhFenV1S3JRWXFtaWhJUXFkNU9HRVhteW9ZCmdPVjdoZ0lWSUR6a1Z0QkRiS3dFV3VFN2pKYzViMXR4Mk1FUFRsVklEZGo0Zm5vdURWemdkczA2RER4aFM4eXAKbXJOSXhxb045ekEzYXVtTnRNZ2haSHVZRHdjbm5GSnBNZHlJSEdHZ1dlNnZZNHFtdEFSVDd3a0x6MTZnUG9LMAo5bFhVU0RmV3YwUDJGUXFHZTJjaXQ3VVE2ZGtsUWsrVGVtUEFwNnhEV09HR3oxRkdmUUoxN040b3AvOGtlOUo2Cm96RVp3QTh1aDVYTUl2N3loM2dobjV1d1R6RDUyZ1BBZFdaekEyaHVWV3p5cVM0WVc0N3ZkaGV6TTFTUndabVEKUmYzNDk0UVFydWd0bzdycWdMUlRTSXN4WEtkU21MaHZjT0hsSlhISW1XNTRzeFlXNm9NUStpRExFT29ZVVdpcgp1aUJvT1RsNEJaOG5Xcm9pV0JtWlFLaVRPYlFRczVWTkIwYnVybmRISTJVdmtIRTE3QTM0bFYySjY5Q0dNNzJ2CjQ5aE9TN3B2Tzg4cEVKZm90d01YYlRhdkR2WTBHazZxbERFMVBud1U2Wm8ySEprcFdUTUxOSzh1alZ1RkhlMGkKR2JvZi9va08vZW4rUi9PUXNyd1JYbzFwVTRiWnlyWGVQeUdqSSsrdFYzemhjd0IwWjNJPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</crt> | ||||||
|  |     <prv>LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRQ2xDZHhSd2ViUEJMb2IKVU5Gdi8va3dMR0pYTHB4OXo4UUdXaXJZM2lqZUN5TENDQXBzMEtoTVp1M1FYZHMzK2p6aWw0Z0hPei9Ib1BKOQpnTHEzL0VidHhwT0Q1aS9DNnBtdzdIYzUza1NDckIra2VZQWdVZnVoNTczMGcrd3BnOUQ0M2hxQTcxdS9xdGQvCnl6K2dNUmdNZ1kyd0QrdlZBZGt0bFVJaWY3amJOZHVENEYyZ2QvSDBvOWNYRFJuczMzTUFabU5GcGozeEFhcEIKL1FaeEpXUkxnUnkrUDkxZzJkRkU3OE1pZjhlNE1IK2tTb2d3MjBUbUluanMrSnRFU3NMUGZsdnlGVGtJZE1XRQptRFY4bUdSTmE1dWhheURtU0RRT3FXQ1ROVkdXdlVaNlNGclFnVDUwMER1eFp0bWJYRnRFakc5SGhneUluUE9HCkptZjNNZXNLd1hyVU0xbUF6dUJFUEVHSXA5NzdRNjZKK2lMNjNZNS9NMHdoYTBlVTRqaTU1UUJyTkI5WloybGsKTzRsZTZNd2ZmbnRzb0lqQytUOHlFbm1tbmdBOVN1Y09Fb0JwUWF3dy9EaE5rUjRpOE1KbUdSTXZqS2s5R1c2dwpnZU1VOElUOEpkNGNOYk5XN0VIanNXTzE3WW5NWEgwRTE5VmprZ3VHd0g4MGd2UThrc05leFlQN1lqNG9FcnJ0CkptZWFZTzBsVWRXS0Z6S011Ly9rRS80bkcrSHh6aVFScDlCZ3JNRE1iSzhmZDNvZmNramRWU01vVXNRSWlpZnUKMUwrUjhXVjcreGxPN1NZLzR0aTdjbzNyM1dFNjJWUThWT1AxWmFxK1ZEWm9TSDFUQmtJU0lOaWlSRUc4WUg0LwpEcnA1ZnZ0cE9wREFEY3VMZ0M0MkdwTGZLWnBUS1FJREFRQUJBb0lDQUFTSHc4Tit4aDR5ckFVcDc4WGFTZlhYCmtnK0FtUTBmRWV0MnVDeGgxTTlia09Xd29OQ2gzYXpUT24zNHhaYkF5TUVUbGNsVkNBZ3IwOXc4RjJRTGljcm4KSTQrQVZ4bExwVkprKzFUY1ZCY2VNSFFzWGFjRmVSblZxYkkzbU5qKzVGS2dqaXV4NWx2WmpiYlZWbmJJUWplOQpxcTBGa3R5ekEwb3NDYmUydDlWVW9pVDVtTGhaOG90Ym9BRGkvQzR6YUEyL3djUGNyMkNaUWhvem51U21PUjJWCmVydUNOMHA4VURGTFA1a0gxdXlvY0NpTFh6ZXdIVEVRQ3krK0YwMEZuRmxqeDVSYW5za3JvMnhqWFR5QlZtZUYKcDYwRHF0Q0hkTjVlS2VlQWxDL0dIRlFvL2swdzd3ejMxbHVsVGgza3FDQzJsaXRwYzVpZ2JsTGxaUDgxSUpXTQp0bkhlczNsTXk1RGNDWUx3L3huZFdmVDZFMTB4WlhFNWI0QTdxYjF4Yjhsd1FoNHFJckhDZ2p1NDVPYXNCMERJClBYZ3E2eWkwL2FKWXV6SU5kcjRTeFRibExGUkp6MXlQaGZTZDVGbjdWQVBYU1JNTDlDbzJrL0M1SDlwdG1HMjYKZHBLQVNib1ZMcStrbXg3anVKYXc0a1JNNHZmYndHZGNMZEhqMXByZ08xNkd1ckpQOVRRQ0x5YzhaR0xOekcvaApIMzBpU2FlclJOUmtDRlhmeTEzWWJJZTZHTE12KzVmODlYSENGNmZrZ1JkZjVrbTA3cEc3SCtMZytmZFdtd2lZCm0waExNSFVZeHJ3WkFma2tvZjhlSllscEVQVmQ3ZytCVjd2eTZhYW0yQituUTdHYk84WUprSnlJME04amlSaDEKeGdjRmFZaGZlT21RZGtVbi9BcUJBb0lCQVFEU1JZbDl0SnJyQk5UOXlZN0twWTJiOGVURFJqdDByU1NQRUJvNgppeWoyVWR5S1ZSbHFFdzRma2IrejV3WWt2bnBpMW1uS3NjNFlLZmoyaDVSdXVDbzVzTUNLbmpDUXBpbll4bWRFCk45Z3l6SWRYMmlzRUh6dXNSZkZiajBVSWU1dHc0TE9pL3cyVzFYWGJUc3liOFdhTmhkbVQ4TGxDNjQ5WkNNUWQKeDZkeTdOWS9uYnVWVVQ0KzM3WmV0VlR1eDg1ekl5OTdnMWp4dFZhaXZrd2hQVWtLcWpXWDYzaUZidjFuY1FVdgpiQURrWkFoOXRWYWV2UGZ2NURDeDZITldiVFlObjVRZWd3OTRyVndoSjhYb1V5ZDRqWFB0VmdXU2VkN0tWd2U5CmNkNW9CZWFBOVhDdnJxdkNIRjI4QXg2OUI2YWQrQlk1S0dVcGU2LythQnlKdlQwUkFvSUJBUURJN2c3c0dMc3AKVWJ4dGhJQm9yRzF5MDRKWWlnaE5VMlF4YjdzSkxocnRTc2NtRkxSZU5DYy8zOTBCV3ZrbVFIazFnZkpxV3hDLwp2R0VMT0Iwd3U5VFBBRWFsS25IZ2RhNkFTMURuM3NTWTltcFBRRjYvbEY2cm00cDlNUU1TTFo1V3ZsL0ZNRklHCjUvaXVSVjJaOVdkeTV4QVFWNG5uZmdMOWJDNzhPa2k3VnFPTDJDZk0vcEJEMHdzRUZmOGZiejFSZXo4dEFRZ2QKVXY4cEpFTWdDTCtQeEdkdG5DYUcxYm5obXhEUUxiWmQ4TTFOQkpKOWZLZFgzVWtLcTlDdmFJVXBIZlduSFBWVAprVWNwMUVTYnEzOFVhTzFSS1NBNUtQd1ZiNkVPVGJBSGtlaEN4ZVhpN2F3YkZwYXlTelpIaWl4Y05QQjk1YUtSCkpJQ0J5ekFwQTVTWkFvSUJBRlZKYXlrWGxqWjVNVUwyKy9ucUNIUVdPeW1SVlJCUUlpSDg4QWFLNTBSeGs3aHcKSit6RWFkZ1lMOTl5ZHlWME5RUGQzKzhkQzNEMXBVdXBWbVZLUWFaQXNQZ0lqYjQrQjM4cmlqczdRMi9uVVlZcQpzWVBzZnpHeTlPQ2tUZVhRN1ExdHRxOElNS1RiVkFCdUI4UEF1RTN5Mm51TkNqZkFmOVluSGhUT0pIY1M1UnZNCmlJZForcHRaOWdpWUdDajUxaDBSU25NWXBYejBobjFnSGxUbEhMazhySnhBSUJSUEhtMVVoRHZsM0w3R2JFTkEKeUM5K2lqbzlIaHNySTQwTW92NEhtZlorUmtvMlZzWUQ4ZHYzem15eFF6SWkwQVBIZHJ3dmJLNUVmMmRGN1dhbApKdDI3UldOb1NnUzJaME5ZMVJZQnlGSEt0cTJLdzZtMjVNeGhlMkVDZ2dFQVhSNFdSRXhoMEpCVXB0eVZOZTFTCis3Z1IzRDU4QW5uM0lRSUt5QUpaOEVhTGJKYUQwSFNUREFNUFJTV0grYlkvZGhDMjY1c3djK3MxZmlHUFJacUcKMFRmcmhYZmFOby9UUXhta2NSRElRNnRQTVZNL2xjR0k3amF6UTdtSEZ0R1ZZOVh1UkZCVWMyYmwxTDNJMXlUbgp3RlJkR1hXNEwxUXl4b2R3YnV3RDhPNEI5VGxEbUxrUTJwM2ZxUkVZbnRUS3NneFFCdWRIZjIrTFdPRzVTZ3RECjI3akZ4Z0pyeUdrY0wvWFJJT2xPYnRLK0VrZGdMRStzcmdlYlpocWlKK2hrYmQyNGpxM1k4OVdNQ1ZLYVNScDkKVmxRYVIxYXIzRkdtSWJrT0JyYnlNVS9wTjZqSEZSZllmdVZGQ1hQWnYrWEZFU1pubmJEaVdpbDBkTEpacTJoQgpZUUtDQVFBOVlTcE1wS3dhVGwrTmhTZlovMXU0NjZiMkpZcmJPYlRJM2VCZUowMnFwNXdQTjZYUHJ5aVZaZ1FXClh5cG04a3M5MEJIblBPNUczNFJnKzhLRFlONU1Ed1hBclJubDdSeTEySG5oV3lSaHNKYmdZOEh1c2d4SEROMU8KMEcwSFBpVWtIbTYydlRNYll6bkhPeE5sS1hFdFhBcTlZM3dQYkFjb01rRXZ0MzEwdEdLSUNtdTdEWkpXRlVvTAp1Y3RVS3Boc0V5VWdHbHIwRjJKekVoQWdMRXplczB0S1JpRWdlaFdnbXdlMEhlTEhCdW5oRFBTMmFJY2lCME1pCjY2SGc3cVZyMDlneXpFeGxrY3RLRzhsSm9WbU8vdlhucWQrWDB5M21YTUVZbkFIWHpIeG1Pd2JCNnF3Y3VWTlEKZytqRXliUWF3d3A2OC9id0JncFREQUhORGxrRQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==</prv> | ||||||
|  |   </cert> | ||||||
|  |   <dhcpdv6/> | ||||||
|  |   <virtualip version="1.0.1"> | ||||||
|  |     <vip/> | ||||||
|  |   </virtualip> | ||||||
|  |   <openvpn/> | ||||||
|  |   <ppps> | ||||||
|  |     <ppp/> | ||||||
|  |   </ppps> | ||||||
|  |   <vlans version="1.0.0"> | ||||||
|  |     <vlan/> | ||||||
|  |   </vlans> | ||||||
|  |   <bridges> | ||||||
|  |     <bridged/> | ||||||
|  |   </bridges> | ||||||
|  |   <gifs version="1.0.0"> | ||||||
|  |     <gif/> | ||||||
|  |   </gifs> | ||||||
|  |   <gres version="1.0.0"> | ||||||
|  |     <gre/> | ||||||
|  |   </gres> | ||||||
|  |   <laggs version="1.0.0"> | ||||||
|  |     <lagg/> | ||||||
|  |   </laggs> | ||||||
|  |   <wireless> | ||||||
|  |     <clone/> | ||||||
|  |   </wireless> | ||||||
|  |   <hasync version="1.0.2"> | ||||||
|  |     <pfsyncinterface/> | ||||||
|  |     <synchronizetoip/> | ||||||
|  |     <verifypeer>0</verifypeer> | ||||||
|  |     <username/> | ||||||
|  |     <password/> | ||||||
|  |     <disablepreempt>0</disablepreempt> | ||||||
|  |     <disconnectppps>0</disconnectppps> | ||||||
|  |     <pfsyncpeerip/> | ||||||
|  |     <pfsyncversion>1400</pfsyncversion> | ||||||
|  |     <syncitems/> | ||||||
|  |   </hasync> | ||||||
|  |   <ifgroups version="1.0.0"/> | ||||||
|  |   <dnsmasq version="1.0.7" persisted_at="1755800176.40"> | ||||||
|  |     <enable>1</enable> | ||||||
|  |     <regdhcp>0</regdhcp> | ||||||
|  |     <regdhcpstatic>0</regdhcpstatic> | ||||||
|  |     <dhcpfirst>0</dhcpfirst> | ||||||
|  |     <strict_order>0</strict_order> | ||||||
|  |     <domain_needed>0</domain_needed> | ||||||
|  |     <no_private_reverse>0</no_private_reverse> | ||||||
|  |     <no_resolv>0</no_resolv> | ||||||
|  |     <log_queries>0</log_queries> | ||||||
|  |     <no_hosts>0</no_hosts> | ||||||
|  |     <strictbind>0</strictbind> | ||||||
|  |     <dnssec>0</dnssec> | ||||||
|  |     <regdhcpdomain/> | ||||||
|  |     <interface>lan</interface> | ||||||
|  |     <port>0</port> | ||||||
|  |     <dns_forward_max/> | ||||||
|  |     <cache_size/> | ||||||
|  |     <local_ttl/> | ||||||
|  |     <add_mac/> | ||||||
|  |     <add_subnet>0</add_subnet> | ||||||
|  |     <strip_subnet>0</strip_subnet> | ||||||
|  |     <dhcp> | ||||||
|  |       <no_interface/> | ||||||
|  |       <fqdn>1</fqdn> | ||||||
|  |       <domain/> | ||||||
|  |       <lease_max/> | ||||||
|  |       <authoritative>0</authoritative> | ||||||
|  |       <default_fw_rules>1</default_fw_rules> | ||||||
|  |       <reply_delay/> | ||||||
|  |       <enable_ra>0</enable_ra> | ||||||
|  |       <nosync>0</nosync> | ||||||
|  |     </dhcp> | ||||||
|  |     <no_ident>1</no_ident> | ||||||
|  |     <dhcp_tags uuid="8d190cf3-8d2d-47db-ab9b-fa21016b533e"> | ||||||
|  |       <tag>ipxe</tag> | ||||||
|  |     </dhcp_tags> | ||||||
|  |     <dhcp_tags uuid="0b2982da-198c-4ca4-9a3e-95813667047c"> | ||||||
|  |       <tag>pxeEfi</tag> | ||||||
|  |     </dhcp_tags> | ||||||
|  |     <dhcp_tags uuid="993e079f-09b9-4a0f-a70f-8898872b9983"> | ||||||
|  |       <tag>pxeBios</tag> | ||||||
|  |     </dhcp_tags> | ||||||
|  |     <dhcp_ranges uuid="843574fc-4c3f-4f81-9e86-56be45f4ba49"> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <set_tag/> | ||||||
|  |       <start_addr>192.168.1.41</start_addr> | ||||||
|  |       <end_addr>192.168.1.245</end_addr> | ||||||
|  |       <subnet_mask/> | ||||||
|  |       <constructor/> | ||||||
|  |       <mode/> | ||||||
|  |       <prefix_len/> | ||||||
|  |       <lease_time/> | ||||||
|  |       <domain_type>range</domain_type> | ||||||
|  |       <domain/> | ||||||
|  |       <nosync>0</nosync> | ||||||
|  |       <ra_mode/> | ||||||
|  |       <ra_priority/> | ||||||
|  |       <ra_mtu/> | ||||||
|  |       <ra_interval/> | ||||||
|  |       <ra_router_lifetime/> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_ranges> | ||||||
|  |     <dhcp_options uuid="1e8d6f0f-7c2c-4873-8960-95a5c9447318"> | ||||||
|  |       <type>match</type> | ||||||
|  |       <option>77</option> | ||||||
|  |       <option6/> | ||||||
|  |       <interface/> | ||||||
|  |       <tag/> | ||||||
|  |       <set_tag>8d190cf3-8d2d-47db-ab9b-fa21016b533e</set_tag> | ||||||
|  |       <value>iPXE</value> | ||||||
|  |       <force/> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_options> | ||||||
|  |     <dhcp_options uuid="9b54181b-aa68-4fd6-9d59-10a77c291fcb"> | ||||||
|  |       <type>match</type> | ||||||
|  |       <option>93</option> | ||||||
|  |       <option6/> | ||||||
|  |       <interface/> | ||||||
|  |       <tag/> | ||||||
|  |       <set_tag>993e079f-09b9-4a0f-a70f-8898872b9983</set_tag> | ||||||
|  |       <value>0</value> | ||||||
|  |       <force/> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_options> | ||||||
|  |     <dhcp_options uuid="26402b93-91bb-48a9-92da-b567a91ed4d8"> | ||||||
|  |       <type>match</type> | ||||||
|  |       <option>93</option> | ||||||
|  |       <option6/> | ||||||
|  |       <interface/> | ||||||
|  |       <tag/> | ||||||
|  |       <set_tag>0b2982da-198c-4ca4-9a3e-95813667047c</set_tag> | ||||||
|  |       <value>7</value> | ||||||
|  |       <force/> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_options> | ||||||
|  |     <dhcp_boot uuid="57436655-6f95-4590-bcdc-dfe542347560"> | ||||||
|  |       <interface/> | ||||||
|  |       <tag>0b2982da-198c-4ca4-9a3e-95813667047c</tag> | ||||||
|  |       <filename>ipxe.efi</filename> | ||||||
|  |       <servername>192.168.1.1</servername> | ||||||
|  |       <address>192.168.1.1</address> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_boot> | ||||||
|  |     <dhcp_boot uuid="dcc00bf2-5148-40ef-9f79-1f17bf572f6c"> | ||||||
|  |       <interface/> | ||||||
|  |       <tag>8d190cf3-8d2d-47db-ab9b-fa21016b533e</tag> | ||||||
|  |       <filename>http://192.168.1.1:8080/boot.ipxe</filename> | ||||||
|  |       <servername>192.168.1.1</servername> | ||||||
|  |       <address>192.168.1.1</address> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_boot> | ||||||
|  |     <dhcp_boot uuid="8b9263a4-d242-4c41-8ff2-6f1af5001c41"> | ||||||
|  |       <interface/> | ||||||
|  |       <tag>993e079f-09b9-4a0f-a70f-8898872b9983</tag> | ||||||
|  |       <filename>undionly.kpxe</filename> | ||||||
|  |       <servername>192.168.1.1</servername> | ||||||
|  |       <address>192.168.1.1</address> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_boot> | ||||||
|  |   </dnsmasq> | ||||||
|  | </opnsense> | ||||||
| @ -0,0 +1,867 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <opnsense> | ||||||
|  |   <theme>opnsense</theme> | ||||||
|  |   <sysctl version="1.0.1" persisted_at="1755708111.39"> | ||||||
|  |     <item/> | ||||||
|  |   </sysctl> | ||||||
|  |   <system> | ||||||
|  |     <serialspeed>115200</serialspeed> | ||||||
|  |     <primaryconsole>serial</primaryconsole> | ||||||
|  |     <optimization>normal</optimization> | ||||||
|  |     <hostname>OPNsense</hostname> | ||||||
|  |     <domain>internal</domain> | ||||||
|  |     <dnsallowoverride>1</dnsallowoverride> | ||||||
|  |     <dnsallowoverride_exclude/> | ||||||
|  |     <group uuid="67305f6f-7f7a-454d-8a4e-65cb8f072d81"> | ||||||
|  |       <gid>1999</gid> | ||||||
|  |       <name>admins</name> | ||||||
|  |       <scope>system</scope> | ||||||
|  |       <description>System Administrators</description> | ||||||
|  |       <priv>page-all</priv> | ||||||
|  |       <member>0</member> | ||||||
|  |       <source_networks/> | ||||||
|  |     </group> | ||||||
|  |     <user uuid="1d2ed537-5d1a-4772-9600-37b93f9f798b"> | ||||||
|  |       <uid>0</uid> | ||||||
|  |       <name>root</name> | ||||||
|  |       <disabled>0</disabled> | ||||||
|  |       <scope>system</scope> | ||||||
|  |       <expires/> | ||||||
|  |       <authorizedkeys/> | ||||||
|  |       <otp_seed/> | ||||||
|  |       <shell/> | ||||||
|  |       <password>$2y$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS</password> | ||||||
|  |       <pwd_changed_at/> | ||||||
|  |       <landing_page/> | ||||||
|  |       <comment/> | ||||||
|  |       <email/> | ||||||
|  |       <apikeys/> | ||||||
|  |       <priv/> | ||||||
|  |       <language/> | ||||||
|  |       <descr>System Administrator</descr> | ||||||
|  |       <dashboard/> | ||||||
|  |     </user> | ||||||
|  |     <timezone>Etc/UTC</timezone> | ||||||
|  |     <timeservers>0.opnsense.pool.ntp.org 1.opnsense.pool.ntp.org 2.opnsense.pool.ntp.org 3.opnsense.pool.ntp.org</timeservers> | ||||||
|  |     <webgui> | ||||||
|  |       <protocol>https</protocol> | ||||||
|  |       <ssl-certref>68a5faf1685db</ssl-certref> | ||||||
|  |       <port/> | ||||||
|  |       <ssl-ciphers/> | ||||||
|  |       <interfaces/> | ||||||
|  |       <compression/> | ||||||
|  |     </webgui> | ||||||
|  |     <disablenatreflection>yes</disablenatreflection> | ||||||
|  |     <usevirtualterminal>1</usevirtualterminal> | ||||||
|  |     <disableconsolemenu>1</disableconsolemenu> | ||||||
|  |     <disablevlanhwfilter>1</disablevlanhwfilter> | ||||||
|  |     <disablechecksumoffloading>1</disablechecksumoffloading> | ||||||
|  |     <disablesegmentationoffloading>1</disablesegmentationoffloading> | ||||||
|  |     <disablelargereceiveoffloading>1</disablelargereceiveoffloading> | ||||||
|  |     <ipv6allow>1</ipv6allow> | ||||||
|  |     <powerd_ac_mode>hadp</powerd_ac_mode> | ||||||
|  |     <powerd_battery_mode>hadp</powerd_battery_mode> | ||||||
|  |     <powerd_normal_mode>hadp</powerd_normal_mode> | ||||||
|  |     <bogons> | ||||||
|  |       <interval>monthly</interval> | ||||||
|  |     </bogons> | ||||||
|  |     <pf_share_forward>1</pf_share_forward> | ||||||
|  |     <lb_use_sticky>1</lb_use_sticky> | ||||||
|  |     <ssh> | ||||||
|  |       <group>admins</group> | ||||||
|  |       <noauto>1</noauto> | ||||||
|  |       <interfaces/> | ||||||
|  |       <kex/> | ||||||
|  |       <ciphers/> | ||||||
|  |       <macs/> | ||||||
|  |       <keys/> | ||||||
|  |       <keysig/> | ||||||
|  |       <rekeylimit/> | ||||||
|  |       <enabled>enabled</enabled> | ||||||
|  |       <passwordauth>1</passwordauth> | ||||||
|  |       <permitrootlogin>1</permitrootlogin> | ||||||
|  |     </ssh> | ||||||
|  |     <rrdbackup>-1</rrdbackup> | ||||||
|  |     <netflowbackup>-1</netflowbackup> | ||||||
|  |     <firmware version="1.0.1" persisted_at="1755708111.32"> | ||||||
|  |       <mirror/> | ||||||
|  |       <flavour/> | ||||||
|  |       <plugins/> | ||||||
|  |       <type/> | ||||||
|  |       <subscription/> | ||||||
|  |       <reboot>0</reboot> | ||||||
|  |     </firmware> | ||||||
|  |     <dnsserver/> | ||||||
|  |     <language>en_US</language> | ||||||
|  |   </system> | ||||||
|  |   <interfaces> | ||||||
|  |     <wan> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <if>vtnet0</if> | ||||||
|  |       <mtu/> | ||||||
|  |       <ipaddr>dhcp</ipaddr> | ||||||
|  |       <ipaddrv6>dhcp6</ipaddrv6> | ||||||
|  |       <subnet/> | ||||||
|  |       <gateway/> | ||||||
|  |       <blockpriv>0</blockpriv> | ||||||
|  |       <blockbogons>1</blockbogons> | ||||||
|  |       <media/> | ||||||
|  |       <mediaopt/> | ||||||
|  |       <dhcp6-ia-pd-len>0</dhcp6-ia-pd-len> | ||||||
|  |       <dhcphostname/> | ||||||
|  |       <spoofmac/> | ||||||
|  |       <mss/> | ||||||
|  |     </wan> | ||||||
|  |     <lan> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <if>vtnet1</if> | ||||||
|  |       <ipaddr>192.168.1.1</ipaddr> | ||||||
|  |       <subnet>24</subnet> | ||||||
|  |       <ipaddrv6>track6</ipaddrv6> | ||||||
|  |       <subnetv6>64</subnetv6> | ||||||
|  |       <media/> | ||||||
|  |       <mediaopt/> | ||||||
|  |       <track6-interface>wan</track6-interface> | ||||||
|  |       <track6-prefix-id>0</track6-prefix-id> | ||||||
|  |     </lan> | ||||||
|  |     <lo0> | ||||||
|  |       <internal_dynamic>1</internal_dynamic> | ||||||
|  |       <descr>Loopback</descr> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <if>lo0</if> | ||||||
|  |       <ipaddr>127.0.0.1</ipaddr> | ||||||
|  |       <ipaddrv6>::1</ipaddrv6> | ||||||
|  |       <subnet>8</subnet> | ||||||
|  |       <subnetv6>128</subnetv6> | ||||||
|  |       <type>none</type> | ||||||
|  |       <virtual>1</virtual> | ||||||
|  |     </lo0> | ||||||
|  |   </interfaces> | ||||||
|  |   <dnsmasq version="1.0.7" persisted_at="1755723263.06"> | ||||||
|  |     <enable>1</enable> | ||||||
|  |     <regdhcp>0</regdhcp> | ||||||
|  |     <regdhcpstatic>0</regdhcpstatic> | ||||||
|  |     <dhcpfirst>0</dhcpfirst> | ||||||
|  |     <strict_order>0</strict_order> | ||||||
|  |     <domain_needed>0</domain_needed> | ||||||
|  |     <no_private_reverse>0</no_private_reverse> | ||||||
|  |     <no_resolv>0</no_resolv> | ||||||
|  |     <log_queries>0</log_queries> | ||||||
|  |     <no_hosts>0</no_hosts> | ||||||
|  |     <strictbind>0</strictbind> | ||||||
|  |     <dnssec>0</dnssec> | ||||||
|  |     <regdhcpdomain/> | ||||||
|  |     <interface>lan</interface> | ||||||
|  |     <port>0</port> | ||||||
|  |     <dns_forward_max/> | ||||||
|  |     <cache_size/> | ||||||
|  |     <local_ttl/> | ||||||
|  |     <add_mac/> | ||||||
|  |     <add_subnet>0</add_subnet> | ||||||
|  |     <strip_subnet>0</strip_subnet> | ||||||
|  |     <dhcp> | ||||||
|  |       <no_interface/> | ||||||
|  |       <fqdn>1</fqdn> | ||||||
|  |       <domain/> | ||||||
|  |       <lease_max/> | ||||||
|  |       <authoritative>0</authoritative> | ||||||
|  |       <default_fw_rules>1</default_fw_rules> | ||||||
|  |       <reply_delay/> | ||||||
|  |       <enable_ra>0</enable_ra> | ||||||
|  |       <nosync>0</nosync> | ||||||
|  |     </dhcp> | ||||||
|  |     <no_ident>1</no_ident> | ||||||
|  |     <dhcp_ranges uuid="78b5c4a4-565d-4cd7-af10-29050f29e494"> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <set_tag/> | ||||||
|  |       <start_addr>192.168.1.41</start_addr> | ||||||
|  |       <end_addr>192.168.1.245</end_addr> | ||||||
|  |       <subnet_mask/> | ||||||
|  |       <constructor/> | ||||||
|  |       <mode/> | ||||||
|  |       <prefix_len/> | ||||||
|  |       <lease_time/> | ||||||
|  |       <domain_type>range</domain_type> | ||||||
|  |       <domain/> | ||||||
|  |       <nosync>0</nosync> | ||||||
|  |       <ra_mode/> | ||||||
|  |       <ra_priority/> | ||||||
|  |       <ra_mtu/> | ||||||
|  |       <ra_interval/> | ||||||
|  |       <ra_router_lifetime/> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_ranges> | ||||||
|  |     <dhcp_options uuid="b05f2b57-bad8-4072-8f51-d051671832fe"> | ||||||
|  |       <type>set</type> | ||||||
|  |       <option>67</option> | ||||||
|  |       <option6/> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <tag/> | ||||||
|  |       <set_tag/> | ||||||
|  |       <value>test/boot/filename</value> | ||||||
|  |       <force>0</force> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_options> | ||||||
|  |     <dhcp_options uuid="5a5a7854-ec44-4631-95a9-85b4897588d5"> | ||||||
|  |       <type>set</type> | ||||||
|  |       <option>128</option> | ||||||
|  |       <option6/> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <tag/> | ||||||
|  |       <set_tag/> | ||||||
|  |       <value>test some pxe setting vendor specific 128</value> | ||||||
|  |       <force>0</force> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_options> | ||||||
|  |     <dhcp_options uuid="7706861d-1851-420f-9b50-891ad413663e"> | ||||||
|  |       <type>set</type> | ||||||
|  |       <option>208</option> | ||||||
|  |       <option6/> | ||||||
|  |       <interface/> | ||||||
|  |       <tag/> | ||||||
|  |       <set_tag/> | ||||||
|  |       <value>pxelinux magic what is this (on any interface)</value> | ||||||
|  |       <force>0</force> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_options> | ||||||
|  |     <dhcp_boot uuid="748c0bd2-ef58-4cba-8486-bbf142f53859"> | ||||||
|  |       <interface/> | ||||||
|  |       <tag/> | ||||||
|  |       <filename>boot options filename</filename> | ||||||
|  |       <servername>boot servername</servername> | ||||||
|  |       <address>boot server address</address> | ||||||
|  |       <description>boot description</description> | ||||||
|  |     </dhcp_boot> | ||||||
|  |   </dnsmasq> | ||||||
|  |   <snmpd> | ||||||
|  |     <syslocation/> | ||||||
|  |     <syscontact/> | ||||||
|  |     <rocommunity>public</rocommunity> | ||||||
|  |   </snmpd> | ||||||
|  |   <nat> | ||||||
|  |     <outbound> | ||||||
|  |       <mode>automatic</mode> | ||||||
|  |     </outbound> | ||||||
|  |   </nat> | ||||||
|  |   <filter> | ||||||
|  |     <rule> | ||||||
|  |       <type>pass</type> | ||||||
|  |       <ipprotocol>inet</ipprotocol> | ||||||
|  |       <descr>Default allow LAN to any rule</descr> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <source> | ||||||
|  |         <network>lan</network> | ||||||
|  |       </source> | ||||||
|  |       <destination> | ||||||
|  |         <any/> | ||||||
|  |       </destination> | ||||||
|  |     </rule> | ||||||
|  |     <rule> | ||||||
|  |       <type>pass</type> | ||||||
|  |       <ipprotocol>inet6</ipprotocol> | ||||||
|  |       <descr>Default allow LAN IPv6 to any rule</descr> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <source> | ||||||
|  |         <network>lan</network> | ||||||
|  |       </source> | ||||||
|  |       <destination> | ||||||
|  |         <any/> | ||||||
|  |       </destination> | ||||||
|  |     </rule> | ||||||
|  |   </filter> | ||||||
|  |   <rrd> | ||||||
|  |     <enable/> | ||||||
|  |   </rrd> | ||||||
|  |   <ntpd> | ||||||
|  |     <prefer>0.opnsense.pool.ntp.org</prefer> | ||||||
|  |   </ntpd> | ||||||
|  |   <revision> | ||||||
|  |     <username>root@192.168.1.5</username> | ||||||
|  |     <description>/api/dnsmasq/settings/set made changes</description> | ||||||
|  |     <time>1755723263.06</time> | ||||||
|  |   </revision> | ||||||
|  |   <OPNsense> | ||||||
|  |     <wireguard> | ||||||
|  |       <client version="1.0.0" persisted_at="1755708111.04"> | ||||||
|  |         <clients/> | ||||||
|  |       </client> | ||||||
|  |       <general version="0.0.1" persisted_at="1755708111.05"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |       </general> | ||||||
|  |       <server version="1.0.0" persisted_at="1755708111.05"> | ||||||
|  |         <servers/> | ||||||
|  |       </server> | ||||||
|  |     </wireguard> | ||||||
|  |     <IPsec version="1.0.4" persisted_at="1755708111.06"> | ||||||
|  |       <general> | ||||||
|  |         <enabled/> | ||||||
|  |         <preferred_oldsa>0</preferred_oldsa> | ||||||
|  |         <disablevpnrules>0</disablevpnrules> | ||||||
|  |         <passthrough_networks/> | ||||||
|  |         <user_source/> | ||||||
|  |         <local_group/> | ||||||
|  |       </general> | ||||||
|  |       <charon> | ||||||
|  |         <max_ikev1_exchanges/> | ||||||
|  |         <threads>16</threads> | ||||||
|  |         <ikesa_table_size>32</ikesa_table_size> | ||||||
|  |         <ikesa_table_segments>4</ikesa_table_segments> | ||||||
|  |         <init_limit_half_open>1000</init_limit_half_open> | ||||||
|  |         <ignore_acquire_ts>1</ignore_acquire_ts> | ||||||
|  |         <install_routes>0</install_routes> | ||||||
|  |         <cisco_unity>0</cisco_unity> | ||||||
|  |         <make_before_break>0</make_before_break> | ||||||
|  |         <retransmit_tries/> | ||||||
|  |         <retransmit_timeout/> | ||||||
|  |         <retransmit_base/> | ||||||
|  |         <retransmit_jitter/> | ||||||
|  |         <retransmit_limit/> | ||||||
|  |         <syslog> | ||||||
|  |           <daemon> | ||||||
|  |             <ike_name>1</ike_name> | ||||||
|  |             <log_level>0</log_level> | ||||||
|  |             <app>1</app> | ||||||
|  |             <asn>1</asn> | ||||||
|  |             <cfg>1</cfg> | ||||||
|  |             <chd>1</chd> | ||||||
|  |             <dmn>1</dmn> | ||||||
|  |             <enc>1</enc> | ||||||
|  |             <esp>1</esp> | ||||||
|  |             <ike>1</ike> | ||||||
|  |             <imc>1</imc> | ||||||
|  |             <imv>1</imv> | ||||||
|  |             <job>1</job> | ||||||
|  |             <knl>1</knl> | ||||||
|  |             <lib>1</lib> | ||||||
|  |             <mgr>1</mgr> | ||||||
|  |             <net>1</net> | ||||||
|  |             <pts>1</pts> | ||||||
|  |             <tls>1</tls> | ||||||
|  |             <tnc>1</tnc> | ||||||
|  |           </daemon> | ||||||
|  |         </syslog> | ||||||
|  |         <plugins> | ||||||
|  |           <attr> | ||||||
|  |             <subnet/> | ||||||
|  |             <split-include/> | ||||||
|  |             <x_28674/> | ||||||
|  |             <x_28675/> | ||||||
|  |             <x_28672/> | ||||||
|  |             <x_28673>0</x_28673> | ||||||
|  |             <x_28679/> | ||||||
|  |             <dns/> | ||||||
|  |             <nbns/> | ||||||
|  |           </attr> | ||||||
|  |           <eap-radius> | ||||||
|  |             <servers/> | ||||||
|  |             <accounting>0</accounting> | ||||||
|  |             <class_group>0</class_group> | ||||||
|  |           </eap-radius> | ||||||
|  |           <xauth-pam> | ||||||
|  |             <pam_service>ipsec</pam_service> | ||||||
|  |             <session>0</session> | ||||||
|  |             <trim_email>1</trim_email> | ||||||
|  |           </xauth-pam> | ||||||
|  |         </plugins> | ||||||
|  |       </charon> | ||||||
|  |       <keyPairs/> | ||||||
|  |       <preSharedKeys/> | ||||||
|  |     </IPsec> | ||||||
|  |     <Swanctl version="1.0.0" persisted_at="1755708111.08"> | ||||||
|  |       <Connections/> | ||||||
|  |       <locals/> | ||||||
|  |       <remotes/> | ||||||
|  |       <children/> | ||||||
|  |       <Pools/> | ||||||
|  |       <VTIs/> | ||||||
|  |       <SPDs/> | ||||||
|  |     </Swanctl> | ||||||
|  |     <OpenVPNExport version="0.0.1" persisted_at="1755708111.40"> | ||||||
|  |       <servers/> | ||||||
|  |     </OpenVPNExport> | ||||||
|  |     <OpenVPN version="1.0.1" persisted_at="1755708111.40"> | ||||||
|  |       <Overwrites/> | ||||||
|  |       <Instances/> | ||||||
|  |       <StaticKeys/> | ||||||
|  |     </OpenVPN> | ||||||
|  |     <captiveportal version="1.0.4" persisted_at="1755708111.41"> | ||||||
|  |       <zones/> | ||||||
|  |       <templates/> | ||||||
|  |     </captiveportal> | ||||||
|  |     <cron version="1.0.4" persisted_at="1755708111.43"> | ||||||
|  |       <jobs/> | ||||||
|  |     </cron> | ||||||
|  |     <DHCRelay version="1.0.1" persisted_at="1755708111.43"/> | ||||||
|  |     <Firewall> | ||||||
|  |       <Lvtemplate version="0.0.1" persisted_at="1755708111.45"> | ||||||
|  |         <templates/> | ||||||
|  |       </Lvtemplate> | ||||||
|  |       <Alias version="1.0.1" persisted_at="1755708111.65"> | ||||||
|  |         <geoip> | ||||||
|  |           <url/> | ||||||
|  |         </geoip> | ||||||
|  |         <aliases/> | ||||||
|  |       </Alias> | ||||||
|  |       <Category version="1.0.0" persisted_at="1755708111.65"> | ||||||
|  |         <categories/> | ||||||
|  |       </Category> | ||||||
|  |       <Filter version="1.0.4" persisted_at="1755708111.70"> | ||||||
|  |         <rules/> | ||||||
|  |         <snatrules/> | ||||||
|  |         <npt/> | ||||||
|  |         <onetoone/> | ||||||
|  |       </Filter> | ||||||
|  |     </Firewall> | ||||||
|  |     <Netflow version="1.0.1" persisted_at="1755708111.45"> | ||||||
|  |       <capture> | ||||||
|  |         <interfaces/> | ||||||
|  |         <egress_only/> | ||||||
|  |         <version>v9</version> | ||||||
|  |         <targets/> | ||||||
|  |       </capture> | ||||||
|  |       <collect> | ||||||
|  |         <enable>0</enable> | ||||||
|  |       </collect> | ||||||
|  |       <activeTimeout>1800</activeTimeout> | ||||||
|  |       <inactiveTimeout>15</inactiveTimeout> | ||||||
|  |     </Netflow> | ||||||
|  |     <IDS version="1.1.0" persisted_at="1755708111.90"> | ||||||
|  |       <rules/> | ||||||
|  |       <policies/> | ||||||
|  |       <userDefinedRules/> | ||||||
|  |       <files/> | ||||||
|  |       <fileTags/> | ||||||
|  |       <general> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <ips>0</ips> | ||||||
|  |         <promisc>0</promisc> | ||||||
|  |         <interfaces>wan</interfaces> | ||||||
|  |         <homenet>192.168.0.0/16,10.0.0.0/8,172.16.0.0/12</homenet> | ||||||
|  |         <defaultPacketSize/> | ||||||
|  |         <UpdateCron/> | ||||||
|  |         <AlertLogrotate>W0D23</AlertLogrotate> | ||||||
|  |         <AlertSaveLogs>4</AlertSaveLogs> | ||||||
|  |         <MPMAlgo/> | ||||||
|  |         <detect> | ||||||
|  |           <Profile/> | ||||||
|  |           <toclient_groups/> | ||||||
|  |           <toserver_groups/> | ||||||
|  |         </detect> | ||||||
|  |         <syslog>0</syslog> | ||||||
|  |         <syslog_eve>0</syslog_eve> | ||||||
|  |         <LogPayload>0</LogPayload> | ||||||
|  |         <verbosity/> | ||||||
|  |         <eveLog> | ||||||
|  |           <http> | ||||||
|  |             <enable>0</enable> | ||||||
|  |             <extended>0</extended> | ||||||
|  |             <dumpAllHeaders/> | ||||||
|  |           </http> | ||||||
|  |           <tls> | ||||||
|  |             <enable>0</enable> | ||||||
|  |             <extended>0</extended> | ||||||
|  |             <sessionResumption>0</sessionResumption> | ||||||
|  |             <custom/> | ||||||
|  |           </tls> | ||||||
|  |         </eveLog> | ||||||
|  |       </general> | ||||||
|  |     </IDS> | ||||||
|  |     <Interfaces> | ||||||
|  |       <loopbacks version="1.0.0" persisted_at="1755708111.95"/> | ||||||
|  |       <neighbors version="1.0.0" persisted_at="1755708111.96"/> | ||||||
|  |       <vxlans version="1.0.2" persisted_at="1755708111.99"/> | ||||||
|  |     </Interfaces> | ||||||
|  |     <Kea> | ||||||
|  |       <ctrl_agent version="0.0.1" persisted_at="1755708111.99"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <http_host>127.0.0.1</http_host> | ||||||
|  |           <http_port>8000</http_port> | ||||||
|  |         </general> | ||||||
|  |       </ctrl_agent> | ||||||
|  |       <dhcp4 version="1.0.4" persisted_at="1755708112.00"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <manual_config>0</manual_config> | ||||||
|  |           <interfaces/> | ||||||
|  |           <valid_lifetime>4000</valid_lifetime> | ||||||
|  |           <fwrules>1</fwrules> | ||||||
|  |           <dhcp_socket_type>raw</dhcp_socket_type> | ||||||
|  |         </general> | ||||||
|  |         <ha> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <this_server_name/> | ||||||
|  |           <max_unacked_clients>2</max_unacked_clients> | ||||||
|  |         </ha> | ||||||
|  |         <subnets/> | ||||||
|  |         <reservations/> | ||||||
|  |         <ha_peers/> | ||||||
|  |       </dhcp4> | ||||||
|  |       <dhcp6 version="1.0.0" persisted_at="1755708112.00"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <manual_config>0</manual_config> | ||||||
|  |           <interfaces/> | ||||||
|  |           <valid_lifetime>4000</valid_lifetime> | ||||||
|  |           <fwrules>1</fwrules> | ||||||
|  |         </general> | ||||||
|  |         <ha> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <this_server_name/> | ||||||
|  |           <max_unacked_clients>2</max_unacked_clients> | ||||||
|  |         </ha> | ||||||
|  |         <subnets/> | ||||||
|  |         <reservations/> | ||||||
|  |         <pd_pools/> | ||||||
|  |         <ha_peers/> | ||||||
|  |       </dhcp6> | ||||||
|  |     </Kea> | ||||||
|  |     <monit version="1.0.13" persisted_at="1755708112.02"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <interval>120</interval> | ||||||
|  |         <startdelay>120</startdelay> | ||||||
|  |         <mailserver>127.0.0.1</mailserver> | ||||||
|  |         <port>25</port> | ||||||
|  |         <username/> | ||||||
|  |         <password/> | ||||||
|  |         <ssl>0</ssl> | ||||||
|  |         <sslversion>auto</sslversion> | ||||||
|  |         <sslverify>1</sslverify> | ||||||
|  |         <logfile/> | ||||||
|  |         <statefile/> | ||||||
|  |         <eventqueuePath/> | ||||||
|  |         <eventqueueSlots/> | ||||||
|  |         <httpdEnabled>0</httpdEnabled> | ||||||
|  |         <httpdUsername>root</httpdUsername> | ||||||
|  |         <httpdPassword/> | ||||||
|  |         <httpdPort>2812</httpdPort> | ||||||
|  |         <httpdAllow/> | ||||||
|  |         <mmonitUrl/> | ||||||
|  |         <mmonitTimeout>5</mmonitTimeout> | ||||||
|  |         <mmonitRegisterCredentials>1</mmonitRegisterCredentials> | ||||||
|  |       </general> | ||||||
|  |       <alert uuid="76cd5195-a487-4f4f-8ef5-4f9815bf19e5"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <recipient>root@localhost.local</recipient> | ||||||
|  |         <noton>0</noton> | ||||||
|  |         <events/> | ||||||
|  |         <format/> | ||||||
|  |         <reminder/> | ||||||
|  |         <description/> | ||||||
|  |       </alert> | ||||||
|  |       <service uuid="11610907-f700-4f0a-9926-c25a47709493"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <name>$HOST</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>system</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path/> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>02014be3-fc31-4af3-a0d5-061eaa67d28a,ebfd0d97-ae21-45d5-8b42-5220c75ce46f,d37f25f0-89e3-44b6-8ad2-280ac83a8904,37afd0d9-990c-4f03-a817-45691461e3d0</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="dcc7e42f-a878-49a3-8b90-67faf21d67bd"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <name>RootFs</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>filesystem</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>b44b859c-bc72-4c2e-82c9-4f56d84a5497</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="5ca544d4-846a-4f61-9d6f-c191eafcd9fb"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <name>carp_status_change</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>custom</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/usr/local/opnsense/scripts/OPNsense/Monit/carp_status</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>0909801b-cd11-41c8-afeb-369396247308</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="6e16e26e-e732-4b9b-ac92-2086b84f3158"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <name>gateway_alert</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>custom</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>56e67d76-cef6-4167-a51e-2c69a921ebc9</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <test uuid="b7671673-5cf1-4123-a58c-37f57a8e9d59"> | ||||||
|  |         <name>Ping</name> | ||||||
|  |         <type>NetworkPing</type> | ||||||
|  |         <condition>failed ping</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="30cf3664-5c6f-4ac5-a52a-36023270c6fb"> | ||||||
|  |         <name>NetworkLink</name> | ||||||
|  |         <type>NetworkInterface</type> | ||||||
|  |         <condition>failed link</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="a7c0836a-b102-4f37-a73b-2a50903ffcc8"> | ||||||
|  |         <name>NetworkSaturation</name> | ||||||
|  |         <type>NetworkInterface</type> | ||||||
|  |         <condition>saturation is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="02014be3-fc31-4af3-a0d5-061eaa67d28a"> | ||||||
|  |         <name>MemoryUsage</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>memory usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="ebfd0d97-ae21-45d5-8b42-5220c75ce46f"> | ||||||
|  |         <name>CPUUsage</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>cpu usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="d37f25f0-89e3-44b6-8ad2-280ac83a8904"> | ||||||
|  |         <name>LoadAvg1</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (1min) is greater than 4</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="37afd0d9-990c-4f03-a817-45691461e3d0"> | ||||||
|  |         <name>LoadAvg5</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (5min) is greater than 3</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="608d2888-9df5-486d-bbfb-bf17fad75a7e"> | ||||||
|  |         <name>LoadAvg15</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (15min) is greater than 2</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="b44b859c-bc72-4c2e-82c9-4f56d84a5497"> | ||||||
|  |         <name>SpaceUsage</name> | ||||||
|  |         <type>SpaceUsage</type> | ||||||
|  |         <condition>space usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="0909801b-cd11-41c8-afeb-369396247308"> | ||||||
|  |         <name>ChangedStatus</name> | ||||||
|  |         <type>ProgramStatus</type> | ||||||
|  |         <condition>changed status</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="56e67d76-cef6-4167-a51e-2c69a921ebc9"> | ||||||
|  |         <name>NonZeroStatus</name> | ||||||
|  |         <type>ProgramStatus</type> | ||||||
|  |         <condition>status != 0</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |     </monit> | ||||||
|  |     <Gateways version="1.0.0" persisted_at="1755721633.53"/> | ||||||
|  |     <Syslog version="1.0.2" persisted_at="1755708112.05"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <loglocal>1</loglocal> | ||||||
|  |         <maxpreserve>31</maxpreserve> | ||||||
|  |         <maxfilesize/> | ||||||
|  |       </general> | ||||||
|  |       <destinations/> | ||||||
|  |     </Syslog> | ||||||
|  |     <TrafficShaper version="1.0.3" persisted_at="1755708112.06"> | ||||||
|  |       <pipes/> | ||||||
|  |       <queues/> | ||||||
|  |       <rules/> | ||||||
|  |     </TrafficShaper> | ||||||
|  |     <trust> | ||||||
|  |       <general version="1.0.1" persisted_at="1755708112.22"> | ||||||
|  |         <store_intermediate_certs>0</store_intermediate_certs> | ||||||
|  |         <install_crls>0</install_crls> | ||||||
|  |         <fetch_crls>0</fetch_crls> | ||||||
|  |         <enable_legacy_sect>1</enable_legacy_sect> | ||||||
|  |         <enable_config_constraints>0</enable_config_constraints> | ||||||
|  |         <CipherString/> | ||||||
|  |         <Ciphersuites/> | ||||||
|  |         <SignatureAlgorithms/> | ||||||
|  |         <groups/> | ||||||
|  |         <MinProtocol/> | ||||||
|  |         <MinProtocol_DTLS/> | ||||||
|  |       </general> | ||||||
|  |     </trust> | ||||||
|  |     <unboundplus version="1.0.12" persisted_at="1755708112.29"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <port>53</port> | ||||||
|  |         <stats>0</stats> | ||||||
|  |         <active_interface/> | ||||||
|  |         <dnssec>0</dnssec> | ||||||
|  |         <dns64>0</dns64> | ||||||
|  |         <dns64prefix/> | ||||||
|  |         <noarecords>0</noarecords> | ||||||
|  |         <regdhcp>0</regdhcp> | ||||||
|  |         <regdhcpdomain/> | ||||||
|  |         <regdhcpstatic>0</regdhcpstatic> | ||||||
|  |         <noreglladdr6>0</noreglladdr6> | ||||||
|  |         <noregrecords>0</noregrecords> | ||||||
|  |         <txtsupport>0</txtsupport> | ||||||
|  |         <cacheflush>0</cacheflush> | ||||||
|  |         <local_zone_type>transparent</local_zone_type> | ||||||
|  |         <outgoing_interface/> | ||||||
|  |         <enable_wpad>0</enable_wpad> | ||||||
|  |       </general> | ||||||
|  |       <advanced> | ||||||
|  |         <hideidentity>0</hideidentity> | ||||||
|  |         <hideversion>0</hideversion> | ||||||
|  |         <prefetch>0</prefetch> | ||||||
|  |         <prefetchkey>0</prefetchkey> | ||||||
|  |         <dnssecstripped>0</dnssecstripped> | ||||||
|  |         <aggressivensec>1</aggressivensec> | ||||||
|  |         <serveexpired>0</serveexpired> | ||||||
|  |         <serveexpiredreplyttl/> | ||||||
|  |         <serveexpiredttl/> | ||||||
|  |         <serveexpiredttlreset>0</serveexpiredttlreset> | ||||||
|  |         <serveexpiredclienttimeout/> | ||||||
|  |         <qnameminstrict>0</qnameminstrict> | ||||||
|  |         <extendedstatistics>0</extendedstatistics> | ||||||
|  |         <logqueries>0</logqueries> | ||||||
|  |         <logreplies>0</logreplies> | ||||||
|  |         <logtagqueryreply>0</logtagqueryreply> | ||||||
|  |         <logservfail>0</logservfail> | ||||||
|  |         <loglocalactions>0</loglocalactions> | ||||||
|  |         <logverbosity>1</logverbosity> | ||||||
|  |         <valloglevel>0</valloglevel> | ||||||
|  |         <privatedomain/> | ||||||
|  |         <privateaddress>0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,169.254.0.0/16,172.16.0.0/12,192.0.2.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,233.252.0.0/24,::1/128,2001:db8::/32,fc00::/8,fd00::/8,fe80::/10</privateaddress> | ||||||
|  |         <insecuredomain/> | ||||||
|  |         <msgcachesize/> | ||||||
|  |         <rrsetcachesize/> | ||||||
|  |         <outgoingnumtcp/> | ||||||
|  |         <incomingnumtcp/> | ||||||
|  |         <numqueriesperthread/> | ||||||
|  |         <outgoingrange/> | ||||||
|  |         <jostletimeout/> | ||||||
|  |         <discardtimeout/> | ||||||
|  |         <cachemaxttl/> | ||||||
|  |         <cachemaxnegativettl/> | ||||||
|  |         <cacheminttl/> | ||||||
|  |         <infrahostttl/> | ||||||
|  |         <infrakeepprobing>0</infrakeepprobing> | ||||||
|  |         <infracachenumhosts/> | ||||||
|  |         <unwantedreplythreshold/> | ||||||
|  |       </advanced> | ||||||
|  |       <acls> | ||||||
|  |         <default_action>allow</default_action> | ||||||
|  |       </acls> | ||||||
|  |       <dnsbl> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <safesearch>0</safesearch> | ||||||
|  |         <type/> | ||||||
|  |         <lists/> | ||||||
|  |         <whitelists/> | ||||||
|  |         <blocklists/> | ||||||
|  |         <wildcards/> | ||||||
|  |         <address/> | ||||||
|  |         <nxdomain>0</nxdomain> | ||||||
|  |       </dnsbl> | ||||||
|  |       <forwarding> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |       </forwarding> | ||||||
|  |       <dots/> | ||||||
|  |       <hosts/> | ||||||
|  |       <aliases/> | ||||||
|  |     </unboundplus> | ||||||
|  |   </OPNsense> | ||||||
|  |   <hasync version="1.0.2" persisted_at="1755708111.35"> | ||||||
|  |     <disablepreempt>0</disablepreempt> | ||||||
|  |     <disconnectppps>0</disconnectppps> | ||||||
|  |     <pfsyncinterface/> | ||||||
|  |     <pfsyncpeerip/> | ||||||
|  |     <pfsyncversion>1400</pfsyncversion> | ||||||
|  |     <synchronizetoip/> | ||||||
|  |     <verifypeer>0</verifypeer> | ||||||
|  |     <username/> | ||||||
|  |     <password/> | ||||||
|  |     <syncitems/> | ||||||
|  |   </hasync> | ||||||
|  |   <openvpn/> | ||||||
|  |   <ifgroups version="1.0.0" persisted_at="1755708111.71"/> | ||||||
|  |   <bridges version="1.0.0" persisted_at="1755708111.91"> | ||||||
|  |     <bridged/> | ||||||
|  |   </bridges> | ||||||
|  |   <gifs version="1.0.0" persisted_at="1755708111.92"> | ||||||
|  |     <gif/> | ||||||
|  |   </gifs> | ||||||
|  |   <gres version="1.0.0" persisted_at="1755708111.93"> | ||||||
|  |     <gre/> | ||||||
|  |   </gres> | ||||||
|  |   <laggs version="1.0.0" persisted_at="1755708111.95"> | ||||||
|  |     <lagg/> | ||||||
|  |   </laggs> | ||||||
|  |   <virtualip version="1.0.1" persisted_at="1755708111.96"> | ||||||
|  |     <vip/> | ||||||
|  |   </virtualip> | ||||||
|  |   <vlans version="1.0.0" persisted_at="1755708111.98"> | ||||||
|  |     <vlan/> | ||||||
|  |   </vlans> | ||||||
|  |   <staticroutes version="1.0.0" persisted_at="1755708112.03"/> | ||||||
|  |   <ppps> | ||||||
|  |     <ppp/> | ||||||
|  |   </ppps> | ||||||
|  |   <wireless> | ||||||
|  |     <clone/> | ||||||
|  |   </wireless> | ||||||
|  |   <ca/> | ||||||
|  |   <dhcpd/> | ||||||
|  |   <dhcpdv6/> | ||||||
|  |   <cert uuid="d60972af-642c-411f-abb6-43e0d680fefd"> | ||||||
|  |     <refid>68a5faf1685db</refid> | ||||||
|  |     <descr>Web GUI TLS certificate</descr> | ||||||
|  |     <caref/> | ||||||
|  |     <crt>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQlBxZ0F3SUJBZ0lVQlpQYjUwMXNaM3hhTTZzSDVUM1ZNRG9mcnlNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhHakFZQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVgpNQk1HQTFVRUNBd01XblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyCkJnTlZCQW9NSkU5UVRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWlRBZUZ3MHkKTlRBNE1qQXhOalF5TWpaYUZ3MHlOakE1TWpFeE5qUXlNalphTUlHR01Sb3dHQVlEVlFRRERCRlBVRTV6Wlc1egpaUzVwYm5SbGNtNWhiREVMTUFrR0ExVUVCaE1DVGt3eEZUQVRCZ05WQkFnTURGcDFhV1F0U0c5c2JHRnVaREVWCk1CTUdBMVVFQnd3TVRXbGtaR1ZzYUdGeWJtbHpNUzB3S3dZRFZRUUtEQ1JQVUU1elpXNXpaU0J6Wld4bUxYTnAKWjI1bFpDQjNaV0lnWTJWeWRHbG1hV05oZEdVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFEQVozZk1lakdOckZYVFhZd24vTUp5YXBYMDdVOTVWZ0JERzZmWU1wZkZEU1NXeFRValRLRlNvR3JRCkpDb0ZyQ0NRQ3BsWnlua0ZjVFZvWVAraGszcndWWmxHZVFyL0RyR2ZiM1lPc2RGbEtublo5YzRTOGVrSkc2WTIKYWI0eFJTcnBja0hoTWQ4MHNENUFRdks1Skc2MS9UMEhNRXVMcGlraUF3MFpCempWbjlVUUpSRTJiS29kOW9IdgpEbG5DVGNTV1FUNWYrV3A0Sll0anVBVHBZMUlRVW4wbkxuWDBHUC9JRGtsWjFrdEZOczRPOGcrQmRVWFU0MUdvCjNGTFpCc1hQVm90WFVrRVl2R0ZldjJRMlBwSnNib25aWEpoR255amREUlZkZFZGOGhacGJua0wwU2xJTVFNeFQKSTRXK051ZmUvUTZGRDdmZnFRa0toemZ3SlJ2N0dGM2RTRlEwWldPUGNTZVZXY3lWTlVVMTMvcnBteUpvNXZhWQpYR000THcxb1d2c1FjUWxaUllnSkxhSWMzM0dnMGQySHhvZTIvdytIeFRXOEw4ZldqbzgxK250YWJXTlZhV0IwCnd6TXNFNGRBOWtxR2dmcWZjbm96ckovamJtNEFBTTB6QTlVZFh0SUJtRGdpeFNkVzB4eHNQNlZWRTdMdnlURTgKWnBJTjRoL1FCYURyc2hhRXJ6TXhUd01IZXJ1RlV4bFpxZEdSa3ErQXRJU1VwRE05VXB0YWZZMk5ydzgxVDFhSwoycFduVFlFQktCUnVwdk00TzFHNXN5NU5GZm13NFRTc0pqRUhtMFEvVTZ4ZU45bVg0OFhIYUZFNnhvQTJEZEMrCjJHS2lKWFlrYi9nZm13cmp1Y3NGWGpBS2tDNWF6ZXFqaERXeGxDbmJNS1YwSTQxOWp3SURBUUFCbzRJQmREQ0MKQVhBd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0JrQXdOQVlKWUlaSUFZYjRRZ0VOQkNjVwpKVTlRVG5ObGJuTmxJRWRsYm1WeVlYUmxaQ0JUWlhKMlpYSWdRMlZ5ZEdsbWFXTmhkR1V3SFFZRFZSME9CQllFCkZMK2YzU0tCM0tMSi9nWStBUTJIZDBhTzVPRU1NSUd3QmdOVkhTTUVnYWd3Z2FXaGdZeWtnWWt3Z1lZeEdqQVkKQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVk1CTUdBMVVFQ0F3TQpXblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyQmdOVkJBb01KRTlRClRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWllJVUJaUGI1MDFzWjN4YU02c0gKNVQzVk1Eb2ZyeU13SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01Bc0dBMVVkRHdRRQpBd0lGb0RBY0JnTlZIUkVFRlRBVGdoRlBVRTV6Wlc1elpTNXBiblJsY201aGJEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBZ0VBY3F3VEN0RmVwWDlFOEFMYXRaWEE4dkN2b1YwZkxuaElPM0lWT2tZNEhYMTRRRU9NUGo4KzVXMXoKc3hyUGNscEJQVU5tbXJRb0hWUHBuWTRFM2hFanlYY1JWSzZtTEFrSkpRUDJPOEE2NFFpL3FNSkhCYUlEU0MzKwpqMHdkMGYyODRvcEppQ0F2UnF0SGh1bDd3akd4QzNZUXRxWTFMODNWUHBOWGRjOVVsZExTUGZ6WWluMlBPekMrClFDWU9qN3VQUDlCVGExTURudkdPdkdrOHdQeUJGaFZQWVFBWjYwb1ZaS2psOUI2R1piRzF1SG1ON3p3a3k0eEIKNk1RSlF3cHFscDdXQ09QSHJlZTFiVGZaMkJMZFQrZzJHakVMT0xNRGJMTHAzNUw0blBDUmNGRkJZNEszMHBncQpVWjdncEtNTmhuR3huQ29lR3dHUk54ZHFoZnNlT3FqaURVM0hBVDEya3FReU1vOEZNT1g1bG9EaVpXSGZTS0JrClVRaG5Tc1BTMG0vZ2dSRVgwNVQzYWM2NUxNOVVXMEs2MXppZUs5WFhTcjNXWlQ1TkhNV2JmU0VScUR4SGRtZ0YKeGt3YXkxZWpCZTFoZzdGMGpicTVDMGo1ZXB5ZDNOc1BTVUtDY2FDNi9aWTNkUHVYWVZZL0J2dnFsZHZJSzRBeAo0R1BLQ0xzaStGRjliSXZRWG4zbTV2KzJoQWF2WlpxcmJOTFUzVFQ0aDd2QllBOVRNcXpwd3lEaW5BR3RhaEE3CnhDSW5IU01kZXpQcnNkZDJrTW5TckhPdWtGeGdKb2lNZ3krVlJmdTdvZk9NMjhYQ1FySWF6djBFYmFhTU1ZMTcKRzlXOFd3SXZUb2lYY1ZWNTk3K1NUNHRsSTVIM3lDZFBtSk1kRm5GcDg1K2JzbW42MFQwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</crt> | ||||||
|  |     <csr/> | ||||||
|  |     <prv>LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRREFaM2ZNZWpHTnJGWFQKWFl3bi9NSnlhcFgwN1U5NVZnQkRHNmZZTXBmRkRTU1d4VFVqVEtGU29HclFKQ29GckNDUUNwbFp5bmtGY1RWbwpZUCtoazNyd1ZabEdlUXIvRHJHZmIzWU9zZEZsS25uWjljNFM4ZWtKRzZZMmFiNHhSU3JwY2tIaE1kODBzRDVBClF2SzVKRzYxL1QwSE1FdUxwaWtpQXcwWkJ6alZuOVVRSlJFMmJLb2Q5b0h2RGxuQ1RjU1dRVDVmK1dwNEpZdGoKdUFUcFkxSVFVbjBuTG5YMEdQL0lEa2xaMWt0Rk5zNE84ZytCZFVYVTQxR28zRkxaQnNYUFZvdFhVa0VZdkdGZQp2MlEyUHBKc2JvblpYSmhHbnlqZERSVmRkVkY4aFpwYm5rTDBTbElNUU14VEk0VytOdWZlL1E2RkQ3ZmZxUWtLCmh6ZndKUnY3R0YzZFNGUTBaV09QY1NlVldjeVZOVVUxMy9ycG15Sm81dmFZWEdNNEx3MW9XdnNRY1FsWlJZZ0oKTGFJYzMzR2cwZDJIeG9lMi93K0h4VFc4TDhmV2pvODErbnRhYldOVmFXQjB3ek1zRTRkQTlrcUdnZnFmY25vegpySi9qYm00QUFNMHpBOVVkWHRJQm1EZ2l4U2RXMHh4c1A2VlZFN0x2eVRFOFpwSU40aC9RQmFEcnNoYUVyek14ClR3TUhlcnVGVXhsWnFkR1JrcStBdElTVXBETTlVcHRhZlkyTnJ3ODFUMWFLMnBXblRZRUJLQlJ1cHZNNE8xRzUKc3k1TkZmbXc0VFNzSmpFSG0wUS9VNnhlTjltWDQ4WEhhRkU2eG9BMkRkQysyR0tpSlhZa2IvZ2Ztd3JqdWNzRgpYakFLa0M1YXplcWpoRFd4bENuYk1LVjBJNDE5andJREFRQUJBb0lDQUNVVkRBTE4zalVXN09leTFPdDBES251Cm52NDRxcU9SRHJYZ1k2WUlnalhKUmE4RlRTdURmbWdsWU5EQzE1S0dUVFJWeHA2R3BuS0ZFaTBPM05Yd1RiWjYKV1BNN0t3SmplNXBsNmhRRTgzMlRCUzhiNzk2NDN4Z1JTeVNibHJ0NlFENEQ5bXlIcHlSSmY0WDFJVURMbzhiUgppdXlTdzB5ajlyT0djUVRNM29oVnFNUFcwUTF6UGdwT1UxYVdwbmdMY3dNZWlmNEhYUnpRNTUrTmZPemFacHVjCnVtQk4xUS81clhxS1BscmhNVnFpcUc0Nit3QVJjU2NKdE5oZHRsMzdyeTQ1Mk5zNGtERkxSVnowZUVUNEpGSmYKcjVQRUE5bEFuYWlVOS9RdVEwbERtcTlqdmpYRkNURXhYKy82SGJHK2RVd0Y2OEY3ZVEzVFQxbkhHK0hkMVJsbgpOWm1JM0p2d0Z1cG9JeU9VdlpJb3VGVmo2ak8ra0JLejkza1BHWmdMbnNmUUw5WDhRbTU3cjh4K3Z1eFNudGI1CjV4WVBxRkdrOWQrbDUwbTlQakdkekxGT3UwYnJ5TmQ4MFVMS2tuUlFtUVpvTngxck5GTUxpSjNlZENWUS9lclUKT1BDQ0Z0WEJMemJGTjR2ZzVWRjZMUkhvZGxqcEgxRzJOSXNoSzJhc1FuWS9RWDFpUUNLSk1tWERSUndMTWVsNQp3MUF4T2FqYVkzbWx2ZlRVd2xqdkE3a0tFUDBvZzRPeXZldDA2WTVRWk1EQXc1V00yT0pZVDVxcmFlYjZDbTdMCjlNckk4bG50TGp3WFVSZG4yU3U2RCtCWXNpcC9KK3BvOFNqYlJBaGJIc0lJbkJ1QWJnbGxqdTB2QXRXZmFkQlQKOTg4YnUwK3VUb1Q2T1Jkbk84Y1JBb0lCQVFEcStWYkVUQWVpSHN6K29jZnFWV3VwVHJrc2FNNm1wUUMwb0tqZApwb1FzWGVuTmNiNThHQ3FhWHkvdTJHWmNKUnR1QXRHamEyUVpEUUFUSjQyVTFmaTFhWm90Y053eXhPdmlud1NjCmVLZyt0ZGcwdW9LeGs2aXJKRFptaDBIK3Ewblg2RFJYK25RNDVmWVNmRkRFK0ZLd1lac0dQMkhYV3dKaVZ6OE0KU2NkL2pETTFRTWV2OXIzZWx1dS9DWFlvZ1N0N00wMklyczVoNjRuNjFmdVZjNHI4YmUwdFkrUTVsUnlwWk9NVwpkQ2VkWGFOV3RaNjF2bEFxamNiWkpkdXFBUjJjNzAyR3NML201TXA4Zmd3YmY2aG51TXJLaVlpQjlZalZxalc2CmYyUW1PclZtMUk0MFJBMC9OaFBTR2NXejBkNXZrdXY0VHUra2JFbERZTCsxaHY1M0FvSUJBUURSbnZaTmJaa1UKTXpmUTRLWEdML3dLUXJEbjNvL0RENWVBR1ZDTGcwTUkyYlAxYWpubHNVTjE4NCs1UWF6cVVOaWlZT3laODczeQpQYkw0cTBOZWFDYXdxby9WbjJMSkVIUFVTTVhUWjB4ckxTa1hPUjFuMDUwT2tDWXhVbFpOUXFvZU1xcHJGNXZLCm1NNlJxalN4NS8ydU9IUlR1SDRVV2RETEpwTDVUN2RpUCtXcFUwSDlSUWhrNDdkQUJaUjZEZjNxaDJEYmVxUWoKdWcxY0hWUVNqaldhUGpVZGlLR2dHemdvdlE2UkdNZDA1UVUzdkRMdzBCSkNPQ25XV2x0VXkvMW1jMUpPUHR2ZQp4UGltV2tRNmlkRHZ4RGZFRGg5U05zY1FPMnBTVjZxNnhCTWlqVGgvTldGN2NsOU1LYUhJWGxzTmt0RFVXWHZyCmNKRlM4eE1TcDhlcEFvSUJBUUNtWktVTjRxMHhIOUNZckdYT1Nta3Yvc0JnYzJPTFhLTXdSZWp1OVFENkRoTUgKMmZsREZUWHVGV1B6SmlqdUxaVE1CWkVBd1lhanVySUgzbVdETlRhbStMNG1XWnFGRlMvWlRqUk12YUNlcjlVSQpHZDk4OG94cGpQNDlBcUU0UDRIT00vQUZNU1ZtT1dwVTB0VzdkZ0hRUjM0cElXOGV1cUxva3RIaDJNaytTRURuCkFCV29SUGxWaTlncmN2N0tWaFk5YXlvSGxZb3VpMFl0YTZSNXc5VnpSa0REZU01Zi9Iak1kOVhieTZ0VjQ3NU0KSTliYzZvVUliVmVYNUJnMnZnMkRXVzZ6NTZ3dFRHMGJWWU1yWWU0V2JTU2w0bGpaZHM5TVJ2ay9OUUR0bFh0cAo4ekUwVDlCMXA4ekhabHE3S08zMFlyMVpIRVRWVVoxYjZrSTN3UDJuQW9JQkFFZ1VGKzlCMjF4RnpGQ0hucGtLClVPa2FTNGcvVUVHcmI5VzlYcVBLUzllVVBEd0wvY0tNZEh6dmRpRW1neFhESE9xZzExcU1wR2pTYkdMelNPUUMKZmlOTFV0QUswVVgvNFVSQ2pidUdqcEZmNHZ3NFNITTJJWkFyWXVhY3dFNHF1U0pQRzZoZFl0V0VPNnQ4MGtmRwpWTVYrWmdtUHE5TEZtM1R2VzZSY2s5czF5M3V3eEVVWllxeUdYTEduK1lrS25KL3pVd3ZGSFFHbjdRWWFrNWtaCnl6YXhZMFEzZ2hQeXFCbmlBRXRHTVBkeDlKeFltMCtRekdaMnQzUWNkOEV0cjRGMTcvdzF3eGJUdGdoRmk2WngKVXlYTzI3b1BmUmVnL0V3SmtpS2tRSEdlRUZKV0t2SWE0ZDAzMDZyMXVjcVRIMDRJaU1RcnpOK0ZRb002VC9tZgpOWmtDZ2dFQUsrRVJNVVdJZTE3V1k3VDIycy9lOEplN0xxSXlUcU9mSGovaWFUUjhHbXhqcU1HNEdod1RpVXJsCkh0Skhud3BMVGFjVmdYUjV3UmtYcEhRT2JqSUFzeVNBUGxwSzBvZUkyK2kvS0cyQjZ2U0cza0V2b1VZY0RlRk4KdzhHd0oxNDNTd21LQXM4eUtWMmd1RjhmRXNNVitEQzNzVHFlZXJmMy82bFprMUVCVFF0QTZqVHdqK0picXgwVgpaalZJUXBwUE8vc1VHdi9LZVE3MW5ockJpT0lXclJ0dDRTUDJ2aWx2em9DUTQxVjFqZ09wS3VwU3E1Y2J3VDRxCmp1bkJIMkx5VnNQaUc4M0Vha1JSUEhDK0craTk1MFJxckxVRUJOeVdHNGlMNTdUdU9xYVJuSmRnN2ZFb2lVLzMKNld4TjlvR2VRWjV0NjZkdTJEL01WSUZ4ZzJ1cXRBPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=</prv> | ||||||
|  |   </cert> | ||||||
|  |   <syslog/> | ||||||
|  | </opnsense> | ||||||
							
								
								
									
										826
									
								
								opnsense-config/src/tests/data/config-full-25.7.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										826
									
								
								opnsense-config/src/tests/data/config-full-25.7.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,826 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <opnsense> | ||||||
|  |   <theme>opnsense</theme> | ||||||
|  |   <sysctl version="1.0.1" persisted_at="1755708111.39"> | ||||||
|  |     <item/> | ||||||
|  |   </sysctl> | ||||||
|  |   <system> | ||||||
|  |     <serialspeed>115200</serialspeed> | ||||||
|  |     <primaryconsole>serial</primaryconsole> | ||||||
|  |     <optimization>normal</optimization> | ||||||
|  |     <hostname>OPNsense</hostname> | ||||||
|  |     <domain>internal</domain> | ||||||
|  |     <dnsallowoverride>1</dnsallowoverride> | ||||||
|  |     <dnsallowoverride_exclude/> | ||||||
|  |     <group uuid="67305f6f-7f7a-454d-8a4e-65cb8f072d81"> | ||||||
|  |       <gid>1999</gid> | ||||||
|  |       <name>admins</name> | ||||||
|  |       <scope>system</scope> | ||||||
|  |       <description>System Administrators</description> | ||||||
|  |       <priv>page-all</priv> | ||||||
|  |       <member>0</member> | ||||||
|  |       <source_networks/> | ||||||
|  |     </group> | ||||||
|  |     <user uuid="1d2ed537-5d1a-4772-9600-37b93f9f798b"> | ||||||
|  |       <uid>0</uid> | ||||||
|  |       <name>root</name> | ||||||
|  |       <disabled>0</disabled> | ||||||
|  |       <scope>system</scope> | ||||||
|  |       <expires/> | ||||||
|  |       <authorizedkeys/> | ||||||
|  |       <otp_seed/> | ||||||
|  |       <shell/> | ||||||
|  |       <password>$2y$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS</password> | ||||||
|  |       <pwd_changed_at/> | ||||||
|  |       <landing_page/> | ||||||
|  |       <comment/> | ||||||
|  |       <email/> | ||||||
|  |       <apikeys/> | ||||||
|  |       <priv/> | ||||||
|  |       <language/> | ||||||
|  |       <descr>System Administrator</descr> | ||||||
|  |       <dashboard/> | ||||||
|  |     </user> | ||||||
|  |     <timezone>Etc/UTC</timezone> | ||||||
|  |     <timeservers>0.opnsense.pool.ntp.org 1.opnsense.pool.ntp.org 2.opnsense.pool.ntp.org 3.opnsense.pool.ntp.org</timeservers> | ||||||
|  |     <webgui> | ||||||
|  |       <protocol>https</protocol> | ||||||
|  |       <ssl-certref>68a5faf1685db</ssl-certref> | ||||||
|  |       <port/> | ||||||
|  |       <ssl-ciphers/> | ||||||
|  |       <interfaces/> | ||||||
|  |       <compression/> | ||||||
|  |     </webgui> | ||||||
|  |     <disablenatreflection>yes</disablenatreflection> | ||||||
|  |     <usevirtualterminal>1</usevirtualterminal> | ||||||
|  |     <disableconsolemenu>1</disableconsolemenu> | ||||||
|  |     <disablevlanhwfilter>1</disablevlanhwfilter> | ||||||
|  |     <disablechecksumoffloading>1</disablechecksumoffloading> | ||||||
|  |     <disablesegmentationoffloading>1</disablesegmentationoffloading> | ||||||
|  |     <disablelargereceiveoffloading>1</disablelargereceiveoffloading> | ||||||
|  |     <ipv6allow>1</ipv6allow> | ||||||
|  |     <powerd_ac_mode>hadp</powerd_ac_mode> | ||||||
|  |     <powerd_battery_mode>hadp</powerd_battery_mode> | ||||||
|  |     <powerd_normal_mode>hadp</powerd_normal_mode> | ||||||
|  |     <bogons> | ||||||
|  |       <interval>monthly</interval> | ||||||
|  |     </bogons> | ||||||
|  |     <pf_share_forward>1</pf_share_forward> | ||||||
|  |     <lb_use_sticky>1</lb_use_sticky> | ||||||
|  |     <ssh> | ||||||
|  |       <group>admins</group> | ||||||
|  |       <noauto>1</noauto> | ||||||
|  |       <interfaces/> | ||||||
|  |       <kex/> | ||||||
|  |       <ciphers/> | ||||||
|  |       <macs/> | ||||||
|  |       <keys/> | ||||||
|  |       <keysig/> | ||||||
|  |       <rekeylimit/> | ||||||
|  |       <enabled>enabled</enabled> | ||||||
|  |       <passwordauth>1</passwordauth> | ||||||
|  |       <permitrootlogin>1</permitrootlogin> | ||||||
|  |     </ssh> | ||||||
|  |     <rrdbackup>-1</rrdbackup> | ||||||
|  |     <netflowbackup>-1</netflowbackup> | ||||||
|  |     <firmware version="1.0.1" persisted_at="1755708111.32"> | ||||||
|  |       <mirror/> | ||||||
|  |       <flavour/> | ||||||
|  |       <plugins/> | ||||||
|  |       <type/> | ||||||
|  |       <subscription/> | ||||||
|  |       <reboot>0</reboot> | ||||||
|  |     </firmware> | ||||||
|  |     <dnsserver/> | ||||||
|  |     <language>en_US</language> | ||||||
|  |   </system> | ||||||
|  |   <interfaces> | ||||||
|  |     <wan> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <if>vtnet0</if> | ||||||
|  |       <mtu/> | ||||||
|  |       <ipaddr>dhcp</ipaddr> | ||||||
|  |       <ipaddrv6>dhcp6</ipaddrv6> | ||||||
|  |       <subnet/> | ||||||
|  |       <gateway/> | ||||||
|  |       <blockpriv>0</blockpriv> | ||||||
|  |       <blockbogons>1</blockbogons> | ||||||
|  |       <media/> | ||||||
|  |       <mediaopt/> | ||||||
|  |       <dhcp6-ia-pd-len>0</dhcp6-ia-pd-len> | ||||||
|  |       <dhcphostname/> | ||||||
|  |       <spoofmac/> | ||||||
|  |       <mss/> | ||||||
|  |     </wan> | ||||||
|  |     <lan> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <if>vtnet1</if> | ||||||
|  |       <ipaddr>192.168.1.1</ipaddr> | ||||||
|  |       <subnet>24</subnet> | ||||||
|  |       <ipaddrv6>track6</ipaddrv6> | ||||||
|  |       <subnetv6>64</subnetv6> | ||||||
|  |       <media/> | ||||||
|  |       <mediaopt/> | ||||||
|  |       <track6-interface>wan</track6-interface> | ||||||
|  |       <track6-prefix-id>0</track6-prefix-id> | ||||||
|  |     </lan> | ||||||
|  |     <lo0> | ||||||
|  |       <internal_dynamic>1</internal_dynamic> | ||||||
|  |       <descr>Loopback</descr> | ||||||
|  |       <enable>1</enable> | ||||||
|  |       <if>lo0</if> | ||||||
|  |       <ipaddr>127.0.0.1</ipaddr> | ||||||
|  |       <ipaddrv6>::1</ipaddrv6> | ||||||
|  |       <subnet>8</subnet> | ||||||
|  |       <subnetv6>128</subnetv6> | ||||||
|  |       <type>none</type> | ||||||
|  |       <virtual>1</virtual> | ||||||
|  |     </lo0> | ||||||
|  |   </interfaces> | ||||||
|  |   <dnsmasq version="1.0.7" persisted_at="1755721633.54"> | ||||||
|  |     <enable>1</enable> | ||||||
|  |     <regdhcp>0</regdhcp> | ||||||
|  |     <regdhcpstatic>0</regdhcpstatic> | ||||||
|  |     <dhcpfirst>0</dhcpfirst> | ||||||
|  |     <strict_order>0</strict_order> | ||||||
|  |     <domain_needed>0</domain_needed> | ||||||
|  |     <no_private_reverse>0</no_private_reverse> | ||||||
|  |     <no_resolv>0</no_resolv> | ||||||
|  |     <log_queries>0</log_queries> | ||||||
|  |     <no_hosts>0</no_hosts> | ||||||
|  |     <strictbind>0</strictbind> | ||||||
|  |     <dnssec>0</dnssec> | ||||||
|  |     <regdhcpdomain/> | ||||||
|  |     <interface>lan</interface> | ||||||
|  |     <port>0</port> | ||||||
|  |     <dns_forward_max/> | ||||||
|  |     <cache_size/> | ||||||
|  |     <local_ttl/> | ||||||
|  |     <add_mac/> | ||||||
|  |     <add_subnet>0</add_subnet> | ||||||
|  |     <strip_subnet>0</strip_subnet> | ||||||
|  |     <dhcp> | ||||||
|  |       <no_interface/> | ||||||
|  |       <fqdn>1</fqdn> | ||||||
|  |       <domain/> | ||||||
|  |       <lease_max/> | ||||||
|  |       <authoritative>0</authoritative> | ||||||
|  |       <default_fw_rules>1</default_fw_rules> | ||||||
|  |       <reply_delay/> | ||||||
|  |       <enable_ra>0</enable_ra> | ||||||
|  |       <nosync>0</nosync> | ||||||
|  |     </dhcp> | ||||||
|  |     <no_ident>1</no_ident> | ||||||
|  |     <dhcp_ranges uuid="78b5c4a4-565d-4cd7-af10-29050f29e494"> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <set_tag/> | ||||||
|  |       <start_addr>192.168.1.41</start_addr> | ||||||
|  |       <end_addr>192.168.1.245</end_addr> | ||||||
|  |       <subnet_mask/> | ||||||
|  |       <constructor/> | ||||||
|  |       <mode/> | ||||||
|  |       <prefix_len/> | ||||||
|  |       <lease_time/> | ||||||
|  |       <domain_type>range</domain_type> | ||||||
|  |       <domain/> | ||||||
|  |       <nosync>0</nosync> | ||||||
|  |       <ra_mode/> | ||||||
|  |       <ra_priority/> | ||||||
|  |       <ra_mtu/> | ||||||
|  |       <ra_interval/> | ||||||
|  |       <ra_router_lifetime/> | ||||||
|  |       <description/> | ||||||
|  |     </dhcp_ranges> | ||||||
|  |   </dnsmasq> | ||||||
|  |   <snmpd> | ||||||
|  |     <syslocation/> | ||||||
|  |     <syscontact/> | ||||||
|  |     <rocommunity>public</rocommunity> | ||||||
|  |   </snmpd> | ||||||
|  |   <nat> | ||||||
|  |     <outbound> | ||||||
|  |       <mode>automatic</mode> | ||||||
|  |     </outbound> | ||||||
|  |   </nat> | ||||||
|  |   <filter> | ||||||
|  |     <rule> | ||||||
|  |       <type>pass</type> | ||||||
|  |       <ipprotocol>inet</ipprotocol> | ||||||
|  |       <descr>Default allow LAN to any rule</descr> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <source> | ||||||
|  |         <network>lan</network> | ||||||
|  |       </source> | ||||||
|  |       <destination> | ||||||
|  |         <any/> | ||||||
|  |       </destination> | ||||||
|  |     </rule> | ||||||
|  |     <rule> | ||||||
|  |       <type>pass</type> | ||||||
|  |       <ipprotocol>inet6</ipprotocol> | ||||||
|  |       <descr>Default allow LAN IPv6 to any rule</descr> | ||||||
|  |       <interface>lan</interface> | ||||||
|  |       <source> | ||||||
|  |         <network>lan</network> | ||||||
|  |       </source> | ||||||
|  |       <destination> | ||||||
|  |         <any/> | ||||||
|  |       </destination> | ||||||
|  |     </rule> | ||||||
|  |   </filter> | ||||||
|  |   <rrd> | ||||||
|  |     <enable/> | ||||||
|  |   </rrd> | ||||||
|  |   <ntpd> | ||||||
|  |     <prefer>0.opnsense.pool.ntp.org</prefer> | ||||||
|  |   </ntpd> | ||||||
|  |   <revision> | ||||||
|  |     <username>root@192.168.1.5</username> | ||||||
|  |     <description>/system_advanced_admin.php made changes</description> | ||||||
|  |     <time>1755721653.06</time> | ||||||
|  |   </revision> | ||||||
|  |   <OPNsense> | ||||||
|  |     <wireguard> | ||||||
|  |       <client version="1.0.0" persisted_at="1755708111.04"> | ||||||
|  |         <clients/> | ||||||
|  |       </client> | ||||||
|  |       <general version="0.0.1" persisted_at="1755708111.05"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |       </general> | ||||||
|  |       <server version="1.0.0" persisted_at="1755708111.05"> | ||||||
|  |         <servers/> | ||||||
|  |       </server> | ||||||
|  |     </wireguard> | ||||||
|  |     <IPsec version="1.0.4" persisted_at="1755708111.06"> | ||||||
|  |       <general> | ||||||
|  |         <enabled/> | ||||||
|  |         <preferred_oldsa>0</preferred_oldsa> | ||||||
|  |         <disablevpnrules>0</disablevpnrules> | ||||||
|  |         <passthrough_networks/> | ||||||
|  |         <user_source/> | ||||||
|  |         <local_group/> | ||||||
|  |       </general> | ||||||
|  |       <charon> | ||||||
|  |         <max_ikev1_exchanges/> | ||||||
|  |         <threads>16</threads> | ||||||
|  |         <ikesa_table_size>32</ikesa_table_size> | ||||||
|  |         <ikesa_table_segments>4</ikesa_table_segments> | ||||||
|  |         <init_limit_half_open>1000</init_limit_half_open> | ||||||
|  |         <ignore_acquire_ts>1</ignore_acquire_ts> | ||||||
|  |         <install_routes>0</install_routes> | ||||||
|  |         <cisco_unity>0</cisco_unity> | ||||||
|  |         <make_before_break>0</make_before_break> | ||||||
|  |         <retransmit_tries/> | ||||||
|  |         <retransmit_timeout/> | ||||||
|  |         <retransmit_base/> | ||||||
|  |         <retransmit_jitter/> | ||||||
|  |         <retransmit_limit/> | ||||||
|  |         <syslog> | ||||||
|  |           <daemon> | ||||||
|  |             <ike_name>1</ike_name> | ||||||
|  |             <log_level>0</log_level> | ||||||
|  |             <app>1</app> | ||||||
|  |             <asn>1</asn> | ||||||
|  |             <cfg>1</cfg> | ||||||
|  |             <chd>1</chd> | ||||||
|  |             <dmn>1</dmn> | ||||||
|  |             <enc>1</enc> | ||||||
|  |             <esp>1</esp> | ||||||
|  |             <ike>1</ike> | ||||||
|  |             <imc>1</imc> | ||||||
|  |             <imv>1</imv> | ||||||
|  |             <job>1</job> | ||||||
|  |             <knl>1</knl> | ||||||
|  |             <lib>1</lib> | ||||||
|  |             <mgr>1</mgr> | ||||||
|  |             <net>1</net> | ||||||
|  |             <pts>1</pts> | ||||||
|  |             <tls>1</tls> | ||||||
|  |             <tnc>1</tnc> | ||||||
|  |           </daemon> | ||||||
|  |         </syslog> | ||||||
|  |         <plugins> | ||||||
|  |           <attr> | ||||||
|  |             <subnet/> | ||||||
|  |             <split-include/> | ||||||
|  |             <x_28674/> | ||||||
|  |             <x_28675/> | ||||||
|  |             <x_28672/> | ||||||
|  |             <x_28673>0</x_28673> | ||||||
|  |             <x_28679/> | ||||||
|  |             <dns/> | ||||||
|  |             <nbns/> | ||||||
|  |           </attr> | ||||||
|  |           <eap-radius> | ||||||
|  |             <servers/> | ||||||
|  |             <accounting>0</accounting> | ||||||
|  |             <class_group>0</class_group> | ||||||
|  |           </eap-radius> | ||||||
|  |           <xauth-pam> | ||||||
|  |             <pam_service>ipsec</pam_service> | ||||||
|  |             <session>0</session> | ||||||
|  |             <trim_email>1</trim_email> | ||||||
|  |           </xauth-pam> | ||||||
|  |         </plugins> | ||||||
|  |       </charon> | ||||||
|  |       <keyPairs/> | ||||||
|  |       <preSharedKeys/> | ||||||
|  |     </IPsec> | ||||||
|  |     <Swanctl version="1.0.0" persisted_at="1755708111.08"> | ||||||
|  |       <Connections/> | ||||||
|  |       <locals/> | ||||||
|  |       <remotes/> | ||||||
|  |       <children/> | ||||||
|  |       <Pools/> | ||||||
|  |       <VTIs/> | ||||||
|  |       <SPDs/> | ||||||
|  |     </Swanctl> | ||||||
|  |     <OpenVPNExport version="0.0.1" persisted_at="1755708111.40"> | ||||||
|  |       <servers/> | ||||||
|  |     </OpenVPNExport> | ||||||
|  |     <OpenVPN version="1.0.1" persisted_at="1755708111.40"> | ||||||
|  |       <Overwrites/> | ||||||
|  |       <Instances/> | ||||||
|  |       <StaticKeys/> | ||||||
|  |     </OpenVPN> | ||||||
|  |     <captiveportal version="1.0.4" persisted_at="1755708111.41"> | ||||||
|  |       <zones/> | ||||||
|  |       <templates/> | ||||||
|  |     </captiveportal> | ||||||
|  |     <cron version="1.0.4" persisted_at="1755708111.43"> | ||||||
|  |       <jobs/> | ||||||
|  |     </cron> | ||||||
|  |     <DHCRelay version="1.0.1" persisted_at="1755708111.43"/> | ||||||
|  |     <Firewall> | ||||||
|  |       <Lvtemplate version="0.0.1" persisted_at="1755708111.45"> | ||||||
|  |         <templates/> | ||||||
|  |       </Lvtemplate> | ||||||
|  |       <Alias version="1.0.1" persisted_at="1755708111.65"> | ||||||
|  |         <geoip> | ||||||
|  |           <url/> | ||||||
|  |         </geoip> | ||||||
|  |         <aliases/> | ||||||
|  |       </Alias> | ||||||
|  |       <Category version="1.0.0" persisted_at="1755708111.65"> | ||||||
|  |         <categories/> | ||||||
|  |       </Category> | ||||||
|  |       <Filter version="1.0.4" persisted_at="1755708111.70"> | ||||||
|  |         <rules/> | ||||||
|  |         <snatrules/> | ||||||
|  |         <npt/> | ||||||
|  |         <onetoone/> | ||||||
|  |       </Filter> | ||||||
|  |     </Firewall> | ||||||
|  |     <Netflow version="1.0.1" persisted_at="1755708111.45"> | ||||||
|  |       <capture> | ||||||
|  |         <interfaces/> | ||||||
|  |         <egress_only/> | ||||||
|  |         <version>v9</version> | ||||||
|  |         <targets/> | ||||||
|  |       </capture> | ||||||
|  |       <collect> | ||||||
|  |         <enable>0</enable> | ||||||
|  |       </collect> | ||||||
|  |       <activeTimeout>1800</activeTimeout> | ||||||
|  |       <inactiveTimeout>15</inactiveTimeout> | ||||||
|  |     </Netflow> | ||||||
|  |     <IDS version="1.1.0" persisted_at="1755708111.90"> | ||||||
|  |       <rules/> | ||||||
|  |       <policies/> | ||||||
|  |       <userDefinedRules/> | ||||||
|  |       <files/> | ||||||
|  |       <fileTags/> | ||||||
|  |       <general> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <ips>0</ips> | ||||||
|  |         <promisc>0</promisc> | ||||||
|  |         <interfaces>wan</interfaces> | ||||||
|  |         <homenet>192.168.0.0/16,10.0.0.0/8,172.16.0.0/12</homenet> | ||||||
|  |         <defaultPacketSize/> | ||||||
|  |         <UpdateCron/> | ||||||
|  |         <AlertLogrotate>W0D23</AlertLogrotate> | ||||||
|  |         <AlertSaveLogs>4</AlertSaveLogs> | ||||||
|  |         <MPMAlgo/> | ||||||
|  |         <detect> | ||||||
|  |           <Profile/> | ||||||
|  |           <toclient_groups/> | ||||||
|  |           <toserver_groups/> | ||||||
|  |         </detect> | ||||||
|  |         <syslog>0</syslog> | ||||||
|  |         <syslog_eve>0</syslog_eve> | ||||||
|  |         <LogPayload>0</LogPayload> | ||||||
|  |         <verbosity/> | ||||||
|  |         <eveLog> | ||||||
|  |           <http> | ||||||
|  |             <enable>0</enable> | ||||||
|  |             <extended>0</extended> | ||||||
|  |             <dumpAllHeaders/> | ||||||
|  |           </http> | ||||||
|  |           <tls> | ||||||
|  |             <enable>0</enable> | ||||||
|  |             <extended>0</extended> | ||||||
|  |             <sessionResumption>0</sessionResumption> | ||||||
|  |             <custom/> | ||||||
|  |           </tls> | ||||||
|  |         </eveLog> | ||||||
|  |       </general> | ||||||
|  |     </IDS> | ||||||
|  |     <Interfaces> | ||||||
|  |       <loopbacks version="1.0.0" persisted_at="1755708111.95"/> | ||||||
|  |       <neighbors version="1.0.0" persisted_at="1755708111.96"/> | ||||||
|  |       <vxlans version="1.0.2" persisted_at="1755708111.99"/> | ||||||
|  |     </Interfaces> | ||||||
|  |     <Kea> | ||||||
|  |       <ctrl_agent version="0.0.1" persisted_at="1755708111.99"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <http_host>127.0.0.1</http_host> | ||||||
|  |           <http_port>8000</http_port> | ||||||
|  |         </general> | ||||||
|  |       </ctrl_agent> | ||||||
|  |       <dhcp4 version="1.0.4" persisted_at="1755708112.00"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <manual_config>0</manual_config> | ||||||
|  |           <interfaces/> | ||||||
|  |           <valid_lifetime>4000</valid_lifetime> | ||||||
|  |           <fwrules>1</fwrules> | ||||||
|  |           <dhcp_socket_type>raw</dhcp_socket_type> | ||||||
|  |         </general> | ||||||
|  |         <ha> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <this_server_name/> | ||||||
|  |           <max_unacked_clients>2</max_unacked_clients> | ||||||
|  |         </ha> | ||||||
|  |         <subnets/> | ||||||
|  |         <reservations/> | ||||||
|  |         <ha_peers/> | ||||||
|  |       </dhcp4> | ||||||
|  |       <dhcp6 version="1.0.0" persisted_at="1755708112.00"> | ||||||
|  |         <general> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <manual_config>0</manual_config> | ||||||
|  |           <interfaces/> | ||||||
|  |           <valid_lifetime>4000</valid_lifetime> | ||||||
|  |           <fwrules>1</fwrules> | ||||||
|  |         </general> | ||||||
|  |         <ha> | ||||||
|  |           <enabled>0</enabled> | ||||||
|  |           <this_server_name/> | ||||||
|  |           <max_unacked_clients>2</max_unacked_clients> | ||||||
|  |         </ha> | ||||||
|  |         <subnets/> | ||||||
|  |         <reservations/> | ||||||
|  |         <pd_pools/> | ||||||
|  |         <ha_peers/> | ||||||
|  |       </dhcp6> | ||||||
|  |     </Kea> | ||||||
|  |     <monit version="1.0.13" persisted_at="1755708112.02"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <interval>120</interval> | ||||||
|  |         <startdelay>120</startdelay> | ||||||
|  |         <mailserver>127.0.0.1</mailserver> | ||||||
|  |         <port>25</port> | ||||||
|  |         <username/> | ||||||
|  |         <password/> | ||||||
|  |         <ssl>0</ssl> | ||||||
|  |         <sslversion>auto</sslversion> | ||||||
|  |         <sslverify>1</sslverify> | ||||||
|  |         <logfile/> | ||||||
|  |         <statefile/> | ||||||
|  |         <eventqueuePath/> | ||||||
|  |         <eventqueueSlots/> | ||||||
|  |         <httpdEnabled>0</httpdEnabled> | ||||||
|  |         <httpdUsername>root</httpdUsername> | ||||||
|  |         <httpdPassword/> | ||||||
|  |         <httpdPort>2812</httpdPort> | ||||||
|  |         <httpdAllow/> | ||||||
|  |         <mmonitUrl/> | ||||||
|  |         <mmonitTimeout>5</mmonitTimeout> | ||||||
|  |         <mmonitRegisterCredentials>1</mmonitRegisterCredentials> | ||||||
|  |       </general> | ||||||
|  |       <alert uuid="76cd5195-a487-4f4f-8ef5-4f9815bf19e5"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <recipient>root@localhost.local</recipient> | ||||||
|  |         <noton>0</noton> | ||||||
|  |         <events/> | ||||||
|  |         <format/> | ||||||
|  |         <reminder/> | ||||||
|  |         <description/> | ||||||
|  |       </alert> | ||||||
|  |       <service uuid="11610907-f700-4f0a-9926-c25a47709493"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <name>$HOST</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>system</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path/> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>02014be3-fc31-4af3-a0d5-061eaa67d28a,ebfd0d97-ae21-45d5-8b42-5220c75ce46f,d37f25f0-89e3-44b6-8ad2-280ac83a8904,37afd0d9-990c-4f03-a817-45691461e3d0</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="dcc7e42f-a878-49a3-8b90-67faf21d67bd"> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <name>RootFs</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>filesystem</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>b44b859c-bc72-4c2e-82c9-4f56d84a5497</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="5ca544d4-846a-4f61-9d6f-c191eafcd9fb"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <name>carp_status_change</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>custom</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/usr/local/opnsense/scripts/OPNsense/Monit/carp_status</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>0909801b-cd11-41c8-afeb-369396247308</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <service uuid="6e16e26e-e732-4b9b-ac92-2086b84f3158"> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <name>gateway_alert</name> | ||||||
|  |         <description/> | ||||||
|  |         <type>custom</type> | ||||||
|  |         <pidfile/> | ||||||
|  |         <match/> | ||||||
|  |         <path>/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert</path> | ||||||
|  |         <timeout>300</timeout> | ||||||
|  |         <starttimeout>30</starttimeout> | ||||||
|  |         <address/> | ||||||
|  |         <interface/> | ||||||
|  |         <start/> | ||||||
|  |         <stop/> | ||||||
|  |         <tests>56e67d76-cef6-4167-a51e-2c69a921ebc9</tests> | ||||||
|  |         <depends/> | ||||||
|  |         <polltime/> | ||||||
|  |       </service> | ||||||
|  |       <test uuid="b7671673-5cf1-4123-a58c-37f57a8e9d59"> | ||||||
|  |         <name>Ping</name> | ||||||
|  |         <type>NetworkPing</type> | ||||||
|  |         <condition>failed ping</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="30cf3664-5c6f-4ac5-a52a-36023270c6fb"> | ||||||
|  |         <name>NetworkLink</name> | ||||||
|  |         <type>NetworkInterface</type> | ||||||
|  |         <condition>failed link</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="a7c0836a-b102-4f37-a73b-2a50903ffcc8"> | ||||||
|  |         <name>NetworkSaturation</name> | ||||||
|  |         <type>NetworkInterface</type> | ||||||
|  |         <condition>saturation is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="02014be3-fc31-4af3-a0d5-061eaa67d28a"> | ||||||
|  |         <name>MemoryUsage</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>memory usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="ebfd0d97-ae21-45d5-8b42-5220c75ce46f"> | ||||||
|  |         <name>CPUUsage</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>cpu usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="d37f25f0-89e3-44b6-8ad2-280ac83a8904"> | ||||||
|  |         <name>LoadAvg1</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (1min) is greater than 4</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="37afd0d9-990c-4f03-a817-45691461e3d0"> | ||||||
|  |         <name>LoadAvg5</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (5min) is greater than 3</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="608d2888-9df5-486d-bbfb-bf17fad75a7e"> | ||||||
|  |         <name>LoadAvg15</name> | ||||||
|  |         <type>SystemResource</type> | ||||||
|  |         <condition>loadavg (15min) is greater than 2</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="b44b859c-bc72-4c2e-82c9-4f56d84a5497"> | ||||||
|  |         <name>SpaceUsage</name> | ||||||
|  |         <type>SpaceUsage</type> | ||||||
|  |         <condition>space usage is greater than 75%</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="0909801b-cd11-41c8-afeb-369396247308"> | ||||||
|  |         <name>ChangedStatus</name> | ||||||
|  |         <type>ProgramStatus</type> | ||||||
|  |         <condition>changed status</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |       <test uuid="56e67d76-cef6-4167-a51e-2c69a921ebc9"> | ||||||
|  |         <name>NonZeroStatus</name> | ||||||
|  |         <type>ProgramStatus</type> | ||||||
|  |         <condition>status != 0</condition> | ||||||
|  |         <action>alert</action> | ||||||
|  |         <path/> | ||||||
|  |       </test> | ||||||
|  |     </monit> | ||||||
|  |     <Gateways version="1.0.0" persisted_at="1755721633.53"/> | ||||||
|  |     <Syslog version="1.0.2" persisted_at="1755708112.05"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <loglocal>1</loglocal> | ||||||
|  |         <maxpreserve>31</maxpreserve> | ||||||
|  |         <maxfilesize/> | ||||||
|  |       </general> | ||||||
|  |       <destinations/> | ||||||
|  |     </Syslog> | ||||||
|  |     <TrafficShaper version="1.0.3" persisted_at="1755708112.06"> | ||||||
|  |       <pipes/> | ||||||
|  |       <queues/> | ||||||
|  |       <rules/> | ||||||
|  |     </TrafficShaper> | ||||||
|  |     <trust> | ||||||
|  |       <general version="1.0.1" persisted_at="1755708112.22"> | ||||||
|  |         <store_intermediate_certs>0</store_intermediate_certs> | ||||||
|  |         <install_crls>0</install_crls> | ||||||
|  |         <fetch_crls>0</fetch_crls> | ||||||
|  |         <enable_legacy_sect>1</enable_legacy_sect> | ||||||
|  |         <enable_config_constraints>0</enable_config_constraints> | ||||||
|  |         <CipherString/> | ||||||
|  |         <Ciphersuites/> | ||||||
|  |         <SignatureAlgorithms/> | ||||||
|  |         <groups/> | ||||||
|  |         <MinProtocol/> | ||||||
|  |         <MinProtocol_DTLS/> | ||||||
|  |       </general> | ||||||
|  |     </trust> | ||||||
|  |     <unboundplus version="1.0.12" persisted_at="1755708112.29"> | ||||||
|  |       <general> | ||||||
|  |         <enabled>1</enabled> | ||||||
|  |         <port>53</port> | ||||||
|  |         <stats>0</stats> | ||||||
|  |         <active_interface/> | ||||||
|  |         <dnssec>0</dnssec> | ||||||
|  |         <dns64>0</dns64> | ||||||
|  |         <dns64prefix/> | ||||||
|  |         <noarecords>0</noarecords> | ||||||
|  |         <regdhcp>0</regdhcp> | ||||||
|  |         <regdhcpdomain/> | ||||||
|  |         <regdhcpstatic>0</regdhcpstatic> | ||||||
|  |         <noreglladdr6>0</noreglladdr6> | ||||||
|  |         <noregrecords>0</noregrecords> | ||||||
|  |         <txtsupport>0</txtsupport> | ||||||
|  |         <cacheflush>0</cacheflush> | ||||||
|  |         <local_zone_type>transparent</local_zone_type> | ||||||
|  |         <outgoing_interface/> | ||||||
|  |         <enable_wpad>0</enable_wpad> | ||||||
|  |       </general> | ||||||
|  |       <advanced> | ||||||
|  |         <hideidentity>0</hideidentity> | ||||||
|  |         <hideversion>0</hideversion> | ||||||
|  |         <prefetch>0</prefetch> | ||||||
|  |         <prefetchkey>0</prefetchkey> | ||||||
|  |         <dnssecstripped>0</dnssecstripped> | ||||||
|  |         <aggressivensec>1</aggressivensec> | ||||||
|  |         <serveexpired>0</serveexpired> | ||||||
|  |         <serveexpiredreplyttl/> | ||||||
|  |         <serveexpiredttl/> | ||||||
|  |         <serveexpiredttlreset>0</serveexpiredttlreset> | ||||||
|  |         <serveexpiredclienttimeout/> | ||||||
|  |         <qnameminstrict>0</qnameminstrict> | ||||||
|  |         <extendedstatistics>0</extendedstatistics> | ||||||
|  |         <logqueries>0</logqueries> | ||||||
|  |         <logreplies>0</logreplies> | ||||||
|  |         <logtagqueryreply>0</logtagqueryreply> | ||||||
|  |         <logservfail>0</logservfail> | ||||||
|  |         <loglocalactions>0</loglocalactions> | ||||||
|  |         <logverbosity>1</logverbosity> | ||||||
|  |         <valloglevel>0</valloglevel> | ||||||
|  |         <privatedomain/> | ||||||
|  |         <privateaddress>0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,169.254.0.0/16,172.16.0.0/12,192.0.2.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,233.252.0.0/24,::1/128,2001:db8::/32,fc00::/8,fd00::/8,fe80::/10</privateaddress> | ||||||
|  |         <insecuredomain/> | ||||||
|  |         <msgcachesize/> | ||||||
|  |         <rrsetcachesize/> | ||||||
|  |         <outgoingnumtcp/> | ||||||
|  |         <incomingnumtcp/> | ||||||
|  |         <numqueriesperthread/> | ||||||
|  |         <outgoingrange/> | ||||||
|  |         <jostletimeout/> | ||||||
|  |         <discardtimeout/> | ||||||
|  |         <cachemaxttl/> | ||||||
|  |         <cachemaxnegativettl/> | ||||||
|  |         <cacheminttl/> | ||||||
|  |         <infrahostttl/> | ||||||
|  |         <infrakeepprobing>0</infrakeepprobing> | ||||||
|  |         <infracachenumhosts/> | ||||||
|  |         <unwantedreplythreshold/> | ||||||
|  |       </advanced> | ||||||
|  |       <acls> | ||||||
|  |         <default_action>allow</default_action> | ||||||
|  |       </acls> | ||||||
|  |       <dnsbl> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |         <safesearch>0</safesearch> | ||||||
|  |         <type/> | ||||||
|  |         <lists/> | ||||||
|  |         <whitelists/> | ||||||
|  |         <blocklists/> | ||||||
|  |         <wildcards/> | ||||||
|  |         <address/> | ||||||
|  |         <nxdomain>0</nxdomain> | ||||||
|  |       </dnsbl> | ||||||
|  |       <forwarding> | ||||||
|  |         <enabled>0</enabled> | ||||||
|  |       </forwarding> | ||||||
|  |       <dots/> | ||||||
|  |       <hosts/> | ||||||
|  |       <aliases/> | ||||||
|  |     </unboundplus> | ||||||
|  |   </OPNsense> | ||||||
|  |   <hasync version="1.0.2" persisted_at="1755708111.35"> | ||||||
|  |     <disablepreempt>0</disablepreempt> | ||||||
|  |     <disconnectppps>0</disconnectppps> | ||||||
|  |     <pfsyncinterface/> | ||||||
|  |     <pfsyncpeerip/> | ||||||
|  |     <pfsyncversion>1400</pfsyncversion> | ||||||
|  |     <synchronizetoip/> | ||||||
|  |     <verifypeer>0</verifypeer> | ||||||
|  |     <username/> | ||||||
|  |     <password/> | ||||||
|  |     <syncitems/> | ||||||
|  |   </hasync> | ||||||
|  |   <openvpn/> | ||||||
|  |   <ifgroups version="1.0.0" persisted_at="1755708111.71"/> | ||||||
|  |   <bridges version="1.0.0" persisted_at="1755708111.91"> | ||||||
|  |     <bridged/> | ||||||
|  |   </bridges> | ||||||
|  |   <gifs version="1.0.0" persisted_at="1755708111.92"> | ||||||
|  |     <gif/> | ||||||
|  |   </gifs> | ||||||
|  |   <gres version="1.0.0" persisted_at="1755708111.93"> | ||||||
|  |     <gre/> | ||||||
|  |   </gres> | ||||||
|  |   <laggs version="1.0.0" persisted_at="1755708111.95"> | ||||||
|  |     <lagg/> | ||||||
|  |   </laggs> | ||||||
|  |   <virtualip version="1.0.1" persisted_at="1755708111.96"> | ||||||
|  |     <vip/> | ||||||
|  |   </virtualip> | ||||||
|  |   <vlans version="1.0.0" persisted_at="1755708111.98"> | ||||||
|  |     <vlan/> | ||||||
|  |   </vlans> | ||||||
|  |   <staticroutes version="1.0.0" persisted_at="1755708112.03"/> | ||||||
|  |   <ppps> | ||||||
|  |     <ppp/> | ||||||
|  |   </ppps> | ||||||
|  |   <wireless> | ||||||
|  |     <clone/> | ||||||
|  |   </wireless> | ||||||
|  |   <ca/> | ||||||
|  |   <dhcpd/> | ||||||
|  |   <dhcpdv6/> | ||||||
|  |   <cert uuid="d60972af-642c-411f-abb6-43e0d680fefd"> | ||||||
|  |     <refid>68a5faf1685db</refid> | ||||||
|  |     <descr>Web GUI TLS certificate</descr> | ||||||
|  |     <caref/> | ||||||
|  |     <crt>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQlBxZ0F3SUJBZ0lVQlpQYjUwMXNaM3hhTTZzSDVUM1ZNRG9mcnlNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhHakFZQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVgpNQk1HQTFVRUNBd01XblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyCkJnTlZCQW9NSkU5UVRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWlRBZUZ3MHkKTlRBNE1qQXhOalF5TWpaYUZ3MHlOakE1TWpFeE5qUXlNalphTUlHR01Sb3dHQVlEVlFRRERCRlBVRTV6Wlc1egpaUzVwYm5SbGNtNWhiREVMTUFrR0ExVUVCaE1DVGt3eEZUQVRCZ05WQkFnTURGcDFhV1F0U0c5c2JHRnVaREVWCk1CTUdBMVVFQnd3TVRXbGtaR1ZzYUdGeWJtbHpNUzB3S3dZRFZRUUtEQ1JQVUU1elpXNXpaU0J6Wld4bUxYTnAKWjI1bFpDQjNaV0lnWTJWeWRHbG1hV05oZEdVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFEQVozZk1lakdOckZYVFhZd24vTUp5YXBYMDdVOTVWZ0JERzZmWU1wZkZEU1NXeFRValRLRlNvR3JRCkpDb0ZyQ0NRQ3BsWnlua0ZjVFZvWVAraGszcndWWmxHZVFyL0RyR2ZiM1lPc2RGbEtublo5YzRTOGVrSkc2WTIKYWI0eFJTcnBja0hoTWQ4MHNENUFRdks1Skc2MS9UMEhNRXVMcGlraUF3MFpCempWbjlVUUpSRTJiS29kOW9IdgpEbG5DVGNTV1FUNWYrV3A0Sll0anVBVHBZMUlRVW4wbkxuWDBHUC9JRGtsWjFrdEZOczRPOGcrQmRVWFU0MUdvCjNGTFpCc1hQVm90WFVrRVl2R0ZldjJRMlBwSnNib25aWEpoR255amREUlZkZFZGOGhacGJua0wwU2xJTVFNeFQKSTRXK051ZmUvUTZGRDdmZnFRa0toemZ3SlJ2N0dGM2RTRlEwWldPUGNTZVZXY3lWTlVVMTMvcnBteUpvNXZhWQpYR000THcxb1d2c1FjUWxaUllnSkxhSWMzM0dnMGQySHhvZTIvdytIeFRXOEw4ZldqbzgxK250YWJXTlZhV0IwCnd6TXNFNGRBOWtxR2dmcWZjbm96ckovamJtNEFBTTB6QTlVZFh0SUJtRGdpeFNkVzB4eHNQNlZWRTdMdnlURTgKWnBJTjRoL1FCYURyc2hhRXJ6TXhUd01IZXJ1RlV4bFpxZEdSa3ErQXRJU1VwRE05VXB0YWZZMk5ydzgxVDFhSwoycFduVFlFQktCUnVwdk00TzFHNXN5NU5GZm13NFRTc0pqRUhtMFEvVTZ4ZU45bVg0OFhIYUZFNnhvQTJEZEMrCjJHS2lKWFlrYi9nZm13cmp1Y3NGWGpBS2tDNWF6ZXFqaERXeGxDbmJNS1YwSTQxOWp3SURBUUFCbzRJQmREQ0MKQVhBd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0JrQXdOQVlKWUlaSUFZYjRRZ0VOQkNjVwpKVTlRVG5ObGJuTmxJRWRsYm1WeVlYUmxaQ0JUWlhKMlpYSWdRMlZ5ZEdsbWFXTmhkR1V3SFFZRFZSME9CQllFCkZMK2YzU0tCM0tMSi9nWStBUTJIZDBhTzVPRU1NSUd3QmdOVkhTTUVnYWd3Z2FXaGdZeWtnWWt3Z1lZeEdqQVkKQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVk1CTUdBMVVFQ0F3TQpXblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyQmdOVkJBb01KRTlRClRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWllJVUJaUGI1MDFzWjN4YU02c0gKNVQzVk1Eb2ZyeU13SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01Bc0dBMVVkRHdRRQpBd0lGb0RBY0JnTlZIUkVFRlRBVGdoRlBVRTV6Wlc1elpTNXBiblJsY201aGJEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBZ0VBY3F3VEN0RmVwWDlFOEFMYXRaWEE4dkN2b1YwZkxuaElPM0lWT2tZNEhYMTRRRU9NUGo4KzVXMXoKc3hyUGNscEJQVU5tbXJRb0hWUHBuWTRFM2hFanlYY1JWSzZtTEFrSkpRUDJPOEE2NFFpL3FNSkhCYUlEU0MzKwpqMHdkMGYyODRvcEppQ0F2UnF0SGh1bDd3akd4QzNZUXRxWTFMODNWUHBOWGRjOVVsZExTUGZ6WWluMlBPekMrClFDWU9qN3VQUDlCVGExTURudkdPdkdrOHdQeUJGaFZQWVFBWjYwb1ZaS2psOUI2R1piRzF1SG1ON3p3a3k0eEIKNk1RSlF3cHFscDdXQ09QSHJlZTFiVGZaMkJMZFQrZzJHakVMT0xNRGJMTHAzNUw0blBDUmNGRkJZNEszMHBncQpVWjdncEtNTmhuR3huQ29lR3dHUk54ZHFoZnNlT3FqaURVM0hBVDEya3FReU1vOEZNT1g1bG9EaVpXSGZTS0JrClVRaG5Tc1BTMG0vZ2dSRVgwNVQzYWM2NUxNOVVXMEs2MXppZUs5WFhTcjNXWlQ1TkhNV2JmU0VScUR4SGRtZ0YKeGt3YXkxZWpCZTFoZzdGMGpicTVDMGo1ZXB5ZDNOc1BTVUtDY2FDNi9aWTNkUHVYWVZZL0J2dnFsZHZJSzRBeAo0R1BLQ0xzaStGRjliSXZRWG4zbTV2KzJoQWF2WlpxcmJOTFUzVFQ0aDd2QllBOVRNcXpwd3lEaW5BR3RhaEE3CnhDSW5IU01kZXpQcnNkZDJrTW5TckhPdWtGeGdKb2lNZ3krVlJmdTdvZk9NMjhYQ1FySWF6djBFYmFhTU1ZMTcKRzlXOFd3SXZUb2lYY1ZWNTk3K1NUNHRsSTVIM3lDZFBtSk1kRm5GcDg1K2JzbW42MFQwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</crt> | ||||||
|  |     <csr/> | ||||||
|  |     <prv>LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRREFaM2ZNZWpHTnJGWFQKWFl3bi9NSnlhcFgwN1U5NVZnQkRHNmZZTXBmRkRTU1d4VFVqVEtGU29HclFKQ29GckNDUUNwbFp5bmtGY1RWbwpZUCtoazNyd1ZabEdlUXIvRHJHZmIzWU9zZEZsS25uWjljNFM4ZWtKRzZZMmFiNHhSU3JwY2tIaE1kODBzRDVBClF2SzVKRzYxL1QwSE1FdUxwaWtpQXcwWkJ6alZuOVVRSlJFMmJLb2Q5b0h2RGxuQ1RjU1dRVDVmK1dwNEpZdGoKdUFUcFkxSVFVbjBuTG5YMEdQL0lEa2xaMWt0Rk5zNE84ZytCZFVYVTQxR28zRkxaQnNYUFZvdFhVa0VZdkdGZQp2MlEyUHBKc2JvblpYSmhHbnlqZERSVmRkVkY4aFpwYm5rTDBTbElNUU14VEk0VytOdWZlL1E2RkQ3ZmZxUWtLCmh6ZndKUnY3R0YzZFNGUTBaV09QY1NlVldjeVZOVVUxMy9ycG15Sm81dmFZWEdNNEx3MW9XdnNRY1FsWlJZZ0oKTGFJYzMzR2cwZDJIeG9lMi93K0h4VFc4TDhmV2pvODErbnRhYldOVmFXQjB3ek1zRTRkQTlrcUdnZnFmY25vegpySi9qYm00QUFNMHpBOVVkWHRJQm1EZ2l4U2RXMHh4c1A2VlZFN0x2eVRFOFpwSU40aC9RQmFEcnNoYUVyek14ClR3TUhlcnVGVXhsWnFkR1JrcStBdElTVXBETTlVcHRhZlkyTnJ3ODFUMWFLMnBXblRZRUJLQlJ1cHZNNE8xRzUKc3k1TkZmbXc0VFNzSmpFSG0wUS9VNnhlTjltWDQ4WEhhRkU2eG9BMkRkQysyR0tpSlhZa2IvZ2Ztd3JqdWNzRgpYakFLa0M1YXplcWpoRFd4bENuYk1LVjBJNDE5andJREFRQUJBb0lDQUNVVkRBTE4zalVXN09leTFPdDBES251Cm52NDRxcU9SRHJYZ1k2WUlnalhKUmE4RlRTdURmbWdsWU5EQzE1S0dUVFJWeHA2R3BuS0ZFaTBPM05Yd1RiWjYKV1BNN0t3SmplNXBsNmhRRTgzMlRCUzhiNzk2NDN4Z1JTeVNibHJ0NlFENEQ5bXlIcHlSSmY0WDFJVURMbzhiUgppdXlTdzB5ajlyT0djUVRNM29oVnFNUFcwUTF6UGdwT1UxYVdwbmdMY3dNZWlmNEhYUnpRNTUrTmZPemFacHVjCnVtQk4xUS81clhxS1BscmhNVnFpcUc0Nit3QVJjU2NKdE5oZHRsMzdyeTQ1Mk5zNGtERkxSVnowZUVUNEpGSmYKcjVQRUE5bEFuYWlVOS9RdVEwbERtcTlqdmpYRkNURXhYKy82SGJHK2RVd0Y2OEY3ZVEzVFQxbkhHK0hkMVJsbgpOWm1JM0p2d0Z1cG9JeU9VdlpJb3VGVmo2ak8ra0JLejkza1BHWmdMbnNmUUw5WDhRbTU3cjh4K3Z1eFNudGI1CjV4WVBxRkdrOWQrbDUwbTlQakdkekxGT3UwYnJ5TmQ4MFVMS2tuUlFtUVpvTngxck5GTUxpSjNlZENWUS9lclUKT1BDQ0Z0WEJMemJGTjR2ZzVWRjZMUkhvZGxqcEgxRzJOSXNoSzJhc1FuWS9RWDFpUUNLSk1tWERSUndMTWVsNQp3MUF4T2FqYVkzbWx2ZlRVd2xqdkE3a0tFUDBvZzRPeXZldDA2WTVRWk1EQXc1V00yT0pZVDVxcmFlYjZDbTdMCjlNckk4bG50TGp3WFVSZG4yU3U2RCtCWXNpcC9KK3BvOFNqYlJBaGJIc0lJbkJ1QWJnbGxqdTB2QXRXZmFkQlQKOTg4YnUwK3VUb1Q2T1Jkbk84Y1JBb0lCQVFEcStWYkVUQWVpSHN6K29jZnFWV3VwVHJrc2FNNm1wUUMwb0tqZApwb1FzWGVuTmNiNThHQ3FhWHkvdTJHWmNKUnR1QXRHamEyUVpEUUFUSjQyVTFmaTFhWm90Y053eXhPdmlud1NjCmVLZyt0ZGcwdW9LeGs2aXJKRFptaDBIK3Ewblg2RFJYK25RNDVmWVNmRkRFK0ZLd1lac0dQMkhYV3dKaVZ6OE0KU2NkL2pETTFRTWV2OXIzZWx1dS9DWFlvZ1N0N00wMklyczVoNjRuNjFmdVZjNHI4YmUwdFkrUTVsUnlwWk9NVwpkQ2VkWGFOV3RaNjF2bEFxamNiWkpkdXFBUjJjNzAyR3NML201TXA4Zmd3YmY2aG51TXJLaVlpQjlZalZxalc2CmYyUW1PclZtMUk0MFJBMC9OaFBTR2NXejBkNXZrdXY0VHUra2JFbERZTCsxaHY1M0FvSUJBUURSbnZaTmJaa1UKTXpmUTRLWEdML3dLUXJEbjNvL0RENWVBR1ZDTGcwTUkyYlAxYWpubHNVTjE4NCs1UWF6cVVOaWlZT3laODczeQpQYkw0cTBOZWFDYXdxby9WbjJMSkVIUFVTTVhUWjB4ckxTa1hPUjFuMDUwT2tDWXhVbFpOUXFvZU1xcHJGNXZLCm1NNlJxalN4NS8ydU9IUlR1SDRVV2RETEpwTDVUN2RpUCtXcFUwSDlSUWhrNDdkQUJaUjZEZjNxaDJEYmVxUWoKdWcxY0hWUVNqaldhUGpVZGlLR2dHemdvdlE2UkdNZDA1UVUzdkRMdzBCSkNPQ25XV2x0VXkvMW1jMUpPUHR2ZQp4UGltV2tRNmlkRHZ4RGZFRGg5U05zY1FPMnBTVjZxNnhCTWlqVGgvTldGN2NsOU1LYUhJWGxzTmt0RFVXWHZyCmNKRlM4eE1TcDhlcEFvSUJBUUNtWktVTjRxMHhIOUNZckdYT1Nta3Yvc0JnYzJPTFhLTXdSZWp1OVFENkRoTUgKMmZsREZUWHVGV1B6SmlqdUxaVE1CWkVBd1lhanVySUgzbVdETlRhbStMNG1XWnFGRlMvWlRqUk12YUNlcjlVSQpHZDk4OG94cGpQNDlBcUU0UDRIT00vQUZNU1ZtT1dwVTB0VzdkZ0hRUjM0cElXOGV1cUxva3RIaDJNaytTRURuCkFCV29SUGxWaTlncmN2N0tWaFk5YXlvSGxZb3VpMFl0YTZSNXc5VnpSa0REZU01Zi9Iak1kOVhieTZ0VjQ3NU0KSTliYzZvVUliVmVYNUJnMnZnMkRXVzZ6NTZ3dFRHMGJWWU1yWWU0V2JTU2w0bGpaZHM5TVJ2ay9OUUR0bFh0cAo4ekUwVDlCMXA4ekhabHE3S08zMFlyMVpIRVRWVVoxYjZrSTN3UDJuQW9JQkFFZ1VGKzlCMjF4RnpGQ0hucGtLClVPa2FTNGcvVUVHcmI5VzlYcVBLUzllVVBEd0wvY0tNZEh6dmRpRW1neFhESE9xZzExcU1wR2pTYkdMelNPUUMKZmlOTFV0QUswVVgvNFVSQ2pidUdqcEZmNHZ3NFNITTJJWkFyWXVhY3dFNHF1U0pQRzZoZFl0V0VPNnQ4MGtmRwpWTVYrWmdtUHE5TEZtM1R2VzZSY2s5czF5M3V3eEVVWllxeUdYTEduK1lrS25KL3pVd3ZGSFFHbjdRWWFrNWtaCnl6YXhZMFEzZ2hQeXFCbmlBRXRHTVBkeDlKeFltMCtRekdaMnQzUWNkOEV0cjRGMTcvdzF3eGJUdGdoRmk2WngKVXlYTzI3b1BmUmVnL0V3SmtpS2tRSEdlRUZKV0t2SWE0ZDAzMDZyMXVjcVRIMDRJaU1RcnpOK0ZRb002VC9tZgpOWmtDZ2dFQUsrRVJNVVdJZTE3V1k3VDIycy9lOEplN0xxSXlUcU9mSGovaWFUUjhHbXhqcU1HNEdod1RpVXJsCkh0Skhud3BMVGFjVmdYUjV3UmtYcEhRT2JqSUFzeVNBUGxwSzBvZUkyK2kvS0cyQjZ2U0cza0V2b1VZY0RlRk4KdzhHd0oxNDNTd21LQXM4eUtWMmd1RjhmRXNNVitEQzNzVHFlZXJmMy82bFprMUVCVFF0QTZqVHdqK0picXgwVgpaalZJUXBwUE8vc1VHdi9LZVE3MW5ockJpT0lXclJ0dDRTUDJ2aWx2em9DUTQxVjFqZ09wS3VwU3E1Y2J3VDRxCmp1bkJIMkx5VnNQaUc4M0Vha1JSUEhDK0craTk1MFJxckxVRUJOeVdHNGlMNTdUdU9xYVJuSmRnN2ZFb2lVLzMKNld4TjlvR2VRWjV0NjZkdTJEL01WSUZ4ZzJ1cXRBPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=</prv> | ||||||
|  |   </cert> | ||||||
|  |   <syslog/> | ||||||
|  | </opnsense> | ||||||
							
								
								
									
										2572
									
								
								opnsense-config/src/tests/data/config-full-ncd0.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2572
									
								
								opnsense-config/src/tests/data/config-full-ncd0.xml
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user