feat(hetzner): add support for enabling backups during server creation

This commit is contained in:
Jan Thiel committed 2026-04-28 15:37:26 +02:00
1 parent a4b27641c8
commit 6637159d7b
6 files changed
+148 -10

No files matched your search

+14 -2
View File
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Actions\Server\ValidateServer;
use App\Enums\ProxyTypes;
use App\Exceptions\RateLimitException;
use App\Http\Controllers\Controller;
@@ -12,6 +13,7 @@ use App\Models\Team;
use App\Rules\ValidCloudInitYaml;
use App\Rules\ValidHostname;
use App\Services\HetznerService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use OpenApi\Attributes as OA;
@@ -668,6 +670,7 @@ class HetznerController extends Controller
'private_key_uuid' => ['type' => 'string', 'example' => 'xyz789', 'description' => 'Private key UUID'],
'enable_ipv4' => ['type' => 'boolean', 'example' => true, 'description' => 'Enable IPv4 (default: true)'],
'enable_ipv6' => ['type' => 'boolean', 'example' => true, 'description' => 'Enable IPv6 (default: true)'],
'enable_backups' => ['type' => 'boolean', 'example' => false, 'description' => 'Enable Hetzner server backups after creation (adds 20% to the monthly server fee)'],
'hetzner_ssh_key_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'Additional Hetzner SSH key IDs'],
'hetzner_firewall_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'Existing Hetzner firewall IDs to apply during server creation'],
'hetzner_network_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'Existing Hetzner network IDs to attach during server creation'],
@@ -728,6 +731,7 @@ class HetznerController extends Controller
'private_key_uuid',
'enable_ipv4',
'enable_ipv6',
'enable_backups',
'hetzner_ssh_key_ids',
'hetzner_firewall_ids',
'hetzner_network_ids',
@@ -741,7 +745,7 @@ class HetznerController extends Controller
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
if ($return instanceof JsonResponse) {
return $return;
}
@@ -755,6 +759,7 @@ class HetznerController extends Controller
'private_key_uuid' => 'required|string',
'enable_ipv4' => 'nullable|boolean',
'enable_ipv6' => 'nullable|boolean',
'enable_backups' => 'nullable|boolean',
'hetzner_ssh_key_ids' => 'nullable|array',
'hetzner_ssh_key_ids.*' => 'integer',
'hetzner_firewall_ids' => 'nullable|array',
@@ -796,6 +801,9 @@ class HetznerController extends Controller
if (is_null($request->enable_ipv6)) {
$request->offsetSet('enable_ipv6', true);
}
if (is_null($request->enable_backups)) {
$request->offsetSet('enable_backups', false);
}
if (is_null($request->hetzner_ssh_key_ids)) {
$request->offsetSet('hetzner_ssh_key_ids', []);
}
@@ -900,6 +908,10 @@ class HetznerController extends Controller
// Create server on Hetzner
$hetznerServer = $hetznerService->createServer($params);
if ($request->enable_backups) {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
}
// Determine IP address to use (prefer IPv4, fallback to IPv6)
$ipAddress = null;
if ($request->enable_ipv4 && isset($hetznerServer['public_net']['ipv4']['ip'])) {
@@ -930,7 +942,7 @@ class HetznerController extends Controller
// Validate server if requested
if ($request->instant_validate) {
\App\Actions\Server\ValidateServer::dispatch($server);
ValidateServer::dispatch($server);
}
return response()->json([
+28 -4
View File
@@ -72,6 +72,8 @@ class ByHetzner extends Component
public bool $enable_ipv6 = true;
public bool $enable_backups = false;
public ?string $cloud_init_script = null;
public bool $save_cloud_init_script = false;
@@ -116,6 +118,7 @@ class ByHetzner extends Component
{
$this->selected_token_id = null;
$this->current_step = 1;
$this->enable_backups = false;
$this->cloud_init_script = null;
$this->save_cloud_init_script = false;
$this->cloud_init_script_name = null;
@@ -177,6 +180,7 @@ class ByHetzner extends Component
'selectedHetznerNetworkIds.*' => 'integer',
'enable_ipv4' => 'required|boolean',
'enable_ipv6' => 'required|boolean',
'enable_backups' => 'required|boolean',
'cloud_init_script' => ['nullable', 'string', new ValidCloudInitYaml],
'save_cloud_init_script' => 'boolean',
'cloud_init_script_name' => 'nullable|string|max:255',
@@ -419,6 +423,23 @@ class ByHetzner extends Component
return '€'.number_format($price, 2);
}
public function getSelectedServerBackupSurchargeProperty(): ?string
{
if (! $this->selected_server_type) {
return null;
}
$serverType = collect($this->serverTypes)->firstWhere('name', $this->selected_server_type);
if (! $serverType || ! isset($serverType['prices'][0]['price_monthly']['gross'])) {
return null;
}
$price = (float) $serverType['prices'][0]['price_monthly']['gross'];
return '€'.number_format($price * 0.2, 2);
}
public function updatedSelectedLocation($value)
{
// Reset server type and image when location changes
@@ -461,10 +482,8 @@ class ByHetzner extends Component
$this->save_cloud_init_script = false;
}
private function createHetznerServer(string $token): array
private function createHetznerServer(HetznerService $hetznerService): array
{
$hetznerService = new HetznerService($token);
// Get the private key and extract public key
$privateKey = PrivateKey::ownedByCurrentTeam()->findOrFail($this->private_key_id);
@@ -564,9 +583,14 @@ class ByHetzner extends Component
}
$hetznerToken = $this->getHetznerToken();
$hetznerService = new HetznerService($hetznerToken);
// Create server on Hetzner
$hetznerServer = $this->createHetznerServer($hetznerToken);
$hetznerServer = $this->createHetznerServer($hetznerService);
if ($this->enable_backups) {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
}
// Determine IP address to use (prefer IPv4, fallback to IPv6)
$ipAddress = null;
+9 -1
View File
@@ -3,6 +3,7 @@
namespace App\Services;
use App\Exceptions\RateLimitException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
class HetznerService
@@ -24,7 +25,7 @@ class HetznerService
->timeout(30)
->retry(3, function (int $attempt, \Exception $exception) {
// Handle rate limiting (429 Too Many Requests)
if ($exception instanceof \Illuminate\Http\Client\RequestException) {
if ($exception instanceof RequestException) {
$response = $exception->response;
if ($response && $response->status() === 429) {
@@ -153,6 +154,13 @@ class HetznerService
return $response['server'] ?? [];
}
public function enableServerBackup(int $serverId): array
{
$response = $this->request('post', "/servers/{$serverId}/actions/enable_backup");
return $response['action'] ?? [];
}
public function getServer(int $serverId): array
{
$response = $this->request('get', "/servers/{$serverId}");