Larry Price

And The Endless Cup Of Coffee

Creating an Asynchronous D-Bus Service With Python

| Comments

This is the second in a series of blog posts on creating an asynchronous D-Bus service in python. For part 1, go here.

Last time we created a base for our asynchronous D-Bus service with a simple synchronous server/client. In this post, we’ll start from that base which can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part1. Of course, all of today’s code can be found in the same project with the part2 tag: https://github.com/larryprice/python-dbus-blog-series/tree/part2.

Why Asynchronous?

Before we dive into making our service asynchronous, we need a reason to make our service asynchronous. Currently, our only d-bus object contains a single method, quick, which lives up to its namesake and is done very quickly. Let’s add another method to RandomData which takes a while to finish its job.

random_data.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import dbus.service
import random
import time

class RandomData(dbus.service.Object):
    def __init__(self, bus_name):
        super().__init__(bus_name, "/com/larry_price/test/RandomData")
        random.seed()

    @dbus.service.method("com.larry_price.test.RandomData",
                         in_signature='i', out_signature='s')
    def quick(self, bits=8):
        return str(random.getrandbits(bits))

    @dbus.service.method("com.larry_price.test.RandomData",
                         in_signature='i', out_signature='s')
    def slow(self, bits=8):
        num = str(random.randint(0, 1))
        while bits > 1:
            num += str(random.randint(0, 1))
            bits -= 1
            time.sleep(1)

        return str(int(num, 2))

Note the addition of the slow method on the RandomData object. slow is a contrived implementation of building an n-bit random number by concatenating 1s and 0s, sleeping for 1 second between each iteration. This will still go fairly quickly for a small number of bits, but could take quite some time for numbers as low as 16 bits.

In order to call the new method, we need to modify our client binary. Let’s add in the argparse module and take in a new argument: --slow. Of course, --slow will instruct the program to call slow instead of quick, which we’ll add to the bottom of the program.

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3

# Take in a single optional integral argument
import sys
import argparse

arg_parser = argparse.ArgumentParser(description='Get random numbers')
arg_parser.add_argument('bits', nargs='?', default=16)
arg_parser.add_argument('-s', '--slow', action='store_true',
                        default=False, required=False,
                        help='Use the slow method')

args = arg_parser.parse_args()

# Create a reference to the RandomData object on the  session bus
import dbus, dbus.exceptions
try:
    bus = dbus.SessionBus()
    random_data = bus.get_object("com.larry-price.test", "/com/larry_price/test/RandomData")
except dbus.exceptions.DBusException as e:
    print("Failed to initialize D-Bus object: '%s'" % str(e))
    sys.exit(2)

# Call the appropriate method with the given number of bits
if args.slow:
    print("Your random number is: %s" % random_data.slow(int(args.bits)))
else:
    print("Your random number is: %s" % random_data.quick(int(args.bits)))

Now we can run our client a few times to see the result of running in slow mode. Make sure to start or restart the service binary before running these commands:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./client 4
Your random number is: 2
$ ./client 4 --slow
Your random number is: 15
$ ./client 16
Your random number is: 64992
$ ./client 16 --slow
Traceback (most recent call last):
  File "./client", line 26, in <module>
    print("Your random number is: %s" % random_data.slow(int(args.bits)))
  File "/usr/lib/python3/dist-packages/dbus/proxies.py", line 70, in __call__
    return self._proxy_method(*args, **keywords)
  File "/usr/lib/python3/dist-packages/dbus/proxies.py", line 145, in __call__
    **keywords)
  File "/usr/lib/python3/dist-packages/dbus/connection.py", line 651, in call_blocking
    message, timeout)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.

Your mileage may vary (it is a random number generator, after all), but you should eventually see a similar crash which is caused by a timeout in the response of the D-Bus server. We know that this algorithm works; it just needs more time to run. Since a synchronous call won’t work here, we’ll have to switch over to more asynchronous methods…

An Asynchronous Service

