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.

417 lines
12 KiB

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