Guix - The Destroyer

6 November 2023 | programming guix guile

Hey guys!

It has been a hot minute since I posted anything here. To be honest, I did not have anything I really wanted to talk about. That was until today.

Preamble

About 2-3 years ago, I was writing a lot, everyday, mostly professionally. It was my jam. However, even before the AI boom, I knew that I had to change what I was currently doing if I wanted to grow as a person.

From my own experience working digitally, I had a gut instinct that programming would be just the thing for me. Spurred on by the likes of Luke Smith to embrace free and open source software (FOSS), I jumped right in and installed doom emacs and never looked back.

Mind you, before all this I had some experience with Linux. I had a very, very used laptop that could not run any version of Windows with acceptable performance. Plus the hard-disk died and I could not replace it. A friend of mine told me that you can boot Linux from a flash-disk and it will just work. I installed Kali on the flash and basically lived like that for a year. The biggest downside was figuring out how to save stuff persistently but that was not too bad, just use another flash-disk!

Anyway, along the way, I read the Linux Bible cover to cover and wrote it down on paper in a hard-cover book. Yes, all 900 pages. It was some of the most fun I have ever had. You have to understand, Linux is magical to me. Coming from winglows, I never imagined all this could be done on a computer with such ease! I have since lost that book, my magnum opus, as it were. If all this sounds crazy to you, then buckle up, we are just getting started.

Shortly, I pestered my parents for a hard-disk, claiming all sorts of benefits and we were soon in concordance. Equipped with my new boon, I installed Manjaro, because I thought it was just perfect. It is a bespoke experience with no bugs, or so I thought.

To be fair, hardly anything broke in the year or so that I used it. But things did break occasionally. Or sometimes you would want a package but you would have to wait for two weeks or so. Or you would just have to configure pacman(arch linux package manager) to access the AUR. Manjaro was good to me but I needed more, I needed total control, I needed the source, I needed arch linux.

By that point in time, I figured I had the linux-chops to use a real hackers distro like arch linux. It was a roller-coaster of a time, for those 2 years I was working with arch. I learnt a lot and I am very grateful to all the hackers who contributed to making this distro. I am forever their humble student.

Ditto for Manjaro and Kali and all the hackers out there sharing and building the FOSS world. I salute you!

Now, I was quite happy and I was learning a lot. I went through the whole rigmarole of ricing (same idea but for pcs) my setup. I cannot think of one workflow I have not tried. From suckless to pretty much anything else you can think of. It took me a while but I eventually settled on hyprland with my doom emacs and librewolf. I was happy, for a time.

Then, one fateful day, horror of horrors, arch broke. An upstream bug in malloc made it onto my pristine arch setup that meant that basically anything that was using C was toast. I fried my socket because of this bug.

I just want to say that whoever developed this malware, shame on you. I have my suspicions about three letter agencies who do this kind of thing, but, as far as I am concerned, this was an attack on my machine. I knew I had to make a change.

Enter GNU Guix, the one tool/distro/package-manager to unite them all! I absolutely love this tool. It is written in guile scheme. It is magical.

Anyway, enough said about me, let us see why guix is awesome! This is not even really a tutorial, or a blog, it is more like a reminder to stop doing stupid things 🤣!

What is guix?

According to the manual:

GNU Guix(1) is a package management tool for and distribution of the GNU system. Guix makes it easy for unprivileged users to install, upgrade, or remove software packages, to roll back to a previous package set, to build packages from source, and generally assists with the creation and maintenance of software environments.

(1) “Guix” is pronounced like “geeks”, or “ɡiːks” using the international phonetic alphabet (IPA).

This definition is so ever deceptively simple. In fact, it sounds like almost every other package manager out there, but as we will see, it is not.

First, what is this business with unprivileged users installing packages? Sounds like a security flaw! Nope. Only the build daemon can write to the store(where packages are kept).

That sounds even worse. Unprivileged users can install packages but root cannot modify them? What the hell? Well, actually, it is a feature not a bug.

This property allows guix to be functional and transactional at the same time. Now, many package managers claim to work in this manner but they do not. In the sense that the package manager cannot always tell if a package has been tampered with or not. Or if it installed properly in some cases. Of course, there is a predetermined order of steps that in theory should work, but in practice can easily be sidestepped and lead to maximum borkage.

For example, what happens if you mess with the source before installation? Welp, sucks to be you I guess. With guix however, the hash for the source is identified in the package definition. Yes, unless the author is malicious, it cannot be tampered with assuming the package definition is correct and if you are getting them from the guix CI server, that is as good as it gets. And yes, there is a free CI server for package distribution, we will come to this later in a moment, I am getting ahead of myself.

Did I mention that the package definitions are declarative? Here is the infamous hello world example, from the manual:

