@@ -20,9 +20,9 @@ impl Default for ServerConfig {
2020 fn default ( ) -> Self {
2121 Self {
2222 name : "ffmpeg-worker" . to_string ( ) ,
23- server_type : "cpx21 " . to_string ( ) , // 4 CPU, 8 GB RAM
23+ server_type : "cpx11 " . to_string ( ) , // 4 CPU, 8 GB RAM
2424 image : "ubuntu-24.04" . to_string ( ) ,
25- location : "fsn1 " . to_string ( ) , // Falkenstein
25+ location : "nbg1 " . to_string ( ) , // Falkenstein
2626 ssh_keys : vec ! [ ] ,
2727 user_data : String :: new ( ) ,
2828 labels : vec ! [ ( "worker" . to_string( ) , "ffmpeg-gpc" . to_string( ) ) ] ,
@@ -53,30 +53,39 @@ pub struct Ipv4 {
5353 pub ip : String ,
5454}
5555
56+ #[ derive( Debug ) ]
57+ pub struct ServerType {
58+ pub name : String ,
59+ pub cores : u32 ,
60+ pub memory : u32 ,
61+ pub disk : u32 ,
62+ pub locations : Vec < String > ,
63+ }
64+
65+ #[ derive( Debug ) ]
66+ pub struct Datacenter {
67+ pub name : String ,
68+ pub location : String ,
69+ pub server_types : Vec < String > ,
70+ }
71+
5672#[ derive( Debug , Serialize ) ]
5773struct CreateServerRequest {
5874 name : String ,
5975 server_type : String ,
6076 image : String ,
61- location : String ,
77+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
78+ location : Option < String > ,
79+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
6280 ssh_keys : Option < Vec < String > > ,
81+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
6382 user_data : Option < String > ,
64- labels : Option < Vec < Label > > ,
65- }
66-
67- #[ derive( Debug , Serialize , Deserialize ) ]
68- struct Label {
69- key : String ,
70- value : String ,
83+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
84+ labels : Option < std:: collections:: HashMap < String , String > > ,
7185}
7286
7387#[ derive( Debug , Deserialize ) ]
7488struct CreateServerResponse {
75- server : ServerData ,
76- }
77-
78- #[ derive( Debug , Deserialize ) ]
79- struct ServerData {
8089 server : Server ,
8190}
8291
@@ -109,16 +118,7 @@ impl HetznerClient {
109118 let labels = if config. labels . is_empty ( ) {
110119 None
111120 } else {
112- Some (
113- config
114- . labels
115- . iter ( )
116- . map ( |( k, v) | Label {
117- key : k. clone ( ) ,
118- value : v. clone ( ) ,
119- } )
120- . collect ( ) ,
121- )
121+ Some ( config. labels . iter ( ) . cloned ( ) . collect ( ) )
122122 } ;
123123
124124 let ssh_keys = if config. ssh_keys . is_empty ( ) {
@@ -133,17 +133,24 @@ impl HetznerClient {
133133 Some ( config. user_data . clone ( ) )
134134 } ;
135135
136+ let location = if config. location . is_empty ( ) {
137+ None
138+ } else {
139+ Some ( config. location . clone ( ) )
140+ } ;
141+
136142 let payload = CreateServerRequest {
137143 name : config. name . clone ( ) ,
138144 server_type : config. server_type . clone ( ) ,
139145 image : config. image . clone ( ) ,
140- location : config . location . clone ( ) ,
146+ location,
141147 ssh_keys,
142148 user_data,
143149 labels,
144150 } ;
145151
146- debug ! ( "Creating server: {}" , config. name) ;
152+ debug ! ( "Creating server: {} with type: {}, location: {:?}" , config. name, config. server_type, & payload. location) ;
153+ debug ! ( "Request payload: {:?}" , serde_json:: to_string( & payload) ) ;
147154
148155 let response = self
149156 . client
@@ -173,10 +180,10 @@ impl HetznerClient {
173180
174181 info ! (
175182 "Server created: {} (ID: {}, IP: {})" ,
176- result. server. server . name, result. server. server . id, result. server . server. public_net. ipv4. ip
183+ result. server. name, result. server. id, result. server. public_net. ipv4. ip
177184 ) ;
178185
179- Ok ( result. server . server )
186+ Ok ( result. server )
180187 }
181188
182189 pub async fn delete_server ( & self , id : u64 ) -> Result < ( ) > {
@@ -231,15 +238,136 @@ impl HetznerClient {
231238
232239 #[ derive( Debug , Deserialize ) ]
233240 struct ListServersResponse {
234- servers : Vec < ServerData > ,
241+ servers : Vec < Server > ,
235242 }
236243
237244 let result: ListServersResponse = response
238245 . json ( )
239246 . await
240247 . map_err ( |e| anyhow:: anyhow!( "Failed to parse response: {}" , e) ) ?;
241248
242- Ok ( result. servers . into_iter ( ) . map ( |s| s. server ) . collect ( ) )
249+ Ok ( result. servers )
250+ }
251+
252+ pub async fn list_server_types ( & self ) -> Result < Vec < ServerType > > {
253+ let url = format ! ( "{}/server_types" , HETZNER_API_BASE ) ;
254+
255+ let response = self
256+ . client
257+ . get ( & url)
258+ . header ( AUTHORIZATION , format ! ( "Bearer {}" , self . api_token) )
259+ . send ( )
260+ . await
261+ . map_err ( |e| anyhow:: anyhow!( "Failed to send request: {}" , e) ) ?;
262+
263+ let status = response. status ( ) ;
264+
265+ if !status. is_success ( ) {
266+ let error_text = response. text ( ) . await . unwrap_or_default ( ) ;
267+ return Err ( anyhow:: anyhow!(
268+ "Failed to list server types: {} - {}" ,
269+ status,
270+ error_text
271+ ) ) ;
272+ }
273+
274+ #[ derive( Debug , Deserialize ) ]
275+ struct ServerTypeResponse {
276+ name : String ,
277+ cores : u32 ,
278+ memory : f64 ,
279+ disk : u32 ,
280+ prices : Vec < PriceInfo > ,
281+ }
282+
283+ #[ derive( Debug , Deserialize ) ]
284+ struct PriceInfo {
285+ location : String ,
286+ }
287+
288+ #[ derive( Debug , Deserialize ) ]
289+ struct ListServerTypesResponse {
290+ server_types : Vec < ServerTypeResponse > ,
291+ }
292+
293+ let result: ListServerTypesResponse = response
294+ . json ( )
295+ . await
296+ . map_err ( |e| anyhow:: anyhow!( "Failed to parse response: {}" , e) ) ?;
297+
298+ Ok ( result
299+ . server_types
300+ . into_iter ( )
301+ . map ( |st| ServerType {
302+ name : st. name ,
303+ cores : st. cores ,
304+ memory : st. memory as u32 ,
305+ disk : st. disk ,
306+ locations : st. prices . into_iter ( ) . map ( |p| p. location ) . collect ( ) ,
307+ } )
308+ . collect ( ) )
309+ }
310+
311+ pub async fn list_datacenters ( & self ) -> Result < Vec < Datacenter > > {
312+ let url = format ! ( "{}/datacenters" , HETZNER_API_BASE ) ;
313+
314+ let response = self
315+ . client
316+ . get ( & url)
317+ . header ( AUTHORIZATION , format ! ( "Bearer {}" , self . api_token) )
318+ . send ( )
319+ . await
320+ . map_err ( |e| anyhow:: anyhow!( "Failed to send request: {}" , e) ) ?;
321+
322+ let status = response. status ( ) ;
323+
324+ if !status. is_success ( ) {
325+ let error_text = response. text ( ) . await . unwrap_or_default ( ) ;
326+ return Err ( anyhow:: anyhow!(
327+ "Failed to list datacenters: {} - {}" ,
328+ status,
329+ error_text
330+ ) ) ;
331+ }
332+
333+ #[ derive( Debug , Deserialize ) ]
334+ struct Location {
335+ name : String ,
336+ }
337+
338+ #[ derive( Debug , Deserialize ) ]
339+ struct ServerTypesAvailable {
340+ available : Vec < u64 > ,
341+ available_for_migration : Vec < u64 > ,
342+ supported : Vec < u64 > ,
343+ }
344+
345+ #[ derive( Debug , Deserialize ) ]
346+ struct DatacenterResponse {
347+ name : String ,
348+ location : Location ,
349+ server_types : ServerTypesAvailable ,
350+ }
351+
352+ #[ derive( Debug , Deserialize ) ]
353+ struct ListDatacentersResponse {
354+ datacenters : Vec < DatacenterResponse > ,
355+ }
356+
357+ let result: ListDatacentersResponse = response
358+ . json ( )
359+ . await
360+ . map_err ( |e| anyhow:: anyhow!( "Failed to parse response: {}" , e) ) ?;
361+
362+ Ok ( result
363+ . datacenters
364+ . into_iter ( )
365+ . map ( |dc| Datacenter {
366+ name : dc. name ,
367+ location : dc. location . name ,
368+ server_types : dc. server_types . available . iter ( ) . map ( |id| id. to_string ( ) ) . collect ( ) ,
369+ } )
370+ . collect ( ) )
243371 }
244372
245373 pub async fn get_server ( & self , id : u64 ) -> Result < Server > {
@@ -264,7 +392,12 @@ impl HetznerClient {
264392 ) ) ;
265393 }
266394
267- let result: ServerData = response
395+ #[ derive( Debug , Deserialize ) ]
396+ struct GetServerResponse {
397+ server : Server ,
398+ }
399+
400+ let result: GetServerResponse = response
268401 . json ( )
269402 . await
270403 . map_err ( |e| anyhow:: anyhow!( "Failed to parse response: {}" , e) ) ?;
@@ -340,9 +473,9 @@ pub async fn provision_worker(
340473
341474 let config = ServerConfig {
342475 name,
343- server_type : "cpx21 " . to_string ( ) ,
476+ server_type : "ccx23 " . to_string ( ) , // 4 dedicated vCPUs, 16GB RAM
344477 image : "ubuntu-24.04" . to_string ( ) ,
345- location : "fsn1 " . to_string ( ) ,
478+ location : "nbg1 " . to_string ( ) , // Nuremberg, Germany
346479 user_data,
347480 ..Default :: default ( )
348481 } ;
0 commit comments