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.

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