feat: Include boot.ipxe script
This commit is contained in:
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
#!ipxe
|
#!ipxe
|
||||||
|
|
||||||
dhcp
|
dhcp
|
||||||
|
chain boot.ipxe
|
||||||
echo "Hello, World!"
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#!ipxe
|
||||||
|
echo Starting ${serial}
|
||||||
|
|
||||||
|
:start
|
||||||
|
goto node_${serial} || exit
|
||||||
|
# Default behavior (non install mode) is to exit iPXE script
|
||||||
|
|
||||||
|
{% for cluster in clusters%}
|
||||||
|
{% for _, node in cluster.nodes|items %}
|
||||||
|
{%- if node.install.serial -%}
|
||||||
|
# {{ cluster.name }}/{{ node.hostname }}
|
||||||
|
:node_{{ node.install.serial }}
|
||||||
|
imgfree
|
||||||
|
kernel https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/kernel-{{ node.arch }} {{ node.kernelArgs|join(" ") }} {% if node.install.auto %}talos.config={{httpUrl}}/talos/{{cluster.name}}/{{node.hostname}}.yaml{% endif +%}
|
||||||
|
initrd https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/initramfs-{{ node.arch }}.xz
|
||||||
|
boot
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
+40
-36
@@ -25,46 +25,50 @@ fn apply_netmask(
|
|||||||
Ok((*ip & *netmask).to_string())
|
Ok((*ip & *netmask).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn configure_environment(env: &mut Environment) {
|
||||||
|
// Configure jinja
|
||||||
|
env.set_trim_blocks(true);
|
||||||
|
env.set_lstrip_blocks(true);
|
||||||
|
env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);
|
||||||
|
env.set_auto_escape_callback(|_| AutoEscape::None);
|
||||||
|
|
||||||
|
// Add filters
|
||||||
|
env.add_filter("to_prefix", |netmask: ViaDeserialize<Ipv4Addr>| {
|
||||||
|
let mask = netmask.to_bits();
|
||||||
|
let prefix = mask.leading_ones();
|
||||||
|
|
||||||
|
if mask.checked_shl(prefix).unwrap_or(0) == 0 {
|
||||||
|
Ok(prefix as u8)
|
||||||
|
} else {
|
||||||
|
Err(minijinja::Error::new(
|
||||||
|
minijinja::ErrorKind::InvalidOperation,
|
||||||
|
"invalid IP prefix length",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
env.add_function("apply_netmask", apply_netmask);
|
||||||
|
|
||||||
|
// Helper function for getting the path to kubeconfig files
|
||||||
|
env.add_filter("kubeconfig", move |names: Vec<String>| {
|
||||||
|
names
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
get_configs_path()
|
||||||
|
.join(name)
|
||||||
|
.join("kubeconfig")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> PathEnvironment<'a> {
|
impl<'a> PathEnvironment<'a> {
|
||||||
pub fn new(path: &Path) -> Self {
|
pub fn new(path: &Path) -> Self {
|
||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
|
|
||||||
// Configure jinja
|
configure_environment(&mut env);
|
||||||
env.set_trim_blocks(true);
|
|
||||||
env.set_lstrip_blocks(true);
|
|
||||||
env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);
|
|
||||||
env.set_auto_escape_callback(|_| AutoEscape::None);
|
|
||||||
|
|
||||||
// Add filters
|
|
||||||
env.add_filter("to_prefix", |netmask: ViaDeserialize<Ipv4Addr>| {
|
|
||||||
let mask = netmask.to_bits();
|
|
||||||
let prefix = mask.leading_ones();
|
|
||||||
|
|
||||||
if mask.checked_shl(prefix).unwrap_or(0) == 0 {
|
|
||||||
Ok(prefix as u8)
|
|
||||||
} else {
|
|
||||||
Err(minijinja::Error::new(
|
|
||||||
minijinja::ErrorKind::InvalidOperation,
|
|
||||||
"invalid IP prefix length",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
env.add_function("apply_netmask", apply_netmask);
|
|
||||||
|
|
||||||
// Helper function for getting the path to kubeconfig files
|
|
||||||
env.add_filter("kubeconfig", move |names: Vec<String>| {
|
|
||||||
names
|
|
||||||
.iter()
|
|
||||||
.map(|name| {
|
|
||||||
get_configs_path()
|
|
||||||
.join(name)
|
|
||||||
.join("kubeconfig")
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add path loader
|
// Add path loader
|
||||||
env.set_loader(path_loader(&path));
|
env.set_loader(path_loader(&path));
|
||||||
|
|||||||
+25
-2
@@ -1,9 +1,14 @@
|
|||||||
use crete::cluster::Cluster;
|
use crete::cluster::Cluster;
|
||||||
|
use crete::environment::configure_environment;
|
||||||
use ipxe::IPXE_DIR;
|
use ipxe::IPXE_DIR;
|
||||||
|
use minijinja::{Environment, context};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
// TODO: Make this a proper async function
|
// TODO: Make this a proper async function
|
||||||
pub fn get_file(filename: impl ToString) -> impl Future<Output = Option<(Vec<u8>, &'static str)>> {
|
pub fn get_file(
|
||||||
|
filename: impl ToString,
|
||||||
|
http_url: impl ToString,
|
||||||
|
) -> impl Future<Output = Option<(Vec<u8>, &'static str)>> {
|
||||||
// TODO: Make this a static that is initialized once
|
// TODO: Make this a static that is initialized once
|
||||||
let re =
|
let re =
|
||||||
Regex::new(r"^talos/(?<cluster>.+)/(?<node>.+)\.ya?ml$").expect("regex should be valid");
|
Regex::new(r"^talos/(?<cluster>.+)/(?<node>.+)\.ya?ml$").expect("regex should be valid");
|
||||||
@@ -13,7 +18,25 @@ pub fn get_file(filename: impl ToString) -> impl Future<Output = Option<(Vec<u8>
|
|||||||
|
|
||||||
async move {
|
async move {
|
||||||
const IPXE_PATTERN: &str = "ipxe/";
|
const IPXE_PATTERN: &str = "ipxe/";
|
||||||
if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) {
|
if filename == format!("{IPXE_PATTERN}boot.ipxe") {
|
||||||
|
let mut env = Environment::new();
|
||||||
|
|
||||||
|
configure_environment(&mut env);
|
||||||
|
|
||||||
|
let template = env
|
||||||
|
.template_from_str(include_str!("./boot.ipxe"))
|
||||||
|
.expect("Template should be valid");
|
||||||
|
|
||||||
|
let clusters = Cluster::get_all().await;
|
||||||
|
let content = template
|
||||||
|
.render(context! {
|
||||||
|
clusters => clusters.values().collect::<Vec<_>>(),
|
||||||
|
httpUrl => http_url.to_string()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Some((content.into(), "text/plain; charset=utf-8"))
|
||||||
|
} else if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) {
|
||||||
Some((
|
Some((
|
||||||
IPXE_DIR.get_file(filename)?.contents().to_owned(),
|
IPXE_DIR.get_file(filename)?.contents().to_owned(),
|
||||||
"application/octet-stream",
|
"application/octet-stream",
|
||||||
|
|||||||
+16
-1
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use axum::debug_handler;
|
use axum::debug_handler;
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
|
use axum::http::Uri;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use reqwest::{StatusCode, header};
|
use reqwest::{StatusCode, header};
|
||||||
|
|
||||||
@@ -9,11 +10,25 @@ use crate::file::get_file;
|
|||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn handler(req: Request) -> impl IntoResponse {
|
pub async fn handler(req: Request) -> impl IntoResponse {
|
||||||
|
let uri: Uri = req
|
||||||
|
.headers()
|
||||||
|
.iter()
|
||||||
|
.find(|(name, _)| header::HOST.eq(name))
|
||||||
|
.map(|(_, value)| value)
|
||||||
|
.expect("Host header should exist")
|
||||||
|
.to_str()
|
||||||
|
.expect("Header value should be valid ASCII")
|
||||||
|
.parse()
|
||||||
|
.expect("Host should be valid Uri");
|
||||||
|
|
||||||
|
let host = uri.host().expect("Uri should have host");
|
||||||
|
let port = uri.port().map(|port| port.as_u16()).unwrap_or(80);
|
||||||
|
|
||||||
let Some(path) = req.uri().path().strip_prefix("/") else {
|
let Some(path) = req.uri().path().strip_prefix("/") else {
|
||||||
return StatusCode::BAD_REQUEST.into_response();
|
return StatusCode::BAD_REQUEST.into_response();
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some((file, content_type)) = get_file(path).await else {
|
let Some((file, content_type)) = get_file(path, format!("http://{host}:{port}")).await else {
|
||||||
return StatusCode::NOT_FOUND.into_response();
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -114,9 +114,13 @@ async fn main() -> Result<(), Error> {
|
|||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
tftp::serve(ip, move |filename, _| {
|
tftp::serve(ip, move |filename, ip| {
|
||||||
let filename = filename.to_owned();
|
let filename = filename.to_owned();
|
||||||
async move { get_file(filename).await.map(|(file, _)| file) }
|
async move {
|
||||||
|
get_file(filename, format!("http://{ip}:{port}"))
|
||||||
|
.await
|
||||||
|
.map(|(file, _)| file)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user