refactor(deployment): Remove WireGuard VPN dependency and restore public service access

Remove WireGuard integration from production deployment to simplify infrastructure:
- Remove docker-compose-direct-access.yml (VPN-bound services)
- Remove VPN-only middlewares from Grafana, Prometheus, Portainer
- Remove WireGuard middleware definitions from Traefik
- Remove WireGuard IPs (10.8.0.0/24) from Traefik forwarded headers

All monitoring services now publicly accessible via subdomains:
- grafana.michaelschiemer.de (with Grafana native auth)
- prometheus.michaelschiemer.de (with Basic Auth)
- portainer.michaelschiemer.de (with Portainer native auth)

All services use Let's Encrypt SSL certificates via Traefik.
This commit is contained in:
2025-11-05 12:48:25 +01:00
parent 7c52065aae
commit 95147ff23e
215 changed files with 29490 additions and 368 deletions

View File

@@ -0,0 +1,309 @@
<?php
declare(strict_types=1);
namespace App\Framework\ExceptionHandling\Context;
use DateTimeImmutable;
/**
* Exception Context Data
*
* Immutable value object containing rich exception context.
* Stored externally via ExceptionContextProvider - never embedded in exceptions.
*
* PHP 8.5+ readonly class with asymmetric visibility for extensibility.
*/
final readonly class ExceptionContextData
{
public readonly DateTimeImmutable $occurredAt;
/**
* @param string|null $operation Operation being performed (e.g., 'user.create', 'payment.process')
* @param string|null $component Component where error occurred (e.g., 'UserService', 'PaymentGateway')
* @param array<string, mixed> $data Domain data (e.g., user_id, order_id, amount)
* @param array<string, mixed> $debug Debug data (queries, traces, internal state)
* @param array<string, mixed> $metadata Additional metadata (tags, severity, fingerprint)
* @param DateTimeImmutable|null $occurredAt When the exception occurred
* @param string|null $userId User ID if available
* @param string|null $requestId Request ID for tracing
* @param string|null $sessionId Session ID if available
* @param string|null $clientIp Client IP address for HTTP requests
* @param string|null $userAgent User agent string for HTTP requests
* @param array<string> $tags Tags for categorization (e.g., ['payment', 'external_api'])
*/
public function __construct(
public ?string $operation = null,
public ?string $component = null,
public array $data = [],
public array $debug = [],
public array $metadata = [],
?DateTimeImmutable $occurredAt = null,
public ?string $userId = null,
public ?string $requestId = null,
public ?string $sessionId = null,
public ?string $clientIp = null,
public ?string $userAgent = null,
public array $tags = [],
) {
$this->occurredAt ??= new DateTimeImmutable();
}
/**
* Create empty context
*/
public static function empty(): self
{
return new self();
}
/**
* Create context with operation
*/
public static function forOperation(string $operation, ?string $component = null): self
{
return new self(
operation: $operation,
component: $component
);
}
/**
* Create context with data
*/
public static function withData(array $data): self
{
return new self(data: $data);
}
/**
* Create new instance with operation
*/
public function withOperation(string $operation, ?string $component = null): self
{
return new self(
operation: $operation,
component: $component ?? $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add data to context
*/
public function addData(array $data): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: array_merge($this->data, $data),
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add debug information
*/
public function addDebug(array $debug): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: array_merge($this->debug, $debug),
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add metadata
*/
public function addMetadata(array $metadata): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: array_merge($this->metadata, $metadata),
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add user ID
*/
public function withUserId(string $userId): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add request ID
*/
public function withRequestId(string $requestId): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add session ID
*/
public function withSessionId(string $sessionId): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add client IP
*/
public function withClientIp(string $clientIp): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $clientIp,
userAgent: $this->userAgent,
tags: $this->tags
);
}
/**
* Add user agent
*/
public function withUserAgent(string $userAgent): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $userAgent,
tags: $this->tags
);
}
/**
* Add tags
*/
public function withTags(string ...$tags): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
occurredAt: $this->occurredAt,
userId: $this->userId,
requestId: $this->requestId,
sessionId: $this->sessionId,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
tags: array_merge($this->tags, $tags)
);
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'operation' => $this->operation,
'component' => $this->component,
'data' => $this->data,
'debug' => $this->debug,
'metadata' => $this->metadata,
'occurred_at' => $this->occurredAt?->format('Y-m-d H:i:s.u'),
'user_id' => $this->userId,
'request_id' => $this->requestId,
'session_id' => $this->sessionId,
'client_ip' => $this->clientIp,
'user_agent' => $this->userAgent,
'tags' => $this->tags,
];
}
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace App\Framework\ExceptionHandling\Context;
use WeakMap;
/**
* Exception Context Provider
*
* Manages exception context externally using WeakMap for automatic garbage collection.
* Context is automatically cleaned up when the exception is garbage collected.
*
* PHP 8.5+ WeakMap-based implementation - no memory leaks possible.
*/
final class ExceptionContextProvider
{
/** @var WeakMap<\Throwable, ExceptionContextData> */
private WeakMap $contexts;
private static ?self $instance = null;
private function __construct()
{
$this->contexts = new WeakMap();
}
/**
* Get singleton instance
*
* Singleton pattern ensures consistent context across the application
*/
public static function instance(): self
{
return self::$instance ??= new self();
}
/**
* Attach context to exception
*
* @param \Throwable $exception The exception to attach context to
* @param ExceptionContextData $context The context data
*/
public function attach(\Throwable $exception, ExceptionContextData $context): void
{
$this->contexts[$exception] = $context;
}
/**
* Get context for exception
*
* @param \Throwable $exception The exception to get context for
* @return ExceptionContextData|null The context data or null if not found
*/
public function get(\Throwable $exception): ?ExceptionContextData
{
return $this->contexts[$exception] ?? null;
}
/**
* Check if exception has context
*
* @param \Throwable $exception The exception to check
* @return bool True if context exists
*/
public function has(\Throwable $exception): bool
{
return isset($this->contexts[$exception]);
}
/**
* Remove context from exception
*
* Note: Usually not needed due to WeakMap automatic cleanup,
* but provided for explicit control if needed.
*
* @param \Throwable $exception The exception to remove context from
*/
public function detach(\Throwable $exception): void
{
unset($this->contexts[$exception]);
}
/**
* Get statistics about context storage
*
* @return array{total_contexts: int}
*/
public function getStats(): array
{
// WeakMap doesn't provide count(), so we iterate
$count = 0;
foreach ($this->contexts as $_) {
$count++;
}
return [
'total_contexts' => $count,
];
}
/**
* Clear all contexts
*
* Mainly for testing purposes
*/
public function clear(): void
{
$this->contexts = new WeakMap();
}
}