Compare commits
No commits in common. "0f59f29ac40408ab95bf871f9a954ba9ed532d03" and "57c3b01e667577d4d99706a5bc801c95d1b64d9a" have entirely different histories.
0f59f29ac4
...
57c3b01e66
@ -1,8 +1,3 @@
|
|||||||
Here lies all the data files required for an OKD cluster PXE boot setup.
|
Here lies all the data files required for an OKD cluster PXE boot setup.
|
||||||
|
|
||||||
This inclues ISO files, binary boot files, ipxe, etc.
|
This inclues ISO files, binary boot files, ipxe, etc.
|
||||||
|
|
||||||
TODO as of august 2025 :
|
|
||||||
|
|
||||||
- `harmony_inventory_agent` should be downloaded from official releases, this embedded version is practical for now though
|
|
||||||
- The cluster ssh key should be generated and handled by harmony with the private key saved in a secret store
|
|
||||||
|
|||||||
9
data/pxe/okd/http_files/.gitattributes
vendored
9
data/pxe/okd/http_files/.gitattributes
vendored
@ -1,9 +0,0 @@
|
|||||||
harmony_inventory_agent filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9 filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9/images filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9/initrd.img filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9/vmlinuz filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9/images/efiboot.img filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9/images/install.img filter=lfs diff=lfs merge=lfs -text
|
|
||||||
os/centos-stream-9/images/pxeboot filter=lfs diff=lfs merge=lfs -text
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2
|
|
||||||
BIN
data/pxe/okd/http_files/harmony_inventory_agent
(Stored with Git LFS)
BIN
data/pxe/okd/http_files/harmony_inventory_agent
(Stored with Git LFS)
Binary file not shown.
BIN
data/pxe/okd/http_files/os/centos-stream-9/images/efiboot.img
(Stored with Git LFS)
BIN
data/pxe/okd/http_files/os/centos-stream-9/images/efiboot.img
(Stored with Git LFS)
Binary file not shown.
BIN
data/pxe/okd/http_files/os/centos-stream-9/images/install.img
(Stored with Git LFS)
BIN
data/pxe/okd/http_files/os/centos-stream-9/images/install.img
(Stored with Git LFS)
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)
BIN
data/pxe/okd/http_files/os/centos-stream-9/initrd.img
(Stored with Git LFS)
Binary file not shown.
BIN
data/pxe/okd/http_files/os/centos-stream-9/vmlinuz
(Stored with Git LFS)
BIN
data/pxe/okd/http_files/os/centos-stream-9/vmlinuz
(Stored with Git LFS)
Binary file not shown.
@ -1,7 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
|
||||||
QyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHAAAAJikacCNpGnA
|
|
||||||
jQAAAAtzc2gtZWQyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHA
|
|
||||||
AAAECiiKk4V6Q5cVs6axDM4sjAzZn/QCZLQekmYQXS9XbEYxx6bDylvC68cVpjKfEFtLQJ
|
|
||||||
/dOFi6PVS2vsIOqPDJIcAAAAEGplYW5nYWJAbGlsaWFuZTIBAgMEBQ==
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2
|
|
||||||
@ -30,7 +30,7 @@ echo "Configuring kernel boot arguments..."
|
|||||||
# - inst.ks: CRITICAL: Points to our Kickstart file for automation.
|
# - inst.ks: CRITICAL: Points to our Kickstart file for automation.
|
||||||
# - ip=dhcp: Ensures the live environment configures its network.
|
# - ip=dhcp: Ensures the live environment configures its network.
|
||||||
# - console=...: Provides boot output on both serial and graphical consoles for debugging.
|
# - 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
|
imgargs vmlinuz initrd=initrd.img inst.stage2=${os_base_url} inst.ks=${ks_url} ip=dhcp console=ttyS0,115200 console=tty1
|
||||||
|
|
||||||
echo "Booting into CentOS Stream 9 live environment..."
|
echo "Booting into CentOS Stream 9 live environment..."
|
||||||
boot || goto failed
|
boot || goto failed
|
||||||
|
|||||||
@ -1,38 +1,66 @@
|
|||||||
# --- Pre-Boot Scripting (The Main Goal) ---
|
# =================================================================
|
||||||
|
# Harmony Discovery Agent - Kickstart File (inventory.kickstart)
|
||||||
|
# =================================================================
|
||||||
|
#
|
||||||
|
# This Kickstart file configures the CentOS Stream 9 live environment.
|
||||||
|
# It does NOT install to disk. It sets up SSH for remote access
|
||||||
|
# and downloads and runs the harmony-inventory-agent.
|
||||||
|
#
|
||||||
|
|
||||||
|
# --- System Configuration
|
||||||
|
lang en_US.UTF-8
|
||||||
|
keyboard --xlayouts='us'
|
||||||
|
timezone America/New_York --isUtc
|
||||||
|
|
||||||
|
# --- Network Configuration
|
||||||
|
# Ensure the network is activated using DHCP.
|
||||||
|
network --bootproto=dhcp --device=link --activate
|
||||||
|
|
||||||
|
# --- Security Configuration
|
||||||
|
# Disable the firewall for this isolated provisioning network.
|
||||||
|
firewall --disabled
|
||||||
|
# Disable SELinux for simplicity in the live environment.
|
||||||
|
selinux --disabled
|
||||||
|
# Disable password-based root login for security.
|
||||||
|
rootpw --lock
|
||||||
|
|
||||||
|
# --- Service Configuration
|
||||||
|
# Ensure the SSH daemon is enabled.
|
||||||
|
services --enabled="sshd"
|
||||||
|
|
||||||
|
# We are running a live environment, so no disk partitioning.
|
||||||
|
# The 'liveimg' command would be used here if booting from a squashfs,
|
||||||
|
# but since we are booting from kernel/initrd, we just use the %post.
|
||||||
|
|
||||||
|
# Do not run the graphical initial setup wizard.
|
||||||
|
firstboot --disable
|
||||||
|
|
||||||
|
# --- Post-Boot Scripting
|
||||||
# This section runs after the live environment has booted into RAM.
|
# This section runs after the live environment has booted into RAM.
|
||||||
# It sets up SSH and downloads/runs the harmony-inventory-agent.
|
%post --log=/root/ks-post.log
|
||||||
%pre --log=/root/ks-pre.log
|
|
||||||
|
|
||||||
echo "Harmony Kickstart: Pre-boot script started."
|
echo "Harmony Kickstart: Post-boot script started."
|
||||||
|
|
||||||
# 1. Configure SSH Access for Root
|
# 1. Configure SSH Access
|
||||||
# Create the .ssh directory and set correct permissions.
|
# Create the .ssh directory and set correct permissions.
|
||||||
echo " - Setting up SSH authorized_keys for root..."
|
echo " - Setting up SSH authorized_keys..."
|
||||||
mkdir -p /root/.ssh
|
mkdir -p /root/.ssh
|
||||||
chmod 700 /root/.ssh
|
chmod 700 /root/.ssh
|
||||||
|
|
||||||
# Download the public key from the provisioning server.
|
# Download the public key and place it in authorized_keys.
|
||||||
# The -sS flags make curl silent but show errors. -L follows redirects.
|
curl -sSL "http://{{ gateway_ip }}:8080/{{ cluster_pubkey_filename }}" -o /root/.ssh/authorized_keys
|
||||||
curl -vSL "http://{{ gateway_ip }}:8080/{{ cluster_pubkey_filename }}" -o /root/.ssh/authorized_keys
|
chmod 600 /root/.ssh/authorized_keys
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo " - ERROR: Failed to download SSH public key."
|
# SELinux context is handled by 'selinux --disabled' above,
|
||||||
else
|
# but if SELinux were enabled, this would be essential:
|
||||||
echo " - SSH key downloaded successfully."
|
# restorecon -R /root/.ssh
|
||||||
chmod 600 /root/.ssh/authorized_keys
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. Download the Harmony Inventory Agent
|
# 2. Download the Harmony Inventory Agent
|
||||||
echo " - Downloading harmony-inventory-agent..."
|
echo " - Downloading harmony-inventory-agent..."
|
||||||
curl -vSL "http://{{ gateway_ip }}:8080/{{ harmony_inventory_agent }}" -o /usr/bin/harmony-inventory-agent
|
curl -sSL "http://{{ gateway_ip }}:8080/{{ harmony_inventory_agent }}" -o /usr/local/bin/harmony-inventory-agent
|
||||||
if [ $? -ne 0 ]; then
|
chmod +x /usr/local/bin/harmony-inventory-agent
|
||||||
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.
|
# 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..."
|
echo " - Creating systemd service for the agent..."
|
||||||
cat > /etc/systemd/system/harmony-agent.service << EOF
|
cat > /etc/systemd/system/harmony-agent.service << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
@ -41,9 +69,8 @@ After=network-online.target
|
|||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
ExecStart=/usr/local/bin/harmony-inventory-agent
|
||||||
ExecStart=/usr/bin/harmony-inventory-agent
|
Restart=always
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
@ -51,77 +78,15 @@ WantedBy=multi-user.target
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# 4. Enable and start the service
|
# 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..."
|
echo " - Enabling and starting harmony-agent.service..."
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now harmony-agent.service
|
systemctl enable --now harmony-agent.service
|
||||||
|
|
||||||
# Check if the service started correctly
|
echo "Harmony Kickstart: Post-boot script finished. The inventory agent is running."
|
||||||
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."
|
curl localhost:8080/inventory | tee -a /tmp/harmony_inventory.json
|
||||||
|
|
||||||
echo "Running cat - to pause system indefinitely"
|
|
||||||
cat -
|
|
||||||
|
|
||||||
%end
|
%end
|
||||||
|
|
||||||
# =================================================================
|
# Do not automatically reboot or poweroff.
|
||||||
# Harmony Discovery Agent - Kickstart File (NON-INSTALL, LIVE BOOT)
|
# The machine should remain running for inventory scraping.
|
||||||
# =================================================================
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use log::{debug, warn};
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@ -104,58 +104,48 @@ impl PhysicalHost {
|
|||||||
|
|
||||||
fn all_tools_available() -> Result<(), String> {
|
fn all_tools_available() -> Result<(), String> {
|
||||||
let required_tools = [
|
let required_tools = [
|
||||||
("lsblk", Some("--version")),
|
("lsblk", "--version"),
|
||||||
("lspci", Some("--version")),
|
("lspci", "--version"),
|
||||||
("lsmod", None),
|
("lsmod", "--version"),
|
||||||
("dmidecode", Some("--version")),
|
("dmidecode", "--version"),
|
||||||
("smartctl", Some("--version")),
|
("smartctl", "--version"),
|
||||||
("ip", Some("route")), // No version flag available
|
("ip", "route"), // No version flag available
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut missing_tools = Vec::new();
|
let mut missing_tools = Vec::new();
|
||||||
|
|
||||||
debug!("Looking for required_tools {required_tools:?}");
|
|
||||||
for (tool, tool_arg) in required_tools.iter() {
|
for (tool, tool_arg) in required_tools.iter() {
|
||||||
// First check if tool exists in PATH using which(1)
|
// First check if tool exists in PATH using which(1)
|
||||||
let mut exists = if let Ok(output) = Command::new("which").arg(tool).output() {
|
let exists = if let Ok(output) = Command::new("which").arg(tool).output() {
|
||||||
output.status.success()
|
output.status.success()
|
||||||
} else {
|
} else {
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
// Fallback: manual PATH search if which(1) is unavailable
|
// Fallback: manual PATH search if which(1) is unavailable
|
||||||
debug!("Looking for {tool} in path");
|
|
||||||
if let Ok(path_var) = std::env::var("PATH") {
|
if let Ok(path_var) = std::env::var("PATH") {
|
||||||
debug!("PATH is {path_var}");
|
path_var.split(':').any(|dir| {
|
||||||
exists = path_var.split(':').any(|dir| {
|
|
||||||
let tool_path = std::path::Path::new(dir).join(tool);
|
let tool_path = std::path::Path::new(dir).join(tool);
|
||||||
tool_path.exists() && Self::is_executable(&tool_path)
|
tool_path.exists() && Self::is_executable(&tool_path)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
warn!("Unable to find tool {tool} from PATH");
|
|
||||||
missing_tools.push(*tool);
|
missing_tools.push(*tool);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify tool is functional by checking version/help output
|
// Verify tool is functional by checking version/help output
|
||||||
let mut cmd = Command::new(tool);
|
let mut cmd = Command::new(tool);
|
||||||
if let Some(tool_arg) = tool_arg {
|
cmd.arg(tool_arg);
|
||||||
cmd.arg(tool_arg);
|
|
||||||
}
|
|
||||||
cmd.stdout(std::process::Stdio::null());
|
cmd.stdout(std::process::Stdio::null());
|
||||||
cmd.stderr(std::process::Stdio::null());
|
cmd.stderr(std::process::Stdio::null());
|
||||||
|
|
||||||
if let Ok(status) = cmd.status() {
|
if let Ok(status) = cmd.status() {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
warn!("Unable to test {tool} status failed");
|
|
||||||
missing_tools.push(*tool);
|
missing_tools.push(*tool);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to test {tool}");
|
|
||||||
missing_tools.push(*tool);
|
missing_tools.push(*tool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +167,6 @@ impl PhysicalHost {
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn is_executable(path: &std::path::Path) -> bool {
|
fn is_executable(path: &std::path::Path) -> bool {
|
||||||
debug!("Checking if {} is executable", path.to_string_lossy());
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
match std::fs::metadata(path) {
|
match std::fs::metadata(path) {
|
||||||
@ -296,11 +285,11 @@ impl PhysicalHost {
|
|||||||
if device_path.exists() {
|
if device_path.exists() {
|
||||||
if drive.model.is_empty() {
|
if drive.model.is_empty() {
|
||||||
drive.model = Self::read_sysfs_string(&device_path.join("device/model"))
|
drive.model = Self::read_sysfs_string(&device_path.join("device/model"))
|
||||||
.unwrap_or(format!("Failed to read model for {}", name));
|
.map_err(|e| format!("Failed to read model for {}: {}", name, e))?;
|
||||||
}
|
}
|
||||||
if drive.serial.is_empty() {
|
if drive.serial.is_empty() {
|
||||||
drive.serial = Self::read_sysfs_string(&device_path.join("device/serial"))
|
drive.serial = Self::read_sysfs_string(&device_path.join("device/serial"))
|
||||||
.unwrap_or(format!("Failed to read serial for {}", name));
|
.map_err(|e| format!("Failed to read serial for {}: {}", name, e))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,10 +655,6 @@ impl PhysicalHost {
|
|||||||
Ok("IDE".to_string())
|
Ok("IDE".to_string())
|
||||||
} else if device_name.starts_with("vd") {
|
} else if device_name.starts_with("vd") {
|
||||||
Ok("VirtIO".to_string())
|
Ok("VirtIO".to_string())
|
||||||
} else if device_name.starts_with("sr") {
|
|
||||||
Ok("CDROM".to_string())
|
|
||||||
} else if device_name.starts_with("zram") {
|
|
||||||
Ok("Ramdisk".to_string())
|
|
||||||
} else {
|
} else {
|
||||||
// Try to determine from device path
|
// Try to determine from device path
|
||||||
let subsystem = Self::read_sysfs_string(&device_path.join("device/subsystem"))?;
|
let subsystem = Self::read_sysfs_string(&device_path.join("device/subsystem"))?;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user