From 406fa6259b1362ab5cd6069d009d71c016261be9 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 2 Jul 2026 04:53:32 +0200 Subject: [PATCH 01/20] feat: Added ftfp server implementation --- Cargo.lock | 104 +++++++++++++ Cargo.toml | 4 + src/lib.rs | 1 + src/tftp/error.rs | 20 +++ src/tftp/mod.rs | 7 + src/tftp/packet.rs | 353 +++++++++++++++++++++++++++++++++++++++++++++ src/tftp/server.rs | 162 +++++++++++++++++++++ 7 files changed, 651 insertions(+) create mode 100644 src/tftp/error.rs create mode 100644 src/tftp/mod.rs create mode 100644 src/tftp/packet.rs create mode 100644 src/tftp/server.rs diff --git a/Cargo.lock b/Cargo.lock index f32d465..c550337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,10 +204,13 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" name = "crete" version = "0.1.3" dependencies = [ + "byteorder", "clap", "clap_complete", "gix-discover", "minijinja", + "netascii", + "num_enum", "optional_struct", "reqwest", "schemars", @@ -216,6 +219,7 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", + "tokio", "walkdir", ] @@ -1083,6 +1087,34 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netascii" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "205c45083b6725eb4671392c7c399510ad1498dd0275f1a2e68a70a34ebdf1b1" + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1224,6 +1256,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1554,6 +1595,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "slab" version = "0.4.12" @@ -1688,11 +1739,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys", ] +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1716,6 +1781,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + [[package]] name = "tower" version = "0.5.3" @@ -1965,6 +2060,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "writeable" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index 0e7cb0f..cbee3bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,13 @@ schemars = { version = "1.2.2", features = ["semver1"] } serde_json = "1.0.151" [dependencies] +byteorder = "1.5.0" clap = { version = "4.6.7", features = ["derive"] } clap_complete = "4.6.11" gix-discover = { version = "0.56.0", features = ["sha1"] } minijinja = { version = "2.24.0", features = ["json", "loader"] } +netascii = "0.1.0" +num_enum = "0.7.6" optional_struct = "0.5.2" reqwest = { version = "0.13.5", default-features = false, features = [ "blocking", @@ -30,6 +33,7 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = { workspace = true } serde_yaml = "0.9.34" thiserror = "2.0.21" +tokio = { version = "1.52.1", features = ["full"] } walkdir = "2.5.0" [lib] diff --git a/src/lib.rs b/src/lib.rs index 8b12a92..7685d78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub mod node; pub mod patch; pub mod schematic; pub mod secret; +pub mod tftp; pub(crate) static REPO_PATH: OnceLock = OnceLock::new(); diff --git a/src/tftp/error.rs b/src/tftp/error.rs new file mode 100644 index 0000000..8fafc2d --- /dev/null +++ b/src/tftp/error.rs @@ -0,0 +1,20 @@ +use std::io; +use std::string::FromUtf8Error; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Invalid op code '{0}'")] + InvalidOpCode(u16), + #[error("Invalid error code '{0}'")] + InvalidErrorCode(u16), + #[error("Invalid mode '{0}'")] + InvalidMode(String), + #[error("String contains invalid characters")] + InvalidString(#[from] FromUtf8Error), + #[error("Unterminated string")] + UnterminatedString, + #[error("Io error: {0}")] + Io(#[from] io::Error), +} diff --git a/src/tftp/mod.rs b/src/tftp/mod.rs new file mode 100644 index 0000000..8eab9f3 --- /dev/null +++ b/src/tftp/mod.rs @@ -0,0 +1,7 @@ +mod error; +mod packet; +mod server; + +pub use error::Error; +pub use packet::Packet; +pub use server::{Error as ServerError, serve}; diff --git a/src/tftp/packet.rs b/src/tftp/packet.rs new file mode 100644 index 0000000..28612a3 --- /dev/null +++ b/src/tftp/packet.rs @@ -0,0 +1,353 @@ +use std::io::{BufRead, BufReader, BufWriter, Cursor, Read, Write}; +use std::net::SocketAddr; + +use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use tokio::net::UdpSocket; + +use crate::tftp::Error; + +// 2 bytes op code + 2 bytes block number + max 512 bytes data +pub(crate) const PACKET_SIZE: usize = 2 + 2 + 512; + +fn decode_null_terminated(reader: &mut BufReader) -> Result +where + R: Read + ?Sized, +{ + let mut text = Vec::new(); + reader.read_until(0, &mut text)?; + + // Make sure we ended with a null terminator + match text.pop() { + Some(0) => {} + None | Some(_) => return Err(Error::UnterminatedString), + } + + Ok(String::from_utf8(text)?) +} + +fn encode_null_terminated(writer: &mut BufWriter, text: impl AsRef) -> Result<(), Error> +where + W: Write + ?Sized, +{ + let text = text.as_ref().as_bytes(); + writer.write_all(text)?; + + // Add null terminator + writer.write_u8(0)?; + + Ok(()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + NetAscii, + Octet, + Mail, +} + +impl Mode { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let mut mode = decode_null_terminated(reader)?; + mode.make_ascii_lowercase(); + let mode = mode; + + Ok(match mode.as_str() { + "netascii" => Self::NetAscii, + "octet" => Self::Octet, + "mail" => Self::Mail, + _ => return Err(Error::InvalidMode(mode)), + }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + let mode = match &self { + Mode::NetAscii => "netascii", + Mode::Octet => "octet", + Mode::Mail => "mail", + }; + + encode_null_terminated(writer, mode)?; + + Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Request { + filename: String, + mode: Mode, +} + +impl Request { + pub fn filename(&self) -> &str { + &self.filename + } + + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let filename = decode_null_terminated(reader)?; + let mode = Mode::decode(reader)?; + + Ok(Self { filename, mode }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + encode_null_terminated(writer, &self.filename)?; + self.mode.encode(writer)?; + + Ok(()) + } + + pub fn mode(&self) -> Mode { + self.mode + } +} + +#[derive(Debug)] +pub struct Data { + block: u16, + data: Vec, +} + +impl Data { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let block = reader.read_u16::()?; + + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + + Ok(Self { data, block }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + writer.write_u16::(self.block)?; + writer.write_all(&self.data)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Ack(u16); + +impl Ack { + pub fn block(&self) -> u16 { + self.0 + } + + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let number = reader.read_u16::()?; + + Ok(Self(number)) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + writer.write_u16::(self.0)?; + + Ok(()) + } +} + +#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy)] +#[repr(u16)] +pub enum ErrorCode { + NotDefined, + FileNotFound, + AccessViolation, + DiskFull, + Illegal, + UnknownTransferId, + FileAlreadyExists, + NoSuchUser, +} + +impl ErrorCode { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let code = reader.read_u16::()?; + Self::try_from_primitive(code).map_err(|err| Error::InvalidErrorCode(err.number)) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + let code: u16 = (*self).into(); + + writer.write_u16::(code)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct ErrorMessage { + code: ErrorCode, + message: String, +} + +impl ErrorMessage { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let code = ErrorCode::decode(reader)?; + let message = decode_null_terminated(reader)?; + + Ok(Self { code, message }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + self.code.encode(writer)?; + + encode_null_terminated(writer, &self.message)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub enum Packet { + Read(Request), + Write(Request), + Data(Data), + Ack(Ack), + Error(ErrorMessage), +} + +impl Packet { + pub fn data(block: u16, data: &[u8]) -> Self { + Self::Data(Data { + block, + data: data.into(), + }) + } + + pub fn ack(block: u16) -> Self { + Self::Ack(Ack(block)) + } + + pub fn error(code: ErrorCode, message: impl Into) -> Self { + Self::Error(ErrorMessage { + code, + message: message.into(), + }) + } + + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let op_code = reader.read_u16::()?; + + Ok(match op_code { + 1 => Self::Read(Request::decode(reader)?), + 2 => Self::Write(Request::decode(reader)?), + 3 => Self::Data(Data::decode(reader)?), + 4 => Self::Ack(Ack::decode(reader)?), + 5 => Self::Error(ErrorMessage::decode(reader)?), + _ => return Err(Error::InvalidOpCode(op_code)), + }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + match &self { + Self::Read(inner) => { + writer.write_u16::(1)?; + inner.encode(writer)?; + } + Self::Write(inner) => { + writer.write_u16::(2)?; + inner.encode(writer)?; + } + Self::Data(inner) => { + writer.write_u16::(3)?; + inner.encode(writer)?; + } + Self::Ack(inner) => { + writer.write_u16::(4)?; + inner.encode(writer)?; + } + Self::Error(inner) => { + writer.write_u16::(5)?; + inner.encode(writer)?; + } + }; + + Ok(()) + } + + pub async fn recv_from(socket: &UdpSocket) -> Result<(Self, SocketAddr), Error> { + let mut buf = Vec::with_capacity(PACKET_SIZE); + let (_, src) = socket.recv_buf_from(&mut buf).await?; + + let packet = buf.try_into()?; + + Ok((packet, src)) + } + + pub async fn send_to(&self, socket: &UdpSocket, src: &SocketAddr) -> Result<(), Error> { + let buf: Vec<_> = self.try_into()?; + socket.send_to(&buf, src).await?; + + Ok(()) + } +} + +impl TryFrom<&Packet> for Vec { + type Error = Error; + + fn try_from(packet: &Packet) -> Result { + let mut buf = Vec::with_capacity(PACKET_SIZE); + let mut writer = BufWriter::new(Cursor::new(&mut buf)); + packet.encode(&mut writer)?; + + // Normally the writer would get dropped after we return, but since it has a mutable + // borrow in buf we have to drop it first. + drop(writer); + + Ok(buf) + } +} + +impl TryFrom> for Packet { + type Error = Error; + + fn try_from(buf: Vec) -> Result>>::Error> { + let mut reader = BufReader::new(Cursor::new(buf)); + + Packet::decode(&mut reader) + } +} diff --git a/src/tftp/server.rs b/src/tftp/server.rs new file mode 100644 index 0000000..e3af45d --- /dev/null +++ b/src/tftp/server.rs @@ -0,0 +1,162 @@ +use std::io; +use std::net::SocketAddr; +use std::time::Duration; + +use thiserror::Error; +use tokio::net::UdpSocket; + +use crate::tftp::packet::{ErrorCode, Mode}; +use crate::tftp::{self, Packet}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("{0}")] + Tftp(#[from] tftp::Error), + #[error("Io error: {0}")] + Io(#[from] io::Error), + #[error("Unsupported operation: {0:#?}")] + UnsupportedOperation(Packet), + #[error("File not found: {0}")] + FileNotFound(String), + #[error("Unsupported mode: {0:?}")] + UnsupportedMode(Mode), + #[error("Source changed: {0}")] + SourceChanged(SocketAddr), + #[error("UnexpectedPacket: {0:#?}")] + UnexpectedPacket(Packet), + #[error("Timeout")] + Timeout, +} + +pub async fn handle_connection( + src: SocketAddr, + packet: Packet, + callback: impl (Fn(&str) -> Option>) + Send + Clone + 'static, +) -> Result<(), Error> { + let socket = UdpSocket::bind("127.0.0.1:0").await?; + println!( + "{src}: Connecting from {}", + socket.local_addr().expect("Socket is bound") + ); + + let Packet::Read(request) = packet else { + Packet::error(ErrorCode::NotDefined, "Operation not supported") + .send_to(&socket, &src) + .await?; + + return Err(Error::UnsupportedOperation(packet)); + }; + + let filename = request.filename(); + println!("{src}: {filename} [{:?}]", request.mode()); + + let Some(data) = callback(filename) else { + Packet::error(ErrorCode::FileNotFound, "Unknown file") + .send_to(&socket, &src) + .await?; + + return Err(Error::FileNotFound(filename.into())); + }; + + let mode = request.mode(); + let data = match mode { + // In case of netascii mode we need to re-encode to escape certain sequences + Mode::NetAscii => netascii::Netascii::from_bytes(data).collect(), + Mode::Octet => data, + _ => { + Packet::error( + ErrorCode::NotDefined, + format!("Mode {mode:?} is not supported"), + ) + .send_to(&socket, &src) + .await?; + + return Err(Error::UnsupportedMode(mode)); + } + }; + + let mut chunks = data.chunks(512).enumerate(); + let mut chunk = chunks.next(); + + let mut timeout = 0; + + loop { + // Send the first chunk + if let Some((index, chunk)) = chunk { + Packet::data((index + 1) as u16, chunk) + .send_to(&socket, &src) + .await?; + } + + tokio::select! { + recv = Packet::recv_from(&socket) => { + timeout = 0; + let (packet, new_src) = recv?; + + // I think this is might technically be possible and fine, but I don't + // really know how to test it properly so we don't support it. + if new_src != src { + Packet::error(ErrorCode::Illegal, "Source changed") + .send_to(&socket, &src) + .await?; + + return Err(Error::SourceChanged(new_src)); + } + + match packet { + Packet::Ack(ack) => { + // If we receive an ack for the current chunk go to the next one + if let Some((index, _)) = chunk + && (index + 1) as u16 == ack.block() + { + chunk = chunks.next(); + } + + if chunk.is_none() { + break; + } + } + _ => { + Packet::error(ErrorCode::Illegal, "Unexpected packet") + .send_to(&socket, &src) + .await?; + + + return Err(Error::UnexpectedPacket(packet)); + } + } + }, + _ = tokio::time::sleep(Duration::from_secs(1)) => { + timeout += 1; + + if timeout == 5 { + return Err(Error::Timeout); + } + } + } + } + + println!("{src}: Transmission complete"); + + Ok(()) +} + +pub async fn serve( + callback: impl (Fn(&str) -> Option>) + Send + Clone + 'static, +) -> Result { + let socket = UdpSocket::bind("127.0.0.1:69").await?; + + println!("Server started"); + loop { + let (packet, src) = Packet::recv_from(&socket).await?; + + let callback = callback.clone(); + tokio::spawn({ + async move { + if let Err(err) = handle_connection(src, packet, callback).await { + println!("{src}: {err}") + } + } + }); + } +} -- 2.54.0 From 992615c116ac64cbd32cd60f28b403ce873023ca Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 25 Sep 2026 05:39:55 +0200 Subject: [PATCH 02/20] feat: Make application async --- Cargo.lock | 189 +++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 5 +- src/cluster.rs | 47 ++++++----- src/environment.rs | 43 +++++------ src/lib.rs | 2 +- src/main.rs | 46 ++++++----- src/node.rs | 12 +-- src/patch.rs | 49 +++++++----- src/schematic.rs | 30 ++++--- 9 files changed, 290 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c550337..b1305fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,57 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-walkdir" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed40ca66d45a6775511920324e4ae05c81673f9133c187c4970a3089c3ae27c" +dependencies = [ + "async-fs", + "futures-lite", + "thiserror", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -82,6 +133,19 @@ version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bstr" version = "1.13.1" @@ -184,6 +248,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -204,9 +277,11 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" name = "crete" version = "0.1.3" dependencies = [ + "async-walkdir", "byteorder", "clap", "clap_complete", + "futures", "gix-discover", "minijinja", "netascii", @@ -220,9 +295,14 @@ dependencies = [ "serde_yaml", "thiserror", "tokio", - "walkdir", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + [[package]] name = "defmt" version = "0.3.100" @@ -302,6 +382,26 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "faster-hex" version = "0.10.1" @@ -350,6 +450,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.34" @@ -366,12 +481,36 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.34" @@ -401,6 +540,7 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1191,6 +1331,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1226,6 +1372,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.34" @@ -1370,9 +1527,7 @@ checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ "base64", "bytes", - "futures-channel", "futures-core", - "futures-util", "http", "http-body", "http-body-util", @@ -1388,14 +1543,12 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", ] @@ -1768,19 +1921,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -2013,19 +2153,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.106" diff --git a/Cargo.toml b/Cargo.toml index cbee3bc..d6e5bd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,17 +14,17 @@ schemars = { version = "1.2.2", features = ["semver1"] } serde_json = "1.0.151" [dependencies] +async-walkdir = "2.2.0" byteorder = "1.5.0" clap = { version = "4.6.7", features = ["derive"] } clap_complete = "4.6.11" +futures = "0.3.34" gix-discover = { version = "0.56.0", features = ["sha1"] } minijinja = { version = "2.24.0", features = ["json", "loader"] } netascii = "0.1.0" num_enum = "0.7.6" optional_struct = "0.5.2" reqwest = { version = "0.13.5", default-features = false, features = [ - "blocking", - "stream", "native-tls", ] } schemars = { workspace = true } @@ -34,7 +34,6 @@ serde_json = { workspace = true } serde_yaml = "0.9.34" thiserror = "2.0.21" tokio = { version = "1.52.1", features = ["full"] } -walkdir = "2.5.0" [lib] test = false diff --git a/src/cluster.rs b/src/cluster.rs index bce3f70..697f4be 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -1,13 +1,15 @@ use std::net::Ipv4Addr; use std::path::PathBuf; -use std::process::Command; use std::str::FromStr; +use async_walkdir::WalkDir; +use futures::StreamExt; +use futures::stream::FuturesUnordered; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use walkdir::WalkDir; +use tokio::process::Command; use crate::environment::PathEnvironment; use crate::node::{Node, OptionalNodeDeserialize}; @@ -68,7 +70,7 @@ struct ClusterDeserialize { base: Base, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Cluster { pub(crate) name: String, @@ -80,11 +82,11 @@ pub struct Cluster { } impl Cluster { - pub(crate) fn get(cluster_name: &str, env: &Environment) -> Self { + pub(crate) async fn get(cluster_name: &str, env: &Environment<'static>) -> Self { let path = get_talos_path().join("clusters").join("default.yaml"); let default: OptionalClusterDeserialize = if path.exists() { - let content = std::fs::read_to_string(path).unwrap(); + let content = tokio::fs::read_to_string(path).await.unwrap(); serde_yaml::from_str(&content).unwrap() } else { Default::default() @@ -92,7 +94,7 @@ impl Cluster { let mut path = get_talos_path().join("clusters").join(cluster_name); path.add_extension("yaml"); - let content = std::fs::read_to_string(path).unwrap(); + let content = tokio::fs::read_to_string(path).await.unwrap(); let mut cluster: OptionalClusterDeserialize = serde_yaml::from_str(&content).unwrap(); @@ -122,32 +124,39 @@ impl Cluster { let nodes = nodes .iter() - .map(|name| Node::get(name, env, &cluster, &default, &base)) - .collect(); + .map(|name| async { Node::get(name, env, &cluster, &default, &base).await }); + + let nodes = FuturesUnordered::from_iter(nodes).collect().await; Self { nodes, ..cluster } } - pub fn get_all() -> Vec { + pub async fn get_all() -> Vec { let env = PathEnvironment::new_patches(); WalkDir::new(get_talos_path().join("clusters")) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.metadata().unwrap().is_file()) + .filter_map(|e| async { e.ok() }) + .filter(|e| { + let e = e.clone(); + async move { e.metadata().await.unwrap().is_file() } + }) .filter_map(|e| { - let mut path = PathBuf::from_str(&e.file_name().to_string_lossy()).unwrap(); - path.set_extension(""); + let env = env.clone(); + async move { + let mut path = PathBuf::from_str(&e.file_name().to_string_lossy()).unwrap(); + path.set_extension(""); - let name = path.to_string_lossy().to_string(); + let name = path.to_string_lossy().to_string(); - if name == "default" { - return None; + if name == "default" { + return None; + } + + Some(Cluster::get(&name, &env).await) } - - Some(Cluster::get(&name, &env)) }) .collect() + .await } pub fn nodes(&self) -> &[Node] { diff --git a/src/environment.rs b/src/environment.rs index 904e36e..615a2ec 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -2,15 +2,17 @@ use std::net::Ipv4Addr; use std::ops::Deref; use std::path::{Path, PathBuf}; +use async_walkdir::WalkDir; +use futures::{Stream, StreamExt}; use minijinja::value::ViaDeserialize; use minijinja::{AutoEscape, Environment, path_loader}; -use walkdir::WalkDir; use crate::{get_configs_path, get_talos_path}; /// Transparent wrapper around minijinja::Environment that loads templates from a path and /// configures better defaults. It also implements IntoIter, making it possible to iterate over all /// the templates. +#[derive(Clone)] pub struct PathEnvironment<'a> { env: Environment<'a>, path: PathBuf, @@ -76,6 +78,23 @@ impl<'a> PathEnvironment<'a> { pub(crate) fn new_patches() -> Self { Self::new(&get_talos_path().join("patches")) } + + pub fn files(&self) -> impl Stream { + // Find all templates in path + WalkDir::new(&self.path) + .filter_map(|e| async { e.ok() }) + .filter(|e| { + let e = e.clone(); + async move { e.metadata().await.unwrap().is_file() } + }) + .map(move |e| { + e.path() + .strip_prefix(&self.path) + .expect("All paths should have prefix") + .to_string_lossy() + .to_string() + }) + } } // Make PathEnvironment act like a normal Environment transparently @@ -86,25 +105,3 @@ impl<'a> Deref for PathEnvironment<'a> { &self.env } } - -// Iterate over all the files in the path -impl<'a, 'b> IntoIterator for &'b PathEnvironment<'a> { - type Item = String; - - type IntoIter = impl Iterator; - - fn into_iter(self) -> Self::IntoIter { - // Find all templates in path - WalkDir::new(&self.path) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.metadata().unwrap().is_file()) - .map(|e| { - e.path() - .strip_prefix(&self.path) - .expect("All paths should have prefix") - .to_string_lossy() - .to_string() - }) - } -} diff --git a/src/lib.rs b/src/lib.rs index 7685d78..549d4b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(impl_trait_in_assoc_type, path_absolute_method)] +#![feature(path_absolute_method)] use std::path::{Path, PathBuf}; use std::sync::OnceLock; diff --git a/src/main.rs b/src/main.rs index b69af7f..08d4c51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,16 @@ mod cli; -use std::process::Command; +use std::pin::pin; use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; use crete::{get_configs_path, get_repo_path, set_repo_path}; +use futures::StreamExt; use minijinja::context; use thiserror::Error; +use tokio::process::Command; use crate::cli::{Cli, Commands, GlobalOpts}; @@ -18,8 +20,8 @@ enum Error { NoClustersFound, } -fn run_command(mut command: Command) { - let output = command.output().unwrap(); +async fn run_command(mut command: Command) { + let output = command.output().await.unwrap(); if !output.stdout.is_empty() { println!("{}", String::from_utf8_lossy(&output.stdout).trim_end()); } @@ -28,24 +30,25 @@ fn run_command(mut command: Command) { } } -fn generate(opts: &GlobalOpts) -> Result<(), Error> { +async fn generate(opts: &GlobalOpts) -> Result<(), Error> { set_repo_path(&opts.repo); - let clusters = Cluster::get_all(); + let clusters = Cluster::get_all().await; if clusters.is_empty() { return Err(Error::NoClustersFound); } let path = opts.repo.join("rendered"); - if path.exists() { - std::fs::remove_dir_all(&path).unwrap(); - } - std::fs::create_dir(&path).unwrap(); + // We don't really care if this fails, as that likely mean that the directory did not exist + tokio::fs::remove_dir_all(&path).await.ok(); + tokio::fs::create_dir_all(&path).await.unwrap(); // Render templates let template_env = PathEnvironment::new(&get_repo_path().join("templates")); - for template_name in &template_env { + + let mut stream = pin!(template_env.files()); + while let Some(template_name) = stream.next().await { let template = template_env.get_template(&template_name).unwrap(); let content = template @@ -55,35 +58,36 @@ fn generate(opts: &GlobalOpts) -> Result<(), Error> { }) .unwrap(); - std::fs::write(path.join(template_name), content).unwrap(); + tokio::fs::write(path.join(template_name), content) + .await + .unwrap(); } // Remove existing config files and create output directory let path = get_configs_path(); - if path.exists() { - std::fs::remove_dir_all(&path).unwrap(); - } - std::fs::create_dir(&path).unwrap(); + tokio::fs::remove_dir_all(&path).await.ok(); + tokio::fs::create_dir_all(&path).await.unwrap(); // Generate config files for cluster in clusters { for node in cluster.nodes() { - run_command(node.talosctl_gen_config_command(&cluster)); + run_command(node.talosctl_gen_config_command(&cluster)).await; } - run_command(cluster.talosctl_gen_config_command()); - run_command(cluster.talosctl_add_endpoint_command()); - run_command(cluster.talosctl_merge_command()); + run_command(cluster.talosctl_gen_config_command()).await; + run_command(cluster.talosctl_add_endpoint_command()).await; + run_command(cluster.talosctl_merge_command()).await; } Ok(()) } -fn main() -> Result<(), Error> { +#[tokio::main] +async fn main() -> Result<(), Error> { let cli = Cli::parse(); match cli.command { - Commands::Generate => generate(&cli.global_opts)?, + Commands::Generate => generate(&cli.global_opts).await?, Commands::ShellCompletions => generate_complete( Shell::from_env().unwrap_or(Shell::Bash), &mut Cli::command(), diff --git a/src/node.rs b/src/node.rs index ab5c166..e4539da 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,10 +1,10 @@ use std::net::Ipv4Addr; -use std::process::Command; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use tokio::process::Command; use crate::cluster::{Base, Cluster}; use crate::patch::{OptionalPatches, OptionalPatchesString, Patches}; @@ -90,7 +90,7 @@ pub(crate) struct NodeDeserialize { sops: Secret, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Node { hostname: String, @@ -106,9 +106,9 @@ pub struct Node { } impl Node { - pub(crate) fn get( + pub(crate) async fn get( node_name: &str, - env: &Environment, + env: &Environment<'static>, cluster: &Cluster, default: &OptionalNodeDeserialize, base: &Base, @@ -121,7 +121,7 @@ impl Node { .to_string(); path.add_extension("yaml"); - let content = std::fs::read_to_string(path).unwrap(); + let content = tokio::fs::read_to_string(path).await.unwrap(); let node: OptionalNodeDeserialize = serde_yaml::from_str(&content).unwrap(); @@ -172,7 +172,7 @@ impl Node { }; // Render patches - let patches = patches.render(env, cluster, &node); + let patches = patches.render(env, cluster, &node).await; Node { patches, ..node } } diff --git a/src/patch.rs b/src/patch.rs index 4121bdb..a0b939f 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -16,27 +16,36 @@ pub(crate) struct Patches { pub(crate) type OptionalPatchesString = OptionalPatches; -fn render( +async fn render( patches: Vec, - env: &Environment, + env: &Environment<'static>, cluster: &Cluster, node: &Node, ) -> Vec { - patches - .into_iter() - .map(|name| { - let content = env - .get_template(&name) - .unwrap() - .render(context! { - node, - cluster - }) - .unwrap(); + let env = env.clone(); + let cluster = cluster.clone(); + let node = node.clone(); - serde_yaml::from_str(&content).unwrap() - }) - .collect() + // Minijinja will still have blocking I/O internally + tokio::task::spawn_blocking(move || { + patches + .into_iter() + .map(move |name| { + let content = env + .get_template(&name) + .unwrap() + .render(context! { + node, + cluster + }) + .unwrap(); + + serde_yaml::from_str(&content).unwrap() + }) + .collect() + }) + .await + .unwrap() } impl Patches { @@ -50,15 +59,15 @@ impl Patches { } } - pub(crate) fn render( + pub(crate) async fn render( self, - env: &Environment, + env: &Environment<'static>, cluster: &Cluster, node: &Node, ) -> Patches { Patches { - all: render(self.all.clone(), env, cluster, node), - control_plane: render(self.control_plane.clone(), env, cluster, node), + all: render(self.all.clone(), env, cluster, node).await, + control_plane: render(self.control_plane.clone(), env, cluster, node).await, } } } diff --git a/src/schematic.rs b/src/schematic.rs index e6f35d7..ace2f72 100644 --- a/src/schematic.rs +++ b/src/schematic.rs @@ -1,5 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize}; +use tokio::runtime::Handle; use crate::get_talos_path; @@ -11,7 +12,7 @@ where let path = get_talos_path().join("schematics").join(name); let content = std::fs::read_to_string(path).unwrap().trim().to_owned(); - let client = reqwest::blocking::ClientBuilder::new() + let client = reqwest::ClientBuilder::new() .user_agent(format!( "{}/{}", std::env!("CARGO_PKG_NAME"), @@ -20,19 +21,30 @@ where .build() .unwrap(); - let res = client - .post("https://factory.talos.dev/schematics") - .body(content) - .send() - .map_err(serde::de::Error::custom)?; - #[derive(Debug, Deserialize)] struct Response { id: String, } - let response: Response = serde_json::from_str(&res.text().map_err(serde::de::Error::custom)?) - .map_err(serde::de::Error::custom)?; + // We can't use async directly inside a custom deserializer, and using the blocking client also + // does not work as it will try to start tokio inside of tokio. + // So we have do this to work around it. + let response = tokio::task::block_in_place(|| { + Handle::current().block_on(async { + let res = client + .post("https://factory.talos.dev/schematics") + .body(content) + .send() + .await + .map_err(serde::de::Error::custom)?; + + let response: Response = + serde_json::from_str(&res.text().await.map_err(serde::de::Error::custom)?) + .map_err(serde::de::Error::custom)?; + + Ok(response) + }) + })?; Ok(response.id) } -- 2.54.0 From 1ad7a928ae4f136ee57ec04df321fee05dd5246e Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 25 Sep 2026 06:52:13 +0200 Subject: [PATCH 03/20] feat: Improve concurrency --- src/cluster.rs | 39 +++++++++++++++++++++++++-------------- src/main.rs | 21 +++++++++++++++++---- src/schematic.rs | 25 ++++++++++++------------- 3 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/cluster.rs b/src/cluster.rs index 697f4be..bd3c670 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use async_walkdir::WalkDir; use futures::StreamExt; +use futures::future::join_all; use futures::stream::FuturesUnordered; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; @@ -87,7 +88,11 @@ impl Cluster { let default: OptionalClusterDeserialize = if path.exists() { let content = tokio::fs::read_to_string(path).await.unwrap(); - serde_yaml::from_str(&content).unwrap() + + // The deserialization makes a blocking web request + tokio::task::spawn_blocking(move || serde_yaml::from_str(&content).unwrap()) + .await + .unwrap() } else { Default::default() }; @@ -134,29 +139,35 @@ impl Cluster { pub async fn get_all() -> Vec { let env = PathEnvironment::new_patches(); - WalkDir::new(get_talos_path().join("clusters")) + let names: Vec<_> = WalkDir::new(get_talos_path().join("clusters")) .filter_map(|e| async { e.ok() }) .filter(|e| { let e = e.clone(); async move { e.metadata().await.unwrap().is_file() } }) - .filter_map(|e| { - let env = env.clone(); - async move { - let mut path = PathBuf::from_str(&e.file_name().to_string_lossy()).unwrap(); - path.set_extension(""); + .filter_map(|e| async move { + let mut path = PathBuf::from_str(&e.file_name().to_string_lossy()).unwrap(); + path.set_extension(""); - let name = path.to_string_lossy().to_string(); + let name = path.to_string_lossy().to_string(); - if name == "default" { - return None; - } - - Some(Cluster::get(&name, &env).await) + if name == "default" { + return None; } + + Some(name) }) .collect() - .await + .await; + + // Get the clusters concurrently + join_all(names.iter().map(|name| { + let env = env.clone(); + let name = name.clone(); + + async move { Cluster::get(&name, &env).await } + })) + .await } pub fn nodes(&self) -> &[Node] { diff --git a/src/main.rs b/src/main.rs index 08d4c51..4cbb473 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use crete::cluster::Cluster; use crete::environment::PathEnvironment; use crete::{get_configs_path, get_repo_path, set_repo_path}; use futures::StreamExt; +use futures::future::join_all; use minijinja::context; use thiserror::Error; use tokio::process::Command; @@ -69,13 +70,25 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { tokio::fs::create_dir_all(&path).await.unwrap(); // Generate config files - for cluster in clusters { - for node in cluster.nodes() { - run_command(node.talosctl_gen_config_command(&cluster)).await; - } + join_all(clusters.iter().map(|cluster| async move { + join_all(cluster.nodes().iter().map(|node| { + let cluster = cluster.clone(); + async move { + run_command(node.talosctl_gen_config_command(&cluster)).await; + } + })) + .await; + // Run these commands in parallel with the the node commands + // NOTE: These do need to happen in the correct order run_command(cluster.talosctl_gen_config_command()).await; run_command(cluster.talosctl_add_endpoint_command()).await; + })) + .await; + + // Merge all the cluster talosconfig files into a single file + // Since this all modifies the same output file this has to happen sequentially + for cluster in clusters { run_command(cluster.talosctl_merge_command()).await; } diff --git a/src/schematic.rs b/src/schematic.rs index ace2f72..794f4b1 100644 --- a/src/schematic.rs +++ b/src/schematic.rs @@ -29,21 +29,20 @@ where // We can't use async directly inside a custom deserializer, and using the blocking client also // does not work as it will try to start tokio inside of tokio. // So we have do this to work around it. - let response = tokio::task::block_in_place(|| { - Handle::current().block_on(async { - let res = client - .post("https://factory.talos.dev/schematics") - .body(content) - .send() - .await + // NOTE: This requires that the deserialization happens inside spawn_blocking + let response = Handle::current().block_on(async { + let res = client + .post("https://factory.talos.dev/schematics") + .body(content) + .send() + .await + .map_err(serde::de::Error::custom)?; + + let response: Response = + serde_json::from_str(&res.text().await.map_err(serde::de::Error::custom)?) .map_err(serde::de::Error::custom)?; - let response: Response = - serde_json::from_str(&res.text().await.map_err(serde::de::Error::custom)?) - .map_err(serde::de::Error::custom)?; - - Ok(response) - }) + Ok(response) })?; Ok(response.id) -- 2.54.0 From 35aedf1d5b07a12ce675a2e18682ef73f6f19a63 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 1 Oct 2026 02:21:39 +0200 Subject: [PATCH 04/20] feat: Configs are captured in a string first --- src/cluster.rs | 31 +++++++++++++++++++++---------- src/lib.rs | 13 +++++++++++++ src/main.rs | 18 +++--------------- src/node.rs | 24 +++++++++++++++++------- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/cluster.rs b/src/cluster.rs index bd3c670..4b02681 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -15,7 +15,7 @@ use tokio::process::Command; use crate::environment::PathEnvironment; use crate::node::{Node, OptionalNodeDeserialize}; use crate::patch::Patches; -use crate::{get_configs_path, get_talos_path}; +use crate::{get_configs_path, get_talos_path, run_command}; #[optional_struct] #[derive(Debug, Deserialize, JsonSchema, Clone)] @@ -174,9 +174,7 @@ impl Cluster { &self.nodes } - pub fn talosctl_gen_config_command(&self) -> Command { - let path = get_configs_path().join(&self.name).join("talosconfig"); - + pub async fn talosconfig(&self) -> String { let mut command = Command::new("talosctl"); command.args([ "gen", @@ -188,13 +186,13 @@ impl Cluster { "--output-types", "talosconfig", "-o", - path.to_str().expect("Path should be valid utf-8"), + "-", ]); - command + run_command(command).await } - pub fn talosctl_add_endpoint_command(&self) -> Command { + async fn talosconfig_add_endpoint(&self) { let path = get_configs_path().join(&self.name).join("talosconfig"); let mut command = Command::new("talosctl"); @@ -206,10 +204,23 @@ impl Cluster { &self.control_plane_ip.to_string(), ]); - command + run_command(command).await; } - pub fn talosctl_merge_command(&self) -> Command { + pub async fn talosconfig_write(&self) { + let config = self.talosconfig().await; + + let path = get_configs_path().join(&self.name).join("talosconfig"); + tokio::fs::create_dir_all(path.parent().expect("talosconfig should have parent")) + .await + .unwrap(); + + tokio::fs::write(path, config).await.unwrap(); + + self.talosconfig_add_endpoint().await + } + + pub async fn talosconfig_merge(&self) { let cluster_path = get_configs_path().join(&self.name).join("talosconfig"); let path = get_configs_path().join("talosconfig"); @@ -222,7 +233,7 @@ impl Cluster { cluster_path.to_str().expect("Path should be valid utf-8"), ]); - command + run_command(command).await; } } diff --git a/src/lib.rs b/src/lib.rs index 549d4b1..ce4e59a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ use std::path::{Path, PathBuf}; use std::sync::OnceLock; +use tokio::process::Command; + pub mod cluster; pub mod environment; pub mod node; @@ -29,3 +31,14 @@ pub fn get_talos_path() -> PathBuf { pub fn get_configs_path() -> PathBuf { get_repo_path().join("configs") } + +pub async fn run_command(mut command: Command) -> String { + let output = command.output().await.unwrap(); + if !output.stderr.is_empty() { + println!("{}", String::from_utf8_lossy(&output.stderr).trim_end()); + } + + String::from_utf8_lossy(&output.stdout) + .trim_end() + .to_owned() +} diff --git a/src/main.rs b/src/main.rs index 4cbb473..6cd6231 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,6 @@ use futures::StreamExt; use futures::future::join_all; use minijinja::context; use thiserror::Error; -use tokio::process::Command; use crate::cli::{Cli, Commands, GlobalOpts}; @@ -21,16 +20,6 @@ enum Error { NoClustersFound, } -async fn run_command(mut command: Command) { - let output = command.output().await.unwrap(); - if !output.stdout.is_empty() { - println!("{}", String::from_utf8_lossy(&output.stdout).trim_end()); - } - if !output.stderr.is_empty() { - println!("{}", String::from_utf8_lossy(&output.stderr).trim_end()); - } -} - async fn generate(opts: &GlobalOpts) -> Result<(), Error> { set_repo_path(&opts.repo); @@ -74,22 +63,21 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { join_all(cluster.nodes().iter().map(|node| { let cluster = cluster.clone(); async move { - run_command(node.talosctl_gen_config_command(&cluster)).await; + node.config_write(&cluster).await; } })) .await; // Run these commands in parallel with the the node commands // NOTE: These do need to happen in the correct order - run_command(cluster.talosctl_gen_config_command()).await; - run_command(cluster.talosctl_add_endpoint_command()).await; + cluster.talosconfig_write().await; })) .await; // Merge all the cluster talosconfig files into a single file // Since this all modifies the same output file this has to happen sequentially for cluster in clusters { - run_command(cluster.talosctl_merge_command()).await; + cluster.talosconfig_merge().await; } Ok(()) diff --git a/src/node.rs b/src/node.rs index e4539da..015e0b8 100644 --- a/src/node.rs +++ b/src/node.rs @@ -10,7 +10,7 @@ use crate::cluster::{Base, Cluster}; use crate::patch::{OptionalPatches, OptionalPatchesString, Patches}; use crate::schematic::Schematic; use crate::secret::Secret; -use crate::{get_configs_path, get_talos_path}; +use crate::{get_configs_path, get_talos_path, run_command}; #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -177,10 +177,7 @@ impl Node { Node { patches, ..node } } - pub fn talosctl_gen_config_command(&self, cluster: &Cluster) -> Command { - let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); - path.add_extension("yaml"); - + pub async fn config(&self, cluster: &Cluster) -> String { let mut command = Command::new("talosctl"); command.args([ "gen", @@ -204,7 +201,7 @@ impl Node { "--with-docs=false", "--with-examples=false", "-o", - path.to_str().expect("Path should be valid utf-8"), + "-", ]); for patch in &self.patches.all { @@ -218,7 +215,20 @@ impl Node { ]); } - command + run_command(command).await + } + + pub async fn config_write(&self, cluster: &Cluster) { + let config = self.config(cluster).await; + + let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); + path.add_extension("yaml"); + + tokio::fs::create_dir_all(path.parent().expect("config should have parent")) + .await + .unwrap(); + + tokio::fs::write(path, config).await.unwrap(); } } -- 2.54.0 From 88405a1e636477e52c11e3813e7589d9116d37e0 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 1 Oct 2026 02:54:17 +0200 Subject: [PATCH 05/20] feat: Retain context --- src/lib.rs | 1 + src/main.rs | 7 +++++++ src/talosconfig.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/talosconfig.rs diff --git a/src/lib.rs b/src/lib.rs index ce4e59a..e8987b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod node; pub mod patch; pub mod schematic; pub mod secret; +pub mod talosconfig; pub mod tftp; pub(crate) static REPO_PATH: OnceLock = OnceLock::new(); diff --git a/src/main.rs b/src/main.rs index 6cd6231..5e07e00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; +use crete::talosconfig::{get_current_context, set_context}; use crete::{get_configs_path, get_repo_path, set_repo_path}; use futures::StreamExt; use futures::future::join_all; @@ -53,6 +54,8 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { .unwrap(); } + let context = get_current_context().await; + // Remove existing config files and create output directory let path = get_configs_path(); tokio::fs::remove_dir_all(&path).await.ok(); @@ -80,6 +83,10 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { cluster.talosconfig_merge().await; } + if let Some(context) = context { + set_context(context).await; + } + Ok(()) } diff --git a/src/talosconfig.rs b/src/talosconfig.rs new file mode 100644 index 0000000..60d630b --- /dev/null +++ b/src/talosconfig.rs @@ -0,0 +1,34 @@ +use serde::Deserialize; +use tokio::process::Command; + +use crate::{get_configs_path, run_command}; + +#[derive(Debug, Deserialize)] +struct TalosConfig { + pub context: String, +} + +pub async fn get_current_context() -> Option { + let path = get_configs_path().join("talosconfig"); + + tokio::fs::read(&path) + .await + .ok() + .and_then(|config| serde_yaml::from_slice(&config).ok()) + .map(|config: TalosConfig| config.context) +} + +pub async fn set_context(context: String) { + let path = get_configs_path().join("talosconfig"); + + let mut command = Command::new("talosctl"); + command.args([ + "config", + "--talosconfig", + path.to_str().expect("Path should be valid utf-8"), + "context", + &context, + ]); + + run_command(command).await; +} -- 2.54.0 From cae78a31a8394e5f77614ccd5e0455ce59ed2ef2 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 1 Oct 2026 03:03:27 +0200 Subject: [PATCH 06/20] refactor: Deduplicate paths --- src/cluster.rs | 24 +++++++++++++++--------- src/node.rs | 12 +++++++++--- src/talosconfig.rs | 22 ++++++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/cluster.rs b/src/cluster.rs index 4b02681..2acbd21 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -15,6 +15,7 @@ use tokio::process::Command; use crate::environment::PathEnvironment; use crate::node::{Node, OptionalNodeDeserialize}; use crate::patch::Patches; +use crate::talosconfig::TalosConfig; use crate::{get_configs_path, get_talos_path, run_command}; #[optional_struct] @@ -192,14 +193,18 @@ impl Cluster { run_command(command).await } - async fn talosconfig_add_endpoint(&self) { - let path = get_configs_path().join(&self.name).join("talosconfig"); + pub fn talosconfig_path(&self) -> PathBuf { + get_configs_path().join(&self.name).join("talosconfig") + } + async fn talosconfig_add_endpoint(&self) { let mut command = Command::new("talosctl"); command.args([ "config", "--talosconfig", - path.to_str().expect("Path should be valid utf-8"), + self.talosconfig_path() + .to_str() + .expect("Path should be valid utf-8"), "endpoint", &self.control_plane_ip.to_string(), ]); @@ -210,7 +215,7 @@ impl Cluster { pub async fn talosconfig_write(&self) { let config = self.talosconfig().await; - let path = get_configs_path().join(&self.name).join("talosconfig"); + let path = self.talosconfig_path(); tokio::fs::create_dir_all(path.parent().expect("talosconfig should have parent")) .await .unwrap(); @@ -221,16 +226,17 @@ impl Cluster { } pub async fn talosconfig_merge(&self) { - let cluster_path = get_configs_path().join(&self.name).join("talosconfig"); - let path = get_configs_path().join("talosconfig"); - let mut command = Command::new("talosctl"); command.args([ "config", "--talosconfig", - path.to_str().expect("Path should be valid utf-8"), + TalosConfig::merged_path() + .to_str() + .expect("Path should be valid utf-8"), "merge", - cluster_path.to_str().expect("Path should be valid utf-8"), + self.talosconfig_path() + .to_str() + .expect("Path should be valid utf-8"), ]); run_command(command).await; diff --git a/src/node.rs b/src/node.rs index 015e0b8..590f742 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,4 +1,5 @@ use std::net::Ipv4Addr; +use std::path::PathBuf; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; @@ -218,12 +219,17 @@ impl Node { run_command(command).await } - pub async fn config_write(&self, cluster: &Cluster) { - let config = self.config(cluster).await; - + pub fn config_path(&self, cluster: &Cluster) -> PathBuf { let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); path.add_extension("yaml"); + path + } + + pub async fn config_write(&self, cluster: &Cluster) { + let config = self.config(cluster).await; + + let path = self.config_path(cluster); tokio::fs::create_dir_all(path.parent().expect("config should have parent")) .await .unwrap(); diff --git a/src/talosconfig.rs b/src/talosconfig.rs index 60d630b..d59c20f 100644 --- a/src/talosconfig.rs +++ b/src/talosconfig.rs @@ -1,17 +1,23 @@ +use std::path::PathBuf; + use serde::Deserialize; use tokio::process::Command; use crate::{get_configs_path, run_command}; #[derive(Debug, Deserialize)] -struct TalosConfig { - pub context: String, +pub struct TalosConfig { + context: String, +} + +impl TalosConfig { + pub fn merged_path() -> PathBuf { + get_configs_path().join("talosconfig") + } } pub async fn get_current_context() -> Option { - let path = get_configs_path().join("talosconfig"); - - tokio::fs::read(&path) + tokio::fs::read(&TalosConfig::merged_path()) .await .ok() .and_then(|config| serde_yaml::from_slice(&config).ok()) @@ -19,13 +25,13 @@ pub async fn get_current_context() -> Option { } pub async fn set_context(context: String) { - let path = get_configs_path().join("talosconfig"); - let mut command = Command::new("talosctl"); command.args([ "config", "--talosconfig", - path.to_str().expect("Path should be valid utf-8"), + TalosConfig::merged_path() + .to_str() + .expect("Path should be valid utf-8"), "context", &context, ]); -- 2.54.0 From 5a5428324840a3b5d8094e256aba53b6d9050010 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 1 Oct 2026 03:20:09 +0200 Subject: [PATCH 07/20] feat: Make tftp serve callback async --- src/tftp/server.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/tftp/server.rs b/src/tftp/server.rs index e3af45d..2cf1f24 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -28,11 +28,15 @@ pub enum Error { Timeout, } -pub async fn handle_connection( +pub async fn handle_connection( src: SocketAddr, packet: Packet, - callback: impl (Fn(&str) -> Option>) + Send + Clone + 'static, -) -> Result<(), Error> { + callback: F, +) -> Result<(), Error> +where + F: Fn(&str) -> Fut, + Fut: Future>>, +{ let socket = UdpSocket::bind("127.0.0.1:0").await?; println!( "{src}: Connecting from {}", @@ -50,7 +54,7 @@ pub async fn handle_connection( let filename = request.filename(); println!("{src}: {filename} [{:?}]", request.mode()); - let Some(data) = callback(filename) else { + let Some(data) = callback(filename).await else { Packet::error(ErrorCode::FileNotFound, "Unknown file") .send_to(&socket, &src) .await?; @@ -141,9 +145,11 @@ pub async fn handle_connection( Ok(()) } -pub async fn serve( - callback: impl (Fn(&str) -> Option>) + Send + Clone + 'static, -) -> Result { +pub async fn serve(callback: F) -> Result +where + F: Fn(&str) -> Fut + Clone + Send + 'static, + Fut: Future>> + Send + 'static, +{ let socket = UdpSocket::bind("127.0.0.1:69").await?; println!("Server started"); -- 2.54.0 From b728cd7d221a2c2f76b7f9e602873fd6ed51d25b Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 1 Oct 2026 03:36:41 +0200 Subject: [PATCH 08/20] feat!: Added command to serve config files over tftp This is a breaking change as the return type of cluster.nodes has changed, resulting in old templates potentially no longer working. --- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/cli.rs | 2 ++ src/cluster.rs | 32 +++++++++++++++++++++----------- src/main.rs | 46 +++++++++++++++++++++++++++++++++++++++------- src/node.rs | 4 ++++ 6 files changed, 100 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1305fa..1329704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "1.0.0" @@ -287,6 +296,7 @@ dependencies = [ "netascii", "num_enum", "optional_struct", + "regex", "reqwest", "schemars", "semver", @@ -1484,11 +1494,34 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "repo_path" diff --git a/Cargo.toml b/Cargo.toml index d6e5bd7..ab8d057 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ minijinja = { version = "2.24.0", features = ["json", "loader"] } netascii = "0.1.0" num_enum = "0.7.6" optional_struct = "0.5.2" +regex = "1.13.1" reqwest = { version = "0.13.5", default-features = false, features = [ "native-tls", ] } diff --git a/src/cli.rs b/src/cli.rs index 4a8d76d..6490d79 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -49,4 +49,6 @@ pub enum Commands { Generate, /// Generate completions for your current shell ShellCompletions, + /// Serve config files over tftp + Serve, } diff --git a/src/cluster.rs b/src/cluster.rs index 2acbd21..7deeae8 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -1,10 +1,10 @@ +use std::collections::HashMap; use std::net::Ipv4Addr; use std::path::PathBuf; use std::str::FromStr; use async_walkdir::WalkDir; use futures::StreamExt; -use futures::future::join_all; use futures::stream::FuturesUnordered; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; @@ -77,7 +77,7 @@ struct ClusterDeserialize { pub struct Cluster { pub(crate) name: String, pub(crate) version: Version, - nodes: Vec, + nodes: HashMap, cluster_env: ClusterEnv, pub(crate) control_plane_ip: Ipv4Addr, pub(crate) secrets_file: PathBuf, @@ -122,22 +122,23 @@ impl Cluster { let cluster = Self { name: cluster_name.into(), version: cluster.version, - nodes: vec![], + nodes: Default::default(), cluster_env: cluster.cluster_env, control_plane_ip: cluster.control_plane_ip, secrets_file, }; - let nodes = nodes - .iter() - .map(|name| async { Node::get(name, env, &cluster, &default, &base).await }); + let nodes = nodes.iter().map(|name| async { + let node = Node::get(name, env, &cluster, &default, &base).await; + (node.hostname().to_owned(), node) + }); let nodes = FuturesUnordered::from_iter(nodes).collect().await; Self { nodes, ..cluster } } - pub async fn get_all() -> Vec { + pub async fn get_all() -> HashMap { let env = PathEnvironment::new_patches(); let names: Vec<_> = WalkDir::new(get_talos_path().join("clusters")) @@ -162,17 +163,26 @@ impl Cluster { .await; // Get the clusters concurrently - join_all(names.iter().map(|name| { + FuturesUnordered::from_iter(names.iter().map(|name| { let env = env.clone(); let name = name.clone(); - async move { Cluster::get(&name, &env).await } + async move { + let cluster = Cluster::get(&name, &env).await; + + (name, cluster) + } })) + .collect() .await } - pub fn nodes(&self) -> &[Node] { - &self.nodes + pub fn nodes(&self) -> impl Iterator { + self.nodes.values() + } + + pub fn node(&self, node: &str) -> Option<&Node> { + self.nodes.get(node) } pub async fn talosconfig(&self) -> String { diff --git a/src/main.rs b/src/main.rs index 5e07e00..ce27253 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,12 @@ use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; use crete::talosconfig::{get_current_context, set_context}; -use crete::{get_configs_path, get_repo_path, set_repo_path}; +use crete::tftp::ServerError; +use crete::{get_configs_path, get_repo_path, set_repo_path, tftp}; use futures::StreamExt; use futures::future::join_all; use minijinja::context; +use regex::Regex; use thiserror::Error; use crate::cli::{Cli, Commands, GlobalOpts}; @@ -19,11 +21,11 @@ use crate::cli::{Cli, Commands, GlobalOpts}; enum Error { #[error("No clusters where found")] NoClustersFound, + #[error("Server error: {0}")] + ServerError(#[from] ServerError), } async fn generate(opts: &GlobalOpts) -> Result<(), Error> { - set_repo_path(&opts.repo); - let clusters = Cluster::get_all().await; if clusters.is_empty() { @@ -44,7 +46,7 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { let content = template .render(context! { - clusters, + clusters => clusters.values().collect::>(), root => opts.repo }) .unwrap(); @@ -62,8 +64,8 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { tokio::fs::create_dir_all(&path).await.unwrap(); // Generate config files - join_all(clusters.iter().map(|cluster| async move { - join_all(cluster.nodes().iter().map(|node| { + join_all(clusters.values().map(|cluster| async move { + join_all(cluster.nodes().map(|node| { let cluster = cluster.clone(); async move { node.config_write(&cluster).await; @@ -79,7 +81,7 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { // Merge all the cluster talosconfig files into a single file // Since this all modifies the same output file this has to happen sequentially - for cluster in clusters { + for (_, cluster) in clusters { cluster.talosconfig_merge().await; } @@ -94,6 +96,8 @@ async fn generate(opts: &GlobalOpts) -> Result<(), Error> { async fn main() -> Result<(), Error> { let cli = Cli::parse(); + set_repo_path(&cli.global_opts.repo); + match cli.command { Commands::Generate => generate(&cli.global_opts).await?, Commands::ShellCompletions => generate_complete( @@ -102,6 +106,34 @@ async fn main() -> Result<(), Error> { "crete", &mut std::io::stdout(), ), + Commands::Serve => { + let re = Regex::new(r"talos/(?.+)/(?.+)\.ya?ml") + .expect("regex should be valid"); + + tftp::serve(move |filename| { + let filename = filename.to_owned(); + let re = re.clone(); + + async move { + if let Some(caps) = re.captures(&filename) { + let cluster = &caps["cluster"]; + let node = &caps["node"]; + + let clusters = Cluster::get_all().await; + let cluster = clusters.get(cluster)?; + + let node = cluster.node(node)?; + + let config = node.config(cluster).await; + + Some(config.into()) + } else { + None + } + } + }) + .await? + } }; Ok(()) diff --git a/src/node.rs b/src/node.rs index 590f742..14ea4fe 100644 --- a/src/node.rs +++ b/src/node.rs @@ -178,6 +178,10 @@ impl Node { Node { patches, ..node } } + pub fn hostname(&self) -> &str { + &self.hostname + } + pub async fn config(&self, cluster: &Cluster) -> String { let mut command = Command::new("talosctl"); command.args([ -- 2.54.0 From 0a72e2e8d92b9e3abe651977b8891b3481e0b83f Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Sun, 4 Oct 2026 02:59:08 +0200 Subject: [PATCH 09/20] feat: Added wrapper crate for including ipxe binaries --- .gitmodules | 3 +++ Cargo.lock | 34 +++++++++++++++++++++++++++ Cargo.toml | 3 ++- ipxe/Cargo.toml | 10 ++++++++ ipxe/build.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++ ipxe/ipxe | 1 + ipxe/src/embed.ipxe | 5 ++++ ipxe/src/lib.rs | 3 +++ src/main.rs | 6 ++++- 9 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 100644 ipxe/Cargo.toml create mode 100644 ipxe/build.rs create mode 160000 ipxe/ipxe create mode 100644 ipxe/src/embed.ipxe create mode 100644 ipxe/src/lib.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..58b8f04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ipxe/ipxe"] + path = ipxe/ipxe + url = git@github.com:rkubes/ipxe diff --git a/Cargo.lock b/Cargo.lock index 1329704..94224f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "build-print" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e6738dfb11354886f890621b4a34c0b177f75538023f7100b608ab9adbd66b" + [[package]] name = "bumpalo" version = "3.20.3" @@ -292,6 +298,7 @@ dependencies = [ "clap_complete", "futures", "gix-discover", + "ipxe", "minijinja", "netascii", "num_enum", @@ -1017,6 +1024,25 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "2.14.2" @@ -1033,6 +1059,14 @@ version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +[[package]] +name = "ipxe" +version = "0.1.0" +dependencies = [ + "build-print", + "include_dir", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" diff --git a/Cargo.toml b/Cargo.toml index ab8d057..6e7eb7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ publish = ["infra"] license = "GPL-3.0-or-later" [workspace] -members = ["xtask"] +members = ["ipxe", "xtask"] [workspace.dependencies] schemars = { version = "1.2.2", features = ["semver1"] } @@ -35,6 +35,7 @@ serde_json = { workspace = true } serde_yaml = "0.9.34" thiserror = "2.0.21" tokio = { version = "1.52.1", features = ["full"] } +ipxe = { path = "./ipxe" } [lib] test = false diff --git a/ipxe/Cargo.toml b/ipxe/Cargo.toml new file mode 100644 index 0000000..2220d92 --- /dev/null +++ b/ipxe/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ipxe" +version = "0.1.0" +edition = "2024" + +[build-dependencies] +build-print = "1.0.1" + +[dependencies] +include_dir = "0.7.4" diff --git a/ipxe/build.rs b/ipxe/build.rs new file mode 100644 index 0000000..720fbe3 --- /dev/null +++ b/ipxe/build.rs @@ -0,0 +1,57 @@ +use std::env; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use build_print::error; + +fn main() { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .canonicalize() + .unwrap(); + let src_dir = manifest_dir.join("ipxe").join("src"); + + let mut command = Command::new("make"); + command + .stdout(Stdio::piped()) + .current_dir(&src_dir) + .env_remove("DEBUG") + .env_remove("PROFILE") + .args([ + "-j", + &std::thread::available_parallelism() + .unwrap() + .get() + .to_string(), + ]) + .arg(format!( + "EMBED={}", + manifest_dir + .join("src") + .join("embed.ipxe") + .to_string_lossy(), + )); + + let files = ["bin/ipxe.pxe", "bin-x86_64-efi/ipxe.efi"].map(PathBuf::from); + for file in &files { + command.arg(file); + } + + let output = command.output().unwrap(); + if !output.status.success() { + error!("{}", String::from_utf8_lossy(&output.stderr)); + } + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()) + .canonicalize() + .unwrap() + .join("ipxe"); + + std::fs::remove_dir_all(&out_dir).ok(); + for file in &files { + let input = src_dir.join(file); + let output = out_dir.join(file.file_name().unwrap()); + + std::fs::create_dir_all(output.parent().unwrap()).unwrap(); + std::fs::copy(input, output).unwrap(); + } +} diff --git a/ipxe/ipxe b/ipxe/ipxe new file mode 160000 index 0000000..8892af6 --- /dev/null +++ b/ipxe/ipxe @@ -0,0 +1 @@ +Subproject commit 8892af68a254d36f778345d5324ec74fb8adbab8 diff --git a/ipxe/src/embed.ipxe b/ipxe/src/embed.ipxe new file mode 100644 index 0000000..f24cea6 --- /dev/null +++ b/ipxe/src/embed.ipxe @@ -0,0 +1,5 @@ +#!ipxe + +dhcp + +echo "Hello, World!" diff --git a/ipxe/src/lib.rs b/ipxe/src/lib.rs new file mode 100644 index 0000000..8b8b128 --- /dev/null +++ b/ipxe/src/lib.rs @@ -0,0 +1,3 @@ +use include_dir::{Dir, include_dir}; + +pub static IPXE_DIR: Dir<'_> = include_dir!("$OUT_DIR/ipxe"); diff --git a/src/main.rs b/src/main.rs index ce27253..09fccfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use crete::tftp::ServerError; use crete::{get_configs_path, get_repo_path, set_repo_path, tftp}; use futures::StreamExt; use futures::future::join_all; +use ipxe::IPXE_DIR; use minijinja::context; use regex::Regex; use thiserror::Error; @@ -115,7 +116,10 @@ async fn main() -> Result<(), Error> { let re = re.clone(); async move { - if let Some(caps) = re.captures(&filename) { + const IPXE_PATTERN: &str = "ipxe/"; + if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) { + Some(IPXE_DIR.get_file(filename)?.contents().to_owned()) + } else if let Some(caps) = re.captures(&filename) { let cluster = &caps["cluster"]; let node = &caps["node"]; -- 2.54.0 From f04e7d80e2f3552de5fa2e607cafc9f92e3a763b Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Sun, 4 Oct 2026 05:38:59 +0200 Subject: [PATCH 10/20] fix: Only connections from localhost are allowed --- src/tftp/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tftp/server.rs b/src/tftp/server.rs index 2cf1f24..372e18f 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -37,7 +37,7 @@ where F: Fn(&str) -> Fut, Fut: Future>>, { - let socket = UdpSocket::bind("127.0.0.1:0").await?; + let socket = UdpSocket::bind("0.0.0.0:0").await?; println!( "{src}: Connecting from {}", socket.local_addr().expect("Socket is bound") @@ -150,7 +150,7 @@ where F: Fn(&str) -> Fut + Clone + Send + 'static, Fut: Future>> + Send + 'static, { - let socket = UdpSocket::bind("127.0.0.1:69").await?; + let socket = UdpSocket::bind("0.0.0.0:69").await?; println!("Server started"); loop { -- 2.54.0 From cc84f13ed728d219d910d069bbfbda3600cdafe8 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 15 Oct 2026 03:31:14 +0200 Subject: [PATCH 11/20] feat: Use fixed size buffer for receiving packet --- src/tftp/packet.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tftp/packet.rs b/src/tftp/packet.rs index 28612a3..1461dc1 100644 --- a/src/tftp/packet.rs +++ b/src/tftp/packet.rs @@ -310,8 +310,8 @@ impl Packet { } pub async fn recv_from(socket: &UdpSocket) -> Result<(Self, SocketAddr), Error> { - let mut buf = Vec::with_capacity(PACKET_SIZE); - let (_, src) = socket.recv_buf_from(&mut buf).await?; + let mut buf = [0u8; PACKET_SIZE]; + let (_, src) = socket.recv_from(&mut buf).await?; let packet = buf.try_into()?; @@ -342,10 +342,12 @@ impl TryFrom<&Packet> for Vec { } } -impl TryFrom> for Packet { +impl TryFrom<[u8; PACKET_SIZE]> for Packet { type Error = Error; - fn try_from(buf: Vec) -> Result>>::Error> { + fn try_from( + buf: [u8; PACKET_SIZE], + ) -> Result>::Error> { let mut reader = BufReader::new(Cursor::new(buf)); Packet::decode(&mut reader) -- 2.54.0 From 5552013eeb283e1145673e1f7ad6eb6c3cb38566 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 15 Oct 2026 03:40:20 +0200 Subject: [PATCH 12/20] feat: Bind to address that was connected to --- Cargo.lock | 13 +++++++++++++ Cargo.toml | 2 ++ src/tftp/server.rs | 39 +++++++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94224f9..feb6127 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,8 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "socket-pktinfo", + "socket2", "thiserror", "tokio", ] @@ -1837,6 +1839,17 @@ version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +[[package]] +name = "socket-pktinfo" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612942246d0cc239cfd83af1dfd39be47f649208a3524e5e9da651910128e0ac" +dependencies = [ + "libc", + "socket2", + "windows-sys", +] + [[package]] name = "socket2" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index 6e7eb7c..0a39bd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ serde_yaml = "0.9.34" thiserror = "2.0.21" tokio = { version = "1.52.1", features = ["full"] } ipxe = { path = "./ipxe" } +socket2 = "0.6.5" +socket-pktinfo = "0.4.1" [lib] test = false diff --git a/src/tftp/server.rs b/src/tftp/server.rs index 372e18f..eb26943 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -1,11 +1,14 @@ use std::io; -use std::net::SocketAddr; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::time::Duration; +use socket_pktinfo::{PktInfo, PktInfoUdpSocket}; +use socket2::Domain; use thiserror::Error; +use tokio::io::unix::AsyncFd; use tokio::net::UdpSocket; -use crate::tftp::packet::{ErrorCode, Mode}; +use crate::tftp::packet::{ErrorCode, Mode, PACKET_SIZE}; use crate::tftp::{self, Packet}; #[derive(Debug, Error)] @@ -29,7 +32,7 @@ pub enum Error { } pub async fn handle_connection( - src: SocketAddr, + info: PktInfo, packet: Packet, callback: F, ) -> Result<(), Error> @@ -37,15 +40,17 @@ where F: Fn(&str) -> Fut, Fut: Future>>, { - let socket = UdpSocket::bind("0.0.0.0:0").await?; + let src = info.addr_src; + let socket = UdpSocket::bind((info.addr_dst, 0)).await?; println!( - "{src}: Connecting from {}", - socket.local_addr().expect("Socket is bound") + "{}: Connecting back from {}", + src, + socket.local_addr().expect("Local addr should be set") ); let Packet::Read(request) = packet else { Packet::error(ErrorCode::NotDefined, "Operation not supported") - .send_to(&socket, &src) + .send_to(&socket, &info.addr_src) .await?; return Err(Error::UnsupportedOperation(packet)); @@ -150,16 +155,30 @@ where F: Fn(&str) -> Fut + Clone + Send + 'static, Fut: Future>> + Send + 'static, { - let socket = UdpSocket::bind("0.0.0.0:69").await?; + let socket = PktInfoUdpSocket::new(Domain::IPV4)?; + socket.set_nonblocking(true)?; + + socket.bind(&SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 69).into())?; + + let async_fd = AsyncFd::new(socket)?; + let mut buf = [0u8; PACKET_SIZE]; println!("Server started"); loop { - let (packet, src) = Packet::recv_from(&socket).await?; + let mut guard = async_fd.readable().await?; + + let (packet, info) = match guard.try_io(|inner| inner.get_ref().recv(&mut buf)) { + Ok(Ok((_, info))) => (buf.try_into()?, info), + Ok(Err(e)) => return Err(e)?, + // Socket was not ready + Err(_would_block) => continue, + }; let callback = callback.clone(); + let src = info.addr_src; tokio::spawn({ async move { - if let Err(err) = handle_connection(src, packet, callback).await { + if let Err(err) = handle_connection(info, packet, callback).await { println!("{src}: {err}") } } -- 2.54.0 From 5a5df6bfc37e65d39f40cf9d06d6fbb67c38f191 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 15 Oct 2026 03:43:37 +0200 Subject: [PATCH 13/20] feat!: Pass connected to ip to serve callback --- src/main.rs | 2 +- src/tftp/server.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 09fccfc..75e8523 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,7 +111,7 @@ async fn main() -> Result<(), Error> { let re = Regex::new(r"talos/(?.+)/(?.+)\.ya?ml") .expect("regex should be valid"); - tftp::serve(move |filename| { + tftp::serve(move |filename, _| { let filename = filename.to_owned(); let re = re.clone(); diff --git a/src/tftp/server.rs b/src/tftp/server.rs index eb26943..b6cd246 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -1,5 +1,5 @@ use std::io; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use std::time::Duration; use socket_pktinfo::{PktInfo, PktInfoUdpSocket}; @@ -37,7 +37,7 @@ pub async fn handle_connection( callback: F, ) -> Result<(), Error> where - F: Fn(&str) -> Fut, + F: Fn(&str, IpAddr) -> Fut, Fut: Future>>, { let src = info.addr_src; @@ -59,7 +59,7 @@ where let filename = request.filename(); println!("{src}: {filename} [{:?}]", request.mode()); - let Some(data) = callback(filename).await else { + let Some(data) = callback(filename, info.addr_dst).await else { Packet::error(ErrorCode::FileNotFound, "Unknown file") .send_to(&socket, &src) .await?; @@ -152,7 +152,7 @@ where pub async fn serve(callback: F) -> Result where - F: Fn(&str) -> Fut + Clone + Send + 'static, + F: Fn(&str, IpAddr) -> Fut + Clone + Send + 'static, Fut: Future>> + Send + 'static, { let socket = PktInfoUdpSocket::new(Domain::IPV4)?; -- 2.54.0 From a5594c768d4f9c744147fb2c046eff1f63d1579b Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 15 Oct 2026 04:46:48 +0200 Subject: [PATCH 14/20] feat: Make bind ip configurable through cli --- src/cli.rs | 6 +++++- src/main.rs | 4 ++-- src/tftp/server.rs | 9 +++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 6490d79..902a5b6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use std::net::IpAddr; use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; @@ -50,5 +51,8 @@ pub enum Commands { /// Generate completions for your current shell ShellCompletions, /// Serve config files over tftp - Serve, + Serve { + #[arg(long, default_value = "0.0.0.0")] + ip: IpAddr, + }, } diff --git a/src/main.rs b/src/main.rs index 75e8523..7bfdcbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,11 +107,11 @@ async fn main() -> Result<(), Error> { "crete", &mut std::io::stdout(), ), - Commands::Serve => { + Commands::Serve { ip } => { let re = Regex::new(r"talos/(?.+)/(?.+)\.ya?ml") .expect("regex should be valid"); - tftp::serve(move |filename, _| { + tftp::serve(ip, move |filename, _| { let filename = filename.to_owned(); let re = re.clone(); diff --git a/src/tftp/server.rs b/src/tftp/server.rs index b6cd246..caff956 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -1,5 +1,5 @@ use std::io; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use socket_pktinfo::{PktInfo, PktInfoUdpSocket}; @@ -150,7 +150,7 @@ where Ok(()) } -pub async fn serve(callback: F) -> Result +pub async fn serve(ip: IpAddr, callback: F) -> Result where F: Fn(&str, IpAddr) -> Fut + Clone + Send + 'static, Fut: Future>> + Send + 'static, @@ -158,12 +158,13 @@ where let socket = PktInfoUdpSocket::new(Domain::IPV4)?; socket.set_nonblocking(true)?; - socket.bind(&SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 69).into())?; + let addr: SocketAddr = (ip, 69).into(); + socket.bind(&addr.into())?; let async_fd = AsyncFd::new(socket)?; let mut buf = [0u8; PACKET_SIZE]; - println!("Server started"); + println!("Tftp server started on {addr}"); loop { let mut guard = async_fd.readable().await?; -- 2.54.0 From 620f36d0bb4a542bb88d90e69e72b055aa2c4b0e Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 20 Oct 2026 02:56:39 +0200 Subject: [PATCH 15/20] feat: Added http server that serve the same files --- Cargo.lock | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/cli.rs | 2 + src/error.rs | 10 +++++ src/file.rs | 37 +++++++++++++++++ src/http.rs | 31 +++++++++++++++ src/main.rs | 58 ++++++++++----------------- 7 files changed, 210 insertions(+), 38 deletions(-) create mode 100644 src/error.rs create mode 100644 src/file.rs create mode 100644 src/http.rs diff --git a/Cargo.lock b/Cargo.lock index feb6127..0cac29c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,70 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "base64" version = "0.23.1" @@ -293,6 +357,7 @@ name = "crete" version = "0.1.3" dependencies = [ "async-walkdir", + "axum", "byteorder", "clap", "clap_complete", @@ -862,6 +927,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.11.1" @@ -875,6 +946,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1213,6 +1285,12 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.8.3" @@ -1234,6 +1312,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c8c750f1a07ea702bbd212bd999fceece9b3d1508b17023b3e174583124b" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minijinja" version = "2.24.0" @@ -1792,6 +1876,29 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -2044,6 +2151,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2082,6 +2190,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-core", ] diff --git a/Cargo.toml b/Cargo.toml index 0a39bd7..5ebf523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ serde_yaml = "0.9.34" thiserror = "2.0.21" tokio = { version = "1.52.1", features = ["full"] } ipxe = { path = "./ipxe" } +axum = { version = "0.8.9", features = ["macros"] } socket2 = "0.6.5" socket-pktinfo = "0.4.1" diff --git a/src/cli.rs b/src/cli.rs index 902a5b6..cfcf512 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -52,6 +52,8 @@ pub enum Commands { ShellCompletions, /// Serve config files over tftp Serve { + #[arg(short, long, default_value = "3000")] + port: u16, #[arg(long, default_value = "0.0.0.0")] ip: IpAddr, }, diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..05be665 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +use crete::tftp::ServerError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("No clusters where found")] + NoClustersFound, + #[error("Server error: {0}")] + ServerError(#[from] ServerError), +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..0bf8dab --- /dev/null +++ b/src/file.rs @@ -0,0 +1,37 @@ +use crete::cluster::Cluster; +use ipxe::IPXE_DIR; +use regex::Regex; + +// TODO: Make this a proper async function +pub fn get_file(filename: impl ToString) -> impl Future, &'static str)>> { + // TODO: Make this a static that is initialized once + let re = + Regex::new(r"^talos/(?.+)/(?.+)\.ya?ml$").expect("regex should be valid"); + + let filename: String = filename.to_string(); + let re = re.clone(); + + async move { + const IPXE_PATTERN: &str = "ipxe/"; + if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) { + Some(( + IPXE_DIR.get_file(filename)?.contents().to_owned(), + "application/octet-stream", + )) + } else if let Some(caps) = re.captures(&filename) { + let cluster = &caps["cluster"]; + let node = &caps["node"]; + + let clusters = Cluster::get_all().await; + let cluster = clusters.get(cluster)?; + + let node = cluster.node(node)?; + + let config = node.config(cluster).await; + + Some((config.into(), "application/yaml; charset=utf-8")) + } else { + None + } + } +} diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..08eefa5 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,31 @@ +use std::path::PathBuf; + +use axum::debug_handler; +use axum::extract::Request; +use axum::response::IntoResponse; +use reqwest::{StatusCode, header}; + +use crate::file::get_file; + +#[debug_handler] +pub async fn handler(req: Request) -> impl IntoResponse { + let Some(path) = req.uri().path().strip_prefix("/") else { + return StatusCode::BAD_REQUEST.into_response(); + }; + + let Some((file, content_type)) = get_file(path).await else { + return StatusCode::NOT_FOUND.into_response(); + }; + + let path = PathBuf::from(path); + let filename = path.file_name().unwrap(); + let filename = filename.to_str().unwrap(); + + let content_type = [(header::CONTENT_TYPE, content_type)]; + let disposition = [( + header::CONTENT_DISPOSITION, + format!("inline; filename=\"{filename}\""), + )]; + + (content_type, disposition, file).into_response() +} diff --git a/src/main.rs b/src/main.rs index 7bfdcbe..ec232de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,27 @@ mod cli; +mod error; +mod file; +mod http; +use std::net::SocketAddr; use std::pin::pin; +use axum::Router; +use axum::routing::get; use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; use crete::talosconfig::{get_current_context, set_context}; -use crete::tftp::ServerError; use crete::{get_configs_path, get_repo_path, set_repo_path, tftp}; use futures::StreamExt; use futures::future::join_all; -use ipxe::IPXE_DIR; use minijinja::context; -use regex::Regex; -use thiserror::Error; use crate::cli::{Cli, Commands, GlobalOpts}; - -#[derive(Debug, Error)] -enum Error { - #[error("No clusters where found")] - NoClustersFound, - #[error("Server error: {0}")] - ServerError(#[from] ServerError), -} +use crate::error::Error; +use crate::file::get_file; +use crate::http::handler; async fn generate(opts: &GlobalOpts) -> Result<(), Error> { let clusters = Cluster::get_all().await; @@ -107,36 +104,21 @@ async fn main() -> Result<(), Error> { "crete", &mut std::io::stdout(), ), - Commands::Serve { ip } => { - let re = Regex::new(r"talos/(?.+)/(?.+)\.ya?ml") - .expect("regex should be valid"); + Commands::Serve { port, ip } => { + tokio::spawn(async move { + let app = Router::new().fallback_service(get(handler)); + let addr: SocketAddr = (ip, port).into(); + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + + println!("Http server started on {addr}"); + axum::serve(listener, app).await.unwrap(); + }); tftp::serve(ip, move |filename, _| { let filename = filename.to_owned(); - let re = re.clone(); - - async move { - const IPXE_PATTERN: &str = "ipxe/"; - if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) { - Some(IPXE_DIR.get_file(filename)?.contents().to_owned()) - } else if let Some(caps) = re.captures(&filename) { - let cluster = &caps["cluster"]; - let node = &caps["node"]; - - let clusters = Cluster::get_all().await; - let cluster = clusters.get(cluster)?; - - let node = cluster.node(node)?; - - let config = node.config(cluster).await; - - Some(config.into()) - } else { - None - } - } + async move { get_file(filename).await.map(|(file, _)| file) } }) - .await? + .await?; } }; -- 2.54.0 From 8149ffbc3709154cb5c798c8533bf49ae4e9de30 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 20 Oct 2026 02:59:13 +0200 Subject: [PATCH 16/20] feat: Include boot.ipxe script --- ipxe/src/embed.ipxe | 3 +- src/boot.ipxe | 19 ++++++++++++ src/environment.rs | 76 ++++++++++++++++++++++++--------------------- src/file.rs | 27 ++++++++++++++-- src/http.rs | 17 +++++++++- src/main.rs | 8 +++-- 6 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 src/boot.ipxe diff --git a/ipxe/src/embed.ipxe b/ipxe/src/embed.ipxe index f24cea6..4ff63af 100644 --- a/ipxe/src/embed.ipxe +++ b/ipxe/src/embed.ipxe @@ -1,5 +1,4 @@ #!ipxe dhcp - -echo "Hello, World!" +chain boot.ipxe diff --git a/src/boot.ipxe b/src/boot.ipxe new file mode 100644 index 0000000..e704798 --- /dev/null +++ b/src/boot.ipxe @@ -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 %} diff --git a/src/environment.rs b/src/environment.rs index 615a2ec..683123c 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -25,46 +25,50 @@ fn apply_netmask( 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| { + 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| { + names + .iter() + .map(|name| { + get_configs_path() + .join(name) + .join("kubeconfig") + .to_string_lossy() + .to_string() + }) + .collect::>() + }); +} + impl<'a> PathEnvironment<'a> { pub fn new(path: &Path) -> Self { let mut env = Environment::new(); - // 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| { - 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| { - names - .iter() - .map(|name| { - get_configs_path() - .join(name) - .join("kubeconfig") - .to_string_lossy() - .to_string() - }) - .collect::>() - }); + configure_environment(&mut env); // Add path loader env.set_loader(path_loader(&path)); diff --git a/src/file.rs b/src/file.rs index 0bf8dab..a37223c 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,9 +1,14 @@ use crete::cluster::Cluster; +use crete::environment::configure_environment; use ipxe::IPXE_DIR; +use minijinja::{Environment, context}; use regex::Regex; // TODO: Make this a proper async function -pub fn get_file(filename: impl ToString) -> impl Future, &'static str)>> { +pub fn get_file( + filename: impl ToString, + http_url: impl ToString, +) -> impl Future, &'static str)>> { // TODO: Make this a static that is initialized once let re = Regex::new(r"^talos/(?.+)/(?.+)\.ya?ml$").expect("regex should be valid"); @@ -13,7 +18,25 @@ pub fn get_file(filename: impl ToString) -> impl Future async move { 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::>(), + 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(( IPXE_DIR.get_file(filename)?.contents().to_owned(), "application/octet-stream", diff --git a/src/http.rs b/src/http.rs index 08eefa5..5d725f1 100644 --- a/src/http.rs +++ b/src/http.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use axum::debug_handler; use axum::extract::Request; +use axum::http::Uri; use axum::response::IntoResponse; use reqwest::{StatusCode, header}; @@ -9,11 +10,25 @@ use crate::file::get_file; #[debug_handler] 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 { 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(); }; diff --git a/src/main.rs b/src/main.rs index ec232de..8a0fb13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,9 +114,13 @@ async fn main() -> Result<(), Error> { axum::serve(listener, app).await.unwrap(); }); - tftp::serve(ip, move |filename, _| { + tftp::serve(ip, move |filename, ip| { 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?; } -- 2.54.0 From 427ec6f61d610ab4468e36e9c5a5e8c48e3df59e Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 20 Oct 2026 03:11:27 +0200 Subject: [PATCH 17/20] feat: Reuse compiled regex --- src/file.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/file.rs b/src/file.rs index a37223c..63dec16 100644 --- a/src/file.rs +++ b/src/file.rs @@ -2,16 +2,14 @@ use crete::cluster::Cluster; use crete::environment::configure_environment; use ipxe::IPXE_DIR; use minijinja::{Environment, context}; -use regex::Regex; +use regex::regex; // TODO: Make this a proper async function pub fn get_file( filename: impl ToString, http_url: impl ToString, ) -> impl Future, &'static str)>> { - // TODO: Make this a static that is initialized once - let re = - Regex::new(r"^talos/(?.+)/(?.+)\.ya?ml$").expect("regex should be valid"); + let re = regex!(r"^talos/(?.+)/(?.+)\.ya?ml$"); let filename: String = filename.to_string(); let re = re.clone(); -- 2.54.0 From b9251ee5dc26febcdc19a927f6e42ecd82937ef6 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 20 Oct 2026 03:45:31 +0200 Subject: [PATCH 18/20] refactor: Improve async --- src/file.rs | 67 ++++++++++++++++++++++------------------------ src/main.rs | 11 +++----- src/tftp/server.rs | 6 ++--- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/file.rs b/src/file.rs index 63dec16..3860acc 100644 --- a/src/file.rs +++ b/src/file.rs @@ -4,55 +4,52 @@ use ipxe::IPXE_DIR; use minijinja::{Environment, context}; use regex::regex; -// TODO: Make this a proper async function -pub fn get_file( +pub async fn get_file( filename: impl ToString, http_url: impl ToString, -) -> impl Future, &'static str)>> { +) -> Option<(Vec, &'static str)> { let re = regex!(r"^talos/(?.+)/(?.+)\.ya?ml$"); let filename: String = filename.to_string(); let re = re.clone(); - async move { - const IPXE_PATTERN: &str = "ipxe/"; - if filename == format!("{IPXE_PATTERN}boot.ipxe") { - let mut env = Environment::new(); + const IPXE_PATTERN: &str = "ipxe/"; + if filename == format!("{IPXE_PATTERN}boot.ipxe") { + let mut env = Environment::new(); - configure_environment(&mut env); + configure_environment(&mut env); - let template = env - .template_from_str(include_str!("./boot.ipxe")) - .expect("Template should be valid"); + 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::>(), - httpUrl => http_url.to_string() - }) - .unwrap(); + let clusters = Cluster::get_all().await; + let content = template + .render(context! { + clusters => clusters.values().collect::>(), + 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(( - IPXE_DIR.get_file(filename)?.contents().to_owned(), - "application/octet-stream", - )) - } else if let Some(caps) = re.captures(&filename) { - let cluster = &caps["cluster"]; - let node = &caps["node"]; + Some((content.into(), "text/plain; charset=utf-8")) + } else if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) { + Some(( + IPXE_DIR.get_file(filename)?.contents().to_owned(), + "application/octet-stream", + )) + } else if let Some(caps) = re.captures(&filename) { + let cluster = &caps["cluster"]; + let node = &caps["node"]; - let clusters = Cluster::get_all().await; - let cluster = clusters.get(cluster)?; + let clusters = Cluster::get_all().await; + let cluster = clusters.get(cluster)?; - let node = cluster.node(node)?; + let node = cluster.node(node)?; - let config = node.config(cluster).await; + let config = node.config(cluster).await; - Some((config.into(), "application/yaml; charset=utf-8")) - } else { - None - } + Some((config.into(), "application/yaml; charset=utf-8")) + } else { + None } } diff --git a/src/main.rs b/src/main.rs index 8a0fb13..03627bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,13 +114,10 @@ async fn main() -> Result<(), Error> { axum::serve(listener, app).await.unwrap(); }); - tftp::serve(ip, move |filename, ip| { - let filename = filename.to_owned(); - async move { - get_file(filename, format!("http://{ip}:{port}")) - .await - .map(|(file, _)| file) - } + tftp::serve(ip, move |filename, ip| async move { + get_file(filename, format!("http://{ip}:{port}")) + .await + .map(|(file, _)| file) }) .await?; } diff --git a/src/tftp/server.rs b/src/tftp/server.rs index caff956..026c1eb 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -37,7 +37,7 @@ pub async fn handle_connection( callback: F, ) -> Result<(), Error> where - F: Fn(&str, IpAddr) -> Fut, + F: Fn(String, IpAddr) -> Fut, Fut: Future>>, { let src = info.addr_src; @@ -59,7 +59,7 @@ where let filename = request.filename(); println!("{src}: {filename} [{:?}]", request.mode()); - let Some(data) = callback(filename, info.addr_dst).await else { + let Some(data) = callback(filename.to_string(), info.addr_dst).await else { Packet::error(ErrorCode::FileNotFound, "Unknown file") .send_to(&socket, &src) .await?; @@ -152,7 +152,7 @@ where pub async fn serve(ip: IpAddr, callback: F) -> Result where - F: Fn(&str, IpAddr) -> Fut + Clone + Send + 'static, + F: Fn(String, IpAddr) -> Fut + Clone + Send + 'static, Fut: Future>> + Send + 'static, { let socket = PktInfoUdpSocket::new(Domain::IPV4)?; -- 2.54.0 From f9f0be1e9259494ffc69abffccc26806eff27f59 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Wed, 21 Oct 2026 06:44:44 +0200 Subject: [PATCH 19/20] feat: Rework iPXE build script to allow for patching --- .gitmodules | 3 - Cargo.lock | 1800 +++++++++++++++++++++++++++++++++++++++-- ipxe/Cargo.toml | 8 +- ipxe/build.rs | 179 +++- ipxe/ipxe | 1 - ipxe/patches/.gitkeep | 0 ipxe/src/lib.rs | 2 +- typos.toml | 2 + 8 files changed, 1891 insertions(+), 104 deletions(-) delete mode 160000 ipxe/ipxe create mode 100644 ipxe/patches/.gitkeep create mode 100644 typos.toml diff --git a/.gitmodules b/.gitmodules index 58b8f04..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "ipxe/ipxe"] - path = ipxe/ipxe - url = git@github.com:rkubes/ipxe diff --git a/Cargo.lock b/Cargo.lock index 0cac29c..45b927e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "1.0.0" @@ -47,7 +53,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -58,7 +64,16 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "arc-swap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" +dependencies = [ + "rustversion", ] [[package]] @@ -124,6 +139,29 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.9" @@ -188,6 +226,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64" version = "0.23.1" @@ -230,12 +274,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "build-print" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e6738dfb11354886f890621b4a34c0b177f75538023f7100b608ab9adbd66b" - [[package]] name = "bumpalo" version = "3.20.3" @@ -272,6 +310,23 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + [[package]] name = "clap" version = "4.6.7" @@ -321,12 +376,40 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -352,6 +435,30 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + [[package]] name = "crete" version = "0.1.3" @@ -362,7 +469,7 @@ dependencies = [ "clap", "clap_complete", "futures", - "gix-discover", + "gix-discover 0.56.0", "ipxe", "minijinja", "netascii", @@ -381,12 +488,76 @@ dependencies = [ "tokio", ] +[[package]] +name = "crossbeam" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71406cd8807725f7ac2f999a4cdd32e98f829fdf65f528343cebf945e41df1e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "defmt" version = "0.3.100" @@ -427,6 +598,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "diffy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3dc2f773b6aaa63b1a7684b8589f670a8a0146a510b74d23a401c882364b49" +dependencies = [ + "hashbrown 0.17.1", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -438,6 +618,17 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "dua-core" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d687862f3206e7c660daeb6ab0200c65e58fccd7e508819e4bd9949304c640a8" +dependencies = [ + "crossbeam", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "dunce" version = "1.0.5" @@ -450,6 +641,20 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -463,7 +668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -504,12 +709,34 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -534,6 +761,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.34" @@ -635,6 +868,19 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -642,8 +888,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "gix" +version = "0.89.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2156d2e07fc3afa7632a82bf3cb0db91f995645a2852f642105c9ac9b52e1cb2" +dependencies = [ + "gix-actor 0.44.0", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date 0.18.0", + "gix-diff", + "gix-dir", + "gix-discover 0.57.0", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-glob", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-ignore", + "gix-index", + "gix-lock 26.0.0", + "gix-negotiate", + "gix-note", + "gix-object 0.66.0", + "gix-odb", + "gix-pack", + "gix-parallel", + "gix-path 0.14.0", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-quote", + "gix-ref 0.69.0", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec 0.16.0", + "gix-shallow", + "gix-status", + "gix-submodule", + "gix-tempfile 26.0.0", + "gix-trace 0.3.0", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "gix-worktree", + "gix-worktree-state", + "gix-worktree-stream", + "gix-zlib", + "nonempty", + "smallvec", ] [[package]] @@ -653,8 +961,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e046e9a929e8e1f40f9a34f736408c4c079f9cf004429f3e718d270e847be96" dependencies = [ "bstr", - "gix-date", - "gix-error", + "gix-date 0.17.0", + "gix-error 0.4.0", +] + +[[package]] +name = "gix-actor" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2127da41042965ed205568a52443465806944f71c8acd73f48ec94b8cd8e798e" +dependencies = [ + "bstr", + "gix-date 0.18.0", + "gix-error 0.5.1", +] + +[[package]] +name = "gix-attributes" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0feca0b8919aba9f123b75de54062dd61eb37d2a2130f34be520da6ed8b8ef72" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-glob", + "gix-parallel", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", + "smallvec", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "408cb9e482e0dcc5fc481850556348dda6981ec054a4a8e81a8e1bbf84f85eef" +dependencies = [ + "gix-error 0.5.1", +] + +[[package]] +name = "gix-chunk" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626335faf30ed235744642c2309aa370683b8bb2757f1c22a807e274dff60074" +dependencies = [ + "gix-error 0.5.1", +] + +[[package]] +name = "gix-command" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac560c05e19455016a4a94b33a77debe59aaed8783da32a73d31c9975947152e" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", +] + +[[package]] +name = "gix-commitgraph" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfede9936d4b789867c09722576aea4ceb0e3aacf962eef035ebab0686b41a80" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "memmap2", + "nonempty", +] + +[[package]] +name = "gix-config" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a9c000fa9a9154f22ffc8f8d74426e9300d759c2029e8701e2ba39d5d464ea" +dependencies = [ + "bstr", + "gix-config-value", + "gix-error 0.5.1", + "gix-glob", + "gix-parallel", + "gix-path 0.14.0", + "gix-ref 0.69.0", + "gix-sec 0.16.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "smallvec", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab206c2fccfd35e0de7fb1b424997cfd42912062c66dae024e08a06e22d46674" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-utils 0.5.1", + "libc", +] + +[[package]] +name = "gix-credentials" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba790d296ded4bcfe485f761a65b8659fcccad825ff59a3acc38578a86f812de" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-prompt", + "gix-quote", + "gix-sec 0.16.0", + "gix-trace 0.3.0", + "gix-url", + "percent-encoding", ] [[package]] @@ -664,11 +1099,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8beceb16a77fb222923592864b94beb4ad4c2b175a7055c2cac6815ee0370a" dependencies = [ "bstr", - "gix-error", + "gix-error 0.4.0", "itoa", "jiff", ] +[[package]] +name = "gix-date" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7eeeecc599082f32b5ea6a8c4c734e38be9730e2c09e3fe2851bb4692393e9" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "itoa", + "jiff", +] + +[[package]] +name = "gix-diff" +version = "0.69.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa640963fdcbc6b4718e48593c3917f5ae764b59fb5a96313cb5a757246d041d" +dependencies = [ + "bstr", + "gix-attributes", + "gix-command", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-imara-diff", + "gix-index", + "gix-object 0.66.0", + "gix-path 0.14.0", + "gix-pathspec", + "gix-tempfile 26.0.0", + "gix-trace 0.3.0", + "gix-traverse", + "gix-worktree", +] + +[[package]] +name = "gix-dir" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e916ef77f19161b738ae9913662bc36fed1e48256a943f40277b3a585841da" +dependencies = [ + "bstr", + "gix-discover 0.57.0", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-ignore", + "gix-index", + "gix-object 0.66.0", + "gix-path 0.14.0", + "gix-pathspec", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-worktree", +] + [[package]] name = "gix-discover" version = "0.56.0" @@ -677,11 +1168,26 @@ checksum = "4476ba055ec5fbb05f59dd98380b596428da060beac5f9dccd7374302ad1fbfb" dependencies = [ "bstr", "dunce", - "gix-error", - "gix-fs", - "gix-path", - "gix-ref", - "gix-sec", + "gix-error 0.4.0", + "gix-fs 0.23.0", + "gix-path 0.13.0", + "gix-ref 0.68.0", + "gix-sec 0.15.0", +] + +[[package]] +name = "gix-discover" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d0f8fd2c7e4b458eb3081362d49b54bd0131d23ecd49be802085a11f0d3cee" +dependencies = [ + "bstr", + "dunce", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-path 0.14.0", + "gix-ref 0.69.0", + "gix-sec 0.16.0", ] [[package]] @@ -693,20 +1199,50 @@ dependencies = [ "bstr", ] +[[package]] +name = "gix-error" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb3308a948a01b94ef13d9ee7a9b28c1303d8d9a9dc33b401ff40c2184d9e04a" +dependencies = [ + "bstr", +] + [[package]] name = "gix-features" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb3a430dbc02d6c5e30a9f92083459593495ea22a476e837d248b22d800eed80" dependencies = [ - "gix-path", - "gix-trace", - "gix-utils", + "gix-path 0.13.0", + "gix-trace 0.2.0", + "gix-utils 0.4.0", "libc", "prodash", "walkdir", ] +[[package]] +name = "gix-filter" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8d932ee4043f7d77d15ee58d2be2bc95accea5dd1d77fd145de1b7c504551b" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-packetline", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "smallvec", +] + [[package]] name = "gix-fs" version = "0.23.0" @@ -714,10 +1250,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0105ab1cef4542d092c27766d2daf1548fcbad7e460dc2208642c120bb19b3ad" dependencies = [ "bstr", - "gix-error", + "gix-error 0.4.0", "gix-features", - "gix-path", - "gix-utils", + "gix-path 0.13.0", + "gix-utils 0.4.0", +] + +[[package]] +name = "gix-fs" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44a05d043e670c055cd8b53d078562dc0bc1e322bff6b0376b407d37cf72dcc5" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-parallel", + "gix-path 0.14.0", + "gix-utils 0.5.1", + "libc", + "walkdir", +] + +[[package]] +name = "gix-glob" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0d03ab50fa841621031960679caf7fe94940968d9a0736d12a580f2c03e525" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-fs 0.24.0", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", ] [[package]] @@ -727,31 +1292,155 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a22b6febaaee8546eb0c9dd21ecdd93aca269a6cb3897e1250f0f60933c0174" dependencies = [ "faster-hex", - "gix-error", + "gix-error 0.4.0", "gix-features", "sha1dc", ] +[[package]] +name = "gix-hash" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ca156064e378d8c657084365846cedb8dc498c6ea4500965a64d74f12af2be" +dependencies = [ + "faster-hex", + "gix-error 0.5.1", + "gix-utils 0.5.1", + "sha1dc", +] + [[package]] name = "gix-hashtable" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55bff2f32f6f5a95bdd43e5e5e01b0656250cffbc48eda19db0afa3d73adf2f" dependencies = [ - "gix-hash", - "hashbrown", + "gix-hash 0.27.0", + "hashbrown 0.17.1", "parking_lot", ] +[[package]] +name = "gix-hashtable" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916cb6b2ec42ffaca624e7867bb382a4673015cb4ffcd7330b48085d02e4c615" +dependencies = [ + "gix-hash 0.28.0", + "hashbrown 0.17.1", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb1c93f60845d493a72fde38ae237498bc4e4464f45afa4af23e53f8295a3ba1" +dependencies = [ + "bstr", + "gix-glob", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "unicode-bom", +] + +[[package]] +name = "gix-imara-diff" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c48eccd894bd15555b0346470e63491ded94aefe76b3fcb1052084b5a393238" +dependencies = [ + "bstr", + "hashbrown 0.17.1", +] + +[[package]] +name = "gix-index" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6db8b20a3dba8836daf90b24199600bb27dcfce3a127ec24ae61e15a7c5b68" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-trace 0.3.0", + "gix-traverse", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "hashbrown 0.17.1", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", +] + [[package]] name = "gix-lock" version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd5ddfd0cc476fef744ac544783b96a9ff5d49f8de541e6e9d6d6002aa90720" dependencies = [ - "gix-error", - "gix-tempfile", - "gix-utils", + "gix-error 0.4.0", + "gix-tempfile 25.0.0", + "gix-utils 0.4.0", +] + +[[package]] +name = "gix-lock" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2f7f5bd6b9fcd0b0dc58b0e6a708f3a47c3313876571dfe800c926c069aa5c" +dependencies = [ + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-tempfile 26.0.0", + "gix-utils 0.5.1", +] + +[[package]] +name = "gix-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11f5429ca73cc3758c9f53af56a9fba100a8701c5b4bc30e29b8b0b15288991" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gix-negotiate" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ddfb0d6da849bd938d929336e07c1f26970d9f6ec59621df74ec3f79c128ea" +dependencies = [ + "bitflags 2.13.2", + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-revwalk", +] + +[[package]] +name = "gix-note" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd364f8bdceaed9e7f3ae40f324b16e39f20e9c7e6a6a058f7c6ec66c3e6205c" +dependencies = [ + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", ] [[package]] @@ -761,18 +1450,112 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a72909eeeed845721a143ef3dbeb4d6cd5df79c839fe7eefc138cb4f982eba3e" dependencies = [ "bstr", - "gix-actor", - "gix-date", - "gix-error", + "gix-actor 0.43.0", + "gix-date 0.17.0", + "gix-error 0.4.0", "gix-features", - "gix-hash", - "gix-hashtable", - "gix-utils", - "gix-validate", + "gix-hash 0.27.0", + "gix-hashtable 0.17.0", + "gix-utils 0.4.0", + "gix-validate 0.12.0", "itoa", "smallvec", ] +[[package]] +name = "gix-object" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e38f50177e211642af154f9a7f710b7a3a637e635f9b6adb9d51973b0f9209" +dependencies = [ + "bstr", + "gix-actor 0.44.0", + "gix-command", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-path 0.14.0", + "gix-tempfile 26.0.0", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "itoa", + "smallvec", +] + +[[package]] +name = "gix-odb" +version = "0.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca444f33f2140876ed6b5313c2b550b4af699008193c96f3bff5023afa9ef4ea" +dependencies = [ + "arc-swap", + "crc32fast", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "gix-pack", + "gix-parallel", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-zlib", + "memmap2", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-pack" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5024cc503cd29461a705ae90b9eae772fa8f0e6fafc1c48513877dc374de05" +dependencies = [ + "clru", + "crc32fast", + "gix-chunk", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-tempfile 26.0.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-zlib", + "memmap2", + "parking_lot", + "smallvec", +] + +[[package]] +name = "gix-packetline" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e51b227f6a925fe9308858ac91170dd00fa2880f5480fc81e8a4594fe2c35" +dependencies = [ + "bstr", + "faster-hex", + "gix-error 0.5.1", + "gix-trace 0.3.0", +] + +[[package]] +name = "gix-parallel" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994cf1eec247ba4be7f9e7c0d58cc73f83eecb5fcb9feb595df5ba3d2078546" +dependencies = [ + "crossbeam-channel", + "gix-trace 0.3.0", + "once_cell", + "parking_lot", +] + [[package]] name = "gix-path" version = "0.13.0" @@ -780,9 +1563,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd051ac60c5dbce2228daabf25b6a76408ac732d416c678cfa0a4914d13f60b8" dependencies = [ "bstr", - "gix-error", - "gix-trace", - "gix-validate", + "gix-error 0.4.0", + "gix-trace 0.2.0", + "gix-validate 0.12.0", +] + +[[package]] +name = "gix-path" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d563d4d0c975ed0df4e853f9ec962a2ba46633b1d324e1a5582541fcae0c6b" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-trace 0.3.0", + "gix-validate 0.13.0", +] + +[[package]] +name = "gix-pathspec" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3b7f03e516a8435d18c3cc2d446925940c81d409145ac21edb5a576f0625e4" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-error 0.5.1", + "gix-glob", + "gix-path 0.14.0", +] + +[[package]] +name = "gix-prompt" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f26063489b415c40bd960ec488251d77d4cfe3cf5ca4331e2122c0c726bcc2d" +dependencies = [ + "gix-command", + "gix-config-value", + "gix-error 0.5.1", + "parking_lot", + "rustix", +] + +[[package]] +name = "gix-protocol" +version = "0.67.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832b5dc46a3c0a6f6fa8f8e571c11074add4b545c6789799ae6d6c36ae1a294e" +dependencies = [ + "bstr", + "gix-credentials", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "gix-macros", + "gix-negotiate", + "gix-object 0.66.0", + "gix-ref 0.69.0", + "gix-refspec", + "gix-revwalk", + "gix-shallow", + "gix-trace 0.3.0", + "gix-transport", + "gix-utils 0.5.1", + "nonempty", +] + +[[package]] +name = "gix-quote" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f92bc1b13c09e88388578e8aa1e987ec35f28fca158c13b1baa7ab1a85b124" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-utils 0.5.1", ] [[package]] @@ -791,20 +1650,84 @@ version = "0.68.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c90ceeec67de03b737c30b1b69116ca56fec803edde022f3d25181f0f597083d" dependencies = [ - "gix-actor", - "gix-error", + "gix-actor 0.43.0", + "gix-error 0.4.0", "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", + "gix-fs 0.23.0", + "gix-hash 0.27.0", + "gix-lock 25.0.0", + "gix-object 0.65.0", + "gix-path 0.13.0", + "gix-tempfile 25.0.0", + "gix-utils 0.4.0", + "gix-validate 0.12.0", "memmap2", ] +[[package]] +name = "gix-ref" +version = "0.69.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073bc0928e1f747823b1eaed94918119dacef4c616532bc3d17e5b7457863471" +dependencies = [ + "gix-actor 0.44.0", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-tempfile 26.0.0", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "memmap2", +] + +[[package]] +name = "gix-refspec" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc7010dae0df2900aa77cc67f8aca042cd80e35938ccc179010d863b1d42a0c" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-validate 0.13.0", + "smallvec", +] + +[[package]] +name = "gix-revision" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63210c1943b67fa700806c3c3486fc16a92b3ea871f5cf092340b8eec10a0ca1" +dependencies = [ + "bstr", + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-revwalk", + "nonempty", +] + +[[package]] +name = "gix-revwalk" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "800479fbbf1e7f94f663e07943a91add5499343e7aac611a81e6a0460fd44af6" +dependencies = [ + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "smallvec", +] + [[package]] name = "gix-sec" version = "0.15.0" @@ -812,9 +1735,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8eb983a830ba586b61f7005deaf44609e1a4186cbf13a0e8668154ca910461" dependencies = [ "bitflags 2.13.2", - "gix-path", + "gix-path 0.13.0", "libc", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-sec" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5640f642a1b471616d48230b1def7ca743cb30903441ff84800cafbab3c0fddf" +dependencies = [ + "bitflags 2.13.2", + "gix-path 0.14.0", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bc0720a3d6b10f18e3ac382a7780bcfc4c6bee94a8b5857b8e4ec94c50d754" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "nonempty", +] + +[[package]] +name = "gix-status" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3174a9f3fb991cf25a7a609ab6c50f19baa7d5f8ce05a48c202f7db4ce3ba" +dependencies = [ + "bstr", + "filetime", + "gix-diff", + "gix-dir", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-index", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-pathspec", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-worktree", + "hashbrown 0.16.1", + "portable-atomic", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-submodule" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d945257bd0d624ca844a5a36c79d748df080f355b0ac92d68793875aa529a3f" +dependencies = [ + "bstr", + "gix-config", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-pathspec", + "gix-refspec", + "gix-url", ] [[package]] @@ -823,11 +1813,25 @@ version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dd7dd8df3345933caddd6ce67821c9eec17192564f1011cf0bd0b8cb0d07ffb" dependencies = [ - "gix-fs", + "gix-fs 0.23.0", "libc", "parking_lot", "tempfile", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-tempfile" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9275c8ae0646d840ae0156d3f90d39b7beea6e31a398afb9ee72feb293a269c" +dependencies = [ + "dashmap", + "gix-fs 0.24.0", + "libc", + "parking_lot", + "tempfile", + "windows-sys 0.61.2", ] [[package]] @@ -836,6 +1840,64 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df45b2cf6052d9e52681130c17d67505b023e8933ad974f94a2be0b0d24f636a" +[[package]] +name = "gix-trace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133f2de116104d637541831b6acb157898556d5eeec363ab2eb1c583adc6bb56" + +[[package]] +name = "gix-transport" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e43d6b0097ea9f2f266d5d6c43b8f615ed3da7c686ec8771cd17c1cb33cb0de" +dependencies = [ + "base64 0.22.1", + "bstr", + "gix-command", + "gix-credentials", + "gix-error 0.5.1", + "gix-packetline", + "gix-path 0.14.0", + "gix-quote", + "gix-sec 0.16.0", + "gix-trace 0.3.0", + "gix-url", + "gix-utils 0.5.1", + "parking_lot", + "reqwest", +] + +[[package]] +name = "gix-traverse" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668a6e7f7688e5a43eaf161f1471ffc559de1a5fd8471e861c64d59d1699e1e" +dependencies = [ + "bitflags 2.13.2", + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "gix-revwalk", + "smallvec", +] + +[[package]] +name = "gix-url" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04317dea7a5dd5253bbcab060c230f3de6a57d17cc9600c6cd7030bf4795b414" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-utils 0.5.1", + "percent-encoding", +] + [[package]] name = "gix-utils" version = "0.4.0" @@ -843,7 +1905,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70525766f53426ca4cd2ea4d58b0b31488dfedb416b3254e2efdd9dc184cc880" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", + "unicode-normalization", +] + +[[package]] +name = "gix-utils" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e770b8c34311044d1addc7b18957db0ac27183707e1140dc90e6f674a6354e" +dependencies = [ + "bstr", + "bytes", + "fastrand", + "getrandom 0.4.3", + "gix-error 0.5.1", + "prodash", "unicode-normalization", ] @@ -854,7 +1931,108 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30574f81fc41b2ca3582ece2bdf8696bdfe9c170aa7e8bcafb81199c86b1ceea" dependencies = [ "bstr", - "gix-error", + "gix-error 0.4.0", +] + +[[package]] +name = "gix-validate" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcd59cc52742468bc5c8a0e7123390d154f45e6cbd37461b0dcecdc681f2179" +dependencies = [ + "bstr", + "gix-error 0.5.1", +] + +[[package]] +name = "gix-worktree" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a285c0f6b41e504e8bb81ca3f861c4da00cb3d5ced3210a4591112f22da6d38f" +dependencies = [ + "bstr", + "dua-core", + "gix-attributes", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-glob", + "gix-hash 0.28.0", + "gix-ignore", + "gix-index", + "gix-object 0.66.0", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-validate 0.13.0", +] + +[[package]] +name = "gix-worktree-state" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003c1162556d4ebf51b8140b955cb04c2458d5ecbdffe50262dbbb270635208c" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-index", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-worktree", + "io-close", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d2789610e8179d4e0e8e51170a52a66483c103a8ad3c7db4d075b571856cb6" +dependencies = [ + "gix-attributes", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-traverse", + "gix-utils 0.5.1", + "parking_lot", +] + +[[package]] +name = "gix-zlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2edb206bf06f3c801f4f6789c696396171558ab9b4c7f1fe3ba4d39b00b0f4" +dependencies = [ + "gix-error 0.5.1", + "zlib-rs", +] + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -866,11 +2044,33 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heapless" @@ -943,6 +2143,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -954,6 +2155,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -976,7 +2192,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-channel", "futures-util", @@ -1124,7 +2340,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", +] + +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", ] [[package]] @@ -1137,7 +2363,8 @@ checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" name = "ipxe" version = "0.1.0" dependencies = [ - "build-print", + "diffy", + "gix", "include_dir", ] @@ -1207,13 +2434,62 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "jobserver" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom", + "getrandom 0.4.3", "libc", ] @@ -1285,6 +2561,12 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + [[package]] name = "matchit" version = "0.8.4" @@ -1337,9 +2619,15 @@ checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "native-tls" version = "0.2.18" @@ -1363,6 +2651,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "205c45083b6725eb4671392c7c399510ad1498dd0275f1a2e68a70a34ebdf1b1" +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + [[package]] name = "num_enum" version = "0.7.6" @@ -1570,6 +2864,63 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -1585,6 +2936,32 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1678,24 +3055,34 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64", + "base64 0.23.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", + "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1705,6 +3092,35 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.5" @@ -1715,7 +3131,33 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] @@ -1724,9 +3166,49 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1754,7 +3236,7 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1934,6 +3416,22 @@ dependencies = [ "libc", ] +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -1954,7 +3452,7 @@ checksum = "612942246d0cc239cfd83af1dfd39be47f649208a3524e5e9da651910128e0ac" dependencies = [ "libc", "socket2", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1964,7 +3462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1979,6 +3477,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.119" @@ -2028,10 +3532,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2084,7 +3588,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2108,6 +3612,30 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -2210,6 +3738,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + [[package]] name = "unicode-ident" version = "1.0.26" @@ -2231,6 +3765,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -2352,21 +3892,71 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2376,6 +3966,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "1.0.4" @@ -2484,6 +4138,12 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" diff --git a/ipxe/Cargo.toml b/ipxe/Cargo.toml index 2220d92..b5e2434 100644 --- a/ipxe/Cargo.toml +++ b/ipxe/Cargo.toml @@ -4,7 +4,13 @@ version = "0.1.0" edition = "2024" [build-dependencies] -build-print = "1.0.1" +diffy = "0.5.2" +gix = { version = "0.89.0", default-features = false, features = [ + "blocking-network-client", + "worktree-mutation", + "sha1", + "blocking-http-transport-reqwest-rust-tls", +] } [dependencies] include_dir = "0.7.4" diff --git a/ipxe/build.rs b/ipxe/build.rs index 720fbe3..e00387b 100644 --- a/ipxe/build.rs +++ b/ipxe/build.rs @@ -1,57 +1,180 @@ -use std::env; -use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::AtomicBool; -use build_print::error; +use diffy::apply; +use diffy::patch_set::{FileOperation, ParseOptions, PatchSet}; +use gix::Repository; +use gix::progress::Discard; + +const URL: &str = "https://github.com/ipxe/ipxe"; +const REVISION: &str = "2fa08affe9a1a1422766f54b2fb045234b571f06"; +const FILES: &[&str] = &["bin/ipxe.pxe", "bin-x86_64-efi/ipxe.efi"]; + +const SRC_DIR: &str = "src"; fn main() { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) .canonicalize() .unwrap(); - let src_dir = manifest_dir.join("ipxe").join("src"); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()) + .canonicalize() + .unwrap(); + let repo_dir = out_dir.join("repo"); + + let repo = checkout(&out_dir, &repo_dir); + + apply_patches(&repo, &manifest_dir, &repo_dir); + println!("cargo::rerun-if-changed=patches"); + + build(&manifest_dir, &repo_dir); + println!("cargo::rerun-if-changed=src/embed.ipxe"); + + copy_files(&repo_dir, &out_dir); +} + +fn checkout(out_dir: &Path, repo_dir: &Path) -> Repository { + // Stamp file is used instead of comparing against HEAD to guard against incomplete clones + let stamp = out_dir.join("checkout.stamp"); + if std::fs::read_to_string(&stamp).is_ok_and(|stamp| stamp == REVISION) + && let Ok(repo) = gix::open(repo_dir) + { + return repo; + } + + // Cleanup any left-overs + std::fs::remove_file(&stamp).ok(); + std::fs::remove_dir_all(repo_dir).ok(); + + std::fs::create_dir_all(repo_dir).unwrap(); + + let mut prepare = gix::prepare_clone(URL, repo_dir) + .unwrap() + .with_revision(Some(REVISION)) + .unwrap(); + + let interrupt = AtomicBool::new(false); + let (mut checkout, _) = prepare.fetch_then_checkout(Discard, &interrupt).unwrap(); + let (repo, _) = checkout.main_worktree(Discard, &interrupt).unwrap(); + + std::fs::write(&stamp, REVISION).unwrap(); + + repo +} + +fn apply_patches(repo: &Repository, manifest_dir: &Path, repo_dir: &Path) { + let commit_id = gix::ObjectId::from_hex(REVISION.as_bytes()).unwrap(); + let tree = repo.find_commit(commit_id).unwrap().tree().unwrap(); + + // Get all the patches and sort them based on name + let mut patch_files: Vec = std::fs::read_dir(manifest_dir.join("patches")) + .unwrap() + .map(|e| e.unwrap().path()) + .filter(|e| e.extension().is_some_and(|e| e == "patch")) + .collect(); + patch_files.sort(); + + let mut patched = HashMap::new(); + for path in patch_files { + let text = std::fs::read_to_string(&path).unwrap(); + let patch_set = PatchSet::parse(&text, ParseOptions::gitdiff()); + + for patch in patch_set { + let patch = patch.unwrap(); + + match patch.operation().strip_prefix(1) { + FileOperation::Modify { original, modified } => { + assert_eq!(original, modified); + let path = original.to_string(); + + // Either get the current in-memory patched file, otherwise get the prisine file + let current = patched + .remove(&path) + .unwrap_or_else(|| pristine(&tree, &path)); + + let new = apply(¤t, patch.patch().as_text().unwrap()).unwrap(); + patched.insert(path, new); + } + _ => todo!(), + } + } + } + + // If the file was previously patched, but now isn't restore the pristine file + for path in modified_files(repo) { + if !patched.contains_key(&path) { + std::fs::write(repo_dir.join(&path), pristine(&tree, &path)).unwrap(); + } + } + + // Write all the patched files + for (path, content) in &patched { + let path = repo_dir.join(path); + let content = content.as_bytes(); + + // But only if the file is actually different from the one on disk + // This prevents changing the mtime unnecessarily + if std::fs::read(&path).ok().as_deref() != Some(content) { + std::fs::write(path, content).unwrap(); + } + } +} + +fn pristine(tree: &gix::Tree<'_>, path: &str) -> String { + let entry = tree.lookup_entry_by_path(path).unwrap().unwrap(); + + String::from_utf8(entry.object().unwrap().data.clone()).unwrap() +} + +fn modified_files(repo: &gix::Repository) -> Vec { + repo.status(Discard) + .unwrap() + .untracked_files(gix::status::UntrackedFiles::None) + .into_index_worktree_iter(Vec::::new()) + .unwrap() + .map(|item| item.unwrap().rela_path().to_string()) + .collect() +} + +fn build(manifest_dir: &Path, repo_dir: &Path) { + let src_dir = repo_dir.join(SRC_DIR); let mut command = Command::new("make"); command - .stdout(Stdio::piped()) .current_dir(&src_dir) .env_remove("DEBUG") .env_remove("PROFILE") - .args([ - "-j", - &std::thread::available_parallelism() - .unwrap() - .get() - .to_string(), - ]) .arg(format!( "EMBED={}", manifest_dir .join("src") .join("embed.ipxe") - .to_string_lossy(), - )); + .to_string_lossy() + )) + .args(FILES); - let files = ["bin/ipxe.pxe", "bin-x86_64-efi/ipxe.efi"].map(PathBuf::from); - for file in &files { - command.arg(file); + if let Ok(flags) = std::env::var("CARGO_MAKEFLAGS") { + command.env("MAKEFLAGS", flags); } let output = command.output().unwrap(); + if !output.status.success() { - error!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("make failed:\n{}", String::from_utf8_lossy(&output.stderr)); } +} - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()) - .canonicalize() - .unwrap() - .join("ipxe"); +fn copy_files(repo_dir: &Path, out_dir: &Path) { + let src_dir = repo_dir.join(SRC_DIR); + let bin_dir = out_dir.join("bin"); - std::fs::remove_dir_all(&out_dir).ok(); - for file in &files { + std::fs::create_dir_all(&bin_dir).unwrap(); + for file in FILES { + let file = Path::new(file); let input = src_dir.join(file); - let output = out_dir.join(file.file_name().unwrap()); + let output = bin_dir.join(file.file_name().unwrap()); - std::fs::create_dir_all(output.parent().unwrap()).unwrap(); std::fs::copy(input, output).unwrap(); } } diff --git a/ipxe/ipxe b/ipxe/ipxe deleted file mode 160000 index 8892af6..0000000 --- a/ipxe/ipxe +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8892af68a254d36f778345d5324ec74fb8adbab8 diff --git a/ipxe/patches/.gitkeep b/ipxe/patches/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ipxe/src/lib.rs b/ipxe/src/lib.rs index 8b8b128..a74d02b 100644 --- a/ipxe/src/lib.rs +++ b/ipxe/src/lib.rs @@ -1,3 +1,3 @@ use include_dir::{Dir, include_dir}; -pub static IPXE_DIR: Dir<'_> = include_dir!("$OUT_DIR/ipxe"); +pub static IPXE_DIR: Dir<'_> = include_dir!("$OUT_DIR/bin"); diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000..8ee3a4b --- /dev/null +++ b/typos.toml @@ -0,0 +1,2 @@ +[default.extend-words] +rela = "rela" -- 2.54.0 From c183f1a5f16c64290527afc05134db8256d511c5 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Wed, 21 Oct 2026 06:46:47 +0200 Subject: [PATCH 20/20] feat: Enable HTTPS in iPXE --- ipxe/patches/0001_enable_protocols.patch | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 ipxe/patches/0001_enable_protocols.patch diff --git a/ipxe/patches/0001_enable_protocols.patch b/ipxe/patches/0001_enable_protocols.patch new file mode 100644 index 0000000..39e3e21 --- /dev/null +++ b/ipxe/patches/0001_enable_protocols.patch @@ -0,0 +1,19 @@ +diff --git a/src/config/general.h b/src/config/general.h +index 43858dceb..54cb1b657 100644 +--- a/src/config/general.h ++++ b/src/config/general.h +@@ -60,10 +60,10 @@ FILE_SECBOOT ( PERMITTED ); + //#define HTTP_ENC_PEERDIST /* PeerDist content encoding */ + + /* Disable protocols not historically included in BIOS builds */ +-#if defined ( PLATFORM_pcbios ) +- #undef DOWNLOAD_PROTO_HTTPS +- #undef HTTP_AUTH_NTLM +-#endif ++// #if defined ( PLATFORM_pcbios ) ++// #undef DOWNLOAD_PROTO_HTTPS ++// #undef HTTP_AUTH_NTLM ++// #endif + + /***************************************************************************** + * -- 2.54.0