From 7807eb0c423cd4f91d0ad44c7044b763c36910f9 Mon Sep 17 00:00:00 2001 From: Maix0 Date: Tue, 25 Jun 2024 19:25:45 +0200 Subject: [PATCH] Dumb af systemctl thingy --- .envrc | 1 + Cargo.toml | 12 ++ flake.lock | 314 ++++++++++++++++++++++++++++++++++++ flake.nix | 99 ++++++++++++ src/main.rs | 206 +++++++++++++++++++++++ src/utils/memory_db_util.rs | 58 +++++++ src/utils/mod.rs | 1 + 7 files changed, 691 insertions(+) create mode 100644 .envrc create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main.rs create mode 100644 src/utils/memory_db_util.rs create mode 100644 src/utils/mod.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..cffc922 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake . --impure diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2037680 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "botloc" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = { version = "0.7.5", features = ["multipart", "macros"] } +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.118" +tokio = { version = "1.38.0", features = ["full"] } +tracing = "0.1.40" +tracing-subscriber = "0.3.18" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1009396 --- /dev/null +++ b/flake.lock @@ -0,0 +1,314 @@ +{ + "nodes": { + "cargo-semver-checks": { + "inputs": { + "flake-utils": "flake-utils", + "naersk": "naersk", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1718446388, + "narHash": "sha256-/4a5CS5EdI+g1Y/nzOeAmI04olLDCk8SDhQbPqL82s4=", + "owner": "Maix0", + "repo": "cargo-semver-checks-flake", + "rev": "10641cbb61ecb2ec40c4847080a08c8d4ee8bec0", + "type": "github" + }, + "original": { + "owner": "Maix0", + "repo": "cargo-semver-checks-flake", + "type": "github" + } + }, + "cargo-workspace": { + "inputs": { + "cargo-workspaces-git": "cargo-workspaces-git", + "flake-utils": "flake-utils_2", + "naersk": "naersk_2", + "nixpkgs": "nixpkgs_4" + }, + "locked": { + "lastModified": 1718446390, + "narHash": "sha256-YAgg27LQwK6BfOIut/U8zqDN87L0SxU9SRC98Bsp16w=", + "owner": "Maix0", + "repo": "cargo-ws-flake", + "rev": "bbb8a52d1a294549f81a52c226fa3c085e44e5af", + "type": "github" + }, + "original": { + "owner": "Maix0", + "repo": "cargo-ws-flake", + "type": "github" + } + }, + "cargo-workspaces-git": { + "flake": false, + "locked": { + "lastModified": 1683825807, + "narHash": "sha256-WlJu8FLaTMzR5JCU5eASHfmJ3C4IdwlPZ6//m5xOb1k=", + "owner": "pksunkara", + "repo": "cargo-workspaces", + "rev": "8bf9c4ab519eeed45e83cbc64aa646431bddd899", + "type": "github" + }, + "original": { + "owner": "pksunkara", + "ref": "v0.2.41", + "repo": "cargo-workspaces", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1717067539, + "narHash": "sha256-oIs5EF+6VpHJRvvpVWuqCYJMMVW/6h59aYUv9lABLtY=", + "owner": "nix-community", + "repo": "naersk", + "rev": "fa19d8c135e776dc97f4dcca08656a0eeb28d5c0", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "naersk_2": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1717067539, + "narHash": "sha256-oIs5EF+6VpHJRvvpVWuqCYJMMVW/6h59aYUv9lABLtY=", + "owner": "nix-community", + "repo": "naersk", + "rev": "fa19d8c135e776dc97f4dcca08656a0eeb28d5c0", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 0, + "narHash": "sha256-yZKhxVIKd2lsbOqYd5iDoUIwsRZFqE87smE2Vzf6Ck0=", + "path": "/nix/store/5jgh89kgmrb687c254wxdac4cj5hqjw8-source", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1718276985, + "narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3f84a279f1a6290ce154c5531378acc827836fbb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 0, + "narHash": "sha256-yZKhxVIKd2lsbOqYd5iDoUIwsRZFqE87smE2Vzf6Ck0=", + "path": "/nix/store/5jgh89kgmrb687c254wxdac4cj5hqjw8-source", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1718276985, + "narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3f84a279f1a6290ce154c5531378acc827836fbb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_5": { + "locked": { + "lastModified": 1719223410, + "narHash": "sha256-jtIo8xR0Zp4SalIwmD+OdCwHF4l7OU6PD63UUK4ckt4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "efb39c6052f3ce51587cf19733f5f4e5d515aa13", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_6": { + "locked": { + "lastModified": 1718428119, + "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "cargo-semver-checks": "cargo-semver-checks", + "cargo-workspace": "cargo-workspace", + "flake-utils": "flake-utils_3", + "nixpkgs": "nixpkgs_5", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_6" + }, + "locked": { + "lastModified": 1719281921, + "narHash": "sha256-LIBMfhM9pMOlEvBI757GOK5l0R58SRi6YpwfYMbf4yc=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "b6032d3a404d8a52ecfc8571ff0c26dfbe221d07", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..eea8403 --- /dev/null +++ b/flake.nix @@ -0,0 +1,99 @@ +{ + description = "A basic flake with a shell"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; + cargo-workspace.url = "github:Maix0/cargo-ws-flake"; + cargo-semver-checks.url = "github:Maix0/cargo-semver-checks-flake"; + }; + outputs = { + self, + nixpkgs, + flake-utils, + rust-overlay, + ... + } @ inputs: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [(import rust-overlay)]; + }; + packageIf = name: packageDef: + if builtins.hasAttr name inputs + then [(packageDef inputs.${name})] + else []; + buildRustToolchain = toolchainDef: + if builtins.isString toolchainDef + then let + split = pkgs.lib.strings.splitString "/" toolchainDef; + toolchainType = + if (builtins.length split) > 3 + then throw "You can only specify a single version using `{type}/{version}/{profile}`" + else if (builtins.length split) >= 1 + then builtins.elemAt split 0 + else throw "You must specify at least a toolchain version with this format `{type}/{version}/{profile}`"; + toolchainVersion = + if (builtins.length split) > 3 + then throw "You can only specify a single version using `{type}/{version}/{profile}`" + else if (builtins.length split) >= 2 + then builtins.elemAt split 1 + else "latest"; + toolchainProfile = + if (builtins.length split) > 3 + then throw "You can only specify a single version using `{type}/{version}/{profile}`" + else if (builtins.length split) >= 3 + then builtins.elemAt split 2 + else "default"; + in + buildRustToolchain { + toolchain = toolchainType; + version = toolchainVersion; + profile = toolchainProfile; + } + else if builtins.isAttrs toolchainDef + then + if toolchainDef.toolchain == "stable" + then pkgs.rust-bin.stable.${toolchainDef.version}.${toolchainDef.profile}.override (builtins.removeAttrs toolchainDef ["toolchain" "version" "profile"]) + else if toolchainDef.toolchain == "beta" + then pkgs.rust-bin.beta.${toolchainDef.version}.${toolchainDef.profile}.override (builtins.removeAttrs toolchainDef ["toolchain" "version" "profile"]) + else if toolchainDef.toolchain == "nightly" + then + if toolchainDef.version == "latest" + then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.${toolchainDef.profile}.override (builtins.removeAttrs toolchainDef ["toolchain" "version" "profile"])) + else pkgs.rust-bin.nightly.${toolchainDef.version}.${toolchainDef.profile}.override (builtins.removeAttrs toolchainDef ["toolchain" "version" "profile"]) + else throw "toolchain version isn't valid (not 'stable' or 'beta' or 'nightly')" + else throw "toolchainDef isn't a string or an attr describing the toolchain"; + in { + devShell = let + rust_dev = buildRustToolchain "stable/latest/default"; + # { + # extensions = []; + # targets = ["x86_64-unknown-linux-gnu"]; + # }; + in + pkgs.mkShell { + packages = with pkgs; + [ + rust_dev + mold + openssl + pkg-config + ] + ++ (packageIf "cargo-semver-checks" (p: p.packages.${system}.default)) + ++ (packageIf "cargo-workspace" (p: p.packages.${system}.default)); + + shellHook = '' + export RUST_STD="${rust_dev}/share/doc/rust/html/std/index.html" + + if [ -z $CARGO_TARGET_DIR ] then + : "''${XDG_CACHE_HOME:="''$HOME/.cache"}" + local hash path + hash="$(sha1sum - <<< "$PWD" | head -c40)" + path="''${PWD//[^a-zA-Z0-9]/}" + export CARGO_TARGET_DIR="''${XDG_CACHE_HOME}/rust-targets/$hash-$path" + fi + ''; + }; + }); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ea3f7f0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,206 @@ +//! Run with +//! +//! ```not_rust +//! cargo run -p example-readme +//! ``` + +use axum::{ + http::StatusCode, + response::{Html, IntoResponse, Redirect}, + routing::{get, post}, + Json, Router, +}; +use serde::{Deserialize, Serialize}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tracing::{error, info}; + +#[tokio::main] +async fn main() { + // initialize tracing + tracing_subscriber::fmt::init(); + + // build our application with a route + let app = Router::new() + // `GET /` goes to `root` + .route("/", get(root)) + .route("/status", get(status)) + .route("/stop", get(stop)) + .route("/start", get(start)) + .route("/restart", get(restart)) + .route("/config", get(get_config).post(post_config)); + + // run our app with hyper + let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") + .await + .unwrap(); + tracing::debug!("listening on {}", listener.local_addr().unwrap()); + axum::serve(listener, app).await.unwrap(); +} + +// basic handler that responds with a static string +async fn root() -> Html<&'static str> { + Html( + r#" + restart
+ stop
+ start
+ status
+ config
+ "#, + ) +} + +async fn restart() -> Redirect { + info!("Requested to restart the bot"); + tokio::spawn(async { + tokio::process::Command::new("systemctl") + .args(["--user", "restart", "botloc.service"]) + .spawn() + .unwrap() + }); + Redirect::to("/") +} + +async fn start() -> Redirect { + info!("Requested to start the bot"); + tokio::spawn(async { + tokio::process::Command::new("systemctl") + .args(["--user", "start", "botloc.service"]) + .spawn() + .unwrap() + }); + Redirect::to("/") +} + +async fn stop() -> Redirect { + info!("Requested to stop the bot"); + tokio::spawn(async { + tokio::process::Command::new("systemctl") + .args(["--user", "stop", "botloc.service"]) + .spawn() + .unwrap() + }); + Redirect::to("/") +} + +#[derive(Serialize, Deserialize)] +struct BotConfig { + piscine: Vec, +} + +async fn get_config() -> Result, (StatusCode, String)> { + info!("Requested config"); + let Ok(mut file) = tokio::fs::File::open(std::env::var("CONFIG_PATH").map_err(|e| { + error!("Failed to open config file: {e}"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "please set env CONFIG_PATH".to_string(), + ) + })?) + .await + .map_err(|e| { + error!("Failed to open config file: {e}"); + e + }) else { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!( + "failed to open config file: {}", + std::env::var("CONFIG_PATH").unwrap() + ), + )); + }; + + let mut s = String::new(); + if file + .read_to_string(&mut s) + .await + .map_err(|e| { + error!("Failed to open config file: {e}"); + e + }) + .is_err() + { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!( + "Failed to read config file at {}", + std::env::var("CONFIG_PATH").unwrap() + ), + )); + }; + let Ok(val) = serde_json::from_str::(&s).map_err(|e| { + error!("Failed to open config file: {e}"); + e + }) else { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!( + "Failed to read config file as json at {}", + std::env::var("CONFIG_PATH").unwrap() + ), + )); + }; + Ok(Json(val)) +} + +async fn post_config(Json(body): Json) -> (StatusCode, String) { + info!("Posted config"); + let Ok(mut file) = tokio::fs::File::open( + match std::env::var("CONFIG_PATH").map_err(|e| { + error!("Unset env {e}"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "please set env CONFIG_PATH".to_string(), + ) + }) { + Ok(v) => v, + Err(e) => return e, + }, + ) + .await + .map_err(|e| { + error!("Failed to open config file: {e}"); + e + }) else { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!( + "failed to open config file: {}", + std::env::var("CONFIG_PATH").unwrap() + ), + ); + }; + + match serde_json::to_string_pretty(&body) { + Err(e) => { + error!("Failed to convert to json {e}"); + return (StatusCode::OK, "Done".to_string()); + } + Ok(s) => match file.write(s.as_bytes()).await { + Err(e) => error!("Got an error when writing file: {e}"), + Ok(_) => (), + }, + } + + (StatusCode::OK, "Done".to_string()) +} + +async fn status() -> Result { + info!("Requested status"); + let mut output = tokio::process::Command::new("systemctl") + .args(["--user", "status", "botloc.service"]) + .output() + .await + // let mut output = child.wait_with_output().await + .map_err(|e| { + error!("Error with systemctl status {e}"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + output.stdout.push(b'\n'); + output.stdout.append(&mut output.stderr); + String::from_utf8(output.stdout).map_err(|e| { + error!("Error with systemctl status output {e}"); + StatusCode::INTERNAL_SERVER_ERROR + }) +} diff --git a/src/utils/memory_db_util.rs b/src/utils/memory_db_util.rs new file mode 100644 index 0000000..6f5a5fe --- /dev/null +++ b/src/utils/memory_db_util.rs @@ -0,0 +1,58 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, + thread::spawn, + time::SystemTime, +}; + +#[derive(Clone)] +pub struct AxumState { + db: Arc>>, +} + +#[derive(Clone, Debug)] +pub struct ItemOauthAxum { + pub verifier: String, + pub created_at: SystemTime, +} + +impl AxumState { + pub fn new() -> Self { + let db: Arc>> = Arc::new(Mutex::new(HashMap::new())); + let db_binding = Arc::clone(&db); + spawn(move || loop { + std::thread::sleep(std::time::Duration::from_secs(10)); + let mut db = db_binding.lock().unwrap(); + let now = SystemTime::now(); + db.retain(|_, item| now.duration_since(item.created_at).unwrap().as_secs() < 900); + }); + AxumState { + db: Arc::clone(&db), + } + } + + pub fn get(&self, key: String) -> Option { + let db = self.db.lock().unwrap(); + if let Some(item) = db.get(&key) { + Some(item.verifier.clone()) + } else { + None + } + } + + pub fn set(&self, key: String, value: String) { + let mut db = self.db.lock().unwrap(); + db.insert( + key, + ItemOauthAxum { + verifier: value, + created_at: SystemTime::now(), + }, + ); + } + + pub fn get_all_items(&self) -> Vec { + let db = self.db.lock().unwrap(); + db.values().cloned().collect::>() + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..d7cc83f --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod memory_db_utils;