docker-webtop/files/linuxserver-kde.user.dockerfile

348 lines
15 KiB
Docker

# Base image must be provided via --build-arg BASE_IMAGE=<image>
ARG BASE_IMAGE=scratch
FROM ${BASE_IMAGE}
ARG USER_NAME
ARG USER_UID
ARG USER_GID
ARG VIDEO_GID=""
ARG RENDER_GID=""
# Note: USER_PASSWORD is used only during image build for initial setup.
# It is not stored in the image layers. Change password after first login.
ARG USER_PASSWORD=""
ARG HOST_HOSTNAME="Docker-Host"
ARG USER_LANGUAGE="en"
ARG USER_LANG_ENV="en_US.UTF-8"
ARG USER_LANGUAGE_ENV="en_US:en"
ENV HOME="/home/${USER_NAME}" \
USER_NAME="${USER_NAME}" \
HOST_HOSTNAME="${HOST_HOSTNAME}" \
SHELL="/bin/bash" \
LANG="${USER_LANG_ENV}" \
LANGUAGE="${USER_LANGUAGE_ENV}" \
LC_ALL="${USER_LANG_ENV}"
RUN set -eux; \
TARGET_USER="${USER_NAME}"; \
TARGET_UID="${USER_UID}"; \
TARGET_GID="${USER_GID}"; \
if [ -z "${TARGET_UID}" ] || [ -z "${TARGET_GID}" ]; then echo "USER_UID/USER_GID must be provided (host UID/GID)"; exit 1; fi; \
if [ -z "${USER_PASSWORD}" ]; then echo "USER_PASSWORD must be provided"; exit 1; fi; \
echo "Using user=${TARGET_USER} uid=${TARGET_UID} gid=${TARGET_GID}"; \
# ensure primary group named ${TARGET_USER} exists with TARGET_GID (allow non-unique gid to satisfy chown <user>:<user>) \
if getent group "${TARGET_USER}" >/dev/null; then \
groupmod -g "${TARGET_GID}" "${TARGET_USER}" || true; \
else \
groupadd -o -g "${TARGET_GID}" "${TARGET_USER}" 2>/dev/null || true; \
fi; \
if [ -n "${VIDEO_GID}" ]; then \
if getent group video >/dev/null; then \
groupmod -o -g "${VIDEO_GID}" video || true; \
else \
groupadd -o -g "${VIDEO_GID}" video || true; \
fi; \
fi; \
if [ -n "${RENDER_GID}" ]; then \
if getent group render >/dev/null; then \
groupmod -o -g "${RENDER_GID}" render || true; \
else \
groupadd -o -g "${RENDER_GID}" render || true; \
fi; \
fi; \
# remove any user that already has the desired UID to avoid conflicts \
if getent passwd "${TARGET_UID}" >/dev/null; then \
OLD_USER=$(getent passwd "${TARGET_UID}" | cut -d: -f1); \
if [ "${OLD_USER}" != "${TARGET_USER}" ]; then \
echo "UID ${TARGET_UID} in use by ${OLD_USER}, removing it"; \
userdel -r "${OLD_USER}" || true; \
fi; \
fi; \
# ensure common supplemental groups exist (similar to Ubuntu adduser) plus docker/sudo \
for g in adm cdrom dip plugdev lpadmin lxd sudo docker users audio video render; do \
getent group "$g" >/dev/null || groupadd "$g"; \
done; \
# set hostname to host-derived value (baked into image) \
echo "${HOST_HOSTNAME}" > /etc/hostname; \
# create or update main user matching host uid/gid (home=/home/<user>) \
if ! getent passwd "${TARGET_USER}" >/dev/null; then \
useradd -m -d "/home/${TARGET_USER}" -u "${TARGET_UID}" -g "${TARGET_USER}" -s /bin/bash "${TARGET_USER}"; \
else \
usermod -u "${TARGET_UID}" -g "${TARGET_USER}" -d "/home/${TARGET_USER}" "${TARGET_USER}"; \
install -d -m 755 "/home/${TARGET_USER}"; \
fi; \
usermod -aG adm,cdrom,dip,plugdev,lpadmin,lxd,sudo,docker,users,audio,video,render "${TARGET_USER}"; \
echo "${TARGET_USER}:${USER_PASSWORD}" | chpasswd; \
# store auth secret/hash for web login \
SECRET_SALT=$(openssl rand -hex 16); \
env TARGET_USER="${TARGET_USER}" TARGET_PW="${USER_PASSWORD}" SECRET_SALT="${SECRET_SALT}" \
python3 -c "import json,hashlib,os;user=os.environ['TARGET_USER'];pw=os.environ['TARGET_PW'];salt=os.environ['SECRET_SALT'];pw_hash=hashlib.sha256((pw+salt).encode()).hexdigest();secret=hashlib.sha256((user+pw+salt).encode()).hexdigest();data={'user':user,'salt':salt,'pw_hash':pw_hash,'secret':secret};open('/etc/web-auth.json','w').write(json.dumps(data));os.chmod('/etc/web-auth.json',0o600)" ; \
# ensure skeleton and Ubuntu-like bashrc for user (HOME=/home/<user>) and root \
install -d -m 755 "/home/${TARGET_USER}"; \
chown -R "${TARGET_UID}:${TARGET_GID}" "/home/${TARGET_USER}"; \
# create common XDG-style folders in the user's home \
for d in Desktop Documents Downloads Music Pictures Videos Templates Public; do \
install -d -m 755 "/home/${TARGET_USER}/${d}"; \
chown "${TARGET_UID}:${TARGET_GID}" "/home/${TARGET_USER}/${d}"; \
done; \
DEFAULT_BASHRC="/usr/local/share/default_bashrc"; \
printf '%s\n' \
"# DEFAULT_BASHRC" \
"# ~/.bashrc: executed by bash(1) for non-login shells." \
"# If not running interactively, don't do anything" \
"case \$- in" \
" *i*) ;;" \
" *) return;;" \
"esac" \
"HISTCONTROL=ignoreboth" \
"shopt -s histappend" \
"HISTSIZE=1000" \
"HISTFILESIZE=2000" \
"shopt -s checkwinsize" \
"[ -x /usr/bin/lesspipe ] && eval \"\$(SHELL=/bin/sh lesspipe)\"" \
"if [ -z \"\${debian_chroot:-}\" ] && [ -r /etc/debian_chroot ]; then" \
" debian_chroot=\$(cat /etc/debian_chroot)" \
"fi" \
"case \"\$TERM\" in" \
" xterm-color|*-256color) color_prompt=yes;;" \
"esac" \
"if [ -n \"\$force_color_prompt\" ]; then" \
" if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then" \
" color_prompt=yes" \
" else" \
" color_prompt=" \
" fi" \
"fi" \
"if [ \"\$color_prompt\" = yes ]; then" \
" PS1=\"\${debian_chroot:+(\$debian_chroot)}\\[\\033[01;32m\\]\\u@${HOST_HOSTNAME}\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[00m\\]\\$ \" " \
"else" \
" PS1=\"\${debian_chroot:+(\$debian_chroot)}\\u@${HOST_HOSTNAME}:\\w\\$ \" " \
"fi" \
"unset color_prompt force_color_prompt" \
"case \"\$TERM\" in" \
"xterm*|rxvt*)" \
" PS1=\"\\[\\e]0;\${debian_chroot:+(\$debian_chroot)}\\u@${HOST_HOSTNAME}: \\w\\a\\]\$PS1\"" \
" ;;" \
"*)" \
" ;;" \
"esac" \
"if [ -x /usr/bin/dircolors ]; then" \
" test -r ~/.dircolors && eval \"\$(dircolors -b ~/.dircolors)\" || eval \"\$(dircolors -b)\"" \
" alias ls='ls --color=auto'" \
" alias grep='grep --color=auto'" \
" alias fgrep='fgrep --color=auto'" \
" alias egrep='egrep --color=auto'" \
"fi" \
"alias ll='ls -alF'" \
"alias la='ls -A'" \
"alias l='ls -CF'" \
"if [ -f ~/.bash_aliases ]; then" \
" . ~/.bash_aliases" \
"fi" \
"if ! shopt -oq posix; then" \
" if [ -f /usr/share/bash-completion/bash_completion ]; then" \
" . /usr/share/bash-completion/bash_completion" \
" elif [ -f /etc/bash_completion ]; then" \
" . /etc/bash_completion" \
" fi" \
"fi" \
> "${DEFAULT_BASHRC}" \
&& cp "${DEFAULT_BASHRC}" "/home/${TARGET_USER}/.bashrc" \
&& cp "${DEFAULT_BASHRC}" /root/.bashrc \
&& chown "${TARGET_UID}:${TARGET_GID}" "/home/${TARGET_USER}/.bashrc" \
&& rm -f /etc/profile.d/00-ps1.sh /etc/profile.d/01-bashcomp.sh; \
# reset sudoers to require password \
sed -i 's/^%sudo\tALL=(ALL:ALL) NOPASSWD: ALL/%sudo\tALL=(ALL:ALL) ALL/' /etc/sudoers; \
if ! grep -q "^%sudo\s\+ALL=(ALL:ALL)\s\+ALL" /etc/sudoers; then echo "%sudo ALL=(ALL:ALL) ALL" >> /etc/sudoers; fi; \
# disable PackageKit/UDisks2 autostart and D-Bus activation (prevents permission-denied spam) \
rm -f \
/etc/xdg/autostart/packagekitd.desktop \
/usr/share/dbus-1/system-services/org.freedesktop.PackageKit.service \
/usr/share/dbus-1/system-services/org.freedesktop.UDisks2.service; \
install -d -m 755 /etc/dbus-1/system.d; \
printf '%s\n' \
'<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"' \
'"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">' \
'<busconfig>' \
' <policy context="default">' \
' <deny send_destination="org.freedesktop.PackageKit"/>' \
' <deny send_destination="org.freedesktop.UDisks2"/>' \
' </policy>' \
'</busconfig>' \
> /etc/dbus-1/system.d/disable-packagekit.conf; \
mkdir -p /defaults /app /lsiopy && \
chown -R "${TARGET_UID}:${TARGET_GID}" /defaults /app /lsiopy
# optional Japanese locale and input (toggle via USER_LANGUAGE=ja)
RUN set -eux; \
LANG_SEL="$(echo "${USER_LANGUAGE}" | tr '[:upper:]' '[:lower:]')" ; \
if [ "${LANG_SEL}" = "ja" ] || [ "${LANG_SEL}" = "ja_jp" ] || [ "${LANG_SEL}" = "ja-jp" ]; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
language-pack-ja-base language-pack-ja im-config \
fonts-noto-cjk fonts-noto-color-emoji \
fcitx fcitx-bin fcitx-data fcitx-table-all \
fcitx-mozc fcitx-config-gtk \
fcitx-frontend-gtk2 fcitx-frontend-gtk3 fcitx-frontend-qt5 \
fcitx-module-dbus fcitx-module-kimpanel fcitx-module-x11 fcitx-module-lua fcitx-ui-classic \
kde-config-fcitx && \
locale-gen ja_JP.UTF-8 && \
update-locale LANG=ja_JP.UTF-8 LANGUAGE=ja_JP:ja LC_ALL=ja_JP.UTF-8 && \
apt-get clean && rm -rf /var/lib/apt/lists/*; \
echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen || true; \
echo 'LANG=ja_JP.UTF-8' > /etc/default/locale; \
echo 'LANGUAGE=ja_JP:ja' >> /etc/default/locale; \
echo 'LC_ALL=ja_JP.UTF-8' >> /etc/default/locale; \
rm -f /etc/localtime; \
ln -snf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime; \
echo "Asia/Tokyo" > /etc/timezone; \
printf '%s\n' \
'XKBMODEL="jp106"' \
'XKBLAYOUT="jp"' \
'XKBVARIANT=""' \
'XKBOPTIONS=""' \
'BACKSPACE="guess"' \
> /etc/default/keyboard; \
install -d -m 755 /etc/X11/xorg.conf.d; \
printf '%s\n' \
'Section "InputClass"' \
' Identifier "system-keyboard"' \
' MatchIsKeyboard "on"' \
' Option "XkbLayout" "jp"' \
' Option "XkbModel" "jp106"' \
' Option "XkbVariant" ""' \
' Option "XkbOptions" ""' \
'EndSection' \
> /etc/X11/xorg.conf.d/00-keyboard.conf; \
im-config -n fcitx; \
install -d -m 755 /etc/xdg/autostart "/home/${USER_NAME}/.config/autostart"; \
printf '%s\n' \
'[Desktop Entry]' \
'Type=Application' \
'Exec=fcitx -d' \
'Hidden=false' \
'X-GNOME-Autostart-enabled=true' \
'Name=fcitx' \
'Comment=Start Fcitx input method daemon' \
> /etc/xdg/autostart/fcitx-autostart.desktop; \
cp /etc/xdg/autostart/fcitx-autostart.desktop "/home/${USER_NAME}/.config/autostart/fcitx-autostart.desktop"; \
chown "${USER_UID}:${USER_GID}" "/home/${USER_NAME}/.config/autostart/fcitx-autostart.desktop"; \
printf '%s\n' \
'[Layout]' \
'DisplayNames=' \
'LayoutList=jp' \
'Model=jp106' \
'Options=' \
'ResetOldOptions=true' \
'Use=true' \
> "/home/${USER_NAME}/.config/kxkbrc"; \
chown "${USER_UID}:${USER_GID}" "/home/${USER_NAME}/.config/kxkbrc"; \
fi
# Set fcitx environment variables globally when Japanese locale is selected
ARG USER_LANGUAGE
RUN LANG_SEL="$(echo "${USER_LANGUAGE}" | tr '[:upper:]' '[:lower:]')" ; \
if [ "${LANG_SEL}" = "ja" ] || [ "${LANG_SEL}" = "ja_jp" ] || [ "${LANG_SEL}" = "ja-jp" ]; then \
mkdir -p /etc/profile.d && \
printf '%s\n' \
'export GTK_IM_MODULE=fcitx' \
'export QT_IM_MODULE=fcitx' \
'export XMODIFIERS="@im=fcitx"' \
'export INPUT_METHOD=fcitx' \
'export SDL_IM_MODULE=fcitx' \
'export GLFW_IM_MODULE=fcitx' \
> /etc/profile.d/99-fcitx-env.sh && \
chmod 644 /etc/profile.d/99-fcitx-env.sh; \
fi
# Apply fcitx ENV globally when USER_LANGUAGE is ja
ENV GTK_IM_MODULE=fcitx \
QT_IM_MODULE=fcitx \
XMODIFIERS="@im=fcitx" \
INPUT_METHOD=fcitx \
SDL_IM_MODULE=fcitx \
GLFW_IM_MODULE=fcitx
# create XDG user dirs and desktop shortcuts (Home/Trash)
RUN set -eux; \
for d in Desktop Documents Downloads Music Pictures Videos Templates Public; do \
install -d -m 755 "/home/${USER_NAME}/${d}"; \
chown "${USER_UID}:${USER_GID}" "/home/${USER_NAME}/${d}"; \
done; \
install -d -m 755 "/home/${USER_NAME}/.config"; \
printf '%s\n' \
'XDG_DESKTOP_DIR="$HOME/Desktop"' \
'XDG_DOWNLOAD_DIR="$HOME/Downloads"' \
'XDG_TEMPLATES_DIR="$HOME/Templates"' \
'XDG_PUBLICSHARE_DIR="$HOME/Public"' \
'XDG_DOCUMENTS_DIR="$HOME/Documents"' \
'XDG_MUSIC_DIR="$HOME/Music"' \
'XDG_PICTURES_DIR="$HOME/Pictures"' \
'XDG_VIDEOS_DIR="$HOME/Videos"' \
> "/home/${USER_NAME}/.config/user-dirs.dirs"; \
printf '%s\n' \
'[Desktop Entry]' \
'Encoding=UTF-8' \
'Name=Home' \
'GenericName=Personal Files' \
'URL[$e]=$HOME' \
'Icon=user-home' \
'Type=Link' \
> "/home/${USER_NAME}/Desktop/home.desktop"; \
printf '%s\n' \
'[Desktop Entry]' \
'Name=Trash' \
'Comment=Contains removed files' \
'Icon=user-trash-full' \
'EmptyIcon=user-trash' \
'URL=trash:/' \
'Type=Link' \
> "/home/${USER_NAME}/Desktop/trash.desktop"; \
chown "${USER_UID}:${USER_GID}" /home/${USER_NAME}/Desktop/home.desktop /home/${USER_NAME}/Desktop/trash.desktop
# browser wrappers (Chromium on arm64, Chrome on amd64) to enforce flags even after package updates
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
if [ "${ARCH}" = "arm64" ]; then \
if [ -x /usr/bin/chromium ]; then \
echo '#!/bin/bash' > /usr/local/bin/chromium-wrapped && \
echo 'CHROME_BIN=\"/usr/bin/chromium\"' >> /usr/local/bin/chromium-wrapped && \
echo 'exec \"${CHROME_BIN}\" --password-store=basic --in-process-gpu --no-sandbox ${CHROME_EXTRA_FLAGS} \"$@\"' >> /usr/local/bin/chromium-wrapped && \
chmod 755 /usr/local/bin/chromium-wrapped; \
if [ -f /usr/share/applications/chromium.desktop ]; then \
mkdir -p /home/${USER_NAME}/.local/share/applications && \
cp /usr/share/applications/chromium.desktop /home/${USER_NAME}/.local/share/applications/chromium.desktop && \
sed -i -e 's#Exec=/usr/bin/chromium#Exec=/usr/local/bin/chromium-wrapped#g' /home/${USER_NAME}/.local/share/applications/chromium.desktop && \
chown ${USER_UID}:${USER_GID} /home/${USER_NAME}/.local/share/applications/chromium.desktop; \
fi; \
fi; \
else \
if [ -x /usr/bin/google-chrome-stable ]; then \
echo '#!/bin/bash' > /usr/local/bin/google-chrome-wrapped && \
echo 'CHROME_BIN="/usr/bin/google-chrome-stable"' >> /usr/local/bin/google-chrome-wrapped && \
echo 'exec "${CHROME_BIN}" --password-store=basic --in-process-gpu --no-sandbox ${CHROME_EXTRA_FLAGS} "$@"' >> /usr/local/bin/google-chrome-wrapped && \
chmod 755 /usr/local/bin/google-chrome-wrapped; \
for chrome_bin in google-chrome google-chrome-beta google-chrome-unstable; do \
if [ -x \"/usr/bin/${chrome_bin}\" ]; then \
echo '#!/bin/bash' > \"/usr/local/bin/${chrome_bin}-wrapped\" && \
echo 'exec /usr/local/bin/google-chrome-wrapped \"$@\"' >> \"/usr/local/bin/${chrome_bin}-wrapped\" && \
chmod 755 \"/usr/local/bin/${chrome_bin}-wrapped\"; \
fi; \
done; \
for desktop in /usr/share/applications/google-chrome*.desktop; do \
[ -f "$desktop" ] || continue; \
sed -i -E 's#Exec=/usr/bin/google-chrome-stable([^\\n]*)#Exec=/usr/local/bin/google-chrome-wrapped#g' "$desktop"; \
done; \
mkdir -p /home/${USER_NAME}/.local/share/applications; \
for desktop in /usr/share/applications/google-chrome*.desktop; do \
[ -f "$desktop" ] || continue; \
base=$(basename "$desktop"); \
cp "$desktop" "/home/${USER_NAME}/.local/share/applications/$base"; \
sed -i -E 's#Exec=/usr/bin/google-chrome-stable([^\\n]*)#Exec=/usr/local/bin/google-chrome-wrapped#g' "/home/${USER_NAME}/.local/share/applications/$base"; \
chown ${USER_UID}:${USER_GID} "/home/${USER_NAME}/.local/share/applications/$base"; \
done; \
fi; \
fi
# Keep default USER=root so s6 init can modify system paths.