forked from NationTech/harmony
		
	Merge pull request 'doc/pxe_test_setup' (#117) from doc/pxe_test_setup into master
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/117
This commit is contained in:
		
						commit
						269f13ae9b
					
				
							
								
								
									
										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_inventory_agent", | ||||
|   "harmony_secret_derive", | ||||
|   "harmony_secret", | ||||
|   "harmony_secret", "adr/agent_discovery/mdns", | ||||
| ] | ||||
| 
 | ||||
| [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 | ||||
| set -e | ||||
| 
 | ||||
| rustc --version | ||||
| cargo check --all-targets --all-features --keep-going | ||||
| cargo fmt --check | ||||
| 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::{ | ||||
|     inventory::Inventory, | ||||
|     modules::dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||
|     modules::{ | ||||
|         dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||
|         inventory::DiscoverInventoryAgentScore, | ||||
|     }, | ||||
|     topology::LocalhostTopology, | ||||
| }; | ||||
| 
 | ||||
| @ -13,6 +16,9 @@ async fn main() { | ||||
|             Box::new(SuccessScore {}), | ||||
|             Box::new(ErrorScore {}), | ||||
|             Box::new(PanicScore {}), | ||||
|             Box::new(DiscoverInventoryAgentScore { | ||||
|                 discovery_timeout: Some(10), | ||||
|             }), | ||||
|         ], | ||||
|         None, | ||||
|     ) | ||||
|  | ||||
| @ -125,9 +125,12 @@ async fn main() { | ||||
|         harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology); | ||||
| 
 | ||||
|     let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string())); | ||||
|     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( | ||||
|     let http_score = StaticFilesHttpScore { | ||||
|         folder_to_serve: Some(Url::LocalFolder( | ||||
|             "./data/watchguard/pxe-http-files".to_string(), | ||||
|     )); | ||||
|         )), | ||||
|         files: vec![], | ||||
|     }; | ||||
|     let ipxe_score = IpxeScore::new(); | ||||
| 
 | ||||
|     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 tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string())); | ||||
|     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( | ||||
|     let http_score = StaticFilesHttpScore { | ||||
|         folder_to_serve: Some(Url::LocalFolder( | ||||
|             "./data/watchguard/pxe-http-files".to_string(), | ||||
|     )); | ||||
|         )), | ||||
|         files: vec![], | ||||
|     }; | ||||
| 
 | ||||
|     harmony_tui::run( | ||||
|         inventory, | ||||
|  | ||||
| @ -11,8 +11,7 @@ testing = [] | ||||
| [dependencies] | ||||
| rand = "0.9" | ||||
| hex = "0.4" | ||||
| libredfish = "0.1.1" | ||||
| reqwest = { version = "0.11", features = ["blocking", "json"] } | ||||
| reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features = false } | ||||
| russh = "0.45.0" | ||||
| rust-ipmi = "0.1.1" | ||||
| semver = "1.0.23" | ||||
| @ -67,7 +66,9 @@ bollard.workspace = true | ||||
| tar.workspace = true | ||||
| base64.workspace = true | ||||
| 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] | ||||
| 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 version; | ||||
| pub use file::*; | ||||
| pub use id::*; | ||||
| pub use version::*; | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| use log::debug; | ||||
| use once_cell::sync::Lazy; | ||||
| use tokio::sync::broadcast; | ||||
| use std::{collections::HashMap, sync::Mutex}; | ||||
| 
 | ||||
| use crate::modules::application::ApplicationFeatureStatus; | ||||
| 
 | ||||
