88use Utopia \Database \Database ;
99use Utopia \Database \Document ;
1010use Utopia \Database \DateTime ;
11+ use Utopia \Database \ID ;
1112use Utopia \Database \Query ;
1213use Utopia \Domains \Domain ;
1314
@@ -100,8 +101,11 @@ public function run(): void
100101 throw new Exception ('Renew isn \'t required. ' );
101102 }
102103
104+ // Prepare folder name for certbot. Using this helps prevent miss-match in LetsEncrypt configuration when renewing certificate
105+ $ folder = ID ::unique ();
106+
103107 // Generate certificate files using Let's Encrypt
104- $ letsEncryptData = $ this ->issueCertificate ($ domain ->get (), $ email );
108+ $ letsEncryptData = $ this ->issueCertificate ($ folder , $ domain ->get (), $ email );
105109
106110 // Command succeeded, store all data into document
107111 // We store stderr too, because it may include warnings
@@ -111,7 +115,7 @@ public function run(): void
111115 ]));
112116
113117 // Give certificates to Traefik
114- $ this ->applyCertificateFiles ($ domain ->get (), $ letsEncryptData );
118+ $ this ->applyCertificateFiles ($ folder , $ domain ->get (), $ letsEncryptData );
115119
116120 // Update certificate info stored in database
117121 $ certificate ->setAttribute ('renewDate ' , $ this ->getRenewDate ($ domain ->get ()));
@@ -125,6 +129,9 @@ public function run(): void
125129 $ attempts = $ certificate ->getAttribute ('attempts ' , 0 ) + 1 ;
126130 $ certificate ->setAttribute ('attempts ' , $ attempts );
127131
132+ // Store cuttent time as renew date to ensure another attempt in next maintenance cycle
133+ $ certificate ->setAttribute ('renewDate ' , DateTime::now ());
134+
128135 // Send email to security email
129136 $ this ->notifyError ($ domain ->get (), $ e ->getMessage (), $ attempts );
130137 } finally {
@@ -259,18 +266,20 @@ private function isRenewRequired(string $domain): bool
259266 /**
260267 * LetsEncrypt communication to issue certificate (using certbot CLI)
261268 *
269+ * @param string $folder Folder into which certificates should be generated
262270 * @param string $domain Domain to generate certificate for
263271 *
264272 * @return array Named array with keys 'stdout' and 'stderr', both string
265273 */
266- private function issueCertificate (string $ domain , string $ email ): array
274+ private function issueCertificate (string $ folder , string $ domain , string $ email ): array
267275 {
268276 $ stdout = '' ;
269277 $ stderr = '' ;
270278
271279 $ staging = (App::isProduction ()) ? '' : ' --dry-run ' ;
272280 $ exit = Console::execute ("certbot certonly --webroot --noninteractive --agree-tos {$ staging }"
273281 . " --email " . $ email
282+ . " --cert-name " . $ folder
274283 . " -w " . APP_STORAGE_CERTIFICATES
275284 . " -d {$ domain }" , '' , $ stdout , $ stderr );
276285
@@ -290,9 +299,9 @@ private function issueCertificate(string $domain, string $email): array
290299 *
291300 * @param string $domain Domain which certificate was generated for
292301 *
293- * @return int
302+ * @return string
294303 */
295- private function getRenewDate (string $ domain ): int
304+ private function getRenewDate (string $ domain ): string
296305 {
297306 $ certPath = APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/cert.pem ' ;
298307 $ certData = openssl_x509_parse (file_get_contents ($ certPath ));
@@ -305,11 +314,12 @@ private function getRenewDate(string $domain): int
305314 * Method to take files from Let's Encrypt, and put it into Traefik.
306315 *
307316 * @param string $domain Domain which certificate was generated for
317+ * @param string $folder Folder in which certificates were generated
308318 * @param array $letsEncryptData Let's Encrypt logs to use for additional info when throwing error
309319 *
310320 * @return void
311321 */
312- private function applyCertificateFiles (string $ domain , array $ letsEncryptData ): void
322+ private function applyCertificateFiles (string $ folder , string $ domain , array $ letsEncryptData ): void
313323 {
314324 // Prepare folder in storage for domain
315325 $ path = APP_STORAGE_CERTIFICATES . '/ ' . $ domain ;
@@ -319,20 +329,20 @@ private function applyCertificateFiles(string $domain, array $letsEncryptData):
319329 }
320330 }
321331
322- // Move generated files from certbot into our storage
323- if (!@\rename ('/etc/letsencrypt/live/ ' . $ domain . '/cert.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/cert.pem ' )) {
332+ // Move generated files
333+ if (!@\rename ('/etc/letsencrypt/live/ ' . $ folder . '/cert.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/cert.pem ' )) {
324334 throw new Exception ('Failed to rename certificate cert.pem. Let \'s Encrypt log: ' . $ letsEncryptData ['stderr ' ] . ' ; ' . $ letsEncryptData ['stdout ' ]);
325335 }
326336
327- if (!@\rename ('/etc/letsencrypt/live/ ' . $ domain . '/chain.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/chain.pem ' )) {
337+ if (!@\rename ('/etc/letsencrypt/live/ ' . $ folder . '/chain.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/chain.pem ' )) {
328338 throw new Exception ('Failed to rename certificate chain.pem. Let \'s Encrypt log: ' . $ letsEncryptData ['stderr ' ] . ' ; ' . $ letsEncryptData ['stdout ' ]);
329339 }
330340
331- if (!@\rename ('/etc/letsencrypt/live/ ' . $ domain . '/fullchain.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/fullchain.pem ' )) {
341+ if (!@\rename ('/etc/letsencrypt/live/ ' . $ folder . '/fullchain.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/fullchain.pem ' )) {
332342 throw new Exception ('Failed to rename certificate fullchain.pem. Let \'s Encrypt log: ' . $ letsEncryptData ['stderr ' ] . ' ; ' . $ letsEncryptData ['stdout ' ]);
333343 }
334344
335- if (!@\rename ('/etc/letsencrypt/live/ ' . $ domain . '/privkey.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/privkey.pem ' )) {
345+ if (!@\rename ('/etc/letsencrypt/live/ ' . $ folder . '/privkey.pem ' , APP_STORAGE_CERTIFICATES . '/ ' . $ domain . '/privkey.pem ' )) {
336346 throw new Exception ('Failed to rename certificate privkey.pem. Let \'s Encrypt log: ' . $ letsEncryptData ['stderr ' ] . ' ; ' . $ letsEncryptData ['stdout ' ]);
337347 }
338348
0 commit comments