Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/Jobs/Server/UpdatePasswordJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ class UpdatePasswordJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public int $tries = 3;
public int $tries = 15;

public int $timeout = 10;


public function backoff(): int
{
return 30; // Not blocking — Horizon waits asynchronously
}

public function __construct(protected int $serverId, protected string $password)
{
//
Expand Down
22 changes: 18 additions & 4 deletions app/Services/Servers/AllocationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,24 @@ public function setBootOrder(Server $server, array $disks)

public function updateHardware(Server $server, int $cpu, int $memory)
{
$payload = [
'cores' => $cpu,
'memory' => $memory / 1048576,
];
$desiredCores = $cpu;
$desiredMemMiB = (int) ($memory / 1048576);

$raw = collect($this->repository->setServer($server)->getConfig());
$currentCores = $raw->where('key', '=', 'cores')->first()['value'] ?? null;
$currentMem = $raw->where('key', '=', 'memory')->first()['value'] ?? null;

$payload = [];
if ((string) $currentCores !== (string) $desiredCores) {
$payload['cores'] = $desiredCores;
}
if ((string) $currentMem !== (string) $desiredMemMiB) {
$payload['memory'] = $desiredMemMiB;
}

if (count($payload) === 0) {
return;
}

return $this->repository->setServer($server)->update($payload);
}
Expand Down
38 changes: 31 additions & 7 deletions app/Services/Servers/CloudinitService.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,23 @@ public function getSSHKeys(Server $server): string
*/
public function updateHostname(Server $server, string $hostname)
{
$this->configRepository->setServer($server)->update([
'name' => $hostname,
]);
$raw = collect($this->configRepository->setServer($server)->getConfig());
$currentName = $raw->where('key', '=', 'name')->first()['value'] ?? null;
$currentSearch = $raw->where('key', '=', 'searchdomain')->first()['value'] ?? null;

$this->configRepository->setServer($server)->update(['searchdomain' => $hostname]);
$payload = [];
if ($currentName !== $hostname) {
$payload['name'] = $hostname;
}
if ($currentSearch !== $hostname) {
$payload['searchdomain'] = $hostname;
}

if (count($payload) === 0) {
return;
}

$this->configRepository->setServer($server)->update($payload);
}

public function getNameservers(Server $server)
Expand Down Expand Up @@ -120,8 +132,20 @@ public function updateIpConfig(Server $server, CloudinitAddressConfigData $addre
$payload[] = 'gw6='.$ipv6->gateway;
}

return $this->configRepository->setServer($server)->update([
'ipconfig0' => Arr::join($payload, ','),
]);
$desired = Arr::join($payload, ',');

$raw = collect($this->configRepository->setServer($server)->getConfig());
$current = $raw->where('key', '=', 'ipconfig0')->first()['value'] ?? null;

if ($current === $desired) {
return;
}
if (($desired === '' || $desired === null) && ($current === '' || $current === null)) return;
if ($desired === '' || $desired === null) {
return $this->configRepository->setServer($server)->update([
'delete' => 'ipconfig0',
]);
}
return $this->configRepository->setServer($server)->update(['ipconfig0' => $desired]);
}
}
139 changes: 111 additions & 28 deletions app/Services/Servers/NetworkService.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,68 @@

class NetworkService
{
private function ensureNet0BaseConfig(Server $server, string $macAddress): void
{
$rawConfig = $this->allocationRepository->setServer($server)->getConfig();
$net0 = collect($rawConfig)->where('key', '=', 'net0')->first();

$parsedConfig = $net0 ? $this->parseConfig($net0['value']) : [];

$models = [
'e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em',
'e1000e', 'i82551', 'i82557b', 'i82559er',
'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139',
'virtio', 'vmxnet3',
];

$modelFound = false;
foreach ($parsedConfig as $item) {
if (in_array($item->key, $models, true)) {
$item->value = $macAddress;
$modelFound = true;
break;
}
}

if (!$modelFound) {
$parsedConfig[] = (object) ['key' => 'virtio', 'value' => $macAddress];
}

$bridgeFound = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'bridge') {
$item->value = $server->node->network;
$bridgeFound = true;
break;
}
}

if (!$bridgeFound) {
$parsedConfig[] = (object) ['key' => 'bridge', 'value' => $server->node->network];
}

$firewallFound = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'firewall') {
$item->value = 1;
$firewallFound = true;
break;
}
}

if (!$firewallFound) {
$parsedConfig[] = (object) ['key' => 'firewall', 'value' => 1];
}

$newConfig = implode(',', array_map(fn ($item) => "{$item->key}={$item->value}", $parsedConfig));

if ($net0 && $this->normalizeNetConfigForComparison($net0['value']) === $this->normalizeNetConfigForComparison($newConfig)) {
return;
}

$this->allocationRepository->setServer($server)->update(['net0' => $newConfig]);
}