| @ -40,43 +39,46 @@ pub enum HarmonyEvent { | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| static HARMONY_EVENT_BUS: Lazy<broadcast::Sender<HarmonyEvent>> = Lazy::new(|| { | ||||
|     // TODO: Adjust channel capacity
 | ||||
|     let (tx, _rx) = broadcast::channel(100); | ||||
|     tx | ||||
| }); | ||||
| type Subscriber = Box<dyn Fn(&HarmonyEvent) + Send + Sync>; | ||||
| 
 | ||||
| pub fn instrument(event: HarmonyEvent) -> Result<(), &'static str> { | ||||
|     if cfg!(any(test, feature = "testing")) { | ||||
|         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"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| static SUBSCRIBERS: Lazy<Mutex<HashMap<String, Subscriber>>> = | ||||
|     Lazy::new(|| Mutex::new(HashMap::new())); | ||||
| 
 | ||||
| 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 | ||||
|     F: FnMut(HarmonyEvent) -> Fut + Send + 'static, | ||||
|     Fut: Future<Output = bool> + Send, | ||||
|     F: Fn(&HarmonyEvent) + Send + Sync + 'static, | ||||
| { | ||||
|     let mut rx = HARMONY_EVENT_BUS.subscribe(); | ||||
|     debug!("[{name}] Service started. Listening for events..."); | ||||
|     loop { | ||||
|         match rx.recv().await { | ||||
|             Ok(event) => { | ||||
|                 if !handler(event).await { | ||||
|                     debug!("[{name}] Handler requested exit."); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             Err(broadcast::error::RecvError::Lagged(n)) => { | ||||
|                 debug!("[{name}] Lagged behind by {n} messages."); | ||||
|             } | ||||
|             Err(_) => break, | ||||
|         } | ||||
|     } | ||||
|     let mut subs = SUBSCRIBERS.lock().unwrap(); | ||||
|     subs.insert(name.to_string(), Box::new(callback)); | ||||
| } | ||||
| 
 | ||||
| /// Instruments an event, notifying all subscribers.
 | ||||
| ///
 | ||||
| /// This will call every closure that was registered with `subscribe`.
 | ||||
| ///
 | ||||
| /// # Example
 | ||||
| /// ```
 | ||||
| /// use harmony::instrumentation;
 | ||||
| /// use harmony::instrumentation::HarmonyEvent;
 | ||||
| /// instrumentation::instrument(HarmonyEvent::HarmonyStarted);
 | ||||
| /// ```
 | ||||
| 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, | ||||
|     ApplicationMonitoring, | ||||
|     K8sPrometheusCrdAlerting, | ||||
|     DiscoverInventoryAgent, | ||||
|     CephClusterHealth, | ||||
| } | ||||
| 
 | ||||
| @ -59,6 +60,7 @@ impl std::fmt::Display for InterpretName { | ||||
|             InterpretName::Lamp => f.write_str("LAMP"), | ||||
|             InterpretName::ApplicationMonitoring => f.write_str("ApplicationMonitoring"), | ||||
|             InterpretName::K8sPrometheusCrdAlerting => f.write_str("K8sPrometheusCrdAlerting"), | ||||
|             InterpretName::DiscoverInventoryAgent => f.write_str("DiscoverInventoryAgent"), | ||||
|             InterpretName::CephClusterHealth => f.write_str("CephClusterHealth"), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -74,6 +74,7 @@ impl<T: Topology> Maestro<T> { | ||||
| 
 | ||||
|     fn is_topology_initialized(&self) -> bool { | ||||
|         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> { | ||||
|  | ||||
| @ -1,9 +1,12 @@ | ||||
| use async_trait::async_trait; | ||||
| use harmony_macros::ip; | ||||
| use harmony_types::net::MacAddress; | ||||
| use log::debug; | ||||
| use log::info; | ||||
| 
 | ||||
| use crate::data::FileContent; | ||||
| use crate::executors::ExecutorError; | ||||
| use crate::topology::PxeOptions; | ||||
| 
 | ||||
| use super::DHCPStaticEntry; | ||||
| use super::DhcpServer; | ||||
| @ -49,9 +52,10 @@ impl Topology for HAClusterTopology { | ||||
|         "HAClusterTopology" | ||||
|     } | ||||
|     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." | ||||
|         ) | ||||
|         ); | ||||
|         Ok(PreparationOutcome::Noop) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -153,12 +157,10 @@ impl DhcpServer for HAClusterTopology { | ||||
|     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> { | ||||
|         self.dhcp_server.list_static_mappings().await | ||||
|     } | ||||
|     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { | ||||
|         self.dhcp_server.set_next_server(ip).await | ||||
|     } | ||||
|     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> { | ||||
|         self.dhcp_server.set_boot_filename(boot_filename).await | ||||
|     async fn set_pxe_options(&self, options: PxeOptions) -> Result<(), ExecutorError> { | ||||
|         self.dhcp_server.set_pxe_options(options).await | ||||
|     } | ||||
| 
 | ||||
|     fn get_ip(&self) -> IpAddress { | ||||
|         self.dhcp_server.get_ip() | ||||
|     } | ||||
| @ -168,16 +170,6 @@ impl DhcpServer for HAClusterTopology { | ||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||
|         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] | ||||
| @ -221,17 +213,21 @@ impl HttpServer for HAClusterTopology { | ||||
|         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 { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|         self.http_server.get_ip() | ||||
|     } | ||||
|     async fn ensure_initialized(&self) -> Result<(), ExecutorError> { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|         self.http_server.ensure_initialized().await | ||||
|     } | ||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|         self.http_server.commit_config().await | ||||
|     } | ||||
|     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)> { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|     } | ||||
|     async fn set_next_server(&self, _ip: IpAddress) -> 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> { | ||||
|     async fn set_pxe_options(&self, _options: PxeOptions) -> Result<(), ExecutorError> { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|     } | ||||
|     fn get_ip(&self) -> IpAddress { | ||||
| @ -381,6 +365,9 @@ impl HttpServer for DummyInfra { | ||||
|     async fn serve_files(&self, _url: &Url) -> Result<(), ExecutorError> { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|     } | ||||
|     async fn serve_file_content(&self, _file: &FileContent) -> Result<(), ExecutorError> { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|     } | ||||
|     fn get_ip(&self) -> IpAddress { | ||||
|         unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) | ||||
|     } | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use crate::executors::ExecutorError; | ||||
| use crate::{data::FileContent, executors::ExecutorError}; | ||||
| use async_trait::async_trait; | ||||
| 
 | ||||
| use super::{IpAddress, Url}; | ||||
| @ -6,6 +6,7 @@ use super::{IpAddress, Url}; | ||||
| #[async_trait] | ||||
| pub trait HttpServer: Send + Sync { | ||||
|     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError>; | ||||
|     async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError>; | ||||
|     fn get_ip(&self) -> IpAddress; | ||||
| 
 | ||||
|     // async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError>;
 | ||||
|  | ||||
| @ -185,7 +185,10 @@ impl K8sClient { | ||||
|                 if let Some(s) = status.status { | ||||
|                     let mut stdout_buf = String::new(); | ||||
|                     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); | ||||
|                     if s == "Success" { | ||||
|  | ||||
| @ -46,16 +46,19 @@ pub trait K8sclient: Send + Sync { | ||||
|     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] | ||||
| pub trait DhcpServer: Send + Sync + std::fmt::Debug { | ||||
|     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; | ||||
|     async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; | ||||
|     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; | ||||
|     async fn set_next_server(&self, ip: IpAddress) -> 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>; | ||||
|     async fn set_pxe_options(&self, pxe_options: PxeOptions) -> Result<(), ExecutorError>; | ||||
|     fn get_ip(&self) -> IpAddress; | ||||
|     fn get_host(&self) -> LogicalHost; | ||||
|     async fn commit_config(&self) -> Result<(), ExecutorError>; | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| use async_trait::async_trait; | ||||
| use harmony_types::net::MacAddress; | ||||
| use log::debug; | ||||
| use log::info; | ||||
| 
 | ||||
| use crate::{ | ||||
|     executors::ExecutorError, | ||||
|     topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost}, | ||||
|     topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost, PxeOptions}, | ||||
| }; | ||||
| 
 | ||||
| use super::OPNSenseFirewall; | ||||
| @ -26,7 +26,7 @@ impl DhcpServer for OPNSenseFirewall { | ||||
|                 .unwrap(); | ||||
|         } | ||||
| 
 | ||||
|         debug!("Registered {:?}", entry); | ||||
|         info!("Registered {:?}", entry); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
| @ -46,57 +46,25 @@ impl DhcpServer for OPNSenseFirewall { | ||||
|         self.host.clone() | ||||
|     } | ||||
| 
 | ||||
|     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { | ||||
|         let ipv4 = match ip { | ||||
|             std::net::IpAddr::V4(ipv4_addr) => ipv4_addr, | ||||
|             std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"), | ||||
|         }; | ||||
|         { | ||||
|     async fn set_pxe_options(&self, options: PxeOptions) -> Result<(), ExecutorError> { | ||||
|         let mut writable_opnsense = self.opnsense_config.write().await; | ||||
|             writable_opnsense.dhcp().set_next_server(ipv4); | ||||
|             debug!("OPNsense dhcp server set next server {ipv4}"); | ||||
|         } | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> { | ||||
|         { | ||||
|             let mut writable_opnsense = self.opnsense_config.write().await; | ||||
|             writable_opnsense.dhcp().set_boot_filename(boot_filename); | ||||
|             debug!("OPNsense dhcp server set boot filename {boot_filename}"); | ||||
|         } | ||||
| 
 | ||||
|         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(()) | ||||
|         let PxeOptions { | ||||
|             ipxe_filename, | ||||
|             bios_filename, | ||||
|             efi_filename, | ||||
|             tftp_ip, | ||||
|         } = options; | ||||
|         writable_opnsense | ||||
|             .dhcp() | ||||
|             .set_pxe_options( | ||||
|                 tftp_ip.map(|i| i.to_string()), | ||||
|                 bios_filename, | ||||
|                 efi_filename, | ||||
|                 ipxe_filename, | ||||
|             ) | ||||
|             .await | ||||
|             .map_err(|dhcp_error| { | ||||
|                 ExecutorError::UnexpectedError(format!("Failed to set_pxe_options : {dhcp_error}")) | ||||
|             }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,23 +2,23 @@ use async_trait::async_trait; | ||||
| use log::info; | ||||
| 
 | ||||
| use crate::{ | ||||
|     data::FileContent, | ||||
|     executors::ExecutorError, | ||||
|     topology::{HttpServer, IpAddress, Url}, | ||||
| }; | ||||
| 
 | ||||
| use super::OPNSenseFirewall; | ||||
| const OPNSENSE_HTTP_ROOT_PATH: &str = "/usr/local/http"; | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl HttpServer for OPNSenseFirewall { | ||||
|     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError> { | ||||
|         let http_root_path = "/usr/local/http"; | ||||
| 
 | ||||
|         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 { | ||||
|             Url::LocalFolder(path) => { | ||||
|                 config | ||||
|                     .upload_files(path, http_root_path) | ||||
|                     .upload_files(path, OPNSENSE_HTTP_ROOT_PATH) | ||||
|                     .await | ||||
|                     .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; | ||||
|             } | ||||
| @ -27,8 +27,29 @@ impl HttpServer for OPNSenseFirewall { | ||||
|         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 { | ||||
|         todo!(); | ||||
|         OPNSenseFirewall::get_ip(self) | ||||
|     } | ||||
| 
 | ||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||
|  | ||||
| @ -28,7 +28,7 @@ impl TftpServer for OPNSenseFirewall { | ||||
|     } | ||||
| 
 | ||||
|     fn get_ip(&self) -> IpAddress { | ||||
|         todo!() | ||||
|         OPNSenseFirewall::get_ip(self) | ||||
|     } | ||||
| 
 | ||||
|     async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError> { | ||||
|  | ||||
| @ -7,7 +7,7 @@ use crate::{ | ||||
|     domain::{data::Version, interpret::InterpretStatus}, | ||||
|     interpret::{Interpret, InterpretError, InterpretName, Outcome}, | ||||
|     inventory::Inventory, | ||||
|     topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, Topology}, | ||||
|     topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, PxeOptions, Topology}, | ||||
| }; | ||||
| 
 | ||||
| use crate::domain::score::Score; | ||||
| @ -98,69 +98,14 @@ impl DhcpInterpret { | ||||
|         _inventory: &Inventory, | ||||
|         dhcp_server: &D, | ||||
|     ) -> Result<Outcome, InterpretError> { | ||||
|         let next_server_outcome = match self.score.next_server { | ||||
|             Some(next_server) => { | ||||
|                 dhcp_server.set_next_server(next_server).await?; | ||||
|                 Outcome::new( | ||||
|                     InterpretStatus::SUCCESS, | ||||
|                     format!("Dhcp Interpret Set next boot to {next_server}"), | ||||
|                 ) | ||||
|             } | ||||
|             None => Outcome::noop(), | ||||
|         let pxe_options = PxeOptions { | ||||
|             ipxe_filename: self.score.filenameipxe.clone().unwrap_or_default(), | ||||
|             bios_filename: self.score.filename.clone().unwrap_or_default(), | ||||
|             efi_filename: self.score.filename64.clone().unwrap_or_default(), | ||||
|             tftp_ip: self.score.next_server, | ||||
|         }; | ||||
| 
 | ||||
|         let boot_filename_outcome = match &self.score.boot_filename { | ||||
|             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()); | ||||
|         } | ||||
|         dhcp_server.set_pxe_options(pxe_options).await?; | ||||
| 
 | ||||
|         Ok(Outcome::new( | ||||
|             InterpretStatus::SUCCESS, | ||||
|  | ||||
| @ -3,7 +3,7 @@ use derive_new::new; | ||||
| use serde::Serialize; | ||||
| 
 | ||||
| use crate::{ | ||||
|     data::{Id, Version}, | ||||
|     data::{FileContent, Id, Version}, | ||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||
|     inventory::Inventory, | ||||
|     score::Score, | ||||
| @ -23,7 +23,8 @@ use crate::{ | ||||
| /// ```
 | ||||
| #[derive(Debug, new, Clone, Serialize)] | ||||
| 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 { | ||||
| @ -50,12 +51,25 @@ impl<T: Topology + HttpServer> Interpret<T> for StaticFilesHttpInterpret { | ||||
|     ) -> Result<Outcome, InterpretError> { | ||||
|         http_server.ensure_initialized().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.reload_restart().await?; | ||||
|         Ok(Outcome::success(format!( | ||||
|             "Http Server running and serving files from {}", | ||||
|             self.score.files_to_serve | ||||
|             "Http Server running and serving files from folder {:?} and content for {}", | ||||
|             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 helm; | ||||
| pub mod http; | ||||
| pub mod inventory; | ||||
| pub mod ipxe; | ||||
| pub mod k3d; | ||||
| pub mod k8s; | ||||
|  | ||||
| @ -37,18 +37,6 @@ pub struct NtfyInterpret { | ||||
|     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)] | ||||
| enum NtfyRole { | ||||
|     #[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 dhcp; | ||||
| pub mod dns; | ||||
| pub mod ipxe; | ||||
| pub mod load_balancer; | ||||
| pub mod upgrade; | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| use std::{ | ||||
|     process::Command, | ||||
|     sync::Arc, | ||||
|     time::{Duration, Instant}, | ||||
| }; | ||||
|  | ||||
| @ -12,7 +12,7 @@ use crate::{ | ||||
| 
 | ||||
| #[derive(Debug, new, Clone, Serialize)] | ||||
| pub struct TftpScore { | ||||
|     files_to_serve: Url, | ||||
|     pub files_to_serve: Url, | ||||
| } | ||||
| 
 | ||||
| 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 std::io::Write; | ||||
| use std::sync::{Arc, Mutex}; | ||||
| use std::sync::Mutex; | ||||
| 
 | ||||
| pub fn init() -> tokio::task::JoinHandle<()> { | ||||
| pub fn init() { | ||||
|     configure_logger(); | ||||
|     let handle = tokio::spawn(handle_events()); | ||||
| 
 | ||||
|     loop { | ||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     handle | ||||
|     handle_events(); | ||||
| } | ||||
| 
 | ||||
| fn configure_logger() { | ||||
| @ -86,16 +78,12 @@ fn configure_logger() { | ||||
|         .init(); | ||||
| } | ||||
| 
 | ||||
| async fn handle_events() { | ||||
|     let preparing_topology = Arc::new(Mutex::new(false)); | ||||
|     let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None)); | ||||
| fn handle_events() { | ||||
|     let preparing_topology = Mutex::new(false); | ||||
|     let current_score: Mutex<Option<String>> = Mutex::new(None); | ||||
| 
 | ||||
|     instrumentation::subscribe("Harmony CLI Logger", { | ||||
|         move |event| { | ||||
|             let preparing_topology = Arc::clone(&preparing_topology); | ||||
|             let current_score = Arc::clone(¤t_score); | ||||
| 
 | ||||
|             async move { | ||||
|             let mut preparing_topology = preparing_topology.lock().unwrap(); | ||||
|             let mut current_score = current_score.lock().unwrap(); | ||||
| 
 | ||||
| @ -104,7 +92,6 @@ async fn handle_events() { | ||||
|                 HarmonyEvent::HarmonyFinished => { | ||||
|                     let emoji = crate::theme::EMOJI_HARMONY.to_string(); | ||||
|                     info!(emoji = emoji.as_str(); "Harmony completed"); | ||||
|                         return false; | ||||
|                 } | ||||
|                 HarmonyEvent::TopologyStateChanged { | ||||
|                     topology, | ||||
| @ -113,7 +100,10 @@ async fn handle_events() { | ||||
|                 } => match status { | ||||
|                     TopologyStatus::Queued => {} | ||||
|                     TopologyStatus::Preparing => { | ||||
|                             let emoji = format!("{}", style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow()); | ||||
|                         let emoji = format!( | ||||
|                             "{}", | ||||
|                             style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow() | ||||
|                         ); | ||||
|                         info!(emoji = emoji.as_str(); "Preparing environment: {topology}..."); | ||||
|                         (*preparing_topology) = true; | ||||
|                     } | ||||
| @ -158,7 +148,7 @@ async fn handle_events() { | ||||
|                     score, | ||||
|                     outcome, | ||||
|                 } => { | ||||
|                         if current_score.is_some() && current_score.clone().unwrap() == score { | ||||
|                     if current_score.is_some() && ¤t_score.clone().unwrap() == score { | ||||
|                         (*current_score) = None; | ||||
|                     } | ||||
| 
 | ||||
| @ -175,7 +165,7 @@ async fn handle_events() { | ||||
|                             } | ||||
|                         }, | ||||
|                         Err(err) => { | ||||
|                                 error!(status = "failed"; "{}", err); | ||||
|                             error!(status = "failed"; "{err}"); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @ -186,19 +176,16 @@ async fn handle_events() { | ||||
|                     status, | ||||
|                 } => match status { | ||||
|                     ApplicationFeatureStatus::Installing => { | ||||
|                             info!("Installing feature '{}' for '{}'...", feature, application); | ||||
|                         info!("Installing feature '{feature}' for '{application}'..."); | ||||
|                     } | ||||
|                     ApplicationFeatureStatus::Installed => { | ||||
|                             info!(status = "finished"; "Feature '{}' installed", feature); | ||||
|                         info!(status = "finished"; "Feature '{feature}' installed"); | ||||
|                     } | ||||
|                     ApplicationFeatureStatus::Failed { details } => { | ||||
|                             error!(status = "failed"; "Feature '{}' installation failed: {}", feature, details); | ||||
|                         error!(status = "failed"; "Feature '{feature}' installation failed: {details}"); | ||||
|                     } | ||||
|                 }, | ||||
|             } | ||||
|                 true | ||||
|         } | ||||
|         } | ||||
|     }) | ||||
|     .await; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| @ -115,7 +115,7 @@ pub async fn run_cli<T: Topology + Send + Sync + 'static>( | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
|     args: Args, | ||||
| ) -> 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(); | ||||
|     maestro.register_all(scores); | ||||
| @ -123,7 +123,6 @@ pub async fn run_cli<T: Topology + Send + Sync + 'static>( | ||||
|     let result = init(maestro, args).await; | ||||
| 
 | ||||
|     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); | ||||
|     let _ = tokio::try_join!(cli_logger_handle); | ||||
|     result | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,39 +1,26 @@ | ||||
| use harmony_cli::progress::{IndicatifProgressTracker, ProgressTracker}; | ||||
| use indicatif::MultiProgress; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use crate::instrumentation::{self, HarmonyComposerEvent}; | ||||
| 
 | ||||
| pub fn init() -> tokio::task::JoinHandle<()> { | ||||
| pub fn init() { | ||||
|     configure_logger(); | ||||
|     let handle = tokio::spawn(handle_events()); | ||||
| 
 | ||||
|     loop { | ||||
|         if instrumentation::instrument(HarmonyComposerEvent::HarmonyComposerStarted).is_ok() { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     handle | ||||
|     handle_events(); | ||||
| } | ||||
| 
 | ||||
| fn configure_logger() { | ||||
|     env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); | ||||
| } | ||||
| 
 | ||||
| pub async fn handle_events() { | ||||
|     let progress_tracker = Arc::new(IndicatifProgressTracker::new(MultiProgress::new())); | ||||
| pub fn handle_events() { | ||||
|     let progress_tracker = IndicatifProgressTracker::new(MultiProgress::new()); | ||||
| 
 | ||||
|     const SETUP_SECTION: &str = "project-initialization"; | ||||
|     const COMPILTATION_TASK: &str = "compilation"; | ||||
|     const PROGRESS_DEPLOYMENT: &str = "deployment"; | ||||
| 
 | ||||
|     instrumentation::subscribe("Harmony Composer Logger", { | ||||
|         move |event| { | ||||
|             let progress_tracker = Arc::clone(&progress_tracker); | ||||
| 
 | ||||
|             async move { | ||||
|                 match event { | ||||
|         move |event| match event { | ||||
|             HarmonyComposerEvent::HarmonyComposerStarted => {} | ||||
|             HarmonyComposerEvent::ProjectInitializationStarted => { | ||||
|                 progress_tracker.add_section( | ||||
| @ -46,13 +33,16 @@ pub async fn handle_events() { | ||||
|             } | ||||
|             HarmonyComposerEvent::ProjectInitialized => {} | ||||
|             HarmonyComposerEvent::ProjectCompilationStarted { details } => { | ||||
|                         progress_tracker.add_task(SETUP_SECTION, COMPILTATION_TASK, &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}")); | ||||
|                 progress_tracker.fail_task( | ||||
|                     COMPILTATION_TASK, | ||||
|                     &format!("failed to compile project:\n{details}"), | ||||
|                 ); | ||||
|             } | ||||
|             HarmonyComposerEvent::DeploymentStarted { target, profile } => { | ||||
|                 progress_tracker.add_section( | ||||
| @ -68,15 +58,9 @@ pub async fn handle_events() { | ||||
|             } | ||||
|             HarmonyComposerEvent::DeploymentFailed { details } => { | ||||
|                 progress_tracker.add_task(PROGRESS_DEPLOYMENT, "deployment-failed", ""); | ||||
|                         progress_tracker.fail_task("deployment-failed", &details); | ||||
|                     }, | ||||
|                     HarmonyComposerEvent::Shutdown => { | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|                 true | ||||
|                 progress_tracker.fail_task("deployment-failed", details); | ||||
|             } | ||||
|             HarmonyComposerEvent::Shutdown => {} | ||||
|         } | ||||
|     }) | ||||
|     .await | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| use log::debug; | ||||
| use once_cell::sync::Lazy; | ||||
| use tokio::sync::broadcast; | ||||
| use std::{collections::HashMap, sync::Mutex}; | ||||
| 
 | ||||
| use crate::{HarmonyProfile, HarmonyTarget}; | ||||
| 
 | ||||
| @ -27,48 +26,43 @@ pub enum HarmonyComposerEvent { | ||||
|     Shutdown, | ||||
| } | ||||
| 
 | ||||
| static HARMONY_COMPOSER_EVENT_BUS: Lazy<broadcast::Sender<HarmonyComposerEvent>> = | ||||
|     Lazy::new(|| { | ||||
|         // TODO: Adjust channel capacity
 | ||||
|         let (tx, _rx) = broadcast::channel(16); | ||||
|         tx | ||||
|     }); | ||||
| type Subscriber = Box<dyn Fn(&HarmonyComposerEvent) + Send + Sync>; | ||||
| 
 | ||||
| pub fn instrument(event: HarmonyComposerEvent) -> Result<(), &'static str> { | ||||
|     #[cfg(not(test))] | ||||
|     { | ||||
|         match HARMONY_COMPOSER_EVENT_BUS.send(event) { | ||||
|             Ok(_) => Ok(()), | ||||
|             Err(_) => Err("send error: no subscribers"), | ||||
|         } | ||||
|     } | ||||
| static SUBSCRIBERS: Lazy<Mutex<HashMap<String, Subscriber>>> = | ||||
|     Lazy::new(|| Mutex::new(HashMap::new())); | ||||
| 
 | ||||
|     #[cfg(test)] | ||||
|     { | ||||
|         let _ = event; // Suppress the "unused variable" warning for `event`
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
| /// ```
 | ||||
| /// instrumentation::subscribe("my_logger", |event| {
 | ||||
| ///   println!("Event occurred: {:?}", event);
 | ||||
| /// });
 | ||||
| /// ```
 | ||||
| pub fn subscribe<F>(name: &str, callback: F) | ||||
| where | ||||
|     F: FnMut(HarmonyComposerEvent) -> Fut + Send + 'static, | ||||
|     Fut: Future<Output = bool> + Send, | ||||
|     F: Fn(&HarmonyComposerEvent) + Send + Sync + 'static, | ||||
| { | ||||
|     let mut rx = HARMONY_COMPOSER_EVENT_BUS.subscribe(); | ||||
|     debug!("[{name}] Service started. Listening for events..."); | ||||
|     loop { | ||||
|         match rx.recv().await { | ||||
|             Ok(event) => { | ||||
|                 if !handler(event).await { | ||||
|                     debug!("[{name}] Handler requested exit."); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             Err(broadcast::error::RecvError::Lagged(n)) => { | ||||
|                 debug!("[{name}] Lagged behind by {n} messages."); | ||||
|             } | ||||
|             Err(_) => break, | ||||
|         } | ||||
|     } | ||||
|     let mut subs = SUBSCRIBERS.lock().unwrap(); | ||||
|     subs.insert(name.to_string(), Box::new(callback)); | ||||
| } | ||||
| 
 | ||||
| /// Instruments an event, notifying all subscribers.
 | ||||
| ///
 | ||||
| /// This will call every closure that was registered with `subscribe`.
 | ||||
| ///
 | ||||
| /// # Example
 | ||||
| /// ```
 | ||||
| /// instrumentation::instrument(HarmonyEvent::HarmonyStarted);
 | ||||
| /// ```
 | ||||
| pub fn instrument(event: HarmonyComposerEvent) -> Result<(), &'static str> { | ||||
|     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] | ||||
| async fn main() { | ||||
|     let hc_logger_handle = harmony_composer_logger::init(); | ||||
|     harmony_composer_logger::init(); | ||||
|     let cli_args = GlobalArgs::parse(); | ||||
| 
 | ||||
|     let harmony_path = Path::new(&cli_args.harmony_path) | ||||
| @ -199,8 +199,6 @@ async fn main() { | ||||
|     } | ||||
| 
 | ||||
|     instrumentation::instrument(HarmonyComposerEvent::Shutdown).unwrap(); | ||||
| 
 | ||||
|     let _ = tokio::try_join!(hc_logger_handle); | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, clap::ValueEnum)] | ||||
|  | ||||
| @ -10,3 +10,8 @@ serde.workspace = true | ||||
| serde_json.workspace = true | ||||
| log.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_json::Value; | ||||
| use std::fs; | ||||
| @ -104,48 +104,58 @@ impl PhysicalHost { | ||||
| 
 | ||||
|     fn all_tools_available() -> Result<(), String> { | ||||
|         let required_tools = [ | ||||
|             ("lsblk", "--version"), | ||||
|             ("lspci", "--version"), | ||||
|             ("lsmod", "--version"), | ||||
|             ("dmidecode", "--version"), | ||||
|             ("smartctl", "--version"), | ||||
|             ("ip", "route"), // No version flag available
 | ||||
|             ("lsblk", Some("--version")), | ||||
|             ("lspci", Some("--version")), | ||||
|             ("lsmod", None), | ||||
|             ("dmidecode", Some("--version")), | ||||
|             ("smartctl", Some("--version")), | ||||
|             ("ip", Some("route")), // No version flag available
 | ||||
|         ]; | ||||
| 
 | ||||
|         let mut missing_tools = Vec::new(); | ||||
| 
 | ||||
|         debug!("Looking for required_tools {required_tools:?}"); | ||||
|         for (tool, tool_arg) in required_tools.iter() { | ||||
|             // 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() | ||||
|             } else { | ||||
|                 // Fallback: manual PATH search if which(1) is unavailable
 | ||||
|                 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 { | ||||
|                 // 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); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             // Verify tool is functional by checking version/help output
 | ||||
|             let mut cmd = Command::new(tool); | ||||
|             if let Some(tool_arg) = tool_arg { | ||||
|                 cmd.arg(tool_arg); | ||||
|             } | ||||
|             cmd.stdout(std::process::Stdio::null()); | ||||
|             cmd.stderr(std::process::Stdio::null()); | ||||
| 
 | ||||
|             if let Ok(status) = cmd.status() { | ||||
|                 if !status.success() { | ||||
|                     warn!("Unable to test {tool} status failed"); | ||||
|                     missing_tools.push(*tool); | ||||
|                 } | ||||
|             } else { | ||||
|                 warn!("Unable to test {tool}"); | ||||
|                 missing_tools.push(*tool); | ||||
|             } | ||||
|         } | ||||
| @ -167,6 +177,7 @@ impl PhysicalHost { | ||||
| 
 | ||||
|     #[cfg(unix)] | ||||
|     fn is_executable(path: &std::path::Path) -> bool { | ||||
|         debug!("Checking if {} is executable", path.to_string_lossy()); | ||||
|         use std::os::unix::fs::PermissionsExt; | ||||
| 
 | ||||
|         match std::fs::metadata(path) { | ||||
| @ -285,11 +296,11 @@ impl PhysicalHost { | ||||
|             if device_path.exists() { | ||||
|                 if drive.model.is_empty() { | ||||
|                     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() { | ||||
|                     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()) | ||||
|         } else if device_name.starts_with("vd") { | ||||
|             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 { | ||||
|             // Try to determine from device path
 | ||||
|             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
 | ||||
| use actix_web::{App, HttpServer, Responder, get}; | ||||
| use hwinfo::PhysicalHost; | ||||
| use log::error; | ||||
| use std::env; | ||||
| 
 | ||||
| use crate::hwinfo::PhysicalHost; | ||||
| 
 | ||||
| mod hwinfo; | ||||
| mod local_presence; | ||||
| 
 | ||||
| #[get("/inventory")] | ||||
| async fn inventory() -> impl Responder { | ||||
| @ -26,10 +29,17 @@ async fn main() -> std::io::Result<()> { | ||||
|     env_logger::init(); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     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)) | ||||
|         .bind(&bind_addr)? | ||||
|         .run() | ||||
|  | ||||
| @ -1,19 +1,20 @@ | ||||
| [package] | ||||
| name = "harmony-secret" | ||||
| name = "harmony_secret" | ||||
| edition = "2024" | ||||
| version.workspace = true | ||||
| readme.workspace = true | ||||
| license.workspace = true | ||||
| 
 | ||||
| [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_json = "1.0.127" | ||||
| thiserror.workspace = true | ||||
| lazy_static.workspace = true | ||||
| directories.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 | ||||
| async-trait.workspace = true | ||||
| http.workspace = true | ||||
|  | ||||
| @ -126,6 +126,7 @@ impl SecretManager { | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use super::*; | ||||
|     #[cfg(secrete2etest)] | ||||
|     use pretty_assertions::assert_eq; | ||||
|     use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
|  | ||||
| @ -70,7 +70,6 @@ mod tests { | ||||
|     #[tokio::test] | ||||
|     async fn test_set_and_get_raw_successfully() { | ||||
|         let dir = tempdir().unwrap(); | ||||
|         let store = LocalFileSecretStore::default(); | ||||
|         let ns = "test-ns"; | ||||
|         let key = "test-key"; | ||||
|         let value = b"{\"data\":\"test-value\"}"; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| [package] | ||||
| name = "harmony-secret-derive" | ||||
| name = "harmony_secret_derive" | ||||
| version = "0.1.0" | ||||
| 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.
 | ||||
|     // 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.
 | ||||
|     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::Name(name)) => { | ||||
|             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() { | ||||
|     instrumentation::subscribe("Harmony TUI Logger", async |event| { | ||||
|         if let HarmonyEvent::HarmonyFinished = event { | ||||
|             return false; | ||||
|         }; | ||||
|         true | ||||
|     }) | ||||
|     .await; | ||||
|     instrumentation::subscribe("Harmony TUI Logger", |_| { | ||||
|         // TODO: Display events in the TUI
 | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| pub struct HarmonyTUI<T: Topology> { | ||||
|  | ||||
| @ -11,7 +11,7 @@ async-trait = { workspace = true } | ||||
| tokio = { workspace = true } | ||||
| octocrab = "0.44.0" | ||||
| 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 | ||||
| sha2 = "0.10.8" | ||||
| futures-util = "0.3.31" | ||||
|  | ||||
| @ -30,15 +30,15 @@ pub struct CaddyGeneral { | ||||
|     #[yaserde(rename = "TlsDnsApiKey")] | ||||
|     pub tls_dns_api_key: MaybeString, | ||||
|     #[yaserde(rename = "TlsDnsSecretApiKey")] | ||||
|     pub tls_dns_secret_api_key: MaybeString, | ||||
|     pub tls_dns_secret_api_key: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsOptionalField1")] | ||||
|     pub tls_dns_optional_field1: MaybeString, | ||||
|     pub tls_dns_optional_field1: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsOptionalField2")] | ||||
|     pub tls_dns_optional_field2: MaybeString, | ||||
|     pub tls_dns_optional_field2: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsOptionalField3")] | ||||
|     pub tls_dns_optional_field3: MaybeString, | ||||
|     pub tls_dns_optional_field3: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsOptionalField4")] | ||||
|     pub tls_dns_optional_field4: MaybeString, | ||||
|     pub tls_dns_optional_field4: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsPropagationTimeout")] | ||||
|     pub tls_dns_propagation_timeout: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsPropagationTimeoutPeriod")] | ||||
| @ -47,6 +47,8 @@ pub struct CaddyGeneral { | ||||
|     pub tls_dns_propagation_delay: Option<MaybeString>, | ||||
|     #[yaserde(rename = "TlsDnsPropagationResolvers")] | ||||
|     pub tls_dns_propagation_resolvers: MaybeString, | ||||
|     #[yaserde(rename = "TlsDnsEchDomain")] | ||||
|     pub tls_dns_ech_domain: Option<MaybeString>, | ||||
|     pub accesslist: MaybeString, | ||||
|     #[yaserde(rename = "DisableSuperuser")] | ||||
|     pub disable_superuser: Option<i32>, | ||||
| @ -56,6 +58,10 @@ pub struct CaddyGeneral { | ||||
|     pub http_version: Option<MaybeString>, | ||||
|     #[yaserde(rename = "HttpVersions")] | ||||
|     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")] | ||||
|     pub log_credentials: MaybeString, | ||||
|     #[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")] | ||||
|     pub physical_interface_name: String, | ||||
|     pub descr: Option<MaybeString>, | ||||
|     pub mtu: Option<MaybeString>, | ||||
|     pub enable: MaybeString, | ||||
|     pub lock: Option<MaybeString>, | ||||
|     #[yaserde(rename = "spoofmac")] | ||||
|     pub spoof_mac: Option<MaybeString>, | ||||
|     pub mss: Option<MaybeString>, | ||||
|     pub ipaddr: Option<MaybeString>, | ||||
|     pub dhcphostname: Option<MaybeString>, | ||||
|     #[yaserde(rename = "alias-address")] | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| mod caddy; | ||||
| mod dhcpd; | ||||
| pub mod dnsmasq; | ||||
| mod haproxy; | ||||
| mod interfaces; | ||||
| mod opnsense; | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| use crate::dnsmasq::DnsMasq; | ||||
| use crate::HAProxy; | ||||
| use crate::{data::dhcpd::DhcpInterface, xml_utils::to_xml_str}; | ||||
| use log::error; | ||||
| @ -22,7 +23,7 @@ pub struct OPNsense { | ||||
|     pub load_balancer: Option<LoadBalancer>, | ||||
|     pub rrd: Option<RawXml>, | ||||
|     pub ntpd: Ntpd, | ||||
|     pub widgets: Widgets, | ||||
|     pub widgets: Option<Widgets>, | ||||
|     pub revision: Revision, | ||||
|     #[yaserde(rename = "OPNsense")] | ||||
|     pub opnsense: OPNsenseXmlSection, | ||||
| @ -45,7 +46,7 @@ pub struct OPNsense { | ||||
|     #[yaserde(rename = "Pischem")] | ||||
|     pub pischem: Option<Pischem>, | ||||
|     pub ifgroups: Ifgroups, | ||||
|     pub dnsmasq: Option<RawXml>, | ||||
|     pub dnsmasq: Option<DnsMasq>, | ||||
| } | ||||
| 
 | ||||
| impl From<String> for OPNsense { | ||||
| @ -165,9 +166,9 @@ pub struct Sysctl { | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| pub struct SysctlItem { | ||||
|     pub descr: MaybeString, | ||||
|     pub tunable: String, | ||||
|     pub value: MaybeString, | ||||
|     pub descr: Option<MaybeString>, | ||||
|     pub tunable: Option<String>, | ||||
|     pub value: Option<MaybeString>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| @ -182,8 +183,8 @@ pub struct System { | ||||
|     pub domain: String, | ||||
|     pub group: Vec<Group>, | ||||
|     pub user: Vec<User>, | ||||
|     pub nextuid: u32, | ||||
|     pub nextgid: u32, | ||||
|     pub nextuid: Option<u32>, | ||||
|     pub nextgid: Option<u32>, | ||||
|     pub timezone: String, | ||||
|     pub timeservers: String, | ||||
|     pub webgui: WebGui, | ||||
| @ -242,6 +243,7 @@ pub struct Ssh { | ||||
|     pub passwordauth: u8, | ||||
|     pub keysig: MaybeString, | ||||
|     pub permitrootlogin: u8, | ||||
|     pub rekeylimit: Option<MaybeString>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| @ -271,6 +273,7 @@ pub struct Group { | ||||
|     pub member: Vec<u32>, | ||||
|     #[yaserde(rename = "priv")] | ||||
|     pub priv_field: String, | ||||
|     pub source_networks: Option<MaybeString>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| @ -1506,7 +1509,7 @@ pub struct Vlans { | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| pub struct Bridges { | ||||
|     pub bridged: MaybeString, | ||||
|     pub bridged: Option<MaybeString>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
|  | ||||
| @ -20,6 +20,7 @@ russh-sftp = "2.0.6" | ||||
| serde_json = "1.0.133" | ||||
| tokio-util = { version = "0.7.13", features = ["codec"] } | ||||
| tokio-stream = "0.1.17" | ||||
| uuid.workspace = true | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| pretty_assertions.workspace = true | ||||
|  | ||||
| @ -4,8 +4,8 @@ use crate::{ | ||||
|     config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, | ||||
|     error::Error, | ||||
|     modules::{ | ||||
|         caddy::CaddyConfig, dhcp::DhcpConfig, dns::DnsConfig, load_balancer::LoadBalancerConfig, | ||||
|         tftp::TftpConfig, | ||||
|         caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::DnsConfig, | ||||
|         dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig, | ||||
|     }, | ||||
| }; | ||||
| use log::{debug, info, trace, warn}; | ||||
| @ -43,23 +43,27 @@ impl Config { | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn dhcp(&mut self) -> DhcpConfig { | ||||
|         DhcpConfig::new(&mut self.opnsense, self.shell.clone()) | ||||
|     pub fn dhcp_legacy_isc(&mut self) -> DhcpConfigLegacyISC<'_> { | ||||
|         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) | ||||
|     } | ||||
| 
 | ||||
|     pub fn tftp(&mut self) -> TftpConfig { | ||||
|     pub fn tftp(&mut self) -> TftpConfig<'_> { | ||||
|         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()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn load_balancer(&mut self) -> LoadBalancerConfig { | ||||
|     pub fn load_balancer(&mut self) -> LoadBalancerConfig<'_> { | ||||
|         LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone()) | ||||
|     } | ||||
| 
 | ||||
| @ -67,6 +71,10 @@ impl Config { | ||||
|         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
 | ||||
|     /// name.
 | ||||
|     ///
 | ||||
| @ -200,7 +208,7 @@ impl Config { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::config::{DummyOPNSenseShell, LocalFileConfigManager}; | ||||
|     use crate::modules::dhcp::DhcpConfig; | ||||
|     use crate::modules::dhcp_legacy::DhcpConfigLegacyISC; | ||||
|     use std::fs; | ||||
|     use std::net::Ipv4Addr; | ||||
| 
 | ||||
| @ -215,6 +223,9 @@ mod tests { | ||||
|             "src/tests/data/config-vm-test.xml", | ||||
|             "src/tests/data/config-structure.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")); | ||||
|             test_file_path.push(path); | ||||
| @ -257,7 +268,7 @@ mod tests { | ||||
| 
 | ||||
|         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 | ||||
|             .add_static_mapping( | ||||
|                 "00:00:00:00:00:00", | ||||
|  | ||||
| @ -9,6 +9,7 @@ use crate::Error; | ||||
| pub trait OPNsenseShell: std::fmt::Debug + Send + Sync { | ||||
|     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_file(&self, content: &str, filename: &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> { | ||||
|         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> { | ||||
|         unimplemented!("This is a dummy implementation"); | ||||
|     } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| use std::{ | ||||
|     net::IpAddr, | ||||
|     path::Path, | ||||
|     sync::Arc, | ||||
|     time::{SystemTime, UNIX_EPOCH}, | ||||
| }; | ||||
| @ -44,6 +45,10 @@ impl OPNsenseShell for SshOPNSenseShell { | ||||
|                 .unwrap() | ||||
|                 .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?; | ||||
|         channel | ||||
|             .request_subsystem(true, "sftp") | ||||
| @ -53,10 +58,18 @@ impl OPNsenseShell for SshOPNSenseShell { | ||||
|             .await | ||||
|             .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?; | ||||
| 
 | ||||
|         Ok(temp_filename) | ||||
|         Ok(filename.to_string()) | ||||
|     } | ||||
| 
 | ||||
|     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error> { | ||||
| @ -69,10 +82,7 @@ impl OPNsenseShell for SshOPNSenseShell { | ||||
|             .await | ||||
|             .expect("Should acquire sftp subsystem"); | ||||
| 
 | ||||
|         if !sftp.try_exists(destination).await? { | ||||
|             info!("Creating remote directory {destination}"); | ||||
|             sftp.create_dir(destination).await?; | ||||
|         } | ||||
|         self.ensure_remote_dir_exists(&sftp, destination).await?; | ||||
| 
 | ||||
|         info!("Reading local directory {source}"); | ||||
|         let mut entries = read_dir(source).await?; | ||||
| @ -149,6 +159,14 @@ impl SshOPNSenseShell { | ||||
|         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 { | ||||
|         info!("Initializing SshOPNSenseShell on host {host:?}"); | ||||
|         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)] | ||||
| pub enum DhcpError { | ||||
|     InvalidMacAddress(String), | ||||
| @ -21,6 +5,7 @@ pub enum DhcpError { | ||||
|     IpAddressAlreadyMapped(String), | ||||
|     MacAddressAlreadyMapped(String), | ||||
|     IpAddressOutOfRange(String), | ||||
|     Configuration(String), | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Display for DhcpError { | ||||
| @ -37,158 +22,9 @@ impl std::fmt::Display for DhcpError { | ||||
|             DhcpError::IpAddressOutOfRange(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<'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 dhcp; | ||||
| pub mod dhcp_legacy; | ||||
| pub mod dns; | ||||
| pub mod dnsmasq; | ||||
| pub mod load_balancer; | ||||
| 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