init repo

This commit is contained in:
b1ek 2024-01-07 02:02:36 +10:00
commit b0cd77f68a
Signed by: blek
GPG Key ID: 14546221E3595D0C
11 changed files with 2602 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
/secret.key

1138
client/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
client/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "blek-heartbeat"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
obfstr = "0.4.3"
reqwest = { version = "0.11.23", features = ["blocking", "serde_json"] }
serde = "1.0.195"
serde_json = "1.0.111"
users = "0.11.0"
[profile.release]
strip = "debuginfo"
lto = true
codegen-units = 1
opt-level = "z"

58
client/src/main.rs Normal file
View File

@ -0,0 +1,58 @@
use reqwest::{blocking, Method};
use std::{result::Result, error::Error, process::exit, io::{Write, stderr}, fs, path::Path};
fn write_metrics(add: u32) -> Result<(), Box<dyn Error>> {
let user = users::get_user_by_uid(users::get_current_uid());
if let Some(user) = user.clone() {
let path = format!("/home/{}/.local/share/bHeartbeat", user.name().to_str().unwrap());
let file_path = path.clone() + "/data_spent.txt";
let _ = fs::create_dir_all(path);
if ! Path::new(&file_path).is_file() {
fs::write(&file_path, add.to_string())?;
} else {
let f = fs::File::open(&file_path)?;
let meta = f.metadata()?;
if meta.permissions().readonly() {
return Err("Can't write to the metrics file".into());
}
let mut data = fs::read_to_string(&file_path)?.parse::<u32>().unwrap_or(0);
data += add;
fs::write(&file_path, data.to_string())?;
}
} else {
return Err("No user data available. Metrics will not be saved.".into())
}
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
let key = include_bytes!("key.bin");
let cli = blocking::Client::new();
let res = cli.request(Method::POST, "https://hb.blek.codes/beat")
.body(key.to_vec())
.send()?;
if res.status() != 200 {
eprintln!("HTTP heartbeat returned {}", res.status());
exit(1);
}
let data = write_metrics((1024 * 10) + key.len() as u32);
if let Err(data) = data {
eprintln!("Can't write metrics:\n{}", data.to_string());
}
let status = res.bytes()?.to_vec();
let status = status.as_slice();
let ok_status = &status[0..2];
if ok_status != "OK".as_bytes() {
eprintln!("ERR: Server returned this:");
eprintln!("-----BEGIN BLOCK-----");
stderr().write(status)?;
eprintln!("\n-----END BLOCK-----");
}
Ok(())
}

1
client/src/secret.key Symbolic link
View File

@ -0,0 +1 @@
../../secret.key

1265
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
server/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "blek-heartbeat-server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytes = "1.5.0"
chrono = "0.4.31"
tokio = { version = "1.35.1", features = ["full"] }
warp = "0.3.6"
[profile.release]
strip = "debuginfo"
lto = true
codegen-units = 1
opt-level = "z"

100
server/src/main.rs Normal file
View File

@ -0,0 +1,100 @@
#![forbid(unsafe_code)]
use std::sync::{Mutex, Arc};
use bytes::Bytes;
use warp::{Filter, reject::{Rejection, Reject}, reply::{Reply, with_header, with_status}};
use chrono::{Local, DateTime};
#[derive(Debug, Clone, Copy)]
struct State {
pub last_heartbeat: DateTime<Local>
}
impl Default for State {
fn default() -> Self {
Self {
last_heartbeat: DateTime::UNIX_EPOCH.into()
}
}
}
#[derive(Debug, Clone)]
enum HttpReject {
String(String)
}
impl Reject for HttpReject {}
async fn beat_f(state: Arc<Mutex<State>>, body: Bytes) -> Result<Box<dyn Reply>, Rejection> {
if body != include_bytes!("secret.key").to_vec() {
return Ok(
Box::new(
with_status("Invalid key", warp::http::StatusCode::FORBIDDEN)
)
)
}
let mut state = state.lock().map_err(|x| HttpReject::String(x.to_string()))?;
state.last_heartbeat = Local::now().into();
Ok(
Box::new("OK")
)
}
async fn gif_f(state: Arc<Mutex<State>>) -> Result<Box<dyn Reply>, Rejection> {
let state = state.lock().map_err(|x| HttpReject::String(x.to_string()))?;
let online_gif = include_bytes!("me_online.gif");
let offline_gif = include_bytes!("me_offline.gif");
let duration = (Local::now() - state.last_heartbeat).num_minutes();
Ok(
Box::new(
with_header(
with_header(
{
if duration < 2 {
online_gif.to_vec()
} else {
offline_gif.to_vec()
}
},
"Content-Type",
"image/gif"
),
"X-Duration",
duration.to_string()
)
)
)
}
fn beat(state: Arc<Mutex<State>>) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path!("beat")
.and(warp::path::end())
.and(warp::post())
.map(move || state.clone())
.and(warp::body::bytes())
.and_then(beat_f)
}
fn gif(state: Arc<Mutex<State>>) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path!("gif")
.and(warp::path::end())
.and(warp::get())
.map(move || state.clone())
.and_then(gif_f)
}
#[tokio::main]
async fn main() {
let state = Arc::new(Mutex::new(State::default()));
warp::serve(
beat(state.clone())
.or(gif(state))
).run(([0, 0, 0, 0], 80)).await;
}

BIN
server/src/me_offline.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

BIN
server/src/me_online.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

1
server/src/secret.key Symbolic link
View File

@ -0,0 +1 @@
../../secret.key