Deploy a custom rootfs to multiple Windows machines with Ubuntu Pro for WSL and the Landscape API

Ubuntu Pro

This page refers to features that require an Ubuntu Pro subscription and access to the Ubuntu Pro for WSL application. The application is currently in beta and not yet generally available.

This guide shows how to use the Landscape API to automate the deployment of a custom rootfs across multiple Windows machines. Scaled deployment is enabled by Ubuntu Pro for WSL, which ensures that Ubuntu WSL instances on Windows machines are automatically registered with Landscape. Cloud-init is used for initialisation and final configuration of the instances. To follow the steps outlined in this guide you can use either:

  • Bash scripting on Linux, or

  • PowerShell scripting on Windows

Prerequisites

  • A running self-hosted Landscape server version 24.10~beta.5 or later.

  • Multiple Windows machines already registered with Landscape via Ubuntu Pro for WSL.

  • Make sure you have installed curl and jq, if you’re following this guide using Bash.

  • Familiarity with Bash and/or PowerShell.

Note

You need PowerShell 7.0 or later to follow this guide. Some commands will fail on PowerShell 5. You can check your PowerShell version by running the command $PSVersionTable.

Prepare the environment

Export the following environment variables, modifying the values that are assigned as needed. They will be used in subsequent commands.

# Credentials to authenticate the API requests
export LANDSCAPE_USER_EMAIL=[email protected]
export LANDSCAPE_USER_PASSWORD=mib
export LANDSCAPE_URL=https://landscape.mib.com

# The URL of the custom rootfs to be deployed
export ROOTFS_URL="http://landscape.mib.com:9009/ubuntu-24.04-custom.tar.gz"

# The list of IDs of the different Windows machines on which we are going to deploy WSL instances
export PARENT_COMPUTER_IDS=(26 30 31)

# The name of the WSL instance to be created
export COMPUTER_NAME=Carbonizer

# Path to the cloud-config file whose contents will be used to initialize the WSL instances
export CLOUD_INIT_FILE="~/Downloads/init.yaml"

Computer IDs

The PARENT_COMPUTER_IDS environment variable contains a list of IDs internally assigned to Windows machines already registered to Landscape. The values used in this guide are examples, and you can get IDs for your machines in the Landscape dashboard or through the Landscape REST API.

Image server

In our example, a custom image ubuntu-24.04-custom.tar.gz is served from the same address as the Landscape server at port 9009. In practice, that URL could point to any address in an intranet or the internet that’s accessible from the client computers.

The image server can also provide an SHA256SUMS file, as done by cloud-images.ubuntu.com and cdimage.ubuntu.com. If that file is available, the Windows agent validates the images against the SHA256SUMS file before installation.

While the example uses the .tar.gz extension, the most recent .wsl format can also be used. Refer to the image customisation for Ubuntu on WSL guide for more information.

Generate a Base64-encoded string with the cloud-config data:

BASE64_ENCODED_CLOUD_INIT=$(cat $CLOUD_INIT_FILE | base64 --wrap=0)

Authenticate against the Landscape API

Build the authentication payload of the form: {"email": "admin@mib.com", "password": "mib"} using the values exported in prior steps:

LOGIN_JSON=$( jq -n \
    --arg em "$LANDSCAPE_USER_EMAIL" \
    --arg pwd "$LANDSCAPE_USER_PASSWORD" \
    '{email: $em, password: $pwd}' )

Issue an authenticate request and retrieve the JSON web token (JWT) to be used in the subsequent API requests.

LOGIN_RESPONSE=$( curl -s -X POST "$LANDSCAPE_URL/api/v2/login" \
    --data "$LOGIN_JSON"                                        \
    --header "Content-Type: application/json"                   \
    --header "Accept: application/json" )

JWT=$( echo $LOGIN_RESPONSE | jq .token | tr -d '"')

Send the Install request

Build the payload with information about the WSL instance to be deployed. In this case it would look like:

{"rootfs_url": "http://landscape.mib.com:9009/ubuntu-24.04-custom.tar.gz", "computer_name": "Carbonizer", "cloud_init": "<base64 encoded material>"}
WSL_JSON=$( jq -n                           \
    --arg rf "$ROOTFS_URL"                  \
    --arg cn "$COMPUTER_NAME"               \
    --arg b64 "$BASE64_ENCODED_CLOUD_INIT"  \
    '{rootfs_url: $rf, computer_name: $cn, cloud_init: $b64}' )

At the moment of this writing there is no specific API endpoint to trigger installation of WSL instances on multiple Windows machines at once. Instead we send one request per target machine.

for COMPUTER_ID in "${PARENT_COMPUTER_IDS[@]}"; do
    API_RESPONSE=$( curl -s -X POST                             \
        "$LANDSCAPE_URL/api/v2/computers/$COMPUTER_ID/children" \
        --data "$WSL_JSON"                                      \
        --header "Authorization:Bearer $JWT"                    \
        --header "Content-Type: application/json"               \
        --header "Accept: application/json" )

    # show the response
    echo $API_RESPONSE
    echo
done

When that completes, you’ll be able to find activities in the Landscape dashboard about the installation of a new WSL instance for each of the Windows machines listed.

Summarising the steps in a single script

The steps above can be made into a single script:

#!/usr/bin/env bash

# Base64-encoding the cloud-config file contents
BASE64_ENCODED_CLOUD_INIT=$(cat $CLOUD_INIT_FILE | base64 --wrap=0)

# Build the auth payload
LOGIN_JSON=$( jq -n                      \
    --arg em "$LANDSCAPE_USER_EMAIL"     \
    --arg pwd "$LANDSCAPE_USER_PASSWORD" \
    '{email: $em, password: $pwd}' )

# Issue an auth request and retrieve the JWT
LOGIN_RESPONSE=$( curl -s -X POST "$LANDSCAPE_URL/api/v2/login" \
    --data "$LOGIN_JSON"                                        \
    --header "Content-Type: application/json"                   \
    --header "Accept: application/json" )

JWT=$( echo $LOGIN_RESPONSE | jq .token | tr -d '"')

# Build the installation payload
WSL_JSON=$( jq -n                           \
    --arg rf "$ROOTFS_URL"                  \
    --arg cn "$COMPUTER_NAME"               \
    --arg b64 "$BASE64_ENCODED_CLOUD_INIT"  \
    '{rootfs_url: $rf, computer_name: $cn, cloud_init: $b64}' )

# Issue the command for each Windows machine
for COMPUTER_ID in "${PARENT_COMPUTER_IDS[@]}"; do
    API_RESPONSE=$( curl -s -X POST                             \
        "$LANDSCAPE_URL/api/v2/computers/$COMPUTER_ID/children" \
        --data "$WSL_JSON"                                      \
        --header "Authorization:Bearer $JWT"                    \
        --header "Content-Type: application/json"               \
        --header "Accept: application/json" )

    # show the response
    echo $API_RESPONSE
    echo
done

Further reading