public function __construct(
private AddressRepository $repository,
private ProxmoxFirewallRepository $firewallRepository,
Expand Down Expand Up @@ -67,8 +129,8 @@ public function getMacAddresses(Server $server, bool $eloquent = true, bool $pro
if ($eloquent) {
$addresses = $this->getAddresses($server);

$eloquentMacAddress = $addresses->ipv4->first(
)?->mac_address ?? $addresses->ipv6->first()?->mac_address;
$eloquentMacAddress = $addresses->ipv4->first()
?->mac_address ?? $addresses->ipv6->first()?->mac_address;
}

if ($proxmox) {
Expand All @@ -86,7 +148,7 @@ public function getMacAddresses(Server $server, bool $eloquent = true, bool $pro

return MacAddressData::from([
'eloquent' => $eloquentMacAddress ?? null,
'proxmox' => $proxmoxMacAddress ?? null,
'proxmox' => $proxmoxMacAddress ?? null,
]);
}

Expand Down Expand Up @@ -125,17 +187,15 @@ public function syncSettings(Server $server): void
]);

$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;

$this->allocationRepository->setServer($server)->update(
['net0' => "virtio={$macAddress},bridge={$server->node->network},firewall=1"],
);
$this->ensureNet0BaseConfig($server, $macAddress);
}

public function updateRateLimit(Server $server, ?int $mebibytes = null): void
{
$macAddresses = $this->getMacAddresses($server, true, true);
$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;
$rawConfig = $this->allocationRepository->setServer($server)->getConfig();
$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;

$rawConfig = $this->allocationRepository->setServer($server)->getConfig();
$networkConfig = collect($rawConfig)->where('key', '=', 'net0')->first();

if (is_null($networkConfig)) {
Expand All @@ -147,22 +207,19 @@ public function updateRateLimit(Server $server, ?int $mebibytes = null): void
// List of possible models
$models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'e1000e', 'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3'];

// Update the model with the new MAC address
$modelFound = false;
foreach ($parsedConfig as $item) {
if (in_array($item->key, $models)) {
if (in_array($item->key, $models, true)) {
$item->value = $macAddress;
$modelFound = true;
$modelFound = true;
break;
}
}

// If no model key exists, add the default model with the MAC address
if (!$modelFound) {
$parsedConfig[] = (object) ['key' => 'virtio', 'value' => $macAddress];
}

// Update or create the bridge value
$bridgeFound = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'bridge') {
Expand All @@ -176,7 +233,6 @@ public function updateRateLimit(Server $server, ?int $mebibytes = null): void
$parsedConfig[] = (object) ['key' => 'bridge', 'value' => $server->node->network];
}

// Update or create the firewall key
$firewallFound = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'firewall') {
Expand All @@ -190,12 +246,11 @@ public function updateRateLimit(Server $server, ?int $mebibytes = null): void
$parsedConfig[] = (object) ['key' => 'firewall', 'value' => 1];
}

// Handle the rate limit
if (is_null($mebibytes)) {
// Remove the 'rate' key if $mebibytes is null
$parsedConfig = array_filter($parsedConfig, fn ($item) => $item->key !== 'rate');
$parsedConfig = array_values(
array_filter($parsedConfig, fn ($item) => $item->key !== 'rate')
);
} else {
// Add or update the 'rate' key
$rateUpdated = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'rate') {
Expand All @@ -210,26 +265,54 @@ public function updateRateLimit(Server $server, ?int $mebibytes = null): void
}
}

// Rebuild the configuration string
$newConfig = implode(',', array_map(fn ($item) => "{$item->key}={$item->value}", $parsedConfig));
$newConfig = implode(
',',
array_map(fn ($item) => "{$item->key}={$item->value}", $parsedConfig)
);

if (
$this->normalizeNetConfigForComparison($networkConfig['value']) ===
$this->normalizeNetConfigForComparison($newConfig)
) {
return;
}

// Update the Proxmox configuration
$this->allocationRepository->setServer($server)->update(['net0' => $newConfig]);
}

private function normalizeNetConfigForComparison(string $config): array
{
$parsed = $this->parseConfig($config);

$normalized = [];
foreach ($parsed as $item) {
$key = strtolower(trim((string) $item->key));
$value = trim((string) $item->value);

if (preg_match('/^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$/i', $value)) {
$value = strtolower($value);
}

$normalized[$key] = $value;
}

ksort($normalized);

return $normalized;
}

private function parseConfig(string $config): array
{
// Split components by commas
$components = explode(',', $config);

// Array to hold the parsed objects
$parsedObjects = [];

foreach ($components as $component) {
// Split each component into key and value
[$key, $value] = explode('=', $component);
$component = trim($component);
if ($component === '') {
continue;
}

// Create an associative array (or object) for key-value pairs
[$key, $value] = array_pad(explode('=', $component, 2), 2, '');
$parsedObjects[] = (object) ['key' => $key, 'value' => $value];
}

Expand Down