Restructure some of the folder structure in unshell-server

This commit is contained in:
Michael Mikovsky
2025-12-20 18:19:08 -07:00
parent c8cfa685ec
commit 338eb93bfc
18 changed files with 98 additions and 198 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ crate-type = ["cdylib"]
[features] [features]
log_debug = ["unshell-lib/log_debug"] log_debug = ["unshell-lib/log_debug"]
obfuscate = ["unshell-lib/obfuscate"] obfuscate = ["unshell-lib/obfuscate", "unshell-obfuscate/obfuscate"]
[dependencies] [dependencies]
unshell-lib = {path = "../../unshell-lib", default-featues = false} unshell-lib = {path = "../../unshell-lib", default-featues = false}
+6 -2
View File
@@ -1,3 +1,5 @@
cargo clean
OBFUSCATION_KEY=abc123abc \ OBFUSCATION_KEY=abc123abc \
cargo build --release cargo build --release
@@ -10,8 +12,10 @@ declare -a headers=(
".shstrtab" #- Section header string table (only needed by tools like readelf) ".shstrtab" #- Section header string table (only needed by tools like readelf)
".note.gnu.bu" ".note.gnu.build-id" # - Build ID note ".note.gnu.bu" ".note.gnu.build-id" # - Build ID note
".eh_frame" ".eh_frame_hdr" # Exception handling info (can break C++ exceptions if removed) ".eh_frame" ".eh_frame_hdr" # Exception handling info (can break C++ exceptions if removed)
".gnu.version" ".gnu.version_r" # Symbol versioning (may be needed for some shared libraries) ".gnu.version"
".gnu.hash" # Hash table for symbol lookup optimization #".gnu.version_r"
# Symbol versioning (may be needed for some shared libraries)
#".gnu.hash" # Hash table for symbol lookup optimization
) )
# TODO: Implement FAKE section header comments and information # TODO: Implement FAKE section header comments and information
+2
View File
@@ -15,6 +15,8 @@ use unshell_lib::{
use crate::client_runtime::ClientRuntime; use crate::client_runtime::ClientRuntime;
pub use unshell_lib::logger::setup_logger;
pub extern "C" fn test1() { pub extern "C" fn test1() {
warn!("Test1 called xxxxxxxxxxx"); warn!("Test1 called xxxxxxxxxxx");
} }
-2
View File
@@ -1,7 +1,5 @@
use bincode::{Decode, Encode}; use bincode::{Decode, Encode};
use crate::config::RuntimeConfig;
/// Mostly temporary server message type /// Mostly temporary server message type
#[derive(Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub enum Announcement { pub enum Announcement {
+1 -1
View File
@@ -3,7 +3,7 @@ use std::{collections::HashMap, fmt::Debug};
// use bincode::{Decode, Encode}; // use bincode::{Decode, Encode};
// use serde::{Deserialize, Serialize}; // use serde::{Deserialize, Serialize};
use bincode::{Decode, Encode}; // use bincode::{Decode, Encode};
use crate::{ModuleError, ModuleRuntime}; use crate::{ModuleError, ModuleRuntime};
@@ -5,12 +5,11 @@ impl Manager {
match announcement { match announcement {
Announcement::TestAnnouncement(str) => { Announcement::TestAnnouncement(str) => {
println!("Got test announcement: {}", str) println!("Got test announcement: {}", str)
} } // Announcement::GetRuntimes => todo!(),
// Announcement::GetRuntimes => todo!(), // Announcement::GetRuntimesAck(_) => todo!(),
// Announcement::GetRuntimesAck(_) => todo!(), // Announcement::StartRuntime(runtime_config) => todo!(),
// Announcement::StartRuntime(runtime_config) => todo!(), // Announcement::StartRuntimeAck(_) => todo!(),
// Announcement::StartRuntimeAck(_) => todo!(), // _ => {}
_ => {}
} }
} }
} }
+7 -7
View File
@@ -22,10 +22,10 @@ unshell-lib = {path = "../unshell-lib", default-features = false}
unshell-obfuscate = {path = "../unshell-obfuscate"} unshell-obfuscate = {path = "../unshell-obfuscate"}
[profile.release] [profile.release]
strip = true # Strip symbols from the binary # strip = true # Strip symbols from the binary
opt-level = "z" # Optimize for size # opt-level = "z" # Optimize for size
lto = true # Link tree optimization # lto = true # Link tree optimization
codegen-units = 1 # codegen-units = 1
panic = "abort" # panic = "abort"
debug = false # Remove debug # debug = false # Remove debug
trim-paths="all" # trim-paths="all"
+9 -6
View File
@@ -3,9 +3,12 @@
# cargo run --no-default-features $@ --release # $(ls ../*/target/release/*.so) # cargo run --no-default-features $@ --release # $(ls ../*/target/release/*.so)
OBFUSCATION_KEY=abc123abc \ OBFUSCATION_KEY=abc123abc \
RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none" \
cargo +nightly build \ cargo build $@ --profile release
-Z build-std=std,panic_abort \
-Z build-std-features="optimize_for_size" \ # RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none" \
$@ \ # cargo +nightly build \
--profile release # -Z build-std=std,panic_abort \
# -Z build-std-features="optimize_for_size" \
# $@ \
# --profile release
+37 -30
View File
@@ -1,10 +1,10 @@
use std::collections::HashMap; use std::{any::Any, collections::HashMap, fs::File, io::Read};
use static_init::dynamic; use static_init::dynamic;
use unshell_lib::{ use unshell_lib::{
ModuleError, ModuleError,
config::{PayloadConfig, RuntimeConfig}, config::{PayloadConfig, RuntimeConfig},
module::Manager, module::{Manager, Module},
}; };
use unshell_obfuscate::{obs, symbol}; use unshell_obfuscate::{obs, symbol};
@@ -34,36 +34,43 @@ fn main() {
debug!("Initialized"); debug!("Initialized");
match || -> Result<(), ModuleError> { match run() {
// let args = std::env::args();
// TEMPORARY, load the module paths from command line args.
// let mut modules = Vec::new();
// for arg in args.skip(1) {
// debug!("Loading module: {}", arg);
// let mut file = File::open(arg).map_err(|e| ModuleError::Error(e.to_string().into()))?;
// let mut buffer = Vec::new();
// file.read_to_end(&mut buffer)
// .map_err(|e| ModuleError::Error(e.to_string().into()))?;
// modules.push(Module::new_bytes(&buffer)?)
// // modules.push(Module::new(&arg)?)
// }
// let modules = vec
// Run the manager, this is blocking.
let manager = Manager::start(&PAYLOAD_CONFIG, Vec::new());
Manager::join(manager);
Ok(())
}() {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
debug!("ERROR! {:?}", e); error!("ERROR! '{:?}'", e);
} }
} }
} }
fn run() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args();
// TEMPORARY, load the module paths from command line args.
let mut modules = Vec::new();
for arg in args.skip(1) {
// debug!("Loading module: {}", arg);
// let mut file = File::open(arg).map_err(|e| ModuleError::Error(e.to_string().into()))?;
// let mut buffer = Vec::new();
// file.read_to_end(&mut buffer)
// .map_err(|e| ModuleError::Error(e.to_string().into()))?;
debug!("Initializing module: {}", arg);
let module = Module::new(&arg)?;
modules.push(module);
// modules.push(Module::new(&arg)?)
}
// let modules = vec
debug!("Starting manager...");
// Run the manager, this is blocking.
let manager = Manager::start(&PAYLOAD_CONFIG, modules);
Manager::join(manager);
Ok(())
}
@@ -9,11 +9,7 @@ use unshell_lib::{debug, info};
// axum_extra:: // axum_extra::
use crate::{ use crate::{auth, auth::structs::CurrentUser, logger::Logger, server::Server};
api::{auth, structs::CurrentUser},
logger::Logger,
server::Server,
};
macro_rules! route_get { macro_rules! route_get {
($router:expr, $path:expr, $func:expr) => {{ ($router:expr, $path:expr, $func:expr) => {{
-20
View File
@@ -1,20 +0,0 @@
use chrono::Duration;
use jsonwebtoken::{DecodingKey, EncodingKey};
use static_init::dynamic;
extern crate unshell_lib;
pub mod app;
mod auth;
mod structs;
pub use structs::CurrentUser;
static EXPIRE_DURATION: Duration = Duration::hours(12);
#[dynamic]
static JWT_SECRET: String = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
#[dynamic]
static JWT_ENCODING_KEY: EncodingKey = EncodingKey::from_secret(JWT_SECRET.as_bytes());
#[dynamic]
static JWT_DECODING_KEY: DecodingKey = DecodingKey::from_secret(JWT_SECRET.as_bytes());
@@ -1,3 +1,5 @@
pub mod structs;
use axum::{ use axum::{
body::Body, body::Body,
extract::{Json, Request}, extract::{Json, Request},
@@ -10,10 +12,9 @@ use jsonwebtoken::{Header, TokenData, Validation, decode, encode};
use serde_json::{Value, json}; use serde_json::{Value, json};
use unshell_lib::{debug, info}; use unshell_lib::{debug, info};
use crate::api::{ use crate::{EXPIRE_DURATION, JWT_DECODING_KEY, JWT_ENCODING_KEY};
EXPIRE_DURATION, JWT_DECODING_KEY, JWT_ENCODING_KEY,
structs::{AuthError, Cliams, CurrentUser, SignInData}, use structs::{AuthError, Cliams, CurrentUser, SignInData};
};
pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> { pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
let hash = hash(password, DEFAULT_COST)?; let hash = hash(password, DEFAULT_COST)?;
-5
View File
@@ -1,8 +1,4 @@
mod blob; mod blob;
// pub mod interface;
pub use blob::Blob;
// pub use interface::InterfaceWrapper;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@@ -27,7 +23,6 @@ struct ComponentMetadata {
// Other components that can be pointed to by this component // Other components that can be pointed to by this component
#[serde(default)] #[serde(default)]
child_components: Vec<PathBuf>, child_components: Vec<PathBuf>,
// config: Option<HashMap<String, ConfigStructField>>,
} }
#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)] #[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
+18 -4
View File
@@ -1,15 +1,14 @@
// #![macro_use]
mod api; mod api;
mod auth;
mod config; mod config;
pub mod logger; pub mod logger;
mod modules; mod modules;
mod server; mod server;
pub use api::app::start_api;
pub use server::Server; pub use server::Server;
use static_init::dynamic;
#[static_init::dynamic] #[static_init::dynamic]
pub static DATABASE_TREES: Vec<&'static str> = vec!["users"]; pub static DATABASE_TREES: Vec<&'static str> = vec!["users"];
@@ -24,3 +23,18 @@ pub static SERVER_CONFIG: unshell_lib::config::PayloadConfig = unshell_lib::conf
components: Vec::new(), components: Vec::new(),
runtime_config: Vec::new(), runtime_config: Vec::new(),
}; };
// Constants for server config
pub use api::start_api;
use chrono::Duration;
use jsonwebtoken::{DecodingKey, EncodingKey};
static EXPIRE_DURATION: Duration = Duration::hours(12);
#[dynamic]
static JWT_SECRET: String = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
#[dynamic]
static JWT_ENCODING_KEY: EncodingKey = EncodingKey::from_secret(JWT_SECRET.as_bytes());
#[dynamic]
static JWT_DECODING_KEY: DecodingKey = DecodingKey::from_secret(JWT_SECRET.as_bytes());
+4 -101
View File
@@ -1,42 +1,33 @@
use axum::extract::{Path, State}; use axum::extract::{Path, State};
use axum::{Extension, Json}; use axum::{Extension, Json};
use chrono::Local; use chrono::Local;
use unshell_lib::debug;
// use lazy_static::lazy_static;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf; use std::path::PathBuf;
use unshell_lib::debug;
use crate::Server; use crate::Server;
use crate::api::CurrentUser; use crate::auth::structs::CurrentUser;
// --- Constants ---
/// The directory where log files will be stored.
const LOG_DIR: &str = "logs"; const LOG_DIR: &str = "logs";
/// The maximum number of logs to return in one call to `poll_logs`.
const LOG_COUNT: usize = 100; const LOG_COUNT: usize = 100;
/// The full path to the log file. /// The full path to the log file.
/// Initialized once based on the startup time. /// Initialized once based on the startup time.
#[static_init::dynamic] #[static_init::dynamic]
static LOG_FILE_PATH: PathBuf = { static LOG_FILE_PATH: PathBuf = {
// 1. Determine the log directory path
let log_dir_path = PathBuf::from(LOG_DIR); let log_dir_path = PathBuf::from(LOG_DIR);
// 2. Create the log directory if it does not exist
if let Err(e) = fs::create_dir_all(&log_dir_path) { if let Err(e) = fs::create_dir_all(&log_dir_path) {
eprintln!("Error creating log directory {:?}: {}", log_dir_path, e); eprintln!("Error creating log directory {:?}: {}", log_dir_path, e);
// Panic or handle error as appropriate for your application's needs
// Panicking here to ensure the logger can't be used if the path is invalid/unwritable
panic!("Failed to initialize log directory."); panic!("Failed to initialize log directory.");
} }
// 3. Generate the unique filename based on the current local time
let now = Local::now(); let now = Local::now();
let filename = format!("{}.log", now.format("%Y%m%d_%H%M%S")); let filename = format!("{}.log", now.format("%Y%m%d_%H%M%S"));
// 4. Combine the directory path and the filename
log_dir_path.join(filename) log_dir_path.join(filename)
}; };
@@ -44,19 +35,9 @@ static LOG_FILE_PATH: PathBuf = {
pub struct Logger; pub struct Logger;
impl Logger { impl Logger {
/// Writes a log entry to the current log file.
///
/// The log entry includes a timestamp and the provided message.
///
/// # Arguments
///
/// * `message` - The string content of the log entry.
pub fn log(message: String) { pub fn log(message: String) {
// 1. Format the complete log line with timestamp
let log_line = format!("{}\n", message); let log_line = format!("{}\n", message);
// 2. Open the file in append mode, creating it if it doesn't exist.
// The file path is guaranteed to be valid due to the `lazy_static` initialization.
match OpenOptions::new() match OpenOptions::new()
.create(true) .create(true)
.append(true) .append(true)
@@ -74,65 +55,38 @@ impl Logger {
} }
} }
/// Reads and returns the most recent logs from the file.
///
/// The total number of logs returned is limited by `LOG_COUNT`.
/// The `offset` determines how far back in history to start reading.
///
/// # Arguments
///
/// * `offset` - The number of most recent logs to skip.
///
/// # Returns
///
/// A `Vec<String>` containing the logs, or an empty `Vec` on failure.
pub fn poll_logs(offset: usize) -> Vec<String> { pub fn poll_logs(offset: usize) -> Vec<String> {
// Array of String is not a idiomatic return type in Rust,
// so we return a Vector, which serves the same purpose.
// The size constraint (LOG_COUNT) is applied internally.
match File::open(&*LOG_FILE_PATH) { match File::open(&*LOG_FILE_PATH) {
Ok(file) => { Ok(file) => {
let reader = BufReader::new(file); let reader = BufReader::new(file);
// Collect all lines into a vector
let lines: Vec<String> = reader let lines: Vec<String> = reader
.lines() .lines()
.filter_map(|line| line.ok()) // Ignore lines that fail to read .filter_map(|line| line.ok()) // Ignore lines that fail to read
.collect(); .collect();
// Determine the starting index for the slice (from the end of the vector)
// This logic correctly handles the offset and LOG_COUNT limits.
let total_lines = lines.len(); let total_lines = lines.len();
if offset >= total_lines { if offset >= total_lines {
// Offset is past the beginning of the file, return nothing
return Vec::new(); return Vec::new();
} }
// Start from the end, minus the offset, minus the number of logs to read.
// We use checked subtraction to prevent panic if it would result in a negative number.
let start_index = total_lines let start_index = total_lines
.checked_sub(offset) .checked_sub(offset)
.and_then(|i| i.checked_sub(LOG_COUNT)) .and_then(|i| i.checked_sub(LOG_COUNT))
.unwrap_or(0); // If either subtraction fails, start from 0 .unwrap_or(0);
// End index is determined by subtracting the offset from the total length.
let end_index = total_lines.checked_sub(offset).unwrap_or(total_lines); let end_index = total_lines.checked_sub(offset).unwrap_or(total_lines);
// Get the slice of the lines
let slice = &lines[start_index..end_index]; let slice = &lines[start_index..end_index];
slice.iter().cloned().collect() slice.iter().cloned().collect()
} }
Err(e) => { Err(e) => {
// Return an empty vector and print an error message if the file cannot be read
eprintln!("Error reading log file {:?}: {}", *LOG_FILE_PATH, e); eprintln!("Error reading log file {:?}: {}", *LOG_FILE_PATH, e);
Vec::new() Vec::new()
} }
} }
} }
// Route the "keys" api for each tree
pub async fn poll_logs_api( pub async fn poll_logs_api(
State(_): State<Server>, State(_): State<Server>,
Extension(_): Extension<CurrentUser>, Extension(_): Extension<CurrentUser>,
@@ -144,54 +98,3 @@ impl Logger {
Json(serde_json::to_value(result).unwrap()) Json(serde_json::to_value(result).unwrap())
} }
} }
// --- Example Usage ---
// fn main() {
// // Write some logs
// Logger::log("Application started.".to_string());
// Logger::log("Configuration loaded.".to_string());
// for i in 1..=20 {
// Logger::log(format!("Processing request #{}", i));
// }
// Logger::log("Task completed.".to_string());
// // --- Test 1: Get the 10 most recent logs (offset 0) ---
// println!("--- Most Recent Logs (LOG_COUNT={}) ---", LOG_COUNT);
// let recent_logs = Logger::poll_logs(0);
// for log in &recent_logs {
// println!("{}", log);
// }
// // The output should be:
// // [timestamp] Task completed.
// // [timestamp] Processing request #20
// // [timestamp] Processing request #19
// // ...
// // [timestamp] Processing request #12
// println!(
// "\n--- Logs from the past (Offset 15, LOG_COUNT={}) ---",
// LOG_COUNT
// );
// // --- Test 2: Skip the 15 most recent logs, then get the next 10 ---
// let historical_logs = Logger::poll_logs(15);
// for log in &historical_logs {
// println!("{}", log);
// }
// // The output should be:
// // [timestamp] Processing request #6
// // [timestamp] Processing request #5
// // ...
// // [timestamp] Application started.
// println!("\n--- Testing a high offset (Offset 100) ---");
// // --- Test 3: Test an offset that goes beyond the log file length ---
// let empty_logs = Logger::poll_logs(100);
// if empty_logs.is_empty() {
// println!("Correctly returned empty set for high offset.");
// }
// }
+1 -1
View File
@@ -8,7 +8,7 @@ use serde_json::Value;
use sled::Tree; use sled::Tree;
use unshell_lib::{debug, error}; use unshell_lib::{debug, error};
use crate::{api::CurrentUser, server::Server}; use crate::{auth::structs::CurrentUser, server::Server};
impl Server { impl Server {
fn get_tree(&self, tree_name: &str) -> Result<Tree, String> { fn get_tree(&self, tree_name: &str) -> Result<Tree, String> {
+1 -3
View File
@@ -1,5 +1,3 @@
use std::collections::HashMap;
use axum::{ use axum::{
Extension, Json, Extension, Json,
extract::{Path, State}, extract::{Path, State},
@@ -8,7 +6,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use unshell_lib::debug; use unshell_lib::debug;
use crate::{Server, api::CurrentUser}; use crate::{Server, auth::structs::CurrentUser};
pub trait Tree { pub trait Tree {
fn is_folder() -> bool { fn is_folder() -> bool {