New architecture
This commit is contained in:
parent
e4b3424ba6
commit
907a4962c5
61 changed files with 2742 additions and 3100 deletions
18
src/artifacts/mod.rs
Normal file
18
src/artifacts/mod.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use std::path::{Path as ActualPath, PathBuf};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Path(pub PathBuf);
|
||||
impl AsRef<ActualPath> for Path {
|
||||
fn as_ref(&self) -> &ActualPath {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserName(pub String);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServiceName(pub String);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DatabaseName(pub String);
|
||||
|
|
@ -40,3 +40,8 @@ pub fn create_static_output_files(source_dir: &str) {
|
|||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn main() {
|
||||
create_static_output_files("static_files");
|
||||
}
|
||||
|
|
|
|||
767
src/builder.rs
Normal file
767
src/builder.rs
Normal file
|
|
@ -0,0 +1,767 @@
|
|||
use crate::command_runner::{SetuidCommandRunner, StdCommandRunner};
|
||||
use crate::resources::{
|
||||
AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeRootCert, AcmeUser, Cert,
|
||||
CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle,
|
||||
LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, Resource,
|
||||
ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
|
||||
SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation,
|
||||
};
|
||||
use crate::static_files::LETS_ENCRYPT_X3_CROSS_SIGNED;
|
||||
use crate::storage::SimpleStorage;
|
||||
use crate::symbols::acme::Cert as CertSymbol;
|
||||
use crate::symbols::concat::Concat as ConcatSymbol;
|
||||
use crate::symbols::cron::Cron as CronSymbol;
|
||||
use crate::symbols::dir::Dir as DirSymbol;
|
||||
use crate::symbols::file::File as FileSymbol;
|
||||
use crate::symbols::git::Checkout as GitCheckoutSymbol;
|
||||
use crate::symbols::mariadb::{
|
||||
Database as MariaDbDatabaseSymbol, Dump as MariaDbDumpSymbol, User as MariaDbUserSymbol,
|
||||
};
|
||||
use crate::symbols::npm::Install as NpmInstallSymbol;
|
||||
use crate::symbols::owner::Owner as OwnerSymbol;
|
||||
use crate::symbols::saved_directory::{SavedDirectory as SavedDirectorySymbol, StorageDirection};
|
||||
use crate::symbols::systemd::{
|
||||
ReloadService as ReloadServiceSymbol, UserService as UserServiceSymbol,
|
||||
UserSession as SystemdUserSessionSymbol,
|
||||
};
|
||||
use crate::symbols::tls::Csr as CsrSymbol;
|
||||
use crate::symbols::tls::Key as KeySymbol;
|
||||
use crate::symbols::user::User as UserSymbol;
|
||||
use crate::symbols::wordpress::{
|
||||
Plugin as WordpressPluginSymbol, Translation as WordpressTranslationSymbol,
|
||||
};
|
||||
use crate::templates::nginx;
|
||||
use crate::templates::php::fpm_pool_config as php_fpm_pool_config;
|
||||
use crate::templates::systemd::{
|
||||
nodejs_service as systemd_nodejs_service, socket_service as systemd_socket_service,
|
||||
};
|
||||
use crate::to_artifact::ToArtifact;
|
||||
use std::fmt::Display;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait SymbolBuilder<R> {
|
||||
type Prerequisites: ToArtifact;
|
||||
fn prerequisites(resource: &R) -> Self::Prerequisites;
|
||||
|
||||
type Symbol;
|
||||
fn create(
|
||||
resource: &R,
|
||||
target: &R::Artifact,
|
||||
inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol
|
||||
where
|
||||
R: Resource;
|
||||
}
|
||||
|
||||
pub struct DefaultBuilder;
|
||||
|
||||
impl<D> SymbolBuilder<Key<D>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &Key<D>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = KeySymbol<StdCommandRunner, PathBuf>;
|
||||
fn create(
|
||||
_resource: &Key<D>,
|
||||
target: &<Key<D> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
KeySymbol::new(StdCommandRunner, target.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<Csr<D>> for DefaultBuilder {
|
||||
type Prerequisites = Key<D>;
|
||||
fn prerequisites(resource: &Csr<D>) -> Self::Prerequisites {
|
||||
Key(resource.0.clone())
|
||||
}
|
||||
|
||||
type Symbol = CsrSymbol<StdCommandRunner, D, PathBuf, PathBuf>;
|
||||
fn create(
|
||||
resource: &Csr<D>,
|
||||
target: &<Csr<D> as Resource>::Artifact,
|
||||
key: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
CsrSymbol::new(
|
||||
StdCommandRunner,
|
||||
resource.0.clone(),
|
||||
key.0,
|
||||
target.0.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<Cert<D>> for DefaultBuilder {
|
||||
type Prerequisites = (
|
||||
Csr<D>,
|
||||
AcmeRootCert,
|
||||
AcmeAccountKey,
|
||||
AcmeChallengesDir,
|
||||
AcmeUser,
|
||||
DefaultServer,
|
||||
);
|
||||
fn prerequisites(resource: &Cert<D>) -> Self::Prerequisites {
|
||||
(
|
||||
Csr(resource.0.clone()),
|
||||
AcmeRootCert,
|
||||
AcmeAccountKey,
|
||||
AcmeChallengesDir,
|
||||
AcmeUser,
|
||||
DefaultServer,
|
||||
)
|
||||
}
|
||||
|
||||
type Symbol = CertSymbol<
|
||||
SetuidCommandRunner<'static, String, StdCommandRunner>,
|
||||
SetuidCommandRunner<'static, String, StdCommandRunner>,
|
||||
D,
|
||||
PathBuf,
|
||||
>;
|
||||
fn create(
|
||||
resource: &Cert<D>,
|
||||
target: &<Cert<D> as Resource>::Artifact,
|
||||
(csr, root_cert, account_key, challenges_dir, user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
CertSymbol::new(
|
||||
resource.0.clone(),
|
||||
SetuidCommandRunner::new(user_name.0, &StdCommandRunner),
|
||||
root_cert.0,
|
||||
account_key.0,
|
||||
challenges_dir.0,
|
||||
csr.0,
|
||||
target.0.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<CertChain<D>> for DefaultBuilder {
|
||||
type Prerequisites = (Cert<D>, AcmeRootCert);
|
||||
fn prerequisites(resource: &CertChain<D>) -> Self::Prerequisites {
|
||||
(Cert(resource.0.clone()), AcmeRootCert)
|
||||
}
|
||||
|
||||
type Symbol = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>;
|
||||
fn create(
|
||||
_resource: &CertChain<D>,
|
||||
target: &<CertChain<D> as Resource>::Artifact,
|
||||
(cert, root_cert): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
ConcatSymbol::new([cert.0, root_cert.0], target.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<KeyAndCertBundle<D>> for DefaultBuilder {
|
||||
type Prerequisites = (CertChain<D>, Key<D>);
|
||||
fn prerequisites(resource: &KeyAndCertBundle<D>) -> Self::Prerequisites {
|
||||
(CertChain(resource.0.clone()), Key(resource.0.clone()))
|
||||
}
|
||||
|
||||
type Symbol = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>;
|
||||
fn create(
|
||||
_resource: &KeyAndCertBundle<D>,
|
||||
target: &<KeyAndCertBundle<D> as Resource>::Artifact,
|
||||
(cert_chain, key): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
ConcatSymbol::new([cert_chain.0, key.0], target.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path> + Clone> SymbolBuilder<File<P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &File<P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = FileSymbol<P, String>;
|
||||
fn create(
|
||||
resource: &File<P>,
|
||||
_target: &<File<P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
FileSymbol::new(resource.0.clone(), resource.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: AsRef<Path> + Clone> SymbolBuilder<GitCheckout<'a, P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &GitCheckout<'a, P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = GitCheckoutSymbol<StdCommandRunner, StdCommandRunner, P, &'a str, &'a str>;
|
||||
fn create(
|
||||
resource: &GitCheckout<'a, P>,
|
||||
_target: &<GitCheckout<'a, P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
GitCheckoutSymbol::new(resource.0.clone(), resource.1, resource.2, StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<DefaultServer> for DefaultBuilder {
|
||||
type Prerequisites = AcmeChallengesNginxSnippet;
|
||||
fn prerequisites(_resource: &DefaultServer) -> Self::Prerequisites {
|
||||
AcmeChallengesNginxSnippet
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
|
||||
);
|
||||
fn create(
|
||||
_resource: &DefaultServer,
|
||||
target: &<DefaultServer as Resource>::Artifact,
|
||||
challenges_snippet_path: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::default_server(challenges_snippet_path),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, "nginx"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeCustom<D>> for DefaultBuilder {
|
||||
type Prerequisites = (CertChain<D>, Key<D>, AcmeChallengesNginxSnippet);
|
||||
fn prerequisites(resource: &ServeCustom<D>) -> Self::Prerequisites {
|
||||
(
|
||||
CertChain(resource.0.clone()),
|
||||
Key(resource.0.clone()),
|
||||
AcmeChallengesNginxSnippet,
|
||||
)
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
|
||||
);
|
||||
fn create(
|
||||
resource: &ServeCustom<D>,
|
||||
target: &<ServeCustom<D> as Resource>::Artifact,
|
||||
(cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::server_config(
|
||||
&resource.0,
|
||||
cert.0,
|
||||
key.0,
|
||||
&resource.1,
|
||||
challenges_snippet_path,
|
||||
),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, "nginx"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone + Display, P: AsRef<Path>> SymbolBuilder<ServePhp<D, P>> for DefaultBuilder {
|
||||
type Prerequisites = (
|
||||
PhpFpmPool<D>,
|
||||
CertChain<D>,
|
||||
Key<D>,
|
||||
AcmeChallengesNginxSnippet,
|
||||
);
|
||||
fn prerequisites(resource: &ServePhp<D, P>) -> Self::Prerequisites {
|
||||
(
|
||||
PhpFpmPool(resource.0.clone(), 10),
|
||||
CertChain(resource.0.clone()),
|
||||
Key(resource.0.clone()),
|
||||
AcmeChallengesNginxSnippet,
|
||||
)
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
|
||||
);
|
||||
fn create(
|
||||
resource: &ServePhp<D, P>,
|
||||
target: &<ServePhp<D, P> as Resource>::Artifact,
|
||||
(pool, cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::server_config(
|
||||
&resource.0,
|
||||
cert.0,
|
||||
key.0,
|
||||
nginx::php_snippet(resource.2, &pool.0, &resource.1) + &resource.3,
|
||||
challenges_snippet_path,
|
||||
),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, "nginx"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone + Display, P: Clone + AsRef<Path>> SymbolBuilder<ServeService<D, P>>
|
||||
for DefaultBuilder
|
||||
{
|
||||
type Prerequisites = (
|
||||
SystemdSocketService<D, P>,
|
||||
CertChain<D>,
|
||||
Key<D>,
|
||||
AcmeChallengesNginxSnippet,
|
||||
);
|
||||
fn prerequisites(resource: &ServeService<D, P>) -> Self::Prerequisites {
|
||||
(
|
||||
SystemdSocketService(
|
||||
resource.0.clone(),
|
||||
resource.1,
|
||||
resource.2.clone(),
|
||||
resource.4.clone(),
|
||||
resource.5,
|
||||
),
|
||||
CertChain(resource.0.clone()),
|
||||
Key(resource.0.clone()),
|
||||
AcmeChallengesNginxSnippet,
|
||||
)
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
|
||||
);
|
||||
fn create(
|
||||
resource: &ServeService<D, P>,
|
||||
target: &<ServeService<D, P> as Resource>::Artifact,
|
||||
(socket, cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::server_config(
|
||||
&resource.0,
|
||||
cert.0,
|
||||
key.0,
|
||||
nginx::proxy_snippet(&socket.0, &resource.3),
|
||||
challenges_snippet_path,
|
||||
),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, "nginx"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeRedir<D>> for DefaultBuilder {
|
||||
type Prerequisites = (CertChain<D>, Key<D>, AcmeChallengesNginxSnippet);
|
||||
fn prerequisites(resource: &ServeRedir<D>) -> Self::Prerequisites {
|
||||
(
|
||||
CertChain(resource.0.clone()),
|
||||
Key(resource.0.clone()),
|
||||
AcmeChallengesNginxSnippet,
|
||||
)
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
|
||||
);
|
||||
fn create(
|
||||
resource: &ServeRedir<D>,
|
||||
target: &<ServeRedir<D> as Resource>::Artifact,
|
||||
(cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::server_config(
|
||||
&resource.0,
|
||||
cert.0,
|
||||
key.0,
|
||||
nginx::redir_snippet(resource.1.as_ref()),
|
||||
challenges_snippet_path,
|
||||
),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, "nginx"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str> + Clone + Display, P: AsRef<Path>> SymbolBuilder<ServeStatic<D, P>>
|
||||
for DefaultBuilder
|
||||
{
|
||||
type Prerequisites = (CertChain<D>, Key<D>, AcmeChallengesNginxSnippet);
|
||||
fn prerequisites(resource: &ServeStatic<D, P>) -> Self::Prerequisites {
|
||||
(
|
||||
CertChain(resource.0.clone()),
|
||||
Key(resource.0.clone()),
|
||||
AcmeChallengesNginxSnippet,
|
||||
)
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
|
||||
);
|
||||
fn create(
|
||||
resource: &ServeStatic<D, P>,
|
||||
target: &<ServeStatic<D, P> as Resource>::Artifact,
|
||||
(cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::server_config(
|
||||
&resource.0,
|
||||
cert.0,
|
||||
key.0,
|
||||
nginx::static_snippet(resource.1.as_ref()),
|
||||
challenges_snippet_path,
|
||||
),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, "nginx"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<PhpFpmPool<D>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &PhpFpmPool<D>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, String>,
|
||||
);
|
||||
fn create(
|
||||
resource: &PhpFpmPool<D>,
|
||||
(socket_path, conf_path, user_name, service_name): &<PhpFpmPool<D> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
conf_path.0.clone(),
|
||||
php_fpm_pool_config(&user_name.0, &socket_path.0, resource.1),
|
||||
),
|
||||
ReloadServiceSymbol::new(StdCommandRunner, service_name.0.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, P: AsRef<Path>> SymbolBuilder<SystemdSocketService<D, P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &SystemdSocketService<D, P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = (
|
||||
FileSymbol<PathBuf, String>,
|
||||
SystemdUserSessionSymbol<'static, String, StdCommandRunner>,
|
||||
UserServiceSymbol<'static, PathBuf, String, StdCommandRunner>,
|
||||
);
|
||||
fn create(
|
||||
resource: &SystemdSocketService<D, P>,
|
||||
(socket_path, conf_path, user_name): &<SystemdSocketService<D, P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
FileSymbol::new(
|
||||
conf_path.0.clone(),
|
||||
if resource.4 {
|
||||
systemd_nodejs_service(&resource.2, socket_path, &resource.3)
|
||||
} else {
|
||||
systemd_socket_service(
|
||||
socket_path,
|
||||
resource.2.as_ref().to_str().unwrap(),
|
||||
&resource.3,
|
||||
"",
|
||||
)
|
||||
},
|
||||
),
|
||||
SystemdUserSessionSymbol::new(user_name.0.clone(), &StdCommandRunner),
|
||||
UserServiceSymbol::new(
|
||||
socket_path.0.clone(),
|
||||
user_name.0.clone(),
|
||||
resource.1,
|
||||
&StdCommandRunner,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Clone> SymbolBuilder<Dir<P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &Dir<P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = DirSymbol<P>;
|
||||
fn create(
|
||||
resource: &Dir<P>,
|
||||
_target: &<Dir<P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
DirSymbol::new(resource.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Clone + AsRef<Path>> SymbolBuilder<NpmInstall<P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &NpmInstall<P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = NpmInstallSymbol<'static, P, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &NpmInstall<P>,
|
||||
_target: &<NpmInstall<P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
NpmInstallSymbol::new(resource.0.clone(), &StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Clone + AsRef<Path>> SymbolBuilder<StoredDirectory<P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &StoredDirectory<P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = SavedDirectorySymbol<StdCommandRunner, StdCommandRunner, P, SimpleStorage>;
|
||||
fn create(
|
||||
resource: &StoredDirectory<P>,
|
||||
target: &<StoredDirectory<P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
SavedDirectorySymbol::new(
|
||||
resource.1.clone(),
|
||||
SimpleStorage::new(target.0.clone()),
|
||||
StorageDirection::Store,
|
||||
StdCommandRunner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Clone + AsRef<Path>> SymbolBuilder<LoadedDirectory<P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &LoadedDirectory<P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = SavedDirectorySymbol<StdCommandRunner, StdCommandRunner, P, SimpleStorage>;
|
||||
fn create(
|
||||
resource: &LoadedDirectory<P>,
|
||||
target: &<LoadedDirectory<P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
SavedDirectorySymbol::new(
|
||||
resource.1.clone(),
|
||||
SimpleStorage::new(target.0.clone()),
|
||||
StorageDirection::Load,
|
||||
StdCommandRunner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<UserForDomain<D>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &UserForDomain<D>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = UserSymbol<String, StdCommandRunner>;
|
||||
fn create(
|
||||
_resource: &UserForDomain<D>,
|
||||
(user_name, _home_path): &<UserForDomain<D> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
UserSymbol::new(user_name.0.clone(), StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<User> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &User) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = UserSymbol<String, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &User,
|
||||
(): &<User as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
UserSymbol::new(resource.0.clone(), StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path> + Clone> SymbolBuilder<Owner<P>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &Owner<P>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = OwnerSymbol<StdCommandRunner, StdCommandRunner, P, String>;
|
||||
fn create(
|
||||
resource: &Owner<P>,
|
||||
(): &<Owner<P> as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
OwnerSymbol::new(resource.1.clone(), resource.0.clone(), StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<AcmeUser> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = UserSymbol<String, StdCommandRunner>;
|
||||
fn create(
|
||||
_resource: &AcmeUser,
|
||||
user_name: &<AcmeUser as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
UserSymbol::new(user_name.0.clone(), StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<AcmeChallengesDir> for DefaultBuilder {
|
||||
type Prerequisites = AcmeUser;
|
||||
fn prerequisites(_resource: &AcmeChallengesDir) -> Self::Prerequisites {
|
||||
AcmeUser
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
DirSymbol<PathBuf>,
|
||||
OwnerSymbol<StdCommandRunner, StdCommandRunner, PathBuf, String>,
|
||||
);
|
||||
fn create(
|
||||
_resource: &AcmeChallengesDir,
|
||||
target: &<AcmeChallengesDir as Resource>::Artifact,
|
||||
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
DirSymbol::new(target.0.clone()),
|
||||
OwnerSymbol::new(target.0.clone(), user_name.0, StdCommandRunner),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<AcmeChallengesNginxSnippet> for DefaultBuilder {
|
||||
type Prerequisites = AcmeChallengesDir;
|
||||
fn prerequisites(_resource: &AcmeChallengesNginxSnippet) -> Self::Prerequisites {
|
||||
AcmeChallengesDir
|
||||
}
|
||||
|
||||
type Symbol = FileSymbol<PathBuf, String>;
|
||||
fn create(
|
||||
_resource: &AcmeChallengesNginxSnippet,
|
||||
target: &<AcmeChallengesNginxSnippet as Resource>::Artifact,
|
||||
challenges_dir: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
FileSymbol::new(
|
||||
target.0.clone(),
|
||||
nginx::acme_challenges_snippet(challenges_dir),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<AcmeAccountKey> for DefaultBuilder {
|
||||
type Prerequisites = AcmeUser;
|
||||
fn prerequisites(_resource: &AcmeAccountKey) -> Self::Prerequisites {
|
||||
AcmeUser
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
KeySymbol<StdCommandRunner, PathBuf>,
|
||||
OwnerSymbol<StdCommandRunner, StdCommandRunner, PathBuf, String>,
|
||||
);
|
||||
fn create(
|
||||
_resource: &AcmeAccountKey,
|
||||
target: &<AcmeAccountKey as Resource>::Artifact,
|
||||
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
(
|
||||
KeySymbol::new(StdCommandRunner, target.0.clone()),
|
||||
OwnerSymbol::new(target.0.clone(), user_name.0, StdCommandRunner),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolBuilder<AcmeRootCert> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &AcmeRootCert) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = FileSymbol<PathBuf, &'static str>;
|
||||
fn create(
|
||||
_resource: &AcmeRootCert,
|
||||
target: &<AcmeRootCert as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
FileSymbol::new(target.0.clone(), LETS_ENCRYPT_X3_CROSS_SIGNED)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> SymbolBuilder<MariaDbUser<D>> for DefaultBuilder {
|
||||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &MariaDbUser<D>) -> Self::Prerequisites {}
|
||||
|
||||
type Symbol = MariaDbUserSymbol<'static, String, StdCommandRunner>;
|
||||
fn create(
|
||||
_resource: &MariaDbUser<D>,
|
||||
user_name: &<MariaDbUser<D> as Resource>::Artifact,
|
||||
_: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
MariaDbUserSymbol::new(user_name.0.clone(), &StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<MariaDbDatabase<D>> for DefaultBuilder {
|
||||
type Prerequisites = MariaDbUser<D>;
|
||||
fn prerequisites(resource: &MariaDbDatabase<D>) -> Self::Prerequisites {
|
||||
MariaDbUser(resource.0.clone())
|
||||
}
|
||||
|
||||
type Symbol = (
|
||||
MariaDbDatabaseSymbol<'static, String, SimpleStorage, StdCommandRunner>,
|
||||
MariaDbDumpSymbol<'static, String, StdCommandRunner, SimpleStorage>,
|
||||
);
|
||||
fn create(
|
||||
_resource: &MariaDbDatabase<D>,
|
||||
(db_name, _, data_path): &<MariaDbDatabase<D> as Resource>::Artifact,
|
||||
_: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
let db_dump = SimpleStorage::new(data_path.0.clone());
|
||||
(
|
||||
MariaDbDatabaseSymbol::new(db_name.0.clone(), db_dump.clone(), &StdCommandRunner),
|
||||
MariaDbDumpSymbol::new(db_name.0.clone(), db_dump, &StdCommandRunner),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Clone + AsRef<Path>> SymbolBuilder<WordpressPlugin<P>> for DefaultBuilder {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn prerequisites(resource: &WordpressPlugin<P>) -> Self::Prerequisites {
|
||||
Dir(resource.0.as_ref().join("wp-content/plugins"))
|
||||
}
|
||||
|
||||
type Symbol = WordpressPluginSymbol<'static, P, &'static str, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &WordpressPlugin<P>,
|
||||
(): &<WordpressPlugin<P> as Resource>::Artifact,
|
||||
_: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
WordpressPluginSymbol::new(resource.0.clone(), resource.1, &StdCommandRunner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Clone + AsRef<Path>> SymbolBuilder<WordpressTranslation<P>> for DefaultBuilder {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn prerequisites(resource: &WordpressTranslation<P>) -> Self::Prerequisites {
|
||||
Dir(resource.0.as_ref().join("wp-content/languages"))
|
||||
}
|
||||
|
||||
type Symbol = WordpressTranslationSymbol<'static, &'static str, PathBuf, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &WordpressTranslation<P>,
|
||||
(): &<WordpressTranslation<P> as Resource>::Artifact,
|
||||
_: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
WordpressTranslationSymbol::new(
|
||||
resource.0.as_ref().join("wp-content/languages"),
|
||||
resource.1,
|
||||
resource.2,
|
||||
&StdCommandRunner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone> SymbolBuilder<Cron<D>> for DefaultBuilder {
|
||||
type Prerequisites = UserForDomain<D>;
|
||||
fn prerequisites(resource: &Cron<D>) -> Self::Prerequisites {
|
||||
UserForDomain(resource.0.clone())
|
||||
}
|
||||
|
||||
type Symbol = CronSymbol<'static, String, String, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &Cron<D>,
|
||||
(): &<Cron<D> as Resource>::Artifact,
|
||||
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
CronSymbol::new((user_name.0).0, resource.1.clone(), &StdCommandRunner)
|
||||
}
|
||||
}
|
||||
125
src/factory.rs
125
src/factory.rs
|
|
@ -1,125 +0,0 @@
|
|||
use regex::Regex;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::loggers::StdErrLogger;
|
||||
use crate::repository::SymbolRepository;
|
||||
use crate::resources::Resource;
|
||||
use crate::schema::{
|
||||
NonRepeatingSymbolRunner, ReportingSymbolRunner, RequirementsResolvingSymbolRunner,
|
||||
};
|
||||
use crate::symbols::dir::Dir;
|
||||
use crate::symbols::list::List;
|
||||
use crate::symbols::owner::Owner;
|
||||
use crate::symbols::systemd::user_session::SystemdUserSession;
|
||||
use crate::symbols::tls::{TlsCsr, TlsKey};
|
||||
use crate::symbols::user::SystemUserAdder;
|
||||
use crate::symbols::user::{User, UserAdder};
|
||||
use crate::symbols::{Symbol, SymbolRunner};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Factory {}
|
||||
|
||||
impl Factory {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get_repo<'a, CR: CommandRunner>(
|
||||
&self,
|
||||
command_runner: &'a CR,
|
||||
) -> DefaultSymbolRepository<'a, SystemUserAdder<'a, CR>, CR> {
|
||||
DefaultSymbolRepository::new(command_runner)
|
||||
}
|
||||
|
||||
pub fn get_symbol_runner<'a, RUNNER: SymbolRunner, REPO: SymbolRepository<'a>>(
|
||||
&self,
|
||||
symbol_runner: &'a RUNNER,
|
||||
repo: &'a REPO,
|
||||
) -> Box<dyn 'a + SymbolRunner> {
|
||||
let runner1 = ReportingSymbolRunner::new(symbol_runner, StdErrLogger);
|
||||
let runner2 = NonRepeatingSymbolRunner::new(runner1);
|
||||
Box::new(RequirementsResolvingSymbolRunner::new(runner2, repo))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultSymbolRepository<'a, A: 'a + UserAdder, C: CommandRunner> {
|
||||
user_adder: A,
|
||||
command_runner: &'a C,
|
||||
|
||||
home: Regex,
|
||||
home_config: Regex,
|
||||
csr: Regex,
|
||||
private_key: Regex,
|
||||
systemd_linger: Regex,
|
||||
}
|
||||
|
||||
impl<'a, C: 'a + CommandRunner> DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C> {
|
||||
pub fn new(command_runner: &'a C) -> Self {
|
||||
Self {
|
||||
command_runner,
|
||||
user_adder: SystemUserAdder::new(command_runner),
|
||||
home: Regex::new("^/home/([^/]+)$").unwrap(),
|
||||
home_config: Regex::new("^/home/([^/]+)/.config(?:/|$)").unwrap(),
|
||||
csr: Regex::new("^/etc/ssl/local_certs/([^/]+).csr$").unwrap(),
|
||||
private_key: Regex::new("^/etc/ssl/private/([^/]+).key$").unwrap(),
|
||||
systemd_linger: Regex::new("^/var/lib/systemd/linger/([^/]+)$").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner> SymbolRepository<'a>
|
||||
for DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C>
|
||||
{
|
||||
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>> {
|
||||
match resource.get_type() {
|
||||
"user" => Some(Box::new(User::new(
|
||||
resource.get_value().to_string().into(),
|
||||
self.command_runner,
|
||||
&self.user_adder,
|
||||
))),
|
||||
"dir" => {
|
||||
let value = resource.get_value();
|
||||
Some(if let Some(matches) = self.home_config.captures(value) {
|
||||
Box::new(List::new(vec![
|
||||
Box::new(Dir::new(value.to_string())),
|
||||
Box::new(Owner::new(
|
||||
value.to_string(),
|
||||
matches[1].to_string(),
|
||||
self.command_runner,
|
||||
)),
|
||||
]))
|
||||
} else if let Some(matches) = self.home.captures(value) {
|
||||
Box::new(User::new(
|
||||
matches[1].to_string().into(),
|
||||
self.command_runner,
|
||||
&self.user_adder,
|
||||
))
|
||||
} else {
|
||||
Box::new(Dir::new(value.to_string()))
|
||||
})
|
||||
}
|
||||
"file" => {
|
||||
let value = resource.get_value();
|
||||
if let Some(matches) = self.csr.captures(value) {
|
||||
Some(Box::new(TlsCsr::new(
|
||||
matches[1].to_string().into(),
|
||||
self.command_runner,
|
||||
)))
|
||||
} else if let Some(matches) = self.private_key.captures(value) {
|
||||
Some(Box::new(TlsKey::new(
|
||||
matches[1].to_string().into(),
|
||||
self.command_runner,
|
||||
)))
|
||||
} else if let Some(matches) = self.systemd_linger.captures(value) {
|
||||
Some(Box::new(SystemdUserSession::new(
|
||||
matches[1].to_string(),
|
||||
self.command_runner,
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/lib.rs
16
src/lib.rs
|
|
@ -1,4 +1,4 @@
|
|||
#![deny(
|
||||
#![warn(
|
||||
macro_use_extern_crate,
|
||||
meta_variable_misuse,
|
||||
non_ascii_idents,
|
||||
|
|
@ -32,10 +32,20 @@ pub mod bin;
|
|||
pub mod build;
|
||||
#[macro_use]
|
||||
pub mod command_runner;
|
||||
pub mod factory;
|
||||
pub mod loggers;
|
||||
pub mod repository;
|
||||
pub mod resources;
|
||||
pub mod schema;
|
||||
pub mod storage;
|
||||
pub mod symbols;
|
||||
pub mod templates;
|
||||
|
||||
mod artifacts;
|
||||
mod builder;
|
||||
mod locator;
|
||||
mod setup;
|
||||
pub mod static_files;
|
||||
mod to_artifact;
|
||||
|
||||
pub use builder::{DefaultBuilder, SymbolBuilder};
|
||||
pub use locator::{DefaultLocator, DefaultPolicy, Policy, ResourceLocator};
|
||||
pub use setup::Setup;
|
||||
|
|
|
|||
443
src/locator.rs
Normal file
443
src/locator.rs
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
use crate::artifacts::{
|
||||
DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact,
|
||||
UserName as UserNameArtifact,
|
||||
};
|
||||
use crate::resources::{
|
||||
AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeRootCert, AcmeUser, Cert,
|
||||
CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle,
|
||||
LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, Resource,
|
||||
ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
|
||||
SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation,
|
||||
};
|
||||
use crate::to_artifact::ToArtifact;
|
||||
use std::fmt::Display;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait Policy {
|
||||
fn acme_user() -> &'static str {
|
||||
"acme"
|
||||
}
|
||||
fn user_home(user_name: &str) -> PathBuf {
|
||||
format!("/home/{}", user_name).into()
|
||||
}
|
||||
|
||||
fn user_name_for_domain(domain_name: &'_ str) -> String {
|
||||
domain_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
|
||||
}
|
||||
|
||||
fn php_version() -> &'static str {
|
||||
"7.0"
|
||||
}
|
||||
|
||||
fn path_for_data(name: impl Display) -> PathBuf {
|
||||
("/root/data".as_ref() as &Path).join(format!("_{}", name))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultPolicy;
|
||||
|
||||
impl Policy for DefaultPolicy {}
|
||||
|
||||
pub trait ResourceLocator<R> {
|
||||
type Prerequisites: ToArtifact;
|
||||
fn locate(resource: &R) -> (R::Artifact, Self::Prerequisites)
|
||||
where
|
||||
R: Resource;
|
||||
}
|
||||
|
||||
pub struct DefaultLocator<P = DefaultPolicy> {
|
||||
phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P, D: AsRef<str>> ResourceLocator<Key<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(resource: &Key<D>) -> (<Key<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(format!("/etc/ssl/private/{}.key", resource.0.as_ref()).into()),
|
||||
Dir("/etc/ssl/private".into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, D: AsRef<str>> ResourceLocator<Csr<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(resource: &Csr<D>) -> (<Csr<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(format!("/etc/ssl/local_certs/{}.csr", resource.0.as_ref()).into()),
|
||||
Dir("/etc/ssl/local_certs".into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, D: AsRef<str>> ResourceLocator<Cert<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(resource: &Cert<D>) -> (<Cert<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(format!("/etc/ssl/local_certs/{}.crt", resource.0.as_ref()).into()),
|
||||
Dir("/etc/ssl/local_certs".into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, D: AsRef<str>> ResourceLocator<CertChain<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
resource: &CertChain<D>,
|
||||
) -> (<CertChain<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(format!("/etc/ssl/local_certs/{}.chained.crt", resource.0.as_ref()).into()),
|
||||
Dir("/etc/ssl/local_certs".into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, D: AsRef<str>> ResourceLocator<KeyAndCertBundle<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
resource: &KeyAndCertBundle<D>,
|
||||
) -> (
|
||||
<KeyAndCertBundle<D> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
(
|
||||
PathArtifact(format!("/etc/ssl/private/{}.with_key.crt", resource.0.as_ref()).into()),
|
||||
Dir("/etc/ssl/private".into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<File<P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(resource: &File<P>) -> (<File<P> as Resource>::Artifact, Self::Prerequisites) {
|
||||
((), Dir(resource.0.as_ref().parent().unwrap().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<GitCheckout<'a, P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
resource: &GitCheckout<'a, P>,
|
||||
) -> (
|
||||
<GitCheckout<'a, P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
((), Dir(resource.0.as_ref().parent().unwrap().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<POLICY, P: Clone + AsRef<Path>> ResourceLocator<Dir<P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = Option<Dir<PathBuf>>;
|
||||
fn locate(resource: &Dir<P>) -> (<Dir<P> as Resource>::Artifact, Self::Prerequisites) {
|
||||
((), resource.0.as_ref().parent().map(|p| Dir(p.into())))
|
||||
}
|
||||
}
|
||||
|
||||
impl<POLICY, P> ResourceLocator<NpmInstall<P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
_resource: &NpmInstall<P>,
|
||||
) -> (<NpmInstall<P> as Resource>::Artifact, Self::Prerequisites) {
|
||||
((), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<StoredDirectory<P>>
|
||||
for DefaultLocator<POLICY>
|
||||
{
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &StoredDirectory<P>,
|
||||
) -> (
|
||||
<StoredDirectory<P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
(PathArtifact(POLICY::path_for_data(resource.0)), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<LoadedDirectory<P>>
|
||||
for DefaultLocator<POLICY>
|
||||
{
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
resource: &LoadedDirectory<P>,
|
||||
) -> (
|
||||
<LoadedDirectory<P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
(
|
||||
PathArtifact(POLICY::path_for_data(resource.0)),
|
||||
Dir(resource.1.as_ref().parent().unwrap().into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> ResourceLocator<AcmeAccountKey> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
_resource: &AcmeAccountKey,
|
||||
) -> (<AcmeAccountKey as Resource>::Artifact, Self::Prerequisites) {
|
||||
let acme_user = P::acme_user();
|
||||
let home = P::user_home(acme_user);
|
||||
(PathArtifact(home.join("account.key")), Dir(home))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) {
|
||||
let acme_user = P::acme_user();
|
||||
(UserNameArtifact(acme_user.into()), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> ResourceLocator<AcmeChallengesDir> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
_resource: &AcmeChallengesDir,
|
||||
) -> (
|
||||
<AcmeChallengesDir as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
let acme_user = P::acme_user();
|
||||
let home = P::user_home(acme_user);
|
||||
(PathArtifact(home.join("challenges")), Dir(home))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> ResourceLocator<AcmeChallengesNginxSnippet> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
_resource: &AcmeChallengesNginxSnippet,
|
||||
) -> (
|
||||
<AcmeChallengesNginxSnippet as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
(
|
||||
PathArtifact("/etc/nginx/snippets/acme-challenge.conf".into()),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> ResourceLocator<AcmeRootCert> for DefaultLocator<P> {
|
||||
type Prerequisites = Dir<PathBuf>;
|
||||
fn locate(
|
||||
_resource: &AcmeRootCert,
|
||||
) -> (<AcmeRootCert as Resource>::Artifact, Self::Prerequisites) {
|
||||
let acme_user = P::acme_user();
|
||||
let home = P::user_home(acme_user);
|
||||
(
|
||||
PathArtifact(home.join("lets_encrypt_x3_cross_signed.pem")),
|
||||
Dir(home),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy, D: AsRef<str>> ResourceLocator<UserForDomain<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &UserForDomain<D>,
|
||||
) -> (
|
||||
<UserForDomain<D> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
let user_name = P::user_name_for_domain(resource.0.as_ref());
|
||||
let home = P::user_home(&user_name);
|
||||
((UserNameArtifact(user_name), PathArtifact(home)), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> ResourceLocator<User> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(_resource: &User) -> (<User as Resource>::Artifact, Self::Prerequisites) {
|
||||
((), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, POLICY> ResourceLocator<Owner<P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = User;
|
||||
fn locate(resource: &Owner<P>) -> (<Owner<P> as Resource>::Artifact, Self::Prerequisites) {
|
||||
((), User(resource.0.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> ResourceLocator<DefaultServer> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
_resource: &DefaultServer,
|
||||
) -> (<DefaultServer as Resource>::Artifact, Self::Prerequisites) {
|
||||
(PathArtifact("/etc/nginx/sites-enabled/default".into()), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeCustom<D>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &ServeCustom<D>,
|
||||
) -> (<ServeCustom<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServePhp<D, P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &ServePhp<D, P>,
|
||||
) -> (<ServePhp<D, P> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeService<D, P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &ServeService<D, P>,
|
||||
) -> (
|
||||
<ServeService<D, P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
(
|
||||
PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeRedir<D>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &ServeRedir<D>,
|
||||
) -> (<ServeRedir<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
(
|
||||
PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeStatic<D, P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &ServeStatic<D, P>,
|
||||
) -> (
|
||||
<ServeStatic<D, P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
(
|
||||
PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone + AsRef<str>, P: Policy> ResourceLocator<PhpFpmPool<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &PhpFpmPool<D>,
|
||||
) -> (<PhpFpmPool<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
let ((user, _), ()) = Self::locate(&UserForDomain(&resource.0));
|
||||
let php_version = P::php_version();
|
||||
(
|
||||
(
|
||||
PathArtifact(format!("/run/php/{}.sock", user.0).into()),
|
||||
PathArtifact(format!("/etc/php/{}/fpm/pool.d/{}.conf", php_version, user.0).into()),
|
||||
user,
|
||||
ServiceNameArtifact(format!("php{}-fpm", php_version)),
|
||||
),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Clone + AsRef<str>, P, POLICY: Policy> ResourceLocator<SystemdSocketService<D, P>>
|
||||
for DefaultLocator<POLICY>
|
||||
{
|
||||
type Prerequisites = (Dir<PathBuf>, Owner<PathBuf>);
|
||||
fn locate(
|
||||
resource: &SystemdSocketService<D, P>,
|
||||
) -> (
|
||||
<SystemdSocketService<D, P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
let ((user_name, home_path), ()) = Self::locate(&UserForDomain(&resource.0));
|
||||
let config = home_path.0.join(".config");
|
||||
let service_dir_path = config.join("systemd/user");
|
||||
(
|
||||
(
|
||||
PathArtifact(format!("/var/tmp/{}-{}.socket", user_name.0, resource.1).into()),
|
||||
PathArtifact(service_dir_path.join(format!("{}.service", resource.1))),
|
||||
user_name.clone(),
|
||||
),
|
||||
(Dir(service_dir_path), Owner(user_name.0, config)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbDatabase<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &MariaDbDatabase<D>,
|
||||
) -> (
|
||||
<MariaDbDatabase<D> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
|
||||
(
|
||||
(
|
||||
DatabaseNameArtifact(user_name.0.clone()),
|
||||
user_name.clone(),
|
||||
PathArtifact(P::path_for_data(format!("{}.sql", user_name.0))),
|
||||
),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbUser<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
resource: &MariaDbUser<D>,
|
||||
) -> (<MariaDbUser<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
|
||||
((user_name), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, POLICY> ResourceLocator<WordpressPlugin<P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
_resource: &WordpressPlugin<P>,
|
||||
) -> (
|
||||
<WordpressPlugin<P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
((), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, POLICY> ResourceLocator<WordpressTranslation<P>> for DefaultLocator<POLICY> {
|
||||
type Prerequisites = ();
|
||||
fn locate(
|
||||
_resource: &WordpressTranslation<P>,
|
||||
) -> (
|
||||
<WordpressTranslation<P> as Resource>::Artifact,
|
||||
Self::Prerequisites,
|
||||
) {
|
||||
((), ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, P> ResourceLocator<Cron<D>> for DefaultLocator<P> {
|
||||
type Prerequisites = ();
|
||||
fn locate(_resource: &Cron<D>) -> (<Cron<D> as Resource>::Artifact, Self::Prerequisites) {
|
||||
((), ())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
pub trait SymbolRepository<'a> {
|
||||
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>>;
|
||||
}
|
||||
|
||||
impl<'a, C> SymbolRepository<'a> for C
|
||||
where
|
||||
C: Fn(&Resource) -> Option<Box<dyn Symbol + 'a>>,
|
||||
{
|
||||
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>> {
|
||||
self(resource)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DispatchingSymbolRepository<'a> {
|
||||
repositories: HashMap<&'a str, Box<dyn SymbolRepository<'a> + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DispatchingSymbolRepository<'a> {
|
||||
pub fn new(repositories: HashMap<&'a str, Box<dyn SymbolRepository<'a> + 'a>>) -> Self {
|
||||
DispatchingSymbolRepository { repositories }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SymbolRepository<'a> for DispatchingSymbolRepository<'a> {
|
||||
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>> {
|
||||
self
|
||||
.repositories
|
||||
.get(resource.get_type())
|
||||
.and_then(|repo| repo.get_symbol(resource))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,282 @@
|
|||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub struct Resource(pub String, pub String);
|
||||
use crate::artifacts::{
|
||||
DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact,
|
||||
UserName as UserNameArtifact,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl<'a> Resource {
|
||||
pub fn new<A: Into<String>, B: Into<String>>(rtype: A, value: B) -> Self {
|
||||
Self(rtype.into(), value.into())
|
||||
}
|
||||
pub trait Resource {
|
||||
type Artifact;
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
pub fn get_value(&self) -> &str {
|
||||
&self.1
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Key<D>(pub D);
|
||||
impl<D> Resource for Key<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Csr<D>(pub D);
|
||||
impl<D> Resource for Csr<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Cert<D>(pub D);
|
||||
impl<D> Resource for Cert<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CertChain<D>(pub D);
|
||||
impl<D> Resource for CertChain<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct KeyAndCertBundle<D>(pub D);
|
||||
impl<D> Resource for KeyAndCertBundle<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct File<P>(pub P, pub String);
|
||||
impl<'a, P> Resource for File<P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct GitCheckout<'a, P>(pub P, pub &'a str, pub &'a str);
|
||||
impl<'a, P> Resource for GitCheckout<'a, P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Dir<P>(pub P);
|
||||
impl<P> Resource for Dir<P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct UserForDomain<D>(pub D);
|
||||
impl<D> Resource for UserForDomain<D> {
|
||||
type Artifact = (UserNameArtifact, PathArtifact);
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AcmeAccountKey;
|
||||
impl Resource for AcmeAccountKey {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AcmeUser;
|
||||
impl Resource for AcmeUser {
|
||||
type Artifact = UserNameArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AcmeRootCert;
|
||||
impl Resource for AcmeRootCert {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AcmeChallengesDir;
|
||||
impl Resource for AcmeChallengesDir {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AcmeChallengesNginxSnippet;
|
||||
impl Resource for AcmeChallengesNginxSnippet {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct StoredDirectory<P>(pub &'static str, pub P);
|
||||
impl<P> Resource for StoredDirectory<P> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct LoadedDirectory<P>(pub &'static str, pub P);
|
||||
impl<P> Resource for LoadedDirectory<P> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
pub fn get_saved_directory<P: Clone>(
|
||||
storage_name: &'static str,
|
||||
target: P,
|
||||
) -> (StoredDirectory<P>, LoadedDirectory<P>) {
|
||||
(
|
||||
StoredDirectory(storage_name, target.clone()),
|
||||
LoadedDirectory(storage_name, target),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct User(pub String);
|
||||
impl Resource for User {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct SystemdSocketService<D, P>(pub D, pub &'static str, pub P, pub P, pub bool);
|
||||
impl<D, P> Resource for SystemdSocketService<D, P> {
|
||||
type Artifact = (PathArtifact, PathArtifact, UserNameArtifact);
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct NpmInstall<P>(pub P);
|
||||
impl<P> Resource for NpmInstall<P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Owner<P>(pub String, pub P);
|
||||
impl<P> Resource for Owner<P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ServeCustom<D>(pub D, pub String);
|
||||
impl<D> Resource for ServeCustom<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ServePhp<D, P>(pub D, pub P, pub &'static str, pub String, pub usize);
|
||||
impl<D, P> Resource for ServePhp<D, P> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
// Domain, service name, exec, static, dir
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ServeService<D, P>(pub D, pub &'static str, pub P, pub P, pub P, pub bool);
|
||||
impl<D, P> Resource for ServeService<D, P> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ServeRedir<D>(pub D, pub D);
|
||||
impl<D> Resource for ServeRedir<D> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ServeStatic<D, P>(pub D, pub P);
|
||||
impl<D, P> Resource for ServeStatic<D, P> {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct DefaultServer;
|
||||
impl Resource for DefaultServer {
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct PhpFpmPool<D>(pub D, pub usize);
|
||||
impl<D> Resource for PhpFpmPool<D> {
|
||||
type Artifact = (
|
||||
PathArtifact,
|
||||
PathArtifact,
|
||||
UserNameArtifact,
|
||||
ServiceNameArtifact,
|
||||
);
|
||||
}
|
||||
|
||||
// A single domain could possibly need multiple databases
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MariaDbDatabase<D>(pub D);
|
||||
impl<D> Resource for MariaDbDatabase<D> {
|
||||
type Artifact = (DatabaseNameArtifact, UserNameArtifact, PathArtifact);
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MariaDbUser<D>(pub D);
|
||||
impl<D> Resource for MariaDbUser<D> {
|
||||
type Artifact = UserNameArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct WordpressPlugin<P>(pub P, pub &'static str);
|
||||
impl<P> Resource for WordpressPlugin<P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct WordpressTranslation<P>(pub P, pub &'static str, pub &'static str);
|
||||
impl<P> Resource for WordpressTranslation<P> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Cron<D>(pub D, pub String);
|
||||
impl<D> Resource for Cron<D> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
pub trait BorrowResource<R> {
|
||||
fn borrow_resource(&self) -> Option<&R>;
|
||||
}
|
||||
|
||||
macro_rules! default_resources {
|
||||
( $($name:ident: $type:ty,)* ) => {
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DefaultResources<'a, D> {
|
||||
$( $name($type) ),*
|
||||
}
|
||||
|
||||
$(impl<'a, D> From<$type> for DefaultResources<'a, D> {
|
||||
fn from(from: $type) -> Self {
|
||||
Self::$name(from)
|
||||
}
|
||||
})*
|
||||
|
||||
$(impl<'a, D> BorrowResource<$type> for DefaultResources<'a, D> {
|
||||
fn borrow_resource(&self) -> Option<&$type> {
|
||||
match self {
|
||||
Self::$name(v) => Some(v),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
})*
|
||||
}
|
||||
}
|
||||
|
||||
default_resources!(
|
||||
AcmeAccountKey: AcmeAccountKey,
|
||||
AcmeChallengesDir: AcmeChallengesDir,
|
||||
AcmeChallengesNginxSnippet: AcmeChallengesNginxSnippet,
|
||||
AcmeRootCert: AcmeRootCert,
|
||||
AcmeUser: AcmeUser,
|
||||
Cert: Cert<D>,
|
||||
CertChain: CertChain<D>,
|
||||
Cron: Cron<D>,
|
||||
Csr: Csr<D>,
|
||||
DefaultServer: DefaultServer,
|
||||
Dir: Dir<PathBuf>,
|
||||
File: File<PathBuf>,
|
||||
GitCheckout: GitCheckout<'a, PathBuf>,
|
||||
Key: Key<D>,
|
||||
KeyAndCertBundle: KeyAndCertBundle<D>,
|
||||
LoadedDirectory: LoadedDirectory<PathBuf>,
|
||||
MariaDbDatabase: MariaDbDatabase<D>,
|
||||
MariaDbUser: MariaDbUser<D>,
|
||||
SystemdSocketService: SystemdSocketService<D, PathBuf>,
|
||||
NpmInstall: NpmInstall<PathBuf>,
|
||||
Owner: Owner<PathBuf>,
|
||||
PhpFpmPool: PhpFpmPool<D>,
|
||||
ServeCustom: ServeCustom<D>,
|
||||
ServeService: ServeService<D, PathBuf>,
|
||||
ServePhp: ServePhp<D, PathBuf>,
|
||||
ServeRedir: ServeRedir<D>,
|
||||
ServeStatic: ServeStatic<D, PathBuf>,
|
||||
StoredDirectory: StoredDirectory<PathBuf>,
|
||||
User: User,
|
||||
UserForDomain: UserForDomain<D>,
|
||||
WordpressPlugin: WordpressPlugin<PathBuf>,
|
||||
WordpressTranslation: WordpressTranslation<PathBuf>,
|
||||
);
|
||||
|
|
|
|||
200
src/schema.rs
200
src/schema.rs
|
|
@ -1,10 +1,19 @@
|
|||
use crate::loggers::Logger;
|
||||
use crate::symbols::Symbol;
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::loggers::Logger;
|
||||
use crate::repository::SymbolRepository;
|
||||
use crate::symbols::{Symbol, SymbolRunner};
|
||||
pub trait SymbolRunner {
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
impl<R: SymbolRunner + ?Sized> SymbolRunner for Box<R> {
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
(**self).run_symbol(symbol, force)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SymbolRunError {
|
||||
|
|
@ -46,31 +55,47 @@ impl<L: Logger> InitializingSymbolRunner<L> {
|
|||
logger: RefCell::new(logger),
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_symbol<S: Symbol + Debug>(&self, symbol: &S) -> Result<(), Box<dyn Error>> {
|
||||
let mut logger = self.logger.borrow_mut();
|
||||
logger.write(format!("Executing {:?}", symbol).as_str());
|
||||
symbol.execute()?;
|
||||
let target_reached = symbol.target_reached()?;
|
||||
logger.debug(
|
||||
format!(
|
||||
"Symbol reports target_reached: {:?} (should be true)",
|
||||
target_reached
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
if target_reached {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Box::new(SymbolRunError::ExecuteDidNotReach(())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
let mut logger = self.logger.borrow_mut();
|
||||
let target_reached = symbol.target_reached()?;
|
||||
if target_reached {
|
||||
logger.write(format!("{} already reached", symbol).as_str());
|
||||
let executed = if force {
|
||||
logger.debug("Forcing symbol execution");
|
||||
drop(logger);
|
||||
self.exec_symbol(symbol)?;
|
||||
true
|
||||
} else {
|
||||
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
|
||||
logger.write(format!("Executing {}", symbol).as_str());
|
||||
symbol.execute()?;
|
||||
let target_reached = symbol.target_reached()?;
|
||||
logger.debug(
|
||||
format!(
|
||||
"Symbol reports target_reached: {:?} (should be true)",
|
||||
target_reached
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
if !target_reached {
|
||||
return Err(Box::new(SymbolRunError::ExecuteDidNotReach(())));
|
||||
if target_reached {
|
||||
logger.write(format!("{:?} already reached", symbol).as_str());
|
||||
} else {
|
||||
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
|
||||
drop(logger);
|
||||
self.exec_symbol(symbol)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
!target_reached
|
||||
};
|
||||
Ok(executed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,24 +112,26 @@ impl<L: Logger> DrySymbolRunner<L> {
|
|||
}
|
||||
|
||||
impl<L: Logger> SymbolRunner for DrySymbolRunner<L> {
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
let mut logger = self.logger.borrow_mut();
|
||||
let target_reached = symbol.target_reached()?;
|
||||
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
|
||||
if !target_reached {
|
||||
logger.write(format!("Would execute {}", symbol).as_str());
|
||||
}
|
||||
Ok(())
|
||||
let would_execute = if force {
|
||||
logger.write(format!("Would force-execute {:?}", symbol).as_str());
|
||||
true
|
||||
} else {
|
||||
let target_reached = symbol.target_reached()?;
|
||||
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
|
||||
if !target_reached {
|
||||
logger.write(format!("Would execute {:?}", symbol).as_str());
|
||||
}
|
||||
!target_reached
|
||||
};
|
||||
Ok(would_execute)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReportingSymbolRunner<'a, R: SymbolRunner, L: Logger>(&'a R, RefCell<L>);
|
||||
pub struct ReportingSymbolRunner<'a, R, L>(&'a R, RefCell<L>);
|
||||
|
||||
impl<'a, R, L> ReportingSymbolRunner<'a, R, L>
|
||||
where
|
||||
R: SymbolRunner,
|
||||
L: Logger,
|
||||
{
|
||||
impl<'a, R, L> ReportingSymbolRunner<'a, R, L> {
|
||||
pub fn new(symbol_runner: &'a R, logger: L) -> Self {
|
||||
ReportingSymbolRunner(symbol_runner, RefCell::new(logger))
|
||||
}
|
||||
|
|
@ -115,92 +142,20 @@ where
|
|||
R: SymbolRunner,
|
||||
L: Logger,
|
||||
{
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
let mut logger = self.1.borrow_mut();
|
||||
logger.debug(format!("Running symbol {}", symbol).as_str());
|
||||
let res = self.0.run_symbol(symbol);
|
||||
logger.debug(format!("Running symbol {:?}", symbol).as_str());
|
||||
let res = self.0.run_symbol(symbol, force);
|
||||
if let Err(ref e) = res {
|
||||
logger.write(format!("Failed on {} with {}, aborting.", symbol, e).as_str())
|
||||
logger.write(format!("Failed on {:?} with {}, aborting.", symbol, e).as_str())
|
||||
} else {
|
||||
logger.debug(format!("Successfully finished {}", symbol).as_str())
|
||||
logger.debug(format!("Successfully finished {:?}", symbol).as_str())
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
use crate::resources::Resource;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct NonRepeatingSymbolRunner<R: SymbolRunner> {
|
||||
upstream: R,
|
||||
done: RefCell<HashSet<Resource>>,
|
||||
}
|
||||
|
||||
impl<R> NonRepeatingSymbolRunner<R>
|
||||
where
|
||||
R: SymbolRunner,
|
||||
{
|
||||
pub fn new(symbol_runner: R) -> Self {
|
||||
Self {
|
||||
upstream: symbol_runner,
|
||||
done: RefCell::new(HashSet::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: SymbolRunner> SymbolRunner for NonRepeatingSymbolRunner<R> {
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(resources) = symbol.provides() {
|
||||
let mut done = self.done.borrow_mut();
|
||||
let mut has_to_run = false;
|
||||
for resource in resources {
|
||||
if !done.contains(&resource) {
|
||||
has_to_run = true;
|
||||
assert!(done.insert(resource.clone()));
|
||||
}
|
||||
}
|
||||
if !has_to_run {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.upstream.run_symbol(&*symbol)
|
||||
}
|
||||
}
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct RequirementsResolvingSymbolRunner<'a, 's, R: 'a + SymbolRunner, G: SymbolRepository<'s>>(
|
||||
R,
|
||||
&'a G,
|
||||
PhantomData<Box<dyn Symbol + 's>>,
|
||||
);
|
||||
|
||||
impl<'s, 'a: 's, R, G> RequirementsResolvingSymbolRunner<'a, 's, R, G>
|
||||
where
|
||||
R: SymbolRunner,
|
||||
G: SymbolRepository<'s>,
|
||||
{
|
||||
pub fn new(symbol_runner: R, symbol_repo: &'a G) -> Self {
|
||||
RequirementsResolvingSymbolRunner(symbol_runner, symbol_repo, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, 'a: 's, R, G> SymbolRunner for RequirementsResolvingSymbolRunner<'a, 's, R, G>
|
||||
where
|
||||
R: SymbolRunner,
|
||||
G: SymbolRepository<'s>,
|
||||
{
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
|
||||
for resource in symbol.get_prerequisites() {
|
||||
if let Some(dep) = self.1.get_symbol(&resource) {
|
||||
dep.as_action(self).run()?;
|
||||
}
|
||||
}
|
||||
self.0.run_symbol(&*symbol)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Add ExpectingSymbolRunner
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::cell::RefCell;
|
||||
|
|
@ -210,7 +165,7 @@ mod test {
|
|||
use crate::loggers::Logger;
|
||||
use crate::schema::InitializingSymbolRunner;
|
||||
use crate::schema::SymbolRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction};
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum DummySymbolError {
|
||||
|
|
@ -229,6 +184,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DummySymbol<'a> {
|
||||
_execute: &'a dyn Fn() -> Result<(), Box<dyn Error>>,
|
||||
_target_reached: &'a dyn Fn() -> Result<bool, Box<dyn Error>>,
|
||||
|
|
@ -241,23 +197,6 @@ mod test {
|
|||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
(self._execute)()
|
||||
}
|
||||
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for DummySymbol<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Dummy symbol")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DummySymbol<'a> {
|
||||
|
|
@ -336,3 +275,4 @@ mod test {
|
|||
assert_eq!(err.description(), "Description");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
345
src/setup.rs
Normal file
345
src/setup.rs
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
use crate::resources::{BorrowResource, DefaultResources, Resource};
|
||||
use crate::schema::SymbolRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use crate::to_artifact::ToArtifact;
|
||||
use crate::{DefaultBuilder, DefaultLocator, ResourceLocator, SymbolBuilder};
|
||||
use std::collections::HashSet;
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub trait CanHandle<X: ToArtifact> {
|
||||
fn handle(&mut self, x: X) -> Result<(X::Artifact, bool), Box<dyn Error>>;
|
||||
}
|
||||
|
||||
macro_rules! can_handle {
|
||||
( $($name:ident)* ) => (
|
||||
#[allow(non_snake_case)]
|
||||
impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, $($name: Resource,)*>
|
||||
CanHandle<($($name,)*)>
|
||||
for Setup<_SR, _L, _R, _B>
|
||||
where
|
||||
$(
|
||||
_B: SymbolBuilder<$name>,
|
||||
<_B as SymbolBuilder<$name>>::Symbol: Runnable + Debug,
|
||||
_L: ResourceLocator<$name>,
|
||||
_R: From<$name> + BorrowResource<$name>,
|
||||
Self: CanHandle<<_B as SymbolBuilder<$name>>::Prerequisites> +
|
||||
CanHandle<<_L as ResourceLocator<$name>>::Prerequisites>
|
||||
),*
|
||||
{
|
||||
fn handle(&mut self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box<dyn Error>>
|
||||
{
|
||||
$(let $name = self.add($name)?;)*
|
||||
Ok((($($name.0,)*), false $(|| $name.1)*))
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for_each_tuple!(can_handle);
|
||||
|
||||
// This is for self-referential T
|
||||
// FIXME: Wait for specialization
|
||||
impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle<Option<T>>
|
||||
for Setup<_SR, _L, _R, _B>
|
||||
where
|
||||
_B: SymbolBuilder<T>,
|
||||
<_B as SymbolBuilder<T>>::Symbol: Runnable + Debug,
|
||||
_L: ResourceLocator<T, Prerequisites = Option<T>>,
|
||||
_R: From<T> + BorrowResource<T>,
|
||||
Self: CanHandle<<_B as SymbolBuilder<T>>::Prerequisites>,
|
||||
{
|
||||
fn handle(
|
||||
&mut self,
|
||||
r: Option<T>,
|
||||
) -> Result<(<Option<T> as ToArtifact>::Artifact, bool), Box<dyn Error>> {
|
||||
Ok(match r {
|
||||
Some(r) => {
|
||||
let (result, did_run) = self.add(r)?;
|
||||
(Some(result), did_run)
|
||||
}
|
||||
None => (None, false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle<T> for Setup<_SR, _L, _R, _B>
|
||||
where
|
||||
_B: SymbolBuilder<T>,
|
||||
<_B as SymbolBuilder<T>>::Symbol: Runnable + Debug,
|
||||
_L: ResourceLocator<T>,
|
||||
_R: From<T> + Debug + BorrowResource<T>,
|
||||
Self: CanHandle<<_B as SymbolBuilder<T>>::Prerequisites>
|
||||
+ CanHandle<<_L as ResourceLocator<T>>::Prerequisites>,
|
||||
{
|
||||
fn handle(&mut self, r: T) -> Result<(<T as ToArtifact>::Artifact, bool), Box<dyn Error>> {
|
||||
self.add(r)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Setup<
|
||||
SR,
|
||||
L = DefaultLocator,
|
||||
R = DefaultResources<'static, &'static str>,
|
||||
B = DefaultBuilder,
|
||||
> {
|
||||
symbol_runner: SR,
|
||||
resources: HashSet<R>,
|
||||
phantom: PhantomData<(L, B)>,
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/issues/27336
|
||||
impl<SR> Setup<SR> {
|
||||
pub fn new(symbol_runner: SR) -> Self {
|
||||
Self {
|
||||
symbol_runner,
|
||||
resources: Default::default(),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SR, L, R: Hash + Eq, B> Setup<SR, L, R, B> {
|
||||
pub fn new_with(symbol_runner: SR) -> Self {
|
||||
Self {
|
||||
symbol_runner,
|
||||
resources: Default::default(),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Runnable {
|
||||
fn run<R: SymbolRunner>(&self, runner: &R, force: bool) -> Result<bool, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
impl<S: Symbol + Debug> Runnable for S {
|
||||
fn run<R: SymbolRunner>(&self, runner: &R, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
runner.run_symbol(self, force)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! runnable_for_tuple {
|
||||
( $($name:ident)* ) => (
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) {
|
||||
#[allow(unused)]
|
||||
fn run<_R: SymbolRunner>(&self, runner: &_R, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
let ($($name,)*) = self;
|
||||
Ok($(runner.run_symbol($name, force)? || )* false)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for_each_tuple!(runnable_for_tuple);
|
||||
|
||||
impl<SR: SymbolRunner, L, Rs: Hash + Eq, B> Setup<SR, L, Rs, B> {
|
||||
pub fn add<R: Resource>(&mut self, resource: R) -> Result<(R::Artifact, bool), Box<dyn Error>>
|
||||
where
|
||||
B: SymbolBuilder<R>,
|
||||
<B as SymbolBuilder<R>>::Symbol: Runnable + Debug,
|
||||
L: ResourceLocator<R>,
|
||||
Rs: From<R> + BorrowResource<R>,
|
||||
Self: CanHandle<B::Prerequisites> + CanHandle<<L as ResourceLocator<R>>::Prerequisites>,
|
||||
{
|
||||
self.add_force(resource, false)
|
||||
}
|
||||
|
||||
pub fn add_force<R: Resource>(
|
||||
&mut self,
|
||||
resource: R,
|
||||
force_run: bool,
|
||||
) -> Result<(R::Artifact, bool), Box<dyn Error>>
|
||||
where
|
||||
B: SymbolBuilder<R>,
|
||||
<B as SymbolBuilder<R>>::Symbol: Runnable + Debug,
|
||||
L: ResourceLocator<R>,
|
||||
Rs: From<R> + BorrowResource<R>,
|
||||
Self: CanHandle<B::Prerequisites> + CanHandle<<L as ResourceLocator<R>>::Prerequisites>,
|
||||
{
|
||||
let (target, target_prereqs) = L::locate(&resource);
|
||||
let storable_resource = Rs::from(resource);
|
||||
let did_run = if self.resources.get(&storable_resource).is_some() {
|
||||
assert!(
|
||||
!force_run,
|
||||
"Forcing to run an already-added resource is a logical error"
|
||||
);
|
||||
false
|
||||
} else {
|
||||
let (_, target_prereqs_did_run) = self.handle(target_prereqs)?;
|
||||
let (symbol, prereqs_did_run) =
|
||||
self.get_symbol(storable_resource.borrow_resource().unwrap(), &target)?;
|
||||
self.resources.insert(storable_resource);
|
||||
self.run_symbol(
|
||||
symbol,
|
||||
force_run || target_prereqs_did_run || prereqs_did_run,
|
||||
)?
|
||||
};
|
||||
Ok((target, did_run))
|
||||
}
|
||||
|
||||
fn get_symbol<R: Resource>(
|
||||
&mut self,
|
||||
resource: &R,
|
||||
target: &R::Artifact,
|
||||
) -> Result<(<B as SymbolBuilder<R>>::Symbol, bool), Box<dyn Error>>
|
||||
where
|
||||
B: SymbolBuilder<R>,
|
||||
Self: CanHandle<B::Prerequisites>,
|
||||
{
|
||||
let (prereqs, prereqs_did_run) = self.handle(B::prerequisites(resource))?;
|
||||
Ok((B::create(resource, target, prereqs), prereqs_did_run))
|
||||
}
|
||||
|
||||
pub fn run_symbol<S: Runnable>(&self, symbol: S, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
symbol.run(&self.symbol_runner, force)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::resources::{BorrowResource, Resource};
|
||||
use crate::schema::SymbolRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use crate::to_artifact::ToArtifact;
|
||||
use crate::{ResourceLocator, Setup, SymbolBuilder};
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
struct TestSymbolRunner {
|
||||
count: Rc<RefCell<usize>>,
|
||||
}
|
||||
|
||||
impl SymbolRunner for TestSymbolRunner {
|
||||
fn run_symbol<S: Symbol + Debug>(
|
||||
&self,
|
||||
symbol: &S,
|
||||
force: bool,
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
let run = force || !symbol.target_reached()?;
|
||||
if run {
|
||||
*self.count.borrow_mut() += 1;
|
||||
}
|
||||
Ok(run)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
struct TestResource<T>(&'static str, T);
|
||||
impl<T> Resource for TestResource<T> {
|
||||
type Artifact = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
enum Resources {
|
||||
A(TestResource<&'static str>),
|
||||
B(TestResource<()>),
|
||||
}
|
||||
impl From<TestResource<&'static str>> for Resources {
|
||||
fn from(from: TestResource<&'static str>) -> Self {
|
||||
Self::A(from)
|
||||
}
|
||||
}
|
||||
impl From<TestResource<()>> for Resources {
|
||||
fn from(from: TestResource<()>) -> Self {
|
||||
Self::B(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorrowResource<TestResource<&'static str>> for Resources {
|
||||
fn borrow_resource(&self) -> Option<&TestResource<&'static str>> {
|
||||
match self {
|
||||
Self::A(a) => Some(a),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl BorrowResource<TestResource<()>> for Resources {
|
||||
fn borrow_resource(&self) -> Option<&TestResource<()>> {
|
||||
match self {
|
||||
Self::B(b) => Some(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TestResourceLocator;
|
||||
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
||||
type Prerequisites = ();
|
||||
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
||||
((), ())
|
||||
}
|
||||
}
|
||||
|
||||
struct TestSymbolBuilder;
|
||||
impl SymbolBuilder<TestResource<&'static str>> for TestSymbolBuilder {
|
||||
type Symbol = TestSymbol;
|
||||
type Prerequisites = TestResource<()>;
|
||||
|
||||
fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites {
|
||||
TestResource(resource.1, ())
|
||||
}
|
||||
fn create(
|
||||
resource: &TestResource<&'static str>,
|
||||
(): &(),
|
||||
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Symbol {
|
||||
TestSymbol {
|
||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SymbolBuilder<TestResource<()>> for TestSymbolBuilder {
|
||||
type Symbol = TestSymbol;
|
||||
type Prerequisites = ();
|
||||
|
||||
fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {}
|
||||
fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Symbol {
|
||||
TestSymbol {
|
||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestSymbol {
|
||||
reached: bool,
|
||||
}
|
||||
impl Symbol for TestSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(self.reached)
|
||||
}
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_setup() -> (
|
||||
Rc<RefCell<usize>>,
|
||||
Setup<TestSymbolRunner, TestResourceLocator, Resources, TestSymbolBuilder>,
|
||||
) {
|
||||
let count = Rc::new(RefCell::new(0));
|
||||
let runner = TestSymbolRunner {
|
||||
count: Rc::clone(&count),
|
||||
};
|
||||
(count, Setup::new_with(runner))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_uses_force() {
|
||||
let (count, mut setup) = get_setup();
|
||||
setup.add(TestResource("A", "b")).unwrap();
|
||||
assert_eq!(*count.borrow(), 2);
|
||||
setup.add(TestResource("A", "b")).unwrap();
|
||||
assert_eq!(*count.borrow(), 2);
|
||||
|
||||
let (count, mut setup) = get_setup();
|
||||
setup.add(TestResource("A", "B")).unwrap();
|
||||
assert_eq!(*count.borrow(), 0);
|
||||
}
|
||||
}
|
||||
1
src/static_files.rs
Normal file
1
src/static_files.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
include!(concat!(env!("OUT_DIR"), "/static_files.rs"));
|
||||
|
|
@ -1,32 +1,33 @@
|
|||
use std::error::Error;
|
||||
use std::fs::read_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub trait Storage {
|
||||
fn write_filename(&self) -> String;
|
||||
fn read_filename(&self) -> Result<String, Box<dyn Error>>;
|
||||
fn write_filename(&self) -> PathBuf;
|
||||
fn read_filename(&self) -> Result<PathBuf, Box<dyn Error>>;
|
||||
fn recent_date(&self) -> Result<u64, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleStorage(String, String);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimpleStorage(PathBuf);
|
||||
|
||||
impl SimpleStorage {
|
||||
pub const fn new(base: String, filename: String) -> Self {
|
||||
Self(base, filename)
|
||||
pub const fn new(base: PathBuf) -> Self {
|
||||
Self(base)
|
||||
}
|
||||
|
||||
fn get_path(&self, date: Option<u64>) -> String {
|
||||
fn get_path(&self, date: Option<u64>) -> PathBuf {
|
||||
match date {
|
||||
Some(d) => format!("{}/_{}/{}", self.0, self.1, d),
|
||||
None => format!("{}/_{}", self.0, self.1),
|
||||
Some(d) => self.0.join(d.to_string()),
|
||||
None => self.0.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Storage for SimpleStorage {
|
||||
fn write_filename(&self) -> String {
|
||||
fn write_filename(&self) -> PathBuf {
|
||||
self.get_path(Some(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
|
@ -35,7 +36,7 @@ impl Storage for SimpleStorage {
|
|||
))
|
||||
}
|
||||
|
||||
fn read_filename(&self) -> Result<String, Box<dyn Error>> {
|
||||
fn read_filename(&self) -> Result<PathBuf, Box<dyn Error>> {
|
||||
Ok(self.get_path(Some(self.recent_date()?)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct AcmeAccountKey<'a, P: AsRef<Path>, C: CommandRunner> {
|
||||
path: P,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, P: AsRef<Path>, C: CommandRunner> AcmeAccountKey<'a, P, C> {
|
||||
pub fn new(path: P, command_runner: &'a C) -> Self {
|
||||
AcmeAccountKey {
|
||||
path,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bytes(&self) -> u32 {
|
||||
4096
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, C: CommandRunner> fmt::Display for AcmeAccountKey<'_, P, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "AcmeAccountKey {}", self.path.as_ref().display())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, C: CommandRunner> Symbol for AcmeAccountKey<'_, P, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
let stdout = self.command_runner.get_output(
|
||||
"openssl",
|
||||
args![
|
||||
"rsa",
|
||||
"-in",
|
||||
self.path.as_ref(),
|
||||
"-noout",
|
||||
"-check",
|
||||
"-text",
|
||||
],
|
||||
)?;
|
||||
Ok(stdout.ends_with("RSA key ok\n".as_bytes()))
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
"openssl",
|
||||
args![
|
||||
"genrsa",
|
||||
"-out",
|
||||
self.path.as_ref(),
|
||||
self.get_bytes().to_string(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
if let Some(parent) = self.path.as_ref().parent() {
|
||||
vec![Resource::new("dir", parent.to_str().unwrap())]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
|
|
@ -1,80 +1,61 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File as FsFile;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct AcmeCert<
|
||||
'a,
|
||||
D: AsRef<str>,
|
||||
R: AsRef<Path>,
|
||||
C: CommandRunner,
|
||||
K: AsRef<Path>,
|
||||
CH: AsRef<Path>,
|
||||
> {
|
||||
#[derive(Debug)]
|
||||
pub struct Cert<_C, C, D, P> {
|
||||
domain: D,
|
||||
command_runner: &'a C,
|
||||
root_cert_path: R,
|
||||
account_key_path: K,
|
||||
challenges_path: CH,
|
||||
command_runner: C,
|
||||
root_cert_path: P,
|
||||
account_key_path: P,
|
||||
challenges_path: P,
|
||||
csr_path: P,
|
||||
cert_path: P,
|
||||
phantom: PhantomData<_C>,
|
||||
}
|
||||
|
||||
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>>
|
||||
AcmeCert<'a, D, R, C, K, CH>
|
||||
{
|
||||
impl<_C, C, D, P> Cert<_C, C, D, P> {
|
||||
pub fn new(
|
||||
domain: D,
|
||||
command_runner: &'a C,
|
||||
root_cert_path: R,
|
||||
account_key_path: K,
|
||||
challenges_path: CH,
|
||||
command_runner: C,
|
||||
root_cert_path: P,
|
||||
account_key_path: P,
|
||||
challenges_path: P,
|
||||
csr_path: P,
|
||||
cert_path: P,
|
||||
) -> Self {
|
||||
AcmeCert {
|
||||
Self {
|
||||
domain,
|
||||
command_runner,
|
||||
root_cert_path,
|
||||
account_key_path,
|
||||
challenges_path,
|
||||
csr_path,
|
||||
cert_path,
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_csr_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/local_certs/{}.csr", self.domain.as_ref()).into()
|
||||
}
|
||||
|
||||
fn get_cert_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/local_certs/{}.crt", self.domain.as_ref()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>> fmt::Display
|
||||
for AcmeCert<'_, D, R, C, K, CH>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "AcmeCert {}", self.domain.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
|
||||
|
||||
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>> Symbol
|
||||
for AcmeCert<'_, D, R, C, K, CH>
|
||||
{
|
||||
impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for Cert<_C, C, D, P> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.get_cert_path().exists() {
|
||||
if !self.cert_path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let output = self.command_runner.run_with_args(
|
||||
let output = self.command_runner.borrow().run_with_args(
|
||||
"openssl",
|
||||
args![
|
||||
"x509",
|
||||
"-in",
|
||||
self.get_cert_path(),
|
||||
self.cert_path.as_ref(),
|
||||
"-noout",
|
||||
"-subject",
|
||||
"-checkend",
|
||||
|
|
@ -92,13 +73,14 @@ impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<
|
|||
Ok(
|
||||
self
|
||||
.command_runner
|
||||
.borrow()
|
||||
.run_successfully(
|
||||
"openssl",
|
||||
args![
|
||||
"verify",
|
||||
"--untrusted",
|
||||
self.root_cert_path.as_ref(),
|
||||
self.get_cert_path(),
|
||||
self.cert_path.as_ref(),
|
||||
],
|
||||
)
|
||||
.is_ok(),
|
||||
|
|
@ -118,36 +100,21 @@ impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<
|
|||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
let output = self.command_runner.get_output(
|
||||
let output = self.command_runner.borrow().get_output(
|
||||
"acme-tiny",
|
||||
args![
|
||||
"--account-key",
|
||||
self.account_key_path.as_ref(),
|
||||
"--csr",
|
||||
self.get_csr_path(),
|
||||
self.csr_path.as_ref(),
|
||||
"--acme-dir",
|
||||
self.challenges_path.as_ref(),
|
||||
],
|
||||
)?;
|
||||
let mut file = FsFile::create(self.get_cert_path())?;
|
||||
let mut file = FsFile::create(self.cert_path.as_ref())?;
|
||||
file.write_all(&output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new("file", self.get_csr_path().to_str().unwrap())]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,117 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File as FsFile;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct AcmeCertChain<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> {
|
||||
domain: D,
|
||||
command_runner: &'a C,
|
||||
root_cert: R,
|
||||
}
|
||||
|
||||
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> AcmeCertChain<'a, D, R, C> {
|
||||
pub fn new(domain: D, command_runner: &'a C, root_cert: R) -> Self {
|
||||
AcmeCertChain {
|
||||
domain,
|
||||
command_runner,
|
||||
root_cert,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_single_cert_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/local_certs/{}.crt", self.domain.as_ref()).into()
|
||||
}
|
||||
|
||||
fn get_cert_chain_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/local_certs/{}.chained.crt", self.domain.as_ref()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> fmt::Display for AcmeCertChain<'_, D, R, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "AcmeCertChain {}", self.domain.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
|
||||
|
||||
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> Symbol for AcmeCertChain<'_, D, R, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.get_cert_chain_path().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let stdout = self.command_runner.get_output(
|
||||
"openssl",
|
||||
args![
|
||||
"x509",
|
||||
"-in",
|
||||
self.get_cert_chain_path(),
|
||||
"-noout",
|
||||
"-subject",
|
||||
"-checkend",
|
||||
(30 * DAYS_IN_SECONDS).to_string(),
|
||||
],
|
||||
)?;
|
||||
if stdout
|
||||
!= format!(
|
||||
"subject=CN = {}\nCertificate will not expire\n",
|
||||
self.domain.as_ref()
|
||||
)
|
||||
.as_bytes()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
// FIXME: From my understanding, the -untrusted *.pem parameter shouldn't be necessary, but is necessary with openssl 1.1.0f-3
|
||||
Ok(
|
||||
self
|
||||
.command_runner
|
||||
.run_successfully(
|
||||
"openssl",
|
||||
args![
|
||||
"verify",
|
||||
"-untrusted",
|
||||
self.root_cert.as_ref(),
|
||||
self.get_cert_chain_path(),
|
||||
],
|
||||
)
|
||||
.is_ok(),
|
||||
)
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
let output = self.command_runner.get_output(
|
||||
"cat",
|
||||
args![self.get_single_cert_path(), self.root_cert.as_ref(),],
|
||||
)?;
|
||||
let mut file = FsFile::create(self.get_cert_chain_path())?;
|
||||
file.write_all(&output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new(
|
||||
"file",
|
||||
self.get_single_cert_path().to_str().unwrap(),
|
||||
)]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
|
|
@ -1,113 +1,2 @@
|
|||
use std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::command_runner::SetuidCommandRunner;
|
||||
use crate::symbols::concat::Concat;
|
||||
use crate::symbols::dir::Dir;
|
||||
use crate::symbols::file::File;
|
||||
use crate::symbols::list::List;
|
||||
use crate::symbols::owner::Owner;
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
mod account_key;
|
||||
mod cert;
|
||||
mod chain;
|
||||
|
||||
pub use self::account_key::AcmeAccountKey;
|
||||
pub use self::cert::AcmeCert;
|
||||
pub use self::chain::AcmeCertChain;
|
||||
|
||||
const ROOT_CERT_FILE_NAME: &str = "lets_encrypt_x3_cross_signed.pem";
|
||||
const ACCOUNT_KEY_FILE_NAME: &str = "account.key";
|
||||
|
||||
pub struct Factory<'a, U: AsRef<str>, H: AsRef<Path>, C: AsRef<str>, R: CommandRunner> {
|
||||
user_name: U,
|
||||
home_dir: H,
|
||||
cert: C,
|
||||
command_runner: &'a R,
|
||||
acme_command_runner: SetuidCommandRunner<'a, U, R>,
|
||||
}
|
||||
|
||||
impl<'a, U: Clone + AsRef<str>, H: AsRef<Path>, C: AsRef<str>, R: CommandRunner>
|
||||
Factory<'a, U, H, C, R>
|
||||
{
|
||||
pub fn new(user_name: U, home_dir: H, cert: C, command_runner: &'a R) -> Self {
|
||||
let acme_command_runner = SetuidCommandRunner::new(user_name.clone(), command_runner);
|
||||
Self {
|
||||
user_name,
|
||||
home_dir,
|
||||
cert,
|
||||
command_runner,
|
||||
acme_command_runner,
|
||||
}
|
||||
}
|
||||
pub fn get_challenges_dir(&'a self) -> Cow<'_, Path> {
|
||||
[self.home_dir.as_ref(), "challenges".as_ref()]
|
||||
.iter()
|
||||
.collect::<PathBuf>()
|
||||
.into()
|
||||
}
|
||||
pub fn get_init(&'a self) -> impl Symbol + 'a {
|
||||
let root_cert_path: PathBuf = [self.home_dir.as_ref(), ROOT_CERT_FILE_NAME.as_ref()]
|
||||
.iter()
|
||||
.collect();
|
||||
let account_key_file: PathBuf = [self.home_dir.as_ref(), ACCOUNT_KEY_FILE_NAME.as_ref()]
|
||||
.iter()
|
||||
.collect();
|
||||
List::from((
|
||||
AcmeAccountKey::new(account_key_file.clone(), self.command_runner),
|
||||
Owner::new(
|
||||
account_key_file,
|
||||
self.user_name.clone(),
|
||||
self.command_runner,
|
||||
),
|
||||
Dir::new(self.get_challenges_dir()),
|
||||
Owner::new(
|
||||
self.get_challenges_dir(),
|
||||
self.user_name.clone(),
|
||||
self.command_runner,
|
||||
),
|
||||
Dir::new("/etc/ssl/local_certs"),
|
||||
Owner::new(
|
||||
"/etc/ssl/local_certs",
|
||||
self.user_name.clone(),
|
||||
self.command_runner,
|
||||
),
|
||||
File::new(root_cert_path, self.cert.as_ref()),
|
||||
))
|
||||
}
|
||||
pub fn get_cert<HOST: 'a + Clone + AsRef<str>>(&'a self, host: HOST) -> impl Symbol + 'a {
|
||||
let root_cert_path: PathBuf = [self.home_dir.as_ref(), ROOT_CERT_FILE_NAME.as_ref()]
|
||||
.iter()
|
||||
.collect();
|
||||
let account_key_path: PathBuf = [self.home_dir.as_ref(), ACCOUNT_KEY_FILE_NAME.as_ref()]
|
||||
.iter()
|
||||
.collect();
|
||||
List::from((
|
||||
AcmeCert::new(
|
||||
host.clone(),
|
||||
&self.acme_command_runner,
|
||||
root_cert_path.clone(),
|
||||
account_key_path,
|
||||
self.get_challenges_dir(),
|
||||
),
|
||||
AcmeCertChain::new(host, &self.acme_command_runner, root_cert_path),
|
||||
))
|
||||
}
|
||||
pub fn get_key_and_cert_bundle<HOST: 'a + Clone + AsRef<str>>(
|
||||
&'a self,
|
||||
host: HOST,
|
||||
) -> impl Symbol + 'a {
|
||||
List::from((
|
||||
self.get_cert(host.clone()),
|
||||
Concat::new(
|
||||
[
|
||||
format!("/etc/ssl/private/{}.key", host.as_ref()),
|
||||
format!("/etc/ssl/local_certs/{}.chained.crt", host.as_ref()),
|
||||
],
|
||||
format!("/etc/ssl/private/{}.with_key.crt", host.as_ref()),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
pub use self::cert::Cert;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::copy;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Concat<S, D, I> {
|
||||
target: D,
|
||||
sources: S,
|
||||
|
|
@ -46,34 +44,4 @@ impl<S: AsRef<[I]>, D: AsRef<Path>, I: AsRef<Path>> Symbol for Concat<S, D, I> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
let mut r: Vec<Resource> = self
|
||||
.sources
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|s| Resource::new("file", s.as_ref().to_str().unwrap()))
|
||||
.collect();
|
||||
if let Some(parent) = self.target.as_ref().parent() {
|
||||
r.push(Resource::new("dir", parent.to_str().unwrap()))
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, D: AsRef<Path>, I> fmt::Display for Concat<S, D, I> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Concat {}", self.target.as_ref().display())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct Cron<'r, C: AsRef<str>, U: AsRef<str>, R: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct Cron<'r, C, U, R> {
|
||||
user: U,
|
||||
content: C,
|
||||
command_runner: &'r R,
|
||||
}
|
||||
|
||||
impl<'r, U: AsRef<str>, R: CommandRunner> Cron<'r, String, U, R> {
|
||||
impl<'r, U, R> Cron<'r, String, U, R> {
|
||||
pub fn new<C: AsRef<str>>(user: U, content: C, command_runner: &'r R) -> Self {
|
||||
Cron {
|
||||
Self {
|
||||
user,
|
||||
content: String::from(content.as_ref()) + "\n",
|
||||
command_runner,
|
||||
|
|
@ -40,21 +38,4 @@ impl<C: AsRef<str>, U: AsRef<str>, R: CommandRunner> Symbol for Cron<'_, C, U, R
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, U: AsRef<str>, R: CommandRunner> fmt::Display for Cron<'_, C, U, R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Cron {}", self.user.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct Dir<D: AsRef<Path>> {
|
||||
path: D,
|
||||
#[derive(Debug)]
|
||||
pub struct Dir<P> {
|
||||
path: P,
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>> Dir<D> {
|
||||
pub fn new(path: D) -> Self {
|
||||
impl<P> Dir<P> {
|
||||
pub fn new(path: P) -> Self {
|
||||
Self { path }
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>> Symbol for Dir<D> {
|
||||
impl<P: AsRef<Path>> Symbol for Dir<P> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
|
|
@ -35,36 +33,4 @@ impl<D: AsRef<Path>> Symbol for Dir<D> {
|
|||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
fs::create_dir(self.path.as_ref()).map_err(|e| Box::new(e) as Box<dyn Error>)
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
if let Some(parent) = self.path.as_ref().parent() {
|
||||
vec![Resource::new("dir", parent.to_str().unwrap())]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
Some(vec![Resource::new(
|
||||
"dir",
|
||||
self.path.as_ref().to_str().unwrap(),
|
||||
)])
|
||||
}
|
||||
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<Path>> fmt::Display for Dir<D> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Dir {}", self.path.as_ref().display())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,436 +0,0 @@
|
|||
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;
|
||||
|
||||
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()
|
||||
}
|
||||
fn php_version(&self) -> &'static str {
|
||||
"7.0"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultPolicy;
|
||||
|
||||
impl Policy for DefaultPolicy {}
|
||||
|
||||
pub struct SymbolFactory<'a, C: CommandRunner, P: Policy> {
|
||||
command_runner: &'a C,
|
||||
acme_factory: AcmeFactory<'a, Cow<'a, str>, PathBuf, &'a str, C>,
|
||||
policy: &'a P,
|
||||
}
|
||||
|
||||
impl<'b, C: 'b + CommandRunner, P: 'b + Policy> SymbolFactory<'b, C, P> {
|
||||
pub fn new(command_runner: &'b C, 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,
|
||||
policy,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cert<'a, H: 'a + AsRef<str> + Clone>(&'a self, host: H) -> impl Symbol + 'a {
|
||||
self.acme_factory.get_cert(host)
|
||||
}
|
||||
|
||||
pub fn get_key_and_cert_bundle<'a, H: 'a + AsRef<str> + Clone>(
|
||||
&'a self,
|
||||
host: H,
|
||||
) -> impl Symbol + 'a {
|
||||
self.acme_factory.get_key_and_cert_bundle(host)
|
||||
}
|
||||
|
||||
pub fn get_nginx_acme_server<'a, S: 'a + Symbol>(
|
||||
&'a self,
|
||||
host: &'a 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.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 {}/;
|
||||
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, max_children: usize) -> impl Symbol + 'a {
|
||||
let socket = self.get_php_fpm_pool_socket_path(user_name);
|
||||
let php_version = self.policy.php_version();
|
||||
Hook::new(
|
||||
File::new(
|
||||
format!("/etc/php/{}/fpm/pool.d/{}.conf", php_version, user_name),
|
||||
format!(
|
||||
"[{0}]
|
||||
|
||||
user = {0}
|
||||
group = www-data
|
||||
listen = {1}
|
||||
listen.owner = www-data
|
||||
pm = ondemand
|
||||
pm.max_children = {2}
|
||||
catch_workers_output = yes
|
||||
env[PATH] = /usr/local/bin:/usr/bin:/bin
|
||||
",
|
||||
user_name,
|
||||
socket.to_str().unwrap(),
|
||||
max_children
|
||||
),
|
||||
),
|
||||
ReloadService::new(format!("php{}-fpm", php_version), self.command_runner),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serve_php<'a, ROOT: AsRef<Path>>(
|
||||
&'a self,
|
||||
host_name: &'static str,
|
||||
root_dir: ROOT,
|
||||
max_children: usize,
|
||||
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, max_children),
|
||||
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,
|
||||
10,
|
||||
"
|
||||
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, 10),
|
||||
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,
|
||||
25,
|
||||
"
|
||||
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,
|
||||
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),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,22 @@
|
|||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File as FsFile;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct File<C: AsRef<str>, D: AsRef<Path>> {
|
||||
#[derive(Debug)]
|
||||
pub struct File<D, C> {
|
||||
path: D,
|
||||
content: C,
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, D: AsRef<Path>> File<C, D> {
|
||||
impl<D, C> File<D, C> {
|
||||
pub fn new(path: D, content: C) -> Self {
|
||||
Self { path, content }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, D: AsRef<Path>> Symbol for File<C, D> {
|
||||
impl<D: AsRef<Path>, C: AsRef<str>> Symbol for File<D, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
|
|
@ -44,29 +40,4 @@ impl<C: AsRef<str>, D: AsRef<Path>> Symbol for File<C, D> {
|
|||
file.write_all(self.content.as_ref().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
if let Some(parent) = self.path.as_ref().parent() {
|
||||
vec![Resource::new("dir", parent.to_str().unwrap())]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, D: AsRef<Path>> fmt::Display for File<C, D> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "File {}", self.path.as_ref().display())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,48 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::fs::metadata;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct GitCheckout<'a, C: CommandRunner, T: AsRef<Path>> {
|
||||
target: T,
|
||||
source: &'a str,
|
||||
branch: &'a str,
|
||||
command_runner: &'a C,
|
||||
#[derive(Debug)]
|
||||
pub struct Checkout<_C, C, P, S, B> {
|
||||
target: P,
|
||||
source: S,
|
||||
branch: B,
|
||||
command_runner: C,
|
||||
phantom: PhantomData<_C>,
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner, T: AsRef<Path>> GitCheckout<'a, C, T> {
|
||||
pub fn new(target: T, source: &'a str, branch: &'a str, command_runner: &'a C) -> Self {
|
||||
GitCheckout {
|
||||
impl<C, _C, P, S, B> Checkout<_C, C, P, S, B> {
|
||||
pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self {
|
||||
Self {
|
||||
target,
|
||||
source,
|
||||
branch,
|
||||
command_runner,
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, T: AsRef<Path>> fmt::Display for GitCheckout<'_, C, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Checkout {} (branch {}) into {}",
|
||||
self.source,
|
||||
self.branch,
|
||||
self.target.as_ref().display()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, T: AsRef<Path>> GitCheckout<'_, C, T> {
|
||||
fn _run_in_target_repo<S: AsRef<OsStr>>(&self, args: &[S]) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S, B> Checkout<C, _C, P, S, B> {
|
||||
fn _run_in_target_repo(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut new_args = vec![OsStr::new("-C"), self.target.as_ref().as_ref()];
|
||||
new_args.extend(args.iter().map(AsRef::as_ref));
|
||||
self.command_runner.get_output("git", &new_args)
|
||||
self.command_runner.borrow().get_output("git", &new_args)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, T: AsRef<Path>> Symbol for GitCheckout<'_, C, T> {
|
||||
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
|
||||
for Checkout<C, _C, P, S, B>
|
||||
{
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if let Err(e) = metadata(self.target.as_ref()) {
|
||||
return if e.kind() == io::ErrorKind::NotFound {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(Box::new(e))
|
||||
};
|
||||
if !self.target.as_ref().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
self._run_in_target_repo(&["fetch", self.source, self.branch])?;
|
||||
self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?;
|
||||
// git rev-list resolves tag objects
|
||||
let fetch_head = self._run_in_target_repo(&["rev-list", "-1", "FETCH_HEAD"])?;
|
||||
let head = self._run_in_target_repo(&["rev-list", "-1", "HEAD"])?;
|
||||
|
|
@ -65,48 +51,23 @@ impl<C: CommandRunner, T: AsRef<Path>> Symbol for GitCheckout<'_, C, T> {
|
|||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
if !self.target.as_ref().exists() {
|
||||
return self.command_runner.run_successfully(
|
||||
return self.command_runner.borrow().run_successfully(
|
||||
"git",
|
||||
&[
|
||||
OsStr::new("clone"),
|
||||
"--depth".as_ref(),
|
||||
"1".as_ref(),
|
||||
"-b".as_ref(),
|
||||
args![
|
||||
"clone",
|
||||
"--depth",
|
||||
"1",
|
||||
"-b",
|
||||
self.branch.as_ref(),
|
||||
self.source.as_ref(),
|
||||
self.target.as_ref().as_ref(),
|
||||
self.target.as_ref(),
|
||||
],
|
||||
);
|
||||
}
|
||||
self._run_in_target_repo(&["fetch", self.source, self.branch])?;
|
||||
self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?;
|
||||
self._run_in_target_repo(&["merge", "FETCH_HEAD"])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new(
|
||||
"dir",
|
||||
self.target.as_ref().parent().unwrap().to_string_lossy(),
|
||||
)]
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
Some(vec![Resource::new(
|
||||
"dir",
|
||||
self.target.as_ref().to_str().unwrap(),
|
||||
)])
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
pub mod checkout;
|
||||
pub mod submodules;
|
||||
mod checkout;
|
||||
//pub mod submodules;
|
||||
|
||||
pub use checkout::Checkout;
|
||||
|
|
|
|||
|
|
@ -1,196 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct Hook<A, B>
|
||||
where
|
||||
A: Symbol,
|
||||
B: Symbol,
|
||||
{
|
||||
a: A,
|
||||
b: B,
|
||||
}
|
||||
|
||||
// A and B are executed if either A or B are not reached
|
||||
impl<A, B> Hook<A, B>
|
||||
where
|
||||
A: Symbol,
|
||||
B: Symbol,
|
||||
{
|
||||
pub fn new(a: A, b: B) -> Self {
|
||||
Self { a, b }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Symbol for Hook<A, B>
|
||||
where
|
||||
A: Symbol,
|
||||
B: Symbol,
|
||||
{
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
self.a.target_reached().and_then(|reached| {
|
||||
if reached {
|
||||
self.b.target_reached()
|
||||
} else {
|
||||
Ok(reached)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.a.execute()?;
|
||||
self.b.execute()
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
let mut r = vec![];
|
||||
r.extend(self.a.get_prerequisites().into_iter());
|
||||
r.extend(self.b.get_prerequisites().into_iter());
|
||||
r
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
let mut r = vec![];
|
||||
if let Some(provides) = self.a.provides() {
|
||||
r.extend(provides.into_iter());
|
||||
}
|
||||
if let Some(provides) = self.b.provides() {
|
||||
r.extend(provides.into_iter());
|
||||
}
|
||||
if r.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> fmt::Display for Hook<A, B>
|
||||
where
|
||||
A: Symbol,
|
||||
B: Symbol,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Hook {} and then {}", self.a, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::symbols::hook::Hook;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
struct ErrSymbol(String);
|
||||
impl Symbol for ErrSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Err(self.0.clone().into())
|
||||
}
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
Err(self.0.clone().into())
|
||||
}
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ErrSymbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "")
|
||||
}
|
||||
}
|
||||
|
||||
struct OkSymbol(bool);
|
||||
impl Symbol for OkSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(self.0)
|
||||
}
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for OkSymbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_target_reached_fails() {
|
||||
let res = Hook::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap_err().description(), "first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_target_not_reached() {
|
||||
let res = Hook::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_target_reached_fails() {
|
||||
let res = Hook::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap_err().description(), "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_target_not_reached() {
|
||||
let res = Hook::new(OkSymbol(true), OkSymbol(false)).target_reached();
|
||||
assert_eq!(res.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_reached() {
|
||||
let res = Hook::new(OkSymbol(true), OkSymbol(true)).target_reached();
|
||||
assert_eq!(res.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_execute_fails() {
|
||||
let res = Hook::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap_err().description(), "first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_execute_fails() {
|
||||
let res = Hook::new(OkSymbol(true), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap_err().description(), "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_executes() {
|
||||
let res = Hook::new(OkSymbol(true), OkSymbol(true)).execute();
|
||||
assert_eq!(res.unwrap(), ());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, Symbol, SymbolRunner};
|
||||
|
||||
pub struct List<'a> {
|
||||
symbols: Vec<Box<dyn Symbol + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a> List<'a> {
|
||||
pub fn new(symbols: Vec<Box<dyn Symbol + 'a>>) -> Self {
|
||||
List { symbols }
|
||||
}
|
||||
}
|
||||
|
||||
impl Symbol for List<'_> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
for symbol in &self.symbols {
|
||||
if !symbol.target_reached()? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
for symbol in &self.symbols {
|
||||
symbol.execute()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
let mut r = vec![];
|
||||
for symbol in &self.symbols {
|
||||
for req in symbol.get_prerequisites() {
|
||||
if self.provides().map_or(true, |p| !p.contains(&req)) {
|
||||
r.push(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
let mut r = vec![];
|
||||
for symbol in &self.symbols {
|
||||
if let Some(provides) = symbol.provides() {
|
||||
r.extend(provides.into_iter());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if r.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolListAction::new(runner, &self.symbols))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(ListAction::new(
|
||||
self
|
||||
.symbols
|
||||
.into_iter()
|
||||
.map(|s| s.into_action(runner))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_tuple {
|
||||
( $($name:ident)* ) => (
|
||||
#[allow(non_snake_case)]
|
||||
impl<'a, $($name: 'a + Symbol,)*> From<($($name,)*)> for List<'a> {
|
||||
fn from(($($name,)*): ($($name,)*)) -> Self {
|
||||
Self::new(vec![$(Box::new($name),)*])
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for_each_tuple!(from_tuple);
|
||||
|
||||
struct SymbolListAction<'a> {
|
||||
runner: &'a dyn SymbolRunner,
|
||||
symbols: &'a [Box<dyn Symbol + 'a>],
|
||||
}
|
||||
|
||||
impl<'a> SymbolListAction<'a> {
|
||||
fn new(runner: &'a dyn SymbolRunner, symbols: &'a [Box<dyn Symbol + 'a>]) -> Self {
|
||||
Self { runner, symbols }
|
||||
}
|
||||
}
|
||||
|
||||
impl Action for SymbolListAction<'_> {
|
||||
fn run(&self) -> Result<(), Box<dyn Error>> {
|
||||
for symbol in self.symbols {
|
||||
symbol.as_action(self.runner).run()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListAction<'a> {
|
||||
actions: Vec<Box<dyn Action + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a> ListAction<'a> {
|
||||
pub fn new(actions: Vec<Box<dyn Action + 'a>>) -> Self {
|
||||
Self { actions }
|
||||
}
|
||||
}
|
||||
|
||||
impl Action for ListAction<'_> {
|
||||
fn run(&self) -> Result<(), Box<dyn Error>> {
|
||||
for action in &self.actions {
|
||||
action.run()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for List<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "List [ ")?;
|
||||
for symbol in &self.symbols {
|
||||
write!(f, "{} ", symbol)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use symbols::hook::List;
|
||||
|
||||
struct ErrSymbol(String);
|
||||
impl Symbol for ErrSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<Error>> { Err(self.0.clone().into()) }
|
||||
fn execute(&self) -> Result<(), Box<Error>> { Err(self.0.clone().into()) }
|
||||
}
|
||||
impl fmt::Display for ErrSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
|
||||
|
||||
struct OkSymbol(bool);
|
||||
impl Symbol for OkSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<Error>> { Ok(self.0) }
|
||||
fn execute(&self) -> Result<(), Box<Error>> { Ok(()) }
|
||||
}
|
||||
impl fmt::Display for OkSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
|
||||
|
||||
#[test]
|
||||
fn first_target_reached_fails() {
|
||||
let res = List::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap_err().description(), "first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_target_not_reached() {
|
||||
let res = List::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_target_reached_fails() {
|
||||
let res = List::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap_err().description(), "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_target_not_reached() {
|
||||
let res = List::new(OkSymbol(true), OkSymbol(false)).target_reached();
|
||||
assert_eq!(res.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_reached() {
|
||||
let res = List::new(OkSymbol(true), OkSymbol(true)).target_reached();
|
||||
assert_eq!(res.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_execute_fails() {
|
||||
let res = List::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap_err().description(), "first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_execute_fails() {
|
||||
let res = List::new(OkSymbol(true), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap_err().description(), "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_executes() {
|
||||
let res = List::new(OkSymbol(true), OkSymbol(true)).execute();
|
||||
assert_eq!(res.unwrap(), ());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::storage::Storage;
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
|
||||
pub struct MariaDBDatabase<'a, D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct Database<'a, D, S, C> {
|
||||
db_name: D,
|
||||
seed_file: S,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> MariaDBDatabase<'a, D, S, C> {
|
||||
impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> {
|
||||
pub fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self {
|
||||
MariaDBDatabase {
|
||||
Self {
|
||||
db_name,
|
||||
seed_file,
|
||||
command_runner,
|
||||
|
|
@ -28,15 +27,7 @@ impl<'a, D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> MariaDBDatabase<'a, D,
|
|||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> fmt::Display
|
||||
for MariaDBDatabase<'_, D, S, C>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "MariaDB Database {}", self.db_name.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> Symbol for MariaDBDatabase<'_, D, S, C> {
|
||||
impl<D: AsRef<str>, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(
|
||||
self
|
||||
|
|
@ -55,22 +46,11 @@ impl<D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> Symbol for MariaDBDatabase
|
|||
format!(
|
||||
"mariadb '{}' < {}",
|
||||
self.db_name.as_ref(),
|
||||
self.seed_file.as_ref().to_str().unwrap()
|
||||
self.seed_file.read_filename()?.to_str().unwrap()
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::storage::Storage;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct DatabaseDump<'a, N: AsRef<str>, C: CommandRunner, S: Storage> {
|
||||
db_name: N,
|
||||
storage: S,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, N: AsRef<str>, C: CommandRunner, S: Storage> DatabaseDump<'a, N, C, S> {
|
||||
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
|
||||
DatabaseDump {
|
||||
db_name,
|
||||
storage,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
|
||||
let b = self
|
||||
.command_runner
|
||||
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
|
||||
Ok(String::from_utf8(b)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: AsRef<str>, C: CommandRunner, S: Storage> fmt::Display for DatabaseDump<'_, N, C, S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Dump MariaDB Database {}", self.db_name.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for DatabaseDump<'_, N, C, S> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let dump_date = self.storage.recent_date()?;
|
||||
let modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref()))?;
|
||||
if modified_date.trim_end() == "NULL" {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(u64::from_str(modified_date.trim_end())? <= dump_date)
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
"sh",
|
||||
args![
|
||||
"-c",
|
||||
format!(
|
||||
"mysqldump '{}' > {}",
|
||||
self.db_name.as_ref(),
|
||||
self.storage.write_filename()
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
56
src/symbols/mariadb/dump.rs
Normal file
56
src/symbols/mariadb/dump.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::error::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::storage::Storage;
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Dump<'a, N, C, S> {
|
||||
db_name: N,
|
||||
storage: S,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> {
|
||||
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
|
||||
Self {
|
||||
db_name,
|
||||
storage,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
|
||||
let b = self
|
||||
.command_runner
|
||||
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
|
||||
Ok(String::from_utf8(b)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for Dump<'_, N, C, S> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let dump_date = self.storage.recent_date()?;
|
||||
let _modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref()))?;
|
||||
let modified_date = _modified_date.trim_end();
|
||||
Ok(modified_date != "NULL" && u64::from_str(modified_date)? <= dump_date)
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
"sh",
|
||||
args![
|
||||
"-c",
|
||||
format!(
|
||||
"mysqldump '{}' > {}",
|
||||
self.db_name.as_ref(),
|
||||
self.storage.write_filename().to_str().unwrap()
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
mod database;
|
||||
mod database_dump;
|
||||
mod dump;
|
||||
mod user;
|
||||
|
||||
pub use self::database::MariaDBDatabase;
|
||||
pub use self::database_dump::DatabaseDump;
|
||||
pub use self::user::MariaDBUser;
|
||||
pub use self::database::Database;
|
||||
pub use self::dump::Dump;
|
||||
pub use self::user::User;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
|
||||
pub struct MariaDBUser<'a, U: AsRef<str>, C: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct User<'a, U, C> {
|
||||
user_name: U,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, U: AsRef<str>, C: CommandRunner> MariaDBUser<'a, U, C> {
|
||||
impl<'a, U: AsRef<str>, C: CommandRunner> User<'a, U, C> {
|
||||
pub fn new(user_name: U, command_runner: &'a C) -> Self {
|
||||
MariaDBUser {
|
||||
Self {
|
||||
user_name,
|
||||
command_runner,
|
||||
}
|
||||
|
|
@ -26,13 +24,7 @@ impl<'a, U: AsRef<str>, C: CommandRunner> MariaDBUser<'a, U, C> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<U: AsRef<str>, C: CommandRunner> fmt::Display for MariaDBUser<'_, U, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "MariaDB User {}", self.user_name.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: AsRef<str>, C: CommandRunner> Symbol for MariaDBUser<'_, U, C> {
|
||||
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<'_, U, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(
|
||||
self
|
||||
|
|
@ -52,21 +44,6 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for MariaDBUser<'_, U, C> {
|
|||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new("user", self.user_name.as_ref())]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,88 +1,22 @@
|
|||
use crate::resources::Resource;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub trait Action {
|
||||
fn run(&self) -> Result<(), Box<dyn Error>>;
|
||||
}
|
||||
|
||||
pub trait SymbolRunner {
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>;
|
||||
}
|
||||
|
||||
impl<R: SymbolRunner + ?Sized> SymbolRunner for Box<R> {
|
||||
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
|
||||
(**self).run_symbol(symbol)
|
||||
}
|
||||
}
|
||||
|
||||
// Symbol
|
||||
pub trait Symbol: Display {
|
||||
pub trait Symbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>>;
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>>;
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![]
|
||||
}
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
None
|
||||
}
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>;
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a;
|
||||
}
|
||||
|
||||
// SymbolAction
|
||||
pub struct SymbolAction<'a, S: Symbol> {
|
||||
runner: &'a dyn SymbolRunner,
|
||||
symbol: &'a S,
|
||||
}
|
||||
|
||||
impl<'a, S: Symbol> SymbolAction<'a, S> {
|
||||
pub fn new(runner: &'a dyn SymbolRunner, symbol: &'a S) -> Self {
|
||||
Self { runner, symbol }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Symbol> Action for SymbolAction<'_, S> {
|
||||
fn run(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.runner.run_symbol(self.symbol)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OwnedSymbolAction<'a, S: Symbol + 'a> {
|
||||
runner: &'a dyn SymbolRunner,
|
||||
symbol: S,
|
||||
}
|
||||
|
||||
impl<'a, S: Symbol + 'a> OwnedSymbolAction<'a, S> {
|
||||
pub fn new(runner: &'a dyn SymbolRunner, symbol: S) -> Self {
|
||||
Self { runner, symbol }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: Symbol + 'a> Action for OwnedSymbolAction<'a, S> {
|
||||
fn run(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.runner.run_symbol(&self.symbol)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod acme;
|
||||
pub mod concat;
|
||||
pub mod cron;
|
||||
pub mod dir;
|
||||
pub mod factory;
|
||||
pub mod file;
|
||||
pub mod git;
|
||||
pub mod hook;
|
||||
pub mod list;
|
||||
pub mod mariadb;
|
||||
pub mod nginx;
|
||||
pub mod noop;
|
||||
pub mod npm;
|
||||
pub mod owner;
|
||||
pub mod postgresql;
|
||||
pub mod stored_directory;
|
||||
pub mod saved_directory;
|
||||
pub mod systemd;
|
||||
pub mod tls;
|
||||
pub mod user;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
pub mod server;
|
||||
|
|
@ -1,246 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::file::File as FileSymbol;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NginxServerError<E: Error> {
|
||||
ExecError(E),
|
||||
GenericError,
|
||||
}
|
||||
|
||||
impl From<io::Error> for NginxServerError<io::Error> {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::ExecError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Error> Error for NginxServerError<E> {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::ExecError(ref e) => e.description(),
|
||||
Self::GenericError => "Generic error",
|
||||
}
|
||||
}
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
match self {
|
||||
Self::ExecError(ref e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Error> fmt::Display for NginxServerError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NginxServer<'a, C: CommandRunner, T: AsRef<str>, P: AsRef<Path>> {
|
||||
command_runner: &'a C,
|
||||
file: FileSymbol<T, P>,
|
||||
}
|
||||
|
||||
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<SOCKET: AsRef<Path>, STATIC: AsRef<Path>>(
|
||||
socket_path: SOCKET,
|
||||
static_path: STATIC,
|
||||
) -> String {
|
||||
format!(
|
||||
"
|
||||
root {};
|
||||
index index.html index.php;
|
||||
location ~ [^/]\\.php(/|$) {{
|
||||
fastcgi_pass unix:{};
|
||||
include \"snippets/fastcgi-php.conf\";
|
||||
}}",
|
||||
static_path.as_ref().to_str().unwrap(),
|
||||
socket_path.as_ref().to_str().unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
pub trait SocketSpec {
|
||||
fn to_nginx(&self) -> String;
|
||||
}
|
||||
|
||||
impl<T> SocketSpec for T
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
fn to_nginx(&self) -> String {
|
||||
format!("unix:{}:", self.as_ref().to_str().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalTcpSocket(usize);
|
||||
|
||||
impl LocalTcpSocket {
|
||||
pub const fn new(x: usize) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl SocketSpec for LocalTcpSocket {
|
||||
fn to_nginx(&self) -> String {
|
||||
format!("localhost:{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner> NginxServer<'a, C, String, PathBuf> {
|
||||
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
|
||||
),
|
||||
);
|
||||
Self::new(domain, content, command_runner)
|
||||
}
|
||||
|
||||
pub fn new_proxy<S: SocketSpec, STATIC: AsRef<Path>>(
|
||||
domain: &'a str,
|
||||
socket_path: &S,
|
||||
static_path: STATIC,
|
||||
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.as_ref().to_str().unwrap(),
|
||||
proxy_content
|
||||
),
|
||||
);
|
||||
Self::new(domain, content, command_runner)
|
||||
}
|
||||
|
||||
pub fn new_php<SOCKET: AsRef<Path>, STATIC: AsRef<Path>>(
|
||||
domain: &'a str,
|
||||
socket_path: SOCKET,
|
||||
static_path: STATIC,
|
||||
command_runner: &'a C,
|
||||
additional_config: &'a str,
|
||||
) -> Self {
|
||||
let content = server_config(
|
||||
domain,
|
||||
&(php_server_config_snippet(socket_path, static_path) + additional_config),
|
||||
);
|
||||
Self::new(domain, content, command_runner)
|
||||
}
|
||||
|
||||
pub fn new_static<S: AsRef<Path>>(
|
||||
domain: &'a str,
|
||||
static_path: S,
|
||||
command_runner: &'a C,
|
||||
) -> Self {
|
||||
let content = server_config(
|
||||
domain,
|
||||
&format!(
|
||||
"
|
||||
root {};
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
",
|
||||
static_path.as_ref().to_str().unwrap()
|
||||
),
|
||||
);
|
||||
Self::new(domain, content, command_runner)
|
||||
}
|
||||
|
||||
pub fn new(domain: &'a str, content: String, command_runner: &'a C) -> Self {
|
||||
let file_path: PathBuf = ["/etc/nginx/sites-enabled/", domain].iter().collect();
|
||||
NginxServer {
|
||||
command_runner,
|
||||
file: FileSymbol::new(file_path, content),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, T: AsRef<str>, P: AsRef<Path>> Symbol for NginxServer<'_, C, T, P> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
self.file.execute()?;
|
||||
self
|
||||
.command_runner
|
||||
.run_successfully("systemctl", args!["reload-or-restart", "nginx"])
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
self.file.get_prerequisites()
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, T: AsRef<str>, P: AsRef<Path>> fmt::Display for NginxServer<'_, C, T, P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Nginx server config")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct NoopSymbol;
|
||||
|
||||
impl Symbol for NoopSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(true)
|
||||
}
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NoopSymbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Noop")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct NpmInstall<'a, T: AsRef<Path>, C: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct Install<'a, T: AsRef<Path>, C: CommandRunner> {
|
||||
target: T,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<Path>, C: CommandRunner> NpmInstall<'a, T, C> {
|
||||
impl<'a, T: AsRef<Path>, C: CommandRunner> Install<'a, T, C> {
|
||||
pub fn new(target: T, command_runner: &'a C) -> Self {
|
||||
NpmInstall {
|
||||
Install {
|
||||
target,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> {
|
||||
impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for Install<'_, T, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
|
|
@ -29,7 +29,7 @@ impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<Path>, C: CommandRunner> Symbol for NpmInstall<'_, T, C> {
|
||||
impl<T: AsRef<Path>, C: CommandRunner> Symbol for Install<'_, T, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.target.as_ref().exists() {
|
||||
return Ok(false);
|
||||
|
|
@ -61,17 +61,6 @@ impl<T: AsRef<Path>, C: CommandRunner> Symbol for NpmInstall<'_, T, C> {
|
|||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,35 +1,37 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
use users::get_user_by_name;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct Owner<'a, C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> {
|
||||
path: D,
|
||||
#[derive(Debug)]
|
||||
pub struct Owner<_C, C, P, U> {
|
||||
path: P,
|
||||
user_name: U,
|
||||
command_runner: &'a C,
|
||||
command_runner: C,
|
||||
phantom: PhantomData<_C>,
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> Owner<'a, C, D, U> {
|
||||
pub fn new(path: D, user_name: U, command_runner: &'a C) -> Self {
|
||||
Owner {
|
||||
impl<_C, C, P, U> Owner<_C, C, P, U> {
|
||||
pub fn new(path: P, user_name: U, command_runner: C) -> Self {
|
||||
Self {
|
||||
path,
|
||||
user_name,
|
||||
command_runner,
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> Symbol for Owner<'_, C, D, U> {
|
||||
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, U: AsRef<str>> Symbol
|
||||
for Owner<_C, C, P, U>
|
||||
{
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !Path::new(self.path.as_ref()).exists() {
|
||||
if !self.path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
let actual_uid = fs::metadata(self.path.as_ref()).unwrap().uid();
|
||||
|
|
@ -38,35 +40,9 @@ impl<C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> Symbol for Owner<'_, C, D,
|
|||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
let uname: &str = self.user_name.as_ref();
|
||||
self
|
||||
.command_runner
|
||||
.run_successfully("chown", args!["-R", uname, self.path.as_ref(),])
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new("user", self.user_name.as_ref())]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> fmt::Display for Owner<'_, C, D, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"Owner {} for {}",
|
||||
self.user_name.as_ref(),
|
||||
self.path.as_ref().display()
|
||||
self.command_runner.borrow().run_successfully(
|
||||
"chown",
|
||||
args!["-R", self.user_name.as_ref(), self.path.as_ref()],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct PostgreSQLDatabase<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> {
|
||||
name: N,
|
||||
seed_file: S,
|
||||
|
|
@ -85,17 +84,6 @@ impl<N: AsRef<str>, S: AsRef<str>, C: CommandRunner> Symbol for PostgreSQLDataba
|
|||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,57 +1,50 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::storage::Storage;
|
||||
use crate::symbols::Symbol;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::storage::Storage;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum StorageDirection {
|
||||
Load,
|
||||
Save,
|
||||
Store,
|
||||
}
|
||||
|
||||
pub struct StoredDirectory<'a, P: AsRef<Path>, S: Storage, C: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct SavedDirectory<_C, C, P, S> {
|
||||
path: P,
|
||||
storage: S,
|
||||
dir: StorageDirection,
|
||||
command_runner: &'a C,
|
||||
command_runner: C,
|
||||
phantom: PhantomData<_C>,
|
||||
}
|
||||
|
||||
impl<'a, P: AsRef<Path>, S: Storage, C: CommandRunner> StoredDirectory<'a, P, S, C> {
|
||||
pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: &'a C) -> Self {
|
||||
StoredDirectory {
|
||||
impl<_C, C, P, S> SavedDirectory<_C, C, P, S> {
|
||||
pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: C) -> Self {
|
||||
Self {
|
||||
path,
|
||||
storage,
|
||||
dir,
|
||||
command_runner,
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, S: Storage, C: CommandRunner> fmt::Display for StoredDirectory<'_, P, S, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Stored directory {} ({:?})",
|
||||
self.path.as_ref().display(),
|
||||
self.dir
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_, P, S, C> {
|
||||
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: Storage> Symbol
|
||||
for SavedDirectory<_C, C, P, S>
|
||||
{
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let metadata = fs::metadata(self.path.as_ref());
|
||||
// Check if dir exists
|
||||
if let Err(e) = metadata {
|
||||
return if e.kind() == io::ErrorKind::NotFound {
|
||||
Ok(self.dir == StorageDirection::Save)
|
||||
Ok(self.dir == StorageDirection::Store)
|
||||
} else {
|
||||
Err(Box::new(e))
|
||||
};
|
||||
|
|
@ -64,7 +57,7 @@ impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_
|
|||
}
|
||||
|
||||
let dump_date = self.storage.recent_date()?;
|
||||
let output = self.command_runner.get_output(
|
||||
let output = self.command_runner.borrow().get_output(
|
||||
"sh",
|
||||
args![
|
||||
"-c",
|
||||
|
|
@ -75,12 +68,12 @@ impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_
|
|||
],
|
||||
)?;
|
||||
let modified_date = u64::from_str(String::from_utf8(output)?.trim_end())?;
|
||||
if if self.dir == StorageDirection::Save {
|
||||
if if self.dir == StorageDirection::Store {
|
||||
modified_date > dump_date
|
||||
} else {
|
||||
dump_date > modified_date
|
||||
} {
|
||||
let output = self.command_runner.run_with_args(
|
||||
let output = self.command_runner.borrow().run_with_args(
|
||||
"diff",
|
||||
args!["-rq", self.storage.read_filename()?, self.path.as_ref()],
|
||||
)?;
|
||||
|
|
@ -98,51 +91,19 @@ impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_
|
|||
if self.dir == StorageDirection::Load {
|
||||
self
|
||||
.command_runner
|
||||
.borrow()
|
||||
.run_successfully("rm", args!["-rf", self.path.as_ref()])?;
|
||||
self.command_runner.run_successfully(
|
||||
self.command_runner.borrow().run_successfully(
|
||||
"cp",
|
||||
args!["-a", self.storage.read_filename()?, self.path.as_ref()],
|
||||
)
|
||||
} else {
|
||||
self.command_runner.run_successfully(
|
||||
self.command_runner.borrow().run_successfully(
|
||||
"cp",
|
||||
args!["-a", self.path.as_ref(), self.storage.write_filename()],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
if self.dir == StorageDirection::Save {
|
||||
return vec![];
|
||||
}
|
||||
if let Some(parent) = self.path.as_ref().parent() {
|
||||
vec![Resource::new("dir", parent.to_str().unwrap())]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
if self.dir == StorageDirection::Load {
|
||||
Some(vec![Resource::new(
|
||||
"dir",
|
||||
self.path.as_ref().to_str().unwrap(),
|
||||
)])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
pub mod reload;
|
||||
pub mod user_service;
|
||||
pub mod user_session;
|
||||
mod reload;
|
||||
mod user_service;
|
||||
mod user_session;
|
||||
|
||||
pub use reload::ReloadService;
|
||||
pub use user_service::UserService;
|
||||
pub use user_session::UserSession;
|
||||
|
|
|
|||
|
|
@ -1,49 +1,35 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct ReloadService<'a, S, C: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct ReloadService<_C, C, S> {
|
||||
service: S,
|
||||
command_runner: &'a C,
|
||||
command_runner: C,
|
||||
phantom: PhantomData<_C>,
|
||||
}
|
||||
|
||||
impl<'a, S, C: CommandRunner> ReloadService<'a, S, C> {
|
||||
pub fn new(service: S, command_runner: &'a C) -> Self {
|
||||
ReloadService {
|
||||
impl<_C, C, S> ReloadService<_C, C, S> {
|
||||
pub fn new(command_runner: C, service: S) -> Self {
|
||||
Self {
|
||||
service,
|
||||
command_runner,
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>, C: CommandRunner> Symbol for ReloadService<'_, S, C> {
|
||||
impl<S: AsRef<str>, _C: CommandRunner, C: Borrow<_C>> Symbol for ReloadService<_C, C, S> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
self.command_runner.borrow().run_successfully(
|
||||
"systemctl",
|
||||
args!["reload-or-restart", self.service.as_ref()],
|
||||
)
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>, C: CommandRunner> fmt::Display for ReloadService<'_, S, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Reload service {}", self.service.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,132 +1,34 @@
|
|||
use crate::command_runner::{CommandRunner, SetuidCommandRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Output;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::command_runner::{CommandRunner, SetuidCommandRunner};
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::file::File as FileSymbol;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UserServiceError<E: Error> {
|
||||
ActivationFailed(io::Result<Output>),
|
||||
ExecError(E),
|
||||
GenericError,
|
||||
}
|
||||
|
||||
impl From<io::Error> for UserServiceError<io::Error> {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::ExecError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Error> Error for UserServiceError<E> {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::ExecError(ref e) => e.description(),
|
||||
Self::GenericError => "Generic error",
|
||||
Self::ActivationFailed(_) => "Activation of service failed",
|
||||
}
|
||||
}
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
match self {
|
||||
Self::ExecError(ref e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Error> fmt::Display for UserServiceError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.description())?;
|
||||
if let Self::ActivationFailed(Ok(ref log)) = self {
|
||||
write!(f, ": {:?}", log)?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> {
|
||||
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> {
|
||||
socket_path: S,
|
||||
service_name: &'a str,
|
||||
user_name: U,
|
||||
command_runner: R,
|
||||
config: FileSymbol<C, PathBuf>,
|
||||
command_runner: SetuidCommandRunner<'a, U, R>,
|
||||
}
|
||||
|
||||
impl<'a, S: AsRef<Path>, U: AsRef<str> + Clone, R: CommandRunner>
|
||||
UserService<'a, S, U, String, SetuidCommandRunner<'a, U, R>>
|
||||
{
|
||||
pub fn new_nodejs(
|
||||
home: &'_ Path,
|
||||
user_name: U,
|
||||
service_name: &'a str,
|
||||
path: &'_ Path,
|
||||
command_runner: &'a R,
|
||||
socket_path: S,
|
||||
) -> Self {
|
||||
let content = format!(
|
||||
"[Service]
|
||||
Environment=NODE_ENV=production
|
||||
Environment=PORT={1}
|
||||
ExecStartPre=/bin/rm -f {1}
|
||||
ExecStart=/usr/bin/nodejs {0}
|
||||
ExecStartPost=/bin/sh -c 'sleep 1 && chmod 666 {1}'
|
||||
|
||||
# FIXME: This only works if the nodejs path is a directory
|
||||
WorkingDirectory={0}
|
||||
#RuntimeDirectory=service
|
||||
#RuntimeDirectoryMode=766
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
",
|
||||
path.to_str().unwrap(),
|
||||
socket_path.as_ref().to_str().unwrap()
|
||||
);
|
||||
Self::new(
|
||||
socket_path,
|
||||
home,
|
||||
user_name,
|
||||
service_name,
|
||||
command_runner,
|
||||
content,
|
||||
)
|
||||
}
|
||||
|
||||
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'static, S, U, R> {
|
||||
pub fn new(
|
||||
socket_path: S,
|
||||
home: &'_ Path,
|
||||
user_name: U,
|
||||
service_name: &'a str,
|
||||
command_runner: &'a R,
|
||||
content: String,
|
||||
service_name: &'static str,
|
||||
command_runner: &'static R,
|
||||
) -> Self {
|
||||
let config_path: PathBuf = [
|
||||
home,
|
||||
format!(".config/systemd/user/{}.service", service_name).as_ref(),
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
UserService {
|
||||
Self {
|
||||
socket_path,
|
||||
service_name,
|
||||
user_name: user_name.clone(),
|
||||
command_runner: SetuidCommandRunner::new(user_name, command_runner),
|
||||
config: FileSymbol::new(config_path, content),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> UserService<'_, S, U, C, R> {
|
||||
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
|
||||
fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result<String, Box<dyn Error>> {
|
||||
let mut tries = 5;
|
||||
loop {
|
||||
|
|
@ -161,12 +63,13 @@ impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> UserService
|
|||
"ActiveState=activating" => sleep(Duration::from_millis(500)),
|
||||
"ActiveState=active" => return Ok(true),
|
||||
"ActiveState=failed" => {
|
||||
return Err(Box::new(
|
||||
UserServiceError::ActivationFailed(self.command_runner.run_with_args(
|
||||
return Err(
|
||||
String::from_utf8(self.command_runner.get_output(
|
||||
"journalctl",
|
||||
args!["--user", format!("--user-unit={}", self.service_name)],
|
||||
)) as UserServiceError<io::Error>,
|
||||
))
|
||||
)?)?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
_ => return Ok(false),
|
||||
}
|
||||
|
|
@ -174,26 +77,18 @@ impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> UserService
|
|||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> Symbol
|
||||
for UserService<'_, S, U, C, R>
|
||||
{
|
||||
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> Symbol for UserService<'_, S, U, R> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !(self.config.target_reached()?) {
|
||||
return Ok(false);
|
||||
}
|
||||
self.check_if_service()
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.config.execute()?;
|
||||
self.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])?;
|
||||
self.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])?;
|
||||
|
||||
loop {
|
||||
if !(self.check_if_service()?) {
|
||||
return Err(Box::new(
|
||||
UserServiceError::GenericError as UserServiceError<io::Error>,
|
||||
));
|
||||
return Err("Generic error".into());
|
||||
}
|
||||
|
||||
if self.socket_path.as_ref().exists() {
|
||||
|
|
@ -202,32 +97,4 @@ impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> Symbol
|
|||
sleep(Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
let mut r = vec![Resource::new(
|
||||
"file",
|
||||
format!("/var/lib/systemd/linger/{}", self.user_name.as_ref()),
|
||||
)];
|
||||
r.extend(self.config.get_prerequisites().into_iter());
|
||||
r
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> fmt::Display
|
||||
for UserService<'_, S, U, C, R>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Systemd user service unit for {}", self.service_name)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,26 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SystemdUserSessionError<E: Error> {
|
||||
ExecError(E),
|
||||
GenericError,
|
||||
}
|
||||
|
||||
impl<E: Error> Error for SystemdUserSessionError<E> {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::ExecError(ref e) => e.description(),
|
||||
Self::GenericError => "Generic error",
|
||||
}
|
||||
}
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
match self {
|
||||
Self::ExecError(ref e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Error> fmt::Display for SystemdUserSessionError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemdUserSession<'a, U: AsRef<str>, C: CommandRunner> {
|
||||
pub struct UserSession<'a, U, C> {
|
||||
user_name: U,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, U: AsRef<str>, C: CommandRunner> SystemdUserSession<'a, U, C> {
|
||||
impl<'a, U, C> UserSession<'a, U, C> {
|
||||
pub fn new(user_name: U, command_runner: &'a C) -> Self {
|
||||
SystemdUserSession {
|
||||
Self {
|
||||
user_name,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: AsRef<str>, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> {
|
||||
impl<U: AsRef<str>, C: CommandRunner> Symbol for UserSession<'_, U, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let mut path = PathBuf::from("/var/lib/systemd/linger");
|
||||
path.push(self.user_name.as_ref());
|
||||
let path = Path::new("/var/lib/systemd/linger").join(self.user_name.as_ref());
|
||||
Ok(path.exists())
|
||||
// Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes`
|
||||
}
|
||||
|
|
@ -59,21 +30,4 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> {
|
|||
.command_runner
|
||||
.run_successfully("loginctl", args!["enable-linger", self.user_name.as_ref()])
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: AsRef<str>, C: CommandRunner> fmt::Display for SystemdUserSession<'_, U, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Systemd user session for {}", self.user_name.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,39 @@
|
|||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct TlsCsr<'a, C: CommandRunner> {
|
||||
domain: Cow<'a, str>,
|
||||
command_runner: &'a C,
|
||||
#[derive(Debug)]
|
||||
pub struct Csr<C, D, K, P> {
|
||||
command_runner: C,
|
||||
domain: D,
|
||||
key_path: K,
|
||||
csr_path: P,
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner> TlsCsr<'a, C> {
|
||||
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
|
||||
TlsCsr {
|
||||
domain,
|
||||
impl<C, D, K, P> Csr<C, D, K, P> {
|
||||
pub fn new(command_runner: C, domain: D, key_path: K, csr_path: P) -> Self {
|
||||
Self {
|
||||
command_runner,
|
||||
domain,
|
||||
key_path,
|
||||
csr_path,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/private/{}.key", self.domain).into()
|
||||
}
|
||||
|
||||
fn get_csr_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/local_certs/{}.csr", self.domain).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner> fmt::Display for TlsCsr<'_, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "TlsCsr {}", self.domain)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner> Symbol for TlsCsr<'_, C> {
|
||||
impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
|
||||
for Csr<C, D, K, P>
|
||||
{
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.get_csr_path().exists() {
|
||||
if !self.csr_path.borrow().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let output = self.command_runner.get_stderr(
|
||||
"openssl",
|
||||
&[
|
||||
OsStr::new("req"),
|
||||
"-in".as_ref(),
|
||||
self.get_csr_path().as_ref(),
|
||||
"-noout".as_ref(),
|
||||
"-verify".as_ref(),
|
||||
],
|
||||
args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",],
|
||||
)?;
|
||||
Ok(output == b"verify OK\n")
|
||||
}
|
||||
|
|
@ -58,35 +41,20 @@ impl<C: CommandRunner> Symbol for TlsCsr<'_, C> {
|
|||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
"openssl",
|
||||
&[
|
||||
OsStr::new("req"),
|
||||
"-new".as_ref(),
|
||||
"-sha256".as_ref(),
|
||||
"-key".as_ref(),
|
||||
self.get_key_path().as_ref(),
|
||||
"-out".as_ref(),
|
||||
self.get_csr_path().as_ref(),
|
||||
"-subj".as_ref(),
|
||||
format!("/CN={}", self.domain).as_ref(),
|
||||
args![
|
||||
"req",
|
||||
"-new",
|
||||
"-sha256",
|
||||
"-key",
|
||||
self.key_path.borrow(),
|
||||
"-out",
|
||||
self.csr_path.borrow(),
|
||||
"-subj",
|
||||
format!("/CN={}", self.domain.borrow()),
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new("file", self.get_key_path().to_str().unwrap())]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,57 +1,42 @@
|
|||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct TlsKey<'a, C: CommandRunner> {
|
||||
domain: Cow<'a, str>,
|
||||
command_runner: &'a C,
|
||||
#[derive(Debug)]
|
||||
pub struct Key<C, P> {
|
||||
file_path: P,
|
||||
command_runner: C,
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner> TlsKey<'a, C> {
|
||||
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
|
||||
TlsKey {
|
||||
domain,
|
||||
impl<C, P> Key<C, P> {
|
||||
pub fn new(command_runner: C, file_path: P) -> Self {
|
||||
Self {
|
||||
file_path,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path(&self) -> PathBuf {
|
||||
["/etc/ssl/private", &format!("{}.key", self.domain)]
|
||||
.iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_bytes(&self) -> u32 {
|
||||
4096
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner> fmt::Display for TlsKey<'_, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "TlsKey {}", self.domain)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner> Symbol for TlsKey<'_, C> {
|
||||
impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.get_path().exists() {
|
||||
if !self.file_path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let stdout = self.command_runner.get_output(
|
||||
"openssl",
|
||||
&[
|
||||
OsStr::new("rsa"),
|
||||
"-in".as_ref(),
|
||||
self.get_path().as_ref(),
|
||||
"-noout".as_ref(),
|
||||
"-check".as_ref(),
|
||||
"-text".as_ref(),
|
||||
args![
|
||||
"rsa",
|
||||
"-in",
|
||||
self.file_path.as_ref(),
|
||||
"-noout",
|
||||
"-check",
|
||||
"-text",
|
||||
],
|
||||
)?;
|
||||
// FIXME check bytes
|
||||
|
|
@ -61,25 +46,14 @@ impl<C: CommandRunner> Symbol for TlsKey<'_, C> {
|
|||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
"openssl",
|
||||
&[
|
||||
OsStr::new("genrsa"),
|
||||
"-out".as_ref(),
|
||||
self.get_path().as_ref(),
|
||||
self.get_bytes().to_string().as_ref(),
|
||||
args![
|
||||
"genrsa",
|
||||
"-out",
|
||||
self.file_path.as_ref(),
|
||||
self.get_bytes().to_string(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
mod csr;
|
||||
mod key;
|
||||
mod self_signed_cert;
|
||||
|
||||
pub use self::csr::TlsCsr;
|
||||
pub use self::key::TlsKey;
|
||||
pub use self::self_signed_cert::SelfSignedTlsCert;
|
||||
pub use self::csr::Csr;
|
||||
pub use self::key::Key;
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct SelfSignedTlsCert<'a, D: AsRef<str>, C: CommandRunner> {
|
||||
domain: D,
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, D: AsRef<str>, C: CommandRunner> SelfSignedTlsCert<'a, D, C> {
|
||||
pub fn new(domain: D, command_runner: &'a C) -> Self {
|
||||
SelfSignedTlsCert {
|
||||
domain,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/private/{}.key", self.domain.as_ref()).into()
|
||||
}
|
||||
|
||||
fn get_cert_path(&self) -> PathBuf {
|
||||
format!("/etc/ssl/local_certs/{}.chained.crt", self.domain.as_ref()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: AsRef<str>, C: CommandRunner> fmt::Display for SelfSignedTlsCert<'_, D, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "SelfSignedTlsCert {}", self.domain.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
|
||||
|
||||
impl<D: AsRef<str>, C: CommandRunner> Symbol for SelfSignedTlsCert<'_, D, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
if !self.get_cert_path().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
let output = self.command_runner.run_with_args(
|
||||
"openssl",
|
||||
args![
|
||||
"x509",
|
||||
"-in",
|
||||
self.get_cert_path(),
|
||||
"-noout",
|
||||
"-subject",
|
||||
"-checkend",
|
||||
(30 * DAYS_IN_SECONDS).to_string(),
|
||||
],
|
||||
)?;
|
||||
println!("{}", output.status.code().unwrap());
|
||||
match output.status.code() {
|
||||
Some(0) => Ok(
|
||||
output.stdout
|
||||
== format!(
|
||||
"subject=CN = {}\nCertificate will not expire\n",
|
||||
self.domain.as_ref()
|
||||
)
|
||||
.as_bytes(),
|
||||
),
|
||||
Some(_) => {
|
||||
if output.stdout
|
||||
== format!(
|
||||
"subject=CN = {}\nCertificate will expire\n",
|
||||
self.domain.as_ref()
|
||||
)
|
||||
.as_bytes()
|
||||
{
|
||||
Ok(false)
|
||||
} else {
|
||||
Err("Exit code non-zero, but wrong stdout".to_string().into())
|
||||
}
|
||||
}
|
||||
_ => Err("Apparently killed by signal".to_string().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.command_runner.run_successfully(
|
||||
"openssl",
|
||||
args![
|
||||
"req",
|
||||
"-x509",
|
||||
"-sha256",
|
||||
"-days",
|
||||
"90",
|
||||
"-key",
|
||||
self.get_key_path(),
|
||||
"-out",
|
||||
self.get_cert_path(),
|
||||
"-subj",
|
||||
format!("/CN={}", self.domain.as_ref()),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new("file", self.get_key_path().to_str().unwrap())]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
|
|
@ -1,97 +1,23 @@
|
|||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UserAdderError {
|
||||
AlreadyExists,
|
||||
UnknownError,
|
||||
ImplError(Box<dyn Error>),
|
||||
pub struct User<U, C> {
|
||||
user_name: U,
|
||||
command_runner: C,
|
||||
}
|
||||
|
||||
impl Error for UserAdderError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::AlreadyExists => "User already exists",
|
||||
Self::UnknownError => "Unknown error",
|
||||
Self::ImplError(_) => "User adding error",
|
||||
}
|
||||
}
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::ImplError(ref e) => Some(e.as_ref()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UserAdderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.source() {
|
||||
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
|
||||
None => write!(f, "{}", self.description()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UserAdder {
|
||||
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum UserError {
|
||||
GenericError,
|
||||
}
|
||||
|
||||
impl Error for UserError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::GenericError => "Could not find out if user exists",
|
||||
}
|
||||
}
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UserError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.source() {
|
||||
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
|
||||
None => write!(f, "{}", self.description()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct User<'a, C: CommandRunner, A: UserAdder> {
|
||||
user_name: Cow<'a, str>,
|
||||
command_runner: &'a C,
|
||||
user_adder: &'a A,
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner, A: 'a + UserAdder> User<'a, C, A> {
|
||||
pub fn new(user_name: Cow<'a, str>, command_runner: &'a C, user_adder: &'a A) -> Self {
|
||||
User {
|
||||
impl<U, C> User<U, C> {
|
||||
pub fn new(user_name: U, command_runner: C) -> Self {
|
||||
Self {
|
||||
user_name,
|
||||
command_runner,
|
||||
user_adder,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner, A: 'a + UserAdder> fmt::Display for User<'a, C, A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "User {}", self.user_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> {
|
||||
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let output = self
|
||||
.command_runner
|
||||
|
|
@ -99,108 +25,34 @@ impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> {
|
|||
match output.status.code() {
|
||||
Some(2) => Ok(false),
|
||||
Some(0) => Ok(true),
|
||||
_ => Err(Box::new(UserError::GenericError)),
|
||||
_ => Err("Unknown error".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
self
|
||||
.user_adder
|
||||
.add_user(self.user_name.as_ref())
|
||||
.map_err(|e| Box::new(e) as Box<dyn Error>)
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
Some(vec![Resource::new("user", self.user_name.to_owned())])
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemUserAdder<'a, C: CommandRunner> {
|
||||
command_runner: &'a C,
|
||||
}
|
||||
|
||||
impl<'a, C: CommandRunner> SystemUserAdder<'a, C> {
|
||||
pub fn new(command_runner: &'a C) -> Self {
|
||||
SystemUserAdder { command_runner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CommandRunner> UserAdder for SystemUserAdder<'_, C> {
|
||||
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError> {
|
||||
let output = self.command_runner.run_with_args(
|
||||
self.command_runner.run_successfully(
|
||||
"adduser",
|
||||
args![
|
||||
// "-m", // Necessary for Fedora, not accepted in Debian
|
||||
"--system", user_name,
|
||||
"--system",
|
||||
self.user_name.as_ref(),
|
||||
],
|
||||
);
|
||||
match output {
|
||||
Ok(output) => match output.status.code() {
|
||||
Some(0) => Ok(()),
|
||||
Some(1) => {
|
||||
println!("{:?}", output);
|
||||
Err(UserAdderError::AlreadyExists)
|
||||
}
|
||||
_ => {
|
||||
println!("{:?}", output);
|
||||
Err(UserAdderError::UnknownError)
|
||||
}
|
||||
},
|
||||
Err(e) => Err(UserAdderError::ImplError(Box::new(e))),
|
||||
}
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::command_runner::StdCommandRunner;
|
||||
use crate::symbols::user::User;
|
||||
use crate::symbols::user::UserAdder;
|
||||
use crate::symbols::user::UserAdderError;
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct DummyError;
|
||||
impl Error for DummyError {
|
||||
fn description(&self) -> &str {
|
||||
"DummyError"
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DummyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DummyError")
|
||||
}
|
||||
}
|
||||
|
||||
struct DummyUserAdder;
|
||||
|
||||
impl UserAdder for DummyUserAdder {
|
||||
fn add_user(&self, _user_name: &str) -> Result<(), UserAdderError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_target_reached_nonexisting() {
|
||||
let symbol = User {
|
||||
user_name: "nonexisting".into(),
|
||||
command_runner: &StdCommandRunner,
|
||||
user_adder: &DummyUserAdder,
|
||||
user_name: "nonexisting",
|
||||
command_runner: StdCommandRunner,
|
||||
};
|
||||
assert_eq!(symbol.target_reached().unwrap(), false);
|
||||
}
|
||||
|
|
@ -208,9 +60,8 @@ mod test {
|
|||
#[test]
|
||||
fn test_target_reached_root() {
|
||||
let symbol = User {
|
||||
user_name: "root".into(),
|
||||
command_runner: &StdCommandRunner,
|
||||
user_adder: &DummyUserAdder,
|
||||
user_name: "root",
|
||||
command_runner: StdCommandRunner,
|
||||
};
|
||||
assert_eq!(symbol.target_reached().unwrap(), true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
pub mod plugin;
|
||||
pub mod translation;
|
||||
mod plugin;
|
||||
mod translation;
|
||||
|
||||
pub use plugin::Plugin;
|
||||
pub use translation::Translation;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
use regex::Regex;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File as FsFile;
|
||||
use std::io;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
use crate::symbols::Symbol;
|
||||
|
||||
pub struct WordpressPlugin<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct Plugin<'a, P, N, R> {
|
||||
base: P,
|
||||
name: N,
|
||||
command_runner: &'a R,
|
||||
}
|
||||
|
||||
impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> WordpressPlugin<'a, P, N, R> {
|
||||
impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Plugin<'a, P, N, R> {
|
||||
pub fn new(base: P, name: N, command_runner: &'a R) -> Self {
|
||||
WordpressPlugin {
|
||||
Self {
|
||||
base,
|
||||
name,
|
||||
command_runner,
|
||||
|
|
@ -34,7 +33,7 @@ impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> WordpressPlugin<'a, P,
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for WordpressPlugin<'_, P, N, R> {
|
||||
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for Plugin<'_, P, N, R> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let base_path = self.get_path();
|
||||
if !base_path.exists() {
|
||||
|
|
@ -99,30 +98,4 @@ impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for WordpressPlugin
|
|||
args![zip, "-d", self.base.as_ref().join("wp-content/plugins")],
|
||||
)
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
match self.get_path().parent() {
|
||||
Some(p) => vec![Resource::new("dir", p.to_string_lossy())],
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> fmt::Display
|
||||
for WordpressPlugin<'_, P, N, R>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "WordpressPlugin {}", self.name.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,26 @@
|
|||
use crate::command_runner::CommandRunner;
|
||||
use crate::symbols::Symbol;
|
||||
use regex::Regex;
|
||||
use std::cmp::max;
|
||||
use std::error::Error;
|
||||
|
||||
use std::fmt;
|
||||
use std::fs::File as FsFile;
|
||||
use std::io;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::command_runner::CommandRunner;
|
||||
use crate::resources::Resource;
|
||||
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
|
||||
|
||||
pub struct WordpressTranslation<'a, C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> {
|
||||
#[derive(Debug)]
|
||||
pub struct Translation<'a, C, D, R> {
|
||||
path: D,
|
||||
version: &'a str,
|
||||
locale: C,
|
||||
command_runner: &'a R,
|
||||
}
|
||||
|
||||
impl<'a, C: AsRef<str>, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R> {
|
||||
pub fn new<D: AsRef<Path>>(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
|
||||
WordpressTranslation {
|
||||
path: [path.as_ref(), "wp-content/languages".as_ref()]
|
||||
.iter()
|
||||
.collect(),
|
||||
impl<'a, D, C: AsRef<str>, R: CommandRunner> Translation<'a, C, D, R> {
|
||||
pub fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
|
||||
Self {
|
||||
path,
|
||||
version,
|
||||
locale,
|
||||
command_runner,
|
||||
|
|
@ -33,7 +28,7 @@ impl<'a, C: AsRef<str>, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R
|
|||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> WordpressTranslation<'_, C, D, R> {
|
||||
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Translation<'_, C, D, R> {
|
||||
fn get_pairs(&self) -> Vec<(String, PathBuf)> {
|
||||
let version_x = self
|
||||
.version
|
||||
|
|
@ -64,7 +59,7 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> WordpressTranslation<'_, C
|
|||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for WordpressTranslation<'_, C, D, R> {
|
||||
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for Translation<'_, C, D, R> {
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
let mut newest = String::new();
|
||||
let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap();
|
||||
|
|
@ -115,27 +110,4 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for WordpressTransl
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
vec![Resource::new("dir", self.path.as_ref().to_str().unwrap())]
|
||||
}
|
||||
|
||||
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
|
||||
Box::new(SymbolAction::new(runner, self))
|
||||
}
|
||||
|
||||
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
|
||||
where
|
||||
Self: 'b,
|
||||
{
|
||||
Box::new(OwnedSymbolAction::new(runner, *self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> fmt::Display
|
||||
for WordpressTranslation<'_, C, D, R>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "WordpressTranslation {}", self.path.as_ref().display())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/templates/mod.rs
Normal file
3
src/templates/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod nginx;
|
||||
pub mod php;
|
||||
pub mod systemd;
|
||||
13
src/templates/nginx/mod.rs
Normal file
13
src/templates/nginx/mod.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
mod server;
|
||||
pub use server::*;
|
||||
|
||||
use std::path::Path;
|
||||
pub fn acme_challenges_snippet<P: AsRef<Path>>(path: P) -> String {
|
||||
format!(
|
||||
"location ^~ /.well-known/acme-challenge/ {{
|
||||
alias {}/;
|
||||
try_files $uri =404;
|
||||
}}",
|
||||
path.as_ref().to_str().unwrap()
|
||||
)
|
||||
}
|
||||
202
src/templates/nginx/server.rs
Normal file
202
src/templates/nginx/server.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn default_server<P: AsRef<Path>>(challenges_snippet_path: P) -> String {
|
||||
format!(
|
||||
"server {{
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
include \"{}\";
|
||||
}}",
|
||||
challenges_snippet_path.as_ref().to_str().unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn server_config<D: Display, C: AsRef<Path>, K: AsRef<Path>, T: Display, S: AsRef<Path>>(
|
||||
domain: D,
|
||||
cert_path: C,
|
||||
key_path: K,
|
||||
content: T,
|
||||
challenges_snippet_path: S,
|
||||
) -> String {
|
||||
format!(
|
||||
"server {{
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name {};
|
||||
include \"{}\";
|
||||
|
||||
ssl_certificate {};
|
||||
ssl_certificate_key {};
|
||||
add_header Strict-Transport-Security \"max-age=31536000\";
|
||||
|
||||
{}
|
||||
}}
|
||||
|
||||
# Redirect all HTTP links to the matching HTTPS page
|
||||
server {{
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {0};
|
||||
include \"{1}\";
|
||||
|
||||
location / {{
|
||||
return 301 https://$host$request_uri;
|
||||
}}
|
||||
}}
|
||||
",
|
||||
domain,
|
||||
challenges_snippet_path.as_ref().to_str().unwrap(),
|
||||
cert_path.as_ref().to_str().unwrap(),
|
||||
key_path.as_ref().to_str().unwrap(),
|
||||
content
|
||||
)
|
||||
}
|
||||
|
||||
pub fn php_snippet<SOCKET: AsRef<Path>, STATIC: AsRef<Path>>(
|
||||
index: &'static str,
|
||||
socket_path: SOCKET,
|
||||
static_path: STATIC,
|
||||
) -> String {
|
||||
format!(
|
||||
"root {};
|
||||
index {};
|
||||
location ~ [^/]\\.php(/|$) {{
|
||||
fastcgi_pass unix:{};
|
||||
include \"snippets/fastcgi-php.conf\";
|
||||
}}",
|
||||
static_path.as_ref().to_str().unwrap(),
|
||||
index,
|
||||
socket_path.as_ref().to_str().unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn redir_snippet(target: &str) -> String {
|
||||
format!(
|
||||
"location / {{
|
||||
return 301 $scheme://{}$request_uri;
|
||||
}}",
|
||||
target
|
||||
)
|
||||
}
|
||||
|
||||
pub trait SocketSpec {
|
||||
fn to_nginx(&self) -> String;
|
||||
}
|
||||
|
||||
impl<T: AsRef<Path>> SocketSpec for T {
|
||||
fn to_nginx(&self) -> String {
|
||||
format!("unix:{}:", self.as_ref().to_str().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalTcpSocket(usize);
|
||||
|
||||
impl LocalTcpSocket {
|
||||
pub const fn new(x: usize) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl SocketSpec for LocalTcpSocket {
|
||||
fn to_nginx(&self) -> String {
|
||||
format!("localhost:{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proxy_snippet<S: SocketSpec, STATIC: AsRef<Path>>(
|
||||
socket_path: &S,
|
||||
static_path: STATIC,
|
||||
) -> String {
|
||||
format!(
|
||||
"root {};
|
||||
location / {{
|
||||
try_files $uri @proxy;
|
||||
}}
|
||||
|
||||
location @proxy {{
|
||||
include fastcgi_params;
|
||||
proxy_pass http://{};
|
||||
proxy_redirect off;
|
||||
}}",
|
||||
static_path.as_ref().to_str().unwrap(),
|
||||
socket_path.to_nginx()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn static_snippet<S: AsRef<Path>>(static_path: S) -> String {
|
||||
format!(
|
||||
"root {};
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
",
|
||||
static_path.as_ref().to_str().unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dokuwiki_snippet() -> String {
|
||||
"
|
||||
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;
|
||||
}".into()
|
||||
}
|
||||
|
||||
pub fn nextcloud_snippet() -> String {
|
||||
"
|
||||
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;
|
||||
}
|
||||
"
|
||||
.into()
|
||||
}
|
||||
24
src/templates/php.rs
Normal file
24
src/templates/php.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::path::Path;
|
||||
|
||||
pub fn fpm_pool_config<U: AsRef<str>, S: AsRef<Path>>(
|
||||
user_name: U,
|
||||
socket_path: S,
|
||||
max_children: usize,
|
||||
) -> String {
|
||||
format!(
|
||||
"[{0}]
|
||||
|
||||
user = {0}
|
||||
group = www-data
|
||||
listen = {1}
|
||||
listen.owner = www-data
|
||||
pm = ondemand
|
||||
pm.max_children = {2}
|
||||
catch_workers_output = yes
|
||||
env[PATH] = /usr/local/bin:/usr/bin:/bin
|
||||
",
|
||||
user_name.as_ref(),
|
||||
socket_path.as_ref().to_str().unwrap(),
|
||||
max_children,
|
||||
)
|
||||
}
|
||||
45
src/templates/systemd.rs
Normal file
45
src/templates/systemd.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use std::path::Path;
|
||||
|
||||
pub fn socket_service(
|
||||
socket_path: impl AsRef<Path>,
|
||||
exec: &str,
|
||||
dir: impl AsRef<Path>,
|
||||
additional: &str,
|
||||
) -> String {
|
||||
format!(
|
||||
"[Service]
|
||||
ExecStartPre=/bin/rm -f {}
|
||||
ExecStart={}
|
||||
WorkingDirectory={}
|
||||
Restart=always
|
||||
{}
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
",
|
||||
socket_path.as_ref().to_str().unwrap(),
|
||||
exec,
|
||||
dir.as_ref().to_str().unwrap(),
|
||||
additional
|
||||
)
|
||||
}
|
||||
|
||||
pub fn nodejs_service<N: AsRef<Path>, S: AsRef<Path>>(
|
||||
nodejs_path: N,
|
||||
socket_path: S,
|
||||
dir: impl AsRef<Path>,
|
||||
) -> String {
|
||||
socket_service(
|
||||
&socket_path,
|
||||
&format!("/usr/bin/nodejs {}", nodejs_path.as_ref().to_str().unwrap()),
|
||||
dir,
|
||||
&format!(
|
||||
"Environment=NODE_ENV=production
|
||||
Environment=PORT={0}
|
||||
ExecStartPost=/bin/sh -c 'sleep 1 && chmod 666 {0}'
|
||||
#RuntimeDirectory=service
|
||||
#RuntimeDirectoryMode=766",
|
||||
socket_path.as_ref().to_str().unwrap()
|
||||
),
|
||||
)
|
||||
}
|
||||
25
src/to_artifact.rs
Normal file
25
src/to_artifact.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use crate::resources::Resource;
|
||||
|
||||
pub trait ToArtifact {
|
||||
type Artifact;
|
||||
}
|
||||
|
||||
macro_rules! to_artifact {
|
||||
( $($name:ident)* ) => (
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($name: Resource,)*> ToArtifact for ($($name,)*)
|
||||
{
|
||||
type Artifact = ($($name::Artifact,)*);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for_each_tuple!(to_artifact);
|
||||
|
||||
impl<T: Resource> ToArtifact for Option<T> {
|
||||
type Artifact = Option<T::Artifact>;
|
||||
}
|
||||
|
||||
impl<T: Resource> ToArtifact for T {
|
||||
type Artifact = T::Artifact;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue