Subject: Re: [bug#75144] [PATCH] machine: Implement 'hetzner-environment-type'.

From: Roman Scherer <[email-masked]>
Date: Fri, 14 Mar 2025 13:28:45 +0100

Sergey Trofimov <[email-masked]> writes:

Hi Sergey,

thanks for trying this out and your feedback.

> Hi Roman,
>
> Roman Scherer <[email-masked]> writes:
>
> > * gnu/machine/hetzner.scm: New file.
> > * gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
> > * guix/ssh.scm (open-ssh-session): Add stricthostkeycheck option.
> > * doc/guix.texi (Invoking guix deploy): Add documentation for
> > 'hetzner-configuration'.
> >
>
> That's a very welcome change and I'm glad it is merged already. I've
> given it a try and stumbled upon some errors. See them below.
>
> I started with the minimal configuration similar to the example from docs.
> --8<---------------cut here---------------start------------->8---
> (hetzner-configuration
>     (server-type "cax11")
>     (location "hel1")
>     (ssh-key ".../id_rsa"))
> --8<---------------cut here---------------end--------------->8---
>
> Deploying it worked only half-way - the server got created, but
> deploying actual OS failed due to my host (x86_64) not able to build
> derivations for aarch64-linux.

You need some way to be able to build for the target
architecture. qemu-binfmt-service-type is one solution to it, I
personally use offloading, since I have machines with different
architectures available.

> This one I fixed by adding `qemu-binfmt-service-type` to my system config.
> Second deployment attempt picked up the work (that's nice!) and started
> building the derivations. After about 40 minutes I'd aborted it,
> although I'm pretty sure it would've completed successfully. I just
> didn't want to wait too long.

I also have seen these long deploy times. You probably run into a
situation where substitutes were not available for the thing you are
deploying. It's a know issue with aarch64.

> Next I've addded `(build-locally? #f)` to the VM config and repeated the
> deployment. This one progressed much quicker, however it failed while
> building `linux-modules`:

I usually had the best experience with build-locally? set to #t, since
for further deploys I have the build artifacts in my local store, and
not have to build them again and again on the servers I'm deploying to.

> --8<---------------cut here---------------start------------->8---
> building path(s) `/gnu/store/lsa1716vbccxf9flpnzbfqzbm9rh4ljl-linux-modules'
> Backtrace:
>           18 (primitive-load "/gnu/store/mfk843zl41s21banhzwkyfdxapa?")
> In ice-9/eval.scm:
>     619:8 17 (_ #f)
>    626:19 16 (_ #<directory (guile-user) fffff771ec80>)
>    293:34 15 (_ #(#<directory (guile-user) fffff771ec80> #<procedu?>))
> In srfi/srfi-1.scm:
>    586:29 14 (map1 _)
>    586:29 13 (map1 _)
>    586:29 12 (map1 _)
>    586:29 11 (map1 _)
>    586:29 10 (map1 _)
>    586:29  9 (map1 _)
>    586:29  8 (map1 _)
>    586:29  7 (map1 _)
>    586:29  6 (map1 _)
>    586:29  5 (map1 _)
>    586:29  4 (map1 _)
>    586:29  3 (map1 _)
>    586:29  2 (map1 _)
>    586:17  1 (map1 ("pata_acpi" "pata_atiixp" "isci" "virtio_pci" # ?))
> In gnu/build/linux-modules.scm:
>     278:5  0 (_)
>
> gnu/build/linux-modules.scm:278:5: kernel module not found "pata_acpi" "/gnu/store/nh5icvr5qvlaq1y54gpkqndy0rv2cq9r-linux-libre-6.13.6/lib/modules"
> --8<---------------cut here---------------end--------------->8---
>
> This seem to be caused by `deploy` not supporting `--target` parameter.
> Adding these looked simple and I've jotted a small patch:
>

Nice! Do you plan to submit this as a patch, once you got it working?

> --8<---------------cut here---------------start------------->8---
> From 0d438d2fadc95fbe2eca73fc3c7f4278d58829d7 Mon Sep 17 00:00:00 2001
> Message-ID: <[email-masked]>
> From: Sergey Trofimov <[email-masked]>
> Subject: [PATCH] Support --target and --system in guix deploy
>
> ---
>  guix/scripts/deploy.scm | 28 +++++++++++++++++++---------
>  1 file changed, 19 insertions(+), 9 deletions(-)
>
> diff --git a/guix/scripts/deploy.scm b/guix/scripts/deploy.scm
> index e2ef0006e0..5b6c6b8e79 100644
> --- a/guix/scripts/deploy.scm
> +++ b/guix/scripts/deploy.scm
> @@ -26,6 +26,7 @@ (define-module (guix scripts deploy)
>    #:use-module (guix scripts)
>    #:use-module (guix scripts build)
>    #:use-module (guix store)
> +  #:use-module (guix utils)
>    #:use-module (guix gexp)
>    #:use-module (guix ui)
>    #:use-module ((guix status) #:select (with-status-verbosity))
> @@ -93,21 +94,22 @@ (define %options
>           (option '(#\x "execute") #f #f
>                   (lambda (opt name arg result)
>                     (alist-cons 'execute-command? #t result)))
> -         (option '(#\s "system") #t #f
> -                 (lambda (opt name arg result)
> -                   (alist-cons 'system arg
> -                               (alist-delete 'system result eq?))))
>           (option '(#\v "verbosity") #t #f
>                   (lambda (opt name arg result)
>                     (let ((level (string->number* arg)))
>                       (alist-cons 'verbosity level
>                                   (alist-delete 'verbosity result)))))
>
> -         %standard-build-options))
> +         (append
> +          %standard-build-options
> +          %standard-native-build-options
> +          %standard-cross-build-options)))
>
>  (define %default-options
>    ;; Alist of default option values.
>    `((verbosity . 1)
> +    (system . ,(%current-system))
> +    (target . #f)
>      (debug . 0)
>      (graft? . #t)
>      (substitutes? . #t)
> @@ -186,9 +188,13 @@ (define (deploy-machine* store machine)
>              (when (deploy-error-should-roll-back c)
>                (info (G_ "rolling back ~a...~%")
>                      (machine-display-name machine))
> -              (run-with-store store (roll-back-machine machine)))
> +              (run-with-store store (roll-back-machine machine)
> +                              #:system (%current-system)
> +                              #:target (%current-target-system)))
>              (apply throw (deploy-error-captured-args c))))
> -      (run-with-store store (deploy-machine machine))
> +      (run-with-store store (deploy-machine machine)
> +           #:system (%current-system)
> +           #:target (%current-target-system))
>
>      (info (G_ "successfully deployed ~a~%")
>            (machine-display-name machine))))
> @@ -266,7 +272,9 @@ (define (invoke-command store machine command)
>                 (loop (cons line lines))))))))
>
>    (match (run-with-store store
> -           (machine-remote-eval machine invocation))
> +           (machine-remote-eval machine invocation)
> +           #:system (%current-system)
> +           #:target (%current-target-system))
>      ((code output)
>       (match code
>         ((? zero?)
> @@ -325,7 +333,9 @@ (define-command (guix-deploy . args)
>                                                #:verbosity
>                                                (assoc-ref opts 'verbosity)
>                                                #:dry-run? dry-run?)
> -            (parameterize ((%graft? (assq-ref opts 'graft?)))
> +            (parameterize ((%graft? (assq-ref opts 'graft?))
> +                           (%current-target-system (assoc-ref opts 'target))
> +                           (%current-system (assoc-ref opts 'system)))
>                (if execute-command?
>                    (match command
>                      (("--" command ..1)
>
> base-commit: 9449ab3c2025820d2e6fd679fa7e34832b667ea7
> --
> 2.48.1
>
> --8<---------------cut here---------------end--------------->8---
>
> I wasn't able to confirm the patch works as during the deployment it
> tries to build the toolchain which I can't afford on my host:
>
> --8<---------------cut here---------------start------------->8---
> The following derivations will be built:
>   /gnu/store/gxr8v1yisdiyndka0abxrc0xzrra66sv-binutils-cross-aarch64-linux-gnu-2.41.drv
>   /gnu/store/lch3711iiczn6smxsr7r3sj991p8avwv-ld-wrapper-aarch64-linux-gnu-0.drv
>   /gnu/store/zmsnlbyml0vmphfdxyxw4ps25bgrwz92-gcc-cross-sans-libc-aarch64-linux-gnu-14.2.0.drv
>   /gnu/store/57jnlmvqlvk6jkyvqcnrk4psffhmak91-linux-libre-headers-cross-aarch64-linux-gnu-5.15.49.drv
>   /gnu/store/b4f1my595ggl7d5qn46vr6qllwx7g49z-glibc-cross-aarch64-linux-gnu-2.39.drv
>   /gnu/store/sl5vfnwdarghf9ypbspq1bdlamnz3j2a-gcc-cross-aarch64-linux-gnu-14.2.0.drv
>   /gnu/store/3vp8a7mz1576xbk278k9b73nx2zqmzlw-libffi-3.4.4.drv
>   /gnu/store/5ij0pv5z7mi25r397y4k62ma7q38qrka-pkg-config-aarch64-linux-gnu-0.29.2.drv
>   /gnu/store/y3hqwsbc8rb2g1mac8c9vsdmaacf20xm-libatomic-ops-7.6.12.drv
>   /gnu/store/bd09d178ni5sp9db62w869c6m7d3sh6v-libgc-8.2.4.drv
>   /gnu/store/cs7mzhrypgdad8v0v29arafc8brl7ynd-bash-minimal-5.1.16.drv
>   /gnu/store/np51g0ak713az6shj6sv9j3wkq4cjvjx-libunistring-1.1.drv
>   /gnu/store/rbkb4ig158h9gblbrah5nx5annvfpb4q-libxcrypt-4.4.36.drv
>   /gnu/store/lfmamfv5vx690l9n6a1ixbbk6kzw3gsr-guile-3.0.9.drv
> --8<---------------cut here---------------end--------------->8---
>
> Finally, I want to highlight a couple things that I haven't figured out
> for my use-case yet:
> 1. My private ssh key is stored in GnuPG and I'd like to keep it that
> way. Afaik `managed-host-environment-type` can utilise the running
> ssh-agent, could it be also implemented for hetzner machines?

Your public key needs to be added as an SSH key via the Hetzner API. I
believe the guix deploy command is doing the same here as the digital
ocean one. It takes the ssh key from the machine config and creates the
public key with the Hetzner API on the server.

Maybe we could also support specifiy a fingerprint in the machine
configuration and somehow get the public ssh key for it somehow from
your GPG agent in Guile. Not sure how to do this though.

I think the difference to managed-host-environment-type, is that with
managed-host-environment-type someone already put the public key on the
server (and authorized it) and Guix is using the private key from the
SSH agent when it connects to it.

> 2. My use-case is an on-demand wireguard VPN. In my current setup I have
> created a static ipv6 address which I attach to the VM created using
> `hcloud`. The wireguard config hardcodes the same ipv6 and is installed
> on the VM during cloud-init provision (`--user-data-from-file`
> parameter). To replicate the same in guix deploy,
> `hetzner-configuration` should be more flexible in regards to public ip
> addresses. I.e. it should allow to use either v4 or v6 and to accept
> existing one provided by the user.
>

Enabling/disabling IPv4/IPv4 should be easy to implement. The public_net
option has settings for enable_ipv4 and enable_ipv6. They both default
to #t, but it should be easy to add a configuration option for it.

https://docs.hetzner.cloud/#servers-create-a-server

The public_net also support ipv4 and ipv6 fields. The docs say:

ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a
new ipv4 Primary IP will automatically be created.

And this seems to be the endpoint for creating those IPs:

https://docs.hetzner.cloud/#primary-ips-create-a-primary-ip

We don't have code to manage primary IPs in the Hetzner modules yet, but
it shouldn't be hard to add it.

I won't have time to look into this right now, but if you plan to do it,
I can certainly answer questions you might have or support you on this.

I hope that helps, and sorry your use case isn't covered yet.

Roman