Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,16 @@ permissions:
security-events: write

jobs:
terminal-backends:
name: Terminal backend tests
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Test shell and session dispatch
run: bash ssh/tests/test-terminal-backends.sh

workflows:
uses: hassio-addons/workflows/.github/workflows/app-ci.yaml@383c10d83acbe341acbb35a4a61bfd14827f00f0 # v3.0.0
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ well. Additionally, it comes out of the box with the following:
your favorite tools, which will be available every single time you log in.
- Execute custom commands on app start so that you can customize the
shell to your likings.
- [ZSH][zsh] as its default shell. Easier to use for the beginner, more advanced
for the more experienced user. It even comes preloaded with
["Oh My ZSH"][ohmyzsh], with some plugins enabled as well.
- Selectable interactive shells: Zsh with Oh My Zsh remains the compatible
default, while Fish and Bash are available through the `shell` option.
- Selectable terminal session backends: tmux keeps the ttyd/xterm.js web
terminal, while Zellij uses its built-in browser client.
- Contains a sensible set of tools right out of the box: curl, Wget, RSync, GIT,
Nmap, Mosquitto client, MariaDB/MySQL client, Awake ("wake on LAN"), Nano,
Neovim, tmux, and a bunch commonly used networking tools.
Neovim, tmux, Zellij, and a bunch commonly used networking tools.

## Support

Expand Down
92 changes: 57 additions & 35 deletions ssh/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ well. Additionally, it comes out of the box with the following:
your favorite tools, which will be available every single time you log in.
- Execute custom commands on app start so that you can customize the
shell to your likings.
- [ZSH][zsh] as its default shell. Easier to use for the beginner, more advanced
for the more experienced user. It even comes preloaded with
["Oh My ZSH"][ohmyzsh], with some plugins enabled as well.
- Selectable interactive shells: Zsh with Oh My Zsh remains the compatible
default, while Fish and Bash are available through the `shell` option.
- Selectable terminal session backends: tmux remains the compatible default
and uses ttyd/xterm.js, while Zellij uses its native browser client.
- Contains a sensible set of tools right out of the box: curl, Wget, RSync, GIT,
Nmap, Mosquitto client, MariaDB/MySQL client, Awake ("wake on LAN"), Nano,
Neovim, tmux, and a bunch commonly used networking tools.
Neovim, tmux, Zellij, and a bunch commonly used networking tools.

## Installation

Expand Down Expand Up @@ -85,7 +86,8 @@ ssh:
allow_agent_forwarding: false
allow_remote_port_forwarding: false
allow_tcp_forwarding: false
zsh: true
shell: fish
session_backend: zellij
share_sessions: true
packages:
- build-base
Expand Down Expand Up @@ -197,19 +199,46 @@ Nevertheless, this warning is debatable._

The following options are shared between both the SSH and the Web Terminal.

#### Option: `shell`

Selects the interactive shell used by SSH and the Web Terminal. Supported values
are `fish`, `zsh`, and `bash`. If this option is omitted, the legacy `zsh`
option remains authoritative so existing installations keep their current shell.

The root account itself deliberately keeps Bash as its account shell. The
selected interactive shell is started only after login, which keeps remote SSH
commands and tools such as rsync on a POSIX-compatible command shell.

#### Option: `session_backend`

Selects the terminal multiplexer used by the Web Terminal and, when session
sharing is enabled, SSH. Supported values are `zellij` and `tmux`. If this
option is omitted, tmux remains the default for compatibility with existing
installations.

With `zellij`, the Home Assistant panel is served by Zellij's built-in web
server; ttyd and xterm.js are not started. A small ingress adapter restricts the
server to Home Assistant Supervisor traffic and forwards its HTTP and WebSocket
connections. With `tmux`, the existing ttyd/xterm.js Web Terminal remains in
use for Fish, Zsh, and Bash.

Zellij's token authentication stays enabled. On first startup the app creates a
token named `home-assistant`, prints it once in the app log, and stores the
token in `/data/zellij/web-token` with root-only permissions. Paste it into
the Zellij login screen and select the remember option if desired.

#### Option: `zsh`

The app has ZSH pre-installed and configured as the default shell.
However, ZSH might not be your preferred choice. By setting this option to
`false`, you will disable ZSH and the app will fallback to Bash instead.
This is the legacy shell selector. It remains supported for upgrades: `true`
selects Zsh and `false` selects Bash when `shell` is absent. New
configurations should use `shell` instead.

#### Option: `share_sessions`

By default, the terminal session between the web client and SSH is shared.
This allows you to pick up where you left your terminal from either of those.

This option allows you to disable this behavior by setting it to `false`, which
effectively sets SSH to behave as it used to be.
When enabled, interactive SSH clients attach to the same multiplexer session as
the Web Terminal. When disabled, SSH starts the selected shell without attaching
to the Web Terminal session. Non-interactive SSH commands never enter a
multiplexer.

#### Option: `packages`

Expand Down Expand Up @@ -243,48 +272,41 @@ single time this app starts.

## Clipboard: copying and pasting

The Web Terminal is based on xterm.js, which follows X11-style clipboard
conventions that may differ from what you expect:
With the `tmux` backend, the Web Terminal uses xterm.js and follows X11-style
clipboard conventions that may differ from what you expect:

- **Copy**: hold `Shift` and select the text with your mouse. The selection is
copied to your system clipboard right away (a small scissors icon briefly
pops up). There is no need to press `Ctrl+C`.
- **Paste**: press `Ctrl+Shift+V`, or right-click and choose paste, depending
on your browser.

This applies to the Web Terminal in the Home Assistant frontend. A regular SSH
client uses the clipboard behavior of its own terminal instead.
The `zellij` backend instead uses Zellij's native browser client and its own
keyboard and mouse handling. A regular SSH client always uses the clipboard
behavior of its own terminal.