(define-public hello
       (package
         (name "hello")
         (version "2.10")
         (source (origin
                   (method url-fetch)
                   (uri (string-append "mirror://gnu/hello/hello-" version
                                       ".tar.gz"))
                   (sha256
                    (base32
                     "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i"))))
         (build-system gnu-build-system)
         (synopsis "Hello, GNU world: An example GNU package")
         (description
          "GNU Hello prints the message \"Hello, world!\" and then exits.  It
     serves as an example of standard GNU coding practices.  As such, it supports
     command-line arguments, multiple languages, and so on.")
         (home-page "https://www.gnu.org/software/hello/")
         (license gpl3+)))

It could not be any clearer. Compare this valid scheme code with package definitions in other distros. I cannot even. This is so elegant and concise and just downright beautiful.

This package definition includes in it everything guix needs to know to download, configure and install the package correctly. Every single time, on any computer. It puts anything else to shame. Nuff said.

How easy would it be to write this definition? Very. Here is one that I was working on today.

(define-public rust-parse-js-0.10
  (package
   (name "rust-parse-js")
   (version "0.10.3")
   (source
    (origin
     (method url-fetch)
     (uri (crate-uri "parse-js" version))
     (file-name (string-append name "-" version ".tar.gz"))
     (sha256
      (base32 "1rb4qz7gmj2qsxpkacyi1c1b298wgrs48r9r9haam1xdwrclflrh"))
     ))
   (build-system cargo-build-system)
   (arguments
    `(
      ;; #:skip-build? #t
      #:cargo-inputs (("rust-aho-corasick" ,rust-aho-corasick-0.7)
                      ("rust-lazy-static" ,rust-lazy-static-1)
                      ("rust-memchr" ,rust-memchr-2)
                      ("rust-serde" ,rust-serde-1)
                      ("rust-serde-json" ,rust-serde-json-1))))
   (home-page "https://github.com/wilsonzlin/parse-js")
   (synopsis "JavaScript parsing library")
   (description "@code{JavaScript} parsing library")
   (license license:asl2.0)))

Yes, this is a rust library. Never mind that you cannot install rust libraries on other distros without storing them in .cargo and getting data pimped by basically all of big tech. Guix has already liberated rust and we will see even more in a moment. Take heed and join the movement rustaceans, you can keep all your fancy toys.

I would like to highlight a few things for those like me who were not born reading Scheme.

Here is a deceptive incantation:

(uri (crate-uri "parse-js" version))

This line instructs the build daemon where to download the crate from crates.io. It is oh so unassuming.

Here is another one;

(sha256
 (base32 "1rb4qz7gmj2qsxpkacyi1c1b298wgrs48r9r9haam1xdwrclflrh"))

This is the hash of the tarball of the crate that is downloaded by cargo when you install a package. You can confirm this by using crates-io API like so:

curl -L https://crates.io/api/v1/crates/parse-js/0.10.3/download --output parse-js-tar
guix hash parse-js-tar

You can also use guix download like so and it will print out the hash:

 guix download https://crates.io/api/v1/crates/parse-js/0.10.3/download

Or you can live dangerously and wait for it to tell you which hashes are wrong during the build phase and just copy that. The legendary hacker Andrew Tropin does that here as he casually blesses you with esoteric knowledge of the black arts.

Did I mention he wrote guix home, yet another amazing tool that we will get into in another blog post. Accept his gifts with gratitude.

Finally, yes, the licensing is mandatory;

(license license:asl2.0)

Everything else is pretty much straightforward.

Now you are empowered and your system will never break again. Go forth, hack and be merry.

Actually wait, there is some more black magic that we have not yet mentioned because it probably flew over your head too.

#:cargo-inputs (("rust-aho-corasick" ,rust-aho-corasick-0.7)
                ("rust-lazy-static" ,rust-lazy-static-1)
                ("rust-memchr" ,rust-memchr-2)
                ("rust-serde" ,rust-serde-1)
                ("rust-serde-json" ,rust-serde-json-1))

This section right here. It seems so obvious. Of course, you would not be tripped up by the version numbers because you know how semantic versioning works, you hacker you.

And of course you know how to read a simple Cargo.toml file, how hard can this be? Especially if you use the curl trick and turn it into a script like jbe on the rust-lang discourse forum:

#!/bin/sh
mkdir tmp-download || exit 1
cd tmp-download || exit 1
curl -L https://crates.io/api/v1/crates/$1/$2/download | tar -xf -
mv $1-$2 ..
cd .. || exit 1
rmdir tmp-download

Except you would be a fool to do so because guix knows everything. Everything we just did can be summarized into this one command:

guix import crate perseus-cli -r >> perseus-cli.scm

This command is so gangsta it is not even fair.

Imagine sitting there writing package definitions for a day. Only a fool would do that. Unfortunately, I was once such a naive linux FOSS user. No longer, through the power vested in me by guix, I am free and at home.

Through this one command, guix will download the specified crate as well as any other crates not in guix, recursively defined (-r) and output the package definition to the specified file.

Magic.

Yes, guix can literally write programs that write other programs. RIP everything else. But what did you expect? It is written in scheme of course. GNU Guile to be specific. Accept the gifts from GNU and guix with humility.

Do you feel enlightened? Do you feel empowered? Well, tone down your self-righteousness and read this post right here so you can understand the extent of your ignorance: https://steve-yegge.blogspot.com/2007/06/rich-programmer-food.html

So yes, guix can do it all, but will you rise up to the challenge?

For now, here is where I sign off and I will be back with more 1337 hacks.

PS:

Coincidentally, for this post, I will use zola but I want to experiment with Haunt for a while, cause it is written in Scheme, sue me.

PPS:

I could not help myself folks. I did it, I rewrote the blog in Haunt.

I used a lot of code from other people:

  • The excellent and very convenient ox-haunt from Jakob L. Kreuze as well as some of the helper functions he used in his own blog which you can find here: Jakob blog source code.
    • At the moment this is specifically his html-reader-prime which parses output from ox-haunt:

      (define (read-html-post-prime port)
        (values (read-metadata-headers port)
                (let loop ((ret '()))
                  (catch 'parser-error
                    (lambda ()
                      (match (xml->sxml port)
                        (('*TOP* sxml) (loop (cons sxml ret)))))
                    (lambda (key . parameters)
                      (rewrite-absolute-urls-as-relative
                       (reverse ret)))))))
      
      (define html-reader-prime
        (make-reader (make-file-extension-matcher "html")
                     (cut call-with-input-file <> read-html-post-prime)))
      
      • Funny story: just as I was finishing up on this blog post and wanted to give attribution to Jakob, I noticed that the link to his website would not work.
      • Being a cryptography expert, I was convinced he had a bot of some kind on his site intercepting requests, filtering the traffic and doing remote executions to keep his identity safe.
      • Turns out it was the rewrite-absolute-urls-as-relative function described in the utils module here:

        (define (rewrite-absolute-urls-as-relative tree)
          (match tree
            (('a attrs body ...)
             (if (assoc 'href (cdr attrs))
                 (let* ((url (car (assoc-ref (cdr attrs) 'href)))
                        ;; (url (if (string-prefix? "https://jakob.space" url)
                        (url (if (string-prefix? "https://kaizerpublications.xyz" url)
                                 ;; (string-drop url (string-length "https://jakob.space"))
                                 (string-drop url (string-length "https://kaizerpublications.xyz"))
                                 url))
                        ;; (url (if (string-prefix? "http://jakob.space" url)
                        (url (if (string-prefix? "http://kaizerpublications.xyz" url)
                                 ;; (string-drop url (string-length "http://jakob.space"))
                                 (string-drop url (string-length "http://kaizerpublications.xyz"))
                                 url))
                        (attrs `(@ (href ,url) ,@(filter (match-lambda
                                                           (('href _) #f)
                                                           (_         #t))
                                                         (cdr attrs)))))
                   `(a ,attrs ,@body))
                 tree))
            ((xs ...)
             (map rewrite-absolute-urls-as-relative xs))
            (elem elem)))
        
      • Which you can see in action here because I have redefined it to redirect my website now :).
      • Click that link for remote execution!!! Mwahahahaha!!!!
      • btw, this might not be inconceivable with guile-hoot
  • As well as a few more helper functions from Duncan Wilkie of which you can find the source code here: Duncan Wilike blog source code.
    • More specifically;
      1. His tagging functionality through tags->page function:

        (define (tags->page site posts)
          (flat-map (match-lambda
                      ((tag . posts)
                       (make-page (tag-uri tag)
                                  (base-template site (tags-template site posts #:title tag)
                                                 #:title tag)
                                  sxml->html)))
                    (group-by-tag posts)))
        
      2. At the moment, parts of his theme as well, including styling.
      3. As well as the index-page functionality;

        (define (recents site posts)
          `(section (@ (id "recent"))
            (div
             (h1 "Recent Posts"
                 ,(hyperlink "/feed.xml" (image "rss.png" "RSS Feed Icon" "rss-icon")))
             (div (@ (class "post-listing"))
                  ,(map (lambda (post)
                          (post-header site post))
                        (take-up-to 10 (posts/reverse-chronological posts))))
             ,(hyperlink "/posts" "All Posts"))))
        
        (define (index-content site posts)
          `(,about ,(recents site posts)))
        
        (define (index-page site posts)
          (make-page "/index.html"
                     (base-template site (index-content site posts) #:title "Home ")
                     sxml->html))
        

A very big thank you to all the people who have helped me learn as well as contributed their source code to the public domain. Much of this work would not have been possible without you.

From one hacker to another:

Thank you.