Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud-Init support #797

Draft
wants to merge 5 commits into
base: arm64
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ The following environment variables are supported:

If set, use this directory path as the location of scripts to run when generating images. An absolute or relative path can be given for a location outside the pi-gen directory.

* `ENABLE_CLOUD_INIT` (Default: `1`)

If set to `1`, cloud-init and netplan will be installed and configured. This will allow you to configure your Raspberry Pi using cloud-init configuration files. The cloud-init configuration files should be placed in the bootfs or by editing the files in `stage2/04-cloud-init/files`. Cloud-init will be configured to read them on first boot.

A simple example for building Raspberry Pi OS:

```bash
Expand Down
2 changes: 2 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ export QUILT_NO_DIFF_INDEX=1
export QUILT_NO_DIFF_TIMESTAMPS=1
export QUILT_REFRESH_ARGS="-p ab"

export ENABLE_CLOUD_INIT=${ENABLE_CLOUD_INIT:-1}

# shellcheck source=scripts/common
source "${SCRIPT_DIR}/common"
# shellcheck source=scripts/dependencies_check
Expand Down
12 changes: 12 additions & 0 deletions export-image/01-user-rename/01-run.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
#!/bin/bash -e

if [[ "${DISABLE_FIRST_BOOT_USER_RENAME}" == "0" ]]; then
# with cloud-init enabled this will throw an error
# when run more than once, as the service will be deleted
on_chroot <<- EOF
SUDO_USER="${FIRST_USER_NAME}" rename-user -f -s
EOF

# delete userconfig service as cloud-init will take care of launching it
rm -f "${ROOTFS_DIR}/lib/systemd/system/userconfig.service"
else
rm -f "${ROOTFS_DIR}/etc/xdg/autostart/piwiz.desktop"

# if cloud-init enabled disable setup wizard launch completely
if [[ "${ENABLE_CLOUD_INIT}" == "1" ]]; then
on_chroot <<- EOF
touch /var/lib/userconf-pi/deactivate
EOF
fi
fi
9 changes: 4 additions & 5 deletions stage2/01-sys-tweaks/01-run.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/bin/bash -e

install -m 755 files/resize2fs_once "${ROOTFS_DIR}/etc/init.d/"
if [ "${ENABLE_CLOUD_INIT}" != "1" ]; then
# if cloud-init is enabled, it will take care of resizing the rootfs
install -m 755 files/resize2fs_once "${ROOTFS_DIR}/etc/init.d/"
fi

install -m 644 files/50raspi "${ROOTFS_DIR}/etc/apt/apt.conf.d/"

Expand Down Expand Up @@ -37,10 +40,6 @@ if [ "${USE_QEMU}" = "1" ]; then
systemctl disable resize2fs_once
EOF
echo "leaving QEMU mode"
else
on_chroot << EOF
systemctl enable resize2fs_once
EOF
fi

on_chroot <<EOF
Expand Down
3 changes: 3 additions & 0 deletions stage2/04-cloud-init/00-packages
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
python3-yaml
netcat-openbsd
netplan.io
34 changes: 34 additions & 0 deletions stage2/04-cloud-init/01-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash -e

if [ "${ENABLE_CLOUD_INIT}" != "1" ]; then
log "Skipping cloud-init stage"
exit 0
fi

install -v -D -m 644 -t "${ROOTFS_DIR}/etc/cloud/cloud.cfg.d/" files/99_raspberry-pi.cfg

# install meta-data file for NoCloud data-source to work
install -v -m 755 files/meta-data "${ROOTFS_DIR}/boot/firmware/meta-data"
install -v -m 755 files/user-data "${ROOTFS_DIR}/boot/firmware/user-data"
install -v -m 755 files/network-config "${ROOTFS_DIR}/boot/firmware/network-config"

# setup default netplan config which will instruct netplan to pass control over to network-manager
# at boot time. This will make NetworkManager manage all devices and by default.
# Any Ethernet device will come up with DHCP, once carrier is detected
install -v -D -m 600 -t "${ROOTFS_DIR}/usr/lib/netplan/" files/00-network-manager-all.yaml

# still does not solve the conflict, maybe some kind of race cond.
# make sure config stage is run before userconfig service
#sed -i '/^\[Unit\]/a Before=userconfig.service' "${ROOTFS_DIR}/lib/systemd/system/cloud-config.service"

install -v -m 755 files/cloud-init-custom.deb "${ROOTFS_DIR}/tmp/cloud-init.deb"

# remove cloud-init if already installed for rebuild support while working with custom deb
on_chroot << EOF
SUDO_USER="${FIRST_USER_NAME}" dpkg -i /tmp/cloud-init.deb || true
Copy link
Member

@XECDesign XECDesign Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't be relevant when we update the package in our repo, so this is just a general heads up. Instead of using dpkg -i and following it up with apt, you could use apt-get install -y /tmp/cloud-init.deb directly and that will resolve dependencies and install the relevant packages in one step. The trick is that apt-get assumes that you're giving it package names unless it detects that you're giving it a path. I'm guessing it's checking for the presence of a / character, so if the file is in the current directory you'd use ./file.deb.

You could probably even just put '/tmp/cloud-init.deb' in 02-packages, but I haven't tried that, so I'm not sure.

As is, using || true means you ignore potential legitimate errors. Also, we normally nudge people towards apt rather than apt-get because it's more user friendly and handles some things automatically which apt-get doesn't, but in non-interactive scripts such as this one, apt-get is still the way to go. In other words, apt-get for scripting and apt otherwise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks for explaining. I'll update this for the current state.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried it and when using apt-get install -y /tmp/cloud-init.deb I got a message Note: using cloud-init instead of /tmp/cloud-init.deb

Copy link
Member

@XECDesign XECDesign Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing the package was updated without bumping the version number? We normally add +rpt1 to the version string to signify that it contains our patches.

SUDO_USER="${FIRST_USER_NAME}" apt-get install -f -y
EOF

rm -f "${ROOTFS_DIR}/tmp/cloud-init.deb"

# userconfig service is deleted in export-image/01-user-rename stage
19 changes: 19 additions & 0 deletions stage2/04-cloud-init/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Cloud-init support for Raspberry Pi OS

TODO: add reference to official documentation for the custom modules when merged

- files/network-config is required because otherwise imager would fail to create the correct filesystem entry

- files/user-data same reason and to include some example configurations

- files/meta-data Cloud-init instance configuration

- files/cloud-init-custom.deb A custom cloud-init build until included apt repositories

- files/99_raspberry-pi.cfg Cloud-init datasource configuration

- files/00-network-manager-all.yaml Example form netplan docs/ubuntu for handing over control from
netplan to NetworkManager by default.

Packages:
- netplan is installed to provide for advanced options like "wifis" in the network-config v2
3 changes: 3 additions & 0 deletions stage2/04-cloud-init/files/00-network-manager-all.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
network:
version: 2
renderer: NetworkManager
6 changes: 6 additions & 0 deletions stage2/04-cloud-init/files/99_raspberry-pi.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# configure cloud-init with NoCloud

datasource_list: [ NoCloud, None ]
datasource:
NoCloud:
fs_label: bootfs
Binary file added stage2/04-cloud-init/files/cloud-init-custom.deb
Binary file not shown.
18 changes: 18 additions & 0 deletions stage2/04-cloud-init/files/meta-data
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This is the meta-data configuration file for cloud-init. Please refer to the
# cloud-init documentation for more information:
#
# https://cloudinit.readthedocs.io/

# Set the datasource mode to "local". This ensures that user-data is acted upon
# prior to bringing up the network (because everything about the datasource is
# assumed to be local). If you wish to use an HTTP datasource instead, you can
# change this to "net" or override it on the kernel cmdline (see README).
dsmode: local

# Specifies the "unique" identifier of the instance. Typically in cloud-init
# this is generated by the owning cloud and is actually unique (to some
# degree). Here our data-source is local, so this is just a fixed string.
# Warning: changing this will cause cloud-init to assume it is running on a
# "new" instance, and to go through first time setup again (the value is
# compared to a cached copy).
instance_id: rpios-image
50 changes: 50 additions & 0 deletions stage2/04-cloud-init/files/network-config
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This file contains a netplan-compatible configuration which cloud-init will
# apply on first-boot (note: it will *not* update the config after the first
# boot). Please refer to the cloud-init documentation and the netplan reference
# for full details:
#
# https://netplan.io/reference
# https://cloudinit.readthedocs.io/en/latest/topics/network-config.html
# https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html
#
# Please note that the YAML format employed by this file is sensitive to
# differences in whitespace; if you are editing this file in an editor (like
# Notepad) which uses literal tabs, take care to only use spaces for
# indentation. See the following link for more details:
#
# https://en.wikipedia.org/wiki/YAML
#
# Additionally, please be aware that if your boot sequence depends on active
# networking (e.g. if your cloud-init configuration pulls packages or SSH
# keys from the network), you *must* mark at least one interface as required
# ("optional: false") below. Otherwise, particularly on faster boards,
# cloud-init will start attempting to use the network before it is ready

# Some additional examples are commented out below

network:
version: 2

ethernets:
eth0:
dhcp4: true
optional: true

# wifis:
# wlan0:
# dhcp4: true
# optional: true
# access-points:
# myhomewifi:
# password: "S3kr1t"
# myworkwifi:
# password: "correct battery horse staple"
# workssid:
# auth:
# key-management: eap
# method: peap
# identity: "[email protected]"
# password: "passw0rd"
# ca-certificate: /etc/my_ca.pem

# regulatory-domain: GB
109 changes: 109 additions & 0 deletions stage2/04-cloud-init/files/user-data
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#cloud-config

# This is the user-data configuration file for cloud-init. By default this sets
# up an initial user called "ubuntu" with password "ubuntu", which must be
# changed at first login. However, many additional actions can be initiated on
# first boot from this file. The cloud-init documentation has more details:
#
# https://cloudinit.readthedocs.io/
#
# Please note that the YAML format employed by this file is sensitive to
# differences in whitespace; if you are editing this file in an editor (like
# Notepad) which uses literal tabs, take care to only use spaces for
# indentation. See the following link for more details:
#
# https://en.wikipedia.org/wiki/YAML
#
# Some additional examples are provided in comments below the default
# configuration.

# disable_piwiz: fase

# Setup default user
# rpi_userconf:
# password: my-hashed-passwd
# user: myusername

## Set the system's hostname. Please note that, unless you have a local DNS
## setup where the hostname is derived from DHCP requests (as with dnsmasq),
## setting the hostname here will not make the machine reachable by this name.
## You may also wish to install avahi-daemon (see the "packages:" key below)
## to make your machine reachable by the .local domain
#hostname: raspberrypi

## Set up the keyboard layout. See localectl(1), in particular the various
## list-x11-* sub-commands, to determine the available models, layouts,
## variants, and options
#keyboard:
# model: pc105
# layout: gb
# variant:
# options: ctrl:nocaps

# Controls password authentication with the SSH daemon; the default here
# prevents logging into SSH with a password. Changing this is a security risk
# and you should at the very least ensure a different default password is
# specified above
ssh_pwauth: false

## On first boot, use ssh-import-id to give the specific users SSH access to
## the default user
#ssh_import_id:
#- lp:my_launchpad_username
#- gh:my_github_username

## Add users and groups to the system, and import keys with the ssh-import-id
## utility
#groups:
#- robot: [robot]
#- robotics: [robot]
#- pi
#
#users:
#- default
#- name: robot
# gecos: Mr. Robot
# primary_group: robot
# groups: users
# ssh_import_id: foobar
# lock_passwd: false
# passwd: $5$hkui88$nvZgIle31cNpryjRfO9uArF7DYiBcWEnjqq7L1AQNN3

## Update apt database and upgrade packages on first boot
#package_update: true
#package_upgrade: true

## Install additional packages on first boot
#packages:
#- avahi-daemon
#- rng-tools
#- python3-gpiozero
#- [python3-serial, 3.5-1]

## Write arbitrary files to the file-system (including binaries!)
#write_files:
#- path: /etc/default/console-setup
# content: |
# # Consult the console-setup(5) manual page.
# ACTIVE_CONSOLES="/dev/tty[1-6]"
# CHARMAP="UTF-8"
# VIDEOMODE=
# FONT="Lat15-Terminus18x10.psf.gz"
# FONTFACE=
# FONTSIZE=
# CODESET="Lat15"
# permissions: '0644'
# owner: root:root
#- encoding: gzip
# path: /root/Makefile
# content: !!binary |
# H4sICF2DTWIAA01ha2VmaWxlAFNWCM8syVBILMjPyU/PTC1WKMlXiPB2dlFQNjSx5MpNteLi
# dLDiSoRQxYl5KeWZyRkgXrSCkoqKRmaKgm6pppKCbmqhgoFCrIKamkK1QmpyRr6Ckn92YqWS
# NdC80uQMBZhOa4VahZoaqIrwjMQSewXfxOxUhcwShcr80qLi1Jw0RSUuAIYfEJmVAAAA
# owner: root:root
# permissions: '0644'

## Run arbitrary commands at rc.local like time
#runcmd:
#- [ ls, -l, / ]
#- [ sh, -xc, "echo $(date) ': hello world!'" ]