## Known issues and limitations

- When SFTP is enabled, the username MUST be set to `root`.
- If you want to use rsync for file transfer, the username MUST be set to
`root`.

## Running the `ha` command or Supervisor API non-interactively

When you log in interactively, the app starts a login shell that sets up the
`SUPERVISOR_TOKEN` environment variable. The `ha` command and the Supervisor
API need that token, so commands like `ha core info` just work.

Running a command non-interactively does **not** start a login shell, so the
token is not set and the command fails with a `401` error. For example, this
fails:
Non-interactive SSH commands always run under Bash and never enter the selected
multiplexer. With the default non-root SSH username, the login wrapper executes
the command through root's Bash login environment. When logging in directly as
root, OpenSSH invokes root's Bash account shell and imports the
`SUPERVISOR_TOKEN` from the permitted SSH environment:

```shell
ssh your-instance "ha core info"
```

Wrap the command in a login shell so the environment, and with it the token,
is loaded:

```shell
ssh your-instance 'bash -lc "ha core info"'
```
The command's output and exit status are returned directly to the SSH client.
Interactive SSH and Web Terminal logins still use the configured `shell` and,
when enabled, the configured shared-session backend.

The same applies when calling the Supervisor API directly or running commands
from automations: invoke them through a login shell (`bash -lc '...'`) so the
`SUPERVISOR_TOKEN` is available.
Mosh bootstraps through non-interactive SSH command mode. It therefore starts
Bash and does not attach to the configured shared-session backend.

## Changelog & Releases

Expand Down
28 changes: 27 additions & 1 deletion ssh/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ COPY requirements.txt /tmp/
# Setup base
ARG BUILD_ARCH=amd64
ARG HA_CLI_VERSION="5.2.0"
ARG ZELLIJ_VERSION="0.44.3"
ARG ZELLIJ_SHA256_AMD64="0f7c346788627f506c0a28296517768633cff24fc822a739f8264b640ecad751"
ARG ZELLIJ_SHA256_AARCH64="15e6534d42644d66973d136c590c49739dcfd6a1a2a0d3d917973f16c81b45fb"

# hadolint ignore=DL3003,DL3042
RUN \
Expand All @@ -42,6 +45,7 @@ RUN \
colordiff=1.0.22-r0 \
docker-bash-completion=29.5.3-r0 \
docker-zsh-completion=29.5.3-r0 \
fish=4.6.0-r1 \
gcompat=1.1.0-r4 \
git=2.54.0-r0 \
htop=3.5.1-r1 \
Expand All @@ -64,6 +68,7 @@ RUN \
net-tools=2.10-r3 \
nmap=7.99-r0 \
nmap-ncat=7.99-r0 \
nginx=1.30.4-r1 \
openssh=10.3_p1-r0 \
openssl=3.5.7-r0 \
procps-ng=4.0.6-r0 \
Expand Down Expand Up @@ -93,8 +98,29 @@ 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 \
&& case "${BUILD_ARCH}" in \
amd64) \
zellij_target="x86_64-unknown-linux-musl"; \
zellij_sha256="${ZELLIJ_SHA256_AMD64}" \
;; \
aarch64) \
zellij_target="aarch64-unknown-linux-musl"; \
zellij_sha256="${ZELLIJ_SHA256_AARCH64}" \
;; \
*) \
echo "Unsupported architecture for Zellij: ${BUILD_ARCH}" >&2; \
exit 1 \
;; \
esac \
&& curl -fsSL -o /tmp/zellij.tar.gz \
"https://github.com/zellij-org/zellij/releases/download/v${ZELLIJ_VERSION}/zellij-${zellij_target}.tar.gz" \
&& echo "${zellij_sha256} /tmp/zellij.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/zellij.tar.gz -C /usr/local/bin zellij \
&& chmod 0755 /usr/local/bin/zellij \
&& zellij --version | grep -Fx "zellij ${ZELLIJ_VERSION}" \
\
&& cp /usr/bin/docker /usr/local/bin/.undocked \
\
Expand Down
2 changes: 2 additions & 0 deletions ssh/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ schema:
allow_agent_forwarding: bool
allow_remote_port_forwarding: bool
allow_tcp_forwarding: bool
shell: list(fish|zsh|bash)?
session_backend: list(zellij|tmux)?
zsh: bool
share_sessions: bool
packages:
Expand Down
10 changes: 4 additions & 6 deletions ssh/rootfs/etc/s6-overlay/s6-rc.d/init-ssh/run
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ readonly SSH_AUTHORIZED_KEYS_PATH=/etc/ssh/authorized_keys
readonly SSH_CONFIG_PATH=/etc/ssh/sshd_config
readonly SSH_HOST_ED25519_KEY=/data/ssh_host_ed25519_key
readonly SSH_HOST_RSA_KEY=/data/ssh_host_rsa_key
readonly SSH_LOGIN_SHELL=/usr/local/bin/ssh-login
declare password
declare port
declare username
Expand Down Expand Up @@ -98,17 +99,14 @@ 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" \
# The wrapper elevates both interactive sessions and remote commands while
# keeping non-interactive commands on root's POSIX-compatible Bash shell.
adduser -D "${username}" -s "${SSH_LOGIN_SHELL}" \
|| bashio::exit.nok 'Failed creating the user account'

# Add new user to the wheel group
adduser "${username}" wheel \
|| bashio::exit.nok 'Failed adding user to wheel group'

# Ensure new user switches to root after login
echo 'exec sudo -i' > "/home/${username}/.zprofile" \
|| bashio::exit.nok 'Failed configuring user profile'
fi

# We need to set a password for the user account
Expand Down
Loading