ox-hugo Dependency Image

Motivation

I decided that I would like to contribute to the ox-hugo project from time to time. I do not have pandoc or pandoc-citeproc installed locally nor have I ever used either before. I did consider emerging pandoc, but when I saw the list of dependencies being pulled from portage I immediately cancelled the install.

For those that are unfamiliar with Gentoo, Portage is Gentoo’s package manager and emerge is Portage’s command-line interface. The following command displays the number of packages, including pandoc itself, that would have been emerged as new packages to the world set. Most of those dependencies are from Haskell.

emerge -putDN pandoc | grep 'ebuild  N' | wc -l
98

Egad! That is a lot of dependencies. What if I need to test against multiple versions of pandoc which together depend on multiple versions of one or more of those 98 dependencies? I really don’t like polluting my local environment with a ton of packages if I can avoid it; hence, my motivation to encapsulate ox-hugo dependencies into a dependency container.

Initial setup

You might find it useful to refer to the following repositories during the initial setup:

Blogger Bust GitHub nauci_base_entry
Contains the entry point source used by the nauci/ox-hugo_deps image. The readme contains documentation on usage.
Blogger Bust GitHub ox-hugo_deps
Contains the Dockerfile used to create the ox-hugo_deps image. It also contains a number of important shell scripts in the bin directory that you will need
nauci Docker Hub nauci/ox-hugo_deps
Contains the latest pre-built nauci/ox-hugo_deps image

Create a shared directory with POSIX ACL support

Before you begin, you will need a file system with POSIX ACL support and extended attributes enabled as described in - Create a shareable file system or directory. Once you have a file system with POSIX ACL support enabled you can simply create a directory for sharing projects between your host and any future dependency containers you create. I named this directory /shared/, but ultimately you can name it anything you like.

Create a developer group

Your host user must be a member of the developer group as described in Create a new group named developer.

groupadd -g 2000 developer
usermod -aG developer dustfinger

Pull nauci/ox-hugo_deps image

If you have not already done so, please install docker for your platform. Docker has no dependencies and uninstalls cleanly, therefore you can install it just to try out this example and uninstall it afterwards if you wish. Once Docker has been installed, you may proceed by pulling the ox-hugo_deps image from Docker Hub.

docker pull nauci/ox-hugo_deps
Using default tag: latest
latest: Pulling from nauci/ox-hugo_deps
54f7e8ac135a: Pulling fs layer
c63217f7ef68: Pulling fs layer
dfd98676afbf: Pulling fs layer
33204c21b26d: Pulling fs layer
5b0628993196: Pulling fs layer
5df98747341b: Pulling fs layer
3f2bb18273d2: Pulling fs layer
33204c21b26d: Waiting
5b0628993196: Waiting
3f2bb18273d2: Waiting
5df98747341b: Waiting
dfd98676afbf: Verifying Checksum
dfd98676afbf: Download complete
33204c21b26d: Verifying Checksum
33204c21b26d: Download complete
54f7e8ac135a: Verifying Checksum
54f7e8ac135a: Download complete
5df98747341b: Verifying Checksum
5df98747341b: Download complete
54f7e8ac135a: Pull complete
c63217f7ef68: Verifying Checksum
c63217f7ef68: Download complete
c63217f7ef68: Pull complete
dfd98676afbf: Pull complete
33204c21b26d: Pull complete
5b0628993196: Verifying Checksum
5b0628993196: Download complete
3f2bb18273d2: Verifying Checksum
3f2bb18273d2: Download complete
5b0628993196: Pull complete
5df98747341b: Pull complete
3f2bb18273d2: Pull complete
Digest: sha256:054a0365d144c2ee6942a74c0511723182a81a0bfb4c8e1207c6b4f0f0f06556
Status: Downloaded newer image for nauci/ox-hugo_deps:latest

You can list your local copy of nauci/ox-hugo_deps using the docker images command.

docker images nauci/ox-hugo_deps
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
nauci/ox-hugo_deps   latest              e0190fa60a02        46 hours ago        925MB

Run the nauci/ox-hugo_deps image and install Go

Docker images are immutable, therefore the docker run command cannot actually run a Docker image. Instead, docker run creates a new container with an initial state exactly matching the Docker image being passed to it. To be honest, I am still learning a lot about the docker run command. It is complex, so let’s simplify our mental model by accepting that its ultimate responsibility is to create an environment in which to run the image’s entry point. This environment includes its own file system, its own networking, and its own isolated process tree. In the case of the nauci/ox-hugo_deps image, the entry point is the nauci_base_init.sh script as defined in the base image nauci/nauci_base_entry.

