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.

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