Using Shellcheck and Docker to Automatically Lint Dotfiles

March 8, 2018
shell dotfiles docker travis

In order to prevent errors and side effects, it’s useful to use Shellcheck to lint all shell scripts. While checking out the Dotfiles of jessfraz, I came across an easy way to integrate this kind of check with Travis CI. The mentioned approach triggers a travis linting process after pushing to to the Dotfiles repository on GitHub. This post will explain all necessary steps to integrate this process into your own repository. All credits go to the original author of the docker images and scripts, of course.

Activating Travis CI

First of all, it’s necessary to link your Github account to an account on Travis CI. To do this, just sign in at the Travis page using your GitHub account. After that, you need to activate Travis for your specific Dotfile repository. You can do this after logging in at Travis. This will add a hook to your repository on GitHub which will trigger the Travis build/linting process after pushing new content.

Adding a Travis job file

To let Travis know which commands should be executed, a .travis.yml file is needed. This can be as simple as:

sudo: required
dist: trusty

services:
  - docker

before_script:
  - // install required packages for the linting or notification process (optional).

script:
  - |
      docker run --rm -i \
          --name df-shellcheck \
          -v $PWD:/usr/src:ro \
          --workdir /usr/src \
          r.j3ss.co/shellcheck ./.test.sh

after_success:
  - // e.g. send notification via Telegram.

after_failure:
  - // dito.

This will use a containerized version of shellcheck to lint all existing scripts using .test.sh, which will be prepared in the next step.

Preparing the test script

The test script has two jobs:

  1. Get all files which will be linted
  2. Execute shellcheck for each file

    #!/bin/bash
    # Stolen from: https://github.com/jessfraz/dotfiles/blob/master/test.sh
    # (modified)
    
    set -e
    set -o pipefail
    
    ERRORS=()
    
    # find all executables and run `shellcheck`
    for f in $(find . -type f \
        -not -iwholename '*.git*' | sort -u); do
    
    
    	if file "$f" | grep --quiet shell; then
    		{
    			shellcheck "$f" && echo "[OK]: sucessfully linted $f"
    		} || {
    		# add to errors
    		ERRORS+=("$f")
    	}
    	fi
    done
    
    if [ ${#ERRORS[@]} -eq 0 ]; then
    	echo "No errors, hooray"
    else
    	echo "These files failed shellcheck: ${ERRORS[*]}"
    	exit 1
    fi
    

Executing tests

If everything has been set up correctly, the output can show the following information:

docker run --rm -i -t \
		--name df-shellcheck \
		-v /home/travis/build/ps1337/Dotfiles:/usr/src:ro \
		--workdir /usr/src \
		r.j3ss.co/shellcheck ./.test.sh
Unable to find image 'r.j3ss.co/shellcheck:latest' locally
latest: Pulling from shellcheck
bec7afd8e02c: Pulling fs layer
245090c4c10c: Pulling fs layer
cb3796b8dbab: Pulling fs layer
245090c4c10c: Verifying Checksum
245090c4c10c: Download complete
cb3796b8dbab: Verifying Checksum
cb3796b8dbab: Download complete
bec7afd8e02c: Verifying Checksum
bec7afd8e02c: Download complete
bec7afd8e02c: Pull complete
245090c4c10c: Pull complete
cb3796b8dbab: Pull complete
Digest: sha256:8c217eaf09cc2a6b960be4931ea2853ef339c052a890ef427e8de4da0cfa42ca
Status: Downloaded newer image for r.j3ss.co/shellcheck:latest
[OK]: sucessfully linted ./.test.sh
[OK]: sucessfully linted ./bash_profile
[OK]: sucessfully linted ./bindings
[OK]: sucessfully linted ./dockerfunc
[OK]: sucessfully linted ./exports
[OK]: sucessfully linted ./i3/polybar/launchPolybar.sh
[OK]: sucessfully linted ./i3/scripts/checkupdates.sh
[OK]: sucessfully linted ./i3/scripts/launch-alt-tab-daemon.sh
[OK]: sucessfully linted ./i3/scripts/launchBrowser.sh
[OK]: sucessfully linted ./i3/scripts/launchFilemanager.sh
[OK]: sucessfully linted ./i3/scripts/launchTerminal.sh
[OK]: sucessfully linted ./i3/scripts/mediacontrol.sh
[OK]: sucessfully linted ./i3/scripts/monitors.sh
[OK]: sucessfully linted ./i3/scripts/randomwallpaper.sh
[OK]: sucessfully linted ./i3/scripts/setbrightness.sh
[OK]: sucessfully linted ./inputrc
[OK]: sucessfully linted ./profile
No errors, hooray

Bonus: Telegram notifications

If you want to get notified after the linting process, you can refer to my Travis configuration and one of my previous blog posts on this subject.

Credits

How I Over-Engineered My Dotfiles

October 16, 2019
linux dotfiles

Information Leak in Docker

January 4, 2019
docker vulnerability

Docker Breakout Using X11

May 18, 2018
docker pentesting hacking