The last argument that is processed by docker-run is the name of the image. All of the arguments following the name of the image are passed to the entry point as additional options to its default behaviour. In particular, the -s optional parameter will cause the image to enter an interactive bash shell provided that the docker run command is run interactively and provides a pseudo terminal via the -it optional parameters. It is important that we enter the interactive shell because we need to perform two manual steps:

  1. Set a password for our guest user
  2. install hugo as a Go module

    docker run -it -p 127.0.0.1:23:22 -p 127.0.0.1:1313:1313 --name ox-hugo_deps -h ox-hugo_deps -v /shared:/shared nauci/ox-hugo_deps -s -n dustfinger -v /shared -gusers,sudo,video,plugdev,staff
    [[ ok  Starting OpenBSD Secure Shell server: sshd.
    
    ##########################################################################
    # Welcome. You have entered an interactive shell. This is a good time to #
    # set user passwords. When you are finished, run the exit command to     #
    # continue with the init script. Any trailing commands that you entered  #
    # will then execute.                                                     #
    ##########################################################################
    
    root@ox-hugo_deps:/# passwd dustfinger
    Enter new UNIX password:
    Retype new UNIX password:
    passwd: password updated successfully
    root@ox-hugo_deps:/# su dustfinger
    Password:
    dustfinger@ox-hugo_deps:~$ go get github.com/gohugoio/hugo
    dustfinger@ox-hugo_deps:~$ exit
    root@ox-hugo_deps:/# exit
    exit

My motivation for installing hugo as a Go module was to provide an easy way to install the latest release from GitHub source. Hopefully they add the ability to use go get to install a module by git tag in the future. You might also be wondering why the docker run command includes the port mapping 1313 –> 1313. This is so that we can run the hugo server inside of the container while working on a blog. For this to work correctly it is important that the hugo port mapping is one-to-one. If instead you provide two different ports then the browser will fail to download site resources and links since the respective URIs will contain the remote port known to the hugo server rather than the local port reachable by your local browser.

If you take a look at the nauci/ox-hugo_deps container you will notice that it is not running.

docker container ls -a -f name=ox-hugo_deps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                          PORTS               NAMES
8b17a78e93e3        nauci/ox-hugo_deps   "nauci_base_init.sh …"   12 minutes ago      Exited (0) About a minute ago                       ox-hugo_deps

That is because we were running an interactive bash session from our entry_point and when we exited bash the rest of the entry point executed and then exited leaving no process to keep the container in a running state. Use docker start to start the container again without an interactive session.

docker start ox-hugo_deps
docker container ls -a -f name=ox-hugo_deps
ox-hugo_deps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                  PORTS                                            NAMES
8b17a78e93e3        nauci/ox-hugo_deps   "nauci_base_init.sh …"   12 minutes ago      Up Less than a second   127.0.0.1:1313->1313/tcp, 127.0.0.1:23->22/tcp   ox-hugo_deps

Fork the ox-hugo repository in the shared volume

If you would like to contribute to the ox-hugo project then you will need to fork ox-hugo in your own GitHub repository. In my example I will be using my own fork of ox-hugo. Once you have ox-hugo forked you should clone the repository in your shared directory. If you have never forked a project before then see the GitHub documentation on forking a repository. If you do not wish to make a fork at this time that is fine, you may simply clone the official repository.

cd /shared/dustfinger/dev/
git clone https://github.com/BloggerBust/ox-hugo.git
ln -sn /shared/dustfinger/dev/ox-hugo /home/dustfinger/dev/ox-hugo
cd ~/dev/ox-hugo

At this point you should have a link named ox-hugo in your home dev directory to the ox-hugo git repository located in your shared dev directory and your user should be a member of the developer group.

ls -la ~/dev/ | grep -iE 'ox-hugo$'
getent group developer
lrwxrwxrwx  1 dustfinger dustfinger      30 Dec 17 12:56 ox-hugo -> /shared/dustfinger/dev/ox-hugo
developer:x:2000:dustfinger

Install and configure the ox-hugo_deps shell scripts

The ox-hugo_deps GitHub repository bin directory has a number of bash shell scripts that you must install and configure. These shell scripts act as proxies for the go, hugo, pandoc and pandoc-citeproc commands within the nauci/ox-hugo_deps running container.

mkdir -p ~/bin
cd ~/bin
curl --remote-name-all -JL https://raw.githubusercontent.com/BloggerBust/ox-hugo_deps/master/bin/{go,hugo,ox-hugo_deps,pandoc,pandoc-citeproc}
chmod 700 {go,hugo,ox-hugo_deps,pandoc,pandoc-citeproc}
ls -la {go,hugo,ox-hugo_deps,pandoc,pandoc-citeproc}
-rwx------ 1 dustfinger dustfinger 152 Dec 20 05:47 go
-rwx------ 1 dustfinger dustfinger 212 Dec 20 05:47 hugo
-rwx------ 1 dustfinger dustfinger 368 Dec 20 05:47 ox-hugo_deps
-rwx------ 1 dustfinger dustfinger 164 Dec 20 05:47 pandoc
-rwx------ 1 dustfinger dustfinger 191 Dec 20 05:47 pandoc-citeproc

Now open ~/bin/ox-hugo_deps and set USER to your guest username. Depending on how and where you ran the image you may also need to change PORT and HOST.

# The username, host and port used to connect to a running ox-hugo_deps container
readonly USER=dustfinger
readonly PORT=23
readonly HOST="localhost"

Save your changes and then test that the script works.

$ ~/bin/ox-hugo_deps
dustfinger@localhost's password:
Linux ox-hugo_deps 4.14.12-gentoo #20 SMP Sun Nov 11 04:46:14 MST 2018 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Dec 20 12:57:22 2018 from 172.17.0.1
dustfinger@ox-hugo_deps:~$ exit
logout
Connection to localhost closed.

Configure key-based authentication

If you don’t have an RSA key at =~.ssh/id_rsa=/ then generate one by running the next command. Leave the password blank if you want password-less authentication (less secure, but more convenient)

ssh-keygen

Once you have an RSA key, send the public RSA id to the container.

ssh-copy-id -p 23 dustfinger@localhost
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/dustfinger/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
dustfinger@localhost's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh -p '23' 'dustfinger@localhost'"
and check to make sure that only the key(s) you wanted were added.

Add bin to PATH

This step is environment specific. We want to add the ~/bin/ directory to our path. I will show you how to do this for bash, but if you are not using bash then you will need to look up how to do this for your host environment.

Edit ~/.bash_profile and set the PATH after ~/.bashrc has been loaded.

# /etc/skel/.bash_profile

# This file is sourced by bash for login shells.  The following line
# runs your .bashrc and is recommended by the bash info pages.
if [[ -f ~/.bashrc ]] ; then
    . ~/.bashrc
fi

PATH=$PATH:~/bin/

My preference is to append ~/bin/ to the end of the path so that it does not overwrite any global equivalents. If I want to run a script that needs to use my local bin then I simply use env to set the path for that command instance. We will be doing this shortly.

Now source your changes so that they apply to the current environment

source ~/.bash_profile

Test each of the binaries

This is the last step of the setup. We want to ensure that ~/bin/go, /bin/hugo, /bin/pandoc and /bin/pandoc-citeproc are all actually working.

STAY=true ~/bin/go version
STAY=true pandoc --version | grep -E ^pandoc
STAY=true pandoc-citeproc --version
STAY=true hugo version
go version go1.11.3 linux/amd64
pandoc 2.2.1
pandoc-citeproc 0.14.3.1
Hugo Static Site Generator v0.53-DEV linux/amd64 BuildDate: unknown

By default, when a proxy is invoked it will cd to the current working directory that matches the host’s. This is necessary so that when building and testing ox-hugo commands that use relative paths are run from the expected directory. If you want to be able to run an ad hoc command without changing the container’s current working directory then you set the environment variable STAY=true.

Build ox-hugo

You might want to refer to the ox-hugo Contributing Guide. Step 3. of the guide instructs the reader to run make md doc. A lot of output is produced during the build so I won’t include the full results below. To ensure that we run our proxy binaries we will use the env command to modify $PATH so that ~/bin is searched first. By not providing a PATH override bash will find host installed versions of the commands rather than the proxies.

cd ~/dev/ox-hugo
env - PATH=~/bin:$PATH make md doc && test 0 -eq $? && echo "BUILD PASSED" || echo "BUILD FAILED"
# A lot more output above this point...

                   | EN
+------------------+----+
  Pages            |  8
  Paginator pages  |  0
  Non-page files   |  0
  Static files     | 14
  Processed images |  0
  Aliases          |  0
  Sitemaps         |  1
  Cleaned          |  0

Total in 53 ms
Connection to localhost closed.
[GitHub Docs] Generating README.org and CONTRIBUTING.org for GitHub ..

./doc/github-files.org ::
Loading /shared/dustfinger/dev/ox-hugo/test/setup-ox-hugo.el (source)...
Debug on Error enabled globally
Mark set
Replaced 3 occurrences
[GitHub Docs] Done
BUILD PASSED

If you see the summary at the end with the last line of text reading TESTS PASSED then that means that the markdown was generated and the docs were built successfully. If on the other hand the last line of text reads “TESTS FAILED” then the error should be reported in the standard output. In that case, you might also want to check the log files in your shared dev directory for each of the proxy commands. Both stdout and stderr from the proxy commands are sent to these log files.

ls -la /shared/dustfinger/dev/*.out
-rw-rw-r--+ 1 dustfinger developer    32 Dec 27 06:41 /shared/dustfinger/dev/go.out
-rw-rw-r--+ 1 dustfinger developer 13053 Dec 27 06:46 /shared/dustfinger/dev/hugo.out
-rw-rw-r--+ 1 dustfinger developer     0 Dec 27 06:53 /shared/dustfinger/dev/pandoc.out

Finally, follow step 6 by running the tests.

env - PATH=~/bin:$PATH make -j1 test && test 0 -eq $? && echo "TESTS PASSED" || echo "TESTS FAILED"
/shared/dustfinger/dev/ox-hugo/test/site/content-org/deep-nesting.org ::
Loading /shared/dustfinger/dev/ox-hugo/test/setup-ox-hugo.el (source)...
Debug on Error enabled globally
[ox-hugo] 1/ Exporting `Index' ..
[ox-hugo] 2/ Exporting `Chapter 1 Index' ..
[ox-hugo] 3/ Exporting `sub section 1' ..
[ox-hugo] 4/ Exporting `sub section 2' ..
[ox-hugo] 5/ Exporting `Chapter 2 Index' ..
[ox-hugo] 6/ Exporting `sub section 1' ..
[ox-hugo] 7/ Exporting `sub section 2' ..
[ox-hugo] Exported 7 subtrees from deep-nesting.org
TESTS PASSED

Again, you will know that the tests have failed if the last line reads TESTS FAILED. In that case there should be an error reported to standard output. As mentioned previously, you can check the proxy command log files for additional insight.

Using the hugo proxy server

If you find yourself contributing to ox-hugo it is probably because you found a defect while working on your own blog. Or perhaps you are adding a brand new feature that you would like to be able to use while composing your own blog. In either case, you probably have a Hugo generated blog. It makes sense that you will want to test your code changes against your own blog from your host environment. If a different version of any one of the proxy binaries (hugo, go, pandoc, pandoc-citeproc) is installed on the host, then you will need to configure ox-hugo to invoke the proxy binaries. I actually only have go installed on my host, so I will use that to illustrate how to change the exec-path in emacs to point to the proxy binaries:

(setq exec-path (split-string (getenv "PATH") path-separator))
(print (format "before inserting ~/bin at head of exec-path: %s" (executable-find "go")))
(setq exec-path (append `("~/bin") (split-string (getenv "PATH") path-separator)))
(print (format "after inserting ~/bin at head of exec-path: %s" (executable-find "go")))
"
\"before inserting ~/bin at head of exec-path: /usr/bin/go\"

\"after inserting ~/bin at head of exec-path: /home/dustfinger/bin/go\"
"

Now when you run org-hugo-export-to-md it will invoke the proxy binaries.

It should be clear by now that whenever you are sharing content between your host and dependency container, that content must reside as a child of /shared/dev/. Blog content is no exception, otherwise the hugo proxy binary will not be able to cd to the root directory of your blog from within the Docker container.

mkdir /shared/dustfinger/dev/org-publish
ln -sn /shared/dustfinger/dev/org-publish/ /home/dustfinger/dev/org-publish
cd ~/dev/org-publish/
git clone https://github.com/BloggerBust/bloggerbust.ca
cd bloggerbust.ca/

Now let’s test hugo server and verify that we can use a local browser to view edits in real-time. When we run hugo server we will need to make sure that it is bound to the correct interface. This is why we mapped port 1313 back in Run the nauci/ox-hugo_deps image and install Go.

ox-hugo_deps ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

Since the only external interface my container has configured is eth0 I know that I need to bind to IP 172.17.0.2.

hugo server --bind 172.17.0.2 -D
Building sites …
                   | EN
+------------------+----+
  Pages            | 35
  Paginator pages  |  1
  Non-page files   |  0
  Static files     |  5
  Processed images |  0
  Aliases          |  1
  Sitemaps         |  1
  Cleaned          |  0

Total in 62 ms
Watching for changes in /shared/dustfinger/dev/org-publish/bloggerbust.ca/{content,data,layouts,static,themes}
Watching for config changes in /shared/dustfinger/dev/org-publish/bloggerbust.ca/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //localhost:1313/ (bind address 172.17.0.2)
Press Ctrl+C to stop

Now you should be able to browse your blog from localhost:1313 and the request will be mapped to the container through port 1313 with the hugo server listening on bind address 172.17.0.2. Any edits that you make using ox-hugo should be noticed by the hugo server running inside of the container and the change should be pushed to the local browser automatically.

Sorry, but I have not yet decided on a good screen capture tool, so for now I took a picture with my phone. I tried frameshot, but it is not capturing the rendered content in the Firefox buffer. The window containing Firefox just shows up blank in the screen capture.

Want to leave a comment?

To leave a comment, please create an issue in GitHub labelled article comment linking back to this article in the opening paragraph.


Comments

Your comment has been submitted and is now pending moderation

Be the first to comment on this article.