getSize() > $maxMb * 1024 * 1024) { throw new ProvisioningException("Maximale ISO-Größe: {$maxMb} MB.", step: 'iso_upload'); } $maxPerCustomer = (int) config('hosting.iso_upload.max_per_customer', 1); $existing = CustomerIsoUpload::query()->where('user_id', $user->id)->where('expires_at', '>', now())->count(); if ($existing >= $maxPerCustomer) { throw new ProvisioningException('Nur eine aktive ISO pro Kunde erlaubt.', step: 'iso_upload'); } $storage = config('hosting.proxmox.iso_storage', 'ISO'); $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $file->getClientOriginalName()) ?: 'upload.iso'; $path = $file->getRealPath(); $this->proxmox->uploadIsoToStorage($storage, $safeName, $path); $volid = "{$storage}:iso/{$safeName}"; $hours = (int) config('hosting.iso_upload.retention_hours', 48); return CustomerIsoUpload::query()->create([ 'user_id' => $user->id, 'filename' => $safeName, 'volid' => $volid, 'size_bytes' => $file->getSize(), 'expires_at' => now()->addHours($hours), ]); } public function purgeExpired(): int { $storage = config('hosting.proxmox.iso_storage', 'ISO'); $count = 0; foreach (CustomerIsoUpload::query()->where('expires_at', '<=', now())->get() as $upload) { try { $this->proxmox->deleteStorageFile($storage, $upload->volid); } catch (\Throwable) { // } $upload->delete(); $count++; } return $count; } }