Your submission was sent successfully! Close

You have successfully unsubscribed! Close

Create a model assertion

At the heart of custom Ubuntu Core image creation is the model assertion. An assertion is a signed recipe that describes the components that comprise a complete image. An assertion is provided as JSON in a text file, signed by a GPG key associated with the publisher’s Ubuntu One account.

The model assertion contains:

  • identification information, such as the developer-id and model name.
  • which essential snaps make up the device system.
  • other required or optional snaps that implement the device functionality.

See below for details on how to download and modify a model assertion to include your own selection of snaps.


1. Download a model assertion

The quickest way to create a new model assertion is to edit one that already exists, and you can find the reference model assertions for every supported Ubuntu Core device in the snapcore/models GitHub repository.

For this project, we’re going to modify the 64-bit reference model assertion for the Raspberry Pi: ubuntu-core-20-pi-arm64.json.

Download and save the file locally with the following wget command. We’ve called it my-model.json:

$ wget -O my-model.json https://raw.githubusercontent.com/snapcore/models/master/ubuntu-core-20-pi-arm64.json

2. Edit the model assertion

We now need to edit my-model.json using a text editor:

$ nano my-model.json

The following fields in my-model.json need to be changed:

2.1 "authority-id" and "brand-id"

"authority-id": "canonical",
"brand-id": "canonical",

These properties define the authority responsible for the image. Change both instances of the string “canonical” to your developer id that you retrieved earlier (“xSfWKGdLoQBoQx88”, in our example output). This links the image to your Ubuntu One account and ensures that only you can push image updates to devices using your model.

2.2 "timestamp"

    "timestamp": "2020-03-31T12:00:00.0Z", 

This needs to be provided at the end of the process; we’ll come back to this.

2.3 "snaps"

    "snaps": [
        {
            "name": "pi",
            "type": "gadget",
            "default-channel": "20/stable",
            "id": "YbGa9O3dAXl88YLI6Y1bGG74pwBxZyKg"
        },
        ...

This section lists the snaps to be included in the image. pi (shown above), pi-kernel, core20 and snapd are the four snaps required for a functioning Ubuntu Core device.

Additional snaps are included in the same way, with each snap requiring the following fields:

  • name: simply the snap name.
  • type: the type of snap. This is app for standard application snaps.
  • default-channel: the channel to install the snap from.
  • id: a unique snap identifier associated with every published snap. This is snap-id in the output from snap info <snap-name>.

For this tutorial, we’re going to add the AdGuard Home snap, an open source network-wide blocker for advertising and tracking. It’s an ideal candidate for an Ubuntu Core image like this because it benefits from frequent autonomous updates and a confined environment.

To add the AdGuard Home snap to our image, we need to add the following JSON stanza to the snaps section in our my-model.json (note the comma you need to append to the preceding snap entry to show continuation):

        },
	    {
            "name": "adguard-home",
            "type": "app",
            "default-channel": "latest/edge",
            "id": "UXZIkJfJT2SPCGejjnSjOBqJ71yHk8bw"
        }

Snaps do not have dependencies, but they do require the presence of the base snap they were built on. AdGuard Home is built using a base of core20 (see the output from snap info adguard-home --verbose | grep "base:"), which is the default base for our image, so no further edits are necessary.

3. Complete model assertion

After finishing all your edits, the completed my-model.json text file should now contain the following:

{
    "type": "model",
    "series": "16",
    "authority-id": "xSfWKGdLoQBoQx88",
    "brand-id": "xSfWKGdLoQBoQx88",
    "model": "ubuntu-core-20-pi-arm64",
    "architecture": "arm64",
    "timestamp": "2022-02-16T12:50:44+00:00",
    "base": "core20",
    "grade": "signed",
    "snaps": [
        {
            "name": "pi",
            "type": "gadget",
            "default-channel": "20/stable",
            "id": "YbGa9O3dAXl88YLI6Y1bGG74pwBxZyKg"
        },
        {
            "name": "pi-kernel",
            "type": "kernel",
            "default-channel": "20/stable",
            "id": "jeIuP6tfFrvAdic8DMWqHmoaoukAPNbJ"
        },
        {
            "name": "core20",
            "type": "base",
            "default-channel": "latest/stable",
            "id": "DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q"
        },
        {
            "name": "snapd",
            "type": "snapd",
            "default-channel": "latest/stable",
            "id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
        },
        {
            "name": "adguard-home",
            "type": "app",
            "default-channel": "latest/edge",
            "id": "UXZIkJfJT2SPCGejjnSjOBqJ71yHk8bw"
        }
    ]
}

After a model assertion has been created, the next step is to sign it. See Sign a model assertion for further details.

Last updated 5 months ago. Help improve this document in the forum.