diff options
| author | Maxim Cournoyer <maxim@guixotic.coop> | 2026-04-30 15:28:44 +0900 |
|---|---|---|
| committer | Maxim Cournoyer <maxim@guixotic.coop> | 2026-05-23 13:21:16 +0900 |
| commit | 3777bf133e00d9ef685e463e51a1fb0350ffb056 (patch) | |
| tree | c7e79d5b89945e5bb146abc732517c4dcdf162dc /gnu/machine | |
| parent | 6e7d100ee39bdfdb2ad350633c34bcd2dc4ca73a (diff) | |
machine: hetzner: Fix initial deploy when ssh-key is #f.
This fixes a regression introduced in commit a2ef2bcbfd7 ("machine: hetzner:
Allow connections using ssh-agent"), where having no ssh-key would cause the
early Hetzner provisioning to fail due to the lack of SSH authentication.
* gnu/machine/hetzner.scm (%hetzner-ssh-key-file): New parameter.
(<hetzner-configuration>) [ssh-public-key]: Compute default according to the
value of the private ssh-key field. Introduce an indirection via...
(hetzner-configuration-ssh-public-key): ... this new procedure, to honor
%hetzner-ssh-key-file.
(hetzner-configuration-ssh-key): Likewise for the private ssh-key.
(hetzner-configuration-ssh-key-fingerprint): Rename to...
(public-key->fingerprint): ... this, taking just the public key as argument.
Update doc.
(hetzner-configuration-ssh-key-public): Rename to...
(public-key->string-with-type): ...this, for clarity, and accept just a
public-key object. Update doc.
(hetzner-machine-ssh-key): Exit early with #f when no ssh key is provided.
(temporary-ssh-key-file): New procedure.
(hetzner-machine-ssh-key-create): Assert a public key is defined. Adjust
calls to renamed procedures, and adjust for the `hetzner-api-server-create'
API change.
(hetzner-machine-enable-rescue-system): Refine doc and fix code path when
ssh-key is #f.
(hetzner-machine-rescue-install-os): Document.
(hetzner-machine-provision): Refine doc. Remove extraneous sleep and #:unwind
argument. Limit ssh-session dynamic scope to where it's needed.
(cleanup-temporary-ssh-key/maybe): New procedure.
(deploy-hetzner): Create and use a temporary SSH key when none is defined, and
clean it up when done or on errors.
* gnu/machine/hetzner/http.scm (hetzner-api-server-create): Make ssh-keys a
keyword argument, and fix execution when it's #f.
(hetzner-api-server-enable-rescue-system): Likewise.
* tests/machine/hetzner.scm ("deploy-machine-mock-with-unprovisioned-server"):
Adjust test.
* tests/machine/hetzner/http.scm (create-server)
("hetzner-api-server-create-unit")
("hetzner-api-server-enable-rescue-system-unit")
("hetzner-api-actions-integration")
("hetzner-api-server-enable-rescue-system-integration"): Likewise.
* doc/guix.texi (Invoking guix deploy): Precise what happens when 'ssh-key' is
#f in hetzner-configuration, and suggest declaratively authorizing your SSH
key.
Change-Id: I812b348fb553f3b5aebd0bf66850c6ecb9e06653
Diffstat (limited to 'gnu/machine')
| -rw-r--r-- | gnu/machine/hetzner.scm | 145 | ||||
| -rw-r--r-- | gnu/machine/hetzner/http.scm | 19 |
2 files changed, 118 insertions, 46 deletions
diff --git a/gnu/machine/hetzner.scm b/gnu/machine/hetzner.scm index b6a13ac8466..b4858fa3d26 100644 --- a/gnu/machine/hetzner.scm +++ b/gnu/machine/hetzner.scm | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | ;;; Copyright © 2024 Roman Scherer <roman@burningswell.com> | 2 | ;;; Copyright © 2024 Roman Scherer <roman@burningswell.com> |
| 3 | ;;; Copyright © 2025 Owen T. Heisler <writer@owenh.net> | 3 | ;;; Copyright © 2025 Owen T. Heisler <writer@owenh.net> |
| 4 | ;;; Copyright © 2025 Remco van 't Veer <remco@remworks.net> | 4 | ;;; Copyright © 2025 Remco van 't Veer <remco@remworks.net> |
| 5 | ;;; Copyright © 2026 Maxim Cournoyer <maxim@guixotic.coop> | ||
| 5 | ;;; | 6 | ;;; |
| 6 | ;;; This file is part of GNU Guix. | 7 | ;;; This file is part of GNU Guix. |
| 7 | ;;; | 8 | ;;; |
| @@ -56,6 +57,7 @@ | |||
| 56 | #:use-module (ice-9 string-fun) | 57 | #:use-module (ice-9 string-fun) |
| 57 | #:use-module (ice-9 textual-ports) | 58 | #:use-module (ice-9 textual-ports) |
| 58 | #:use-module (json) | 59 | #:use-module (json) |
| 60 | #:use-module ((rnrs base) #:select (assert)) | ||
| 59 | #:use-module (srfi srfi-1) | 61 | #:use-module (srfi srfi-1) |
| 60 | #:use-module (srfi srfi-2) | 62 | #:use-module (srfi srfi-2) |
| 61 | #:use-module (srfi srfi-34) | 63 | #:use-module (srfi srfi-34) |
| @@ -240,6 +242,11 @@ Have you run 'guix archive --generate-key'?") | |||
| 240 | ;;; Hetzner configuration. | 242 | ;;; Hetzner configuration. |
| 241 | ;;; | 243 | ;;; |
| 242 | 244 | ||
| 245 | ;;; This parameter overrides the value returned by | ||
| 246 | ;;; `hetzner-configuration-ssh-key'. | ||
| 247 | (define %hetzner-ssh-key-file | ||
| 248 | (make-parameter #f)) | ||
| 249 | |||
| 243 | (define-record-type* <hetzner-configuration> hetzner-configuration | 250 | (define-record-type* <hetzner-configuration> hetzner-configuration |
| 244 | make-hetzner-configuration hetzner-configuration? this-hetzner-configuration | 251 | make-hetzner-configuration hetzner-configuration? this-hetzner-configuration |
| 245 | (allow-downgrades? hetzner-configuration-allow-downgrades? ; boolean | 252 | (allow-downgrades? hetzner-configuration-allow-downgrades? ; boolean |
| @@ -262,28 +269,39 @@ Have you run 'guix archive --generate-key'?") | |||
| 262 | (default #t)) | 269 | (default #t)) |
| 263 | (ipv6 hetzner-configuration-ipv6 ; boolean | string | 270 | (ipv6 hetzner-configuration-ipv6 ; boolean | string |
| 264 | (default #t)) | 271 | (default #t)) |
| 265 | (ssh-public-key hetzner-configuration-ssh-public-key ; public-key | string | 272 | (ssh-public-key hetzner-configuration-ssh-public-key* ; #f | public-key | string |
| 266 | (thunked) | 273 | (thunked) |
| 267 | (default (public-key-from-file (hetzner-configuration-ssh-key this-hetzner-configuration))) | 274 | (default (and=> (hetzner-configuration-ssh-key |
| 268 | (sanitize | 275 | this-hetzner-configuration) |
| 269 | (lambda (value) | 276 | public-key-from-file)) |
| 270 | (if (string? value) (public-key-from-file value) value)))) | 277 | (sanitize (lambda (value) |
| 271 | (ssh-key hetzner-configuration-ssh-key | 278 | (if (string? value) |
| 272 | (default #f)) ; #f | string | 279 | (public-key-from-file value) |
| 273 | (user hetzner-configuration-user | 280 | value)))) |
| 274 | (default "root"))) ;string | 281 | (ssh-key hetzner-configuration-ssh-key* ; #f | string |
| 275 | 282 | (default #f)) | |
| 276 | (define (hetzner-configuration-ssh-key-fingerprint config) | 283 | (user hetzner-configuration-user ;string |
| 277 | "Return the SSH public key fingerprint of CONFIG as a string." | 284 | (default "root"))) |
| 278 | (and-let* ((pubkey (hetzner-configuration-ssh-public-key config)) | 285 | |
| 279 | (hash (get-public-key-hash pubkey 'md5))) | 286 | (define (hetzner-configuration-ssh-key config) |
| 287 | (or (%hetzner-ssh-key-file) | ||
| 288 | (hetzner-configuration-ssh-key* config))) | ||
| 289 | |||
| 290 | (define (hetzner-configuration-ssh-public-key config) | ||
| 291 | (or (and=> (%hetzner-ssh-key-file) public-key-from-file) | ||
| 292 | (hetzner-configuration-ssh-public-key* config))) | ||
| 293 | |||
| 294 | (define (public-key->fingerprint public-key) | ||
| 295 | "Return the SSH public key fingerprint of PUBLIC-KEY, a Guile-SSH public key | ||
| 296 | object, in the expected format for the Hetzner API." | ||
| 297 | (let ((hash (get-public-key-hash public-key 'md5))) | ||
| 280 | (bytevector->hex-string hash))) | 298 | (bytevector->hex-string hash))) |
| 281 | 299 | ||
| 282 | (define (hetzner-configuration-ssh-key-public config) | 300 | (define (public-key->string-with-type public-key) |
| 283 | "Return the SSH public key of CONFIG as a string." | 301 | "Return the SSH public key string of PUBLIC-KEY, a Guile-SSH public key |
| 284 | (let ((public-key (hetzner-configuration-ssh-public-key config))) | 302 | object, in the expected format for the Hetzner API." |
| 285 | (format #f "ssh-~a ~a" (get-key-type public-key) | 303 | (format #f "ssh-~a ~a" (get-key-type public-key) |
| 286 | (public-key->string public-key)))) | 304 | (public-key->string public-key))) |
| 287 | 305 | ||
| 288 | 306 | ||
| 289 | ;;; | 307 | ;;; |
| @@ -477,8 +495,9 @@ values: list of output lines returned by CMD and its exit code." | |||
| 477 | 495 | ||
| 478 | (define (hetzner-machine-ssh-key machine) | 496 | (define (hetzner-machine-ssh-key machine) |
| 479 | "Find the SSH key for MACHINE on the Hetzner API." | 497 | "Find the SSH key for MACHINE on the Hetzner API." |
| 480 | (let* ((config (machine-configuration machine)) | 498 | (and-let* ((config (machine-configuration machine)) |
| 481 | (expected (hetzner-configuration-ssh-key-fingerprint config))) | 499 | (public-key (hetzner-configuration-ssh-public-key config)) |
| 500 | (expected (public-key->fingerprint public-key))) | ||
| 482 | (find (lambda (ssh-key) | 501 | (find (lambda (ssh-key) |
| 483 | (equal? expected (hetzner-ssh-key-fingerprint ssh-key))) | 502 | (equal? expected (hetzner-ssh-key-fingerprint ssh-key))) |
| 484 | (hetzner-api-ssh-keys | 503 | (hetzner-api-ssh-keys |
| @@ -491,10 +510,12 @@ values: list of output lines returned by CMD and its exit code." | |||
| 491 | (format #t "creating ssh key for '~a'...\n" name) | 510 | (format #t "creating ssh key for '~a'...\n" name) |
| 492 | (let* ((config (machine-configuration machine)) | 511 | (let* ((config (machine-configuration machine)) |
| 493 | (api (hetzner-configuration-api config)) | 512 | (api (hetzner-configuration-api config)) |
| 513 | (pubkey (hetzner-configuration-ssh-public-key config)) | ||
| 514 | (_ (assert pubkey)) | ||
| 494 | (ssh-key (hetzner-api-ssh-key-create | 515 | (ssh-key (hetzner-api-ssh-key-create |
| 495 | (hetzner-configuration-api config) | 516 | api |
| 496 | (hetzner-configuration-ssh-key-fingerprint config) | 517 | (public-key->fingerprint pubkey) |
| 497 | (hetzner-configuration-ssh-key-public config) | 518 | (public-key->string-with-type pubkey) |
| 498 | #:labels (hetzner-configuration-labels config)))) | 519 | #:labels (hetzner-configuration-labels config)))) |
| 499 | (format #t "successfully created ssh key for '~a'\n" name) | 520 | (format #t "successfully created ssh key for '~a'\n" name) |
| 500 | ssh-key))) | 521 | ssh-key))) |
| @@ -520,6 +541,16 @@ values: list of output lines returned by CMD and its exit code." | |||
| 520 | (formatted-message (G_ "primary ip '~a' does not exist.") | 541 | (formatted-message (G_ "primary ip '~a' does not exist.") |
| 521 | name)))) | 542 | name)))) |
| 522 | 543 | ||
| 544 | (define temporary-ssh-key-file | ||
| 545 | (let ((ssh-key-file (port-filename (mkstemp "/tmp/hetzner-key-XXXXXX"))) | ||
| 546 | (ssh-key #f)) | ||
| 547 | (lambda () | ||
| 548 | "Return a dynamically created, temporary private SSH key." | ||
| 549 | (unless ssh-key | ||
| 550 | (set! ssh-key (make-keypair 'ed25519)) | ||
| 551 | (private-key-to-file ssh-key ssh-key-file)) | ||
| 552 | ssh-key-file))) | ||
| 553 | |||
| 523 | (define (hetzner-machine-create-server machine) | 554 | (define (hetzner-machine-create-server machine) |
| 524 | "Create the Hetzner server for MACHINE." | 555 | "Create the Hetzner server for MACHINE." |
| 525 | (let* ((config (machine-configuration machine)) | 556 | (let* ((config (machine-configuration machine)) |
| @@ -527,13 +558,14 @@ values: list of output lines returned by CMD and its exit code." | |||
| 527 | (server-type (hetzner-configuration-server-type config))) | 558 | (server-type (hetzner-configuration-server-type config))) |
| 528 | (format #t "creating '~a' server for '~a'...\n" server-type name) | 559 | (format #t "creating '~a' server for '~a'...\n" server-type name) |
| 529 | (let* ((ssh-key (hetzner-machine-ssh-key machine)) | 560 | (let* ((ssh-key (hetzner-machine-ssh-key machine)) |
| 561 | (_ (assert ssh-key)) ;required during provisioning | ||
| 530 | (ipv4 (hetzner-configuration-ipv4 config)) | 562 | (ipv4 (hetzner-configuration-ipv4 config)) |
| 531 | (ipv6 (hetzner-configuration-ipv6 config)) | 563 | (ipv6 (hetzner-configuration-ipv6 config)) |
| 532 | (api (hetzner-configuration-api config)) | 564 | (api (hetzner-configuration-api config)) |
| 533 | (server (hetzner-api-server-create | 565 | (server (hetzner-api-server-create |
| 534 | api | 566 | api |
| 535 | (machine-display-name machine) | 567 | (machine-display-name machine) |
| 536 | (list ssh-key) | 568 | #:ssh-keys (list ssh-key) |
| 537 | #:ipv4 (if (string? ipv4) | 569 | #:ipv4 (if (string? ipv4) |
| 538 | (hetzner-primary-ip-id (hetzner-resolve-ip api ipv4)) | 570 | (hetzner-primary-ip-id (hetzner-resolve-ip api ipv4)) |
| 539 | ipv4) | 571 | ipv4) |
| @@ -581,13 +613,15 @@ values: list of output lines returned by CMD and its exit code." | |||
| 581 | (write-known-host! ssh-session))) | 613 | (write-known-host! ssh-session))) |
| 582 | 614 | ||
| 583 | (define (hetzner-machine-enable-rescue-system machine server) | 615 | (define (hetzner-machine-enable-rescue-system machine server) |
| 584 | "Enable the rescue system on the Hetzner SERVER for MACHINE." | 616 | "Enable the rescue system on the Hetzner SERVER for MACHINE, provisioning it |
| 617 | with the configured SSH key, or a dynamically created temporary one." | ||
| 585 | (let* ((name (machine-display-name machine)) | 618 | (let* ((name (machine-display-name machine)) |
| 586 | (config (machine-configuration machine)) | 619 | (config (machine-configuration machine)) |
| 587 | (api (hetzner-configuration-api config)) | 620 | (api (hetzner-configuration-api config)) |
| 588 | (ssh-keys (list (hetzner-machine-ssh-key machine)))) | 621 | (ssh-keys (and=> (hetzner-machine-ssh-key machine) list))) |
| 589 | (format #t "enabling rescue system on '~a'...\n" name) | 622 | (format #t "enabling rescue system on '~a'...\n" name) |
| 590 | (let ((action (hetzner-api-server-enable-rescue-system api server ssh-keys))) | 623 | (let ((action (hetzner-api-server-enable-rescue-system |
| 624 | api server #:ssh-keys ssh-keys))) | ||
| 591 | (format #t "successfully enabled rescue system on '~a'\n" name) | 625 | (format #t "successfully enabled rescue system on '~a'\n" name) |
| 592 | action))) | 626 | action))) |
| 593 | 627 | ||
| @@ -623,6 +657,7 @@ values: list of output lines returned by CMD and its exit code." | |||
| 623 | (set! hetzner-machine-ssh-run-script hetzner-machine-ssh-run-script) | 657 | (set! hetzner-machine-ssh-run-script hetzner-machine-ssh-run-script) |
| 624 | 658 | ||
| 625 | (define (hetzner-machine-rescue-install-os machine ssh-session server) | 659 | (define (hetzner-machine-rescue-install-os machine ssh-session server) |
| 660 | "Init a minimal Guix System to bootstrap from." | ||
| 626 | (let ((name (machine-display-name machine)) | 661 | (let ((name (machine-display-name machine)) |
| 627 | (os (hetzner-machine-bootstrap-os-form machine server))) | 662 | (os (hetzner-machine-bootstrap-os-form machine server))) |
| 628 | (format #t "installing guix operating system on '~a'...\n" name) | 663 | (format #t "installing guix operating system on '~a'...\n" name) |
| @@ -719,7 +754,8 @@ apt-get install cloud-initramfs-growroot uidmap --assume-yes")) | |||
| 719 | action))) | 754 | action))) |
| 720 | 755 | ||
| 721 | (define (hetzner-machine-provision machine) | 756 | (define (hetzner-machine-provision machine) |
| 722 | "Provision a server for MACHINE on the Hetzner Cloud service." | 757 | "Provision a Guix System bootstrap server for MACHINE on the Hetzner Cloud |
| 758 | service." | ||
| 723 | (with-exception-handler | 759 | (with-exception-handler |
| 724 | (lambda (exception) | 760 | (lambda (exception) |
| 725 | (let ((config (machine-configuration machine)) | 761 | (let ((config (machine-configuration machine)) |
| @@ -734,12 +770,10 @@ apt-get install cloud-initramfs-growroot uidmap --assume-yes")) | |||
| 734 | (let ((ssh-session (hetzner-machine-wait-for-ssh machine server))) | 770 | (let ((ssh-session (hetzner-machine-wait-for-ssh machine server))) |
| 735 | (hetzner-machine-rescue-install-packages machine ssh-session) | 771 | (hetzner-machine-rescue-install-packages machine ssh-session) |
| 736 | (hetzner-machine-rescue-partition machine ssh-session) | 772 | (hetzner-machine-rescue-partition machine ssh-session) |
| 737 | (hetzner-machine-rescue-install-os machine ssh-session server) | 773 | (hetzner-machine-rescue-install-os machine ssh-session server)) |
| 738 | (hetzner-machine-reboot machine server) | 774 | (hetzner-machine-reboot machine server) |
| 739 | (sleep 5) | 775 | (hetzner-machine-authenticate-host machine server) |
| 740 | (hetzner-machine-authenticate-host machine server) | 776 | server)))) |
| 741 | server))) | ||
| 742 | #:unwind? #t)) | ||
| 743 | 777 | ||
| 744 | (define (machine-not-provisioned machine) | 778 | (define (machine-not-provisioned machine) |
| 745 | (formatted-message | 779 | (formatted-message |
| @@ -765,16 +799,47 @@ an environment type of 'hetzner-environment-type'." | |||
| 765 | ;;; System deployment. | 799 | ;;; System deployment. |
| 766 | ;;; | 800 | ;;; |
| 767 | 801 | ||
| 802 | (define (cleanup-temporary-ssh-key/maybe api ssh-key) | ||
| 803 | "Delete the temporary SSH private key file from the file system and the | ||
| 804 | Hetzner API, if one was created used." | ||
| 805 | (when (%hetzner-ssh-key-file) | ||
| 806 | (format #t "cleaning up temporary ssh key~%") | ||
| 807 | (delete-file (%hetzner-ssh-key-file)) | ||
| 808 | (%hetzner-ssh-key-file #f) | ||
| 809 | (hetzner-api-ssh-key-delete api ssh-key))) | ||
| 810 | |||
| 768 | (define (deploy-hetzner machine) | 811 | (define (deploy-hetzner machine) |
| 769 | "Internal implementation of 'deploy-machine' for 'machine' instances with an | 812 | "Internal implementation of 'deploy-machine' for 'machine' instances with an |
| 770 | environment type of 'hetzner-environment-type'." | 813 | environment type of 'hetzner-environment-type'." |
| 771 | (hetzner-machine-validate machine) | 814 | (hetzner-machine-validate machine) |
| 772 | (unless (hetzner-machine-ssh-key machine) | ||
| 773 | (hetzner-machine-ssh-key-create machine)) | ||
| 774 | (let ((server (or (hetzner-machine-server machine) | ||
| 775 | (hetzner-machine-provision machine)))) | ||
| 776 | (deploy-machine (hetzner-machine-delegate machine server)))) | ||
| 777 | 815 | ||
| 816 | (define server (hetzner-machine-server machine)) | ||
| 817 | (define config (machine-configuration machine)) | ||
| 818 | (define api (hetzner-configuration-api config)) | ||
| 819 | (define ssh-key #f) ;hetzner API key | ||
| 820 | |||
| 821 | ;; Provisioning requires uploading an SSH key via the Hetzner API to | ||
| 822 | ;; authenticate non-interactively with the newly created server. | ||
| 823 | (unless server ;provisioning? | ||
| 824 | (unless (hetzner-configuration-ssh-public-key config) | ||
| 825 | (format #t "using a temporary ssh key for provisioning~%") | ||
| 826 | (%hetzner-ssh-key-file (temporary-ssh-key-file))) | ||
| 827 | (set! ssh-key (or (hetzner-machine-ssh-key machine) | ||
| 828 | (hetzner-machine-ssh-key-create machine)))) | ||
| 829 | |||
| 830 | ;; Cleaning-up here is a bit tricky, as `deploy-machine' returns a monadic | ||
| 831 | ;; value immediately, to be executed later; this means the clean-up must be | ||
| 832 | ;; delayed until monadic evaluation time. | ||
| 833 | (let* ((server (or server (hetzner-machine-provision machine))) | ||
| 834 | (delegate-machine (hetzner-machine-delegate machine server))) | ||
| 835 | ;; FIXME: The clean-up in the guard does not run even when | ||
| 836 | ;; `deploy-machine' fails. | ||
| 837 | (guard (c (#t (cleanup-temporary-ssh-key/maybe api ssh-key) | ||
| 838 | (raise c))) | ||
| 839 | (mlet %store-monad | ||
| 840 | ((value (deploy-machine delegate-machine))) | ||
| 841 | (cleanup-temporary-ssh-key/maybe api ssh-key) | ||
| 842 | (return value))))) | ||
| 778 | 843 | ||
| 779 | 844 | ||
| 780 | ;;; | 845 | ;;; |
diff --git a/gnu/machine/hetzner/http.scm b/gnu/machine/hetzner/http.scm index bd12e8ee5eb..4f81b27ca92 100644 --- a/gnu/machine/hetzner/http.scm +++ b/gnu/machine/hetzner/http.scm | |||
| @@ -598,8 +598,8 @@ | |||
| 598 | (apply hetzner-api-list api "/locations" "locations" json->hetzner-location options)) | 598 | (apply hetzner-api-list api "/locations" "locations" json->hetzner-location options)) |
| 599 | 599 | ||
| 600 | (define* (hetzner-api-server-create | 600 | (define* (hetzner-api-server-create |
| 601 | api name ssh-keys | 601 | api name #:key |
| 602 | #:key | 602 | ssh-keys |
| 603 | (ipv4 #f) | 603 | (ipv4 #f) |
| 604 | (ipv6 #f) | 604 | (ipv6 #f) |
| 605 | (image %hetzner-default-server-image) | 605 | (image %hetzner-default-server-image) |
| @@ -620,7 +620,11 @@ | |||
| 620 | ,@(if (integer? ipv6) `(("ipv6" . ,ipv6)) '()))) | 620 | ,@(if (integer? ipv6) `(("ipv6" . ,ipv6)) '()))) |
| 621 | ("location" . ,location) | 621 | ("location" . ,location) |
| 622 | ("server_type" . ,server-type) | 622 | ("server_type" . ,server-type) |
| 623 | ("ssh_keys" . ,(apply vector (map hetzner-ssh-key-id ssh-keys))) | 623 | ,@(if ssh-keys |
| 624 | `(("ssh_keys" . | ||
| 625 | ,(apply vector (map hetzner-ssh-key-id | ||
| 626 | ssh-keys)))) | ||
| 627 | '()) | ||
| 624 | ("start_after_create" . ,start-after-create?))))) | 628 | ("start_after_create" . ,start-after-create?))))) |
| 625 | (hetzner-api-action-wait api (hetzner-api-body-action body)) | 629 | (hetzner-api-action-wait api (hetzner-api-body-action body)) |
| 626 | (json->hetzner-server (assoc-ref body "server")))) | 630 | (json->hetzner-server (assoc-ref body "server")))) |
| @@ -631,12 +635,15 @@ | |||
| 631 | (hetzner-api-action-wait api (hetzner-api-body-action body)))) | 635 | (hetzner-api-action-wait api (hetzner-api-body-action body)))) |
| 632 | 636 | ||
| 633 | (define* (hetzner-api-server-enable-rescue-system | 637 | (define* (hetzner-api-server-enable-rescue-system |
| 634 | api server ssh-keys #:key (type "linux64")) | 638 | api server #:key ssh-keys (type "linux64")) |
| 635 | "Enable the rescue system for SERVER with the Hetzner API." | 639 | "Enable the rescue system for SERVER with the Hetzner API." |
| 636 | (let* ((ssh-keys (apply vector (map hetzner-ssh-key-id ssh-keys))) | 640 | (let* ((ssh-keys (and ssh-keys |
| 641 | (apply vector (map hetzner-ssh-key-id ssh-keys)))) | ||
| 637 | (body (hetzner-api-post | 642 | (body (hetzner-api-post |
| 638 | api (hetzner-server-path server "/actions/enable_rescue") | 643 | api (hetzner-server-path server "/actions/enable_rescue") |
| 639 | #:body `(("ssh_keys" . ,ssh-keys) | 644 | #:body `(,@(if ssh-keys |
| 645 | `(("ssh_keys" . ,ssh-keys)) | ||
| 646 | '()) | ||
| 640 | ("type" . ,type))))) | 647 | ("type" . ,type))))) |
| 641 | (hetzner-api-action-wait api (hetzner-api-body-action body)))) | 648 | (hetzner-api-action-wait api (hetzner-api-body-action body)))) |
| 642 | 649 | ||