At this point, we can go one of two ways. We can use the threading module to spin threads within our process, or we can use the multiprocessing module to create child processes. Child processes will be slightly pudgier, but will give us more functionality. Threads are a little simpler, so we’ll start there. We’ll create a class called SlowThread, which will do the work we used to do within the slow method. This class will spin up a thread that performs our work. When the work is finished, it will set a threading.Event that can be used to check that the work is completed. threading.Event is a cross-thread synchronization object; when the thread calls set on the Event, we know that the thread is ready for us to check the result. In our case, we call is_set on our event to tell a user whether or not our data is ready.

random_data.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# ...

import threading
class SlowThread(object):
    def __init__(self, bits):
        self.finished = threading.Event()
        self.result = ''

        self.thread = threading.Thread(target=self.work, args=(bits,))
        self.thread.start()
        self.thread_id = str(self.thread.ident)

    @property
    def done(self):
        return self.finished.wait(1)

    def work(self, bits):
        num = str(random.randint(0, 1))
        while bits > 1:
            num += str(random.randint(0, 1))
            bits -= 1
            time.sleep(1)

        self.result = str(num)
        self.finished.set()

# ...

On the RandomData object itself, we’ll initialize a new thread tracking list called threads. In slow, we’ll initialize a SlowThread object, append it to our threads list, and return the thread identifier from SlowThread. We’ll also want to add a method to try to get the result from a given SlowThread called slow_result, which will take in the thread identifier we returned earlier and try to find the appropriate thread. If the thread is finished (the event is set), we’ll remove the thread from our list and return the result to the caller.

random_data.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ...

class RandomData(dbus.service.Object):
    def __init__(self, bus_name):
        super().__init__(bus_name, "/com/larry_price/test/RandomData")

        random.seed()
        self.threads = []

    # ...

    @dbus.service.method("com.larry_price.test.RandomData",
                         in_signature='i', out_signature='s')
    def slow(self, bits=8):
        thread = SlowThread(bits)
        self.threads.append(thread)
        return thread.thread_id

    @dbus.service.method("com.larry_price.test.RandomData",
                         in_signature='s', out_signature='s')
    def slow_result(self, thread_id):
        thread = [t for t in self.threads if t.thread_id == thread_id]
        if not thread:
            return 'No thread matching id %s' % thread_id

        thread = thread[-1]
        if thread.done:
            result = thread.result
            self.threads.remove(thread)
            return result

        return ''

Last thing we need to do is to update the client to use the new methods. We’ll call slow as we did before, but this time we’ll store the intermediate result as the thread identifier. Next we’ll use a while loop to spin forever until the result is ready.

client
1
2
3
4
5
6
7
8
9
10
11
12
13
# ...

if args.slow:
    import time
    thread_id = random_data.slow(int(args.bits))
    while True:
        result = random_data.slow_result(thread_id)
        if result:
            print("Your random number is: %s" % result)
            break
        time.sleep(1)

# ...

Note that this is not the smartest way to do this; more on that in the next post. Let’s give it a try!

1
2
3
4
5
6
7
8
$ ./client 4
Your random number is: 7
$ ./client 4 --slow
Your random number is: 12
$ ./client 16
Your random number is: 5192
$ ./client 16 --slow
27302

Next time

This polling method works as a naive approach, but we can do better. Next time we’ll look into using D-Bus signals to make our client more asynchronous and remove our current polling implementation.

As a reminder, the end result of our code in this post is MIT Licensed and can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part2.

Creating a D-Bus Service With Python

| Comments

I’ve been working on a d-bus service to replace some of the management guts of my project for a while now. We started out creating a simple service, but some of our management processes take a long time to run, causing a timeout error when calling these methods. I needed a way to run these tasks in the background and report status to any possible clients. I’d like to outline my approach to making this possible. This will be a multi-part blog series starting from the bottom: a very simple, synchronous d-bus service. By the end of this series, we’ll have a small codebase with asynchronous tasks which can be interacted with (input/output) from D-Bus clients.

All of this code is written with python3.5 on Ubuntu 17.04 (beta), is MIT licensed, and can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part1.

