├── .gitignore ├── packer ├── plugins.pkr.hcl ├── build.pkr.hcl └── sources.pkr.hcl ├── Vagrantfile ├── LICENSE ├── .github └── workflows │ ├── release.yaml │ └── build.yaml ├── README.md └── scripts └── install-cloud-init.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | output-arm-image 3 | packer_cache 4 | dist 5 | *.log -------------------------------------------------------------------------------- /packer/plugins.pkr.hcl: -------------------------------------------------------------------------------- 1 | packer { 2 | required_plugins { 3 | arm-image = { 4 | version = ">= 0.2.5" 5 | source = "github.com/solo-io/arm-image" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /packer/build.pkr.hcl: -------------------------------------------------------------------------------- 1 | 2 | build { 3 | sources = [ 4 | "source.arm-image.raspios_bullseye_armhf", 5 | "source.arm-image.raspios_bullseye_arm64" 6 | ] 7 | 8 | provisioner "shell" { 9 | scripts = [ 10 | "scripts/install-cloud-init.sh" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | $script = <<-SCRIPT 2 | curl -sL get.hashi-up.dev | sudo sh 3 | sudo hashi-up packer get -d /usr/local/bin 4 | SCRIPT 5 | 6 | Vagrant.configure("2") do |config| 7 | 8 | config.vm.box = "ubuntu/focal64" 9 | 10 | config.vm.boot_timeout = 600 11 | 12 | config.vm.provider "virtualbox" do |vb| 13 | vb.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ] 14 | end 15 | 16 | config.vm.provision "shell", inline: $script 17 | 18 | end -------------------------------------------------------------------------------- /packer/sources.pkr.hcl: -------------------------------------------------------------------------------- 1 | source "arm-image" "raspios_bullseye_armhf" { 2 | image_type = "raspberrypi" 3 | iso_url = "http://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2023-02-22/2023-02-21-raspios-bullseye-armhf-lite.img.xz" 4 | iso_checksum = "sha256:c4582dd776e24066cf008f4ff389c38769a052c46ec63317635694dd767b86c9" 5 | output_filename = "images/rpi-cloud-init-raspios-bullseye-armhf.img" 6 | } 7 | 8 | source "arm-image" "raspios_bullseye_arm64" { 9 | image_type = "raspberrypi" 10 | iso_url = "http://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-02-22/2023-02-21-raspios-bullseye-arm64-lite.img.xz" 11 | iso_checksum = "sha256:883eb0006c8841b7950ef69a7bf55f73c2250ecc15e6bf507f39f0d82fa2ea0a" 12 | output_filename = "images/rpi-cloud-init-raspios-bullseye-arm64.img" 13 | qemu_binary = "qemu-aarch64-static" 14 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Johan Siebens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: [ created ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "⬇ Checkout" 12 | uses: actions/checkout@v2.3.4 13 | with: 14 | fetch-depth: 1 15 | 16 | - name: "⬇ Install Packer" 17 | uses: hashicorp-contrib/setup-packer@v1 18 | 19 | - name: "🏗 Run Packer" 20 | run: | 21 | mkdir images 22 | sudo packer init packer/ 23 | sudo packer build -parallel-builds=1 packer/ 24 | 25 | - name: "📦 Package the bullseye armhf image" 26 | uses: OctoPrint/actions/package-rpi-image@main 27 | with: 28 | image_path: "images/rpi-cloud-init-raspios-bullseye-armhf.img" 29 | 30 | - name: "📦 Package the bullseye arm64 image" 31 | uses: OctoPrint/actions/package-rpi-image@main 32 | with: 33 | image_path: "images/rpi-cloud-init-raspios-bullseye-arm64.img" 34 | 35 | - name: "🔖 Attach assets" 36 | uses: skx/github-action-publish-binaries@master 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | args: "./images/*.zip*" -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "⬇ Checkout" 12 | uses: actions/checkout@v2.3.4 13 | with: 14 | fetch-depth: 1 15 | 16 | - name: "⬇ Install Packer" 17 | uses: hashicorp-contrib/setup-packer@v1 18 | 19 | - name: "🏗 Run Packer" 20 | run: | 21 | mkdir images 22 | sudo packer init packer/ 23 | sudo packer build -parallel-builds=1 packer/ 24 | 25 | - name: "📦 Package the bullseye armhf image" 26 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' 27 | uses: OctoPrint/actions/package-rpi-image@main 28 | with: 29 | image_path: "images/rpi-cloud-init-raspios-bullseye-armhf.img" 30 | 31 | - name: "📦 Package the bullseye arm64 image" 32 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' 33 | uses: OctoPrint/actions/package-rpi-image@main 34 | with: 35 | image_path: "images/rpi-cloud-init-raspios-bullseye-arm64.img" 36 | 37 | - name: "🔖 Create release & attach assets" 38 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' 39 | uses: marvinpinto/action-automatic-releases@latest 40 | with: 41 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 42 | automatic_release_tag: "latest" 43 | prerelease: true 44 | title: "Development Build" 45 | files: | 46 | images/*.zip* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloud-init on a Raspberry Pi 2 | 3 | ![main](https://github.com/jsiebens/rpi-cloud-init/actions/workflows/build.yaml/badge.svg?branch=main) 4 | 5 | This repository contains Packer templates and scripts to build a Raspbian / Raspberry Pi OS image with [cloud-init](https://cloud-init.io/) pre-installed. 6 | 7 | > cloud-init: Cloud images are operating system templates and every instance starts out as an identical clone of every other instance. It is the user data that gives every cloud instance its personality and cloud-init is the tool that applies user data to your instances automatically. 8 | 9 | This setup includes the following image: 10 | 11 | - __rpi-cloud-init.img__: a image with cloud-init pre-installed. 12 | 13 | ## How to use this image 14 | 15 | 1. Download the image of the latest [release](https://github.com/jsiebens/rpi-cloud-init/releases) or build the image. 16 | 17 | 2. Flash the image to an SD card. 18 | 19 | 3. Customize the /boot/user-data with e.g. hostname, authorized ssh keys, ... 20 | 21 | 4. Insert the SD card into the Raspberry Pi and power it up. 22 | 23 | ## Building the images 24 | 25 | This project includes a Vagrant file and some scripts to build the images in an isolated environment. 26 | 27 | To use the Vagrant environment, start by cloning this repository: 28 | 29 | ``` 30 | git clone https://github.com/jsiebens/rpi-cloud-init 31 | cd rpi-cloud-init 32 | ``` 33 | 34 | Next, start the Vagrant box and ssh into it: 35 | 36 | ``` 37 | vagrant up 38 | vagrant ssh 39 | ``` 40 | 41 | When connected with the Vagrant box, run `build.sh` in the `/vagrant` directory: 42 | 43 | ``` 44 | cd /vagrant 45 | ./build.sh 46 | ``` -------------------------------------------------------------------------------- /scripts/install-cloud-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | apt-get install cloud-init -y 5 | 6 | touch /boot/meta-data 7 | touch /boot/user-data 8 | 9 | cat - > /etc/cloud/templates/sources.list.debian.tmpl <<'EOF' 10 | ## template:jinja 11 | ## Note, this file is written by cloud-init on first boot of an instance 12 | ## modifications made here will not survive a re-bundle. 13 | ## if you wish to make changes you can: 14 | ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg 15 | ## or do the same in user-data 16 | ## b.) add sources in /etc/apt/sources.list.d 17 | ## c.) make changes to template file /etc/cloud/templates/sources.list.debian.tmpl 18 | ### 19 | 20 | deb {{mirror}} {{codename}} main contrib non-free rpi 21 | deb-src {{mirror}} {{codename}} main contrib non-free rpi 22 | EOF 23 | 24 | cat -> /etc/cloud/cloud.cfg.d/99_fake_cloud.cfg <<'EOF' 25 | # configure cloud-init for NoCloud 26 | datasource_list: [ NoCloud, None ] 27 | datasource: 28 | NoCloud: 29 | fs_label: bootfs 30 | EOF 31 | 32 | cat - > /etc/cloud/cloud.cfg.d/99_raspbian.cfg <<'EOF' 33 | system_info: 34 | default_user: 35 | name: pi 36 | lock_passwd: false 37 | gecos: Raspbian 38 | groups: [pi adm dialout cdrom sudo audio video plugdev games users input netdev spi i2c gpio] 39 | sudo: ["ALL=(ALL) NOPASSWD: ALL"] 40 | shell: /bin/bash 41 | package_mirrors: 42 | - arches: [default] 43 | failsafe: 44 | primary: http://raspbian.raspberrypi.org/raspbian 45 | security: http://raspbian.raspberrypi.org/raspbian 46 | EOF 47 | 48 | # Disable dhcpcd - it has a conflict with cloud-init network config 49 | systemctl mask dhcpcd --------------------------------------------------------------------------------