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
andjq
, 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"
# Credentials to authenticate the API requests
$LANDSCAPE_USER_EMAIL="[email protected]"
$LANDSCAPE_USER_PASSWORD="mib"
$LANDSCAPE_URL="https://landscape.mib.com"
# The URL of the custom rootfs to be deployed
$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
$PARENT_COMPUTER_IDS=@(26, 30, 31)
# The name of the WSL instance to be created
$COMPUTER_NAME="Carbonizer"
# Path to the cloud-config file whose contents will be used to initialize the WSL instances
$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)
$content = Get-Content -Path $CLOUD_INIT_FILE -Raw
$bytes = [System.Text.Encoding]::UTF8.GetBytes($content)
$BASE64_ENCODED_CLOUD_INIT = [System.Convert]::ToBase64String($bytes)
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}' )
$LOGIN_JSON = @{
email = "$LANDSCAPE_USER_EMAIL"
password = "$LANDSCAPE_USER_PASSWORD"
} | ConvertTo-Json
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 '"')
$LOGIN_RESPONSE = Invoke-WebRequest -Method POST `
-URI "$LANDSCAPE_URL/api/v2/login" `
-Body "$LOGIN_JSON" -ContentType "application/json"
$JWT = ConvertTo-SecureString -AsPlainText -Force $( $LOGIN_RESPONSE.Content | ConvertFrom-Json).token
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}' )
$WSL_JSON = @{
rootfs_url = "$ROOTFS_URL"
computer_name = "$COMPUTER_NAME"
cloud_init = "$BASE64_ENCODED_CLOUD_INIT"
} | ConvertTo-Json
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
foreach ($COMPUTER_ID in $PARENT_COMPUTER_IDS) {
$API_RESPONSE = Invoke-WebRequest -Method POST -Body "$WSL_JSON" `
-Uri "$LANDSCAPE_URL/api/v2/computers/$COMPUTER_ID/children" `
-Authentication Bearer -Token $JWT -ContentType "application/json"
# show the response
Write-Output $API_RESPONSE
}
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
# Base64-encoding the cloud-config file contents
$content = Get-Content -Path $CLOUD_INIT_FILE -Raw
$bytes = [System.Text.Encoding]::UTF8.GetBytes($content)
$BASE64_ENCODED_CLOUD_INIT = [System.Convert]::ToBase64String($bytes)
# Build the auth payload
$LOGIN_JSON = @{
email = "$LANDSCAPE_USER_EMAIL"
password = "$LANDSCAPE_USER_PASSWORD"
} | ConvertTo-Json
# Issue an auth request and retrieve the JWT
$LOGIN_RESPONSE = Invoke-WebRequest -Method POST `
-URI "$LANDSCAPE_URL/api/v2/login" `
-Body "$LOGIN_JSON" -ContentType "application/json"
$JWT = ConvertTo-SecureString -AsPlainText -Force $( $LOGIN_RESPONSE.Content | ConvertFrom-Json).token
# Build the installation payload
$WSL_JSON = @{
rootfs_url = "$ROOTFS_URL"
computer_name = "$COMPUTER_NAME"
cloud_init = "$BASE64_ENCODED_CLOUD_INIT"
} | ConvertTo-Json
# Issue the command for each Windows machine
foreach ($COMPUTER_ID in $PARENT_COMPUTER_IDS) {
$API_RESPONSE = Invoke-WebRequest -Method POST -Body "$WSL_JSON" `
-Uri "$LANDSCAPE_URL/api/v2/computers/$COMPUTER_ID/children" `
-Authentication Bearer -Token $JWT -ContentType "application/json"
# show the response
Write-Output $API_RESPONSE
}
Further reading¶
Visit the Landscape API documentation to learn more about it.
Landscape documentation about WSL integration contains more information about this and other methods of creating WSL instances on Windows machines registered with Landscape via its REST API.