What is D-Bus?

From Wikipedia:

In computing, D-Bus or DBus (for “Desktop Bus”), a software bus, is an inter-process communication (IPC) and remote procedure call (RPC) mechanism that allows communication between multiple computer programs (that is, processes) concurrently running on the same machine.

D-Bus allows different processes to communicate indirectly through a known interface. The bus can be system-wide or user-specific (session-based). A D-Bus service will post a list of available objects with available methods which D-Bus clients can consume. It’s at the heart of much Linux desktop software, allowing processes to communicate with one another without forcing direct dependencies.

A synchronous service

Let’s start by building a base of a simple, synchronous service. We’re going to initialize a loop as a context to run our service within, claim a unique name for our service on the session bus, and then start the loop.

service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3

import dbus, dbus.service, dbus.exceptions
import sys

from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib

# Initialize a main loop
DBusGMainLoop(set_as_default=True)
loop = GLib.MainLoop()

# Declare a name where our service can be reached
try:
    bus_name = dbus.service.BusName("com.larry-price.test",
                                    bus=dbus.SessionBus(),
                                    do_not_queue=True)
except dbus.exceptions.NameExistsException:
    print("service is already running")
    sys.exit(1)

# Run the loop
try:
    loop.run()
except KeyboardInterrupt:
    print("keyboard interrupt received")
except Exception as e:
    print("Unexpected exception occurred: '{}'".format(str(e)))
finally:
    loop.quit()

Make this binary executable (chmod +x service) and run it. Your service should run indefinitely and do… nothing. Although we’ve already written a lot of code, we haven’t added any objects or methods which can be accessed on our service. Let’s fix that.

dbustest/random_data.py
1
2
3
4
5
6
7
8
9
10
11
12
import dbus.service
import random

class RandomData(dbus.service.Object):
    def __init__(self, bus_name):
        super().__init__(bus_name, "/com/larry_price/test/RandomData")
        random.seed()

    @dbus.service.method("com.larry_price.test.RandomData",
                         in_signature='i', out_signature='s')
    def quick(self, bits=8):
        return str(random.getrandbits(bits))

We’ve defined a D-Bus object RandomData which can be accessed using the path /com/larry_price/test/RandomData. This style of string is the general style of an object path. We’ve defined an interface implemented by RandomData called com.larry_price.test.RandomData with a single method quick as declared with the @dbus.service.method context decorator. quick will take in a single parameter, bits, which must be an integer as designated by the in_signature in our context decorator. quick will return a string as specified by the out_signature parameter. All that quick does is return a random string given a number of bits. It’s simple and it’s fast.

Now that we have an object, we need to declare an instance of that object in our service to attach it properly. Let’s assume that random_data.py is in a directory dbustest with an empty __init__.py, and our service binary is still sitting in the root directory. Just before we start the loop in the service binary, we can add the following code:

service
1
2
3
4
5
6
7
8
9
# ...
# Run the loop
try:
    # Create our initial objects
    from dbustest.random_data import RandomData
    RandomData(bus_name)

    loop.run()
# ...

We don’t need to do anything with the object we’ve initialized; creating it is enough to attach it to our D-Bus service and prevent it from being garbage collected until the service exits. We pass in bus_name so that RandomData will connect to the right bus name.

A synchronous client

Now that you have an object with an available method on our service, you’re probably interested in calling that method. You can do this on the command line with something like dbus-send, or you could find the service using a GUI tool such as d-feet and call the method directly. But eventually we’ll want to do this with a custom program, so let’s build a very small program to get started.

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3

# Take in a single optional integral argument
import sys
bits = 16
if len(sys.argv) == 2:
    try:
        bits = int(sys.argv[1])
    except ValueError:
        print("input argument must be integer")
        sys.exit(1)

# Create a reference to the RandomData object on the  session bus
import dbus, dbus.exceptions
try:
    bus = dbus.SessionBus()
    random_data = bus.get_object("com.larry-price.test", "/com/larry_price/test/RandomData")
