🎉 init

This commit is contained in:
JMARyA 2025-04-28 18:44:09 +02:00
commit 812c4adb15
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
16 changed files with 4631 additions and 0 deletions

8
src/api.rs Normal file
View file

@ -0,0 +1,8 @@
use owl::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct JoinParams {
pub join_token: Option<String>,
pub machine_id: String,
pub hostname: String,
}

54
src/server.rs Normal file
View file

@ -0,0 +1,54 @@
use axum::{
Json, Router,
http::StatusCode,
routing::{get, post},
};
use axum_client_ip::{ClientIp, ClientIpSource};
use based::auth::{Sessions, User};
use owl::{prelude::*, save, set_global_db};
use rand::RngCore;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::net::SocketAddr;
mod api;
mod server_core;
use server_core::route::{join_device, login_user};
fn generate_token() -> String {
let mut rng = rand::rng();
let mut token = vec![0u8; 32];
rng.fill_bytes(&mut token);
hex::encode(token)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let db = Database::in_memory();
set_global_db!(db);
User::create("admin".to_string(), "admin", based::auth::UserRole::Admin)
.await
.unwrap();
let device = Router::new()
.route("/join", post(join_device))
.layer(ClientIpSource::ConnectInfo.into_extension()); // Direct IP
// .layer(ClientIpSource::XRealIp.into_extension()) // Proxy
let user = Router::new().route("/login", post(login_user));
let app = Router::new().merge(device).merge(user);
log::info!("Starting server");
let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
}

10
src/server_core/config.rs Normal file
View file

@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Config {}
impl Default for Config {
fn default() -> Self {
toml::from_str(&std::fs::read_to_string("./config.toml").unwrap()).unwrap()
}
}

3
src/server_core/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod config;
pub mod model;
pub mod route;

22
src/server_core/model.rs Normal file
View file

@ -0,0 +1,22 @@
use crate::api;
use crate::generate_token;
use owl::prelude::*;
#[model]
pub struct Machine {
pub id: Id,
pub hostname: String,
pub token: String,
pub next_token: Option<String>,
}
impl Machine {
pub fn from_join_param(join: api::JoinParams) -> Self {
Self {
id: Id::String(join.machine_id),
hostname: join.hostname.trim().to_string(),
token: generate_token(),
next_token: None,
}
}
}

48
src/server_core/route.rs Normal file
View file

@ -0,0 +1,48 @@
use crate::api;
use crate::server_core::model::Machine;
use axum::Json;
use axum::http::StatusCode;
use axum_client_ip::ClientIp;
use based::auth::Sessions;
use based::auth::User;
use owl::save;
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
pub struct LoginParam {
username: String,
password: String,
}
pub async fn login_user(Json(payload): Json<LoginParam>) -> (StatusCode, Json<serde_json::Value>) {
log::info!("Login attempt for {}", payload.username);
let u = User::find(&payload.username).await.unwrap();
if u.read().verify_pw(&payload.password) {
let ses = u.read().session().await;
(StatusCode::OK, Json(json!({"token": ses.read().token})))
} else {
(StatusCode::FORBIDDEN, Json(json!({"error": "invalid"})))
}
}
pub async fn join_device(
ClientIp(ip): ClientIp,
Json(payload): Json<api::JoinParams>,
) -> (StatusCode, Json<serde_json::Value>) {
// TODO : check if exists already
// TODO : validate join token
log::info!(
"New device joined: {} [{}]",
payload.hostname.trim(),
payload.machine_id
);
let machine = Machine::from_join_param(payload);
let new_token = machine.token.clone();
save!(machine);
(StatusCode::OK, Json(json!({"ok": new_token})))
}

18
src/sheepd.rs Normal file
View file

@ -0,0 +1,18 @@
use sheepd_core::args::{SheepdArgs, SheepdCommand};
mod api;
mod sheepd_core;
fn main() {
tracing_subscriber::fmt::init();
let args: SheepdArgs = argh::from_env();
if let Some(command) = args.command {
match command {
SheepdCommand::Join(join_command) => sheepd_core::cmd::join(join_command),
}
} else {
log::info!("Starting sheepd");
// TODO : daemon loop
}
}

23
src/sheepd_core/args.rs Normal file
View file

@ -0,0 +1,23 @@
use argh::FromArgs;
#[derive(FromArgs)]
/// Sheep Daemon
pub struct SheepdArgs {
#[argh(subcommand)]
pub command: Option<SheepdCommand>,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
pub enum SheepdCommand {
Join(JoinCommand),
}
#[derive(FromArgs, PartialEq, Debug)]
/// Join a herd
#[argh(subcommand, name = "join")]
pub struct JoinCommand {
#[argh(positional)]
/// home server domain
pub home: String,
}

37
src/sheepd_core/cmd.rs Normal file
View file

@ -0,0 +1,37 @@
use crate::{api, sheepd_core::config::AgentConfig};
use super::args::JoinCommand;
pub fn join(conf: JoinCommand) {
// TODO : check for root
// TODO : check if joined somewhere already
log::info!("Joining to {}", conf.home);
let url = format!("http://{}/join", conf.home);
println!("{url}");
let mut res = ureq::post(url)
.send_json(&api::JoinParams {
join_token: None,
machine_id: std::fs::read_to_string("/etc/machine-id").unwrap(),
hostname: std::fs::read_to_string("/etc/hostname").unwrap(),
})
.unwrap();
let res: serde_json::Value = res.body_mut().read_json().unwrap();
let token = res
.as_object()
.unwrap()
.get("ok")
.unwrap()
.as_str()
.unwrap();
log::info!("Joined {} successfully", conf.home);
std::fs::write(
"/etc/sheepd.toml",
toml::to_string(&AgentConfig::new(&conf.home, token)).unwrap(),
)
.unwrap();
}

16
src/sheepd_core/config.rs Normal file
View file

@ -0,0 +1,16 @@
use owl::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct AgentConfig {
pub home: String,
pub token: String,
}
impl AgentConfig {
pub fn new(home_server: &str, token: &str) -> Self {
Self {
home: home_server.to_string(),
token: token.to_string(),
}
}
}

3
src/sheepd_core/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod args;
pub mod cmd;
pub mod config;