[WIP] A simple wake-on-lan service

create basic webpage

vielle.dev 3e1c9770 f72d92f0

verified
+468 -1
+345
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "atomic-waker" 7 + version = "1.1.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 10 + 11 + [[package]] 12 + name = "axum" 13 + version = "0.8.8" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" 16 + dependencies = [ 17 + "axum-core", 18 + "bytes", 19 + "form_urlencoded", 20 + "futures-util", 21 + "http", 22 + "http-body", 23 + "http-body-util", 24 + "hyper", 25 + "hyper-util", 26 + "itoa", 27 + "matchit", 28 + "memchr", 29 + "mime", 30 + "percent-encoding", 31 + "pin-project-lite", 32 + "serde_core", 33 + "serde_json", 34 + "serde_path_to_error", 35 + "serde_urlencoded", 36 + "sync_wrapper", 37 + "tokio", 38 + "tower", 39 + "tower-layer", 40 + "tower-service", 41 + "tracing", 42 + ] 43 + 44 + [[package]] 45 + name = "axum-core" 46 + version = "0.5.6" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" 49 + dependencies = [ 50 + "bytes", 51 + "futures-core", 52 + "http", 53 + "http-body", 54 + "http-body-util", 55 + "mime", 56 + "pin-project-lite", 57 + "sync_wrapper", 58 + "tower-layer", 59 + "tower-service", 60 + "tracing", 61 + ] 62 + 63 + [[package]] 6 64 name = "bitflags" 7 65 version = "2.10.0" 8 66 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 37 95 ] 38 96 39 97 [[package]] 98 + name = "form_urlencoded" 99 + version = "1.2.2" 100 + source = "registry+https://github.com/rust-lang/crates.io-index" 101 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 102 + dependencies = [ 103 + "percent-encoding", 104 + ] 105 + 106 + [[package]] 107 + name = "futures-channel" 108 + version = "0.3.32" 109 + source = "registry+https://github.com/rust-lang/crates.io-index" 110 + checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" 111 + dependencies = [ 112 + "futures-core", 113 + ] 114 + 115 + [[package]] 116 + name = "futures-core" 117 + version = "0.3.32" 118 + source = "registry+https://github.com/rust-lang/crates.io-index" 119 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 120 + 121 + [[package]] 122 + name = "futures-task" 123 + version = "0.3.32" 124 + source = "registry+https://github.com/rust-lang/crates.io-index" 125 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 126 + 127 + [[package]] 128 + name = "futures-util" 129 + version = "0.3.32" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 132 + dependencies = [ 133 + "futures-core", 134 + "futures-task", 135 + "pin-project-lite", 136 + "slab", 137 + ] 138 + 139 + [[package]] 40 140 name = "hashbrown" 41 141 version = "0.16.1" 42 142 source = "registry+https://github.com/rust-lang/crates.io-index" 43 143 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 44 144 45 145 [[package]] 146 + name = "http" 147 + version = "1.4.0" 148 + source = "registry+https://github.com/rust-lang/crates.io-index" 149 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 150 + dependencies = [ 151 + "bytes", 152 + "itoa", 153 + ] 154 + 155 + [[package]] 156 + name = "http-body" 157 + version = "1.0.1" 158 + source = "registry+https://github.com/rust-lang/crates.io-index" 159 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 160 + dependencies = [ 161 + "bytes", 162 + "http", 163 + ] 164 + 165 + [[package]] 166 + name = "http-body-util" 167 + version = "0.1.3" 168 + source = "registry+https://github.com/rust-lang/crates.io-index" 169 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 170 + dependencies = [ 171 + "bytes", 172 + "futures-core", 173 + "http", 174 + "http-body", 175 + "pin-project-lite", 176 + ] 177 + 178 + [[package]] 179 + name = "httparse" 180 + version = "1.10.1" 181 + source = "registry+https://github.com/rust-lang/crates.io-index" 182 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 183 + 184 + [[package]] 185 + name = "httpdate" 186 + version = "1.0.3" 187 + source = "registry+https://github.com/rust-lang/crates.io-index" 188 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 189 + 190 + [[package]] 191 + name = "hyper" 192 + version = "1.8.1" 193 + source = "registry+https://github.com/rust-lang/crates.io-index" 194 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 195 + dependencies = [ 196 + "atomic-waker", 197 + "bytes", 198 + "futures-channel", 199 + "futures-core", 200 + "http", 201 + "http-body", 202 + "httparse", 203 + "httpdate", 204 + "itoa", 205 + "pin-project-lite", 206 + "pin-utils", 207 + "smallvec", 208 + "tokio", 209 + ] 210 + 211 + [[package]] 212 + name = "hyper-util" 213 + version = "0.1.20" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 216 + dependencies = [ 217 + "bytes", 218 + "http", 219 + "http-body", 220 + "hyper", 221 + "pin-project-lite", 222 + "tokio", 223 + "tower-service", 224 + ] 225 + 226 + [[package]] 46 227 name = "indexmap" 47 228 version = "2.13.0" 48 229 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 53 234 ] 54 235 55 236 [[package]] 237 + name = "itoa" 238 + version = "1.0.17" 239 + source = "registry+https://github.com/rust-lang/crates.io-index" 240 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 241 + 242 + [[package]] 56 243 name = "libc" 57 244 version = "0.2.182" 58 245 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 68 255 ] 69 256 70 257 [[package]] 258 + name = "log" 259 + version = "0.4.29" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 262 + 263 + [[package]] 264 + name = "matchit" 265 + version = "0.8.4" 266 + source = "registry+https://github.com/rust-lang/crates.io-index" 267 + checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 268 + 269 + [[package]] 270 + name = "memchr" 271 + version = "2.8.0" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 274 + 275 + [[package]] 276 + name = "mime" 277 + version = "0.3.17" 278 + source = "registry+https://github.com/rust-lang/crates.io-index" 279 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 280 + 281 + [[package]] 71 282 name = "mio" 72 283 version = "1.1.1" 73 284 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 79 290 ] 80 291 81 292 [[package]] 293 + name = "once_cell" 294 + version = "1.21.3" 295 + source = "registry+https://github.com/rust-lang/crates.io-index" 296 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 297 + 298 + [[package]] 82 299 name = "parking_lot" 83 300 version = "0.12.5" 84 301 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 102 319 ] 103 320 104 321 [[package]] 322 + name = "percent-encoding" 323 + version = "2.3.2" 324 + source = "registry+https://github.com/rust-lang/crates.io-index" 325 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 326 + 327 + [[package]] 105 328 name = "pin-project-lite" 106 329 version = "0.2.16" 107 330 source = "registry+https://github.com/rust-lang/crates.io-index" 108 331 checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 109 332 110 333 [[package]] 334 + name = "pin-utils" 335 + version = "0.1.0" 336 + source = "registry+https://github.com/rust-lang/crates.io-index" 337 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 338 + 339 + [[package]] 111 340 name = "proc-macro2" 112 341 version = "1.0.106" 113 342 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 135 364 ] 136 365 137 366 [[package]] 367 + name = "ryu" 368 + version = "1.0.23" 369 + source = "registry+https://github.com/rust-lang/crates.io-index" 370 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 371 + 372 + [[package]] 138 373 name = "scopeguard" 139 374 version = "1.2.0" 140 375 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 168 403 "proc-macro2", 169 404 "quote", 170 405 "syn", 406 + ] 407 + 408 + [[package]] 409 + name = "serde_json" 410 + version = "1.0.149" 411 + source = "registry+https://github.com/rust-lang/crates.io-index" 412 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 413 + dependencies = [ 414 + "itoa", 415 + "memchr", 416 + "serde", 417 + "serde_core", 418 + "zmij", 419 + ] 420 + 421 + [[package]] 422 + name = "serde_path_to_error" 423 + version = "0.1.20" 424 + source = "registry+https://github.com/rust-lang/crates.io-index" 425 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 426 + dependencies = [ 427 + "itoa", 428 + "serde", 429 + "serde_core", 171 430 ] 172 431 173 432 [[package]] ··· 180 439 ] 181 440 182 441 [[package]] 442 + name = "serde_urlencoded" 443 + version = "0.7.1" 444 + source = "registry+https://github.com/rust-lang/crates.io-index" 445 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 446 + dependencies = [ 447 + "form_urlencoded", 448 + "itoa", 449 + "ryu", 450 + "serde", 451 + ] 452 + 453 + [[package]] 183 454 name = "signal-hook-registry" 184 455 version = "1.4.8" 185 456 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 190 461 ] 191 462 192 463 [[package]] 464 + name = "slab" 465 + version = "0.4.12" 466 + source = "registry+https://github.com/rust-lang/crates.io-index" 467 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 468 + 469 + [[package]] 193 470 name = "smallvec" 194 471 version = "1.15.1" 195 472 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 215 492 "quote", 216 493 "unicode-ident", 217 494 ] 495 + 496 + [[package]] 497 + name = "sync_wrapper" 498 + version = "1.0.2" 499 + source = "registry+https://github.com/rust-lang/crates.io-index" 500 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 218 501 219 502 [[package]] 220 503 name = "thiserror" ··· 304 587 checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" 305 588 306 589 [[package]] 590 + name = "tower" 591 + version = "0.5.3" 592 + source = "registry+https://github.com/rust-lang/crates.io-index" 593 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 594 + dependencies = [ 595 + "futures-core", 596 + "futures-util", 597 + "pin-project-lite", 598 + "sync_wrapper", 599 + "tokio", 600 + "tower-layer", 601 + "tower-service", 602 + "tracing", 603 + ] 604 + 605 + [[package]] 606 + name = "tower-layer" 607 + version = "0.3.3" 608 + source = "registry+https://github.com/rust-lang/crates.io-index" 609 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 610 + 611 + [[package]] 612 + name = "tower-service" 613 + version = "0.3.3" 614 + source = "registry+https://github.com/rust-lang/crates.io-index" 615 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 616 + 617 + [[package]] 618 + name = "tracing" 619 + version = "0.1.44" 620 + source = "registry+https://github.com/rust-lang/crates.io-index" 621 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 622 + dependencies = [ 623 + "log", 624 + "pin-project-lite", 625 + "tracing-core", 626 + ] 627 + 628 + [[package]] 629 + name = "tracing-core" 630 + version = "0.1.36" 631 + source = "registry+https://github.com/rust-lang/crates.io-index" 632 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 633 + dependencies = [ 634 + "once_cell", 635 + ] 636 + 637 + [[package]] 307 638 name = "unicode-ident" 308 639 version = "1.0.24" 309 640 source = "registry+https://github.com/rust-lang/crates.io-index" 310 641 checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 642 + 643 + [[package]] 644 + name = "wake-on-lan" 645 + version = "0.2.0" 646 + source = "registry+https://github.com/rust-lang/crates.io-index" 647 + checksum = "1ccf60b60ad7e5b1b37372c5134cbcab4db0706c231d212e0c643a077462bc8f" 311 648 312 649 [[package]] 313 650 name = "wasi" ··· 414 751 name = "wol" 415 752 version = "0.1.0" 416 753 dependencies = [ 754 + "axum", 417 755 "serde", 418 756 "thiserror", 419 757 "tokio", 420 758 "toml", 759 + "wake-on-lan", 421 760 ] 761 + 762 + [[package]] 763 + name = "zmij" 764 + version = "1.0.21" 765 + source = "registry+https://github.com/rust-lang/crates.io-index" 766 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+2
Cargo.toml
··· 8 8 thiserror = "2.0.18" 9 9 tokio = { version = "1.49.0", features = ["full"] } 10 10 toml = "1.0.2" 11 + axum = "0.8.8" 12 + wake-on-lan = "0.2.0"
+6
src/mac.rs
··· 16 16 } 17 17 } 18 18 19 + impl From<MacAddress> for [u8; 6] { 20 + fn from(value: MacAddress) -> Self { 21 + value.0 22 + } 23 + } 24 + 19 25 #[derive(Error, Debug)] 20 26 pub enum MacAddressError { 21 27 #[error("Integer error: {}", .0)]
+10 -1
src/main.rs
··· 1 1 use std::path::PathBuf; 2 2 3 3 mod config; 4 + mod mac; 5 + mod server; 4 6 5 7 #[derive(thiserror::Error, Debug)] 6 8 enum Error { 7 9 #[error("Config error: {}", .0)] 8 10 Config(#[from] config::ConfigError), 11 + #[error("Io error: {}", .0)] 12 + Io(#[from] std::io::Error), 13 + #[error("Axum error: {}", .0)] 14 + Axum(#[from] axum::Error), 9 15 } 10 16 11 17 #[tokio::main] ··· 13 19 async fn main() -> Result<(), Error> { 14 20 let config = config::Config::load(PathBuf::from("./wol.toml"))?; 15 21 println!("Binding to {}", config.binding); 16 - for (k, v) in config.targets { 22 + for (k, v) in &config.targets { 17 23 println!("target: {k}: {v}"); 18 24 } 25 + let listener = tokio::net::TcpListener::bind(config.binding).await?; 26 + axum::serve(listener, server::router(config.targets)).await?; 27 + 19 28 Ok(()) 20 29 } 21 30
+29
src/server/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>WOL</title> 7 + <link rel="stylesheet" href="styles.css" /> 8 + <script> 9 + async function wake(mac) { 10 + fetch("/wake", { 11 + method: "POST", 12 + body: JSON.stringify({ mac }), 13 + headers: { 14 + "Content-Type": "application/json", 15 + }, 16 + }).catch((err) => { 17 + console.err(err); 18 + alert(err); 19 + }); 20 + } 21 + </script> 22 + </head> 23 + <body> 24 + <h1>WOL</h1> 25 + <ul> 26 + <button-template></button-template> 27 + </ul> 28 + </body> 29 + </html>
+73
src/server/mod.rs
··· 1 + use std::collections::HashMap; 2 + 3 + use axum::{ 4 + Json, Router, 5 + http::{Response, StatusCode}, 6 + response::Redirect, 7 + routing::{get, post}, 8 + }; 9 + use serde::Deserialize; 10 + 11 + use crate::mac::MacAddress; 12 + 13 + fn index(map: &HashMap<String, MacAddress>) -> String { 14 + include_str!("index.html").replace( 15 + "<button-template></button-template>", 16 + &map.into_iter() 17 + .map(|(k, v)| { 18 + format!( 19 + r#"<li><button onclick="wake('{}')">{}</button></li>"#, 20 + v.to_string(), 21 + k 22 + ) 23 + }) 24 + .collect::<Vec<_>>() 25 + .join(""), 26 + ) 27 + } 28 + 29 + #[derive(Deserialize)] 30 + pub struct WakeRequest { 31 + #[serde(deserialize_with = "crate::mac::deserialize_mac")] 32 + mac: MacAddress, 33 + } 34 + 35 + pub async fn wake(Json(req): Json<WakeRequest>) -> Result<(), Response<String>> { 36 + println!("Waking {}", req.mac); 37 + wake_on_lan::MagicPacket::new(&req.mac.into()) 38 + .send() 39 + .map_err(|err| { 40 + Response::builder() 41 + .status(500) 42 + .body(err.to_string()) 43 + // unwrap is safe since this will always be a valid respose 44 + .unwrap() 45 + }) 46 + } 47 + 48 + pub fn router(map: HashMap<String, MacAddress>) -> Router { 49 + Router::new() 50 + .route( 51 + "/", 52 + get(async move || { 53 + Response::builder() 54 + .status(200) 55 + .header("Content-Type", "text/html") 56 + .body(index(&map.clone())) 57 + // this will always be a valid response 58 + .unwrap() 59 + }), 60 + ) 61 + .route("/wake", post(wake)) 62 + .route( 63 + "/styles.css", 64 + get(async || { 65 + Response::builder() 66 + .status(200) 67 + .header("Content-Type", "text/css") 68 + .body(String::from(include_str!("styles.css"))) 69 + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) 70 + }), 71 + ) 72 + .fallback(async || Redirect::permanent("/")) 73 + }
+3
src/server/styles.css
··· 1 + :root { 2 + color-scheme: dark; 3 + }