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.

419 lines
12 KiB

6 years ago
6 years ago
5 years ago
5 years ago
7 years ago
6 years ago
5 years ago
5 years ago
6 years ago
6 years ago
5 years ago
7 years ago
5 years ago
6 years ago
5 years ago
7 years ago
5 years ago
5 years ago
5 years ago
5 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
5 years ago
5 years ago
7 years ago
5 years ago
7 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
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
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
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
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
7 years ago
  1. use std::borrow::Cow;
  2. use std::path::Path;
  3. use std::path::PathBuf;
  4. use crate::command_runner::CommandRunner;
  5. use crate::storage::{SimpleStorage, Storage};
  6. use crate::symbols::acme::Factory as AcmeFactory;
  7. use crate::symbols::cron::Cron;
  8. use crate::symbols::file::File;
  9. use crate::symbols::git::checkout::GitCheckout;
  10. use crate::symbols::hook::Hook;
  11. use crate::symbols::list::List;
  12. use crate::symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser};
  13. use crate::symbols::nginx::server::{server_config, NginxServer};
  14. use crate::symbols::owner::Owner;
  15. use crate::symbols::stored_directory::{StorageDirection, StoredDirectory};
  16. use crate::symbols::systemd::reload::ReloadService;
  17. use crate::symbols::systemd::user_service::UserService;
  18. use crate::symbols::tls::SelfSignedTlsCert;
  19. use crate::symbols::{Symbol, SymbolRunner};
  20. pub trait Policy {
  21. fn user_name_for_host(&self, host_name: &'static str) -> String {
  22. host_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
  23. }
  24. fn home_for_user(&self, user_name: &str) -> PathBuf {
  25. format!("/home/{}", user_name).into()
  26. }
  27. fn socket_path(&self, user_name: &str, service_name: &str) -> PathBuf {
  28. format!("/var/tmp/{}-{}.socket", user_name, service_name).into()
  29. }
  30. fn acme_user(&self) -> Cow<str> {
  31. "acme".into()
  32. }
  33. }
  34. pub struct DefaultPolicy;
  35. impl Policy for DefaultPolicy {}
  36. pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy> {
  37. command_runner: &'a C,
  38. acme_factory: AcmeFactory<'a, Cow<'a, str>, PathBuf, &'a str, C>,
  39. symbol_runner: &'a R,
  40. policy: &'a P,
  41. }
  42. impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> {
  43. pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P, cert: &'b str) -> Self {
  44. let acme_user = policy.acme_user();
  45. let acme_home = policy.home_for_user(&acme_user);
  46. let acme_factory = AcmeFactory::new(acme_user, acme_home, cert, command_runner);
  47. SymbolFactory {
  48. command_runner,
  49. acme_factory,
  50. symbol_runner,
  51. policy,
  52. }
  53. }
  54. pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(
  55. &'c self,
  56. host: &'static str,
  57. nginx_server_symbol: S,
  58. ) -> impl Symbol + 'a {
  59. List::from((
  60. SelfSignedTlsCert::new(host, self.command_runner),
  61. Hook::new(
  62. nginx_server_symbol,
  63. ReloadService::new("nginx", self.command_runner),
  64. ),
  65. Hook::new(
  66. self.acme_factory.get_cert(host),
  67. ReloadService::new("nginx", self.command_runner),
  68. ),
  69. ))
  70. }
  71. pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> impl Symbol + 'a {
  72. File::new(
  73. "/etc/nginx/snippets/acme-challenge.conf",
  74. format!(
  75. "location ^~ /.well-known/acme-challenge/ {{
  76. alias {}/challenges/;
  77. try_files $uri =404;
  78. }}",
  79. self.acme_factory.get_challenges_dir().to_str().unwrap()
  80. ),
  81. )
  82. }
  83. fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> PathBuf {
  84. format!("/run/php/{}.sock", user_name).into()
  85. }
  86. fn get_php_fpm_pool<'a>(&'a self, user_name: &str) -> impl Symbol + 'a {
  87. let socket = self.get_php_fpm_pool_socket_path(user_name);
  88. Hook::new(
  89. File::new(
  90. format!("/etc/php/7.0/fpm/pool.d/{}.conf", user_name),
  91. format!(
  92. "[{0}]
  93. user = {0}
  94. group = www-data
  95. listen = {1}
  96. listen.owner = www-data
  97. pm = ondemand
  98. pm.max_children = 10
  99. catch_workers_output = yes
  100. env[PATH] = /usr/local/bin:/usr/bin:/bin
  101. ",
  102. user_name,
  103. socket.to_str().unwrap()
  104. ),
  105. ),
  106. ReloadService::new("php7.0-fpm", self.command_runner),
  107. )
  108. }
  109. pub fn serve_php<'a, ROOT: AsRef<Path>>(
  110. &'a self,
  111. host_name: &'static str,
  112. root_dir: ROOT,
  113. additional_config: &'a str,
  114. ) -> impl Symbol + 'a {
  115. let user_name = self.policy.user_name_for_host(host_name);
  116. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  117. List::from((
  118. self.get_php_fpm_pool(&user_name),
  119. self.get_nginx_acme_server(
  120. host_name,
  121. NginxServer::new_php(
  122. host_name,
  123. socket,
  124. root_dir,
  125. self.command_runner,
  126. additional_config,
  127. ),
  128. ),
  129. ))
  130. }
  131. pub fn serve_wordpress<'a, ROOT: 'a + AsRef<Path>>(
  132. &'a self,
  133. host_name: &'static str,
  134. root_dir: ROOT,
  135. ) -> impl Symbol + 'a {
  136. self.serve_php(
  137. host_name,
  138. root_dir,
  139. "
  140. location / {{
  141. try_files $uri $uri/ /index.php?$args;
  142. }}
  143. ",
  144. )
  145. }
  146. pub fn serve_dokuwiki<'a>(
  147. &'a self,
  148. host_name: &'static str,
  149. root_dir: &'static str,
  150. ) -> impl Symbol + 'a {
  151. let user_name = self.policy.user_name_for_host(host_name);
  152. let socket = self.get_php_fpm_pool_socket_path(&user_name);
  153. List::from((
  154. self.get_php_fpm_pool(&user_name),
  155. self.get_nginx_acme_server(host_name,
  156. NginxServer::new(
  157. host_name,
  158. server_config(host_name, &format!("
  159. root {};
  160. index doku.php;
  161. location ~ [^/]\\.php(/|$) {{
  162. fastcgi_pass unix:{};
  163. include \"snippets/fastcgi-php.conf\";
  164. }}
  165. location ~ /(data/|conf/|bin/|inc/|install.php) {{ deny all; }}
  166. location / {{ try_files $uri $uri/ @dokuwiki; }}
  167. location @dokuwiki {{
  168. # rewrites \"doku.php/\" out of the URLs if you set the userewrite setting to .htaccess in dokuwiki config page
  169. rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
  170. rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
  171. rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
  172. rewrite ^/(.*) /doku.php?id=$1&$args last;
  173. }}
  174. ",
  175. root_dir,
  176. socket.to_str().unwrap())),
  177. self.command_runner
  178. ))
  179. ))
  180. }
  181. pub fn serve_nextcloud<'a, ROOT: 'a + AsRef<Path>>(
  182. &'a self,
  183. host_name: &'static str,
  184. root_dir: ROOT,
  185. ) -> impl Symbol + 'a {
  186. self.serve_php(
  187. host_name,
  188. root_dir,
  189. "
  190. client_max_body_size 500M;
  191. # Disable gzip to avoid the removal of the ETag header
  192. gzip off;
  193. rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect;
  194. rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect;
  195. rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect;
  196. error_page 403 /core/templates/403.php;
  197. error_page 404 /core/templates/404.php;
  198. location = /robots.txt {
  199. allow all;
  200. log_not_found off;
  201. access_log off;
  202. }
  203. location ~ ^/(?:\\.htaccess|data|config|db_structure\\.xml|README) {
  204. deny all;
  205. }
  206. location / {
  207. # The following 2 rules are only needed with webfinger
  208. rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
  209. rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
  210. rewrite ^/.well-known/carddav /remote.php/carddav/ redirect;
  211. rewrite ^/.well-known/caldav /remote.php/caldav/ redirect;
  212. rewrite ^(/core/doc/[^\\/]+/)$ $1/index.html;
  213. try_files $uri $uri/ /index.php;
  214. }
  215. # Adding the cache control header for js and css files
  216. # Make sure it is BELOW the location ~ \\.php(?:$|/) { block
  217. location ~* \\.(?:css|js)$ {
  218. add_header Cache-Control \"public, max-age=7200\";
  219. # Optional: Don't log access to assets
  220. access_log off;
  221. }
  222. # Optional: Don't log access to other assets
  223. location ~* \\.(?:jpg|jpeg|gif|bmp|ico|png|swf)$ {
  224. access_log off;
  225. }
  226. ",
  227. )
  228. }
  229. pub fn serve_redir<'a>(
  230. &'a self,
  231. host_name: &'static str,
  232. target: &'static str,
  233. ) -> impl Symbol + 'a {
  234. self.get_nginx_acme_server(
  235. host_name,
  236. NginxServer::new_redir(host_name, target, self.command_runner),
  237. )
  238. }
  239. pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> impl Symbol + 'a {
  240. self.get_nginx_acme_server(
  241. host_name,
  242. NginxServer::new_static(host_name, dir, self.command_runner),
  243. )
  244. }
  245. pub fn get_stored_directory<'a, T: Into<String>>(
  246. &'a self,
  247. storage_name: &'static str,
  248. target: T,
  249. ) -> (impl Symbol + 'a, impl Symbol + 'a) {
  250. let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
  251. let string_target = target.into();
  252. (
  253. StoredDirectory::new(
  254. string_target.clone(),
  255. data.clone(),
  256. StorageDirection::Save,
  257. self.command_runner,
  258. ),
  259. StoredDirectory::new(
  260. string_target,
  261. data.clone(),
  262. StorageDirection::Load,
  263. self.command_runner,
  264. ),
  265. )
  266. }
  267. pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> impl Symbol + 'a {
  268. let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
  269. List::from((
  270. MariaDBDatabase::new(
  271. name,
  272. db_dump.read_filename().expect("Initial db dump missing"),
  273. self.command_runner,
  274. ),
  275. DatabaseDump::new(name, db_dump, self.command_runner),
  276. ))
  277. }
  278. pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> impl Symbol + 'a {
  279. MariaDBUser::new(user_name, self.command_runner)
  280. }
  281. pub fn get_git_checkout<'a, T: 'a + AsRef<Path>>(
  282. &'a self,
  283. target: T,
  284. source: &'a str,
  285. branch: &'a str,
  286. ) -> impl Symbol + 'a {
  287. GitCheckout::new(target, source, branch, self.command_runner)
  288. }
  289. pub fn get_owner<'a, U: 'a + AsRef<str>, F: 'a + AsRef<Path>>(
  290. &'a self,
  291. file: F,
  292. user: U,
  293. ) -> impl Symbol + 'a {
  294. Owner::new(file, user, self.command_runner)
  295. }
  296. pub fn get_file<'a, F: 'a + AsRef<str>, Q: 'a + AsRef<Path>>(
  297. &'a self,
  298. path: Q,
  299. content: F,
  300. ) -> impl Symbol + 'a {
  301. File::new(path, content)
  302. }
  303. pub fn get_cron<'a, T: 'a + AsRef<str>, U: 'a + AsRef<str>>(
  304. &'a self,
  305. user: U,
  306. content: T,
  307. ) -> impl Symbol + 'a {
  308. Cron::new(user, content, self.command_runner)
  309. }
  310. pub fn get_acme_user<'a>(&'a self) -> impl Symbol + 'a {
  311. self.acme_factory.get_init()
  312. }
  313. pub fn get_systemd_user_service<'a, U: 'a + AsRef<str> + Clone>(
  314. &'a self,
  315. user_name: U,
  316. service_name: &'a str,
  317. config: String,
  318. ) -> impl Symbol + 'a {
  319. let socket_path = self.policy.socket_path(user_name.as_ref(), service_name);
  320. let home = self.policy.home_for_user(user_name.as_ref());
  321. UserService::new(
  322. socket_path,
  323. &home,
  324. user_name,
  325. service_name,
  326. self.command_runner,
  327. config,
  328. )
  329. }
  330. fn get_nodejs_systemd_user_service<'a, U: 'a + AsRef<str> + Clone>(
  331. &'a self,
  332. user_name: U,
  333. service_name: &'a str,
  334. path: &'a Path,
  335. ) -> impl Symbol + 'a {
  336. let socket_path = self.policy.socket_path(user_name.as_ref(), service_name);
  337. let home = self.policy.home_for_user(user_name.as_ref());
  338. UserService::new_nodejs(
  339. &home,
  340. user_name,
  341. service_name,
  342. path,
  343. self.command_runner,
  344. socket_path,
  345. )
  346. }
  347. pub fn proxy_socket<'a, T: AsRef<Path>>(
  348. &'a self,
  349. host_name: &'static str,
  350. service_name: &'a str,
  351. root_dir: T,
  352. ) -> impl Symbol + 'a {
  353. let user_name = self.policy.user_name_for_host(host_name);
  354. let socket_path = self.policy.socket_path(&user_name, service_name);
  355. self.get_nginx_acme_server(
  356. host_name,
  357. NginxServer::new_proxy(host_name, socket_path, root_dir, self.command_runner),
  358. )
  359. }
  360. pub fn serve_nodejs<'a, 'c: 'a, S: 'a + Symbol, SP: 'a + AsRef<Path>>(
  361. &'c self,
  362. host: &'static str,
  363. service_name: &'a str,
  364. path: &'a Path,
  365. static_path: SP,
  366. nodejs_symbol: S,
  367. ) -> impl Symbol + 'a {
  368. let user_name = self.policy.user_name_for_host(host);
  369. List::from((
  370. Hook::new(
  371. nodejs_symbol,
  372. self.get_nodejs_systemd_user_service(user_name, service_name, path),
  373. ),
  374. self.proxy_socket(host, service_name, static_path),
  375. ))
  376. }
  377. }