A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
6.1 KiB

7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
5 years ago
5 years ago
5 years ago
7 years ago
7 years ago
7 years ago
5 years ago
5 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
5 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
5 years ago
7 years ago
7 years ago
5 years ago
5 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
  1. use std::borrow::Cow;
  2. use std::error::Error;
  3. use std::ffi::OsStr;
  4. use std::fmt;
  5. use std::io;
  6. use std::path::Path;
  7. use std::path::PathBuf;
  8. use std::process::Output;
  9. use std::thread::sleep;
  10. use std::time::Duration;
  11. use command_runner::{CommandRunner, SetuidCommandRunner};
  12. use resources::Resource;
  13. use symbols::file::File as FileSymbol;
  14. use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
  15. #[derive(Debug)]
  16. pub enum UserServiceError<E: Error> {
  17. ActivationFailed(io::Result<Output>),
  18. ExecError(E),
  19. GenericError,
  20. }
  21. impl From<io::Error> for UserServiceError<io::Error> {
  22. fn from(err: io::Error) -> Self {
  23. Self::ExecError(err)
  24. }
  25. }
  26. impl<E: Error> Error for UserServiceError<E> {
  27. fn description(&self) -> &str {
  28. match self {
  29. UserServiceError::ExecError(ref e) => e.description(),
  30. UserServiceError::GenericError => "Generic error",
  31. UserServiceError::ActivationFailed(_) => "Activation of service failed",
  32. }
  33. }
  34. fn cause(&self) -> Option<&dyn Error> {
  35. match self {
  36. UserServiceError::ExecError(ref e) => Some(e),
  37. _ => None,
  38. }
  39. }
  40. }
  41. impl<E: Error> fmt::Display for UserServiceError<E> {
  42. fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
  43. write!(f, "{}", self.description())?;
  44. if let UserServiceError::ActivationFailed(Ok(ref log)) = self {
  45. write!(f, ": {:?}", log)?;
  46. };
  47. Ok(())
  48. }
  49. }
  50. pub struct UserService<'a, C: AsRef<str>, R: CommandRunner> {
  51. socket_path: Cow<'a, Path>,
  52. service_name: &'a str,
  53. user_name: Cow<'a, str>,
  54. command_runner: R,
  55. config: FileSymbol<C, PathBuf>,
  56. }
  57. impl<'a, R: CommandRunner> UserService<'a, String, SetuidCommandRunner<'a, Cow<'a, str>, R>> {
  58. pub fn new_nodejs(
  59. home: Cow<'a, Path>,
  60. user_name: Cow<'a, str>,
  61. service_name: &'a str,
  62. path: Cow<'a, Path>,
  63. command_runner: &'a R,
  64. socket_path: Cow<'a, Path>,
  65. ) -> Self {
  66. let content = format!(
  67. "[Service]
  68. Environment=NODE_ENV=production
  69. Environment=PORT={1}
  70. ExecStartPre=/bin/rm -f {1}
  71. ExecStart=/usr/bin/nodejs {0}
  72. ExecStartPost=/bin/sh -c 'sleep 1 && chmod 666 {1}'
  73. # FIXME: This only works if the nodejs path is a directory
  74. WorkingDirectory={0}
  75. #RuntimeDirectory=service
  76. #RuntimeDirectoryMode=766
  77. Restart=always
  78. [Install]
  79. WantedBy=default.target
  80. ",
  81. path.to_str().unwrap(),
  82. socket_path.to_str().unwrap()
  83. );
  84. UserService::new(
  85. socket_path,
  86. home,
  87. user_name,
  88. service_name,
  89. command_runner,
  90. content,
  91. )
  92. }
  93. pub fn new(
  94. socket_path: Cow<'a, Path>,
  95. home: Cow<'a, Path>,
  96. user_name: Cow<'a, str>,
  97. service_name: &'a str,
  98. command_runner: &'a R,
  99. content: String,
  100. ) -> Self {
  101. let config_path: PathBuf = [
  102. home.as_ref(),
  103. format!(".config/systemd/user/{}.service", service_name).as_ref(),
  104. ]
  105. .iter()
  106. .collect();
  107. UserService {
  108. socket_path,
  109. service_name,
  110. user_name: user_name.clone(),
  111. command_runner: SetuidCommandRunner::new(user_name, command_runner),
  112. config: FileSymbol::new(config_path, content),
  113. }
  114. }
  115. }
  116. impl<'a, C: AsRef<str>, R: CommandRunner> UserService<'a, C, R> {
  117. fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result<String, Box<dyn Error>> {
  118. let mut tries = 5;
  119. loop {
  120. let result = self.command_runner.run_with_args("systemctl", args)?;
  121. if !result.status.success() {
  122. let raw_stderr = String::from_utf8(result.stderr)?;
  123. let stderr = raw_stderr.trim_end();
  124. if stderr != "Failed to connect to bus: No such file or directory" {
  125. return Err(stderr.into());
  126. }
  127. } else {
  128. return Ok(String::from_utf8(result.stdout)?.trim_end().to_string());
  129. }
  130. tries -= 1;
  131. if tries == 0 {
  132. return Err("Gave up waiting for dbus to appear".to_string().into());
  133. }
  134. sleep(Duration::from_millis(500));
  135. }
  136. }
  137. fn check_if_service(&self) -> Result<bool, Box<dyn Error>> {
  138. loop {
  139. let active_state = self.systemctl_wait_for_dbus(args![
  140. "--user",
  141. "show",
  142. "--property",
  143. "ActiveState",
  144. self.service_name,
  145. ])?;
  146. match active_state.as_ref() {
  147. "ActiveState=activating" => sleep(Duration::from_millis(500)),
  148. "ActiveState=active" => return Ok(true),
  149. "ActiveState=failed" => {
  150. return Err(Box::new(
  151. UserServiceError::ActivationFailed(self.command_runner.run_with_args(
  152. "journalctl",
  153. args!["--user", format!("--user-unit={}", self.service_name)],
  154. )) as UserServiceError<io::Error>,
  155. ))
  156. }
  157. _ => return Ok(false),
  158. }
  159. }
  160. }
  161. }
  162. impl<'a, C: AsRef<str>, R: CommandRunner> Symbol for UserService<'a, C, R> {
  163. fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
  164. if !(self.config.target_reached()?) {
  165. return Ok(false);
  166. }
  167. self.check_if_service()
  168. }
  169. fn execute(&self) -> Result<(), Box<dyn Error>> {
  170. self.config.execute()?;
  171. self.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])?;
  172. self.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])?;
  173. loop {
  174. if !(self.check_if_service()?) {
  175. return Err(Box::new(
  176. UserServiceError::GenericError as UserServiceError<io::Error>,
  177. ));
  178. }
  179. if self.socket_path.exists() {
  180. return Ok(());
  181. }
  182. sleep(Duration::from_millis(500));
  183. }
  184. }
  185. fn get_prerequisites(&self) -> Vec<Resource> {
  186. let mut r = vec![Resource::new(
  187. "file",
  188. format!("/var/lib/systemd/linger/{}", self.user_name),
  189. )];
  190. r.extend(self.config.get_prerequisites().into_iter());
  191. r
  192. }
  193. fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
  194. Box::new(SymbolAction::new(runner, self))
  195. }
  196. fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
  197. where
  198. Self: 'b,
  199. {
  200. Box::new(OwnedSymbolAction::new(runner, *self))
  201. }
  202. }
  203. impl<'a, C: AsRef<str>, R: CommandRunner> fmt::Display for UserService<'a, C, R> {
  204. fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
  205. write!(f, "Systemd user service unit for {}", self.service_name)
  206. }
  207. }