diff --git a/src/command_runner.rs b/src/command_runner.rs index a5682d7..52ac7ce 100644 --- a/src/command_runner.rs +++ b/src/command_runner.rs @@ -11,7 +11,34 @@ pub struct StdCommandRunner; impl CommandRunner for StdCommandRunner { fn run_with_args(&self, program: &str, args: &[&str]) -> IoResult { - Command::new(program).args(args).output() + // FIXME: logger + println!("{} {:?}", program, args); + let res = Command::new(program).args(args).output(); + println!("{:?}", res); + res + } +} + +#[derive(Debug)] +pub struct UserCommandRunner<'a, C> where C: 'a + CommandRunner { + command_runner: &'a C, + user_name: &'a str +} + +impl<'a, C> UserCommandRunner<'a, C> where C: 'a + CommandRunner { + pub fn new(user_name: &'a str, command_runner: &'a C) -> UserCommandRunner<'a, C> { + UserCommandRunner { + command_runner: command_runner, + user_name: user_name + } + } +} + +impl<'a, C> CommandRunner for UserCommandRunner<'a, C> where C: 'a + CommandRunner { + fn run_with_args(&self, program: &str, args: &[&str]) -> IoResult { + let mut new_args = vec![self.user_name, "-s", "/usr/bin/env", "--", program]; + new_args.extend_from_slice(args); + self.command_runner.run_with_args("su", &new_args) } } diff --git a/src/schema.rs b/src/schema.rs index 8aacf90..1960d04 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,6 +1,5 @@ use std::error::Error; use std::fmt; -use std::io::Write; use loggers::Logger; use symbols::Symbol; @@ -42,14 +41,14 @@ impl From for SymbolRunError { } pub trait SymbolRunner { - fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> + fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> where S::Error: Error; } pub struct InitializingSymbolRunner; impl SymbolRunner for InitializingSymbolRunner { - fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> + fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> where S::Error: Error { let target_reached = try!(symbol.target_reached()); @@ -72,7 +71,7 @@ impl SymbolRunner for InitializingSymbolRunner { pub struct DrySymbolRunner; impl SymbolRunner for DrySymbolRunner { - fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> + fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> where S::Error: Error { let target_reached = try!(symbol.target_reached()); diff --git a/src/symbols/file.rs b/src/symbols/file.rs new file mode 100644 index 0000000..d749ed0 --- /dev/null +++ b/src/symbols/file.rs @@ -0,0 +1,94 @@ +use std::error::Error; +use std::fmt; +use std::io; +use std::ops::Deref; + +use symbols::Symbol; + +#[derive(Debug)] +pub enum FileError { + ExecError(E), + GenericError +} + +impl From for FileError { + fn from(err: io::Error) -> FileError { + FileError::ExecError(err) + } +} + +impl Error for FileError { + fn description(&self) -> &str { + match self { + &FileError::ExecError(ref e) => e.description(), + &FileError::GenericError => "Generic error" + } + } + fn cause(&self) -> Option<&Error> { + match self { + &FileError::ExecError(ref e) => Some(e), + _ => None + } + } +} + +impl fmt::Display for FileError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self.description()) + } +} + +use std::convert::AsRef; + +pub struct File where C: Deref, D: AsRef + fmt::Display { + path: D, + content: C +} + +impl File where C: Deref, D: AsRef + fmt::Display { + pub fn new(path: D, content: C) -> Self { + File { + path: path, + content: content + } + } +} + +use std::fs::File as FsFile; +use std::io::{Read, Write}; + +impl Symbol for File where C: Deref, D: AsRef + fmt::Display { + type Error = FileError; + fn target_reached(&self) -> Result { + let file = FsFile::open(self.path.as_ref()); + // Check if file exists + if let Err(e) = file { + return if e.kind() == io::ErrorKind::NotFound { + Ok(false) + } else { + Err(e.into()) + }; + } + // Check if content is the same + let file_content = file.unwrap().bytes(); + let content_equal = try!(self.content.bytes().zip(file_content).fold( + Ok(true), + |state, (target_byte, file_byte_option)| state.and_then(|s| file_byte_option.map(|file_byte| s && file_byte == target_byte)) + )); + return Ok(content_equal) + } + + fn execute(&self) -> Result<(), Self::Error> { + //try!(self.command_runner.run_with_args("mkdir", &["-p", Path::new(&path).parent().unwrap().to_str().unwrap()])); + // FIXME: Permissions + // try!(create_dir_all(Path::new(&path).parent().unwrap())); + try!(try!(FsFile::create(self.path.as_ref())).write_all(self.content.as_bytes())); + Ok(()) + } +} + +impl fmt::Display for File where C: Deref, D: AsRef + fmt::Display { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{ + write!(f, "File {}", self.path) + } +} diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs index 7fa1f0b..c7cee44 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -1,9 +1,13 @@ -pub trait Symbol { - type Error; +use std::error::Error; +use std::fmt::Display; + +pub trait Symbol: Display { + type Error: Error; fn target_reached(&self) -> Result; fn execute(&self) -> Result<(), Self::Error>; } +pub mod file; pub mod git; pub mod user; pub mod systemd; diff --git a/src/symbols/systemd/mod.rs b/src/symbols/systemd/mod.rs index c339c2b..75a2027 100644 --- a/src/symbols/systemd/mod.rs +++ b/src/symbols/systemd/mod.rs @@ -1 +1,2 @@ pub mod user_session; +pub mod node_js_user_service; diff --git a/src/symbols/systemd/node_js_user_service.rs b/src/symbols/systemd/node_js_user_service.rs new file mode 100644 index 0000000..63e2f82 --- /dev/null +++ b/src/symbols/systemd/node_js_user_service.rs @@ -0,0 +1,124 @@ +use std::error::Error; +use std::fmt; +use std::io; +use std::path::Path; +use std::thread::sleep; +use std::time::Duration; +use std::ops::Deref; + +use command_runner::CommandRunner; +use symbols::Symbol; +use symbols::file::File as FileSymbol; + +#[derive(Debug)] +pub enum NodeJsSystemdUserServiceError { + ExecError(E), + GenericError +} + +impl From for NodeJsSystemdUserServiceError { + fn from(err: io::Error) -> NodeJsSystemdUserServiceError { + NodeJsSystemdUserServiceError::ExecError(err) + } +} + +impl Error for NodeJsSystemdUserServiceError { + fn description(&self) -> &str { + match self { + &NodeJsSystemdUserServiceError::ExecError(ref e) => e.description(), + &NodeJsSystemdUserServiceError::GenericError => "Generic error" + } + } + fn cause(&self) -> Option<&Error> { + match self { + &NodeJsSystemdUserServiceError::ExecError(ref e) => Some(e), + _ => None + } + } +} + +impl fmt::Display for NodeJsSystemdUserServiceError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self.description()) + } +} + +pub struct NodeJsSystemdUserService<'a, P, C> where P: AsRef + fmt::Display, C: Deref { + name: &'a str, + path: P, + command_runner: &'a CommandRunner, + file: FileSymbol> +} + +use std::borrow::Cow; + +impl<'a> NodeJsSystemdUserService<'a, Cow<'a, str>, String> { + pub fn new(name: &'a str, path: &'a str, command_runner: &'a CommandRunner) -> Self { + let home = String::from_utf8(command_runner.run_with_args("sh", &["-c", "echo \"$HOME\""]).unwrap().stdout).unwrap(); + let file_path: Cow = Cow::from(String::from(home.trim_right()) + "/.config/systemd/user/" + name + ".service"); + + let content = format!("[Service] +ExecStart=/usr/bin/nodejs {} +Restart=always +Environment=NODE_ENV=production +Environment=PORT={}/var/service.socket + +[Install] +WantedBy=default.target +", path, home); + NodeJsSystemdUserService { + name: name, + path: file_path.clone(), + command_runner: command_runner, + file: FileSymbol::new(file_path, content) + } + } +} + +impl<'a, P, C> Symbol for NodeJsSystemdUserService<'a, P, C> where P: AsRef + fmt::Display, C: Deref { + type Error = NodeJsSystemdUserServiceError; + fn target_reached(&self) -> Result { + match self.file.target_reached() { + Ok(false) => return Ok(false), + Ok(true) => {}, + Err(e) => return Err(NodeJsSystemdUserServiceError::GenericError) + } +/* + if !(try!(self.file.target_reached())) { + return Ok(false) + } +*/ + loop { + // Check if service is registered + let active_state = try!(self.command_runner.run_with_args("systemctl", &["--user", "show", "--property", "ActiveState", self.name])); + if !active_state.status.success() { + return Ok(false); + } + // Check if service is running + match String::from_utf8(active_state.stdout).unwrap().trim_right() { + "ActiveState=activating" => sleep(Duration::from_millis(500)), + "ActiveState=active" => return Ok(true), + _ => return Ok(false) + } + } + } + + fn execute(&self) -> Result<(), Self::Error> { + try!(self.command_runner.run_with_args("mkdir", &["-p", Path::new(self.path.as_ref()).parent().unwrap().to_str().unwrap()])); + // FIXME: Permissions + // try!(create_dir_all(Path::new(&path).parent().unwrap())); + match self.file.execute() { + Ok(_) => {}, + Err(e) => return Err(NodeJsSystemdUserServiceError::GenericError) + } + try!(self.command_runner.run_with_args("systemctl", &["--user", "enable", self.name])); + try!(self.command_runner.run_with_args("systemctl", &["--user", "start", self.name])); + Ok(()) + } +} + +impl<'a, P, C> fmt::Display for NodeJsSystemdUserService<'a, P, C> where P: AsRef + fmt::Display, C: Deref { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{ + write!(f, "Systemd Node.js user service unit for {}", self.path) + } +} diff --git a/src/symbols/systemd/user_session.rs b/src/symbols/systemd/user_session.rs index 4ab19a2..d705782 100644 --- a/src/symbols/systemd/user_session.rs +++ b/src/symbols/systemd/user_session.rs @@ -2,24 +2,28 @@ use std::error::Error; use std::fmt; use std::io::Error as IoError; use std::path::PathBuf; +use std::str::from_utf8; use command_runner::CommandRunner; use symbols::Symbol; #[derive(Debug)] pub enum SystemdUserSessionError { - ExecError(E) + ExecError(E), + GenericError } impl Error for SystemdUserSessionError { fn description(&self) -> &str { match self { - &SystemdUserSessionError::ExecError(ref e) => e.description() + &SystemdUserSessionError::ExecError(ref e) => e.description(), + &SystemdUserSessionError::GenericError => "Generic error" } } fn cause(&self) -> Option<&Error> { match self { - &SystemdUserSessionError::ExecError(ref e) => Some(e) + &SystemdUserSessionError::ExecError(ref e) => Some(e), + _ => None } } } @@ -50,11 +54,15 @@ impl<'a> Symbol for SystemdUserSession<'a> { let mut path = PathBuf::from("/var/lib/systemd/linger"); path.push(self.user_name); Ok(path.exists()) + // Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes` } fn execute(&self) -> Result<(), Self::Error> { match self.command_runner.run_with_args("loginctl", &["enable-linger", self.user_name]) { - Ok(_) => Ok(()), + Ok(output) => { println!("{:?} {:?}", from_utf8(&output.stdout).unwrap(), from_utf8(&output.stderr).unwrap() ); match output.status.code() { + Some(0) => Ok(()), + _ => Err(SystemdUserSessionError::GenericError) + } }, Err(e) => Err(SystemdUserSessionError::ExecError(e)) } } diff --git a/src/symbols/user.rs b/src/symbols/user.rs index 71d221f..c397824 100644 --- a/src/symbols/user.rs +++ b/src/symbols/user.rs @@ -126,13 +126,29 @@ impl<'a> SystemUserAdder<'a> { impl<'a> UserAdder for SystemUserAdder<'a> { type SubE = IoError; fn add_user(&self, user_name: &str) -> Result<(), UserAdderError> { - let output = self.command_runner.run_with_args("adduser", &["--system", "--disabled-login", "--disabled-password", user_name]); + let output = self.command_runner.run_with_args( + "adduser", + &[ + // "-m", // Necessary for Fedora, not accepted in Debian + "--system", + user_name + ]); match output { Ok(output) => match output.status.code() { Some(0) => Ok(()), - Some(1) => Err(UserAdderError::AlreadyExists), - Some(_) => Err(UserAdderError::UnknownError), - None => Err(UserAdderError::UnknownError), + Some(1) => +{ + println!("{:?}", output); +Err(UserAdderError::AlreadyExists)}, + Some(_) => +{ + println!("{:?}", output); +Err(UserAdderError::UnknownError) +}, + None => { + println!("{:?}", output); +Err(UserAdderError::UnknownError) +}, }, Err(e) => Err(UserAdderError::ImplError(e)) }