Compare commits

5 Commits

Author SHA1 Message Date
daniel fusser f1f2d9c2e5 add devcontainer 2026-07-09 00:33:52 +02:00
daniel fusser e65c3331b4 switch tmux to individual sessions 2026-07-08 22:23:02 +02:00
daniel fusser b4b2ac91c2 mksh: auto-attach a shared tmux session per host on login
Guarded to only fire for interactive shells with a real tty, when
tmux isn't already active, and only if the tmux binary is found -
so it falls back to a plain shell if any of that isn't true. Not
exec'd, so an unexpected tmux exit also drops back to a normal shell
instead of closing the connection. Replaces manually running bin/tmx.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 19:35:47 +02:00
daniel fusser 3e44c74ce8 mksh: color the prompt symbol and hostname label by host
Same host-differentiation as the tmux statusbar: blue accent for
prosrvdemadev01, existing yellow/gray colors kept as the default
elsewhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 19:34:57 +02:00
daniel fusser c0de925634 tmux: color hostname and active tab by host
Adds a blue accent for prosrvdemadev01 in the status bar (hostname
and selected window name), keeping the existing color as the default
for all other hosts, via tmux's native host_short format conditional.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 19:33:52 +02:00
5 changed files with 166 additions and 4 deletions
+2
View File
@@ -6,6 +6,8 @@
!hooks/*
!bin/
!bin/*
!devcontainer/
!devcontainer/*
!dircolors/
!dircolors/*
!git/
+117
View File
@@ -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/<GITHUB_USER>.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 <pkg>@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"]
+21
View File
@@ -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:
+18 -2
View File
@@ -56,6 +56,12 @@ und="$(tput smul 2>/dev/null || echo '\e[4m')"
# Reset
rst="$(tput sgr 0 2>/dev/null || echo '\e[0m')" # Text Reset
# host-specific accent color (default stays as-is; override per host below)
case $HOSTNAME in
prosrvdemadev01) hostcol="$txtblu"; hostlblcol="$txtblu" ;;
*) hostcol="$txtylw"; hostlblcol="$txtgry" ;;
esac
# rust
export PATH=$PATH:$HOME/.cargo/bin
# go
@@ -191,7 +197,7 @@ PS4='[$EPOCHREALTIME] '; PS1='#'; (( USER_ID )) && PS1='→'; PS1=$'\001\r''${|
\\builtin typeset e=$?
(( e )) && REPLY+="${txtred}$e${txtgry} "
{ ( [[ -n $SSH_CLIENT ]] || [[ -n $SSH_TTY ]] ) && [[ -z $TMUX ]] } && REPLY+="${ita}${txtgry}${HOSTNAME} ${rst}"
{ ( [[ -n $SSH_CLIENT ]] || [[ -n $SSH_TTY ]] ) && [[ -z $TMUX ]] } && REPLY+="${ita}${hostlblcol}${HOSTNAME} ${rst}"
# REPLY+=${USER}@${HOSTNAME%%.*}:
\\builtin typeset d=${PWD:-?}/ p=~; [[ $p = ?(*/) ]] || d=${d/#$p\//\~/}
@@ -208,4 +214,14 @@ PS4='[$EPOCHREALTIME] '; PS1='#'; (( USER_ID )) && PS1='→'; PS1=$'\001\r''${|
print -n "\e]133;A\e\\" # foot sequence for ctrl+shift+x/z
\\builtin return $e
} '"${txtylw}${bld}$PS1 ${rst}"
} '"${hostcol}${bld}$PS1 ${rst}"
# tmux auto-attach: bare invocation so tmux.conf's default-client-command
# grants each connection its own session grouped with "main" (shared panes,
# independent window navigation). No -exec, so if tmux is missing or
# exits/crashes for any reason, this shell just continues normally.
tmuxbin=$(\\builtin whence -p tmux 2>/dev/null)
if [[ $- = *i* ]] && [[ -t 0 ]] && [[ -z $TMUX ]] && [[ -n $tmuxbin ]]; then
"$tmuxbin"
fi
unset tmuxbin
+8 -2
View File
@@ -4,6 +4,12 @@ set-option -g focus-events on
set -s escape-time 0
set -g detach-on-destroy off
set -g destroy-unattached off
# bare `tmux` (no subcommand, e.g. from mkshrc's auto-attach) gets its own
# session grouped with "main" - shared panes/windows, independent per-client
# current-window navigation. Self-cleans on detach via destroy-unattached.
new-session -d -s main
set -g default-client-command "new-session -t main ; set-option destroy-unattached on"
# set -g default-terminal "screen-256color"
set -g default-terminal "tmux-256color"
set -g history-limit 50000
@@ -163,12 +169,12 @@ set-window-option -g window-status-activity-style none
set-window-option -g window-status-separator ''
set -g status-left "#[fg=$COL_FG,bg=$COL_BG_DARK] #S #[fg=$COL_BG_DARK,bg=$COL_BG_VERY_DARK] "
set -g status-right "#[fg=color230] #h "
set -g status-right "#{?#{==:#{host_short},prosrvdemadev01},#[fg=colour4],#[fg=color230]} #h "
activity_color="#{?window_activity_flag,colour1,$COL_FG}"
status_color="#{?window_silence_flag,colour3,${activity_color}}"
set-window-option -g window-status-current-format "#[fg=$COL_BG_DARK] #{window_index} #[fg=color230]#{window_name} #[fg=$COL_BG_MEDIUM]"
set-window-option -g window-status-current-format "#[fg=$COL_BG_DARK] #{window_index} #{?#{==:#{host_short},prosrvdemadev01},#[fg=colour4],#[fg=color230]}#{window_name} #[fg=$COL_BG_MEDIUM]"
set-window-option -g window-status-format "#[fg=$COL_BG_DARK,bg=$COL_BG_VERY_DARK]#[fg=${status_color},bg=$COL_BG_DARK] #{window_index} #{window_name} #[fg=$COL_BG_DARK,bg=$COL_BG_VERY_DARK]"
set -g pane-active-border-style bg=default,fg=color240