How-to deploy a custom rootfs across multiple Windows machines with the Landscape API

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.

Prepare the environment

For convenience when writing subsequent commands, first export the following environment variables, modifying the values that are assigned as needed:

# 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"

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