1use serde::{Deserialize, Serialize};
4use sova_core::{Error, Result};
5use std::path::PathBuf;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct CertMeta {
10 pub domains: Vec<String>,
11 pub not_after_unix: u64,
12 pub staging: bool,
13}
14
15#[derive(Clone, Debug)]
16pub struct AcmeStorage {
17 root: PathBuf,
18}
19
20impl AcmeStorage {
21 pub fn new(root: impl Into<PathBuf>) -> Self {
22 Self { root: root.into() }
23 }
24
25 pub fn cert_path(&self) -> PathBuf {
26 self.root.join("cert.pem")
27 }
28
29 pub fn key_path(&self) -> PathBuf {
30 self.root.join("key.pem")
31 }
32
33 pub fn account_path(&self) -> PathBuf {
34 self.root.join("account.json")
35 }
36
37 pub fn meta_path(&self) -> PathBuf {
38 self.root.join("meta.json")
39 }
40
41 pub fn ensure_dir(&self) -> Result<()> {
42 std::fs::create_dir_all(&self.root)
43 .map_err(|e| Error::Internal(format!("acme dir {}: {e}", self.root.display())))
44 }
45
46 pub fn has_cert(&self) -> bool {
47 self.cert_path().is_file() && self.key_path().is_file()
48 }
49
50 pub fn load_meta(&self) -> Option<CertMeta> {
51 let raw = std::fs::read_to_string(self.meta_path()).ok()?;
52 serde_json::from_str(&raw).ok()
53 }
54
55 pub fn save_meta(&self, meta: &CertMeta) -> Result<()> {
56 self.ensure_dir()?;
57 let raw = serde_json::to_string_pretty(meta)
58 .map_err(|e| Error::Internal(format!("acme meta serialize: {e}")))?;
59 std::fs::write(self.meta_path(), raw)
60 .map_err(|e| Error::Internal(format!("acme meta write: {e}")))
61 }
62
63 pub fn load_account_json(&self) -> Option<String> {
64 std::fs::read_to_string(self.account_path()).ok()
65 }
66
67 pub fn save_account_json(&self, json: &str) -> Result<()> {
68 self.ensure_dir()?;
69 std::fs::write(self.account_path(), json)
70 .map_err(|e| Error::Internal(format!("acme account write: {e}")))
71 }
72
73 pub fn write_pem(&self, cert_pem: &str, key_pem: &str) -> Result<()> {
74 self.ensure_dir()?;
75 std::fs::write(self.cert_path(), cert_pem)
76 .map_err(|e| Error::Internal(format!("acme cert write: {e}")))?;
77 std::fs::write(self.key_path(), key_pem)
78 .map_err(|e| Error::Internal(format!("acme key write: {e}")))
79 }
80}
81
82pub fn not_after_from_pem(cert_pem: &str) -> Option<u64> {
83 let (_, pem) = x509_parser::pem::parse_x509_pem(cert_pem.as_bytes()).ok()?;
84 let (_, cert) = x509_parser::parse_x509_certificate(pem.contents.as_ref()).ok()?;
85 let ts = cert.validity().not_after.timestamp();
86 if ts < 0 {
87 None
88 } else {
89 Some(ts as u64)
90 }
91}
92
93pub fn now_unix() -> u64 {
94 SystemTime::now()
95 .duration_since(UNIX_EPOCH)
96 .map(|d| d.as_secs())
97 .unwrap_or(0)
98}
99
100pub fn needs_renew(meta: &CertMeta, renew_days: u64) -> bool {
101 let now = now_unix();
102 let threshold = renew_days.saturating_mul(24 * 3600);
103 meta.not_after_unix.saturating_sub(now) <= threshold
104}