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.

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