Mebe and the Remote Shell

Posted on .

What better way to spend a slow weekend than by writing another blog engine? Plenty, actually, but that's what I did anyway. The result of this work: Mebe! This blog is now powered by a wonderful mix of Elixir and Phoenix. It has all the features that Laine had, with the addition of an actually working Disqus comment system. It's also search engine indexable, which I thought I didn't care about, until I didn't have it anymore. Not that I'm aiming for tons of visitors, but writing about some tech problem I have fixed is kind of pointless if no one can find the post by googling for it.

In any case, I ended up with an awesome new blog engine and learned about Elixir supervisors and applications in the process. Another cool thing I learned is the Elixir remote shell. As could be guessed from the name, it's the ability to open a shell remotely. But the great thing is, you can open the shell to any running node. This allows for some cool stuff that is really hard or just not possible in other stacks.

I'll use this blog engine as an example. When the blog engine starts, it parses all of the posts into memory. As such, they are not read from files on every page load (which would kill performance). To update the data cache, you can call a function that will reread all the markdown files. But since the memory is only accessible from the node that is running the server, how do we do that? By connecting to the running server directly.

Let's say our Mebe server is running in some terminal window and spouting nonsense like:

21:56:28.930 request_id=Jw+Qnnpbzs6nrxUmaVku [info] GET /
21:56:28.930 request_id=Jw+Qnnpbzs6nrxUmaVku [info] Processing by MebeWeb.PageController.index/2
  Parameters: %{"format" => "html"}
  Pipelines: [:browser]
21:56:28.931 request_id=Jw+Qnnpbzs6nrxUmaVku [info] Sent 200 in 630µs

When starting the server, we have given it a name by giving the --sname argument to the elixir command. As a result, our server node is using the name mebe@ksenon (ksenon being the host). What we do is start a new IEx shell and give it a name too:

[nicd@ksenon ~]$ iex --sname meberefresher
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(meberefresher@ksenon)1>

Now we have our own node with the name meberefresher@ksenon. To connect to our running Mebe server, we press Ctrl-G and type in the parts after the arrows:

...
iex(meberefresher@ksenon)1>
User switch command
 --> r 'mebe@ksenon' 'Elixir.IEx'
 --> c

You can find a more detailed description of the above here, but the gist is that r starts a remote shell in the given node. The second part of the command gives the shell to execute, which for us Elixorians is IEx. Finally typing in c connects to the started shell. Boom:

...
--> c
Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(mebe@ksenon)1>

We now have a shell open in the running server instance! Pretty cool, huh? In the shell we can execute any code and it will run in that node. As an example, let's refresh our Mebe post database:

iex(mebe@ksenon)1> GenServer.call MebeEngine.worker_name, :refresh
:ok

When we press enter, we can instantly see what happened in the other window, where the server is originally running:

22:32:49.092 [info] Destroying database…
22:32:49.092 [info] Reloading database…
22:32:49.092 [info] Loading post database from '/var/www/blog/data'…
22:32:49.092 [info] Searching files using '/var/www/blog/data/**/*.md' with cwd '/var/www/blog'
22:32:49.096 [info] Found files:
22:32:49.096 [info] /var/www/blog/data/pages/about.md
...

That's not all! If our server had a GUI, we could run :observer.start to start a nice app, which would tell us the state of the server, scheduler usage, memory usage, list of processes and more (in fact it should be possible to run it remotely with SSH tunneling, but I was unable to get it working with a quick try). Another use case might be Phoenix's Channels, which could be used for group messaging, like in a chat room. With a remote shell, you could connect to a channel, observe its messages and send your own. Maybe you have a big single-page app and you decide to send a note to every client using it, for example about an impending maintenance window. Just open a remote shell and send your message to a channel!

Granted, those are somewhat silly examples but you can probably think of more useful applications for it.

PS.: Whoops, I accidentally published this post too early, because I ran the demo commands there on my live blog. 😅 Thankfully I don't have that much traffic on this blog. Allows for playing around…