1import platform
2import shutil
3import subprocess
4import sys
5import time
6import urllib.request
7
8import click
9
10
11class MkcertManager:
12 def __init__(self):
13 self.mkcert_bin = None
14
15 def setup_mkcert(self, install_path):
16 """Set up mkcert by checking if it's installed or downloading the binary and installing the local CA."""
17 if mkcert_path := shutil.which("mkcert"):
18 # mkcert is already installed somewhere
19 self.mkcert_bin = mkcert_path
20 else:
21 self.mkcert_bin = install_path / "mkcert"
22 install_path.mkdir(parents=True, exist_ok=True)
23 if not self.mkcert_bin.exists():
24 system = platform.system()
25 arch = platform.machine()
26
27 # Map platform.machine() to mkcert's expected architecture strings
28 arch_map = {
29 "x86_64": "amd64",
30 "amd64": "amd64",
31 "AMD64": "amd64",
32 "arm64": "arm64",
33 "aarch64": "arm64",
34 }
35 arch = arch_map.get(
36 arch.lower(), "amd64"
37 ) # Default to amd64 if unknown
38
39 if system == "Darwin":
40 os_name = "darwin"
41 elif system == "Linux":
42 os_name = "linux"
43 elif system == "Windows":
44 os_name = "windows"
45 else:
46 click.secho("Unsupported OS", fg="red")
47 sys.exit(1)
48
49 mkcert_url = f"https://dl.filippo.io/mkcert/latest?for={os_name}/{arch}"
50 click.secho(f"Downloading mkcert from {mkcert_url}...", bold=True)
51 urllib.request.urlretrieve(mkcert_url, self.mkcert_bin)
52 self.mkcert_bin.chmod(0o755)
53 self.mkcert_bin = str(self.mkcert_bin) # Convert Path object to string
54
55 if not self.is_mkcert_ca_installed():
56 click.secho(
57 "Installing mkcert local CA. You may be prompted for your password.",
58 bold=True,
59 )
60 subprocess.run([self.mkcert_bin, "-install"], check=True)
61
62 def is_mkcert_ca_installed(self):
63 """Check if mkcert local CA is already installed using mkcert -check."""
64 try:
65 result = subprocess.run([self.mkcert_bin, "-check"], capture_output=True)
66 output = result.stdout.decode() + result.stderr.decode()
67 if "The local CA is not installed" in output:
68 return False
69 return True
70 except Exception as e:
71 click.secho(f"Error checking mkcert CA installation: {e}", fg="red")
72 return False
73
74 def generate_certs(self, domain, storage_path):
75 cert_path = storage_path / f"{domain}-cert.pem"
76 key_path = storage_path / f"{domain}-key.pem"
77 timestamp_path = storage_path / f"{domain}.timestamp"
78 update_interval = 60 * 24 * 3600 # 60 days in seconds
79
80 # Check if the certs exist and if the timestamp is recent enough
81 if cert_path.exists() and key_path.exists() and timestamp_path.exists():
82 last_updated = timestamp_path.stat().st_mtime
83 if time.time() - last_updated < update_interval:
84 return cert_path, key_path
85
86 storage_path.mkdir(parents=True, exist_ok=True)
87
88 click.secho(f"Generating SSL certificates for {domain}...", bold=True)
89 subprocess.run(
90 [
91 self.mkcert_bin,
92 "-cert-file",
93 str(cert_path),
94 "-key-file",
95 str(key_path),
96 domain,
97 ],
98 check=True,
99 )
100
101 # Update the timestamp file to the current time
102 with open(timestamp_path, "w") as f:
103 f.write(str(time.time()))
104
105 return cert_path, key_path