@@ -460,6 +460,195 @@ public function sshKeys(Request $request)
460460 }
461461 }
462462
463+ #[OA \Get(
464+ summary: 'Get Hetzner Firewalls ' ,
465+ description: 'Get all existing Hetzner firewalls for the current project. ' ,
466+ path: '/hetzner/firewalls ' ,
467+ operationId: 'get-hetzner-firewalls ' ,
468+ security: [
469+ ['bearerAuth ' => []],
470+ ],
471+ tags: ['Hetzner ' ],
472+ parameters: [
473+ new OA \Parameter (
474+ name: 'cloud_provider_token_uuid ' ,
475+ in: 'query ' ,
476+ required: false ,
477+ description: 'Cloud provider token UUID. Required if cloud_provider_token_id is not provided. ' ,
478+ schema: new OA \Schema (type: 'string ' )
479+ ),
480+ new OA \Parameter (
481+ name: 'cloud_provider_token_id ' ,
482+ in: 'query ' ,
483+ required: false ,
484+ deprecated: true ,
485+ description: 'Deprecated: Use cloud_provider_token_uuid instead. Cloud provider token UUID. ' ,
486+ schema: new OA \Schema (type: 'string ' )
487+ ),
488+ ],
489+ responses: [
490+ new OA \Response (
491+ response: 200 ,
492+ description: 'List of Hetzner firewalls. ' ,
493+ content: [
494+ new OA \MediaType (
495+ mediaType: 'application/json ' ,
496+ schema: new OA \Schema (
497+ type: 'array ' ,
498+ items: new OA \Items (
499+ type: 'object ' ,
500+ properties: [
501+ 'id ' => ['type ' => 'integer ' ],
502+ 'name ' => ['type ' => 'string ' ],
503+ ]
504+ )
505+ )
506+ ),
507+ ]),
508+ new OA \Response (
509+ response: 401 ,
510+ ref: '#/components/responses/401 ' ,
511+ ),
512+ new OA \Response (
513+ response: 404 ,
514+ ref: '#/components/responses/404 ' ,
515+ ),
516+ ]
517+ )]
518+ public function firewalls (Request $ request )
519+ {
520+ $ teamId = getTeamIdFromToken ();
521+ if (is_null ($ teamId )) {
522+ return invalidTokenResponse ();
523+ }
524+
525+ $ validator = customApiValidator ($ request ->all (), [
526+ 'cloud_provider_token_uuid ' => 'required_without:cloud_provider_token_id|string ' ,
527+ 'cloud_provider_token_id ' => 'required_without:cloud_provider_token_uuid|string ' ,
528+ ]);
529+
530+ if ($ validator ->fails ()) {
531+ return response ()->json ([
532+ 'message ' => 'Validation failed. ' ,
533+ 'errors ' => $ validator ->errors (),
534+ ], 422 );
535+ }
536+
537+ $ tokenUuid = $ this ->getCloudProviderTokenUuid ($ request );
538+ $ token = CloudProviderToken::whereTeamId ($ teamId )
539+ ->whereUuid ($ tokenUuid )
540+ ->where ('provider ' , 'hetzner ' )
541+ ->first ();
542+
543+ if (! $ token ) {
544+ return response ()->json (['message ' => 'Hetzner cloud provider token not found. ' ], 404 );
545+ }
546+ $ this ->authorize ('view ' , $ token );
547+
548+ try {
549+ $ hetznerService = new HetznerService ($ token ->token );
550+
551+ return response ()->json ($ hetznerService ->getFirewalls ());
552+ } catch (\Throwable $ e ) {
553+ return response ()->json (['message ' => 'Failed to fetch Hetzner firewalls. ' ], 500 );
554+ }
555+ }
556+
557+ #[OA \Get(
558+ summary: 'Get Hetzner Networks ' ,
559+ description: 'Get all existing Hetzner private networks for the current project. ' ,
560+ path: '/hetzner/networks ' ,
561+ operationId: 'get-hetzner-networks ' ,
562+ security: [
563+ ['bearerAuth ' => []],
564+ ],
565+ tags: ['Hetzner ' ],
566+ parameters: [
567+ new OA \Parameter (
568+ name: 'cloud_provider_token_uuid ' ,
569+ in: 'query ' ,
570+ required: false ,
571+ description: 'Cloud provider token UUID. Required if cloud_provider_token_id is not provided. ' ,
572+ schema: new OA \Schema (type: 'string ' )
573+ ),
574+ new OA \Parameter (
575+ name: 'cloud_provider_token_id ' ,
576+ in: 'query ' ,
577+ required: false ,
578+ deprecated: true ,
579+ description: 'Deprecated: Use cloud_provider_token_uuid instead. Cloud provider token UUID. ' ,
580+ schema: new OA \Schema (type: 'string ' )
581+ ),
582+ ],
583+ responses: [
584+ new OA \Response (
585+ response: 200 ,
586+ description: 'List of Hetzner networks. ' ,
587+ content: [
588+ new OA \MediaType (
589+ mediaType: 'application/json ' ,
590+ schema: new OA \Schema (
591+ type: 'array ' ,
592+ items: new OA \Items (
593+ type: 'object ' ,
594+ properties: [
595+ 'id ' => ['type ' => 'integer ' ],
596+ 'name ' => ['type ' => 'string ' ],
597+ 'ip_range ' => ['type ' => 'string ' ],
598+ ]
599+ )
600+ )
601+ ),
602+ ]),
603+ new OA \Response (
604+ response: 401 ,
605+ ref: '#/components/responses/401 ' ,
606+ ),
607+ new OA \Response (
608+ response: 404 ,
609+ ref: '#/components/responses/404 ' ,
610+ ),
611+ ]
612+ )]
613+ public function networks (Request $ request )
614+ {
615+ $ teamId = getTeamIdFromToken ();
616+ if (is_null ($ teamId )) {
617+ return invalidTokenResponse ();
618+ }
619+
620+ $ validator = customApiValidator ($ request ->all (), [
621+ 'cloud_provider_token_uuid ' => 'required_without:cloud_provider_token_id|string ' ,
622+ 'cloud_provider_token_id ' => 'required_without:cloud_provider_token_uuid|string ' ,
623+ ]);
624+
625+ if ($ validator ->fails ()) {
626+ return response ()->json ([
627+ 'message ' => 'Validation failed. ' ,
628+ 'errors ' => $ validator ->errors (),
629+ ], 422 );
630+ }
631+
632+ $ tokenUuid = $ this ->getCloudProviderTokenUuid ($ request );
633+ $ token = CloudProviderToken::whereTeamId ($ teamId )
634+ ->whereUuid ($ tokenUuid )
635+ ->where ('provider ' , 'hetzner ' )
636+ ->first ();
637+
638+ if (! $ token ) {
639+ return response ()->json (['message ' => 'Hetzner cloud provider token not found. ' ], 404 );
640+ }
641+ $ this ->authorize ('view ' , $ token );
642+
643+ try {
644+ $ hetznerService = new HetznerService ($ token ->token );
645+
646+ return response ()->json ($ hetznerService ->getNetworks ());
647+ } catch (\Throwable $ e ) {
648+ return response ()->json (['message ' => 'Failed to fetch Hetzner networks. ' ], 500 );
649+ }
650+ }
651+
463652 #[OA \Post(
464653 summary: 'Create Hetzner Server ' ,
465654 description: 'Create a new server on Hetzner and register it in Coolify. ' ,
@@ -487,7 +676,10 @@ public function sshKeys(Request $request)
487676 'private_key_uuid ' => ['type ' => 'string ' , 'example ' => 'xyz789 ' , 'description ' => 'Private key UUID ' ],
488677 'enable_ipv4 ' => ['type ' => 'boolean ' , 'example ' => true , 'description ' => 'Enable IPv4 (default: true) ' ],
489678 'enable_ipv6 ' => ['type ' => 'boolean ' , 'example ' => true , 'description ' => 'Enable IPv6 (default: true) ' ],
679+ 'enable_backups ' => ['type ' => 'boolean ' , 'example ' => false , 'description ' => 'Enable Hetzner server backups after creation (adds 20% to the monthly server fee) ' ],
490680 'hetzner_ssh_key_ids ' => ['type ' => 'array ' , 'items ' => ['type ' => 'integer ' ], 'description ' => 'Additional Hetzner SSH key IDs ' ],
681+ 'hetzner_firewall_ids ' => ['type ' => 'array ' , 'items ' => ['type ' => 'integer ' ], 'description ' => 'Existing Hetzner firewall IDs to apply during server creation ' ],
682+ 'hetzner_network_ids ' => ['type ' => 'array ' , 'items ' => ['type ' => 'integer ' ], 'description ' => 'Existing Hetzner network IDs to attach during server creation ' ],
491683 'cloud_init_script ' => ['type ' => 'string ' , 'description ' => 'Cloud-init YAML script (optional) ' ],
492684 'instant_validate ' => ['type ' => 'boolean ' , 'example ' => false , 'description ' => 'Validate server immediately after creation ' ],
493685 ],
@@ -545,7 +737,10 @@ public function createServer(Request $request)
545737 'private_key_uuid ' ,
546738 'enable_ipv4 ' ,
547739 'enable_ipv6 ' ,
740+ 'enable_backups ' ,
548741 'hetzner_ssh_key_ids ' ,
742+ 'hetzner_firewall_ids ' ,
743+ 'hetzner_network_ids ' ,
549744 'cloud_init_script ' ,
550745 'instant_validate ' ,
551746 ];
@@ -571,8 +766,13 @@ public function createServer(Request $request)
571766 'private_key_uuid ' => 'required|string ' ,
572767 'enable_ipv4 ' => 'nullable|boolean ' ,
573768 'enable_ipv6 ' => 'nullable|boolean ' ,
769+ 'enable_backups ' => 'nullable|boolean ' ,
574770 'hetzner_ssh_key_ids ' => 'nullable|array ' ,
575771 'hetzner_ssh_key_ids.* ' => 'integer ' ,
772+ 'hetzner_firewall_ids ' => 'nullable|array ' ,
773+ 'hetzner_firewall_ids.* ' => 'integer ' ,
774+ 'hetzner_network_ids ' => 'nullable|array ' ,
775+ 'hetzner_network_ids.* ' => 'integer ' ,
576776 'cloud_init_script ' => ['nullable ' , 'string ' , new ValidCloudInitYaml ],
577777 'instant_validate ' => 'nullable|boolean ' ,
578778 ]);
@@ -608,13 +808,32 @@ public function createServer(Request $request)
608808 if (is_null ($ request ->enable_ipv6 )) {
609809 $ request ->offsetSet ('enable_ipv6 ' , true );
610810 }
811+ if (is_null ($ request ->enable_backups )) {
812+ $ request ->offsetSet ('enable_backups ' , false );
813+ }
611814 if (is_null ($ request ->hetzner_ssh_key_ids )) {
612815 $ request ->offsetSet ('hetzner_ssh_key_ids ' , []);
613816 }
817+ if (is_null ($ request ->hetzner_firewall_ids )) {
818+ $ request ->offsetSet ('hetzner_firewall_ids ' , []);
819+ }
820+ if (is_null ($ request ->hetzner_network_ids )) {
821+ $ request ->offsetSet ('hetzner_network_ids ' , []);
822+ }
614823 if (is_null ($ request ->instant_validate )) {
615824 $ request ->offsetSet ('instant_validate ' , false );
616825 }
617826
827+ if (! $ request ->boolean ('enable_ipv4 ' ) && ! $ request ->boolean ('enable_ipv6 ' )) {
828+ return response ()->json ([
829+ 'message ' => 'Validation failed. ' ,
830+ 'errors ' => [
831+ 'enable_ipv4 ' => ['Enable at least one public IP protocol. ' ],
832+ 'enable_ipv6 ' => ['Enable at least one public IP protocol. ' ],
833+ ],
834+ ], 422 );
835+ }
836+
618837 // Validate cloud provider token
619838 $ tokenUuid = $ this ->getCloudProviderTokenUuid ($ request );
620839 $ token = CloudProviderToken::whereTeamId ($ teamId )
@@ -687,6 +906,18 @@ public function createServer(Request $request)
687906 ],
688907 ];
689908
909+ $ firewallIds = array_values (array_unique ($ request ->hetzner_firewall_ids ));
910+ if ($ firewallIds !== []) {
911+ $ params ['firewalls ' ] = array_map (function (int $ firewallId ): array {
912+ return ['firewall ' => $ firewallId ];
913+ }, $ firewallIds );
914+ }
915+
916+ $ networkIds = array_values (array_unique ($ request ->hetzner_network_ids ));
917+ if ($ networkIds !== []) {
918+ $ params ['networks ' ] = $ networkIds ;
919+ }
920+
690921 // Add cloud-init script if provided
691922 if (! empty ($ request ->cloud_init_script )) {
692923 $ params ['user_data ' ] = $ request ->cloud_init_script ;
@@ -723,6 +954,14 @@ public function createServer(Request $request)
723954 $ server ->proxy ->set ('type ' , ProxyTypes::TRAEFIK ->value );
724955 $ server ->save ();
725956
957+ if ($ request ->enable_backups ) {
958+ try {
959+ $ hetznerService ->enableServerBackup ((int ) $ hetznerServer ['id ' ]);
960+ } catch (\Throwable $ e ) {
961+ report ($ e );
962+ }
963+ }
964+
726965 // Validate server if requested
727966 if ($ request ->instant_validate ) {
728967 ValidateServer::dispatch ($ server );
0 commit comments