initial commit

This commit is contained in:
TheOnlyMace
2026-05-17 13:26:14 +02:00
commit 75299b723d
176 changed files with 20327 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
<?php
namespace App\Services\Hosting\Provisioning;
use App\Enums\IpPoolType;
use App\Exceptions\Hosting\ProvisioningException;
use App\Models\Customer;
use App\Models\IpPool;
use Illuminate\Support\Facades\DB;
class IpAddressAllocator
{
public function allocateFromPool(?IpPool $pool = null, ?string $preferred = null): string
{
$pool ??= $this->defaultPrivatePool();
return $this->allocate($pool, $preferred);
}
public function allocatePublicIp(?IpPool $pool = null, ?string $preferred = null): string
{
$pool ??= IpPool::query()
->where('type', IpPoolType::Public)
->where('is_active', true)
->first() ?? $this->bootstrapPublicPool();
if ($pool->type !== IpPoolType::Public) {
throw new ProvisioningException('Selected pool is not a public IP pool.', step: 'ip_allocation');
}
return $this->allocate($pool, $preferred, 'public_ip');
}
public function allocate(IpPool $pool, ?string $preferred = null, string $column = 'ip_address'): string
{
return DB::transaction(function () use ($pool, $preferred, $column) {
IpPool::query()->whereKey($pool->id)->lockForUpdate()->first();
if ($preferred !== null) {
if (! $pool->containsIp($preferred)) {
throw new ProvisioningException(
"IP {$preferred} is outside pool {$pool->name}.",
step: 'ip_allocation',
);
}
if ($this->isIpUsed($preferred, $pool, $column)) {
throw new ProvisioningException(
"IP address {$preferred} is already in use.",
step: 'ip_allocation',
);
}
return $preferred;
}
$start = ip2long($pool->start_ip);
$end = ip2long($pool->end_ip);
if ($start === false || $end === false || $start > $end) {
throw new ProvisioningException('Invalid IP pool range.', step: 'ip_allocation');
}
$used = $this->usedIpsInPool($pool, $column);
for ($long = $start; $long <= $end; $long++) {
if (! isset($used[$long])) {
return long2ip($long);
}
}
throw new ProvisioningException("No free IPs in pool {$pool->name}.", step: 'ip_allocation');
}, 3);
}
private function defaultPrivatePool(): IpPool
{
$pool = IpPool::query()
->where('type', IpPoolType::Private)
->where('is_active', true)
->orderBy('id')
->first();
if ($pool) {
return $pool;
}
return $this->bootstrapPoolFromConfig(IpPoolType::Private);
}
private function bootstrapPublicPool(): IpPool
{
return IpPool::query()->firstOrCreate(
['name' => 'Öffentlich 185.45.149.x', 'type' => IpPoolType::Public],
[
'start_ip' => config('hosting.public_network.ip_pool_start'),
'end_ip' => config('hosting.public_network.ip_pool_end'),
'gateway' => config('hosting.public_network.gateway'),
'cidr' => config('hosting.public_network.cidr'),
'is_active' => true,
],
);
}
private function bootstrapPoolFromConfig(IpPoolType $type): IpPool
{
return IpPool::query()->firstOrCreate(
['name' => 'Privat 10.32.0.0/24', 'type' => IpPoolType::Private],
[
'start_ip' => config('hosting.network.ip_pool_start'),
'end_ip' => config('hosting.network.ip_pool_end'),
'gateway' => config('hosting.network.gateway'),
'cidr' => config('hosting.network.cidr'),
'is_active' => true,
],
);
}
private function usedIpsInPool(IpPool $pool, string $column): array
{
$query = Customer::query()
->where('ip_pool_id', $pool->id)
->whereIn('status', ['pending', 'active']);
if ($column === 'public_ip') {
return $query->whereNotNull('public_ip')
->pluck('public_ip')
->merge(
Customer::query()
->whereIn('status', ['pending', 'active'])
->whereNotNull('public_ip')
->pluck('public_ip')
)
->unique()
->map(fn (string $ip) => ip2long($ip))
->filter()
->flip()
->all();
}
return $query->whereNotNull('ip_address')
->pluck('ip_address')
->map(fn (string $ip) => ip2long($ip))
->filter()
->flip()
->all();
}
private function isIpUsed(string $ip, IpPool $pool, string $column): bool
{
$check = Customer::query()
->whereIn('status', ['pending', 'active']);
if ($column === 'public_ip') {
return (clone $check)->where('public_ip', $ip)->exists()
|| (clone $check)->where('ip_address', $ip)->exists();
}
return (clone $check)->where('ip_address', $ip)->exists()
|| (clone $check)->where('public_ip', $ip)->exists();
}
}