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.

338 lines
9.7 KiB

6 years ago
7 years ago
6 years ago
7 years ago
5 years ago
7 years ago
6 years ago
7 years ago
6 years ago
5 years ago
6 years ago
5 years ago
7 years ago
7 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
7 years ago
6 years ago
5 years ago
7 years ago
6 years ago
7 years ago
5 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
5 years ago
7 years ago
6 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
5 years ago
5 years ago
5 years ago
6 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
5 years ago
6 years ago
6 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
5 years ago
6 years ago
5 years ago
5 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::acme::{AcmeCert, AcmeCertChain};
  7. use symbols::cron::Cron;
  8. use symbols::file::File;
  9. use symbols::git::checkout::GitCheckout;
  10. use symbols::hook::Hook;
  11. use symbols::list::List;
  12. use symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser};
  13. use symbols::nginx::server::{php_server_config_snippet, server_config, NginxServer};
  14. use symbols::owner::Owner;
  15. use symbols::stored_directory::{StorageDirection, StoredDirectory};
  16. use symbols::systemd::reload::ReloadService;
  17. use symbols::tls::SelfSignedTlsCert;
  18. use symbols::{Symbol, SymbolRunner};
  19. pub trait Policy {
  20. fn user_name_for_host(&self, host_name: &'static str) -> String;
  21. fn home_for_user(&self, user_name: &str) -> String {
  22. format!("/home/{}", user_name)
  23. }
  24. }
  25. pub struct DefaultPolicy;
  26. impl Policy for DefaultPolicy {
  27. fn user_name_for_host(&self, host_name: &'static str) -> String {
  28. host_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
  29. }
  30. }
  31. pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy> {
  32. command_runner: &'a C,
  33. acme_command_runner: SetuidCommandRunner<'a, C>,
  34. symbol_runner: &'a R,
  35. policy: &'a P,
  36. }
  37. impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> {
  38. pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P) -> Self {
  39. let acme_user = "acme"; // FIXME: CONFIG
  40. let acme_command_runner = SetuidCommandRunner::new(acme_user, command_runner);
  41. SymbolFactory {
  42. command_runner,
  43. acme_command_runner,
  44. symbol_runner,
  45. policy,
  46. }
  47. }
  48. pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(
  49. &'c self,
  50. host: &'static str,
  51. nginx_server_symbol: S,
  52. ) -> impl Symbol + 'a {
  53. List::from((
  54. SelfSignedTlsCert::new(host.into(), self.command_runner),
  55. Hook::new(
  56. nginx_server_symbol,
  57. ReloadService::new("nginx", self.command_runner),
  58. ),
  59. AcmeCert::new(host.into(), &self.acme_command_runner),
  60. Hook::new(
  61. AcmeCertChain::new(host.into(), &self.acme_command_runner),
  62. ReloadService::new("nginx", self.command_runner),
  63. ),
  64. ))
  65. }
  66. pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> impl Symbol + 'a {
  67. File::new(
  68. "/etc/nginx/snippets/acme-challenge.conf",
  69. "location ^~ /.well-known/acme-challenge/ {
  70. alias /home/acme/challenges/;
  71. try_files $uri =404;
  72. }",
  73. )
  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) -> impl Symbol + 'a {
  79. let socket = self.get_php_fpm_pool_socket_path(user_name);
  80. 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. catch_workers_output = yes
  92. env[PATH] = /usr/local/bin:/usr/bin:/bin
  93. ",
  94. user_name, socket
  95. ),
  96. ),
  97. ReloadService::new("php7.0-fpm", self.command_runner),
  98. )
  99. }
  100. pub fn serve_php<'a>(
  101. &'a self,
  102. host_name: &'static str,
  103. root_dir: Cow<'a, str>,
  104. additional_config: &'a str,
  105. ) -> impl Symbol + 'a {
  106. let user_name = self.policy.user_name_for_host(host_name);
  107. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  108. List::from((
  109. self.get_php_fpm_pool(&user_name),
  110. self.get_nginx_acme_server(
  111. host_name,
  112. NginxServer::new_php(
  113. host_name,
  114. socket.into(),
  115. root_dir,
  116. self.command_runner,
  117. additional_config,
  118. ),
  119. ),
  120. ))
  121. }
  122. pub fn serve_wordpress<'a>(
  123. &'a self,
  124. host_name: &'static str,
  125. root_dir: Cow<'a, str>,
  126. ) -> impl Symbol + 'a {
  127. self.serve_php(
  128. host_name,
  129. root_dir,
  130. "
  131. location / {{
  132. try_files $uri $uri/ /index.php?$args;
  133. }}
  134. ",
  135. )
  136. }
  137. pub fn serve_dokuwiki<'a>(
  138. &'a self,
  139. host_name: &'static str,
  140. root_dir: &'static str,
  141. ) -> impl Symbol + 'a {
  142. let user_name = self.policy.user_name_for_host(host_name);
  143. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  144. List::from((
  145. self.get_php_fpm_pool(&user_name),
  146. self.get_nginx_acme_server(host_name,
  147. NginxServer::new(
  148. host_name,
  149. server_config(host_name, &format!("
  150. root {};
  151. index doku.php;
  152. location ~ [^/]\\.php(/|$) {{
  153. fastcgi_pass unix:{};
  154. include \"snippets/fastcgi-php.conf\";
  155. }}
  156. location ~ /(data/|conf/|bin/|inc/|install.php) {{ deny all; }}
  157. location / {{ try_files $uri $uri/ @dokuwiki; }}
  158. location @dokuwiki {{
  159. # rewrites \"doku.php/\" out of the URLs if you set the userewrite setting to .htaccess in dokuwiki config page
  160. rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
  161. rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
  162. rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
  163. rewrite ^/(.*) /doku.php?id=$1&$args last;
  164. }}
  165. ",
  166. root_dir,
  167. socket)),
  168. self.command_runner
  169. ))
  170. ))
  171. }
  172. pub fn serve_nextcloud<'a>(
  173. &'a self,
  174. host_name: &'static str,
  175. root_dir: Cow<'a, str>,
  176. ) -> impl Symbol + 'a {
  177. self.serve_php(
  178. host_name,
  179. root_dir,
  180. "
  181. client_max_body_size 500M;
  182. # Disable gzip to avoid the removal of the ETag header
  183. gzip off;
  184. rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect;
  185. rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect;
  186. rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect;
  187. error_page 403 /core/templates/403.php;
  188. error_page 404 /core/templates/404.php;
  189. location = /robots.txt {
  190. allow all;
  191. log_not_found off;
  192. access_log off;
  193. }
  194. location ~ ^/(?:\\.htaccess|data|config|db_structure\\.xml|README) {
  195. deny all;
  196. }
  197. location / {
  198. # The following 2 rules are only needed with webfinger
  199. rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
  200. rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
  201. rewrite ^/.well-known/carddav /remote.php/carddav/ redirect;
  202. rewrite ^/.well-known/caldav /remote.php/caldav/ redirect;
  203. rewrite ^(/core/doc/[^\\/]+/)$ $1/index.html;
  204. try_files $uri $uri/ /index.php;
  205. }
  206. # Adding the cache control header for js and css files
  207. # Make sure it is BELOW the location ~ \\.php(?:$|/) { block
  208. location ~* \\.(?:css|js)$ {
  209. add_header Cache-Control \"public, max-age=7200\";
  210. # Optional: Don't log access to assets
  211. access_log off;
  212. }
  213. # Optional: Don't log access to other assets
  214. location ~* \\.(?:jpg|jpeg|gif|bmp|ico|png|swf)$ {
  215. access_log off;
  216. }
  217. ",
  218. )
  219. }
  220. pub fn serve_redir<'a>(
  221. &'a self,
  222. host_name: &'static str,
  223. target: &'static str,
  224. ) -> impl Symbol + 'a {
  225. self.get_nginx_acme_server(
  226. host_name,
  227. NginxServer::new_redir(host_name, target, self.command_runner),
  228. )
  229. }
  230. pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> impl Symbol + 'a {
  231. self.get_nginx_acme_server(
  232. host_name,
  233. NginxServer::new_static(host_name, dir, self.command_runner),
  234. )
  235. }
  236. pub fn get_stored_directory<'a, T: Into<String>>(
  237. &'a self,
  238. storage_name: &'static str,
  239. target: T,
  240. ) -> (impl Symbol + 'a, impl Symbol + 'a) {
  241. let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
  242. let string_target = target.into();
  243. (
  244. StoredDirectory::new(
  245. string_target.clone().into(),
  246. data.clone(),
  247. StorageDirection::Save,
  248. self.command_runner,
  249. ),
  250. StoredDirectory::new(
  251. string_target.into(),
  252. data.clone(),
  253. StorageDirection::Load,
  254. self.command_runner,
  255. ),
  256. )
  257. }
  258. pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> impl Symbol + 'a {
  259. let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
  260. List::from((
  261. MariaDBDatabase::new(
  262. name.into(),
  263. db_dump
  264. .read_filename()
  265. .expect("Initial db dump missing")
  266. .into(),
  267. self.command_runner,
  268. ),
  269. DatabaseDump::new(name, db_dump, self.command_runner),
  270. ))
  271. }
  272. pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> impl Symbol + 'a {
  273. MariaDBUser::new(user_name.into(), self.command_runner)
  274. }
  275. pub fn get_git_checkout<'a, T: 'a + AsRef<str>>(
  276. &'a self,
  277. target: T,
  278. source: &'a str,
  279. branch: &'a str,
  280. ) -> impl Symbol + 'a {
  281. GitCheckout::new(target, source, branch, self.command_runner)
  282. }
  283. pub fn get_owner<'a, F: 'a + AsRef<str>>(&'a self, file: F, user: &'a str) -> impl Symbol + 'a {
  284. Owner::new(file, user.into(), self.command_runner)
  285. }
  286. pub fn get_file<'a, F: 'a + Deref<Target = str>, Q: 'a + AsRef<Path>>(
  287. &'a self,
  288. path: Q,
  289. content: F,
  290. ) -> impl Symbol + 'a {
  291. File::new(path, content)
  292. }
  293. pub fn get_cron<'a, T: 'a + Deref<Target = str>, U: 'a + Deref<Target = str>>(
  294. &'a self,
  295. user: U,
  296. content: T,
  297. ) -> impl Symbol + 'a {
  298. Cron::new(user, content, self.command_runner)
  299. }
  300. }