Added Windows service mode!

This commit is contained in:
Revertron
2023-06-08 00:07:15 +02:00
parent 09303149d9
commit aa500b3ad8
4 changed files with 369 additions and 76 deletions
+141 -55
View File
@@ -6,7 +6,7 @@
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{env, thread};
use std::{env, fs, thread};
use getopts::{Matches, Options};
#[allow(unused_imports)]
@@ -20,17 +20,23 @@ use std::fs::{File, OpenOptions};
use std::io::{Seek, SeekFrom, Write};
use std::process::exit;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use alfis::event::Event;
use alfis::eventbus::{post, register};
use alfis::keystore::create_key;
use alfis::{dns_utils, Block, Bytes, Chain, Context, Keystore, Miner, Network, Settings, Transaction, ALFIS_DEBUG, ALFIS_TRACE, DB_NAME, ORIGIN_DIFFICULTY};
#[cfg(windows)]
use crate::win_service::start_service;
#[cfg(feature = "webgui")]
mod web_ui;
#[cfg(windows)]
mod win_service;
const SETTINGS_FILENAME: &str = "alfis.toml";
const LOG_TARGET_MAIN: &str = "alfis::Main";
const CONFIG: &str = include_str!("../alfis.toml");
fn main() {
#[allow(unused_assignments, unused_mut)]
@@ -55,6 +61,12 @@ fn main() {
opts.optflag("t", "trace", "Show trace messages, more than debug");
opts.optflag("b", "blocks", "List blocks from DB and exit");
opts.optflag("g", "generate", "Generate new config file. Generated config will be printed to console.");
#[cfg(windows)]
{
opts.optflag("", "install", "Install self as Windows service.");
opts.optflag("", "uninstall", "Uninstall self as Windows service.");
opts.optflag("", "service", "Run as Windows service.");
}
opts.optopt("k", "gen-key", "Generate new keys and save them to file.", "FILE");
opts.optopt("l", "log", "Write log to file", "FILE");
opts.optopt("s", "status", "Write status to file", "FILE");
@@ -79,7 +91,40 @@ fn main() {
}
if opt_matches.opt_present("g") {
println!("{}", include_str!("../alfis.toml"));
println!("{}", CONFIG);
exit(0);
}
#[cfg(windows)]
if opt_matches.opt_present("install") {
let progdata = env::var("PROGRAMDATA").expect("Failed to get APPDATA directory");
// Create a new directory inside the AppData directory
let new_directory = format!("{}\\ALFIS", progdata);
fs::create_dir_all(&new_directory).expect("Failed to create directory");
// Change the current directory to the new directory
env::set_current_dir(&new_directory).expect("Failed to change directory");
let mut file = File::create("alfis.toml").expect("Failed to create alfis.toml in AppData\\ALFIS");
file.write_all(CONFIG.as_bytes()).expect("Failed to write alfis.toml");
use crate::win_service::*;
install_service(SERVICE_NAME, &program);
// Without explicitly detaching the console cmd won't redraw it's prompt.
unsafe {
FreeConsole();
}
exit(0);
}
#[cfg(windows)]
if opt_matches.opt_present("uninstall") {
use crate::win_service::*;
uninstall_service(SERVICE_NAME);
// Without explicitly detaching the console cmd won't redraw it's prompt.
unsafe {
FreeConsole();
}
exit(0);
}
@@ -97,10 +142,22 @@ fn main() {
};
#[cfg(feature = "webgui")]
let no_gui = opt_matches.opt_present("n");
let mut no_gui = opt_matches.opt_present("n");
#[cfg(not(feature = "webgui"))]
let no_gui = true;
if opt_matches.opt_present("service") {
let appdata = env::var("PROGRAMDATA").expect("Failed to get APPDATA directory");
// Create a new directory inside the AppData directory
let new_directory = format!("{}\\ALFIS", appdata);
fs::create_dir_all(&new_directory).expect("Failed to create directory");
// Change the current directory to the new directory
env::set_current_dir(&new_directory).expect("Failed to change directory");
no_gui = true;
}
if let Some(path) = opt_matches.opt_str("w") {
env::set_current_dir(Path::new(&path)).unwrap_or_else(|_| panic!("Unable to change working directory to '{}'", &path));
}
@@ -131,33 +188,22 @@ fn main() {
let settings = Settings::load(&config_name).unwrap_or_else(|| panic!("Cannot load settings from {}!", &config_name));
debug!(target: LOG_TARGET_MAIN, "Loaded settings: {:?}", &settings);
let chain: Chain = Chain::new(&settings, DB_NAME);
if opt_matches.opt_present("b") {
for i in 1..(chain.get_height() + 1) {
if let Some(block) = chain.get_block(i) {
info!(target: LOG_TARGET_MAIN, "{:?}", &block);
}
let settings_copy = settings.clone();
let context = match create_context(opt_matches.opt_present("b"), settings) {
Some(value) => value,
None => return,
};
#[cfg(windows)]
if opt_matches.opt_present("service") {
if let Err(e) = start_service(settings_copy, context) {
error!("Unable to start service: {}", e);
exit(1);
}
info!("Service is stopped.");
return;
}
info!("Blocks count: {}, domains count: {}, users count: {}", chain.get_height(), chain.get_domains_count(), chain.get_users_count());
let settings_copy = settings.clone();
let mut keys = Vec::new();
if !settings.key_files.is_empty() {
for name in &settings.key_files {
match Keystore::from_file(name, "") {
None => {
warn!("Error loading keyfile from {}", name);
}
Some(keystore) => {
info!("Successfully loaded keyfile {}", name);
keys.push(keystore);
}
}
}
}
let context = Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keys, chain);
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(context));
// If we just need to generate keys
if let Some(filename) = opt_matches.opt_str("k") {
@@ -192,34 +238,7 @@ fn main() {
exit(0);
}
if let Ok(mut context) = context.lock() {
context.chain.check_chain(settings_copy.check_blocks);
match context.chain.get_block(1) {
None => {
info!(target: LOG_TARGET_MAIN, "No blocks found in DB");
}
Some(block) => {
trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash);
}
}
}
let dns_server_ok = if settings_copy.dns.threads > 0 {
dns_utils::start_dns_server(&context, &settings_copy)
} else {
true
};
let mut miner_obj = Miner::new(Arc::clone(&context));
miner_obj.start_mining_thread();
let miner: Arc<Mutex<Miner>> = Arc::new(Mutex::new(miner_obj));
let mut network = Network::new(Arc::clone(&context));
let network = thread::Builder::new().name(String::from("Network")).spawn(move || {
// Give UI some time to appear :)
thread::sleep(Duration::from_millis(1000));
network.start();
}).expect("Could not start network thread!");
let (dns_server_ok, miner, network) = start_services(&settings_copy, &context);
create_genesis_if_needed(&context, &miner);
if no_gui {
@@ -243,6 +262,73 @@ fn main() {
}
}
fn create_context(only_print_blocks: bool, settings: Settings) -> Option<Arc<Mutex<Context>>> {
let chain: Chain = Chain::new(&settings, DB_NAME);
if only_print_blocks {
for i in 1..(chain.get_height() + 1) {
if let Some(block) = chain.get_block(i) {
info!(target: LOG_TARGET_MAIN, "{:?}", &block);
}
}
return None;
}
info!("Blocks count: {}, domains count: {}, users count: {}", chain.get_height(), chain.get_domains_count(), chain.get_users_count());
let keys = load_keys(&settings);
let context = Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keys, chain);
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(context));
Some(context)
}
fn load_keys(settings: &Settings) -> Vec<Keystore> {
let mut keys = Vec::new();
if !settings.key_files.is_empty() {
for name in &settings.key_files {
match Keystore::from_file(name, "") {
None => {
warn!("Error loading keyfile from {}", name);
}
Some(keystore) => {
info!("Successfully loaded keyfile {}", name);
keys.push(keystore);
}
}
}
}
keys
}
pub fn start_services(settings: &Settings, context: &Arc<Mutex<Context>>) -> (bool, Arc<Mutex<Miner>>, JoinHandle<()>) {
if let Ok(mut context) = context.lock() {
context.chain.check_chain(settings.check_blocks);
match context.chain.get_block(1) {
None => {
info!(target: LOG_TARGET_MAIN, "No blocks found in DB");
}
Some(block) => {
trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash);
}
}
}
let dns_server_ok = if settings.dns.threads > 0 {
dns_utils::start_dns_server(&context, &settings)
} else {
true
};
let mut miner_obj = Miner::new(Arc::clone(&context));
miner_obj.start_mining_thread();
let miner: Arc<Mutex<Miner>> = Arc::new(Mutex::new(miner_obj));
let mut network = Network::new(Arc::clone(&context));
let network = thread::Builder::new().name(String::from("Network")).spawn(move || {
// Give UI some time to appear :)
thread::sleep(Duration::from_millis(1000));
network.start();
}).expect("Could not start network thread!");
(dns_server_ok, miner, network)
}
/// Sets up logger in accordance with command line options
fn setup_logger(opt_matches: &Matches, console_attached: bool) {
let mut level = LevelFilter::Info;
+164
View File
@@ -0,0 +1,164 @@
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use std::sync::{Arc, mpsc, Mutex};
use std::thread;
use std::time::Duration;
use lazy_static::lazy_static;
use log::{error, info};
use windows_service::{define_windows_service, service::{
ServiceControl, ServiceExitCode, ServiceState, ServiceStatus, ServiceType,
}, service_control_handler::ServiceControlHandlerResult, service_dispatcher, Result, service_control_handler};
use windows_service::service::ServiceControlAccept;
use alfis::{Context, Settings};
use crate::start_services;
// Define the service entry point and its behavior
define_windows_service!(ffi_service_main, alfis_service_main);
pub const SERVICE_NAME: &str = "ALFIS";
pub const SERVICE_DESCRIPTION: &str = "Alternative Free Identity System, DNS on a smallest blockchain.";
lazy_static! {
// Sending parameters through static variables. Don't do this!
static ref SETTINGS: Mutex<(Option<Settings>, Option<Arc<Mutex<Context>>>)> = Mutex::new((None, None));
}
pub fn start_service(settings: Settings, context: Arc<Mutex<Context>>) -> Result<()> {
if let Ok(mut option) = SETTINGS.lock() {
let _ = option.0.insert(settings);
let _ = option.1.insert(context);
}
// Register the service entry point and control handler
service_dispatcher::start(SERVICE_NAME, ffi_service_main)
}
fn alfis_service_main(_arguments: Vec<OsString>) {
if let Err(e) = run_service_logic() {
error!("Error while starting service: {}", e);
}
}
fn run_service_logic() -> Result<()> {
let (shutdown_tx, shutdown_rx) = mpsc::channel();
let event_handler = move |control_event| -> ServiceControlHandlerResult {
info!("Event: {:?}", &control_event);
match control_event {
ServiceControl::Stop => {
// Handle stop event and return control back to the system.
shutdown_tx.send(()).unwrap();
ServiceControlHandlerResult::NoError
}
// All services must accept Interrogate even if it's a no-op.
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
_ => ServiceControlHandlerResult::NotImplemented,
}
};
// Register system service event handler
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
let next_status = ServiceStatus {
// Should match the one from system service registry
service_type: ServiceType::OWN_PROCESS,
// The new state
current_state: ServiceState::Running,
// Accept stop events when running
controls_accepted: ServiceControlAccept::STOP,
// Used to report an error when starting or stopping only, otherwise must be zero
exit_code: ServiceExitCode::Win32(0),
// Only used for pending states, otherwise must be zero
checkpoint: 0,
// Only used for pending states, otherwise must be zero
wait_hint: Duration::default(),
// Unused for setting status
process_id: None,
};
// Tell the system that the service is running now
status_handle.set_service_status(next_status)?;
let (settings, context) = {
let mut lock = SETTINGS.lock().unwrap();
(lock.0.take().unwrap(), lock.1.take().unwrap())
};
let (_dns_server_ok, _miner, _network) = start_services(&settings, &context);
loop {
thread::sleep(Duration::from_secs(1));
// Poll shutdown event.
match shutdown_rx.recv_timeout(Duration::from_secs(1)) {
// Break the loop either upon stop or channel disconnect
Ok(_) | Err(mpsc::RecvTimeoutError::Disconnected) => break,
// Continue work if no events were received within the timeout
Err(mpsc::RecvTimeoutError::Timeout) => (),
};
}
let next_status = ServiceStatus {
// Should match the one from system service registry
service_type: ServiceType::OWN_PROCESS,
// The new state
current_state: ServiceState::Stopped,
// Accept stop events when running
controls_accepted: ServiceControlAccept::empty(),
// Used to report an error when starting or stopping only, otherwise must be zero
exit_code: ServiceExitCode::Win32(0),
// Only used for pending states, otherwise must be zero
checkpoint: 0,
// Only used for pending states, otherwise must be zero
wait_hint: Duration::default(),
// Unused for setting status
process_id: None,
};
status_handle.set_service_status(next_status)?;
Ok(())
}
// Function to install a Windows service
pub fn install_service(service_name: &str, bin_path: &str) {
use windows_service::service_manager::*;
use windows_service::service::*;
let error = "Error creating service. Try to start with admin rights";
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
let manager = ServiceManager::local_computer(None::<&str>, manager_access).expect(error);
let my_service_info = ServiceInfo {
name: OsString::from(service_name),
display_name: OsString::from(service_name),
service_type: ServiceType::OWN_PROCESS,
start_type: ServiceStartType::AutoStart,
error_control: ServiceErrorControl::Normal,
executable_path: PathBuf::from(bin_path),
launch_arguments: vec![OsString::from("--service"), OsString::from("-l"), OsString::from("alfis_log.txt")],
dependencies: vec![],
account_name: None, // run as System
account_password: None,
};
let my_service = manager.create_service(&my_service_info, ServiceAccess::CHANGE_CONFIG | ServiceAccess::START).expect(error);
let _ = my_service.set_description(&OsStr::new(SERVICE_DESCRIPTION));
thread::sleep(Duration::from_secs(1));
match my_service.start(&[OsStr::new("--service")]) {
Ok(_) => println!("Service successfully installed and started"),
Err(e) => println!("Error starting service: {}", e)
}
}
// Function to uninstall a Windows service
pub fn uninstall_service(service_name: &str) {
use windows_service::service_manager::*;
use windows_service::service::*;
let error = "Error creating service. Try to start with admin rights";
let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT).expect(error);
let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
match manager.open_service(&OsStr::new(service_name), service_access) {
Ok(service) => {
let _ = service.stop();
thread::sleep(Duration::from_secs(2));
let _ = service.delete();
}
Err(e) => println!("Error opening service. Try running with admin rights: {}", e)
}
}