except dbus.exceptions.DBusException as e:
    print("Failed to initialize D-Bus object: '%s'" % str(e))
    sys.exit(2)

# Call the quick method with the given number of bits
print("Your random number is: %s" % random_data.quick(bits))

A large chunk of this code is parsing an input argument as an integer. By default, client will request a 16-bit random number unless it gets a number as input from the command line. Next we spin up a reference to the session bus and attempt to find our RandomData object on the bus using our known service name and object path. Once that’s initialized, we can directly call the quick method over the bus with the specified number of bits and print the result.

Make this binary executable also. If you try to run client without running service, you should see an error message explaining that the com.larry-price.test D-Bus service is not running (which would be true). Start service, and then run client with a few different input options and observe the results:

1
2
3
4
5
6
7
8
9
$ ./service & # to kill service later, be sure to note the pid here!
$ ./client
Your random number is: 41744
$ ./client 100
Your random number is: 401996322348922753881103222071
$ ./client 4
Your random number is: 14
$ ./client "new donk city"
input argument must be integer

That’s all there is to it. A simple, synchronous server and client. The server and client do not directly depend on each other but are able to communicate unidirectionally through simple method calls.

Next time

Next time, I’ll go into detail on how we can create an asynchronous service and client, and hopefully utilize signals to add a new direction to our communication.

Again, all the code can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part1.

Snappy Libertine

| Comments

Libertine is software suite for runnin X11 apps in non-X11 environments and installing deb-based applications on a system without dpkg. Snappy is a package management system to confine applications from one another. Wouldn’t it be cool to run libertine as a snap?

Yes. Yes it would.

snapd

The first thing to install is snapd itself. You can find installation instructions for many Linux distros at snapcraft.io, but here’s the simple command if you’re on a debian-based operating system:

1
$ sudo apt install snapd

Ubuntu users may be surprised to find that snapd is already installed on their systems. snapd is the daemon for handling all things snappy: installing, removing, handling interface connections, etc.

lxd

We use lxd as our container backend for libertine in the snap. lxd is essentially a layer on top of lxc to give a better user experience. Fortunately for us, lxd has a snap all ready to go. Unfortunately, the snap version of lxd is incompatible with the deb-based version, so you’ll need to completely remove that before continuing. Skip this step if you never installed lxd:

1
2
3
4
$ sudo apt remove --purge lxd lxd-client
$ sudo zpool destroy lxd                 # if you use zfs
$ sudo ip link set lxdbr0 down           # take down the bridge (lxdbr0 is the default)
$ sudo brctl delbr lxdbr0                # delete the bridge

For installing, in-depth instructions can be found in this blog post by one of the lxd devs. In short, we’re going to create a new group called lxd, add ourselves to it, and then add our own user ID and group ID to map to root within the container.

1
2
3
4
5
6
$ sudo groupadd --system lxd                      # Create the group on your system
$ sudo usermod -G lxd -a $USER                    # Add the current user
$ newgrp lxd                                      # update current session with new group
$ echo root:`id --user ${USER}`:1 >> /etc/subuid  # Setup subuid to map correctly
$ echo root:`id --group ${USER}`:1 >> /etc/subgid # Setup subgid to map correctly
$ sudo snap install lxd                           # actually install the snap!

We also need to initialize lxd manually. For me, the defaults all work great. The important pieces here are setting up a new network bridge and a new filestore for lxd to use. You can optionally use zfs if you have it installed (zfsutils-linux should do it on Ubuntu). Generally, I just hit “return” as fast as the questions show up and everything turns out alright. If anything goes wrong, you may need to manually delete zpools, network bridges, or reinstall the lxd snap. No warranties here.

1
2
3
4
5
6
7
8
9
10
11
$ sudo lxd init
Do you want to configure a new storage pool (yes/no) [default=yes]?
Name of the new storage pool [default=default]:
Name of the storage backend to use (dir or zfs) [default=zfs]:
Create a new ZFS pool (yes/no) [default=yes]?
Would you like to use an existing block device (yes/no) [default=no]?
Would you like LXD to be available over the network (yes/no) [default=no]?
Would you like stale cached images to be updated automatically (yes/no) [default=yes]?
Would you like to create a new network bridge (yes/no) [default=yes]?
What should the new bridge be called [default=lxdbr0]?
What IPv4 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]?

