Skip to content

Instantly share code, notes, and snippets.

@TheRealFalcon
Last active September 3, 2025 21:18
Show Gist options
  • Select an option

  • Save TheRealFalcon/ab32edb7189b29d6ae08b2f206cc1735 to your computer and use it in GitHub Desktop.

Select an option

Save TheRealFalcon/ab32edb7189b29d6ae08b2f206cc1735 to your computer and use it in GitHub Desktop.
# This script is a cloud-init userdata script that may be used to create a
# MAAS instance within an LXD VM, set up a custom MAAS image server with
# the cloud-init daily PPA installed, and launch an LXD VM from within
# the MAAS instance using the updated image.
# This script does not run quickly. Expect to wait 15+ minutes, even on
# reasonably fast hardware. The image build process is not fast at all.
# It requires several rounds of installing packages to set up dependencies.
# You can watch the progress with:
# $ lxc exec maas -- tail -f /var/log/cloud-init-output.log
# To run this, first create a profile with expanded usage limits. I.e.,
### $ lxc profile show maas
### name: maas
### description: ""
### config:
### limits.cpu: "4"
### limits.memory: 16GiB # 8G is probably enough here
### devices:
### root:
### path: /
### pool: default
### size: 50GiB
### type: disk
### used_by:
### - /1.0/instances/maas
### project: default
# Then launch:
# lxc launch ubuntu-daily:plucky maas --config=cloud-init.user-data="$(cat ~/maas.sh)" -p default -p maas --vm
# The contents of `~/maas.sh` is what follows. Much of this script was
# borrowed from the script referenced at
# https://canonical.com/maas/tutorials/build-a-maas-and-lxd-environment-in-30-minutes-with-multipass-on-ubuntu#2-1-setting-up-multipass-and-launching-the-vm
#!/bin/bash
set -xe
cat > /tmp/lxd.cfg <<EOF
config:
core.https_address: '[::]:8443'
core.trust_password: password
networks:
- config:
ipv4.address: 10.10.10.1/24
ipv6.address: none
description: ""
name: lxdbr0
type: ""
project: default
storage_pools:
- config:
size: 24GB
description: ""
name: default
driver: btrfs # only doing this to workaround a stupid error about zpool not being found even when installed
profiles:
- config: {}
description: ""
devices:
eth0:
name: eth0
network: lxdbr0
type: nic
root:
path: /
pool: default
type: disk
name: default
projects: []
cluster: null
EOF
until apt install -y jq; do
echo "Retrying apt install -y jq..."
sleep 1
done
# Setup custom image url
git clone --single-branch --branch master --depth 1 https://git.launchpad.net/maas-images maas-images
cd maas-images
./system-setup
./tools/build-stream --arch amd64 --ppa ppa:cloud-init-dev/daily jammy
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
sudo rm -rf /var/www/html/images
sudo mv build/images /var/www/html/
sudo tee /etc/nginx/sites-available/default <<EOF
server {
listen 80;
server_name maas-images;
location / {
root /var/www/html/images;
autoindex on;
try_files \$uri \$uri/ =404;
}
}
EOF
sudo systemctl restart nginx
# Get all the snap things
snap wait system seed.loaded
# snap will throw errors early in boot. If you didn't spend forever
# setting up the image server, this sleep is probably necessary
# sleep 10
snap install maas --channel=3.6/stable
snap install maas-test-db --channel=3.6/stable
# Note that later versions of LXD will complain about the use of
# `core.trust_password` that we set above
snap install lxd --channel=5.0/stable
# Initialize LXD
cat /tmp/lxd.cfg | lxd init --preseed
lxd waitready
# Get uplink interface/IP
export IP_ADDRESS
IP_ADDRESS=$(ip -j route show default | jq -r '.[].prefsrc')
export INTERFACE
INTERFACE=$(ip -j route show default | jq -r '.[].dev')
# Initialise MAAS
maas init region+rack --database-uri maas-test-db:/// --maas-url "http://${IP_ADDRESS}:5240/MAAS"
sleep 15 # honestly no idea...
# Create MAAS admin and grab API key
maas createadmin --username admin --password admin --email admin
export APIKEY
APIKEY=$(maas apikey --username admin)
# MAAS admin login
maas login admin 'http://localhost:5240/MAAS/' "$APIKEY"
# Configure MAAS networking (gateway, DHCP, VLANs, DNS)
export SUBNET=10.10.10.0/24
export FABRIC_ID
FABRIC_ID=$(maas admin subnet read "$SUBNET" | jq -r ".vlan.fabric_id")
export VLAN_TAG
VLAN_TAG=$(maas admin subnet read "$SUBNET" | jq -r ".vlan.vid")
export PRIMARY_RACK
PRIMARY_RACK=$(maas admin rack-controllers read | jq -r ".[] | .system_id")
maas admin subnet update "$SUBNET" gateway_ip=10.10.10.1
maas admin ipranges create type=dynamic start_ip=10.10.10.200 end_ip=10.10.10.254
maas admin vlan update "$FABRIC_ID" "$VLAN_TAG" dhcp_on=True primary_rack="$PRIMARY_RACK"
maas admin maas set-config name=upstream_dns value=8.8.8.8
# We could get fancy and set up IP forwarding such that we can ssh directly
# from our host to the LXD VMs that can be created by MAAS, but I don't
# care enough currently.
# See https://raw.githubusercontent.com/canonical/maas-multipass/main/maas.yml
# Automatically create and add ssh keys to MAAS
# This isn't necessary if we're just going to run lxc exec commands
# rather than trying to ssh into our containers
ssh-keygen -q -t rsa -N "" -f "/root/.ssh/id_rsa"
chown ubuntu:ubuntu /root/.ssh/id_rsa /root/.ssh/id_rsa.pub
chmod 600 /root/.ssh/id_rsa
chmod 644 /root/.ssh/id_rsa.pub
maas admin sshkeys create key="$(cat /root/.ssh/id_rsa.pub)"
# Use custom image stream
maas admin boot-source delete 1
wget http://${IP_ADDRESS}/gpg.key -O /var/snap/maas/current/custom-stream-gpg.key
maas admin boot-sources create url="http://${IP_ADDRESS}/" keyring_filename="/var/snap/maas/current/custom-stream-gpg.key"
maas admin boot-source-selections create 2 os="ubuntu" release="jammy" arches="amd64" subarches="*" labels="*"
maas admin boot-resources import
while true; do
STATUS=$(maas admin boot-resources is-importing)
[ "$STATUS" = "false" ] && break
sleep 5
done
# Create LXD host and machine
export HOST_ID
HOST_ID=$(maas admin vm-hosts create name="lxd_host" password=password type=lxd power_address="https://${IP_ADDRESS}:8443" project=default | jq -r ".id")
export MACHINE_ID
MACHINE_ID=$(maas admin vm-host compose "$HOST_ID" cores=1 memory=2048 storage=8 | jq -r ".system_id")
while true; do
STATUS=$(maas admin machine read "$MACHINE_ID" | jq -r .status_name)
[ "$STATUS" = "Ready" ] && break
sleep 5
done
maas admin machine deploy "$MACHINE_ID"
while true; do
STATUS=$(maas admin machine read "$MACHINE_ID" | jq -r .status_name)
[ "$STATUS" = "Deployed" ] && break
sleep 5
done
# At this point we can use `maas admin machine read "$MACHINE_ID"` to get an ip address
# or the hostname will be the same name as the lxd VM that can be `lxc exec`ed.
# From the host, we can do something like:
# $ export VM_NAME=$(lxc exec maas -- lxc list --format json | jq -r ".[0].name")
# $ lxc exec maas -- lxc exec $VM_NAME -- cloud-init --version
# /usr/bin/cloud-init 100.0.0.daily-202508222033-9648ad628~ubuntu22.04.1
# Additional deployment logs:
# On MAAS machine:
# maas admin machine get-curtin-config <id>
# On deployed machine:
# /root/curtin-install-cfg.yaml
# /root/curtin-install.log
# Also check:
# journalctl SYSLOG_IDENTIFIER=maas-regiond
# journalctl SYSLOG_IDENTIFIER=maas-rackd
# or more generally:
# journactl -u maas
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment