A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

454 lines
13 KiB

  1. use crate::artifacts::{
  2. DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact,
  3. UserName as UserNameArtifact,
  4. };
  5. use crate::resources::{
  6. AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeRootCert, AcmeUser, Cert,
  7. CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle,
  8. LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, Resource,
  9. ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
  10. SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation,
  11. };
  12. use crate::to_artifact::ToArtifact;
  13. use std::fmt::Display;
  14. use std::marker::PhantomData;
  15. use std::path::{Path, PathBuf};
  16. pub trait Policy {
  17. fn acme_user() -> &'static str {
  18. "acme"
  19. }
  20. fn user_home(user_name: &str) -> PathBuf {
  21. format!("/home/{}", user_name).into()
  22. }
  23. fn user_name_for_domain(domain_name: &'_ str) -> String {
  24. domain_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
  25. }
  26. fn php_version() -> &'static str {
  27. "7.0"
  28. }
  29. fn path_for_data(name: impl Display) -> PathBuf {
  30. ("/root/data".as_ref() as &Path).join(format!("_{}", name))
  31. }
  32. }
  33. #[derive(Debug)]
  34. pub struct DefaultPolicy;
  35. impl Policy for DefaultPolicy {}
  36. pub trait ResourceLocator<R> {
  37. type Prerequisites: ToArtifact;
  38. fn locate(resource: &R) -> (R::Artifact, Self::Prerequisites)
  39. where
  40. R: Resource;
  41. }
  42. #[derive(Debug)]
  43. pub struct DefaultLocator<P = DefaultPolicy> {
  44. phantom: PhantomData<P>,
  45. }
  46. impl<P, D: AsRef<str>> ResourceLocator<Key<D>> for DefaultLocator<P> {
  47. type Prerequisites = Dir<PathBuf>;
  48. fn locate(resource: &Key<D>) -> (<Key<D> as Resource>::Artifact, Self::Prerequisites) {
  49. (
  50. PathArtifact::from(format!("/etc/ssl/private/{}.key", resource.0.as_ref())),
  51. Dir("/etc/ssl/private".into()),
  52. )
  53. }
  54. }
  55. impl<P, D: AsRef<str>> ResourceLocator<Csr<D>> for DefaultLocator<P> {
  56. type Prerequisites = Dir<PathBuf>;
  57. fn locate(resource: &Csr<D>) -> (<Csr<D> as Resource>::Artifact, Self::Prerequisites) {
  58. (
  59. PathArtifact::from(format!("/etc/ssl/local_certs/{}.csr", resource.0.as_ref())),
  60. Dir("/etc/ssl/local_certs".into()),
  61. )
  62. }
  63. }
  64. impl<P, D: AsRef<str>> ResourceLocator<Cert<D>> for DefaultLocator<P> {
  65. type Prerequisites = Dir<PathBuf>;
  66. fn locate(resource: &Cert<D>) -> (<Cert<D> as Resource>::Artifact, Self::Prerequisites) {
  67. (
  68. PathArtifact::from(format!("/etc/ssl/local_certs/{}.crt", resource.0.as_ref())),
  69. Dir("/etc/ssl/local_certs".into()),
  70. )
  71. }
  72. }
  73. impl<P, D: AsRef<str>> ResourceLocator<CertChain<D>> for DefaultLocator<P> {
  74. type Prerequisites = Dir<PathBuf>;
  75. fn locate(
  76. resource: &CertChain<D>,
  77. ) -> (<CertChain<D> as Resource>::Artifact, Self::Prerequisites) {
  78. (
  79. PathArtifact::from(format!(
  80. "/etc/ssl/local_certs/{}.chained.crt",
  81. resource.0.as_ref()
  82. )),
  83. Dir("/etc/ssl/local_certs".into()),
  84. )
  85. }
  86. }
  87. impl<P, D: AsRef<str>> ResourceLocator<KeyAndCertBundle<D>> for DefaultLocator<P> {
  88. type Prerequisites = Dir<PathBuf>;
  89. fn locate(
  90. resource: &KeyAndCertBundle<D>,
  91. ) -> (
  92. <KeyAndCertBundle<D> as Resource>::Artifact,
  93. Self::Prerequisites,
  94. ) {
  95. (
  96. PathArtifact::from(format!(
  97. "/etc/ssl/private/{}.with_key.crt",
  98. resource.0.as_ref()
  99. )),
  100. Dir("/etc/ssl/private".into()),
  101. )
  102. }
  103. }
  104. impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<File<P>> for DefaultLocator<POLICY> {
  105. type Prerequisites = Dir<PathBuf>;
  106. fn locate(resource: &File<P>) -> (<File<P> as Resource>::Artifact, Self::Prerequisites) {
  107. ((), Dir(resource.0.as_ref().parent().unwrap().into()))
  108. }
  109. }
  110. impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<GitCheckout<'a, P>> for DefaultLocator<POLICY> {
  111. type Prerequisites = Dir<PathBuf>;
  112. fn locate(
  113. resource: &GitCheckout<'a, P>,
  114. ) -> (
  115. <GitCheckout<'a, P> as Resource>::Artifact,
  116. Self::Prerequisites,
  117. ) {
  118. ((), Dir(resource.0.as_ref().parent().unwrap().into()))
  119. }
  120. }
  121. impl<POLICY, P: Clone + AsRef<Path>> ResourceLocator<Dir<P>> for DefaultLocator<POLICY> {
  122. type Prerequisites = Option<Dir<PathBuf>>;
  123. fn locate(resource: &Dir<P>) -> (<Dir<P> as Resource>::Artifact, Self::Prerequisites) {
  124. ((), resource.0.as_ref().parent().map(|p| Dir(p.into())))
  125. }
  126. }
  127. impl<POLICY, P> ResourceLocator<NpmInstall<P>> for DefaultLocator<POLICY> {
  128. type Prerequisites = ();
  129. fn locate(
  130. _resource: &NpmInstall<P>,
  131. ) -> (<NpmInstall<P> as Resource>::Artifact, Self::Prerequisites) {
  132. ((), ())
  133. }
  134. }
  135. impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<StoredDirectory<P>>
  136. for DefaultLocator<POLICY>
  137. {
  138. type Prerequisites = ();
  139. fn locate(
  140. resource: &StoredDirectory<P>,
  141. ) -> (
  142. <StoredDirectory<P> as Resource>::Artifact,
  143. Self::Prerequisites,
  144. ) {
  145. (PathArtifact::from(POLICY::path_for_data(resource.0)), ())
  146. }
  147. }
  148. impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<LoadedDirectory<P>>
  149. for DefaultLocator<POLICY>
  150. {
  151. type Prerequisites = Dir<PathBuf>;
  152. fn locate(
  153. resource: &LoadedDirectory<P>,
  154. ) -> (
  155. <LoadedDirectory<P> as Resource>::Artifact,
  156. Self::Prerequisites,
  157. ) {
  158. (
  159. PathArtifact::from(POLICY::path_for_data(resource.0)),
  160. Dir(resource.1.as_ref().parent().unwrap().into()),
  161. )
  162. }
  163. }
  164. impl<P: Policy> ResourceLocator<AcmeAccountKey> for DefaultLocator<P> {
  165. type Prerequisites = Dir<PathBuf>;
  166. fn locate(
  167. _resource: &AcmeAccountKey,
  168. ) -> (<AcmeAccountKey as Resource>::Artifact, Self::Prerequisites) {
  169. let acme_user = P::acme_user();
  170. let home = P::user_home(acme_user);
  171. (PathArtifact::from(home.join("account.key")), Dir(home))
  172. }
  173. }
  174. impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
  175. type Prerequisites = ();
  176. fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) {
  177. let acme_user = P::acme_user();
  178. (UserNameArtifact(acme_user.into()), ())
  179. }
  180. }
  181. impl<P: Policy> ResourceLocator<AcmeChallengesDir> for DefaultLocator<P> {
  182. type Prerequisites = Dir<PathBuf>;
  183. fn locate(
  184. _resource: &AcmeChallengesDir,
  185. ) -> (
  186. <AcmeChallengesDir as Resource>::Artifact,
  187. Self::Prerequisites,
  188. ) {
  189. let acme_user = P::acme_user();
  190. let home = P::user_home(acme_user);
  191. (PathArtifact::from(home.join("challenges")), Dir(home))
  192. }
  193. }
  194. impl<P: Policy> ResourceLocator<AcmeChallengesNginxSnippet> for DefaultLocator<P> {
  195. type Prerequisites = ();
  196. fn locate(
  197. _resource: &AcmeChallengesNginxSnippet,
  198. ) -> (
  199. <AcmeChallengesNginxSnippet as Resource>::Artifact,
  200. Self::Prerequisites,
  201. ) {
  202. (
  203. PathArtifact::from("/etc/nginx/snippets/acme-challenge.conf"),
  204. (),
  205. )
  206. }
  207. }
  208. impl<P: Policy> ResourceLocator<AcmeRootCert> for DefaultLocator<P> {
  209. type Prerequisites = Dir<PathBuf>;
  210. fn locate(
  211. _resource: &AcmeRootCert,
  212. ) -> (<AcmeRootCert as Resource>::Artifact, Self::Prerequisites) {
  213. let acme_user = P::acme_user();
  214. let home = P::user_home(acme_user);
  215. (
  216. PathArtifact::from(home.join("lets_encrypt_x3_cross_signed.pem")),
  217. Dir(home),
  218. )
  219. }
  220. }
  221. impl<P: Policy, D: AsRef<str>> ResourceLocator<UserForDomain<D>> for DefaultLocator<P> {
  222. type Prerequisites = ();
  223. fn locate(
  224. resource: &UserForDomain<D>,
  225. ) -> (
  226. <UserForDomain<D> as Resource>::Artifact,
  227. Self::Prerequisites,
  228. ) {
  229. let user_name = P::user_name_for_domain(resource.0.as_ref());
  230. let home = P::user_home(&user_name);
  231. ((UserNameArtifact(user_name), PathArtifact::from(home)), ())
  232. }
  233. }
  234. impl<P> ResourceLocator<User> for DefaultLocator<P> {
  235. type Prerequisites = ();
  236. fn locate(_resource: &User) -> (<User as Resource>::Artifact, Self::Prerequisites) {
  237. ((), ())
  238. }
  239. }
  240. impl<P, POLICY> ResourceLocator<Owner<P>> for DefaultLocator<POLICY> {
  241. type Prerequisites = User;
  242. fn locate(resource: &Owner<P>) -> (<Owner<P> as Resource>::Artifact, Self::Prerequisites) {
  243. ((), User(resource.0.clone()))
  244. }
  245. }
  246. impl<P> ResourceLocator<DefaultServer> for DefaultLocator<P> {
  247. type Prerequisites = ();
  248. fn locate(
  249. _resource: &DefaultServer,
  250. ) -> (<DefaultServer as Resource>::Artifact, Self::Prerequisites) {
  251. (PathArtifact::from("/etc/nginx/sites-enabled/default"), ())
  252. }
  253. }
  254. impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeCustom<D>> for DefaultLocator<POLICY> {
  255. type Prerequisites = ();
  256. fn locate(
  257. resource: &ServeCustom<D>,
  258. ) -> (<ServeCustom<D> as Resource>::Artifact, Self::Prerequisites) {
  259. (
  260. PathArtifact::from(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
  261. (),
  262. )
  263. }
  264. }
  265. impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServePhp<D, P>> for DefaultLocator<POLICY> {
  266. type Prerequisites = ();
  267. fn locate(
  268. resource: &ServePhp<D, P>,
  269. ) -> (<ServePhp<D, P> as Resource>::Artifact, Self::Prerequisites) {
  270. (
  271. PathArtifact::from(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
  272. (),
  273. )
  274. }
  275. }
  276. impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeService<D, P>> for DefaultLocator<POLICY> {
  277. type Prerequisites = ();
  278. fn locate(
  279. resource: &ServeService<D, P>,
  280. ) -> (
  281. <ServeService<D, P> as Resource>::Artifact,
  282. Self::Prerequisites,
  283. ) {
  284. (
  285. PathArtifact::from(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
  286. (),
  287. )
  288. }
  289. }
  290. impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeRedir<D>> for DefaultLocator<POLICY> {
  291. type Prerequisites = ();
  292. fn locate(
  293. resource: &ServeRedir<D>,
  294. ) -> (<ServeRedir<D> as Resource>::Artifact, Self::Prerequisites) {
  295. (
  296. PathArtifact::from(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
  297. (),
  298. )
  299. }
  300. }
  301. impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeStatic<D, P>> for DefaultLocator<POLICY> {
  302. type Prerequisites = ();
  303. fn locate(
  304. resource: &ServeStatic<D, P>,
  305. ) -> (
  306. <ServeStatic<D, P> as Resource>::Artifact,
  307. Self::Prerequisites,
  308. ) {
  309. (
  310. PathArtifact::from(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)),
  311. (),
  312. )
  313. }
  314. }
  315. impl<D: Clone + AsRef<str>, P: Policy> ResourceLocator<PhpFpmPool<D>> for DefaultLocator<P> {
  316. type Prerequisites = ();
  317. fn locate(
  318. resource: &PhpFpmPool<D>,
  319. ) -> (<PhpFpmPool<D> as Resource>::Artifact, Self::Prerequisites) {
  320. let ((user, _), ()) = Self::locate(&UserForDomain(&resource.0));
  321. let php_version = P::php_version();
  322. (
  323. (
  324. PathArtifact::from(format!("/run/php/{}.sock", user.0)),
  325. PathArtifact::from(format!(
  326. "/etc/php/{}/fpm/pool.d/{}.conf",
  327. php_version, user.0
  328. )),
  329. user,
  330. ServiceNameArtifact(format!("php{}-fpm", php_version)),
  331. ),
  332. (),
  333. )
  334. }
  335. }
  336. impl<D: Clone + AsRef<str>, P, POLICY: Policy> ResourceLocator<SystemdSocketService<D, P>>
  337. for DefaultLocator<POLICY>
  338. {
  339. type Prerequisites = (Dir<PathBuf>, Owner<PathBuf>);
  340. fn locate(
  341. resource: &SystemdSocketService<D, P>,
  342. ) -> (
  343. <SystemdSocketService<D, P> as Resource>::Artifact,
  344. Self::Prerequisites,
  345. ) {
  346. let ((user_name, home_path), ()) = Self::locate(&UserForDomain(&resource.0));
  347. let config = home_path.as_ref().join(".config");
  348. let service_dir_path = config.join("systemd/user");
  349. (
  350. (
  351. PathArtifact::from(format!("/var/tmp/{}-{}.socket", user_name.0, resource.1)),
  352. PathArtifact::from(service_dir_path.join(format!("{}.service", resource.1))),
  353. user_name.clone(),
  354. ),
  355. (Dir(service_dir_path), Owner(user_name.0, config)),
  356. )
  357. }
  358. }
  359. impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbDatabase<D>> for DefaultLocator<P> {
  360. type Prerequisites = ();
  361. fn locate(
  362. resource: &MariaDbDatabase<D>,
  363. ) -> (
  364. <MariaDbDatabase<D> as Resource>::Artifact,
  365. Self::Prerequisites,
  366. ) {
  367. let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
  368. (
  369. (
  370. DatabaseNameArtifact(user_name.0.clone()),
  371. user_name.clone(),
  372. PathArtifact::from(P::path_for_data(format!("{}.sql", user_name.0))),
  373. ),
  374. (),
  375. )
  376. }
  377. }
  378. impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbUser<D>> for DefaultLocator<P> {
  379. type Prerequisites = ();
  380. fn locate(
  381. resource: &MariaDbUser<D>,
  382. ) -> (<MariaDbUser<D> as Resource>::Artifact, Self::Prerequisites) {
  383. let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
  384. ((user_name), ())
  385. }
  386. }
  387. impl<P, POLICY> ResourceLocator<WordpressPlugin<P>> for DefaultLocator<POLICY> {
  388. type Prerequisites = ();
  389. fn locate(
  390. _resource: &WordpressPlugin<P>,
  391. ) -> (
  392. <WordpressPlugin<P> as Resource>::Artifact,
  393. Self::Prerequisites,
  394. ) {
  395. ((), ())
  396. }
  397. }
  398. impl<P, POLICY> ResourceLocator<WordpressTranslation<P>> for DefaultLocator<POLICY> {
  399. type Prerequisites = ();
  400. fn locate(
  401. _resource: &WordpressTranslation<P>,
  402. ) -> (
  403. <WordpressTranslation<P> as Resource>::Artifact,
  404. Self::Prerequisites,
  405. ) {
  406. ((), ())
  407. }
  408. }
  409. impl<D, P> ResourceLocator<Cron<D>> for DefaultLocator<P> {
  410. type Prerequisites = ();
  411. fn locate(_resource: &Cron<D>) -> (<Cron<D> as Resource>::Artifact, Self::Prerequisites) {
  412. ((), ())
  413. }
  414. }