You should now be able to run lxd.lxc list without errors. It may warn you about running lxd init, but don’t worry about that if your initialization succeeded.

libertine

Now we’re onto the easy part. libertine is only available from edge channels in the app store, but we’re fairly close to having a version that we could push into more stable channels. For the latest and greatest libertine:

1
2
$ sudo snap install --edge libertine
$ sudo snap connect libertine:libertined-client libertine:libertined

If we want libertine to work fully, we need to jump through a couple of hoops. For starters, dbus-activation is not fully functional at this time for snaps. Lucky for us, we can fake this by either running the d-bus service manually (/snap/bin/libertined), or by adding this file to /usr/share/dbus-1/services/com.canonical.libertine.Service.service

/usr/share/dbus-1/services/com.canonical.libertine.Service.service
1
2
3
[D-BUS Service]
Name=com.canonical.libertine.Service
Exec=/snap/bin/libertine.libertined --cache-output

Personally, I always create the file, which will allow libertined to start automatically on the session bus whenever a user calls it. Hopefully d-bus activation will be fixed sooner rather than later, but this works fine for now.

Another issue is that existing deb-based libertine binaries may conflict with the snap binaries. We can fix this by adjusting PATH in our .bashrc file:

$HOME/.bashrc
1
2
# ...
export PATH=/snap/bin:$PATH

This will give higher priority to snap binaries (which should be the default, IMO). One more thing to fix before running full-force is to add an environment variable to /etc/environment such that the correct libertine binary is picked up in Unity 8:

/etc/environment
1
2
# ...
UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH=/snap/bin/libertine-launch

OK! Now we’re finally ready to start creating containers and installing packages:

1
2
3
4
$ libertine-container-manager create -i my-container
# ... (this could take a few minutes)
$ libertine-container-manager install-package -i my-container -p xterm
# ... (and any other packages you may want)

If you want to launch your apps in Unity 7 (why not?):

1
2
$ libertine-launch -i my-container xterm
# ... (lots of ourput, hopefully an open window!)

When running Unity 8, your apps should show up in the app drawer with all the other applications. This will all depend on libertined running, so make sure that it runs at startup!

I’ve been making a lot of improvements on the snap lately, especially as the ecosystem continues to mature. One day we plan for a much smoother experience, but this current setup will let us work out some of the kinks and find issues. If you want to switch back the deb-based libertine, you can just install it through apt and remove the change to /etc/environment.

2016 Retrospective

| Comments

This has been a unique year for me, and I wanted to quickly lay out what I’ve accomplished and where I think I’m going for the coming year. This is now officially a tradition at three posts (see: 2014 and 2015).

Revisiting 2016’s Goals

These are the goals I set for myself at the start of the year and how I met or missed them:

  • Spend Money
    • I wanted to become less of a miser and spend more money. Note that this is not for bragging purposes, just that I am naturally very frugal and hesitant to spend money. I think we did a pretty good job, though! For starters, I swapped out my commuter car for a tech-heavy crossover. We stayed in a really cool art-hotel in downtown Cincinnati in the Spring, drove across the country for a big Yellowstone trip in early Summer, stayed at the Indiana Dunes for a few days in the Fall, and took a brief trip to Madison, WI, shortly thereafter. I bought a nice sitting/standing desk and chair for my home office. I paid the entry fee to go to GenCon for a day. Our fridge, dishwasher, and furnace all died one weekend, and it ended with me buying upgraded, modern appliances. I’ve also been keeping the post office busy with plenty of orders off Amazon, and I’ve been trying to Kickstart more games that I think deserve attention. I also found a new hobby in homebrewing which has been a great use of fun-money.
  • Back Into Web
    • At the start of 2016, I though I really wanted to do more web work. Turns out I’ve done a 180 on this one, and I now work on primarily desktop/mobile with minimal web work, and I wouldn’t have it any other way for the time being.
  • Work With Others
    • In 2015, I worked alone a lot. In 2016, I joined a new company where I work remotely. Although I don’t necessarily see my coworkers every day, I am working on projects with multiple developers and communicating constantly with my teammates (and others) through chat. Working with others again has helped me grow socially and become a better engineer.
  • Become Part of an Open-Source Community
    • I really wanted to better integrate FLOSS into my life and my career, and I believe I’ve hit the jackpot. My job is to build popular open-source operating system Ubuntu, where all the code I write is public and generally GPLv3. My job is to write open-source software, and to interact with the community. I use my own software and report/fix bugs that affect me all the time.
  • Good Vibes
    • I was feeling a bit down at the end of 2015, and I wanted to be a more positive person in 2016. Although there were some depressing things going on worldwide in 2016, I am generally happier with my personal and professional life on a micro-level.

