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