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.

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