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.

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