Intro

If you are planning to make a presentation including some live command-line examples, the following article could be useful for you.

I’d like to share with you my setup which results in HTML presentation containing embedded console windows which can run isolated from your host system.


Prerequisites

Following technologies are used to create the resulting presentation:

  • reveal.js - framework for creating HTML presentations
  • ttyd - tool to access Linux shell over HTTP
  • podman - tool for managing containers

Show the code first

Prepare the example

Let’s say we want to present and describe an example of our source code and then show the audience how it is being run on the target system using the CLI.

Here we’ll use this simple Python snippet:

import random

# Generate the number
number = random.randint(1, 100)

# Print the number
print(f'Your lucky number is {number}.')

Insert it inside the presentation

Suppose we have already configured and running some instance of the reveal.js presentation, we can insert a section with our code example there:

<section>
    <h4>Python demo</h4>
    <pre>
        <code>
            import random

            # Generate the number
            number = random.randint(1, 100)

            # Print the number
            print(f'Your lucky number is {number}.')
        </code>
    </pre>
</section>

Stylize the snippet

Now we can play a bit more with the layout. We could change the default font-size and the width of the code block, so our snippet doesn’t contain any scrollbars and it is better centered within the presentation screen.

By default the reveal.js presentation has configured the highlight.js plugin for syntax highlighting, so we can define the language of our snippet and apply the colors by adding the class="hljs language-python".

To emphasize only part of the code step by step, we can use the data-line-numbers attribute where the vertical bar character denotes the transitions, f.e. "|1|3-4|6-7" means starting with the whole code highlighted, followed by just line number 1, then lines 3-4 and ending with lines 6-7.

The result could look like this:

<section>
    <h4>Python demo</h4>
    <pre style="font-size: 18px; width: 60%;">
        <code class="hljs language-python" data-line-numbers="|1|3-4|6-7">
            import random

            # Generate the number
            number = random.randint(1, 100)

            # Print the number
            print(f'Your lucky number is {number}.')
        </code>
    </pre>
</section>

Add an interactive console

Setup the web terminal

Deploying the shell web server is very simple. When we have downloaded the ttyd binary, we just provide the port number where the daemon will be listening and providing the HTTP layer above the console.

Following example will deploy ttyd web server on the port 1234 and for every connected client it will create a new process with bash:

ttyd -p 1234 bash

Integrate the console

Putting the console into the presentation is as simple as adding new iframe pointing to our ttyd service at http://localhost:1234/:

<iframe src="http://localhost:1234/"></iframe>

Tuning the visual

Now to create a seamless transition between the code snippet and the console window we need to add a bit more configuration.

We can setup a custom font size and also change the colors for ttyd console like this:

ttyd -p 1234 -t fontSize=12 -t 'theme={"background": "white", "foreground": "black"}' bash

In reveal.js we will stack the console frame window on the top of the code snippet while keeping it invisible until the snippet code slides are fully traversed. This could be done by including the both frames inside the parent div having the r-stack class and showing the console at the right moment by adding the fragment class to the console iframe.

In the end we can change the console frame size to match the code snippet.

One hack that could be handy when we don’t want to show the vertical scrollbar inside the console frame, but still keeping the scrolling functionality. In this case we can wrap the console in the div which will match the size of the code example frame, but we stretch the width of the actual iframe a bit, so the scrollbar is hidden. This also needs to setup overflow: hidden; in the wrapping div.

So the result could look like this:

<section>
    <h4>Python demo</h4>
    <div class="r-stack">
        <pre id="code">
            <code class="hljs language-python" data-trim data-line-numbers="|1|3-4|6-7">
                import random

                # Generate the number
                number = random.randint(1, 100)

                # Print the number
                print(f'Your lucky number is {number}.')
            </code>
        </pre>
        <div id="cli-wrapper">
            <iframe id="cli" class="fragment fade-up" src="http://localhost:1234/"></iframe>
        </div>
    </div>
</section>

And the CSS now moved into it’s own stylesheet:

#code {
    font-size: 18px; 
    width: 60%;
}

#cli-wrapper {
    max-width: 60%; 
    width: 60%; 
    max-height: 100%; 
    height: 100%; 
    overflow: hidden;
}

#cli {
    max-width: 103%; 
    width: 103%; 
    max-height: 100%; 
    height: 100%;
}

Note: I am definitely not a CSS guy, so please don’t blame me if you find anything ridiculous about the mentioned code. But, it should work 😇

This is the final output when running the presentation in the web browser:


Using containers

When doing more examples in the presentation it might be useful to always have an isolated environment for each demo.

This can be done easily by using the podman containers. We can deploy a container from the public image, do some customizations, prepare our demo environment and then serialize the state of the container. Then we can setup ttyd to run a clean container from this image every time client requests new console.

So if we use the Fedora Linux as an example, we can download the latest Fedora container image and get inside that:

podman pull fedora
podman run -it fedora

It will redirect us inside the container terminal:

[root@fdee00b17d43 /]# 

Now prepare the environment needed for the demo, like installing dependencies, copying the example scripts into the container etc.

When everything is ready, we can export the container from another shell:

podman container export fdee00b17d43 > container.tar

Then we can import it in the image registry on local or any other computer and tagging it with example-container name by doing:

podman image import container.tar example-container

Finally we will prepare the ttyd daemon to spawn a new container for us on each attach:

ttyd -p 1234 podman run -it example-container /bin/bash

We can also change the container’s hostname with -h my-hostname, so the shell on the live demo will not show the ugly auto-generated id.

And of course we can prepare many containers running on different ports with various font sizes, configuration, etc.

Note: each time client connects to the ttyd, new container is created. This also means when the page having the embedded terminal is refreshed. Therefore it may be desirable to cleanup all the related containers after the presentation is done:

podman container rm --filter ancestor=localhost/example-container

References

Here are links to show you an example of such a presentation. It is from our DNF5 talk we had at FOSDEM last week: