liana: remove the daemon feature
We expect user to use systemd service to manage a daemon or to handle their process by themself. The json-rpc server is exposed only for unix systems through unix sockets, it is moved to its own module while the implementation for windows is marked as TODO.
This commit is contained in:
parent
d83f34c876
commit
a636cda2f7
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2812,7 +2812,6 @@ dependencies = [
|
||||
"fern",
|
||||
"getrandom",
|
||||
"jsonrpc 0.17.0",
|
||||
"libc",
|
||||
"log",
|
||||
"miniscript",
|
||||
"rdrand",
|
||||
|
||||
@ -12,16 +12,12 @@ exclude = [".github/", ".cirrus.yml", "tests/", "test_data/", "contrib/", "pypr
|
||||
[[bin]]
|
||||
name = "lianad"
|
||||
path = "src/bin/daemon.rs"
|
||||
required-features = ["daemon"]
|
||||
|
||||
[[bin]]
|
||||
name = "liana-cli"
|
||||
path = "src/bin/cli.rs"
|
||||
required-features = ["daemon"]
|
||||
|
||||
[features]
|
||||
default = ["daemon"]
|
||||
daemon = ["libc"]
|
||||
nonblocking_shutdown = []
|
||||
|
||||
[dependencies]
|
||||
@ -59,9 +55,6 @@ rusqlite = { version = "0.30", features = ["bundled", "unlock_notify"] }
|
||||
# To talk to bitcoind
|
||||
jsonrpc = { version = "0.17", features = ["minreq_http"], default-features = false }
|
||||
|
||||
# Used for daemonization
|
||||
libc = { version = "0.2", optional = true }
|
||||
|
||||
# Used for generating mnemonics
|
||||
getrandom = "0.2"
|
||||
|
||||
|
||||
@ -80,12 +80,7 @@ fn main() {
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let handle = DaemonHandle::start_default(
|
||||
config,
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
true,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
let handle = DaemonHandle::start_default(config, cfg!(unix)).unwrap_or_else(|e| {
|
||||
log::error!("Error starting Liana daemon: {}", e);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
use std::env::set_current_dir;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::prelude::*;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
|
||||
// This code was highly inspired from Frank Denis (@jedisct1) 'daemonize-simple' crate,
|
||||
// available at https://github.com/jedisct1/rust-daemonize-simple/blob/master/src/unix.rs .
|
||||
// MIT licensed according to https://github.com/jedisct1/rust-daemonize-simple/blob/master/Cargo.toml
|
||||
pub unsafe fn daemonize(
|
||||
chdir: &Path,
|
||||
log_file: &Path,
|
||||
pid_file: &Path,
|
||||
) -> Result<(), &'static str> {
|
||||
match libc::fork() {
|
||||
-1 => return Err("fork() failed"),
|
||||
0 => {}
|
||||
_ => {
|
||||
libc::_exit(0);
|
||||
}
|
||||
}
|
||||
libc::setsid();
|
||||
match libc::fork() {
|
||||
-1 => return Err("Second fork() failed"),
|
||||
0 => {}
|
||||
_ => {
|
||||
libc::_exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
let fd = OpenOptions::new()
|
||||
.read(true)
|
||||
.open("/dev/null")
|
||||
.map_err(|_| "Unable to open the stdin file")?;
|
||||
if libc::dup2(fd.as_raw_fd(), 0) == -1 {
|
||||
return Err("dup2(stdin) failed");
|
||||
}
|
||||
let fd = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file)
|
||||
.map_err(|_| "Unable to open the stdout file")?;
|
||||
if libc::dup2(fd.as_raw_fd(), 1) == -1 {
|
||||
return Err("dup2(stdout) failed");
|
||||
}
|
||||
let fd = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file)
|
||||
.map_err(|_| "Unable to open the stderr file")?;
|
||||
if libc::dup2(fd.as_raw_fd(), 2) == -1 {
|
||||
return Err("dup2(stderr) failed");
|
||||
}
|
||||
|
||||
let pid = match libc::getpid() {
|
||||
-1 => return Err("getpid() failed"),
|
||||
pid => pid,
|
||||
};
|
||||
let pid_str = format!("{}", pid);
|
||||
File::create(pid_file)
|
||||
.map_err(|_| "Creating the PID file failed")?
|
||||
.write_all(pid_str.as_bytes())
|
||||
.map_err(|_| "Writing to the PID file failed")?;
|
||||
|
||||
set_current_dir(chdir).map_err(|_| "chdir() failed")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
31
liana/src/jsonrpc/server/mod.rs
Normal file
31
liana/src/jsonrpc/server/mod.rs
Normal file
@ -0,0 +1,31 @@
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
|
||||
use std::{
|
||||
io, path,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use crate::DaemonControl;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn run(
|
||||
socket_path: &path::Path,
|
||||
daemon_control: DaemonControl,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
) -> Result<(), io::Error> {
|
||||
let listener = unix::rpcserver_setup(socket_path)?;
|
||||
log::info!("JSONRPC server started.");
|
||||
let res = unix::rpcserver_loop(listener, daemon_control, shutdown);
|
||||
log::info!("JSONRPC server stopped.");
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn run(
|
||||
_socket_path: &path::Path,
|
||||
_daemon_control: DaemonControl,
|
||||
_shutdown: Arc<AtomicBool>,
|
||||
) -> Result<(), io::Error> {
|
||||
todo!("Implement a json rpc server over Named pipe");
|
||||
}
|
||||
@ -9,8 +9,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use std::{
|
||||
io,
|
||||
os::unix::net,
|
||||
fs, io,
|
||||
os::unix::{fs::PermissionsExt, net},
|
||||
path,
|
||||
sync::{self, atomic},
|
||||
thread, time,
|
||||
@ -194,17 +194,13 @@ fn bind(socket_path: &path::Path) -> Result<net::UnixListener, io::Error> {
|
||||
pub fn rpcserver_setup(socket_path: &path::Path) -> Result<net::UnixListener, io::Error> {
|
||||
log::debug!("Binding socket at {}", socket_path.display());
|
||||
// Create the socket with RW permissions only for the user
|
||||
#[cfg(not(test))]
|
||||
let old_umask = unsafe { libc::umask(0o177) };
|
||||
#[allow(clippy::all)]
|
||||
let listener = bind(socket_path);
|
||||
let listener = bind(socket_path)?;
|
||||
|
||||
#[cfg(not(test))]
|
||||
unsafe {
|
||||
libc::umask(old_umask);
|
||||
}
|
||||
// Set the permissions to RW for the user only
|
||||
let permissions = fs::Permissions::from_mode(0o600);
|
||||
fs::set_permissions(socket_path, permissions)?;
|
||||
|
||||
listener
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -1,11 +1,8 @@
|
||||
mod bitcoin;
|
||||
pub mod commands;
|
||||
pub mod config;
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
mod daemonize;
|
||||
mod database;
|
||||
pub mod descriptors;
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
mod jsonrpc;
|
||||
pub mod random;
|
||||
pub mod signer;
|
||||
@ -22,8 +19,8 @@ pub use crate::bitcoin::{
|
||||
d::{BitcoinD, BitcoindError, WalletError},
|
||||
electrum::{Electrum, ElectrumError},
|
||||
};
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
use crate::jsonrpc::server::{rpcserver_loop, rpcserver_setup};
|
||||
|
||||
use crate::jsonrpc::server;
|
||||
use crate::{
|
||||
bitcoin::{poller, BitcoinInterface},
|
||||
config::Config,
|
||||
@ -103,8 +100,6 @@ pub enum StartupError {
|
||||
Database(SqliteDbError),
|
||||
Bitcoind(BitcoindError),
|
||||
Electrum(ElectrumError),
|
||||
#[cfg(unix)]
|
||||
Daemonization(&'static str),
|
||||
#[cfg(windows)]
|
||||
NoWatchonlyInDatadir,
|
||||
}
|
||||
@ -140,8 +135,6 @@ impl fmt::Display for StartupError {
|
||||
Self::Database(e) => write!(f, "Error initializing database: '{}'.", e),
|
||||
Self::Bitcoind(e) => write!(f, "Error setting up bitcoind interface: '{}'.", e),
|
||||
Self::Electrum(e) => write!(f, "Error setting up Electrum interface: '{}'.", e),
|
||||
#[cfg(unix)]
|
||||
Self::Daemonization(e) => write!(f, "Error when daemonizing: '{}'.", e),
|
||||
#[cfg(windows)]
|
||||
Self::NoWatchonlyInDatadir => {
|
||||
write!(
|
||||
@ -411,7 +404,6 @@ pub enum DaemonHandle {
|
||||
poller_handle: thread::JoinHandle<()>,
|
||||
control: DaemonControl,
|
||||
},
|
||||
#[cfg(feature = "daemon")]
|
||||
Server {
|
||||
poller_sender: mpsc::SyncSender<poller::PollerMessage>,
|
||||
poller_handle: thread::JoinHandle<()>,
|
||||
@ -435,7 +427,7 @@ impl DaemonHandle {
|
||||
config: Config,
|
||||
bitcoin: Option<impl BitcoinInterface + 'static>,
|
||||
db: Option<impl DatabaseInterface + 'static>,
|
||||
#[cfg(all(unix, feature = "daemon"))] with_rpc_server: bool,
|
||||
with_rpc_server: bool,
|
||||
) -> Result<Self, StartupError> {
|
||||
#[cfg(not(test))]
|
||||
setup_panic_hook();
|
||||
@ -490,20 +482,6 @@ impl DaemonHandle {
|
||||
(None, None) => Err(StartupError::MissingBitcoinBackendConfig)?,
|
||||
};
|
||||
|
||||
// If we are on a UNIX system and they told us to daemonize, do it now.
|
||||
// NOTE: it's safe to daemonize now, as we don't carry any open DB connection
|
||||
// https://www.sqlite.org/howtocorrupt.html#_carrying_an_open_database_connection_across_a_fork_
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
if config.daemon {
|
||||
log::info!("Daemonizing");
|
||||
let log_file = data_dir.as_path().join("log");
|
||||
let pid_file = data_dir.as_path().join("lianad.pid");
|
||||
unsafe {
|
||||
daemonize::daemonize(&data_dir, &log_file, &pid_file)
|
||||
.map_err(StartupError::Daemonization)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the poller thread. Keep the thread handle to be able to check if it crashed. Store
|
||||
// an atomic to be able to stop it.
|
||||
let mut bitcoin_poller =
|
||||
@ -525,7 +503,6 @@ impl DaemonHandle {
|
||||
// structure or through the JSONRPC server we may setup below.
|
||||
let control = DaemonControl::new(config, bit, poller_sender.clone(), db, secp);
|
||||
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
if with_rpc_server {
|
||||
let rpcserver_shutdown = sync::Arc::from(sync::atomic::AtomicBool::from(false));
|
||||
let rpcserver_handle = thread::Builder::new()
|
||||
@ -535,11 +512,7 @@ impl DaemonHandle {
|
||||
move || {
|
||||
let mut rpc_socket = data_dir;
|
||||
rpc_socket.push("lianad_rpc");
|
||||
let listener = rpcserver_setup(&rpc_socket)?;
|
||||
log::info!("JSONRPC server started.");
|
||||
|
||||
rpcserver_loop(listener, control, shutdown)?;
|
||||
log::info!("JSONRPC server stopped.");
|
||||
server::run(&rpc_socket, control, shutdown)?;
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
@ -564,13 +537,12 @@ impl DaemonHandle {
|
||||
/// and SQLite).
|
||||
pub fn start_default(
|
||||
config: Config,
|
||||
#[cfg(all(unix, feature = "daemon"))] with_rpc_server: bool,
|
||||
with_rpc_server: bool,
|
||||
) -> Result<DaemonHandle, StartupError> {
|
||||
Self::start(
|
||||
config,
|
||||
Option::<BitcoinD>::None,
|
||||
Option::<SqliteDb>::None,
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
with_rpc_server,
|
||||
)
|
||||
}
|
||||
@ -583,7 +555,6 @@ impl DaemonHandle {
|
||||
Self::Controller {
|
||||
ref poller_handle, ..
|
||||
} => !poller_handle.is_finished(),
|
||||
#[cfg(feature = "daemon")]
|
||||
Self::Server {
|
||||
ref poller_handle,
|
||||
ref rpcserver_handle,
|
||||
@ -606,7 +577,6 @@ impl DaemonHandle {
|
||||
poller_handle.join().expect("Poller thread must not panic");
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature = "daemon")]
|
||||
Self::Server {
|
||||
poller_sender,
|
||||
poller_handle,
|
||||
@ -867,12 +837,7 @@ mod tests {
|
||||
let t = thread::spawn({
|
||||
let config = config.clone();
|
||||
move || {
|
||||
let handle = DaemonHandle::start_default(
|
||||
config,
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let handle = DaemonHandle::start_default(config, false).unwrap();
|
||||
handle.stop().unwrap();
|
||||
}
|
||||
});
|
||||
@ -892,12 +857,7 @@ mod tests {
|
||||
let t = thread::spawn({
|
||||
let config = config.clone();
|
||||
move || {
|
||||
let handle = DaemonHandle::start_default(
|
||||
config,
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let handle = DaemonHandle::start_default(config, false).unwrap();
|
||||
handle.stop().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
@ -544,7 +544,7 @@ impl DummyLiana {
|
||||
pub fn _new(
|
||||
bitcoin_interface: impl BitcoinInterface + 'static,
|
||||
database: impl DatabaseInterface + 'static,
|
||||
#[cfg(all(unix, feature = "daemon"))] rpc_server: bool,
|
||||
rpc_server: bool,
|
||||
) -> DummyLiana {
|
||||
let tmp_dir = tmp_dir();
|
||||
fs::create_dir_all(&tmp_dir).unwrap();
|
||||
@ -575,14 +575,9 @@ impl DummyLiana {
|
||||
main_descriptor: desc,
|
||||
};
|
||||
|
||||
let handle = DaemonHandle::start(
|
||||
config,
|
||||
Some(bitcoin_interface),
|
||||
Some(database),
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
rpc_server,
|
||||
)
|
||||
.unwrap();
|
||||
let handle =
|
||||
DaemonHandle::start(config, Some(bitcoin_interface), Some(database), rpc_server)
|
||||
.unwrap();
|
||||
DummyLiana { tmp_dir, handle }
|
||||
}
|
||||
|
||||
@ -591,16 +586,10 @@ impl DummyLiana {
|
||||
bitcoin_interface: impl BitcoinInterface + 'static,
|
||||
database: impl DatabaseInterface + 'static,
|
||||
) -> DummyLiana {
|
||||
Self::_new(
|
||||
bitcoin_interface,
|
||||
database,
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
false,
|
||||
)
|
||||
Self::_new(bitcoin_interface, database, false)
|
||||
}
|
||||
|
||||
/// Creates a new DummyLiana interface which also spins up an RPC server.
|
||||
#[cfg(all(unix, feature = "daemon"))]
|
||||
pub fn new_server(
|
||||
bitcoin_interface: impl BitcoinInterface + 'static,
|
||||
database: impl DatabaseInterface + 'static,
|
||||
@ -611,7 +600,6 @@ impl DummyLiana {
|
||||
pub fn control(&self) -> &DaemonControl {
|
||||
match self.handle {
|
||||
DaemonHandle::Controller { ref control, .. } => control,
|
||||
#[cfg(feature = "daemon")]
|
||||
DaemonHandle::Server { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user