273 lines
10 KiB
Bash
Executable File
273 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Share ONE Docker daemon across WSL distros, instead of running one per distro.
|
|
#
|
|
# Why this exists
|
|
# ---------------
|
|
# WSL2 distros share a kernel and a network stack. Two dockerd instances then
|
|
# contend over docker0 and iptables, which can disturb the daemon you actually
|
|
# depend on. Docker Desktop avoids this by running a single daemon in a
|
|
# dedicated distro and sharing its socket — this is the same idea, without
|
|
# Docker Desktop.
|
|
#
|
|
# So a throwaway rig box does NOT install Docker. It borrows the daemon from
|
|
# whichever distro is the designated host. That also makes the test more honest:
|
|
# rig never installs Docker anyway — Docker is its documented prerequisite.
|
|
#
|
|
# How
|
|
# ---
|
|
# /mnt/wsl is a tmpfs with `shared` mount propagation, visible to every distro
|
|
# in the WSL VM. The owning distro exposes its socket there; guests point
|
|
# DOCKER_HOST at it. Two ways, with different costs:
|
|
#
|
|
# share bind-mount the existing socket onto the shared tmpfs.
|
|
# Instant, and dockerd is NEVER restarted. Lasts until the
|
|
# next WSL shutdown.
|
|
# share --persist additionally install a systemd drop-in so dockerd listens
|
|
# there itself. Survives restarts, but requires one Docker
|
|
# restart now — which stops every container that has no
|
|
# restart policy, since live-restore is off by default.
|
|
#
|
|
# The bind mount is the default precisely because the persistent version's cost
|
|
# is paid on a machine that is already working.
|
|
#
|
|
# Reversibility is the whole design
|
|
# ---------------------------------
|
|
# `unshare` removes the bind mount (no restart) and, if present, the drop-in.
|
|
# The original systemd unit is never edited — only an additive drop-in file is
|
|
# ever created — so undoing is deletion, not repair. `status` always states
|
|
# which of the three roles a distro is in, in those words.
|
|
#
|
|
# Nothing here runs automatically. It does nothing until invoked.
|
|
#
|
|
# Usage:
|
|
# dockerhost.sh status # which distro owns Docker; what this one uses
|
|
# dockerhost.sh share # share it (bind mount, no daemon restart)
|
|
# dockerhost.sh share --persist # ...and survive WSL restarts (restarts Docker)
|
|
# dockerhost.sh unshare # undo it; this distro owns its Docker again
|
|
# dockerhost.sh use [--persist] # point THIS distro at the shared socket
|
|
set -euo pipefail
|
|
|
|
SHARED_DIR=/mnt/wsl/shared-docker
|
|
SHARED_SOCK="$SHARED_DIR/docker.sock"
|
|
OWNER_FILE="$SHARED_DIR/OWNER"
|
|
DROPIN=/etc/systemd/system/docker.service.d/10-rig-shared-socket.conf
|
|
PROFILE_D=/etc/profile.d/rig-docker-host.sh
|
|
|
|
distro_name() { echo "${WSL_DISTRO_NAME:-$(hostname)}"; }
|
|
|
|
require_wsl() {
|
|
grep -qi microsoft /proc/version 2>/dev/null && return 0
|
|
echo "dockerhost is WSL-only: it relies on /mnt/wsl being shared between distros." >&2
|
|
exit 1
|
|
}
|
|
|
|
# ── status ─────────────────────────────────────────────────────────────────
|
|
|
|
status() {
|
|
require_wsl
|
|
echo "distro $(distro_name)"
|
|
|
|
if [ -f "$DROPIN" ] || mountpoint -q "$SHARED_SOCK" 2>/dev/null; then
|
|
echo "role SHARING — this distro's Docker is offered to other distros"
|
|
elif [ -n "${DOCKER_HOST:-}" ] && [ "${DOCKER_HOST}" = "unix://$SHARED_SOCK" ]; then
|
|
echo "role BORROWING — using another distro's Docker"
|
|
else
|
|
echo "role standalone — this WSL installation has the main host Docker"
|
|
fi
|
|
|
|
echo
|
|
if [ -S "$SHARED_SOCK" ]; then
|
|
echo "shared sock $SHARED_SOCK (present)"
|
|
[ -f "$OWNER_FILE" ] && sed 's/^/ /' "$OWNER_FILE"
|
|
else
|
|
echo "shared sock none — no distro is sharing right now"
|
|
fi
|
|
|
|
echo
|
|
echo "DOCKER_HOST ${DOCKER_HOST:-(unset — using /var/run/docker.sock)}"
|
|
if command -v docker >/dev/null 2>&1; then
|
|
echo "docker $(docker version --format '{{.Server.Version}}' 2>/dev/null || echo unreachable)"
|
|
else
|
|
echo "docker cli not installed"
|
|
fi
|
|
}
|
|
|
|
# ── share / unshare (run on the host distro) ───────────────────────────────
|
|
|
|
# Default: expose the EXISTING socket by bind-mounting it onto the shared tmpfs.
|
|
# /mnt/wsl has `shared` propagation, so the mount is visible in other distros.
|
|
#
|
|
# The point of doing it this way is that dockerd is never restarted. Restarting
|
|
# it stops every container that has no restart policy (live-restore is off by
|
|
# default), which on a working machine means quietly killing whatever you had
|
|
# running. Not a trade worth making just to expose a socket.
|
|
#
|
|
# Cost: a bind mount does not survive a WSL VM shutdown. `--persist` adds the
|
|
# systemd drop-in as well, which does survive but needs that one restart.
|
|
share_bind() {
|
|
mkdir -p "$SHARED_DIR"
|
|
chmod 0755 "$SHARED_DIR"
|
|
|
|
if mountpoint -q "$SHARED_SOCK" 2>/dev/null; then
|
|
echo "already bind-mounted at $SHARED_SOCK"
|
|
else
|
|
[ -S /var/run/docker.sock ] || { echo "no /var/run/docker.sock here" >&2; exit 1; }
|
|
# The target must exist as a file for a bind mount onto it.
|
|
[ -e "$SHARED_SOCK" ] || : > "$SHARED_SOCK"
|
|
mount --bind /var/run/docker.sock "$SHARED_SOCK"
|
|
echo "bind-mounted /var/run/docker.sock -> $SHARED_SOCK (no daemon restart)"
|
|
fi
|
|
|
|
cat > "$OWNER_FILE" <<EOF
|
|
owner distro: $(distro_name)
|
|
docker gid: $(getent group docker | cut -d: -f3)
|
|
socket: $SHARED_SOCK
|
|
method: bind-mount (until the next WSL shutdown)
|
|
EOF
|
|
}
|
|
|
|
share() {
|
|
require_wsl
|
|
[ "$(id -u)" -eq 0 ] || { echo "run with sudo: sudo bash ctrl/dockerhost.sh share" >&2; exit 1; }
|
|
|
|
share_bind
|
|
|
|
if [ "${1:-}" != "--persist" ]; then
|
|
echo
|
|
echo "This lasts until the next WSL shutdown. To make it survive, re-run with"
|
|
echo "--persist — but note that adds a systemd drop-in and RESTARTS Docker,"
|
|
echo "which stops any container that has no restart policy."
|
|
return 0
|
|
fi
|
|
|
|
if [ -f "$DROPIN" ]; then
|
|
echo "drop-in already present — sharing persists across restarts."
|
|
return 0
|
|
fi
|
|
|
|
echo
|
|
echo "--persist: installing a systemd drop-in and restarting Docker."
|
|
echo "Containers without a restart policy will stop and will NOT come back."
|
|
docker ps --format ' {{.Names}} restart={{.HostConfig.RestartPolicy.Name}}' 2>/dev/null \
|
|
|| docker ps --format ' {{.Names}}' 2>/dev/null || true
|
|
echo
|
|
|
|
local exec_line
|
|
exec_line=$(systemctl cat docker.service | grep -m1 '^ExecStart=')
|
|
if [ -z "$exec_line" ]; then
|
|
echo "could not read docker.service ExecStart — refusing to guess" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$DROPIN")" "$SHARED_DIR"
|
|
# Additive only: blank the inherited ExecStart, then restate it verbatim
|
|
# with one extra -H. Nothing about the original unit is edited.
|
|
cat > "$DROPIN" <<EOF
|
|
# Added by rig (ctrl/dockerhost.sh share).
|
|
#
|
|
# Adds a SECOND listening socket on the WSL-shared tmpfs so other distros can
|
|
# use this daemon instead of running their own. The original socket is
|
|
# untouched, so this distro behaves exactly as before.
|
|
#
|
|
# To undo: sudo bash ctrl/dockerhost.sh unshare
|
|
[Service]
|
|
ExecStartPre=-/bin/mkdir -p $SHARED_DIR
|
|
ExecStartPre=-/bin/chmod 0755 $SHARED_DIR
|
|
ExecStart=
|
|
${exec_line} -H unix://$SHARED_SOCK
|
|
EOF
|
|
|
|
systemctl daemon-reload
|
|
systemctl restart docker
|
|
|
|
# Guests need a group with a MATCHING GID to use the socket; GIDs are not
|
|
# consistent across distros, so record ours rather than assume.
|
|
cat > "$OWNER_FILE" <<EOF
|
|
owner distro: $(distro_name)
|
|
docker gid: $(getent group docker | cut -d: -f3)
|
|
socket: $SHARED_SOCK
|
|
EOF
|
|
|
|
echo "sharing from '$(distro_name)'"
|
|
echo " guests: export DOCKER_HOST=unix://$SHARED_SOCK"
|
|
echo " undo: sudo bash ctrl/dockerhost.sh unshare"
|
|
echo
|
|
echo "NOTE: /mnt/wsl is tmpfs and is cleared when the WSL VM shuts down."
|
|
echo " The drop-in recreates the directory on the next Docker start."
|
|
}
|
|
|
|
unshare_() {
|
|
require_wsl
|
|
[ "$(id -u)" -eq 0 ] || { echo "run with sudo: sudo bash ctrl/dockerhost.sh unshare" >&2; exit 1; }
|
|
|
|
local did=0
|
|
|
|
# The bind mount first: undoing it needs no restart, so a plain `share`
|
|
# is fully reversible without disturbing anything.
|
|
if mountpoint -q "$SHARED_SOCK" 2>/dev/null; then
|
|
umount "$SHARED_SOCK"
|
|
rm -f "$SHARED_SOCK"
|
|
echo " removed the bind mount (no restart needed)"
|
|
did=1
|
|
fi
|
|
rm -f "$OWNER_FILE"
|
|
rmdir "$SHARED_DIR" 2>/dev/null || true
|
|
|
|
if [ -f "$DROPIN" ]; then
|
|
rm -f "$DROPIN"
|
|
rmdir "$(dirname "$DROPIN")" 2>/dev/null || true
|
|
systemctl daemon-reload
|
|
systemctl restart docker
|
|
echo " removed the systemd drop-in and restarted Docker"
|
|
did=1
|
|
fi
|
|
|
|
if [ "$did" -eq 0 ]; then
|
|
echo "not sharing — this WSL installation already has the main host Docker."
|
|
return 0
|
|
fi
|
|
|
|
echo "restored: this WSL installation has the main host Docker again."
|
|
echo " (nothing else was changed; the original unit was never edited)"
|
|
}
|
|
|
|
# ── use (run on a guest distro) ────────────────────────────────────────────
|
|
|
|
use() {
|
|
require_wsl
|
|
if [ ! -S "$SHARED_SOCK" ]; then
|
|
echo "no shared socket at $SHARED_SOCK" >&2
|
|
echo "Run 'sudo bash ctrl/dockerhost.sh share' in the distro that owns Docker." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Align the local docker group GID with the owner's, or the socket is
|
|
# unreadable here even though it is visible.
|
|
if [ -f "$OWNER_FILE" ] && [ "$(id -u)" -eq 0 ]; then
|
|
local gid; gid=$(awk '/docker gid:/ {print $3}' "$OWNER_FILE")
|
|
if [ -n "$gid" ]; then
|
|
if getent group docker >/dev/null; then
|
|
[ "$(getent group docker | cut -d: -f3)" = "$gid" ] || groupmod -g "$gid" docker
|
|
else
|
|
groupadd -g "$gid" docker
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "${1:-}" = "--persist" ]; then
|
|
[ "$(id -u)" -eq 0 ] || { echo "--persist needs root" >&2; exit 1; }
|
|
echo "export DOCKER_HOST=unix://$SHARED_SOCK" > "$PROFILE_D"
|
|
echo "persisted in $PROFILE_D"
|
|
fi
|
|
|
|
echo "export DOCKER_HOST=unix://$SHARED_SOCK"
|
|
}
|
|
|
|
case "${1:-status}" in
|
|
status) status ;;
|
|
share) shift; share "${1:-}" ;;
|
|
unshare) unshare_ ;;
|
|
use) shift; use "${1:-}" ;;
|
|
*) echo "usage: $0 [status|share|unshare|use [--persist]]" >&2; exit 1 ;;
|
|
esac
|