diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..201244d --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,26 @@ +# GitHub Actions Workflows + +This directory contains GitHub Actions workflows for building Linux images. + +## build-linux-images.yml + +This workflow builds images for three different Linux distributions: +- Alpine Linux +- Arch Linux +- Ubuntu 20.04 with kernel 5.15 + +### Schedule +- Runs every Monday at 2 AM UTC (weekly) +- Can also be triggered manually via GitHub UI + +### What it does +1. Checks out the repository +2. Sets up Packer and QEMU +3. Builds all three Linux images in sequence +4. Uploads the resulting qcow2 images as artifacts + +### Artifacts +Each build produces a qcow2 image that is retained for 7 days: +- `alpine-image` +- `archlinux-image` +- `ubuntu-image` \ No newline at end of file diff --git a/.github/workflows/build-linux-images.yml b/.github/workflows/build-linux-images.yml new file mode 100644 index 0000000..8c6444d --- /dev/null +++ b/.github/workflows/build-linux-images.yml @@ -0,0 +1,61 @@ +name: Build Linux Images + +on: + schedule: + # Run every Monday at 2 AM UTC (weekly) + - cron: '0 2 * * 1' + workflow_dispatch: + +jobs: + build-all-images: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Packer + uses: hashicorp/setup-packer@v3 + + - name: Setup QEMU + run: | + sudo apt-get update + sudo apt-get install -y qemu-system-x86 qemu-utils + + - name: Build Alpine Linux Image + run: | + cd example_build_alpine + packer init . + packer build -force alpine.pkr.hcl + + - name: Build Arch Linux Image + run: | + cd example_build_archlinux + packer init . + packer build -force archlinux.pkr.hcl + + - name: Build Ubuntu Image + run: | + cd example_build_ubuntu-20.04-kernel-5.15 + packer init . + packer build -force ubuntu-20.04-kernel-5.15.pkr.hcl + + - name: Upload Alpine Artifacts + uses: actions/upload-artifact@v4 + with: + name: alpine-image + path: example_build_alpine/output/*.qcow2 + retention-days: 7 + + - name: Upload Arch Linux Artifacts + uses: actions/upload-artifact@v4 + with: + name: archlinux-image + path: example_build_archlinux/output/*.qcow2 + retention-days: 7 + + - name: Upload Ubuntu Artifacts + uses: actions/upload-artifact@v4 + with: + name: ubuntu-image + path: example_build_ubuntu-20.04-kernel-5.15/output/*.qcow2 + retention-days: 7 \ No newline at end of file diff --git a/README.md b/README.md index 78f91dd..452e377 100644 --- a/README.md +++ b/README.md @@ -266,3 +266,8 @@ See the [`make_image_bootable.sh`](example_build/files/make_image_bootable.sh) e ## Related links * [Report bugs](https://github.com/ovh/bringyourownlinux/issues) + +## Automated Builds + +All example images are built automatically on a weekly basis using GitHub Actions. +See the [.github/workflows](.github/workflows) directory for details. diff --git a/example_build_alpine/README.md b/example_build_alpine/README.md new file mode 100644 index 0000000..bad0fc1 --- /dev/null +++ b/example_build_alpine/README.md @@ -0,0 +1,35 @@ +# Alpine Linux Build Example + +This directory contains a Packer configuration for building an Alpine Linux image suitable for OVHcloud baremetal servers. + +## Files + +- `alpine.pkr.hcl` - Main Packer configuration file in HCL format +- `provision.sh` - Script to prepare the Alpine Linux system for baremetal deployment +- `make_image_bootable.sh` - Script to make the image bootable on OVHcloud baremetal servers + +## Building the Image + +To build the image, run: + +```bash +packer build alpine.pkr.hcl +``` + +The resulting image will be located in the `output/` directory. + +## Configuration Details + +This build uses: +- Alpine Linux v3.20.1 (latest stable release) +- QEMU builder for virtualization +- Cloud-init for initial configuration +- GRUB bootloader configured for baremetal boot +- Support for RAID, LVM, and various filesystems +- Intel and AMD microcode support + +## Requirements + +- Packer 1.7+ +- QEMU/KVM support +- At least 4GB RAM for building \ No newline at end of file diff --git a/example_build_alpine/alpine.pkr.hcl b/example_build_alpine/alpine.pkr.hcl new file mode 100644 index 0000000..a948d2d --- /dev/null +++ b/example_build_alpine/alpine.pkr.hcl @@ -0,0 +1,65 @@ +packer { + required_plugins { + qemu = { + source = "github.com/hashicorp/qemu" + version = "~> 1" + } + } +} + +source "qemu" "builder" { + # cloud-init will use the CD as datasource, see https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html#source-2-drive-with-labeled-filesystem + cd_label = "cidata" + cd_content = { + "/meta-data" = "" + "/user-data" = <<-EOF + #cloud-config + ssh_pwauth: true + users: + - name: packer + plain_text_passwd: packer + sudo: ALL=(ALL) NOPASSWD:ALL + lock_passwd: false + EOF + } + # Enabling this makes the build longer but reduces the image size + disk_compression = true + # The source is a disk image, not an ISO file + disk_image = true + # Same size as the source image + disk_size = "2G" + format = "qcow2" + # Do not launch QEMU's GUI + headless = true + iso_checksum = "file:https://alpinelinux.org/downloads/alpine-v3.20.1-x86_64.iso.sha256" + iso_url = "https://alpinelinux.org/downloads/alpine-v3.20.1-x86_64.iso" + # Allows us to see the VM's console when PACKER_LOG=1 is set + qemuargs = [["-serial", "stdio"]] + # Before shutting down, truncate logs and remove everything linked to the provisioning user + shutdown_command = "sudo sh -c 'find /var/log/ -type f -exec truncate --size 0 {} + && rm -f /etc/sudoers.d/90-cloud-init-users && userdel -fr packer && poweroff'" + communicator = "ssh" + ssh_clear_authorized_keys = true + ssh_username = "packer" + ssh_password = "packer" + # The resulting image will be written to output/alpine.qcow2 + output_directory = "output" + vm_name = "alpine.qcow2" +} + +build { + sources = ["source.qemu.builder"] + + provisioner "shell" { + execute_command = "chmod +x {{ .Path }} && sudo {{ .Path }}" + script = "provision.sh" + } + + provisioner "file" { + destination = "/tmp/make_image_bootable.sh" + source = "make_image_bootable.sh" + } + + provisioner "shell" { + inline = ["sudo sh -c 'mkdir /root/.ovh/ && mv /tmp/make_image_bootable.sh /root/.ovh/ && chmod -R +x /root/.ovh/'"] + } +} \ No newline at end of file diff --git a/example_build_alpine/make_image_bootable.sh b/example_build_alpine/make_image_bootable.sh new file mode 100755 index 0000000..54f8ce0 --- /dev/null +++ b/example_build_alpine/make_image_bootable.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this +# file except in compliance with the License. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific language +# governing permissions and limitations under the License. + +# This script makes an Alpine Linux image bootable on OVHcloud baremetal servers + +set -eo pipefail + +configure_console() { + echo "Getting console parameters from the cmdline" + # Get the right console parameters (including SOL if available) from the + # rescue's cmdline - PUBM-16534 + console_parameters="$(grep -Po '\bconsole=\S+' /proc/cmdline | paste -s -d" ")" + if ! grep -qF "$console_parameters" /etc/default/grub; then + sed -i "s/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"$console_parameters\"/" /etc/default/grub + fi + + # Also pass these parameters to GRUB + parameters=$(sed -nE "s/.*\bconsole=ttyS([0-9]),([0-9]+)([noe])([0-9]+)\b.*/\1 \2 \3 \4/p" /proc/cmdline) + if [ -z "$parameters" ]; then + # No SOL, nothing to do + return + fi + read -r unit speed parity bits <<< "$parameters" + declare -A parities=([o]=odd [e]=even [n]=no) + parity="${parities["$parity"]}" + serial_command="serial --unit=$unit --speed=$speed --parity=$parity --word=$bits" + + if grep -qFx 'GRUB_TERMINAL="console serial"' /etc/default/grub; then + # Configuration already applied + return + fi + + sed -i \ + -e "/^# Uncomment to disable graphical terminal/d" \ + -e "s/^#GRUB_TERMINAL=.*/GRUB_TERMINAL=\"console serial\"\nGRUB_SERIAL_COMMAND=\"$serial_command\"/" \ + /etc/default/grub +} + +# Detect boot mode and install GRUB +if [ -d /sys/firmware/efi ]; then + echo "INFO - GRUB will be configured for UEFI boot" + # Pass --no-nvram to avoid changing the boot order, the server needs to boot via PXE + grub-install --target=x86_64-efi --no-nvram +else + echo "INFO - GRUB will be configured for legacy boot" + bootDevice="$(findmnt -A -c -e -l -n -T /boot/ -o SOURCE)" + realBootDevices="$(lsblk -n -p -b -l -o TYPE,NAME $bootDevice -s | awk '$1 == "disk" && !seen[$2]++ {print $2}')" + # realBootDevices are disks at this point + for realBootDevice in $realBootDevices; do + echo "INFO - GRUB will be configured on disk ${realBootDevice}" + # realBootDevice is the boot disk device canonical path + grub-install --target=i386-pc --force "$realBootDevice" + done +fi + +# Configure SOL console +configure_console + +# Generate unique machine-id +systemd-machine-id-setup + +# Generate GRUB config +grub-mkconfig -o /boot/grub/grub.cfg + +# Regenerate initramfs to include e.g. mdadm.conf and required kernel modules +mkinitfs -b + +# Cleanup +rm -fr /root/.ovh/ \ No newline at end of file diff --git a/example_build_alpine/provision.sh b/example_build_alpine/provision.sh new file mode 100755 index 0000000..850c257 --- /dev/null +++ b/example_build_alpine/provision.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# This script prepares an Alpine Linux cloud image for OVHcloud baremetal servers + +set -euo pipefail + +# Update the system +apk update && apk upgrade + +# Install useful packages for baremetal (RAID/LVM/filesystems/microcodes) +# We need to install packages that support hardware components commonly found in OVH baremetal servers +apk add --no-cache \ + mdadm \ + lvm2 \ + btrfs-progs \ + dosfstools \ + xfsprogs \ + linux-firmware-intel \ + intel-ucode \ + amd-ucode \ + openrc + +# Remove Network configuration +rm -f /etc/network/interfaces + +# Configure GRUB for proper boot on baremetal +# Alpine uses OpenRC instead of systemd, so we need to adjust accordingly +# Set default GRUB parameters +echo 'GRUB_CMDLINE_LINUX_DEFAULT=""' > /etc/default/grub +echo 'GRUB_CMDLINE_LINUX="nomodeset"' >> /etc/default/grub +echo 'GRUB_GFXPAYLOAD_LINUX="text"' >> /etc/default/grub + +# Add mdadm, LVM and net hooks to the initramfs +# In Alpine, we need to ensure the right modules are included +echo 'MODULES="mdraid lvm2"' > /etc/mkinitfs/modules +echo 'HOOKS="mount rootfs"' > /etc/mkinitfs/hooks + +# Remove machine-id, it is regenerated during OS installation +rm -f /etc/machine-id + +# Clean package cache +apk cache clean \ No newline at end of file