About GitLab and Pages by Safely Dysfunctional
Tags: gitlab, nix, haskell, hakyll
Published: 2021-07-10 03:23:51

## About GitLab and Pages
> Posted on July 6, 2021 at [Safely Dysfunctional Blog](https://safelydysfunctional.gitlab.io/) by Janil Garcia ![Janil Garcia](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lrrgqy3vi2rioz1d2mj.png) in Brazilian Portuguese and translated by Eduardo Pereira Garcia



## What is GitLab?
[GitLab](https://gitlab.com/) is a git repository host as [GitHub](https://github.com), the main difference is, the framework itself is open source (written in Ruby, as GitHub too) and have many hosting options, including its Cloud and, for sure, your own dedicated server.

To serve from GitLab, two options are offered, the Community Edition (which is totally free) and the Enterprise Edition (which is paid, but have access to more features).

Furthermore, GitLab has a bunch of Project Management and DevOps tools. And this since before GitHub offers this kind of services. We are going to use both here: GitLab CI/CD and GitLab Pages.


## GitLab CI/CD
This service of Continuous Integration/Continuous Delivery is offered by GitLab. Through a YAML file at root directory of the project, where a series of steps that will be executed every time a specific event occurs in the repository can be described (as a push to a branch or a merge request for instance).

It is also possible to specify the environment where these instructions will be executed, starting from a [Docker](https://www.docker.com/) image/container. Also, environment variables and secret files can be settled up, if needed. GitLab offers this service for free, with limitations though. The instance has low memory, and processing capacity to run for 1 hour only.

Your own runner (an instance able to run CI/CD) can be plugged in or additional GitLab ones can be provisioned. Well, I don't want to expend some money on it with this blog (Safely Dysfunctional) so I decided to use the free tier.

This info is relevant because [Hakyll](https://jaspervdj.be/hakyll/) application requires to be complied before it generates the pages, and the compilation process of [Haskell](https://www.haskell.org/) is a pretty expensive (computationally saying). Although, the executable is incredible fast, due to great work made by the compiler. This processing cost will be discussed soon.


## GitLab Pages

Well, the [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/) is a GitLab service created to hosts static pages. And guess who has static pages to be hosted? This guy (Janil points himself). - very funny bro, ha -

GitLab Pages is integrated to GitLab CI/CD, therefore it lets you generate yours pages through any SSG ([Static Site Generator](https://www.cloudflare.com/learning/performance/static-site-generator/))[^1] you want, then you put the files at a web server so everyone can see.

This is very useful, especially for project documentation or a blog in my case.

The most important step is to choose the project's name used to host the site, as incredible as it seems. My username in GitLab is **janilcgarcia**, it is mapped the page to the project as below:

> consider $user_name = janilcgarcia and $project_name = blog

* If name of the project follows the pattern *$user_name.gitlab.io* (janilcgarcia.gitlab.io in this case), the page is hosted at *janilcgarcia.gitlab.io*.

* If the project name is different from that it is hosted at *$user_name.gitlab.io/$project_name* (that is *`janilcgarcia.gitlab.io/blog`*).

There is an extra detail, GitLab supports the groups concept, which is and underlying feature especially for related projects and people.

Each group receive a domain as following form *`$group.gitlab.io`* (in my case https://safelydysfunctional.gitlab.io) to host static pages too. Using this you can use legal domains where to serve your site. The mapping for groups works as previously described for users.

In GitLab Pages' website, it is possible to configure your own page accordingly with the desired tool, but the idea is:

```
# You start setting the image for the runner
image: docker/base/image

# Then you define the steps to generate the pages
pages:
  # on `script` you put the steps necessary to build the page
  script:
    - ssg compile -o public/

  # on artifacts you specify files that should be kept after the execution
  # the pages MUST be on the public/ directory in project root
  artifacts:
    paths:
      - public

  # with `only` you can specify which branches trigger this action
  only:
    - master
```


## Hakyll and CI/CD

Do you remember I said that Haskell compilling takes time. Very well, if you take the obvious choice to get a Haskell docker image, compile the project and use the binary to construct static pages, the task will fail. After one hour I've learned this at hardest way.

The site is running, so you know I found another path, right?

Well, for a time I was creating an image with the pre-compiled binary, therefore I could run the compiler in my PC, which have more processing power and after that doing the upload to Docker Hub. 
This approach works very well, because the binary does not change frequently, only when you change something in its code.

However it has the disadvantage to requires an image creation in my PC, sending to Docker Hub and having to do it every time that I changed the code. But I was aware of another approach that Haskell guys are using, the **nix**.


## NIX

Nix is the [NixOS](https://nixos.org/) package manager, but it runs in other distros too (I believe it does even on MacOS). The great difference of Nix is that it isolates the packages from the systems in specific directories and after that hooks them in a very compatible manner when needed.
This allows multiple versions of same packages available in the system. And to drive this integration, Nix uses a declarative language (purely funcional). Thus all OS environment becomes immutable and installing a package is nothing more then apply a function that updates this environmento to a new state that have a package.
THe perfect companion to Haskell, not even the OS itself has side effects.

Looks like great, right? But a weak point is the documentation shortage, help is hard to find and understand how the package menager works is not an easy task too.

Fortunatelly I found a tutorial that gave me a north.
https://cah6.github.io/technology/nix-haskell-1/

After a long fight and deep search I ended with a 3 files repository config (`blog.nix`, `default.nix` and `shell.nix`):

`blog.nix` describes a new package based on a project using [cabal](https://www.haskell.org/cabal/)
```
{ mkDerivation, base, binary, bytestring, data-default, directory
, filepath, fsnotify, hakyll, http-types, lib, pandoc, pandoc-types
, process, tagsoup, text, wai, wai-app-static, wai-extra, warp
, pythonPackages
}:
mkDerivation {
  pname = "blog";
  version = "0.1.0.0";
  src = ./.;
  isLibrary = false;
  isExecutable = true;
  executableHaskellDepends = [
    base binary bytestring data-default directory filepath fsnotify
    hakyll http-types pandoc pandoc-types process tagsoup text wai
    wai-app-static wai-extra warp
  ];
  executableSystemDepends = [ pythonPackages.pygments ];
  license = lib.licenses.gpl3;
  hydraPlatforms = lib.platforms.none;
}
```

`default.nix` describes how to compile the project and defines the environment where the compilation must be done.
```
let
  pinnedPkgs = import ./pkgs-from-json.nix { json = ./release-21.05.json; };
in
pinnedPkgs.pkgs.haskellPackages.callPackage ./blog.nix { }
```

at last `shell.nix` describes that nix-shell can start from inside the same environment that th compilation one. It is from here you could put [hoogle](https://hoogle.haskell.org/) and haskell-language-server if needed.
```
{ }:
(import ./default.nix).env
```

Now, if you need to know how this works, I'm the wrong guy to ask. I know it works, and that's all.

A detail I needed to figure how to implement out was install [pygments](https://pygments.org/) inside the project's compilation environment. To do that, I had to modify the file `blog.nix` and add the property `executableSystemDepends` containing `pythonPackages.pygments`, the pygments package name in Nix.

I believe that, maybe, this is not the right place for this package to be installed (still this is for sure a project dependency). Perhaps I should have inserted it in `shell.nix`, but until I figure this whole thing out that's how it'll remain. Most importantly: it works as is, so let's try not to go fixing what ain't broke.


## Nix at GitLab

Once a functional environment have been created in Nix, using it in GitLab CI/CD is easy.
That's all I needed:
```
image: nixos/nix

pages:
  before_script:
    - nix-build default.nix

  script:
    - nix-shell --run 'result/bin/site build'

  artifacts:
    paths:
      - public

  only:
    - master
```

Very simple, isn't it? It uses `nixos/nix` avaiable at Docker Hub (which is very small, containing only the package manager - not even `bash` seems to be there), it compiles the site generator using the description into `default.nix` and after it generates the site, storing the result in the public directory, that will be collected by GitLab Pages and goes to web.


## Conclusion

The hardest part of it all is to make Nix works. GitLab CI and GitLab Pages are very simple and flexible. An this post encloses the Hakyll's legend, for now!!! hehehe

> "That's all folks!"


[^1]: [More about SSGs](https://jamstack.org/generators/)