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.

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