Merge pull request 'doc/pxe_test_setup' (#117) from doc/pxe_test_setup into master
All checks were successful
Run Check Script / check (push) Successful in 1m15s
Compile and package harmony_composer / package_harmony_composer (push) Successful in 6m45s

Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/117
This commit is contained in:
johnride 2025-08-30 14:33:36 +00:00
commit 269f13ae9b
91 changed files with 7880 additions and 959 deletions

744
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ members = [
"harmony_composer",
"harmony_inventory_agent",
"harmony_secret_derive",
"harmony_secret",
"harmony_secret", "adr/agent_discovery/mdns",
]
[workspace.package]

View 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"

View 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.");
}

View 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();
}

View 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,
}
}

View File

@ -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
View 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

View 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

View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

108
docs/pxe_test/README.md Normal file
View 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
View 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

View File

@ -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,
)

View File

@ -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(

View 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

View 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();
}

View 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![],
}
}

View File

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHAAAAJikacCNpGnA
jQAAAAtzc2gtZWQyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHA
AAAECiiKk4V6Q5cVs6axDM4sjAzZn/QCZLQekmYQXS9XbEYxx6bDylvC68cVpjKfEFtLQJ
/dOFi6PVS2vsIOqPDJIcAAAAEGplYW5nYWJAbGlsaWFuZTIBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2

View File

@ -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,

View File

@ -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

View 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}")),
}
}
}

View File

@ -1,4 +1,6 @@
mod file;
mod id;
mod version;
pub use file::*;
pub use id::*;
pub use version::*;

View File

@ -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(())
}

View File

@ -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"),
}
}

View File

@ -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> {

View File

@ -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)
}

View File

@ -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>;

View File

@ -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" {

View File

@ -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>;

View File

@ -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}"))
})
}
}

View File

@ -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> {

View File

@ -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> {

View File

@ -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,

View File

@ -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(",")
)))
}

View 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:?}");
}

View File

@ -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;

View File

@ -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")]

View 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,
}

View File

@ -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;

View File

@ -1,5 +1,4 @@
use std::{
process::Command,
sync::Arc,
time::{Duration, Instant},
};

View File

@ -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 {

View 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

View 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

View 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.

View File

@ -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(&current_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() && &current_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;
});
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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(())
}

View File

@ -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)]

View File

@ -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"

View 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
```

View File

@ -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"))?;

View File

@ -0,0 +1,2 @@
mod hwinfo;
pub mod local_presence;

View 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(())
}

View 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();
}
}

View 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,
}

View File

@ -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()

View File

@ -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

View File

@ -126,6 +126,7 @@ impl SecretManager {
#[cfg(test)]
mod test {
use super::*;
#[cfg(secrete2etest)]
use pretty_assertions::assert_eq;
use serde::{Deserialize, Serialize};

View File

@ -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\"}";

View File

@ -1,5 +1,5 @@
[package]
name = "harmony-secret-derive"
name = "harmony_secret_derive"
version = "0.1.0"
edition = "2024"

View File

@ -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());

View File

@ -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> {

View File

@ -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"

View File

@ -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")]

View 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,
}

View File

@ -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")]

View File

@ -1,5 +1,6 @@
mod caddy;
mod dhcpd;
pub mod dnsmasq;
mod haproxy;
mod interfaces;
mod opnsense;

View File

@ -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)]

View File

@ -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

View File

@ -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",

View File

@ -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");
}

View File

@ -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 {

View File

@ -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());
}
}

View 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());
}
}

View 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(())
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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>

View 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>

File diff suppressed because it is too large Load Diff