use std::error::Error; use std::fmt; use std::io; use std::ops::Deref; use command_runner::CommandRunner; use resources::Resource; use symbols::file::File as FileSymbol; use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; #[derive(Debug)] pub enum NginxServerError { ExecError(E), GenericError, } impl From for NginxServerError { fn from(err: io::Error) -> NginxServerError { NginxServerError::ExecError(err) } } impl Error for NginxServerError { fn description(&self) -> &str { match self { NginxServerError::ExecError(ref e) => e.description(), NginxServerError::GenericError => "Generic error", } } fn cause(&self) -> Option<&dyn Error> { match self { NginxServerError::ExecError(ref e) => Some(e), _ => None, } } } impl fmt::Display for NginxServerError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{}", self.description()) } } pub struct NginxServer<'a, C: 'a + CommandRunner, T> where T: Deref, { command_runner: &'a C, file: FileSymbol, } use std::borrow::Cow; pub fn server_config(domain: &str, content: &str) -> String { format!( "server {{ listen 443 ssl http2; server_name {0}; include \"snippets/acme-challenge.conf\"; ssl_certificate /etc/ssl/local_certs/{0}.chained.crt; ssl_certificate_key /etc/ssl/private/{0}.key; add_header Strict-Transport-Security \"max-age=31536000\"; {1} }} # Redirect all HTTP links to the matching HTTPS page server {{ listen 80; server_name {0}; include \"snippets/acme-challenge.conf\"; location / {{ return 301 https://$host$request_uri; }} }} ", domain, content ) } pub fn php_server_config_snippet<'a>( socket_path: Cow<'a, str>, static_path: Cow<'a, str>, ) -> String { format!( " root {}; index index.html index.php; location ~ [^/]\\.php(/|$) {{ fastcgi_pass unix:{}; include \"snippets/fastcgi-php.conf\"; }}", static_path, socket_path ) } pub trait SocketSpec { fn to_nginx(&self) -> String; } impl SocketSpec for &str { fn to_nginx(&self) -> String { format!("unix:{}:", self) } } pub struct LocalTcpSocket(usize); impl LocalTcpSocket { pub fn new(x: usize) -> Self { LocalTcpSocket(x) } } impl SocketSpec for LocalTcpSocket { fn to_nginx(&self) -> String { format!("localhost:{}", self.0) } } impl<'a, C: CommandRunner> NginxServer<'a, C, String> { pub fn new_redir(domain: &'a str, target: &'a str, command_runner: &'a C) -> Self { let content = server_config( domain, &format!( "location / {{ return 301 $scheme://{}$request_uri; }}", target ), ); NginxServer::new(domain, content, command_runner) } pub fn new_proxy( domain: &'a str, socket_path: S, static_path: &'a str, command_runner: &'a C, ) -> Self { let proxy_content = format!( "location / {{ try_files $uri @proxy; }} location @proxy {{ include fastcgi_params; proxy_pass http://{}; proxy_redirect off; }}", socket_path.to_nginx() ); let content = server_config( domain, &format!( " root {}; {} ", static_path, proxy_content ), ); NginxServer::new(domain, content, command_runner) } pub fn new_php( domain: &'a str, socket_path: Cow<'a, str>, static_path: Cow<'a, str>, command_runner: &'a C, additional_config: &'a str, ) -> Self { let content = server_config( domain, &(php_server_config_snippet(socket_path, static_path) + additional_config), ); NginxServer::new(domain, content, command_runner) } pub fn new_static(domain: &'a str, static_path: &'a str, command_runner: &'a C) -> Self { let content = server_config( domain, &format!( " root {}; try_files $uri $uri/ $uri.html =404; ", static_path ), ); NginxServer::new(domain, content, command_runner) } pub fn new(domain: &'a str, content: String, command_runner: &'a C) -> Self { let file_path = String::from("/etc/nginx/sites-enabled/") + domain; NginxServer { command_runner, file: FileSymbol::new(file_path, content), } } } impl<'a, C: CommandRunner, T> Symbol for NginxServer<'a, C, T> where T: Deref, { fn target_reached(&self) -> Result> { if !self.file.target_reached()? { return Ok(false); } // TODO: Could try to find out if the server is in the live config Ok(true) } fn execute(&self) -> Result<(), Box> { self.file.execute()?; self .command_runner .run_successfully("systemctl", &["reload-or-restart", "nginx"]) } fn get_prerequisites(&self) -> Vec { self.file.get_prerequisites() } fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { Box::new(SymbolAction::new(runner, self)) } fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box where Self: 'b, { Box::new(OwnedSymbolAction::new(runner, *self)) } } impl<'a, C: CommandRunner, T> fmt::Display for NginxServer<'a, C, T> where T: Deref, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "Nginx server config") } }