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 which is signed by a GPG key associated with the publisher’s Ubuntu One account.

The model 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 file to include your own selection of snaps.


1. Download a model file

The quickest way to create a new model assertion is to edit a model that already exists. Reference models for every supported Ubuntu Core device can be found in the snapcore/models GitHub repository.

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

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

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

2. Edit the model file

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, retrieved with the snapcraft whoami command. (“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": "2024-04-19T08:42:32+00:00",

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": "24/stable",
            "id": "YbGa9O3dAXl88YLI6Y1bGG74pwBxZyKg"
        },
       ...

This section lists the snaps to be included in the image. pi (shown above), pi-kernel, core24 and snapd are the four snaps required for a functioning Ubuntu Core device. The additional console-conf snap is required for Ubuntu Core 24 devices.

  1. Console-conf is the interactive setup utility that’s used to configure the network and default user when the device is first booted. This is marked as optional, but for this tutorial we need this to be mandatory in our own image. To do this, delete the "presence": "optional" line (line 41) and delete the comma at the end of the preceding line.

  2. We also need to change the default-channel for all the default snaps, from 24/stable to 24/edge. Your editor’s Replace function is ideal for this.

Additional snaps are included using the same schema, 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 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/stable",
            "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 core22 (see the output from snap info adguard-home --verbose | grep "base:"). This can be added with the following:

        {
            "name": "core22",
            "type": "base",
            "default-channel": "latest/stable",
            "id": "amcUKQILKXHHTlmSa7NMdnXSx02dNeeT"
        }

The snap-id for a snap is in the output of the snap info <snap-name> command.

3. Complete model example

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

{
    "type": "model",
    "series": "16",
    "model": "ubuntu-core-24-pi-arm64",
    "architecture": "arm64",
    "authority-id": "Zg6Qv6Z53HIkziXyxtn1XItIq",
    "brand-id": "Zg6Qv6Z53HIkziXyxtn1XItIq",
   "timestamp": "2024-04-19T08:42:32+00:00",
    "base": "core24",
    "grade": "signed",
    "snaps": [
        {
            "name": "pi",
            "type": "gadget",
            "default-channel": "24/edge",
            "id": "YbGa9O3dAXl88YLI6Y1bGG74pwBxZyKg"
        },
        {
            "name": "pi-kernel",
            "type": "kernel",
            "default-channel": "24/edge",
            "id": "jeIuP6tfFrvAdic8DMWqHmoaoukAPNbJ"
        },
        {
            "name": "core24",
            "type": "base",
            "default-channel": "latest/edge",
            "id": "dwTAh7MZZ01zyriOZErqd1JynQLiOGvM"
        },
        {
            "name": "snapd",
            "type": "snapd",
            "default-channel": "latest/edge",
            "id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
        },
        {
            "name": "console-conf",
            "type": "app",
            "default-channel": "24/edge",
            "id": "ASctKBEHzVt3f1pbZLoekCvcigRjtuqw"
        },
        {
            "name": "adguard-home",
            "type": "app",
            "default-channel": "latest/stable",
            "id": "UXZIkJfJT2SPCGejjnSjOBqJ71yHk8bw"
        },
        {
            "name": "core22",
            "type": "base",
            "default-channel": "latest/stable",
            "id": "amcUKQILKXHHTlmSa7NMdnXSx02dNeeT"
        }
	]
}

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

Can I clarify that a “model assertion” is, by definition, signed? For precision, it seems that the text file one creates initially is just the “model”, and once it is signed, it becomes a “model assertion.” What this suggests is that it’s redundant to describe something as a “signed model assertion” since the model assertion is signed by definition. Thoughts?

I might update this page in a couple ways to make it more current. First, use a core22 example. Also, find a snap other than adguard-home as that now has a base snap of core22; pick something else that has an older base snap to explicitly show how to add an older snap in a core22 model file.

1 Like

This is a good point, and I totally agree. We should explain the distinction and then use the terms consistently. I’ll update this and take a look over our other assertion pages.

I admit, I’m not sure what the consensus is.

I’m going to mention again that I think it’s confusing to refer to “signed model assertions”, or to direct the reader to “sign” the model assertion. By definition, any assertion is already “a digitally signed document” (right there in the first sentence), https://ubuntu.com/core/docs/reference/assertions. I think the proper verbiage is that one creates a “model”, then signs it to get the “model assertion.”

P.S. If you read the explanation on this page, it is confusing in that it directs the reader to download a model assertion. Again, that is not what is happening; it is a model that is downloaded, edited and subsequently signed.

1 Like

Came across this issue with adguard-home as it needs a base snap core22.
Please consider using another snap as an example or up the tutorial to use core22.

I’ll repeat myself just to bring this to the top – you should distinguish between text-based “models”, and signed “assertions.” I believe the word “assertion” should be reserved for digitally-signed objects. So the thing displayed on this page is not an assertion, it is just a model, to be subsequently signed.

Thanks for bringing this up. I’ve made a start on being consistent with the use of assertion only for the signed artefact, and I’ll change other references as I go through the docs.

@ycheng And thank you for flagging that adguard-home is now built on core22. I’ve updated the model example to reflect this change.

It would be great to have a reference to how to specify the snap revision
https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/ubuntu-image.html#options-of-the-snap-command

So far my experience to mantain a custom Ubuntu Core image seems to be a bit split, not centralized

  1. Git maintain the changes of the model.json file
  2. Git mantain a script with the specific comands for ubuntu-image to build the image
    Since there is no revision on the model, but the only way to mantain the revisions seems to be through a parameter in ubuntu-image --revision <snap-name>:<revision>

You could use validation-set assertions if you want to fix the revisions of an image. That can be done in two steps:

  1. Create a validation-set assertion by following Validation sets | Snapcraft documentation - snapcraft will push the assertion to the store
  2. Reference the validation-set in your model assertion (see https://ubuntu.com/core/docs/reference/assertions/model for the sintax)
1 Like

We also need to change the default-channel for all the default snaps, from 24/stable to 24/edge.

Why do we need to do this? What are the implications of leaving the default snaps on stable?

I have a bash script that follows the steps in this guide and successfully builds the img. It looks like the edge changes in the guide are new and I’m trying to get some clarity about it.