Surprise Victories

  • New Job
    • The big one! After almost four years at SEP, I transitioned to a new role at Canonical. I get to work on Ubuntu, one of my favorite open-source projects, as my full-time job. This also means I get to work remotely, dropping my commute from 30 miles in 2012 to 3 miles in 2014 to 50 feet in 2016. I’ve been having a great time working on software that’s in the public spotlight, working with the community, and traveling every few months to see my coworkers. With this new job, I’ve also had the opportunity to visit Europe for the first time this year.
  • Less Own-Time Work
    • Although I’ve hit pretty hard in the past that developers should do some learning outside of work, this year likely contained the least own-time work I’ve ever done. I’ve been finding joys in non-software hobbies and home maintenance, and working on Ubuntu as my full-time job has made me less needy for doing open-source work in my off-hours. I tend to respond to bugs and Github PRs at any hour, and I follow more technical people on social media than I used to. I think this stems from a satisfaction from the learning I do throughout the day, and the difficulty of separating work-life and home-life when one works at home.
  • FOSDEM
    • Yesterday, I learned that I’ll be giving a talk at FOSDEM. I’m very excited (and nervous) to give my first-ever conference talk in Brussels this February.
  • Homebrewing
    • I picked up homebrewing beer at the start of 2016, and I love it. I started with simple pre-made extract kits, but have worked my way up to creating my own all-grain recipes and labels. Brewing is a fun, tasty hobby giving me some creative, manual labor to break the mold of always doing computer work.
  • Books
  • Spotify
    • Spotify is very good for listening to whatever music I want anytime, especially now that I work at home. If you really like a band, you should still buy their music or merch through their website to make sure they get paid to keep existing.

2017 Goals

  • Local
    • I’d like to make sure I stay involved locally, especially as I continue to work from home. I’ve let my Golang group dwindle over the past few months, and I’d like to see us back up to our numbers at the start of 2016. If possible, I’d also like to attend other meetups and meet more local devs.
  • Linux Greybeard
    • This is a slow process, but I want to get better at using Linux in general. I’ve been surprised at how much I’ve learned about the low-level workings of Ubuntu over the past 9 months. I’m excited to see what I can learn over the next year, especially as I’ll likely move into a different codebase at some point this year.
  • More talking
    • I’m very excited to be giving a talk at FOSDEM this year, but I would enjoy doing such things more regularly. It doesn’t necessarily have to be at conferences, as I could do meetups much more easily. I need to try to get back into blogging more regularly. Additionally, I’ve recently been kicking around ideas for a discussion-based podcast on the worst parts of software development, although that may have already been done to death. Contact if interested.
  • Transition Web Tooling
    • I would like to switch over my analytics systems to use a personal Piwik instance, and I would love to replace the (hopefully unobtrusive) ads on this site with some kind of tip jar system. I would also like to update this blog to use a Let’s Encrypt certificate, as well as Ollert once I’ve been given full control.
  • Kegging
    • In my homebrewing, I’ve been bottling my beers. This is generally ok, but I think the beer would be consumed faster if I kegged it and could fill growlers for my friends and family. Getting started with kegging is expensive, requiring the purchase of kegs, tanks, parts, and some sort of refrigeration unit. By the end of the year, I intend to have a kegerator-style setup with the ability to stow and distribute from two kegs.
  • Moving
    • My wife is looking into graduate schools for Fall 2017, and has already been accepted by one. I’m currently assuming a big part of my life this Spring/Summer will be finding and adjusting to a new home.
  • Active Activism
    • I’ve complained a lot about our government and the way the world works on social media, at the “water cooler”, and privately to my wife, but it’s become obvious that passive activism isn’t good enough. Signing petitions and pledges are nice gestures, but are more meaningful when backed up by real action. I’d like to do something, though I’m not sure what at the moment. By the end of 2017, I would like to, at minimum, have a plan to donate, join, create, or generally be more involved.

