From f1f2d9c2e588fec628125734815271e2416e0cbe Mon Sep 17 00:00:00 2001 From: daniel fusser Date: Thu, 9 Jul 2026 00:33:52 +0200 Subject: [PATCH] add devcontainer --- .gitignore | 2 + devcontainer/Dockerfile | 117 ++++++++++++++++++++++++++++++++ devcontainer/docker-compose.yml | 21 ++++++ 3 files changed, 140 insertions(+) create mode 100644 devcontainer/Dockerfile create mode 100644 devcontainer/docker-compose.yml diff --git a/.gitignore b/.gitignore index e6441a4..091c833 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ !hooks/* !bin/ !bin/* +!devcontainer/ +!devcontainer/* !dircolors/ !dircolors/* !git/ diff --git a/devcontainer/Dockerfile b/devcontainer/Dockerfile new file mode 100644 index 0000000..7b89fdb --- /dev/null +++ b/devcontainer/Dockerfile @@ -0,0 +1,117 @@ +# Alpine dev container with dropbear SSH access and this dotfiles repo +# pre-cloned and installed. +# +# Build: +# docker build -t dev-container \ +# --build-arg USERNAME=dafu \ +# --build-arg GITHUB_USER=dafu \ +# devcontainer +# +# Run: +# docker run -d -p 2222:22 --name dev-container dev-container +# ssh -p 2222 dafu@localhost + +FROM alpine:latest + +ARG USERNAME=dafu +# public keys are pulled from https://github.com/.keys at build time +ARG GITHUB_USER=dafu +ARG REPO_URL=https://git.dafu.dev/dafu/dot.config.git +# gid of the docker group on the host, so it lines up with the mounted +# /var/run/docker.sock (check with: stat -c '%g' /var/run/docker.sock) +ARG DOCKER_GID=104 + +# edge repos, aliased so packages stay on the stable branch by default; +# pull a specific package from edge with `apk add @edge` +RUN cat <<'EOF' > /etc/apk/repositories +https://dl-cdn.alpinelinux.org/alpine/edge/main +https://dl-cdn.alpinelinux.org/alpine/edge/community +https://dl-cdn.alpinelinux.org/alpine/edge/testing +EOF +RUN apk update + +RUN apk add --no-cache \ + dropbear htop fd fzf fzf-tmux gnupg age go-task gopass ripgrep lnav lazygit mysql-client \ + nmap p7zip restic rsync shellcheck tree-sitter tree-sitter-cli tree-sitter-go tree-sitter-markdown \ + tzdata zk \ + git go \ + neovim \ + tmux \ + mksh \ + curl \ + foot-extra-terminfo \ + doas \ + docker-cli \ + tini + +RUN ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime && \ + echo "Europe/Berlin" > /etc/timezone +ENV TZ=Europe/Berlin + +# make mksh a valid login shell +RUN echo "/bin/mksh" >> /etc/shells + +# create the user with mksh as its shell, no password (pubkey-only login) +RUN adduser -D -s /bin/mksh "$USERNAME" + +# let the user talk to the docker socket mounted in at runtime without doas: +# reuse whatever group already owns DOCKER_GID, or create "docker" if free +RUN addgroup -g "$DOCKER_GID" docker 2>/dev/null; \ + docker_group="$(getent group "$DOCKER_GID" | cut -d: -f1)" && \ + addgroup "$USERNAME" "$docker_group" + +# passwordless doas for package installs etc. (no password auth is set up +# for the user at all, so this must be nopass) +RUN echo "permit nopass $USERNAME as root" > /etc/doas.conf && \ + chown root:root /etc/doas.conf && \ + chmod 0400 /etc/doas.conf + +# ssh access: authorized_keys sourced from GitHub, pubkey auth only +RUN mkdir -p "/home/$USERNAME/.ssh" && \ + curl -fsSL "https://github.com/${GITHUB_USER}.keys" -o "/home/$USERNAME/.ssh/authorized_keys" && \ + test -s "/home/$USERNAME/.ssh/authorized_keys" && \ + chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/.ssh" && \ + chmod 700 "/home/$USERNAME/.ssh" && \ + chmod 600 "/home/$USERNAME/.ssh/authorized_keys" + +# clone the dotfiles repo and run its installer as the login user +USER $USERNAME +RUN git clone "$REPO_URL" "/home/$USERNAME/.config" && \ + "/home/$USERNAME/.config/install.sh" +USER root + +RUN mkdir -p /etc/dropbear + +# crontab spool lives on the home-data volume so it survives rebuilds; the +# standard /var/spool/cron/crontabs path is symlinked to it so crond and +# `crontab -e` keep working with no extra flags +RUN mkdir -p "/home/$USERNAME/.crontabs" && \ + touch "/home/$USERNAME/.crontabs/$USERNAME" && \ + chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/.crontabs" && \ + chmod 700 "/home/$USERNAME/.crontabs" && \ + chmod 600 "/home/$USERNAME/.crontabs/$USERNAME" && \ + mkdir -p /var/spool/cron && \ + ln -s "/home/$USERNAME/.crontabs" /var/spool/cron/crontabs + +RUN <<'EOF' cat > /usr/local/bin/entrypoint.sh +#!/bin/sh +set -e + +# host keys are generated on first start rather than at build time, so a +# shared private key never ends up baked into the image. +for t in rsa ed25519; do + key="/etc/dropbear/dropbear_${t}_host_key" + [ -f "$key" ] || dropbearkey -t "$t" -f "$key" +done + +# busybox crond daemonizes itself by default (no -f), so this returns +# immediately; tini (real PID 1) reaps it and any of its orphaned children +crond -L /dev/stdout + +# -F: foreground, -E: log to stderr, -w: no root login, -s: no password auth +exec dropbear -F -E -w -s -p 22 +EOF +RUN chmod +x /usr/local/bin/entrypoint.sh + +EXPOSE 22 +ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"] diff --git a/devcontainer/docker-compose.yml b/devcontainer/docker-compose.yml new file mode 100644 index 0000000..f272c69 --- /dev/null +++ b/devcontainer/docker-compose.yml @@ -0,0 +1,21 @@ +services: + dev: + build: + context: . + dockerfile: Dockerfile + args: + USERNAME: dafu + GITHUB_USER: dafu + REPO_URL: https://git.dafu.dev/dafu/dot.config.git + DOCKER_GID: "104" + container_name: dev-container + hostname: dev-container + ports: + - "2222:22" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - home-data:/home/dafu + restart: unless-stopped + +volumes: + home-data: