use std::borrow::Cow; use std::ops::Deref; use std::path::Path; use command_runner::{CommandRunner, SetuidCommandRunner}; use storage::{SimpleStorage, Storage}; use symbols::{Action, Symbol, SymbolRunner}; use symbols::acme::{AcmeCert, AcmeCertChain}; use symbols::file::File; use symbols::git::checkout::GitCheckout; use symbols::hook::Hook; use symbols::list::ListAction; use symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser}; use symbols::nginx::server::{NginxServer, server_config}; use symbols::owner::Owner; use symbols::stored_directory::{StoredDirectory, StorageDirection}; use symbols::systemd::reload::ReloadService; use symbols::tls::SelfSignedTlsCert; pub trait Policy { fn user_name_for_host(&self, host_name: &'static str) -> String; } pub struct DefaultPolicy; impl Policy for DefaultPolicy { fn user_name_for_host(&self, host_name: &'static str) -> String { host_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part) } } pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy>{ command_runner: &'a C, acme_command_runner: SetuidCommandRunner<'a, C>, symbol_runner: &'a R, policy: &'a P } impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> { pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P) -> Self { let acme_user = "acme"; // FIXME: CONFIG let acme_command_runner = SetuidCommandRunner::new(acme_user, command_runner); SymbolFactory { command_runner, acme_command_runner, symbol_runner, policy } } pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(&'c self, host: &'static str, nginx_server_symbol: S) -> Box { Box::new(ListAction::new(vec![ Box::new(SelfSignedTlsCert::new( host.into(), self.command_runner )).into_action(self.symbol_runner), Box::new(Hook::new( nginx_server_symbol, ReloadService::new("nginx", self.command_runner) )).into_action(self.symbol_runner), Box::new(AcmeCert::new( host.into(), &self.acme_command_runner )).into_action(self.symbol_runner), Box::new(Hook::new( AcmeCertChain::new( host.into(), &self.acme_command_runner ), ReloadService::new("nginx", self.command_runner) )).into_action(self.symbol_runner) ])) } pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> Box { Box::new(File::new( "/etc/nginx/snippets/acme-challenge.conf", "location ^~ /.well-known/acme-challenge/ { alias /home/acme/challenges/; try_files $uri =404; }" )).into_action(self.symbol_runner) } fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> String { format!("/run/php/{}.sock", user_name) } fn get_php_fpm_pool<'a>(&'a self, user_name: &str) -> Box { let socket = self.get_php_fpm_pool_socket_path(user_name); Box::new(Hook::new( File::new( format!("/etc/php/7.0/fpm/pool.d/{}.conf", user_name), format!( "[{0}] user = {0} group = www-data listen = {1} listen.owner = www-data pm = ondemand pm.max_children = 10" , user_name, socket)), ReloadService::new("php7.0-fpm", self.command_runner) )).into_action(self.symbol_runner) } pub fn serve_php<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box { let user_name = self.policy.user_name_for_host(host_name); let socket = self.get_php_fpm_pool_socket_path(&user_name); Box::new(ListAction::new(vec![ self.get_php_fpm_pool(&user_name), self.get_nginx_acme_server(host_name, NginxServer::new_php( host_name, socket.into(), root_dir, self.command_runner ) ) ])) } pub fn serve_dokuwiki<'a>(&'a self, host_name: &'static str, root_dir: &'static str) -> Box { let user_name = self.policy.user_name_for_host(host_name); let socket = self.get_php_fpm_pool_socket_path(&user_name); Box::new(ListAction::new(vec![ self.get_php_fpm_pool(&user_name), self.get_nginx_acme_server(host_name, NginxServer::new( host_name, server_config("hostname", &format!(" root {}; index doku.php; location ~ [^/]\\.php(/|$) {{ fastcgi_pass unix:{}; include \"snippets/fastcgi-php.conf\"; }} location ~ /(data/|conf/|bin/|inc/|install.php) {{ deny all; }} location / {{ try_files $uri $uri/ @dokuwiki; }} location @dokuwiki {{ # rewrites \"doku.php/\" out of the URLs if you set the userewrite setting to .htaccess in dokuwiki config page rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last; rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last; rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last; rewrite ^/(.*) /doku.php?id=$1&$args last; }} ", root_dir, socket)), self.command_runner )) ])) } pub fn serve_redir<'a>(&'a self, host_name: &'static str, target: &'static str) -> Box { self.get_nginx_acme_server(host_name, NginxServer::new_redir(host_name, target, self.command_runner)) } pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> Box { self.get_nginx_acme_server(host_name, NginxServer::new_static(host_name, dir, self.command_runner)) } pub fn get_stored_directory<'a, T: Into>(&'a self, storage_name: &'static str, target: T) -> (Box, Box) { let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string()); let string_target = target.into(); ( Box::new(StoredDirectory::new(string_target.clone().into(), data.clone(), StorageDirection::Save, self.command_runner)).into_action(self.symbol_runner), Box::new(StoredDirectory::new(string_target.into(), data.clone(), StorageDirection::Load, self.command_runner)).into_action(self.symbol_runner) ) } pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> Box { let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name)); Box::new(ListAction::new(vec![ Box::new(MariaDBDatabase::new(name.into(), db_dump.read_filename().unwrap().into(), self.command_runner)).into_action(self.symbol_runner), Box::new(DatabaseDump::new(name, db_dump, self.command_runner)).into_action(self.symbol_runner) ])) } pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> Box { Box::new(MariaDBUser::new(user_name.into(), self.command_runner)).into_action(self.symbol_runner) } pub fn get_git_checkout<'a, T: 'a + AsRef>(&'a self, target: T, source: &'a str, branch: &'a str) -> Box { Box::new(GitCheckout::new(target, source, branch, self.command_runner)).into_action(self.symbol_runner) } pub fn get_owner<'a, F: 'a + AsRef>(&'a self, file: F, user: &'a str) -> Box { Box::new(Owner::new(file, user.into(), self.command_runner)).into_action(self.symbol_runner) } pub fn get_file<'a, F: 'a + Deref, Q: 'a + AsRef>(&'a self, path: Q, content: F) -> Box { Box::new(File::new(path, content)).into_action(self.symbol_runner) } }