From 40f2327242db3ec620dc375f8a4c8e90c6bd3f8b Mon Sep 17 00:00:00 2001 From: "Nick B." Date: Thu, 25 Jun 2026 15:59:16 +0200 Subject: [PATCH 1/5] Add selectable shell and session backend options --- ssh/config.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ssh/config.yaml b/ssh/config.yaml index 13668960..9bf6fd2f 100644 --- a/ssh/config.yaml +++ b/ssh/config.yaml @@ -63,7 +63,9 @@ options: allow_agent_forwarding: false allow_remote_port_forwarding: false allow_tcp_forwarding: false - zsh: true + shell: fish + session_backend: zellij + zsh: false share_sessions: false packages: [] init_commands: [] @@ -79,7 +81,9 @@ schema: allow_agent_forwarding: bool allow_remote_port_forwarding: bool allow_tcp_forwarding: bool - zsh: bool + shell: list(fish|zsh|bash) + session_backend: list(zellij|tmux) + zsh: bool? share_sessions: bool packages: - str From e5947e1f981388e17e6ca6a77e6749204e4aac33 Mon Sep 17 00:00:00 2001 From: "Nick B." Date: Thu, 25 Jun 2026 15:59:38 +0200 Subject: [PATCH 2/5] Install fish and zellij in the image --- ssh/Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ssh/Dockerfile b/ssh/Dockerfile index b7acbd3d..e38b6bf2 100644 --- a/ssh/Dockerfile +++ b/ssh/Dockerfile @@ -42,6 +42,7 @@ RUN \ colordiff=1.0.22-r0 \ docker-bash-completion=29.5.3-r0 \ docker-zsh-completion=29.5.3-r0 \ + fish \ gcompat=1.1.0-r4 \ git=2.54.0-r0 \ htop=3.5.1-r1 \ @@ -66,7 +67,7 @@ RUN \ nmap-ncat=7.99-r0 \ openssh=10.3_p1-r0 \ openssl=3.5.7-r0 \ - procps-ng=4.0.6-r0 \ + procps-ng=4.0.6-r1 \ pwgen=2.08-r3 \ pulseaudio-utils=17.0-r7 \ py3-pip=26.1.2-r0 \ @@ -80,6 +81,7 @@ RUN \ tmux=3.6b-r0 \ ttyd=1.7.7-r0 \ wget=1.25.0-r3 \ + zellij \ zip=3.0-r13 \ zsh-autosuggestions=0.7.1-r0 \ zsh-syntax-highlighting=0.8.0-r1 \ @@ -93,8 +95,10 @@ RUN \ \ && chmod a+x /usr/bin/ha \ && ha completion bash > /usr/share/bash-completion/completions/ha \ + && mkdir -p /usr/share/fish/vendor_completions.d \ + && ha completion fish > /usr/share/fish/vendor_completions.d/ha.fish \ \ - && sed -i -e "s#bin/sh#bin/zsh#" /etc/passwd \ + && sed -i -e "s#/bin/sh#/usr/bin/fish#" /etc/passwd \ \ && cp /usr/bin/docker /usr/local/bin/.undocked \ \ From df00a22b2bf0dbef84f734e9d1812ace9baf7b96 Mon Sep 17 00:00:00 2001 From: "Nick B." Date: Thu, 25 Jun 2026 15:59:50 +0200 Subject: [PATCH 3/5] Use selected shell and session backend in web terminal --- ssh/rootfs/etc/s6-overlay/s6-rc.d/ttyd/run | 57 ++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/ssh/rootfs/etc/s6-overlay/s6-rc.d/ttyd/run b/ssh/rootfs/etc/s6-overlay/s6-rc.d/ttyd/run index abd96cb4..4b3b42e2 100755 --- a/ssh/rootfs/etc/s6-overlay/s6-rc.d/ttyd/run +++ b/ssh/rootfs/etc/s6-overlay/s6-rc.d/ttyd/run @@ -7,6 +7,44 @@ declare ttyd_command declare -a options declare ingress_port +declare login_shell +declare session_backend +declare shell_path + +get_login_shell() { + if bashio::config.has_value 'shell'; then + bashio::config 'shell' + elif bashio::config.true 'zsh'; then + echo 'zsh' + else + echo 'bash' + fi +} + +get_shell_path() { + case "$1" in + fish) + echo '/usr/bin/fish' + ;; + zsh) + echo '/bin/zsh' + ;; + bash) + echo '/bin/bash' + ;; + *) + bashio::exit.nok "Unsupported shell: $1" + ;; + esac +} + +get_session_backend() { + if bashio::config.has_value 'session_backend'; then + bashio::config 'session_backend' + else + echo 'zellij' + fi +} bashio::log.info 'Starting the ttyd daemon...' @@ -27,10 +65,21 @@ options+=(--writable) ingress_port=$(bashio::app.ingress_port) options+=(-p "${ingress_port}") -ttyd_command=(tmux -u new -A -s homeassistant zsh -l) -if ! bashio::config.true "zsh"; then - ttyd_command=(tmux -u new -A -s homeassistant bash -l) -fi +login_shell=$(get_login_shell) +shell_path=$(get_shell_path "${login_shell}") +session_backend=$(get_session_backend) + +case "${session_backend}" in + zellij) + ttyd_command=(zellij attach --create homeassistant options --default-shell "${shell_path}") + ;; + tmux) + ttyd_command=(tmux -u new -A -s homeassistant "${shell_path}" -l) + ;; + *) + bashio::exit.nok "Unsupported session backend: ${session_backend}" + ;; +esac # Change working directory cd /root || bashio::exit.nok 'Unable to change working directory' From ff4cda3663e65745e617367886493781d5e6726e Mon Sep 17 00:00:00 2001 From: "Nick B." Date: Thu, 25 Jun 2026 16:00:11 +0200 Subject: [PATCH 4/5] Use a neutral wrapper shell for the SSH login user --- .../etc/s6-overlay/s6-rc.d/init-ssh/run | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-ssh/run b/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-ssh/run index b6fa82fc..01ed84e9 100755 --- a/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-ssh/run +++ b/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-ssh/run @@ -98,8 +98,9 @@ username=$(bashio::string.lower "${username}") # Create user account if the user isn't root if [[ "${username}" != "root" ]]; then - # Create an user account - adduser -D "${username}" -s "/bin/zsh" \ + # Create an user account. Keep the SSH login user on /bin/sh so it can + # consistently hand off to the configured root login shell via sudo -i. + adduser -D "${username}" -s "/bin/sh" \ || bashio::exit.nok 'Failed creating the user account' # Add new user to the wheel group @@ -107,7 +108,7 @@ if [[ "${username}" != "root" ]]; then || bashio::exit.nok 'Failed adding user to wheel group' # Ensure new user switches to root after login - echo 'exec sudo -i' > "/home/${username}/.zprofile" \ + echo 'exec sudo -i' > "/home/${username}/.profile" \ || bashio::exit.nok 'Failed configuring user profile' fi @@ -129,7 +130,7 @@ if bashio::config.has_value 'ssh.authorized_keys'; then fi # Port -sed -i "s/Port\\ .*/Port\\ ${port}/" "${SSH_CONFIG_PATH}" \ +sed -i "s/Port\ .*/Port\ ${port}/" "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed configuring port' # SFTP access @@ -140,16 +141,16 @@ fi # Allow specified user to log in if [[ "${username}" != "root" ]]; then - sed -i "s/AllowUsers\\ .*/AllowUsers\\ ${username}/" "${SSH_CONFIG_PATH}" \ + sed -i "s/AllowUsers\ .*/AllowUsers\ ${username}/" "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed opening SSH for the configured user' else - sed -i "s/PermitRootLogin\\ .*/PermitRootLogin\\ yes/" "${SSH_CONFIG_PATH}" \ + sed -i "s/PermitRootLogin\ .*/PermitRootLogin\ yes/" "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed opening SSH for the root user' fi # Enable password authentication when password is set if bashio::config.has_value 'ssh.password'; then - sed -i "s/PasswordAuthentication.*/PasswordAuthentication\\ yes/" \ + sed -i "s/PasswordAuthentication.*/PasswordAuthentication\ yes/" \ "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed to setup SSH password authentication' fi @@ -166,21 +167,21 @@ fi # Enable Agent forwarding if bashio::config.true 'ssh.allow_agent_forwarding'; then - sed -i "s/AllowAgentForwarding.*/AllowAgentForwarding\\ yes/" \ + sed -i "s/AllowAgentForwarding.*/AllowAgentForwarding\ yes/" \ "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed to setup SSH Agent Forwarding' fi # Allow remote port forwarding if bashio::config.true 'ssh.allow_remote_port_forwarding'; then - sed -i "s/GatewayPorts.*/GatewayPorts\\ yes/" \ + sed -i "s/GatewayPorts.*/GatewayPorts\ yes/" \ "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed to setup remote port forwarding' fi # Allow TCP forewarding if bashio::config.true 'ssh.allow_tcp_forwarding'; then - sed -i "s/AllowTcpForwarding.*/AllowTcpForwarding\\ yes/" \ + sed -i "s/AllowTcpForwarding.*/AllowTcpForwarding\ yes/" \ "${SSH_CONFIG_PATH}" \ || bashio::exit.nok 'Failed to setup SSH TCP Forwarding' fi From e0c8aef1f8f529e61c972d3913dbbb1cd0886e96 Mon Sep 17 00:00:00 2001 From: "Nick B." Date: Thu, 25 Jun 2026 16:00:36 +0200 Subject: [PATCH 5/5] Configure fish and zellij session startup --- .../etc/s6-overlay/s6-rc.d/init-user/run | 188 +++++++++++++++--- 1 file changed, 158 insertions(+), 30 deletions(-) diff --git a/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-user/run b/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-user/run index 113b8e86..3283a5bb 100755 --- a/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-user/run +++ b/ssh/rootfs/etc/s6-overlay/s6-rc.d/init-user/run @@ -7,6 +7,10 @@ readonly -a DIRECTORIES=(addon_configs addons backup homeassistant media share ssl) readonly BASH_HISTORY_FILE=/root/.bash_history readonly BASH_HISTORY_PERSISTENT_FILE=/data/.bash_history +readonly FISH_CONF_D_PATH=/root/.config/fish/conf.d +readonly FISH_HOME_ASSISTANT_CONF_D_FILE=/root/.config/fish/conf.d/homeassistant.fish +readonly FISH_HISTORY_FILE=/root/.local/share/fish/fish_history +readonly FISH_HISTORY_PERSISTENT_FILE=/data/fish_history readonly GIT_CONFIG=/data/.gitconfig readonly HOME_ASSISTANT_PROFILE_D_FILE=/etc/profile.d/homeassistant.sh readonly SSH_USER_PATH=/data/.ssh @@ -14,6 +18,104 @@ readonly VSCODE_SERVER_PATH=/root/.vscode-server readonly VSCODE_SERVER_PERSISTENT_PATH=/data/.vscode-server readonly ZSH_HISTORY_FILE=/root/.zsh_history readonly ZSH_HISTORY_PERSISTENT_FILE=/data/.zsh_history +declare login_shell +declare session_backend +declare shell_path + +get_login_shell() { + if bashio::config.has_value 'shell'; then + bashio::config 'shell' + elif bashio::config.true 'zsh'; then + echo 'zsh' + else + echo 'bash' + fi +} + +get_shell_path() { + case "$1" in + fish) + echo '/usr/bin/fish' + ;; + zsh) + echo '/bin/zsh' + ;; + bash) + echo '/bin/bash' + ;; + *) + bashio::exit.nok "Unsupported shell: $1" + ;; + esac +} + +get_session_backend() { + if bashio::config.has_value 'session_backend'; then + bashio::config 'session_backend' + else + echo 'zellij' + fi +} + +set_root_shell() { + sed -i -r -e "s|^(root:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:).*|\1$1|" /etc/passwd* \ + || bashio::exit.nok 'Failed setting the root login shell' +} + +write_bash_profile() { + cat > /root/.bash_profile < /root/.zprofile < "${FISH_HOME_ASSISTANT_CONF_D_FILE}" <> "${FISH_HOME_ASSISTANT_CONF_D_FILE}" <> "${HOME_ASSISTANT_PROFILE_D_FILE}" \ @@ -88,17 +213,20 @@ if ! bashio::fs.directory_exists "${VSCODE_SERVER_PERSISTENT_PATH}"; then fi ln -s "${VSCODE_SERVER_PERSISTENT_PATH}" "${VSCODE_SERVER_PATH}" -# Disable SSH & Web Terminal session sharing if configured -if ! bashio::config.true 'share_sessions'; then +# Configure SSH & Web Terminal session sharing if requested. +if bashio::config.true 'share_sessions'; then + write_bash_profile + write_zsh_profile +else bashio::log.notice 'Session sharing has been disabled!' - rm /root/.bash_profile - rm /root/.zprofile + rm -f /root/.bash_profile + rm -f /root/.zprofile fi # Install user configured/requested packages # # Failures here are intentionally non-fatal: if the package indexes or a -# package cannot be fetched (e.g. broken DNS or no network), we still want +# package cannot be fetched (e.g., broken DNS or no network), we still want # the terminal to come up so the host remains reachable for debugging. if bashio::config.has_value 'packages'; then if apk update; then @@ -119,11 +247,11 @@ if bashio::config.has_value 'init_commands'; then # Use bashio::config to properly iterate over the array, preserving multi-line commands length=$(bashio::config 'init_commands | length') \ || bashio::exit.nok 'Failed to get init_commands array length' - + for (( i=0; i