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.

235 lines
6.2 KiB

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