Shell.nix for GitHub Pages development

I want to do local development of a GitHub Pages site. GitHub Pages serves your site using Jekyll, which is written in Ruby. The exact dependencies are listed at

Dependency versions | GitHub Pages

and available as JSON on

https://pages.github.com/versions.json

The dependencies (apart from Ruby itself) are also available as a Ruby package (“gem”):

GitHub - github/pages-gem: A simple Ruby Gem to bootstrap dependencies for setting up and maintaining a local Jekyll environment in sync with GitHub Pages

Given this, is there a simple shell.nix automatically giving a local development environment matching the environment GitHub Pages runs on?

At the very least, if one gives up on automatically keeping up with the Ruby version used by GitHub Pages, then Ruby together with pages-gem should suffice. It is however not clear to me how to accomplish this.

I looked at

Packaging/Ruby - NixOS Wiki

and I also looked at

Building a Jekyll Environment with NixOS

As far as I can see, those approaches generate a shell.nix (and additional files) fixing a development environment on whatever is specified by the current version of pages-gem. Thus when pages-gem is updated this development environment would become outdated. And in any case, ideally one should not need to deal with the large auto-generated Gemfile.lock and gemset.nix files since basically the desired development environment is fully specified by the two lines in the Gemfile file.

direnv is your friend in this - in your repository, create the following files:

.envrc:

use nix
layout ruby

Gemfile:

source 'https://rubygems.org'

gem 'github-pages'

shell.nix:

with import <nixpkgs> {};

stdenv.mkDerivation {
  name = "env";

  buildInputs = [
    bashInteractive
    libxml2
    zlib
  ];

  nativeBuildInputs = [
    bundler
  ];
}

Then simply run bundle install and you’re off to the races.

If github-pages changes, you run bundle update.

Thanks! I had to add ruby to buildInputs but then this worked like a charm. On MacOS this seems to require XCode development tools. Any way around this? (The approaches I mentioned above works on MacOS without XCode development tools installed.)

(I would still be interested in a “more pure” nix-shell solution.)

I solved this by adding libxslt and pkgconfig to the buildInputs.

Consider adding your solution to: GitHub - nix-community/nix-environments: Repository to maintain out-of-tree shell.nix files (maintainer=@mic92)
So future people having an easier time to get something working.

You should be okay with just “nix-shell -p jekyll”, unless you specify a plugin that is not packaged already.

I mean, all the ruby parts about a github page are dealing with installing and configuring jekyll

We’re using the following for https://blog.hercules-ci.com

{ pkgs ? import ./nixpkgs.nix
}:

with pkgs;

stdenv.mkDerivation {
  name = "blog.hercules-ci.com";

  src = lib.cleanSource ./.;

  buildInputs = [
    (jekyll.override { withOptionalDependencies = true; })
    glibcLocales
  ];

  LANG = "en_US.utf8";

  buildPhase = ''
    jekyll build
  '';

  installPhase = ''
    mkdir -p $out
    cp -R _site/* $out
  '';
}

This worked for me until recently. I used nix-shell -p jekyll bundler followed by bundle exec jekyll serve but now I get the following error:

/home/wswinks/.local/share/gem/ruby/3.1.0/gems/sass-embedded-1.77.8/lib/sass/compiler/connection.rb:61: warning: Could not start dynamically linked executable: /home/wswinks/.local/share/gem/ruby/3.1.0/gems/sass-embedded-1.77.8/ext/sass/dart-sass/src/dart
/home/wswinks/.local/share/gem/ruby/3.1.0/gems/sass-embedded-1.77.8/lib/sass/compiler/connection.rb:61: warning: NixOS cannot run dynamically linked executables intended for generic
/home/wswinks/.local/share/gem/ruby/3.1.0/gems/sass-embedded-1.77.8/lib/sass/compiler/connection.rb:61: warning: linux environments out of the box. For more information, see:
/home/wswinks/.local/share/gem/ruby/3.1.0/gems/sass-embedded-1.77.8/lib/sass/compiler/connection.rb:61: warning: https://nix.dev/permalink/stub-ld
Conversion error: Jekyll::Converters::Scss encountered an error while converting 'assets/css/jekyll-theme-chirpy.scss':
                    Broken pipe
                    ------------------------------------------------
Jekyll 4.3.3   Please append `--trace` to the `serve` command 
                     for any additional information or backtrace. 
                    ------------------------------------------------

Apparently Jekyll uses now Dart SASS instead of SASS which can also be seen in the output. Do the warnings have to do something with this error? The connection.rb files contains:

# frozen_string_literal: true

require 'open3'

require_relative '../../../ext/sass/cli'

module Sass
  class Compiler
    # The stdio based {Connection} between the {Dispatcher} and the compiler.
    #
    # It runs the `sass --embedded` command.
    class Connection
      def initialize
        @mutex = Mutex.new
        @stdin, @stdout, @stderr, @wait_thread = begin
          Open3.popen3(*CLI::COMMAND, '--embedded', chdir: __dir__)
        rescue Errno::ENOENT
          require_relative '../elf'

          raise if ELF::INTERPRETER.nil?

          Open3.popen3(ELF::INTERPRETER, *CLI::COMMAND, '--embedded', chdir: __dir__)
        end

        @stdin.binmode

        # # https://dart.dev/tools/dart-devtools
        # if 'dart' == File.basename(CLI::COMMAND.first, '.exe') && CLI::COMMAND.include?('--observe')
        #   Kernel.warn(@stdout.readline, uplevel: 0)
        #   Kernel.warn(@stdout.readline, uplevel: 0)
        # end

        @stdout.binmode

        @wait_thread.name = "sass-embedded-process-waiter-#{id}"
      end

      def id
        @wait_thread.pid
      end

      def listen(dispatcher)
        Thread.new do
          Thread.current.name = "sass-embedded-process-stdout-poller-#{id}"
          loop do
            length = Varint.read(@stdout)
            id = Varint.read(@stdout)
            proto = @stdout.read(length - Varint.length(id))
            dispatcher.receive_proto(id, proto)
          end
        rescue IOError, Errno::EBADF, Errno::EPROTO => e
          dispatcher.error(e)
          @mutex.synchronize do
            @stdout.close
          end
        end

        Thread.new do
          Thread.current.name = "sass-embedded-process-stderr-poller-#{id}"
          loop do
            Kernel.warn(@stderr.readline, uplevel: 0)
          end
        rescue IOError, Errno::EBADF
          @mutex.synchronize do
            @stderr.close
          end
        end
      end

      def close
        @mutex.synchronize do
          @stdin.close
          @wait_thread.join
          @stdout.close
          @stderr.close
        end
      end

      def closed?
        @mutex.synchronize do
          @stdin.closed? && !@wait_thread.alive?
        end
      end

      def write(id, proto)
        buffer = []
        Varint.write(buffer, Varint.length(id) + proto.length)
        Varint.write(buffer, id)
        @mutex.synchronize do
          @stdin.write(buffer.pack('C*'), proto)
        end
      end
    end

    private_constant :Connection
  end
end

At the moment I don’t know how to resolve this issue. Does anybody have suggestions on how to trouble shoot this?

My blog is github-pages and uses Nix

It was good for quite a while but…

The bundix codebase unfortunately is broken now and it was producing a lot of incorrect hashes… Quite unfortunate.

I had to hand edit the SHA256…

Program language support in Nixpkgs is very hit and miss.

Anyways should serve as a good reference.

1 Like

Thank you, I will have a look at it.

I made a small blog post on how to setup Jekyll with bundix. I will leave it here for reference.