Adieu, 2016

Major changes in my life, career, and the world at large have made 2016 a memorable year for me. I highly encourage you to reflect on the year you’ve had and think about what you can do to make 2017 great. Happy new year!

Confining a Snapped X11 Application

| Comments

Looking for the code? Look no further: https://github.com/larryprice/pingus-snap

In my last post, I demonstrated creating a snap package for an application available in the archive. I left that application unconfined, which is taboo in the long run if we want our system to be secure. In a few steps, we can add the necessary components to confine our pingus snap.

For reference, this is the original snapcraft.yaml file for creating a pingus snap, except that we’ve updated the confinement property to strict:

snapcraft.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: pingus
version: '0.1'
summary: Free Lemmings(TM) clone
description: |
    Pingus is a free clone of the popular Lemmings game.
    |
    Your goal is to guide a horde of penguins through a world full of obstacles
    and penguin traps to safety. Although penguins (unlike lemmings) are rather
    smart, they sometimes lack the necessary overview and now rely on you to
    save them.

grade: devel
confinement: strict

parts:
  archives:
    plugin: nil
    stage-packages:
      - pingus
  env:
    plugin: dump
    organize:
      pingus.wrapper: usr/bin/pingus

apps:
  pingus:
    command: pingus

If you’re feeling bold, you can build and install the snap from here, but be warned that this led me into an ncurses nightmare that I had to forcibly kill. That’s largely because pingus depends on X11, which is not available out-of-the-box once we’ve confined our snap. If we want to use X11, we’re going to need to connect to it using the snap-land concept of interfaces. Interfaces allow us to access shared resources and connections provided by the system or other snaps. There’s some terminology to grapple with here, but the bottom line is that a “slot” provides an interface which a “plug” connects to. You can see a big list of available interfaces with descriptions on the wiki. Our pingus app will “plug” into the X11 interface’s “slot”:

snapcraft.yaml
1
2
3
4
5
6
# ...
apps:
  pingus:
    command: pingus
    plugs:
      - x11

You can build and install the new snap with the --dangerous flag for your local confined snap. After that, you can verify the interface connection with the snap interfaces command:

1
2
3
4
5
6
7
8
9
$ snapcraft
$ sudo snap install --dangerous pingus_0.1_amd64.snap
pingus 0.1 installed
$ snap interfaces
Slot                     Plug
:alsa                    -
# ...
:upower-observe          -
:x11                     pingus

Now, when we run pingus… it works! Well, video works. If you want sound, we’ll also need the pulseaudio interface:

snapcraft.yaml
1
2
3
4
5
6
7
# ...
apps:
  pingus:
    command: pingus
    plugs:
      - x11
      - pulseaudio

Once again: build, install, and run… et voilà! Is it just me, or was that surprisingly painless? Of course, not all applications live such isolated lives. Make note that the x11 interface is supposed to be a transitional interface, meaning that we would rather our app fully transition to Mir or some alternative. To go a step further with this snap, we could create a snapcraft.yaml to build from source to get the absolute latest version of our app. At this point, we can change our grade property to stable and feel good about something that we could push to the store for review.

Any code you see here is free software. Find the project here: https://github.com/larryprice/pingus-snap