mcp-proxy-adapter 6.7.2__py3-none-any.whl → 6.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,285 +0,0 @@
1
- """
2
- Asynchronous proxy registration with heartbeat functionality.
3
-
4
- This module provides automatic proxy registration in a separate thread
5
- with continuous heartbeat monitoring and automatic re-registration on failures.
6
-
7
- Author: Vasiliy Zdanovskiy
8
- email: vasilyvz@gmail.com
9
- """
10
-
11
- import asyncio
12
- import time
13
- import threading
14
- from typing import Dict, Any, Optional
15
- from dataclasses import dataclass
16
- from enum import Enum
17
-
18
- from mcp_proxy_adapter.core.logging import logger
19
- from mcp_proxy_adapter.core.proxy_registration import ProxyRegistrationManager
20
-
21
-
22
- class RegistrationState(Enum):
23
- """Registration state enumeration."""
24
- DISABLED = "disabled"
25
- NOT_REGISTERED = "not_registered"
26
- REGISTERING = "registering"
27
- REGISTERED = "registered"
28
- HEARTBEAT_FAILED = "heartbeat_failed"
29
- ERROR = "error"
30
-
31
-
32
- @dataclass
33
- class RegistrationStatus:
34
- """Registration status information."""
35
- state: RegistrationState
36
- server_key: Optional[str] = None
37
- last_attempt: Optional[float] = None
38
- last_success: Optional[float] = None
39
- last_error: Optional[str] = None
40
- attempt_count: int = 0
41
- success_count: int = 0
42
-
43
-
44
- class AsyncProxyRegistrationManager:
45
- """
46
- Asynchronous proxy registration manager with heartbeat functionality.
47
-
48
- Runs registration and heartbeat in a separate thread, automatically
49
- handling re-registration on failures and continuous monitoring.
50
- """
51
-
52
- def __init__(self, config: Dict[str, Any]):
53
- """
54
- Initialize the async proxy registration manager.
55
-
56
- Args:
57
- config: Application configuration
58
- """
59
- self.config = config
60
- self.registration_manager = ProxyRegistrationManager(config)
61
- self.status = RegistrationStatus(state=RegistrationState.NOT_REGISTERED)
62
- self._thread: Optional[threading.Thread] = None
63
- self._stop_event = threading.Event()
64
- self._loop: Optional[asyncio.AbstractEventLoop] = None
65
-
66
- # Get heartbeat configuration
67
- heartbeat_config = config.get("registration", {}).get("heartbeat", {})
68
- self.heartbeat_enabled = heartbeat_config.get("enabled", True)
69
- self.heartbeat_interval = heartbeat_config.get("interval", 30)
70
- self.heartbeat_timeout = heartbeat_config.get("timeout", 10)
71
- self.retry_attempts = heartbeat_config.get("retry_attempts", 3)
72
- self.retry_delay = heartbeat_config.get("retry_delay", 5)
73
-
74
- logger.info(f"AsyncProxyRegistrationManager initialized: heartbeat_enabled={self.heartbeat_enabled}, interval={self.heartbeat_interval}s")
75
-
76
- def is_enabled(self) -> bool:
77
- """Check if proxy registration is enabled."""
78
- return self.registration_manager.is_enabled()
79
-
80
- def set_server_url(self, server_url: str):
81
- """Set the server URL for registration."""
82
- self.registration_manager.set_server_url(server_url)
83
- logger.info(f"Server URL set for async registration: {server_url}")
84
-
85
- def start(self):
86
- """Start the async registration and heartbeat thread."""
87
- if not self.is_enabled():
88
- logger.info("Proxy registration is disabled, not starting async manager")
89
- self.status.state = RegistrationState.DISABLED
90
- return
91
-
92
- if self._thread and self._thread.is_alive():
93
- logger.warning("Async registration thread is already running")
94
- return
95
-
96
- logger.info("Starting async proxy registration and heartbeat thread")
97
- self._stop_event.clear()
98
- self._thread = threading.Thread(target=self._run_async_loop, daemon=True)
99
- self._thread.start()
100
-
101
- def stop(self):
102
- """Stop the async registration and heartbeat thread."""
103
- if not self._thread or not self._thread.is_alive():
104
- return
105
-
106
- logger.info("Stopping async proxy registration and heartbeat thread")
107
- self._stop_event.set()
108
-
109
- # Unregister if registered
110
- if self.status.state == RegistrationState.REGISTERED:
111
- try:
112
- # Run unregistration in the thread's event loop
113
- if self._loop and not self._loop.is_closed():
114
- future = asyncio.run_coroutine_threadsafe(
115
- self.registration_manager.unregister_server(),
116
- self._loop
117
- )
118
- future.result(timeout=10)
119
- logger.info("Successfully unregistered from proxy during shutdown")
120
- except Exception as e:
121
- logger.error(f"Failed to unregister during shutdown: {e}")
122
-
123
- self._thread.join(timeout=5)
124
- if self._thread.is_alive():
125
- logger.warning("Async registration thread did not stop gracefully")
126
-
127
- def _run_async_loop(self):
128
- """Run the async event loop in the thread."""
129
- try:
130
- self._loop = asyncio.new_event_loop()
131
- asyncio.set_event_loop(self._loop)
132
- self._loop.run_until_complete(self._async_main())
133
- except Exception as e:
134
- logger.error(f"Async registration loop error: {e}")
135
- finally:
136
- if self._loop and not self._loop.is_closed():
137
- self._loop.close()
138
-
139
- async def _async_main(self):
140
- """Main async loop for registration and heartbeat."""
141
- logger.info("Async registration loop started")
142
-
143
- # Initial registration attempt
144
- await self._attempt_registration()
145
-
146
- # Main loop
147
- while not self._stop_event.is_set():
148
- try:
149
- if self.status.state == RegistrationState.REGISTERED:
150
- if self.heartbeat_enabled:
151
- # Perform heartbeat
152
- await self._perform_heartbeat()
153
- else:
154
- # Just wait if heartbeat is disabled
155
- await asyncio.sleep(self.heartbeat_interval)
156
- else:
157
- # Try to register
158
- await self._attempt_registration()
159
- # Wait before retry
160
- await asyncio.sleep(self.retry_delay)
161
-
162
- except Exception as e:
163
- logger.error(f"Error in async registration loop: {e}")
164
- self.status.state = RegistrationState.ERROR
165
- self.status.last_error = str(e)
166
- await asyncio.sleep(self.retry_delay)
167
-
168
- logger.info("Async registration loop stopped")
169
-
170
- async def _attempt_registration(self):
171
- """Attempt to register with the proxy."""
172
- if self.status.state == RegistrationState.REGISTERING:
173
- return # Already registering
174
-
175
- self.status.state = RegistrationState.REGISTERING
176
- self.status.attempt_count += 1
177
- self.status.last_attempt = time.time()
178
-
179
- logger.info(f"Attempting proxy registration (attempt #{self.status.attempt_count})")
180
-
181
- try:
182
- success = await self.registration_manager.register_server()
183
-
184
- if success:
185
- self.status.state = RegistrationState.REGISTERED
186
- self.status.last_success = time.time()
187
- self.status.success_count += 1
188
- self.status.last_error = None
189
- logger.info(f"✅ Proxy registration successful (attempt #{self.status.attempt_count})")
190
- else:
191
- self.status.state = RegistrationState.NOT_REGISTERED
192
- self.status.last_error = "Registration returned False"
193
- logger.warning(f"❌ Proxy registration failed (attempt #{self.status.attempt_count}): Registration returned False")
194
-
195
- except Exception as e:
196
- self.status.state = RegistrationState.NOT_REGISTERED
197
- self.status.last_error = str(e)
198
- logger.error(f"❌ Proxy registration failed (attempt #{self.status.attempt_count}): {e}")
199
-
200
- async def _perform_heartbeat(self):
201
- """Perform heartbeat to the proxy."""
202
- try:
203
- # Use the registration manager's heartbeat functionality
204
- success = await self.registration_manager.heartbeat()
205
-
206
- if success:
207
- logger.debug("💓 Heartbeat successful")
208
- self.status.last_success = time.time()
209
- else:
210
- logger.warning("💓 Heartbeat failed")
211
- self.status.state = RegistrationState.HEARTBEAT_FAILED
212
- self.status.last_error = "Heartbeat failed"
213
-
214
- # Try to re-register after heartbeat failure
215
- logger.info("Attempting re-registration after heartbeat failure")
216
- await self._attempt_registration()
217
-
218
- except Exception as e:
219
- logger.error(f"💓 Heartbeat error: {e}")
220
- self.status.state = RegistrationState.HEARTBEAT_FAILED
221
- self.status.last_error = str(e)
222
-
223
- # Try to re-register after heartbeat error
224
- logger.info("Attempting re-registration after heartbeat error")
225
- await self._attempt_registration()
226
-
227
- # Wait for next heartbeat
228
- await asyncio.sleep(self.heartbeat_interval)
229
-
230
- def get_status(self) -> Dict[str, Any]:
231
- """Get current registration status."""
232
- return {
233
- "state": self.status.state.value,
234
- "server_key": self.status.server_key,
235
- "last_attempt": self.status.last_attempt,
236
- "last_success": self.status.last_success,
237
- "last_error": self.status.last_error,
238
- "attempt_count": self.status.attempt_count,
239
- "success_count": self.status.success_count,
240
- "heartbeat_enabled": self.heartbeat_enabled,
241
- "heartbeat_interval": self.heartbeat_interval,
242
- "thread_alive": self._thread.is_alive() if self._thread else False
243
- }
244
-
245
-
246
- # Global instance
247
- _async_registration_manager: Optional[AsyncProxyRegistrationManager] = None
248
-
249
-
250
- def get_async_registration_manager() -> Optional[AsyncProxyRegistrationManager]:
251
- """Get the global async registration manager instance."""
252
- return _async_registration_manager
253
-
254
-
255
- def initialize_async_registration(config: Dict[str, Any]) -> AsyncProxyRegistrationManager:
256
- """Initialize the global async registration manager."""
257
- global _async_registration_manager
258
- _async_registration_manager = AsyncProxyRegistrationManager(config)
259
- return _async_registration_manager
260
-
261
-
262
- def start_async_registration(server_url: str):
263
- """Start async registration with the given server URL."""
264
- global _async_registration_manager
265
- if _async_registration_manager:
266
- _async_registration_manager.set_server_url(server_url)
267
- _async_registration_manager.start()
268
- else:
269
- logger.error("Async registration manager not initialized")
270
-
271
-
272
- def stop_async_registration():
273
- """Stop async registration."""
274
- global _async_registration_manager
275
- if _async_registration_manager:
276
- _async_registration_manager.stop()
277
-
278
-
279
- def get_registration_status() -> Dict[str, Any]:
280
- """Get current registration status."""
281
- global _async_registration_manager
282
- if _async_registration_manager:
283
- return _async_registration_manager.get_status()
284
- else:
285
- return {"state": "not_initialized"}
@@ -1,385 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Generate Certificates Using mcp_security_framework with Bugfix
4
- This script generates all necessary SSL certificates using the mcp_security_framework
5
- with the CertificateConfig bugfix applied.
6
-
7
- Author: Vasiliy Zdanovskiy
8
- email: vasilyvz@gmail.com
9
- """
10
- import json
11
- import os
12
- import sys
13
- from pathlib import Path
14
- from typing import Dict, List, Optional
15
-
16
- # Import mcp_security_framework components
17
- from mcp_security_framework.core.cert_manager import CertificateManager
18
- from mcp_security_framework.schemas.config import CAConfig, ServerCertConfig, ClientCertConfig, CertificateConfig
19
- from mcp_security_framework.utils.cert_utils import validate_certificate_format
20
-
21
- # Import required certificates configuration
22
- from required_certificates import REQUIRED_CERTIFICATES, get_all_required_certificates
23
-
24
-
25
- class BugfixCertificateGenerator:
26
- """Certificate generator using mcp_security_framework with bugfix."""
27
-
28
- def __init__(self):
29
- """Initialize the certificate generator."""
30
- self.working_dir = Path.cwd()
31
- self.certs_dir = self.working_dir / "certs"
32
- self.keys_dir = self.working_dir / "keys"
33
-
34
- # Ensure directories exist
35
- self.certs_dir.mkdir(exist_ok=True)
36
- self.keys_dir.mkdir(exist_ok=True)
37
-
38
- # Certificate manager will be initialized after CA is created
39
- self.cert_manager = None
40
-
41
- def print_step(self, step: str, description: str):
42
- """Print a formatted step header."""
43
- print(f"\n{'=' * 60}")
44
- print(f"🔧 STEP {step}: {description}")
45
- print(f"{'=' * 60}")
46
-
47
- def print_success(self, message: str):
48
- """Print a success message."""
49
- print(f"✅ {message}")
50
-
51
- def print_error(self, message: str):
52
- """Print an error message."""
53
- print(f"❌ {message}")
54
-
55
- def print_info(self, message: str):
56
- """Print an info message."""
57
- print(f"ℹ️ {message}")
58
-
59
- def check_framework(self) -> bool:
60
- """Check if mcp_security_framework is available."""
61
- try:
62
- from mcp_security_framework import __version__
63
- self.print_success(f"mcp_security_framework v{__version__} is available")
64
- return True
65
- except ImportError as e:
66
- self.print_error(f"mcp_security_framework is not available: {e}")
67
- return False
68
-
69
- def _initialize_cert_manager(self, ca_creation_mode: bool = False):
70
- """Initialize certificate manager with configuration."""
71
- cert_config = CertificateConfig(
72
- enabled=True,
73
- ca_creation_mode=ca_creation_mode,
74
- ca_cert_path=str(self.certs_dir / "ca_cert.pem") if not ca_creation_mode else None,
75
- ca_key_path=str(self.keys_dir / "ca_key.pem") if not ca_creation_mode else None,
76
- cert_storage_path=str(self.certs_dir),
77
- key_storage_path=str(self.keys_dir),
78
- default_validity_days=365,
79
- key_size=2048,
80
- hash_algorithm="sha256"
81
- )
82
- self.cert_manager = CertificateManager(cert_config)
83
-
84
- def generate_ca_certificate(self) -> bool:
85
- """Generate CA certificate using mcp_security_framework with bugfix."""
86
- self.print_step("1", "Generating CA Certificate with Bugfix")
87
-
88
- ca_info = REQUIRED_CERTIFICATES["ca_cert"]
89
-
90
- try:
91
- # Check if CA certificate already exists
92
- if ca_info["output_cert"].exists() and ca_info["output_key"].exists():
93
- self.print_info(f"CA certificate already exists: {ca_info['output_cert']}")
94
- return True
95
-
96
- # Create CA configuration
97
- ca_config = CAConfig(
98
- common_name=ca_info["common_name"],
99
- organization=ca_info["organization"],
100
- country=ca_info["country"],
101
- state=ca_info["state"],
102
- locality=ca_info["city"],
103
- validity_years=ca_info["validity_days"] // 365,
104
- key_size=2048,
105
- hash_algorithm="sha256"
106
- )
107
-
108
- self.print_info(f"Generating CA certificate: {ca_info['common_name']}")
109
-
110
- # Initialize certificate manager in CA creation mode
111
- self._initialize_cert_manager(ca_creation_mode=True)
112
-
113
- # Generate CA certificate using framework
114
- result = self.cert_manager.create_root_ca(ca_config=ca_config)
115
-
116
- if result:
117
- # Save the generated certificate and key to expected locations
118
- with open(ca_info["output_cert"], 'w') as f:
119
- f.write(result.certificate_pem)
120
- with open(ca_info["output_key"], 'w') as f:
121
- f.write(result.private_key_pem)
122
-
123
- self.print_success(f"CA certificate generated: {ca_info['output_cert']}")
124
- # Reinitialize certificate manager in normal mode
125
- self._initialize_cert_manager(ca_creation_mode=False)
126
- return True
127
- else:
128
- self.print_error("Failed to generate CA certificate")
129
- return False
130
-
131
- except Exception as e:
132
- self.print_error(f"Exception during CA certificate generation: {e}")
133
- return False
134
-
135
- def generate_server_certificate(self) -> bool:
136
- """Generate server certificate using mcp_security_framework."""
137
- self.print_step("2", "Generating Server Certificate")
138
-
139
- server_info = REQUIRED_CERTIFICATES["server_cert"]
140
-
141
- try:
142
- # Check if server certificate already exists
143
- if server_info["output_cert"].exists() and server_info["output_key"].exists():
144
- self.print_info(f"Server certificate already exists: {server_info['output_cert']}")
145
- return True
146
-
147
- # Ensure certificate manager is initialized in normal mode
148
- if self.cert_manager is None:
149
- self._initialize_cert_manager(ca_creation_mode=False)
150
-
151
- # Create server certificate configuration
152
- server_config = ServerCertConfig(
153
- common_name=server_info["common_name"],
154
- organization=server_info["organization"],
155
- country=server_info["country"],
156
- state=server_info["state"],
157
- locality=server_info["city"],
158
- validity_days=server_info["validity_days"],
159
- key_size=2048,
160
- hash_algorithm="sha256",
161
- ca_cert_path=str(server_info["ca_cert_path"]),
162
- ca_key_path=str(server_info["ca_key_path"]),
163
- san=server_info.get("san", [])
164
- )
165
-
166
- self.print_info(f"Generating server certificate: {server_info['common_name']}")
167
-
168
- # Generate server certificate using framework
169
- result = self.cert_manager.create_server_certificate(server_config=server_config)
170
-
171
- if result:
172
- # Save the generated certificate and key to expected locations
173
- with open(server_info["output_cert"], 'w') as f:
174
- f.write(result.certificate_pem)
175
- with open(server_info["output_key"], 'w') as f:
176
- f.write(result.private_key_pem)
177
-
178
- self.print_success(f"Server certificate generated: {server_info['output_cert']}")
179
- return True
180
- else:
181
- self.print_error("Failed to generate server certificate")
182
- return False
183
-
184
- except Exception as e:
185
- self.print_error(f"Exception during server certificate generation: {e}")
186
- return False
187
-
188
- def generate_client_certificate(self, cert_name: str) -> bool:
189
- """Generate client certificate using mcp_security_framework."""
190
- self.print_step(f"3.{cert_name}", f"Generating {cert_name.title()} Client Certificate")
191
-
192
- client_info = REQUIRED_CERTIFICATES[cert_name]
193
-
194
- try:
195
- # Check if client certificate already exists
196
- if client_info["output_cert"].exists() and client_info["output_key"].exists():
197
- self.print_info(f"{cert_name} certificate already exists: {client_info['output_cert']}")
198
- return True
199
-
200
- # Ensure certificate manager is initialized in normal mode
201
- if self.cert_manager is None:
202
- self._initialize_cert_manager(ca_creation_mode=False)
203
-
204
- # Create client certificate configuration
205
- client_config = ClientCertConfig(
206
- common_name=client_info["common_name"],
207
- organization=client_info["organization"],
208
- country=client_info["country"],
209
- state=client_info["state"],
210
- locality=client_info["city"],
211
- validity_days=client_info["validity_days"],
212
- key_size=2048,
213
- hash_algorithm="sha256",
214
- ca_cert_path=str(client_info["ca_cert_path"]),
215
- ca_key_path=str(client_info["ca_key_path"]),
216
- roles=client_info.get("roles", []),
217
- permissions=client_info.get("permissions", [])
218
- )
219
-
220
- self.print_info(f"Generating {cert_name} certificate: {client_info['common_name']}")
221
-
222
- # Generate client certificate using framework
223
- result = self.cert_manager.create_client_certificate(client_config=client_config)
224
-
225
- if result:
226
- # Save the generated certificate and key to expected locations
227
- with open(client_info["output_cert"], 'w') as f:
228
- f.write(result.certificate_pem)
229
- with open(client_info["output_key"], 'w') as f:
230
- f.write(result.private_key_pem)
231
-
232
- # Also create a copy in certs/ directory for easier access
233
- cert_name_base = client_info["common_name"].replace("-", "_")
234
- certs_cert = self.certs_dir / f"{cert_name_base}_client.crt"
235
- certs_key = self.certs_dir / f"{cert_name_base}_client.key"
236
-
237
- with open(certs_cert, 'w') as f:
238
- f.write(result.certificate_pem)
239
- with open(certs_key, 'w') as f:
240
- f.write(result.private_key_pem)
241
-
242
- self.print_success(f"{cert_name} certificate generated: {client_info['output_cert']}")
243
- self.print_success(f"Also created: {certs_cert} and {certs_key}")
244
- return True
245
- else:
246
- self.print_error(f"Failed to generate {cert_name} certificate")
247
- return False
248
-
249
- except Exception as e:
250
- self.print_error(f"Exception during {cert_name} certificate generation: {e}")
251
- return False
252
-
253
- def create_certificate_aliases(self) -> bool:
254
- """Create certificate aliases for different configurations."""
255
- self.print_step("4", "Creating Certificate Aliases")
256
-
257
- try:
258
- # Create aliases for HTTPS configurations
259
- if (self.certs_dir / "server_cert.pem").exists():
260
- # HTTPS aliases
261
- (self.certs_dir / "mcp_proxy_adapter_server.crt").unlink(missing_ok=True)
262
- (self.certs_dir / "mcp_proxy_adapter_server.crt").symlink_to("server_cert.pem")
263
-
264
- (self.certs_dir / "mcp_proxy_adapter_server.key").unlink(missing_ok=True)
265
- (self.certs_dir / "mcp_proxy_adapter_server.key").symlink_to(self.keys_dir / "server_key.pem")
266
-
267
- # mTLS aliases
268
- (self.certs_dir / "localhost_server.crt").unlink(missing_ok=True)
269
- (self.certs_dir / "localhost_server.crt").symlink_to("server_cert.pem")
270
-
271
- self.print_success("Certificate aliases created")
272
-
273
- # Create CA alias
274
- if (self.certs_dir / "ca_cert.pem").exists():
275
- (self.certs_dir / "mcp_proxy_adapter_ca_ca.crt").unlink(missing_ok=True)
276
- (self.certs_dir / "mcp_proxy_adapter_ca_ca.crt").symlink_to("ca_cert.pem")
277
-
278
- self.print_success("CA certificate alias created")
279
-
280
- return True
281
-
282
- except Exception as e:
283
- self.print_error(f"Failed to create certificate aliases: {e}")
284
- return False
285
-
286
- def validate_certificates(self) -> bool:
287
- """Validate generated certificates using framework."""
288
- self.print_step("5", "Validating Certificates")
289
-
290
- all_required = get_all_required_certificates()
291
- validation_results = []
292
-
293
- for cert_name in all_required:
294
- cert_info = REQUIRED_CERTIFICATES[cert_name]
295
- cert_file = cert_info["output_cert"]
296
- key_file = cert_info["output_key"]
297
-
298
- if cert_file.exists() and key_file.exists():
299
- try:
300
- # Validate certificate format using framework
301
- format_valid = validate_certificate_format(str(cert_file))
302
-
303
- if format_valid:
304
- self.print_success(f"{cert_name}: Valid format")
305
- validation_results.append(True)
306
- else:
307
- self.print_error(f"{cert_name}: Invalid format")
308
- validation_results.append(False)
309
-
310
- except Exception as e:
311
- self.print_error(f"{cert_name}: Validation failed - {e}")
312
- validation_results.append(False)
313
- else:
314
- self.print_error(f"{cert_name}: Missing files")
315
- validation_results.append(False)
316
-
317
- success_count = sum(validation_results)
318
- total_count = len(validation_results)
319
-
320
- self.print_info(f"Validation results: {success_count}/{total_count} certificates valid")
321
-
322
- return success_count == total_count
323
-
324
- def generate_all_certificates(self) -> bool:
325
- """Generate all required certificates."""
326
- print("🔐 Generating All Certificates Using mcp_security_framework with Bugfix")
327
- print("=" * 60)
328
-
329
- try:
330
- # Check framework availability
331
- if not self.check_framework():
332
- return False
333
-
334
- # Generate CA certificate first
335
- if not self.generate_ca_certificate():
336
- return False
337
-
338
- # Generate server certificate
339
- if not self.generate_server_certificate():
340
- return False
341
-
342
- # Generate client certificates
343
- client_certs = ["admin_cert", "user_cert", "proxy_cert"]
344
- for cert_name in client_certs:
345
- if cert_name in REQUIRED_CERTIFICATES:
346
- if not self.generate_client_certificate(cert_name):
347
- return False
348
-
349
- # Create aliases
350
- if not self.create_certificate_aliases():
351
- return False
352
-
353
- # Validate certificates
354
- if not self.validate_certificates():
355
- return False
356
-
357
- # Print summary
358
- print(f"\n{'=' * 60}")
359
- print("📊 CERTIFICATE GENERATION SUMMARY")
360
- print(f"{'=' * 60}")
361
- print("✅ All certificates generated successfully!")
362
- print(f"📁 Certificates directory: {self.certs_dir}")
363
- print(f"📁 Keys directory: {self.keys_dir}")
364
-
365
- return True
366
-
367
- except Exception as e:
368
- self.print_error(f"Certificate generation failed: {e}")
369
- return False
370
-
371
-
372
- def main():
373
- """Main entry point."""
374
- generator = BugfixCertificateGenerator()
375
-
376
- try:
377
- success = generator.generate_all_certificates()
378
- sys.exit(0 if success else 1)
379
- except Exception as e:
380
- print(f"❌ Fatal error: {e}")
381
- sys.exit(1)
382
-
383
-
384
- if __name__ == "__main__":
385
- main()