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.

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