From b05b07345084c415e6da431a5e247ac9afa09065 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Wed, 6 May 2020 15:44:59 +0300 Subject: fix GitHub action tests That's a messy commit, but it required a lot of changes to get everything right. * Docker: create user jekyll with UID/GID that match the user that built the image (for seamless writes to /project). * Docker: run the container by the current user for the same purpose. * Docker: add an ENTRYPOINT to drop root privileges & check if the running user is the same as the one who built the image. * Jekyll: use --drafts. * Makefile: add docker/logs. As a side note, Docker + non-root users + bind mounts are a pain, I even wrote a blog post to make sense of it all: https://egor-tensin.github.io/blog/2020/05/06/docker-bind-mounts.html --- .dockerignore | 1 + .gitattributes | 2 ++ Dockerfile.base | 68 ++++++++++++++++++++++++++++++++++++---------------- Dockerfile.project | 13 +++++----- Makefile | 24 ++++++++++++++----- docker-compose.yml | 7 ++++-- docker-entrypoint.sh | 32 +++++++++++++++++++++++++ 7 files changed, 112 insertions(+), 35 deletions(-) create mode 100755 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore index 5aded0c..6d35443 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ * !/Makefile +!/docker-entrypoint.sh diff --git a/.gitattributes b/.gitattributes index 176a458..d76765e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ * text=auto + +*.sh text eol=lf diff --git a/Dockerfile.base b/Dockerfile.base index fbb7e5c..f56a9b3 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -7,32 +7,58 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt update -yq && \ apt install -yq --no-install-recommends \ build-essential \ - ca-certificates wget \ + ca-certificates gnupg wget \ sudo \ nano vim -# Creating regular user 'developer': -ARG JEKYLL_USER=developer -ENV JEKYLL_USER="$JEKYLL_USER" -RUN addgroup "$JEKYLL_USER" && \ - adduser --disabled-password --gecos "" --ingroup "$JEKYLL_USER" --home "/home/$JEKYLL_USER" "$JEKYLL_USER" && \ - addgroup "$JEKYLL_USER" sudo && \ +# Install gosu (better sudo, basically). +ENV GOSU_VERSION 1.12 +RUN DPKG_ARCH="$( dpkg --print-architecture | awk -F- '{ print $NF }' )" && \ + wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$DPKG_ARCH" && \ + wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$DPKG_ARCH.asc" && \ + export GNUPGHOME="$( mktemp -d )" && \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 && \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu && \ + gpgconf --kill all && \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc && \ + chmod +x /usr/local/bin/gosu && \ + gosu nobody true + +ENV DEFAULT_UID=999 +ENV DEFAULT_GID="$DEFAULT_UID" + +ARG JEKYLL_UID +ARG JEKYLL_GID +RUN test -n "$JEKYLL_UID" && test -n "$JEKYLL_GID" +ENV JEKYLL_UID="$JEKYLL_UID" +ENV JEKYLL_GID="$JEKYLL_GID" + +RUN if [ "$JEKYLL_UID" = 0 ]; then JEKYLL_UID="$DEFAULT_UID"; fi && \ + if [ "$JEKYLL_GID" = 0 ]; then JEKYLL_GID="$DEFAULT_GID"; fi && \ + addgroup --gid "${JEKYLL_GID:-$DEFAULT_GID}" jekyll && \ + adduser \ + --disabled-password \ + --gecos '' \ + --home /home/jekyll \ + --ingroup jekyll \ + --uid "${JEKYLL_UID:-$DEFAULT_UID}" \ + jekyll && \ + addgroup jekyll sudo && \ echo -e '%sudo ALL=(ALL) NOPASSWD:ALL\nDefaults env_keep += "HOME"' >> /etc/sudoers -USER "$JEKYLL_USER" -ENV PATH="/home/$JEKYLL_USER/.local/bin:$PATH" +RUN mkdir /utils && chown jekyll /utils +WORKDIR /utils +COPY --chown=jekyll:jekyll ["Makefile", "./"] -ENV MAKEFILE_DIR="/utils" -RUN sudo mkdir -p -- "$MAKEFILE_DIR" && \ - sudo chown -- "$JEKYLL_USER:$JEKYLL_USER" "$MAKEFILE_DIR" -WORKDIR "$MAKEFILE_DIR" +ENV PATH="/home/jekyll/.local/bin:$PATH" -COPY --chown="$JEKYLL_USER:$JEKYLL_USER" ["Makefile", "./"] +RUN gosu jekyll make ruby-install && \ + gosu jekyll make ruby-install/clean && \ + gosu jekyll make ruby && \ + gosu jekyll make chruby && \ + gosu jekyll make chruby/.bashrc && \ + gosu jekyll make chruby/clean && \ + gosu jekyll make bundler -RUN make ruby-install && \ - make ruby-install/clean && \ - make ruby && \ - make chruby && \ - sudo make chruby/profile.d && \ - make chruby/clean && \ - make bundler +COPY ["docker-entrypoint.sh", "/"] +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/Dockerfile.project b/Dockerfile.project index bae05e6..c9e0b50 100644 --- a/Dockerfile.project +++ b/Dockerfile.project @@ -1,10 +1,11 @@ FROM jekyll_base -ENV PROJECT_DIR="/project" -RUN sudo mkdir -p -- "$PROJECT_DIR" && \ - sudo chown -- "$JEKYLL_USER:$JEKYLL_USER" "$PROJECT_DIR" +ENV PROJECT_DIR=/project +RUN mkdir /project && chown jekyll /project +VOLUME /project + +COPY --chown=jekyll:jekyll ["Gemfile", "Gemfile.lock", "/project/"] +RUN gosu jekyll make dependencies +COPY --chown=jekyll:jekyll [".", "/project/"] -COPY --chown="$JEKYLL_USER:$JEKYLL_USER" ["Gemfile", "Gemfile.lock", "$PROJECT_DIR/"] -RUN make dependencies -COPY --chown="$JEKYLL_USER:$JEKYLL_USER" [".", "$PROJECT_DIR/"] CMD make jekyll/serve diff --git a/Makefile b/Makefile index df15d22..fe2a34f 100644 --- a/Makefile +++ b/Makefile @@ -56,17 +56,21 @@ chruby/uninstall: chruby/clean: rm -rf -- '$(chruby_archive)' '$(chruby_dir)' -define chruby_profile_d +define chruby_source if [ -n "$$BASH_VERSION" ] || [ -n "$$ZSH_VERSION" ]; then [ -r '$(chruby_sh)' ] && source '$(chruby_sh)' [ -r '$(auto_sh)' ] && source '$(auto_sh)' fi endef -export chruby_profile_d +export chruby_source + +.PHONY: chruby/.bashrc +chruby/.bashrc: + echo "$$chruby_source" >> ~/.bashrc .PHONY: chruby/profile.d chruby/profile.d: - echo "$$chruby_profile_d" > /etc/profile.d/chruby.sh + echo "$$chruby_source" > /etc/profile.d/chruby.sh .PHONY: chruby/profile.d/clean chruby/profile.d/clean: @@ -97,11 +101,15 @@ deps/update: dependencies/update .PHONY: jekyll/build jekyll/build: - $(jekyll) build --config _config.yml,_config_dev.yml + $(jekyll) build --drafts --config _config.yml,_config_dev.yml .PHONY: jekyll/serve jekyll/serve: - $(jekyll) serve --host 0.0.0.0 --config _config.yml,_config_dev.yml + $(jekyll) serve --drafts --config _config.yml,_config_dev.yml --host 0.0.0.0 + +JEKYLL_UID ?= $(shell id -u) +JEKYLL_GID ?= $(shell id -g) +export JEKYLL_UID JEKYLL_GID # Not an absolute path, cause you know, Windows (more specifically, Cygwin + # native Windows docker-compose). @@ -109,12 +117,16 @@ docker_compose := cd -- '$(makefile_dir)' && docker-compose --env-file ./.env .PHONY: docker/build docker/build: - $(docker_compose) build --force-rm + $(docker_compose) build --force-rm --build-arg 'JEKYLL_UID=$(JEKYLL_UID)' --build-arg 'JEKYLL_GID=$(JEKYLL_GID)' .PHONY: docker/up docker/up: $(docker_compose) up -d project +.PHONY: docker/logs +docker/logs: + $(docker_compose) logs + .PHONY: docker/down docker/down: $(docker_compose) down -v diff --git a/docker-compose.yml b/docker-compose.yml index 97aa312..5ed8a86 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,13 +20,15 @@ services: context: . dockerfile: Dockerfile.base args: - JEKYLL_USER: developer + - JEKYLL_UID + - JEKYLL_GID + user: "$JEKYLL_UID:$JEKYLL_GID" project: build: context: "${PROJECT_DIR:-..}" # Dockerfile outside of the build context. # This is supposedly supported by Docker since 18.03, but I couldn't find - # a less hacky way to do it. + # a less hacky way to do it in docker-compose. # Source: https://github.com/docker/compose/issues/4926. dockerfile: "$PWD/Dockerfile.project" depends_on: @@ -35,5 +37,6 @@ services: - base ports: - 4000:4000 + user: "$JEKYLL_UID:$JEKYLL_GID" volumes: - "${PROJECT_DIR:-..}:/project" diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..4b4fb4d --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# We make sure that the container is run by the same user as the one who built +# the image (so that /project is seamlessly writable). +# Unless, of course, the image was built by root, in which case we fall back +# to a custom user with UID 999. + +set -o errexit -o nounset -o pipefail + +echo 'User info:' +id +uid="$( id -u )" +gid="$( id -g )" + +if [ "$uid" = 0 ]; then + echo 'Going to run as jekyll instead of root, fixing /project permissions...' + chown -R -- jekyll:jekyll /project + exec gosu jekyll "$0" "$@" +fi + +if [ "$uid" != "$JEKYLL_UID" ] && [ "$JEKYLL_UID" != 0 ]; then + echo "User jekyll was created with ID $JEKYLL_UID, are you sure you want to run the container with UID $uid?" + exit 1 +fi + +if [ "$gid" != "$JEKYLL_GID" ] && [ "$JEKYLL_GID" != 0 ]; then + echo "Group jekyll was created with ID $JEKYLL_GID, are you sure you want to run the container with GID $gid?" + exit 1 +fi + +echo "The container is running with UID $uid and GID $gid, just as planned..." +exec "$@" -- cgit v1.2.3