From 2e407d4d60853168d796cf53143d13ac67e51e9e Mon Sep 17 00:00:00 2001 From: Tim Perkins Date: Fri, 12 Jun 2026 15:17:13 -0400 Subject: [PATCH] Use bind mounts for Emacs config Use bind mounts instead of volumes for the config because they're more convenient. But there is some extra complexity here because we want the image to initialize the config, like it was doing for the volumes. We create tarballs containing the default config, and extract them when we detect that the config directories are empty. --- containers/base/Containerfile | 4 ++ containers/emacs/Containerfile | 57 ++++++++++++----------- containers/emacs/doom_cache/Containerfile | 15 ++++++ containers/emacs/emacs_init.sh | 31 ++++++++++++ containers/run.sh | 11 +++-- 5 files changed, 86 insertions(+), 32 deletions(-) create mode 100755 containers/emacs/emacs_init.sh diff --git a/containers/base/Containerfile b/containers/base/Containerfile index 1df7208..444d842 100644 --- a/containers/base/Containerfile +++ b/containers/base/Containerfile @@ -150,6 +150,10 @@ RUN echo "$DEV_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers \ ENV VOLS_DIR="/vols" RUN mkdir -m 755 $VOLS_DIR +# Create directory to hold volume initialization data +ENV VOLS_INIT_DIR="$VOLS_DIR/.init" +RUN mkdir -m 755 $VOLS_INIT_DIR + # Make a directory for shell config ENV SHELL_CONFIG_DIR="$VOLS_DIR/shell" RUN mkdir -m 755 $SHELL_CONFIG_DIR \ diff --git a/containers/emacs/Containerfile b/containers/emacs/Containerfile index 6350d34..95025a9 100644 --- a/containers/emacs/Containerfile +++ b/containers/emacs/Containerfile @@ -63,11 +63,8 @@ RUN curl -fL -o $FD_FILE "$FD_URL" \ && dpkg -i $FD_FILE \ && rm -f $FD_FILE -# Switch to non-root user -USER $DEV_USER -WORKDIR $DEV_USER_HOME -# Doom Emacs variables +# Doom Emacs variables from the cache image ARG DE_EMACS_DIR="/vols/emacs.d" ARG DE_DOOM_DIR="/vols/doom.d" @@ -75,21 +72,38 @@ ARG DE_DOOM_DIR="/vols/doom.d" # organization. Copying those symlinks will break them, unless the absolute # paths stay the same. That is to say, $DE_EMACS_DIR must equal # $EMACS_CONFIG_DIR, etc. -ENV EMACS_CONFIG_DIR="$DE_EMACS_DIR" -ENV DOOM_CONFIG_DIR="$DE_DOOM_DIR" +ENV EMACS_CONFIG_DIR=$DE_EMACS_DIR +ENV DOOM_CONFIG_DIR=$DE_DOOM_DIR # Make blank directories to ensure permissions are set correctly -RUN sudo mkdir -m 755 $EMACS_CONFIG_DIR $DOOM_CONFIG_DIR \ - && sudo chown $DEV_USER:$DEV_USER $EMACS_CONFIG_DIR $DOOM_CONFIG_DIR +RUN mkdir -m 755 $EMACS_CONFIG_DIR $DOOM_CONFIG_DIR \ + && chown $DEV_USER:$DEV_USER $EMACS_CONFIG_DIR $DOOM_CONFIG_DIR + +# Add Doom executables to PATH +ENV PATH=$EMACS_CONFIG_DIR/bin:$PATH + +# Set environment variables used by Doom +ENV EMACSDIR=$EMACS_CONFIG_DIR +ENV DOOMDIR=$DOOM_CONFIG_DIR + +# Tarballs which will be made to initialize Emacs volumes +ENV EMACS_CONFIG_TARBALL="$VOLS_INIT_DIR/emacs.d.tar.gz" +ENV DOOM_CONFIG_TARBALL="$VOLS_INIT_DIR/doom.d.tar.gz" + +# Create tarballs for the Emacs Doom config files +RUN --mount=type=bind,from=doom-cache,src=$DE_EMACS_DIR,dst=$EMACS_CONFIG_DIR \ + tar -C $EMACS_CONFIG_DIR --create -z -f $EMACS_CONFIG_TARBALL . +RUN --mount=type=bind,from=doom-cache,src=$DE_DOOM_DIR,dst=$DOOM_CONFIG_DIR \ + tar -C $DOOM_CONFIG_DIR --create -z -f $DOOM_CONFIG_TARBALL . + +# Switch to non-root user +USER $DEV_USER +WORKDIR $DEV_USER_HOME # Add links to the home directory RUN ln -s $EMACS_CONFIG_DIR $DEV_USER_HOME/.emacs.d \ && ln -s $DOOM_CONFIG_DIR $DEV_USER_HOME/.doom.d -# Copy files from the build layer -COPY --from=doom-cache --chown=$DEV_USER:$DEV_USER $DE_EMACS_DIR $EMACS_CONFIG_DIR -COPY --from=doom-cache --chown=$DEV_USER:$DEV_USER $DE_DOOM_DIR $DOOM_CONFIG_DIR - # Doom font variables ARG DE_NERDFONT_URL="https://raw.githubusercontent.com/rainstormstudio/nerd-icons.el/main/fonts/NFM.ttf" ARG DE_NERDFONT_FILE="$DEV_USER_HOME/.local/share/fonts/NFM.ttf" @@ -99,20 +113,6 @@ RUN mkdir -p $(dirname $DE_NERDFONT_FILE) \ && curl -fL -o $DE_NERDFONT_FILE "$DE_NERDFONT_URL" \ && fc-cache -f -# Add Doom executables to PATH -ENV PATH=$EMACS_CONFIG_DIR/bin:$PATH - -# Set environment variables used by Doom -ENV EMACSDIR=$DE_EMACS_DIR -ENV DOOMDIR=$DE_DOOM_DIR - -# Build the VTerm module. We must locate the exact build directory because it's -# name depends on the Emacs version, e.g., `build-28.1`, etc. -RUN VTERM_BUILD_DIR=$(fd -uu -p "build-[0-9.]+/vterm$" $EMACS_CONFIG_DIR) \ - && cd $VTERM_BUILD_DIR \ - && cmake -S . -B build \ - && cmake --build build - # Add VTerm integration to Bash config RUN { \ echo; \ @@ -124,11 +124,14 @@ RUN { \ echo "fi"; \ } >> $DEV_USER_HOME/.bashrc +# Copy entry point for config initialization +COPY emacs_init.sh $ENTRYPOINT_DIR/80_emacs_init.sh + # Persistent storage for the Emacs and Doom config directories. These # directories will be initialized with the Doom files from above. VOLUME ["$EMACS_CONFIG_DIR", "$DOOM_CONFIG_DIR"] -# Copy the Emacs shim for the entry point +# Copy the Emacs shim for the default command COPY emacs_shim.sh /emacs_shim.sh # Launch Emacs by default diff --git a/containers/emacs/doom_cache/Containerfile b/containers/emacs/doom_cache/Containerfile index 7ecade0..d797908 100644 --- a/containers/emacs/doom_cache/Containerfile +++ b/containers/emacs/doom_cache/Containerfile @@ -61,3 +61,18 @@ RUN mkdir -p $DE_DOOM_DIR \ && cp $DE_EMACS_DIR/static/packages.example.el $DE_DOOM_DIR/packages.el \ && sed -e "s/[(]\?evil/;;&/" -e "s/;;\([(]\?vterm\)/\1/" -i $DE_DOOM_DIR/init.el \ && doom install --force --verbose --no-config --no-env --no-hooks + +# Install dependencies for building VTerm +RUN apt-get update \ + && apt-get install -y \ + build-essential \ + cmake \ + libtool-bin \ + && rm -rf /var/lib/apt/lists/* + +# Build the VTerm module. We must locate the exact build directory because it's +# name depends on the Emacs version, e.g., `build-28.1`, etc. +RUN VTERM_BUILD_DIR=$(find "$DE_EMACS_DIR" -path "*/build-[0-9.]*/vterm") \ + && cd $VTERM_BUILD_DIR \ + && cmake -S . -B build \ + && cmake --build build diff --git a/containers/emacs/emacs_init.sh b/containers/emacs/emacs_init.sh new file mode 100755 index 0000000..6b80702 --- /dev/null +++ b/containers/emacs/emacs_init.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Copyright (c) 2023 Tim Perkins + +set -o errexit +set -o nounset +set -o pipefail +IFS=$'\n\t' + +# This should never happen, but check anyway +if [ -z "${EMACS_CONFIG_DIR:-}" -o -z "${EMACS_CONFIG_TARBALL:-}" \ + -o -z "${DOOM_CONFIG_DIR:-}" -o -z "${DOOM_CONFIG_TARBALL:-}" ]; then + echo "ERROR: Essential Emacs variables are not defined!" >&2 + exit 1 +fi + +is_empty_dir() { + find "$1" -maxdepth 0 -type d -empty | grep -q . +} + +if is_empty_dir $EMACS_CONFIG_DIR; then + echo "Initializing Emacs config: $EMACS_CONFIG_DIR" + tar -C $EMACS_CONFIG_DIR --extract -f $EMACS_CONFIG_TARBALL +fi + +if is_empty_dir $DOOM_CONFIG_DIR; then + echo "Initializing Doom config: $DOOM_CONFIG_DIR" + tar -C $DOOM_CONFIG_DIR --extract -f $DOOM_CONFIG_TARBALL +fi + +exec "$@" diff --git a/containers/run.sh b/containers/run.sh index f90feda..3b27d71 100755 --- a/containers/run.sh +++ b/containers/run.sh @@ -13,8 +13,6 @@ DEFAULT_TARGET_TAG="main" # The names of the volumes SHELL_CONFIG_VOL="shell-config" -EMACS_CONFIG_VOL="emacs-config" -DOOM_CONFIG_VOL="doom-config" # The default projects directory DEFAULT_PROJECTS_DIR="$HOME/Projects" @@ -141,7 +139,7 @@ EMACS_CONFIG_DIR=$(get_from_env "$IMAGE_ENV" "EMACS_CONFIG_DIR" || true) DOOM_CONFIG_DIR=$(get_from_env "$IMAGE_ENV" "DOOM_CONFIG_DIR" || true) # Check for volumes, create them if necessary -vols=($SHELL_CONFIG_VOL $EMACS_CONFIG_VOL $DOOM_CONFIG_VOL) +vols=($SHELL_CONFIG_VOL) for vol in "${vols[@]}"; do if ! docker volume ls -q | grep -q $vol; then echo "Creating volume: $vol" >&2 @@ -157,6 +155,9 @@ ensure_exists f 644 $HOME/.gitconfig ensure_exists d 700 $HOME/.xpra ensure_exists f 600 $HOME/.claude.json ensure_exists d 700 $HOME/.claude +ensure_exists d 700 $HOME/.taughz +ensure_exists d 700 $HOME/.taughz/emacs.d +ensure_exists d 700 $HOME/.taughz/doom.d # Get the user data ready passwd_ent=$(getent passwd $(id -u)) @@ -214,8 +215,8 @@ SHELL_FLAGS=( emacs_flags=() if [ -n "$EMACS_CONFIG_DIR" -a -n "$DOOM_CONFIG_DIR" ]; then - emacs_flags+=(--mount "type=volume,src=$EMACS_CONFIG_VOL,dst=$EMACS_CONFIG_DIR") - emacs_flags+=(--mount "type=volume,src=$DOOM_CONFIG_VOL,dst=$DOOM_CONFIG_DIR") + emacs_flags+=(--mount "type=bind,src=$HOME/.taughz/emacs.d,dst=$EMACS_CONFIG_DIR") + emacs_flags+=(--mount "type=bind,src=$HOME/.taughz/doom.d,dst=$DOOM_CONFIG_DIR") fi projects_flags=()