Create an Ubuntu Core image with Landscape Client included
Canonical recently released the Landscape Client snap which, along with the new snap management features in the Landscape web portal, allows for device management of Ubuntu Core devices. In this blog we will look at how this can be deployed at scale by building a custom Ubuntu Core image that includes the Landscape Client snap and how to configure the image to automatically enrol the device after its first boot.
This blog follows the tutorial Build your own Ubuntu Core image, which shows how to create a custom image for a Raspberry Pi.
Defining your Image
The Model Assertion
As we are following the tutorial we will have already set up our Ubuntu One account and now we are ready to create our model assertion. This is the recipe that describes all the components that comprise our image and will therefore need the Landscape Client to be added into the mix.
We will base this example on a Raspberry Pi running Ubuntu Core 22, and so we will start with the reference model file we can download with:
wget -O my-model.json https://raw.githubusercontent.com/snapcore/models/master/ubuntu-core-22-pi-arm64.json
Now we need to edit the model file, again following the tutorial we set our authority-id and brand-id to our developer id.
{
"type": "model",
"series": "16",
"model": "ubuntu-core-22-pi-arm64",
"architecture": "arm64",
"authority-id": "<your id>",
"brand-id": "<your id>",
"timestamp": "2022-04-04T10:40:41+00:00",
"base": "core22",
"grade": "signed",
"snaps": [
{
"name": "pi",
"type": "gadget",
"default-channel": "22/stable",
"id": "YbGa9O3dAXl88YLI6Y1bGG74pwBxZyKg"
},
{
"name": "pi-kernel",
"type": "kernel",
"default-channel": "22/stable",
"id": "jeIuP6tfFrvAdic8DMWqHmoaoukAPNbJ"
},
{
"name": "core22",
"type": "base",
"default-channel": "latest/stable",
"id": "amcUKQILKXHHTlmSa7NMdnXSx02dNeeT"
},
{
"name": "snapd",
"type": "snapd",
"default-channel": "latest/stable",
"id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
}
]
}
Adding the Landscape Client
Having gotten our base image definition we want to add the Landscape Client by adding this stanza to the snaps list. As the Landscape Client is currently in beta and doesn’t have a stable release yet, we will specify that with the default-channel parameter. The id parameter is unique to each snap with the value shown below belonging to the client. If you need to find the id of any other snap, you can use the snap info <snap-name> command in your terminal and look for the snap-id.
{
"name": "landscape-client",
"type": "app",
"default-channel": "latest/beta",
"id": "ffnH0sJpX3NFAclH777M8BdXIWpo93af"
}
Default Configuration and the Gadget Snap
Now we have our model assertion, we could sign this and build an image and we would have the Landscape Client. However, it would only have a default configuration that wouldn’t do much, leaving us having to manually configure the client. This works perfectly well, but what if we don’t want to have to access each device and do this? Can we pre-configure the client when we build our image? Also, can we make the client automatically enrol without any external intervention?
Of course, the answer to these questions is yes. Yes, we can. We just need to create our own gadget snap.
The gadget snap is a special type of snap that contains device specific support code and data. You can read more about them here in the snapcraft documentation.
This example is based on the official Ubuntu Core 22 gadget snap for the Pi. Fork this repository to your local environment and we can configure it for our needs.
Essentially, all we need to do is append the following configuration at the bottom of the gadget.yaml file that defines the gadget snap:
defaults:
# landscape client
ffnH0sJpX3NFAclH777M8BdXIWpo93af:
landscape-url: <landscape-url>
account-name: <account-name>
registration-key: "<registration-key>"
auto-register:
enabled: true
computer-title-pattern: test-${model:7}-${serial:0:8}
wait-for-serial-as: true
Don’t forget to replace the placeholder values like <landscape-url> with the relevant config values.
Automatic Registration
The first part of the configuration defines the details of the Landscape server instance we’ll be using and will be the same for all devices that run this image. After this we want to configure the automatic registration component so that the device will register itself with the server shortly after being started up for the first time.
We have three parameters in this example. The first one enables the auto-registration on first boot. The second one, computer-title-pattern, allows us to define the computer title for this specific device.
The pattern uses the bash shell parameter expansion format to manipulate the available parameters. In this example the computer title will be set to the string “test-” followed by the device model starting from the 8th character (see our model assertion) and then the first 8 characters of the device serial number taken from its serial assertion.
For example, in this case it would something like: test-core-22-pi-arm64-f6ec1539
The fields available are listed below. The final parameter though, wait-for-serial-as, tells the auto registration function to wait until the device has been able to obtain its serial assertion from a serial vault before trying to create the title and perform the registration. This is necessary as a completely fresh device will not initially have a serial assertion.
Parameter | Description |
serial | Serial from device’s serial assertion |
model | model id from device’s serial assertion |
brand | brand-id from device’s serial assertion |
hostname | device’s network hostname |
ip | device’s IP address of primary NIC |
mac | device’s MAC address of primary NIC |
prodiden | Product identifier |
serialno | Serial Number |
datetime | date/time of auto-enrolment |
Build the Gadget and Updating our Model Assertion
Now we have our configuration for Landscape all set up, we just need to build the gadget snap. This simply requires the following command to be run in the base folder of your local gadget snap repository:
$ snapcraft
After some whirring, you will have your snap and this is the one we want to include in our model assertion.
With our current model assertion, when we build our image, we will go off to the Snap Store and download the listed snaps and include them in the image, including the reference gadget snap. Now we have our own gadget snap, we want to use this one instead.
If you have your own brand store, you can publish your custom gadget snap there. Then change the name and id of the gadget snap in your model assertion and all will be well. If you do not have your own brand store, the process is a little more manual. It is not permitted to upload custom gadget snaps to the global snap store so we will have to use our local .snap file.
The first step is to set the grade of the snap to “dangerous”. This is because your custom gadget snap will not have been signed by the global snap store or a brand store and its provenance can not be verified except by yourself.
"grade": "dangerous",
"snaps": [
{
"name": "pi_22-2_arm64",
"type": "gadget",
},
Next, remove the snap-id and default-channel values as these are related to downloading from a store. Finally, update the name to that of your snap filename.
Signing the Model Assertion
If you don’t have a signing key yet, run the following command to create one:
$ snapcraft create-key <key-name>
Next, we’ll sign the model assertion by running:
$ snap sign -k <key-name> model.json > landscape.model
Finally, we’ll build the custom Ubuntu Core image from the signed model using the ubuntu-image tool. If you do not have this already it can be installed using the snap install ubuntu-image command.
Ubuntu-image will take our signed model assertion, download all the required snaps and compile them all together into an image file. As we want to use a local snap, we will have to tell it where to find that snap file so we will need the –snap flag. In this case let’s assume the snap file is in the same directory as our signed model assertion.
ubuntu-image snap --snap pi_22-2_arm64.snap landscape.model
This will produce the image pi.img which is ready to be written to an sd card and inserted into our Raspberry Pi.
There are various tools for writing this image to an sd card, the quickest is probably to use the startup image creator that is included with most Ubuntu variants and can be found in your app drawer (if not, it is available from the snap store). Select your img file and your target sd card and click “Make Startup Disk”.
Booting up your device
Take your freshly written SD card with the image and put it into your Raspberry Pi. Turn the device on and after a short delay your device should appear fully registered with your Landscape Server.
Conclusion
By following this process we can quickly and easily create an Ubuntu Core device that only needs a power cable and a network cable plugged into it for it to automatically get itself into a state where it can be remotely managed and maintained. This functionality is essential if attempting to deploy a large fleet or installing devices in inaccessible areas.
Learn more
For more information on the power and capabilities of Ubuntu Core check out: Ubuntu Core.
For more information on the features and functionality of Landscape check out: Landscape | Ubuntu.
Are you interested in running Ubuntu Core with Landscape management on your devices and are working on a commercial project? Get in touch with our team today.
Further reading
Ubuntu Core as an immutable Linux Desktop base
Managing software in complex network environments: the Snap Store Proxy
Manage FIPS-enabled Linux machines at scale with Landscape 23.03