diff --git a/.gitignore b/.gitignore index 57fce96f..4758aaf1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Gemfile.lock *.swp examples/make_example/build examples/temp_sensor/build +docker/.env diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..e3c60932 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,30 @@ +# CMock Docker environment for development + +A Docker-based virtual development environment based on Arch. The container uses SSH keys from the host OS, loaded as Read-only when starting the container (executing `./run.sh`), allowing to use host OS SSH authentication. Keys are not persistent in the image, and are only injected when starting the container. The only persistent things in the image is the workspace and the configuration scripts placed inside the image during building. The environment comes with pre-configured Vim with Clangd support. The `run.sh` is configured to clean up the container when you call `exit` from inside, to avoid any hanging running containers. The container does not affect any files or settings on your host OS. + +# Instruction + +1. Install Docker for your host OS; + +2. Generate your SSH keys on the host OS if you don't have any yet, and add them in your GitHub account settings to be able to contribute; + +3. Fork the CMock repository, so that the Docker environment can resolve it from your GitHub and clone the fork into the workspace during first run. + +4. Create `.env` file locally in your cloned repository, with `GIT_USER_NAME` and `GIT_USER_EMAIL` variables set; + Example: + ``` + // .env + GIT_USER_NAME=TheJourneymansGuide + GIT_USER_EMAIL=k.woj.coding@gmail.com + ``` + This allows `git` inside the container to resolve your fork of the CMock repo, and to know what user name and e-mail to use when you commit something; + +5. Run `./setup.sh` - this will create a docker volume for workspace persistance on your local machine, and will create the environment image; + +6. Run `./run.sh` - this will start the image. If the CMock repository is not present in workspace, it will automatically get cloned during the first start. + +7. \* If you would like to attach to the container with a second terminal, the `attach.sh` script will automatically resolve your current cmock-dev-arch instance and connect to it (executed as: `./attach.sh`); + +> __IMPORTANT__: Be careful when you run multiple terminals in the same container. If you close the 'root' terminal that started the container, it will kill the container and subsequently disconnect all the other terminals from it. + +> __NOTE__: Make sure you have Unicode-compliant font to enable the fish terminal to display symbols instead of rectangles. Example fonts are `Fira Code` and `Cascadia Code`. diff --git a/docker/Resources/Dockerfile b/docker/Resources/Dockerfile new file mode 100644 index 00000000..ae38e2c9 --- /dev/null +++ b/docker/Resources/Dockerfile @@ -0,0 +1,119 @@ +#syntax=docker/dockerfile:1.6 + +FROM archlinux:latest + +ARG GIT_USER_NAME="ThrowTheSwitch" +ENV GIT_USER_NAME=${GIT_USER_NAME} + +# Set the locale +ENV TERM=xterm-256color +ENV STARSHIP_CONFIG=/etc/starship/starship.toml + +# Install basic dependencies for the system +RUN pacman -Syu --noconfirm && \ + pacman -S --noconfirm \ + glibc \ + base \ + base-devel \ + sudo \ + bash \ + less + +ENV PATH="/usr/bin:$PATH" + +RUN pacman -Syu --noconfirm \ + git \ + curl \ + wget + +RUN pacman -Syu --noconfirm \ + openssh \ + ca-certificates \ + zip \ + unzip \ + vim \ + tree + +RUN pacman -Syu --noconfirm \ + fish \ + starship \ + noto-fonts \ + noto-fonts-emoji \ + fzf \ + ripgrep \ + fd \ + universal-ctags + +# Create dev user +ARG USERNAME=Dev +ARG UID=1000 +ARG GID=1000 + +RUN groupadd -g ${GID} ${USERNAME} && \ + useradd -m -u ${UID} -g ${GID} -s /usr/bin/fish ${USERNAME} + +RUN echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${USERNAME} && \ + chmod 0440 /etc/sudoers.d/${USERNAME} + +# Install development dependencies +RUN pacman -Syu --noconfirm \ + make \ + cmake \ + ninja + +RUN pacman -Syu --noconfirm \ + bash-language-server \ + fzf + +RUN pacman -Syu --noconfirm \ + llvm \ + clang \ + clang-tools-extra + +RUN pacman -Syu --noconfirm \ + gdb \ + valgrind \ + gtest + + +RUN pacman -Syu --noconfirm \ + ruby \ + ruby-irb \ + ruby-rake \ + ruby-bundler + +# Seed the Docker image with CMock repository and its dependencies +RUN git clone "https://github.com/${GIT_USER_NAME}/CMock.git" /opt/CMock && \ + cd /opt/CMock && \ + bundle install && \ + chown -R Dev:Dev /opt/CMock + +USER ${USERNAME} +ENV HOME=/home/Dev + +# Install Vim cofiguration +RUN sudo mkdir -p /usr/share/vim/vimfiles/autoload && \ + sudo curl -fLo /usr/share/vim/vimfiles/autoload/plug.vim \ + https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim + +COPY Resources/vim /home/Dev/.vim +COPY Resources/vim/.vimrc /home/Dev/.vimrc +RUN vim +'PlugInstall --sync' +qa + +# Copy and execute configuration scripts +RUN sudo mkdir -p /etc/fish/conf.d +RUN sudo mkdir -p /opt/container-scripts + +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +COPY Resources/init.sh /opt/container-scripts/init.sh +COPY Resources/logging.sh /opt/container-scripts/logging.sh +COPY Resources/fish-starship.fish /etc/fish/conf.d/starship.fish +COPY Resources/starship.toml /etc/starship/starship.toml + +RUN sudo chmod +x /opt/container-scripts/init.sh + +# Configure the entrypoint +WORKDIR /workspace +ENTRYPOINT ["/usr/bin/fish", "-lc", "/opt/container-scripts/init.sh; exec fish"] diff --git a/docker/Resources/fish-starship.fish b/docker/Resources/fish-starship.fish new file mode 100644 index 00000000..cc6e292f --- /dev/null +++ b/docker/Resources/fish-starship.fish @@ -0,0 +1,9 @@ +if status --is-interactive + starship init fish | source +end + +set -gx STARSHIP_LOG error +set -gx EDITOR nvim + +set -gx LANG C.UTF-8 +set -gx LC_ALL C.UTF-8 diff --git a/docker/Resources/init.sh b/docker/Resources/init.sh new file mode 100644 index 00000000..ce5c912b --- /dev/null +++ b/docker/Resources/init.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -e + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +source "$SCRIPT_DIR/logging.sh" + +section "Running container initialization.." + +if [[ -n "${GIT_USER_NAME:-}" ]]; then + git config --global user.name "$GIT_USER_NAME" +fi + +if [[ -n "${GIT_USER_EMAIL:-}" ]]; then + git config --global user.email "$GIT_USER_EMAIL" +fi + +git config --global credential.helper 'cache --timeout=36000' + +section "Git config inside container:" +log "User name: $(git config --global --get user.name || "(user.name not set)")" +log "User email: $(git config --global --get user.email || "(user.email not set)")" + +section "Searching for CMock forked repository" + +CMOCK_REPO="/workspace/CMock" +CMOCK_REFERENCE="/opt/CMock" + +if [[ ! -d "$CMOCK_REPO/.git" ]]; then + log "CMock repository not found in workspace. Seeding CMock into workspace volume" + cp -a "$CMOCK_REFERENCE" "$CMOCK_REPO" + cd $CMOCK_REPO && git remote set-url origin git@github.com:$GIT_USER_NAME/CMock.git + success "CMock seeded successfully" +else + success "CMock repository found in workspace" +fi + +echo; diff --git a/docker/Resources/logging.sh b/docker/Resources/logging.sh new file mode 100644 index 00000000..aaa9161b --- /dev/null +++ b/docker/Resources/logging.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +[[ -n "${__LOGGING_SH_LOADED:-}" ]] && return +__LOGGING_SH_LOADED=1 + +set -o pipefail + +if [[ -t 1 ]]; then + RED='\033[0;31m' + GREEN='\033[0;92m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + MAGENTA='\033[0;35m' + LIGHT_RED='\033[0;91m' + CYAN='\033[0;36m' + BOLD='\033[1m' + DIM='\033[2m' + RESET='\033[0m' + LIGHT_CYAN='\033[0;96m' + LIGHT_YELLOW='\033[0;93m' +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + MAGENTA='' + LIGHT_RED='' + CYAN='' + BOLD='' + DIM='' + RESET='' + LIGHT_CYAN='' + LIGHT_YELL0W='' +fi + +section() { echo -e "${BOLD}${LIGHT_RED} ==> ${RESET}$*"; } +log() { echo -e "${LIGHT_CYAN} [\u2139]${RESET} $*"; } +success() { echo -e "${LIGHT_CYAN} [\u2714]${RESET} $*\n"; } +warn() { echo -e "${YELLOW} [\u26A0]${RESET} $*"; } +error() { echo -e "${RED} [\u2716]${RESET} $*" >&2; } + +run() { + log "$*" + "$0" +} + +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + trap 'error "Command failed at line ${BASH_LINENO[0]}"' ERR +fi diff --git a/docker/Resources/starship.toml b/docker/Resources/starship.toml new file mode 100644 index 00000000..0a701b1b --- /dev/null +++ b/docker/Resources/starship.toml @@ -0,0 +1,28 @@ +add_newline = true + +[username] +format = " [\\u256D\\u2500$user]($style)@" +style_user = "bold red" +show_always = true + +[hostname] +format = "[$hostname]($style) in " +style = "bold dimmed red" + +[directory] +style = "purple" +truncate_to_repo = true + +[git_branch] +style = "bold cyan" + +[git_status] +style = "white" + +[cmd_duration] +format = " took [$duration]($style)" + +[character] +success_symbol = " [\\u2570\\u2500\\u03BB](bold red)" +error_symbol = " [\\u0D7](bold red)" +use_symbol_for_status = true diff --git a/docker/Resources/vim/.vimrc b/docker/Resources/vim/.vimrc new file mode 100644 index 00000000..ce2ee4ef --- /dev/null +++ b/docker/Resources/vim/.vimrc @@ -0,0 +1,4 @@ +source ~/.vim/plugins.vim +source ~/.vim/ui.vim +source ~/.vim/mappings.vim +source ~/.vim/lsp.vim diff --git a/docker/Resources/vim/lsp.vim b/docker/Resources/vim/lsp.vim new file mode 100644 index 00000000..ca3a1af4 --- /dev/null +++ b/docker/Resources/vim/lsp.vim @@ -0,0 +1,12 @@ +if executable('clangd') + au User lsp_setup call lsp#register_server({ + \ 'name': 'clangd' + \ 'cmd': {server_info->['clangd']}, + \ 'whitelist': ['c', 'cpp'], + \ }) +endif + +nnoremap gd :LspDefinition +nnoremap gr :LspReferences +nnoremap gi :LspImplementation +nnoremap K :LspHover diff --git a/docker/Resources/vim/mappings.vim b/docker/Resources/vim/mappings.vim new file mode 100644 index 00000000..d5a53cbb --- /dev/null +++ b/docker/Resources/vim/mappings.vim @@ -0,0 +1,6 @@ +nnoremap :Files +nnoremap :Buffers + +if executable($SHELL) + nnoremap t :vsplit | terminal fish +endif diff --git a/docker/Resources/vim/plugins.vim b/docker/Resources/vim/plugins.vim new file mode 100644 index 00000000..116d5d78 --- /dev/null +++ b/docker/Resources/vim/plugins.vim @@ -0,0 +1,13 @@ +call plug#begin('/home/Dev/.vim/plugged') + +" LSP +Plug 'prabirshrestha/vim-lsp' +Plug 'mattn/vim-lsp-settings' + +" Fuzzy finder +Plug 'junegunn/fzf.vim' + +" Language support +Plug 'sheerun/vim-polyglot' + +call plug#end() diff --git a/docker/Resources/vim/ui.vim b/docker/Resources/vim/ui.vim new file mode 100644 index 00000000..0d3be9ec --- /dev/null +++ b/docker/Resources/vim/ui.vim @@ -0,0 +1,14 @@ +set number +set relativenumber +set colorcolumn=120 +set signcolumn=yes +set cursorline + +syntax on +set background=dark + +if has("termguicolors") + set termguicolors +endif + +hi Normal ctermbg=NONE guibg=NONE diff --git a/docker/attach.sh b/docker/attach.sh new file mode 100755 index 00000000..8bf0d6a8 --- /dev/null +++ b/docker/attach.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -e + +IMAGE_NAME="cmock-dev-arch" +CONTAINER_WORKDIR="/workspace" + +export MSYS_NO_PATHCONV=1 + +CONTAINER_ID=$(docker ps --filter "ancestor=$IMAGE_NAME" --format "{{.ID}}" | head -n 1) + +if [ -z "$CONTAINER_ID" ]; then + error "No running container found for image '$IMAGE_NAME';" + exit 1 +fi + +success "Attaching to container $CONTAINER_ID..." + +docker exec -it -w "$CONTAINER_WORKDIR" "$CONTAINER_ID" fish diff --git a/docker/run.sh b/docker/run.sh new file mode 100755 index 00000000..1afe6a04 --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,40 @@ +#! /usr/bin/env bash +set -e + +IMAGE_NAME="cmock-dev-arch" +VOLUME_NAME="cmock-dev-workspace" +CONTAINER_WORKDIR="/workspace" + +export MSYS_NO_PATHCONV=1 + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +source "$SCRIPT_DIR/Resources/logging.sh" + +if [[ ! -f "$SCRIPT_DIR/.env" ]]; then + error "No .env file present. Please create it and set GIT_USER_NAME and GIT_USER_EMAIL before proceeding" + exit 1 +fi + +SSH_KEYS=() + +if [[ -f ${HOME}/.ssh/id_ed25519 ]]; then + SSH_KEYS+=("-v" "${HOME}/.ssh/id_ed25519:/home/Dev/.ssh/id_ed25519:ro") + SSH_KEYS+=("-v" "${HOME}/.ssh/id_ed25519.pub:/home/Dev/.ssh/id_ed25519.pub:ro") +fi + +if [[ -f ${HOME}/.ssh/id_rsa ]]; then + SSH_KEYS+=("-v" "${HOME}/.ssh/id_rsa:/home/Dev/.ssh/id_rsa:ro") + SSH_KEYS+=("-v" "${HOME}/.ssh/id_rsa.pub:/home/Dev/.ssh/id_rsa.pub:ro") +fi + +OS_TYPE="$(uname -s)" + +DOCKER_RUN_ARGS=( + -it + --rm + -v "${VOLUME_NAME}:${CONTAINER_WORKDIR}" + -w "${CONTAINER_WORKDIR}" + ${SSH_KEYS[@]} +) + +docker run --env-file .env ${DOCKER_RUN_ARGS[@]} "$IMAGE_NAME" diff --git a/docker/setup.sh b/docker/setup.sh new file mode 100755 index 00000000..0c992109 --- /dev/null +++ b/docker/setup.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -e + +VOLUME_NAME="cmock-dev-workspace" +IMAGE_NAME="cmock-dev-arch" +DOCKERFILE_PATH="./Resources/Dockerfile" +CONTEXT_DIR="." + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +source "$SCRIPT_DIR/Resources/logging.sh" + +if [[ ! -f "$SCRIPT_DIR/.env" ]]; then + error "No .env file present. Please create it and set GIT_USER_NAME and GIT_USER_EMAIL before proceeding" + exit 1 +fi + +set -a +source <(grep -v '^\s*#' $SCRIPT_DIR/.env | grep -v '^\s*$') +set +a + +section "Cleaning up dangling Docker volumes..." +docker volume prune -f + +success "Docker cleanup completed" + +section "Checking if Docker volume '$VOLUME_NAME' exists..." +if ! docker volume inspect "$VOLUME_NAME" > /dev/null 2>&1; then + log "Volume '$VOLUME_NAME' does not exist. Creating..." + docker volume create "$VOLUME_NAME" + success "Volume '$VOLUME_NAME' created." +else + success "Volume '$VOLUME_NAME' found." +fi + +echo "Building Docker image '$IMAGE_NAME' from $DOCKERFILE_PATH..." + +# Temporarily forward ssh to allow for cloning during image build +DOCKER_BUILDKIT=1 docker build \ + --build-arg GIT_USER_NAME=$GIT_USER_NAME \ + -t "$IMAGE_NAME" \ + -f "$DOCKERFILE_PATH" \ + "$CONTEXT_DIR" +success "Docker image '$IMAGE_NAME' built successfully." + +log "Setup complete. Volume: $VOLUME_NAME | Image: $IMAGE_NAME" +log "Run with ./run.sh" +echo;