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.

198 lines
7.4 KiB

6 years ago
7 years ago
6 years ago
7 years ago
7 years ago
6 years ago
7 years ago
6 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
7 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
  1. use std::borrow::Cow;
  2. use std::ops::Deref;
  3. use std::path::Path;
  4. use command_runner::{CommandRunner, SetuidCommandRunner};
  5. use storage::{SimpleStorage, Storage};
  6. use symbols::{Action, Symbol, SymbolRunner};
  7. use symbols::acme::{AcmeCert, AcmeCertChain};
  8. use symbols::file::File;
  9. use symbols::git::checkout::GitCheckout;
  10. use symbols::hook::Hook;
  11. use symbols::list::ListAction;
  12. use symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser};
  13. use symbols::nginx::server::{NginxServer, server_config};
  14. use symbols::owner::Owner;
  15. use symbols::stored_directory::{StoredDirectory, StorageDirection};
  16. use symbols::systemd::reload::ReloadService;
  17. use symbols::tls::SelfSignedTlsCert;
  18. pub trait Policy {
  19. fn user_name_for_host(&self, host_name: &'static str) -> String;
  20. }
  21. pub struct DefaultPolicy;
  22. impl Policy for DefaultPolicy {
  23. fn user_name_for_host(&self, host_name: &'static str) -> String {
  24. host_name.split('.').rev().fold(String::new(), |result, part| if result.len() > 0 { result + "_" } else { result } + part)
  25. }
  26. }
  27. pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy>{
  28. command_runner: &'a C,
  29. acme_command_runner: SetuidCommandRunner<'a, C>,
  30. symbol_runner: &'a R,
  31. policy: &'a P
  32. }
  33. impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> {
  34. pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P) -> Self {
  35. let acme_user = "acme"; // FIXME: CONFIG
  36. let acme_command_runner = SetuidCommandRunner::new(acme_user, command_runner);
  37. SymbolFactory {
  38. command_runner: command_runner,
  39. acme_command_runner: acme_command_runner,
  40. symbol_runner: symbol_runner,
  41. policy: policy
  42. }
  43. }
  44. pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(&'c self, host: &'static str, nginx_server_symbol: S) -> Box<Action + 'a> {
  45. Box::new(ListAction::new(vec![
  46. Box::new(SelfSignedTlsCert::new(
  47. host.into(),
  48. self.command_runner
  49. )).into_action(self.symbol_runner),
  50. Box::new(Hook::new(
  51. nginx_server_symbol,
  52. ReloadService::new("nginx", self.command_runner)
  53. )).into_action(self.symbol_runner),
  54. Box::new(AcmeCert::new(
  55. host.into(),
  56. &self.acme_command_runner
  57. )).into_action(self.symbol_runner),
  58. Box::new(Hook::new(
  59. AcmeCertChain::new(
  60. host.into(),
  61. &self.acme_command_runner
  62. ),
  63. ReloadService::new("nginx", self.command_runner)
  64. )).into_action(self.symbol_runner)
  65. ]))
  66. }
  67. pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> Box<Action + 'a> {
  68. Box::new(File::new(
  69. "/etc/nginx/snippets/acme-challenge.conf", "location ^~ /.well-known/acme-challenge/ {
  70. alias /home/acme/challenges/;
  71. try_files $uri =404;
  72. }"
  73. )).into_action(self.symbol_runner)
  74. }
  75. fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> String {
  76. format!("/run/php/{}.sock", user_name)
  77. }
  78. fn get_php_fpm_pool<'a>(&'a self, user_name: &str) -> Box<Action + 'a> {
  79. let socket = self.get_php_fpm_pool_socket_path(user_name);
  80. Box::new(Hook::new(
  81. File::new(
  82. format!("/etc/php/7.0/fpm/pool.d/{}.conf", user_name),
  83. format!(
  84. "[{0}]
  85. user = {0}
  86. group = www-data
  87. listen = {1}
  88. listen.owner = www-data
  89. pm = ondemand
  90. pm.max_children = 10"
  91. , user_name, socket)),
  92. ReloadService::new("php7.0-fpm", self.command_runner)
  93. )).into_action(self.symbol_runner)
  94. }
  95. pub fn serve_php<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<Action + 'a> {
  96. let user_name = self.policy.user_name_for_host(host_name);
  97. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  98. Box::new(ListAction::new(vec![
  99. self.get_php_fpm_pool(&user_name),
  100. self.get_nginx_acme_server(host_name,
  101. NginxServer::new_php(
  102. host_name,
  103. socket.into(),
  104. root_dir,
  105. self.command_runner
  106. )
  107. )
  108. ]))
  109. }
  110. pub fn serve_dokuwiki<'a>(&'a self, host_name: &'static str, root_dir: &'static str) -> Box<Action + 'a> {
  111. let user_name = self.policy.user_name_for_host(host_name);
  112. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  113. Box::new(ListAction::new(vec![
  114. self.get_php_fpm_pool(&user_name),
  115. self.get_nginx_acme_server(host_name,
  116. NginxServer::new(
  117. host_name,
  118. server_config("hostname", &format!("
  119. root {};
  120. index doku.php;
  121. location ~ [^/]\\.php(/|$) {{
  122. fastcgi_pass unix:{};
  123. include \"snippets/fastcgi-php.conf\";
  124. }}
  125. location ~ /(data/|conf/|bin/|inc/|install.php) {{ deny all; }}
  126. location / {{ try_files $uri $uri/ @dokuwiki; }}
  127. location @dokuwiki {{
  128. # rewrites \"doku.php/\" out of the URLs if you set the userewrite setting to .htaccess in dokuwiki config page
  129. rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
  130. rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
  131. rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
  132. rewrite ^/(.*) /doku.php?id=$1&$args last;
  133. }}
  134. ",
  135. root_dir,
  136. socket)),
  137. self.command_runner
  138. ))
  139. ]))
  140. }
  141. pub fn serve_redir<'a>(&'a self, host_name: &'static str, target: &'static str) -> Box<Action + 'a> {
  142. self.get_nginx_acme_server(host_name, NginxServer::new_redir(host_name, target, self.command_runner))
  143. }
  144. pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> Box<Action + 'a> {
  145. self.get_nginx_acme_server(host_name, NginxServer::new_static(host_name, dir, self.command_runner))
  146. }
  147. pub fn get_stored_directory<'a, T: Into<String>>(&'a self, storage_name: &'static str, target: T) -> (Box<Action + 'a>, Box<Action + 'a>) {
  148. let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
  149. let string_target = target.into();
  150. (
  151. Box::new(StoredDirectory::new(string_target.clone().into(), data.clone(), StorageDirection::Save, self.command_runner)).into_action(self.symbol_runner),
  152. Box::new(StoredDirectory::new(string_target.into(), data.clone(), StorageDirection::Load, self.command_runner)).into_action(self.symbol_runner)
  153. )
  154. }
  155. pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> Box<Action + 'a> {
  156. let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
  157. Box::new(ListAction::new(vec![
  158. Box::new(MariaDBDatabase::new(name.into(), db_dump.read_filename().unwrap().into(), self.command_runner)).into_action(self.symbol_runner),
  159. Box::new(DatabaseDump::new(name, db_dump, self.command_runner)).into_action(self.symbol_runner)
  160. ]))
  161. }
  162. pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> Box<Action + 'a> {
  163. Box::new(MariaDBUser::new(user_name.into(), self.command_runner)).into_action(self.symbol_runner)
  164. }
  165. pub fn get_git_checkout<'a, T: 'a + AsRef<str>>(&'a self, target: T, source: &'a str, branch: &'a str) -> Box<Action + 'a> {
  166. Box::new(GitCheckout::new(target, source, branch, self.command_runner)).into_action(self.symbol_runner)
  167. }
  168. pub fn get_owner<'a, F: 'a + AsRef<str>>(&'a self, file: F, user: &'a str) -> Box<Action + 'a> {
  169. Box::new(Owner::new(file, user.into(), self.command_runner)).into_action(self.symbol_runner)
  170. }
  171. pub fn get_file<'a, F: 'a + Deref<Target=str>, Q: 'a + AsRef<Path>>(&'a self, path: Q, content: F) -> Box<Action + 'a> {
  172. Box::new(File::new(path, content)).into_action(self.symbol_runner)
  173. }
  174. }