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.

276 lines
10 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
5 years ago
6 years ago
7 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
7 years ago
5 years ago
7 years ago
7 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 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, php_server_config_snippet};
  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. fn home_for_user(&self, user_name: &str) -> String {
  21. format!("/home/{}", user_name)
  22. }
  23. }
  24. pub struct DefaultPolicy;
  25. impl Policy for DefaultPolicy {
  26. fn user_name_for_host(&self, host_name: &'static str) -> String {
  27. host_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
  28. }
  29. }
  30. pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy>{
  31. command_runner: &'a C,
  32. acme_command_runner: SetuidCommandRunner<'a, C>,
  33. symbol_runner: &'a R,
  34. policy: &'a P
  35. }
  36. impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> {
  37. pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P) -> Self {
  38. let acme_user = "acme"; // FIXME: CONFIG
  39. let acme_command_runner = SetuidCommandRunner::new(acme_user, command_runner);
  40. SymbolFactory { command_runner, acme_command_runner, symbol_runner, policy }
  41. }
  42. pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(&'c self, host: &'static str, nginx_server_symbol: S) -> Box<Action + 'a> {
  43. Box::new(ListAction::new(vec![
  44. Box::new(SelfSignedTlsCert::new(
  45. host.into(),
  46. self.command_runner
  47. )).into_action(self.symbol_runner),
  48. Box::new(Hook::new(
  49. nginx_server_symbol,
  50. ReloadService::new("nginx", self.command_runner)
  51. )).into_action(self.symbol_runner),
  52. Box::new(AcmeCert::new(
  53. host.into(),
  54. &self.acme_command_runner
  55. )).into_action(self.symbol_runner),
  56. Box::new(Hook::new(
  57. AcmeCertChain::new(
  58. host.into(),
  59. &self.acme_command_runner
  60. ),
  61. ReloadService::new("nginx", self.command_runner)
  62. )).into_action(self.symbol_runner)
  63. ]))
  64. }
  65. pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> Box<Action + 'a> {
  66. Box::new(File::new(
  67. "/etc/nginx/snippets/acme-challenge.conf", "location ^~ /.well-known/acme-challenge/ {
  68. alias /home/acme/challenges/;
  69. try_files $uri =404;
  70. }"
  71. )).into_action(self.symbol_runner)
  72. }
  73. fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> String {
  74. format!("/run/php/{}.sock", user_name)
  75. }
  76. fn get_php_fpm_pool<'a>(&'a self, user_name: &str) -> Box<Action + 'a> {
  77. let socket = self.get_php_fpm_pool_socket_path(user_name);
  78. Box::new(Hook::new(
  79. File::new(
  80. format!("/etc/php/7.0/fpm/pool.d/{}.conf", user_name),
  81. format!(
  82. "[{0}]
  83. user = {0}
  84. group = www-data
  85. listen = {1}
  86. listen.owner = www-data
  87. pm = ondemand
  88. pm.max_children = 10"
  89. , user_name, socket)),
  90. ReloadService::new("php7.0-fpm", self.command_runner)
  91. )).into_action(self.symbol_runner)
  92. }
  93. pub fn serve_php<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<Action + 'a> {
  94. let user_name = self.policy.user_name_for_host(host_name);
  95. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  96. Box::new(ListAction::new(vec![
  97. self.get_php_fpm_pool(&user_name),
  98. self.get_nginx_acme_server(host_name,
  99. NginxServer::new_php(
  100. host_name,
  101. socket.into(),
  102. root_dir,
  103. self.command_runner
  104. )
  105. )
  106. ]))
  107. }
  108. pub fn serve_wordpress<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<Action + 'a> {
  109. let user_name = self.policy.user_name_for_host(host_name);
  110. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  111. Box::new(ListAction::new(vec![
  112. self.get_php_fpm_pool(&user_name),
  113. self.get_nginx_acme_server(host_name,
  114. NginxServer::new(
  115. host_name,
  116. server_config(host_name, &format!("{}
  117. location / {{
  118. try_files $uri $uri/ /index.php?$args;
  119. }}
  120. ", php_server_config_snippet(socket.into(), root_dir))),
  121. self.command_runner
  122. ))
  123. ]))
  124. }
  125. pub fn serve_dokuwiki<'a>(&'a self, host_name: &'static str, root_dir: &'static str) -> Box<Action + 'a> {
  126. let user_name = self.policy.user_name_for_host(host_name);
  127. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  128. Box::new(ListAction::new(vec![
  129. self.get_php_fpm_pool(&user_name),
  130. self.get_nginx_acme_server(host_name,
  131. NginxServer::new(
  132. host_name,
  133. server_config(host_name, &format!("
  134. root {};
  135. index doku.php;
  136. location ~ [^/]\\.php(/|$) {{
  137. fastcgi_pass unix:{};
  138. include \"snippets/fastcgi-php.conf\";
  139. }}
  140. location ~ /(data/|conf/|bin/|inc/|install.php) {{ deny all; }}
  141. location / {{ try_files $uri $uri/ @dokuwiki; }}
  142. location @dokuwiki {{
  143. # rewrites \"doku.php/\" out of the URLs if you set the userewrite setting to .htaccess in dokuwiki config page
  144. rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
  145. rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
  146. rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
  147. rewrite ^/(.*) /doku.php?id=$1&$args last;
  148. }}
  149. ",
  150. root_dir,
  151. socket)),
  152. self.command_runner
  153. ))
  154. ]))
  155. }
  156. pub fn serve_nextcloud<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<Action + 'a> {
  157. let user_name = self.policy.user_name_for_host(host_name);
  158. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  159. Box::new(ListAction::new(vec![
  160. self.get_php_fpm_pool(&user_name),
  161. self.get_nginx_acme_server(host_name,
  162. NginxServer::new(
  163. host_name,
  164. server_config(host_name, &format!("{}
  165. client_max_body_size 500M;
  166. # Disable gzip to avoid the removal of the ETag header
  167. gzip off;
  168. rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect;
  169. rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect;
  170. rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect;
  171. error_page 403 /core/templates/403.php;
  172. error_page 404 /core/templates/404.php;
  173. location = /robots.txt {{
  174. allow all;
  175. log_not_found off;
  176. access_log off;
  177. }}
  178. location ~ ^/(?:\\.htaccess|data|config|db_structure\\.xml|README) {{
  179. deny all;
  180. }}
  181. location / {{
  182. # The following 2 rules are only needed with webfinger
  183. rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
  184. rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
  185. rewrite ^/.well-known/carddav /remote.php/carddav/ redirect;
  186. rewrite ^/.well-known/caldav /remote.php/caldav/ redirect;
  187. rewrite ^(/core/doc/[^\\/]+/)$ $1/index.html;
  188. try_files $uri $uri/ /index.php;
  189. }}
  190. # Adding the cache control header for js and css files
  191. # Make sure it is BELOW the location ~ \\.php(?:$|/) {{ block
  192. location ~* \\.(?:css|js)$ {{
  193. add_header Cache-Control \"public, max-age=7200\";
  194. # Optional: Don't log access to assets
  195. access_log off;
  196. }}
  197. # Optional: Don't log access to other assets
  198. location ~* \\.(?:jpg|jpeg|gif|bmp|ico|png|swf)$ {{
  199. access_log off;
  200. }}
  201. ", php_server_config_snippet(socket.into(), root_dir))),
  202. self.command_runner
  203. ))
  204. ]))
  205. }
  206. pub fn serve_redir<'a>(&'a self, host_name: &'static str, target: &'static str) -> Box<Action + 'a> {
  207. self.get_nginx_acme_server(host_name, NginxServer::new_redir(host_name, target, self.command_runner))
  208. }
  209. pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> Box<Action + 'a> {
  210. self.get_nginx_acme_server(host_name, NginxServer::new_static(host_name, dir, self.command_runner))
  211. }
  212. pub fn get_stored_directory<'a, T: Into<String>>(&'a self, storage_name: &'static str, target: T) -> (Box<Action + 'a>, Box<Action + 'a>) {
  213. let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
  214. let string_target = target.into();
  215. (
  216. Box::new(StoredDirectory::new(string_target.clone().into(), data.clone(), StorageDirection::Save, self.command_runner)).into_action(self.symbol_runner),
  217. Box::new(StoredDirectory::new(string_target.into(), data.clone(), StorageDirection::Load, self.command_runner)).into_action(self.symbol_runner)
  218. )
  219. }
  220. pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> Box<Action + 'a> {
  221. let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
  222. Box::new(ListAction::new(vec![
  223. Box::new(MariaDBDatabase::new(name.into(), db_dump.read_filename().unwrap().into(), self.command_runner)).into_action(self.symbol_runner),
  224. Box::new(DatabaseDump::new(name, db_dump, self.command_runner)).into_action(self.symbol_runner)
  225. ]))
  226. }
  227. pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> Box<Action + 'a> {
  228. Box::new(MariaDBUser::new(user_name.into(), self.command_runner)).into_action(self.symbol_runner)
  229. }
  230. pub fn get_git_checkout<'a, T: 'a + AsRef<str>>(&'a self, target: T, source: &'a str, branch: &'a str) -> Box<Action + 'a> {
  231. Box::new(GitCheckout::new(target, source, branch, self.command_runner)).into_action(self.symbol_runner)
  232. }
  233. pub fn get_owner<'a, F: 'a + AsRef<str>>(&'a self, file: F, user: &'a str) -> Box<Action + 'a> {
  234. Box::new(Owner::new(file, user.into(), self.command_runner)).into_action(self.symbol_runner)
  235. }
  236. pub fn get_file<'a, F: 'a + Deref<Target=str>, Q: 'a + AsRef<Path>>(&'a self, path: Q, content: F) -> Box<Action + 'a> {
  237. Box::new(File::new(path, content)).into_action(self.symbol_runner)
  238. }
  239. }