This commit is contained in:
DarkCat09 2025-03-26 14:39:50 +04:00
commit c5300b9912
Signed by: DarkCat09
SSH key fingerprint: SHA256:SnH1iYY/ogxxRmt1I6piy+aVUUzrOWvfksQWfhZ8JUM
5 changed files with 1173 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1014
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "rpictrl"
version = "0.1.0"
edition = "2024"
[dependencies]
ntex = { version = "2.12.3", default-features = false, features = ["tokio"] }
tokio = { version = "1.44.1", features = ["fs", "process"] }
[profile.release]
panic = "abort"
codegen-units = 1
lto = "fat"
strip = "symbols"

102
src/main.rs Normal file
View file

@ -0,0 +1,102 @@
use ntex::{
http::StatusCode,
web::{self, App, HttpServer, Responder, WebResponseError},
};
type Result<T> = core::result::Result<T, ErrorWrapper>;
#[ntex::main]
async fn main() -> std::io::Result<()> {
let bind = std::env::var("BIND").unwrap_or("10.3.141.1:5544".to_owned());
HttpServer::new(|| {
App::new()
.service(root)
.service(poweroff)
.service(ping)
.service(clients)
.service(temp)
})
.bind(bind)?
.run()
.await
}
#[web::get("/")]
async fn root() -> impl Responder {
include_bytes!("../static/index.html")
.with_header("content-type", "text/html")
.with_status(StatusCode::OK)
}
#[web::get("/off")]
async fn poweroff() -> Result<impl Responder> {
tokio::process::Command::new("/sbin/poweroff")
.kill_on_drop(false)
.spawn()?;
Ok("OK")
}
#[web::get("/ping")]
async fn ping() -> Result<impl Responder> {
let out = tokio::process::Command::new("/bin/ping")
.arg("-c")
.arg("4")
.arg("1.1.1.1")
.output()
.await?;
Ok(format!(
"exited with {}\n\nstdout:\n{}\n----\n\nstderr:\n{}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
))
}
#[web::get("/ips")]
async fn clients() -> Result<impl Responder> {
tokio::fs::read_to_string("/var/lib/misc/dnsmasq.leases")
.await
.map_err(Into::into)
}
#[web::get("/temp")]
async fn temp() -> Result<impl Responder> {
let out = tokio::process::Command::new("/usr/bin/vcgencmd")
.arg("measure_temp")
.output()
.await?;
Ok(String::from_utf8(out.stdout)?)
}
#[derive(Debug)]
struct ErrorWrapper(std::io::Error);
impl From<std::io::Error> for ErrorWrapper {
#[inline]
fn from(value: std::io::Error) -> Self {
Self(value)
}
}
impl From<std::string::FromUtf8Error> for ErrorWrapper {
fn from(value: std::string::FromUtf8Error) -> Self {
Self(std::io::Error::new(std::io::ErrorKind::InvalidData, value))
}
}
impl std::fmt::Display for ErrorWrapper {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl WebResponseError for ErrorWrapper {
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
fn error_response(&self, _: &web::HttpRequest) -> web::HttpResponse {
format!("{:?}", self.0).into()
}
}

42
static/index.html Normal file
View file

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RPi Ctrl</title>
<style>
body {
margin: 0;
padding: 0.5rem;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #222;
color: #eee;
font-family: system-ui, sans-serif;
}
a {
display: block;
margin-bottom: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #3684d3;
border-radius: 0.25rem;
text-decoration: none;
color: #eee;
}
a:hover {
background: #3684d3;
}
</style>
</head>
<body>
<a href="/off">Power off</a>
<a href="/ping">Ping 1.1.1.1</a>
<a href="/ips">List IPs</a>
<a href="/temp">Get CPU temp</a>
</body>
</html>