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.
419 lines
12 KiB
419 lines
12 KiB
use std::borrow::Cow;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
use crate::command_runner::CommandRunner;
|
|
use crate::storage::{SimpleStorage, Storage};
|
|
use crate::symbols::acme::Factory as AcmeFactory;
|
|
use crate::symbols::cron::Cron;
|
|
use crate::symbols::file::File;
|
|
use crate::symbols::git::checkout::GitCheckout;
|
|
use crate::symbols::hook::Hook;
|
|
use crate::symbols::list::List;
|
|
use crate::symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser};
|
|
use crate::symbols::nginx::server::{server_config, NginxServer};
|
|
use crate::symbols::owner::Owner;
|
|
use crate::symbols::stored_directory::{StorageDirection, StoredDirectory};
|
|
use crate::symbols::systemd::reload::ReloadService;
|
|
use crate::symbols::systemd::user_service::UserService;
|
|
use crate::symbols::tls::SelfSignedTlsCert;
|
|
use crate::symbols::{Symbol, SymbolRunner};
|
|
|
|
pub trait Policy {
|
|
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)
|
|
}
|
|
fn home_for_user(&self, user_name: &str) -> PathBuf {
|
|
format!("/home/{}", user_name).into()
|
|
}
|
|
fn socket_path(&self, user_name: &str, service_name: &str) -> PathBuf {
|
|
format!("/var/tmp/{}-{}.socket", user_name, service_name).into()
|
|
}
|
|
fn acme_user(&self) -> Cow<str> {
|
|
"acme".into()
|
|
}
|
|
}
|
|
|
|
pub struct DefaultPolicy;
|
|
|
|
impl Policy for DefaultPolicy {}
|
|
|
|
pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy> {
|
|
command_runner: &'a C,
|
|
acme_factory: AcmeFactory<'a, Cow<'a, str>, PathBuf, &'a str, 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, cert: &'b str) -> Self {
|
|
let acme_user = policy.acme_user();
|
|
let acme_home = policy.home_for_user(&acme_user);
|
|
let acme_factory = AcmeFactory::new(acme_user, acme_home, cert, command_runner);
|
|
SymbolFactory {
|
|
command_runner,
|
|
acme_factory,
|
|
symbol_runner,
|
|
policy,
|
|
}
|
|
}
|
|
|
|
pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(
|
|
&'c self,
|
|
host: &'static str,
|
|
nginx_server_symbol: S,
|
|
) -> impl Symbol + 'a {
|
|
List::from((
|
|
SelfSignedTlsCert::new(host, self.command_runner),
|
|
Hook::new(
|
|
nginx_server_symbol,
|
|
ReloadService::new("nginx", self.command_runner),
|
|
),
|
|
Hook::new(
|
|
self.acme_factory.get_cert(host),
|
|
ReloadService::new("nginx", self.command_runner),
|
|
),
|
|
))
|
|
}
|
|
pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> impl Symbol + 'a {
|
|
File::new(
|
|
"/etc/nginx/snippets/acme-challenge.conf",
|
|
format!(
|
|
"location ^~ /.well-known/acme-challenge/ {{
|
|
alias {}/challenges/;
|
|
try_files $uri =404;
|
|
}}",
|
|
self.acme_factory.get_challenges_dir().to_str().unwrap()
|
|
),
|
|
)
|
|
}
|
|
|
|
fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> PathBuf {
|
|
format!("/run/php/{}.sock", user_name).into()
|
|
}
|
|
|
|
fn get_php_fpm_pool<'a>(&'a self, user_name: &str) -> impl Symbol + 'a {
|
|
let socket = self.get_php_fpm_pool_socket_path(user_name);
|
|
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
|
|
catch_workers_output = yes
|
|
env[PATH] = /usr/local/bin:/usr/bin:/bin
|
|
",
|
|
user_name,
|
|
socket.to_str().unwrap()
|
|
),
|
|
),
|
|
ReloadService::new("php7.0-fpm", self.command_runner),
|
|
)
|
|
}
|
|
|
|
pub fn serve_php<'a, ROOT: AsRef<Path>>(
|
|
&'a self,
|
|
host_name: &'static str,
|
|
root_dir: ROOT,
|
|
additional_config: &'a str,
|
|
) -> impl Symbol + 'a {
|
|
let user_name = self.policy.user_name_for_host(host_name);
|
|
let socket = self.get_php_fpm_pool_socket_path(&user_name);
|
|
List::from((
|
|
self.get_php_fpm_pool(&user_name),
|
|
self.get_nginx_acme_server(
|
|
host_name,
|
|
NginxServer::new_php(
|
|
host_name,
|
|
socket,
|
|
root_dir,
|
|
self.command_runner,
|
|
additional_config,
|
|
),
|
|
),
|
|
))
|
|
}
|
|
|
|
pub fn serve_wordpress<'a, ROOT: 'a + AsRef<Path>>(
|
|
&'a self,
|
|
host_name: &'static str,
|
|
root_dir: ROOT,
|
|
) -> impl Symbol + 'a {
|
|
self.serve_php(
|
|
host_name,
|
|
root_dir,
|
|
"
|
|
location / {{
|
|
try_files $uri $uri/ /index.php?$args;
|
|
}}
|
|
",
|
|
)
|
|
}
|
|
|
|
pub fn serve_dokuwiki<'a>(
|
|
&'a self,
|
|
host_name: &'static str,
|
|
root_dir: &'static str,
|
|
) -> impl Symbol + 'a {
|
|
let user_name = self.policy.user_name_for_host(host_name);
|
|
let socket = self.get_php_fpm_pool_socket_path(&user_name);
|
|
List::from((
|
|
self.get_php_fpm_pool(&user_name),
|
|
self.get_nginx_acme_server(host_name,
|
|
NginxServer::new(
|
|
host_name,
|
|
server_config(host_name, &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.to_str().unwrap())),
|
|
self.command_runner
|
|
))
|
|
))
|
|
}
|
|
|
|
pub fn serve_nextcloud<'a, ROOT: 'a + AsRef<Path>>(
|
|
&'a self,
|
|
host_name: &'static str,
|
|
root_dir: ROOT,
|
|
) -> impl Symbol + 'a {
|
|
self.serve_php(
|
|
host_name,
|
|
root_dir,
|
|
"
|
|
client_max_body_size 500M;
|
|
|
|
# Disable gzip to avoid the removal of the ETag header
|
|
gzip off;
|
|
|
|
rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect;
|
|
rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect;
|
|
rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect;
|
|
|
|
error_page 403 /core/templates/403.php;
|
|
error_page 404 /core/templates/404.php;
|
|
|
|
location = /robots.txt {
|
|
allow all;
|
|
log_not_found off;
|
|
access_log off;
|
|
}
|
|
|
|
location ~ ^/(?:\\.htaccess|data|config|db_structure\\.xml|README) {
|
|
deny all;
|
|
}
|
|
|
|
location / {
|
|
# The following 2 rules are only needed with webfinger
|
|
rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
|
|
rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
|
|
|
|
rewrite ^/.well-known/carddav /remote.php/carddav/ redirect;
|
|
rewrite ^/.well-known/caldav /remote.php/caldav/ redirect;
|
|
|
|
rewrite ^(/core/doc/[^\\/]+/)$ $1/index.html;
|
|
|
|
try_files $uri $uri/ /index.php;
|
|
}
|
|
|
|
# Adding the cache control header for js and css files
|
|
# Make sure it is BELOW the location ~ \\.php(?:$|/) { block
|
|
location ~* \\.(?:css|js)$ {
|
|
add_header Cache-Control \"public, max-age=7200\";
|
|
# Optional: Don't log access to assets
|
|
access_log off;
|
|
}
|
|
|
|
# Optional: Don't log access to other assets
|
|
location ~* \\.(?:jpg|jpeg|gif|bmp|ico|png|swf)$ {
|
|
access_log off;
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
pub fn serve_redir<'a>(
|
|
&'a self,
|
|
host_name: &'static str,
|
|
target: &'static str,
|
|
) -> impl Symbol + 'a {
|
|
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) -> impl Symbol + 'a {
|
|
self.get_nginx_acme_server(
|
|
host_name,
|
|
NginxServer::new_static(host_name, dir, self.command_runner),
|
|
)
|
|
}
|
|
|
|
pub fn get_stored_directory<'a, T: Into<String>>(
|
|
&'a self,
|
|
storage_name: &'static str,
|
|
target: T,
|
|
) -> (impl Symbol + 'a, impl Symbol + 'a) {
|
|
let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
|
|
let string_target = target.into();
|
|
(
|
|
StoredDirectory::new(
|
|
string_target.clone(),
|
|
data.clone(),
|
|
StorageDirection::Save,
|
|
self.command_runner,
|
|
),
|
|
StoredDirectory::new(
|
|
string_target,
|
|
data.clone(),
|
|
StorageDirection::Load,
|
|
self.command_runner,
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> impl Symbol + 'a {
|
|
let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
|
|
List::from((
|
|
MariaDBDatabase::new(
|
|
name,
|
|
db_dump.read_filename().expect("Initial db dump missing"),
|
|
self.command_runner,
|
|
),
|
|
DatabaseDump::new(name, db_dump, self.command_runner),
|
|
))
|
|
}
|
|
|
|
pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> impl Symbol + 'a {
|
|
MariaDBUser::new(user_name, self.command_runner)
|
|
}
|
|
|
|
pub fn get_git_checkout<'a, T: 'a + AsRef<Path>>(
|
|
&'a self,
|
|
target: T,
|
|
source: &'a str,
|
|
branch: &'a str,
|
|
) -> impl Symbol + 'a {
|
|
GitCheckout::new(target, source, branch, self.command_runner)
|
|
}
|
|
|
|
pub fn get_owner<'a, U: 'a + AsRef<str>, F: 'a + AsRef<Path>>(
|
|
&'a self,
|
|
file: F,
|
|
user: U,
|
|
) -> impl Symbol + 'a {
|
|
Owner::new(file, user, self.command_runner)
|
|
}
|
|
|
|
pub fn get_file<'a, F: 'a + AsRef<str>, Q: 'a + AsRef<Path>>(
|
|
&'a self,
|
|
path: Q,
|
|
content: F,
|
|
) -> impl Symbol + 'a {
|
|
File::new(path, content)
|
|
}
|
|
|
|
pub fn get_cron<'a, T: 'a + AsRef<str>, U: 'a + AsRef<str>>(
|
|
&'a self,
|
|
user: U,
|
|
content: T,
|
|
) -> impl Symbol + 'a {
|
|
Cron::new(user, content, self.command_runner)
|
|
}
|
|
|
|
pub fn get_acme_user<'a>(&'a self) -> impl Symbol + 'a {
|
|
self.acme_factory.get_init()
|
|
}
|
|
|
|
pub fn get_systemd_user_service<'a, U: 'a + AsRef<str> + Clone>(
|
|
&'a self,
|
|
user_name: U,
|
|
service_name: &'a str,
|
|
config: String,
|
|
) -> impl Symbol + 'a {
|
|
let socket_path = self.policy.socket_path(user_name.as_ref(), service_name);
|
|
let home = self.policy.home_for_user(user_name.as_ref());
|
|
UserService::new(
|
|
socket_path,
|
|
&home,
|
|
user_name,
|
|
service_name,
|
|
self.command_runner,
|
|
config,
|
|
)
|
|
}
|
|
|
|
fn get_nodejs_systemd_user_service<'a, U: 'a + AsRef<str> + Clone>(
|
|
&'a self,
|
|
user_name: U,
|
|
service_name: &'a str,
|
|
path: &'a Path,
|
|
) -> impl Symbol + 'a {
|
|
let socket_path = self.policy.socket_path(user_name.as_ref(), service_name);
|
|
let home = self.policy.home_for_user(user_name.as_ref());
|
|
UserService::new_nodejs(
|
|
&home,
|
|
user_name,
|
|
service_name,
|
|
path,
|
|
self.command_runner,
|
|
socket_path,
|
|
)
|
|
}
|
|
|
|
pub fn proxy_socket<'a, T: AsRef<Path>>(
|
|
&'a self,
|
|
host_name: &'static str,
|
|
service_name: &'a str,
|
|
root_dir: T,
|
|
) -> impl Symbol + 'a {
|
|
let user_name = self.policy.user_name_for_host(host_name);
|
|
let socket_path = self.policy.socket_path(&user_name, service_name);
|
|
self.get_nginx_acme_server(
|
|
host_name,
|
|
NginxServer::new_proxy(host_name, socket_path, root_dir, self.command_runner),
|
|
)
|
|
}
|
|
|
|
pub fn serve_nodejs<'a, 'c: 'a, S: 'a + Symbol, SP: 'a + AsRef<Path>>(
|
|
&'c self,
|
|
host: &'static str,
|
|
service_name: &'a str,
|
|
path: &'a Path,
|
|
static_path: SP,
|
|
nodejs_symbol: S,
|
|
) -> impl Symbol + 'a {
|
|
let user_name = self.policy.user_name_for_host(host);
|
|
List::from((
|
|
Hook::new(
|
|
nodejs_symbol,
|
|
self.get_nodejs_systemd_user_service(user_name, service_name, path),
|
|
),
|
|
self.proxy_socket(host, service_name, static_path),
|
|
))
|
|
}
|
|
}
|