Skip to content

Commit

Permalink
erlexec 2.0, add section to README
Browse files Browse the repository at this point in the history
  • Loading branch information
SteffenDE committed Jul 17, 2022
1 parent 8a28628 commit b8181a9
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 8 deletions.
92 changes: 92 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,98 @@ If you are migrating from `:nerves_firmware_ssh`, or updating to `:nerves_pack
[SSHSubsystemFwup](https://hexdocs.pm/ssh_subsystem_fwup/readme.html) for
other supported options
## Experimental: Adding a Unix shell
Nerves devices typically only expose an Elixir or Erlang shell prompt. While this is handy,
some tasks are easier to run in a more `bash`-like shell environment. `:nerves_ssh` supports
running a separate SSH daemon that launches a system shell (busybox's `ash` by default).

To enable this functionality, you need to add `erlexec` as a dependency to your project
(at least version `2.0`):

```elixir
def deps do
[
{:erlexec, "~> 2.0"}
]
end
```

You also have to configure `erlexec` to allow running as `root`:

```elixir
# config/target.exs
config :erlexec,
root: true,
user: "root",
limit_users: ["root"]
```

Then, change the `erlinit` configuration to set the `SHELL` environment variable:

```elixir
# config/target.exs
config :nerves,
erlinit: [
hostname_pattern: "nerves-%s",
# add this
env: "SHELL=/bin/sh"
]
```

If you use a custom base system with another shell installed, you can change this path,
e.g. to `/bin/bash`.

The last step is to start the separate daemon in your application. This assumes that you
configured the default daemon using the application environment:

```elixir
# application.ex
def children(_target) do
[
# run a second ssh daemon on another port
# but with all other options being the same
# as the default daemon on port 22
{NervesSSH,
NervesSSH.Options.with_defaults(
Application.get_all_env(:nerves_ssh)
|> Keyword.merge(
name: :shell,
port: 2222,
shell: :disabled,
daemon_option_overrides: [{:ssh_cli, {NervesSSH.SystemShell, []}}]
)
)}
]
end
```

As an alternative to the last step, you may also run the Unix shell in a subsystem
similar to the firmware update functionality. This allows all SSH functionality to run
on a single TCP port, but has the following known issues that cannot be fixed:

* the terminal is only sized correctly after resizing it for the first time
* direct command execution is not possible (e.g. `ssh my-nerves-device -s shell echo foo` will not work)
* correct interactivity requires your ssh client to force pty allocation (e.g. `ssh my-nerves-device -tt -s shell`)
* setting environment variables is not supported (e.g. `ssh -o SetEnv="FOO=Bar" my-nerves-device`)

You can enable the shell subsystem by adding it to the default configuration:

```elixir
# config/target.exs
config :nerves_ssh,
subsystems: [
:ssh_sftpd.subsystem_spec(cwd: '/'),
{'shell', {NervesSSH.SystemShellSubsystem, []}},
],
# ...
```

Then, connect using `ssh your-nerves-device -tt -s shell` (`shell` being the name set in your
configuration).

Please report any issues you find when trying this functionality.

## Goals

* [X] Support public key authentication
Expand Down
43 changes: 37 additions & 6 deletions lib/nerves_ssh/system_shell.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@ defmodule NervesSSH.SystemShellUtils do
end
end

def get_term({term, _, _, _, _, _}) when is_list(term),
# erlang pty_ch_msg contains the value of TERM
# https://www.erlang.org/doc/man/ssh_connection.html#type-pty_ch_msg
def get_term({term, _, _, _, _, _} = _pty_ch_msg) when is_list(term),
do: [{"TERM", List.to_string(term)}]
end

defmodule NervesSSH.SystemShell do
@moduledoc """
A `:ssh_server_channel` that uses `:erlexec` to provide an interactive system shell.
> #### Warning {: .error}
>
> This module does not work when used as an SSH subsystem, as it expects to receive
> `pty`, `exec` / `shell` ssh messages that are not available when running as a subsystem.
> If you want to run a Unix shell in a subsystem, have a look at `NervesSSH.SystemShellSubsystem`
> instead.
"""

@behaviour :ssh_server_channel
Expand All @@ -50,12 +59,10 @@ defmodule NervesSSH.SystemShell do
nil ->
base_opts ++ [:stderr]

{_term, _cols, _rows, _, _, opts} ->
{_term, cols, rows, _, _, opts} ->
# https://www.erlang.org/doc/man/ssh_connection.html#type-pty_ch_msg
# erlexec understands the format of the erlang ssh pty_ch_msg
base_opts ++ [{:stderr, :stdout}, {:pty, opts}]
# not yet released
# ++ [{:winsz, {rows, cols}}]
base_opts ++ [{:stderr, :stdout}, {:pty, opts}, {:winsz, {rows, cols}}]
end

:exec.run(cmd, opts)
Expand Down Expand Up @@ -199,7 +206,31 @@ defmodule NervesSSH.SystemShellSubsystem do
# maybe merge this into the SystemShell module
# but not sure yet if it's worth the effort

@moduledoc false
@moduledoc """
A `:ssh_server_channel` that uses `:erlexec` to provide an interactive system shell
running as an SSH subsystem.
## Configuration
This module accepts a keywordlist for configuring it. Currently, the only supported
options are:
* `command` - the command to run when a client connects, defaults to the SHELL
environment variable or `sh`.
* `force_pty` - enables pseudoterminal allocation, defaults to `true`.
For example:
```elixir
# config/target.exs
config :nerves_ssh,
subsystems: [
:ssh_sftpd.subsystem_spec(cwd: '/'),
{'shell', {NervesSSH.SystemShellSubsystem, [command: '/bin/cat', force_pty: false]}},
],
# ...
```
"""

@behaviour :ssh_server_channel

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule NervesSSH.MixProject do
{:ex_doc, "~> 0.22", only: :docs, runtime: false},
{:ssh_subsystem_fwup, "~> 0.5"},
{:nerves_runtime, "~> 0.11"},
{:erlexec, "~> 1.21.0", optional: true},
{:erlexec, "~> 2.0", optional: true},
# lfe currently requires `compile: "make"` to build and this is
# disallowed when pushing the package to hex.pm. Work around this by
# listing it as dev/test only.
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlexec": {:hex, :erlexec, "1.21.0", "748af41a9b77fde69bb82d5ec37e37384d4f849f971687de49d57e8cb59422ea", [:rebar3], [], "hexpm", "826616a344d1bc0a35c9d2c7175478166f607a4e3b14c878229fd405694d65c0"},
"erlexec": {:hex, :erlexec, "2.0.0", "73eef450ef20760a88c8d65da6ad8ee0446390160bd84064a2682ec1e9afaaed", [:rebar3], [], "hexpm", "7273d744c1409d6b692005160593bd7179ef004ebeb96ee9976e126a88161824"},
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
Expand Down

0 comments on commit b8181a9

Please sign in to comment.