@underpostnet/underpost 2.96.0 → 2.96.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  <!-- badges -->
20
20
 
21
- [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.96.0)](https://socket.dev/npm/package/underpost/overview/2.96.0) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
21
+ [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.96.1)](https://socket.dev/npm/package/underpost/overview/2.96.1) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
22
22
 
23
23
  <!-- end-badges -->
24
24
 
@@ -66,7 +66,7 @@ Run dev client server
66
66
  npm run dev
67
67
  ```
68
68
  <!-- -->
69
- ## underpost ci/cd cli v2.96.0
69
+ ## underpost ci/cd cli v2.96.1
70
70
 
71
71
  ### Usage: `underpost [options] [command]`
72
72
  ```
@@ -9,5 +9,16 @@
9
9
  "filetype": "tgz",
10
10
  "content": "rocky9.tar.gz"
11
11
  }
12
+ },
13
+ "Rocky9Arm64": {
14
+ "dir": "packer/images/Rocky9Arm64",
15
+ "maas": {
16
+ "name": "custom/rocky9-arm64",
17
+ "title": "Rocky 9 Arm64 Custom",
18
+ "architecture": "arm64/generic",
19
+ "base_image": "rhel/9",
20
+ "filetype": "tgz",
21
+ "content": "rocky9.tar.gz"
22
+ }
12
23
  }
13
24
  }
package/cli.md CHANGED
@@ -1,4 +1,4 @@
1
- ## underpost ci/cd cli v2.96.0
1
+ ## underpost ci/cd cli v2.96.1
2
2
 
3
3
  ### Usage: `underpost [options] [command]`
4
4
  ```
@@ -909,6 +909,7 @@ Options:
909
909
  --packer-workflow-id <workflow-id> Specifies the workflow ID for Packer MAAS image operations.
910
910
  --packer-maas-image-build Builds a MAAS image using Packer for the workflow specified by --packer-workflow-id.
911
911
  --packer-maas-image-upload Uploads an existing MAAS image artifact without rebuilding for the workflow specified by --packer-workflow-id.
912
+ --packer-maas-image-cached Continue last build without removing artifacts (used with --packer-maas-image-build).
912
913
  --commission Init workflow for commissioning a physical machine.
913
914
  --nfs-build Builds an NFS root filesystem for a workflow id config architecture using QEMU emulation.
914
915
  --nfs-mount Mounts the NFS root filesystem for a workflow id config architecture.
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-default-development-blue
20
- image: localhost/rockylinux9-underpost:v2.96.0
20
+ image: localhost/rockylinux9-underpost:v2.96.1
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -100,7 +100,7 @@ spec:
100
100
  spec:
101
101
  containers:
102
102
  - name: dd-default-development-green
103
- image: localhost/rockylinux9-underpost:v2.96.0
103
+ image: localhost/rockylinux9-underpost:v2.96.1
104
104
  # resources:
105
105
  # requests:
106
106
  # memory: "124Ki"
@@ -18,7 +18,7 @@ spec:
18
18
  spec:
19
19
  containers:
20
20
  - name: dd-test-development-blue
21
- image: localhost/rockylinux9-underpost:v2.96.0
21
+ image: localhost/rockylinux9-underpost:v2.96.1
22
22
 
23
23
  command:
24
24
  - /bin/sh
@@ -103,7 +103,7 @@ spec:
103
103
  spec:
104
104
  containers:
105
105
  - name: dd-test-development-green
106
- image: localhost/rockylinux9-underpost:v2.96.0
106
+ image: localhost/rockylinux9-underpost:v2.96.1
107
107
 
108
108
  command:
109
109
  - /bin/sh
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "@underpostnet/underpost",
5
- "version": "2.96.0",
5
+ "version": "2.96.1",
6
6
  "description": "pwa api rest template",
7
7
  "scripts": {
8
8
  "start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
@@ -107,8 +107,12 @@ source "qemu" "rocky9" {
107
107
  disk_size = "45G"
108
108
  format = "qcow2"
109
109
  headless = var.headless
110
- iso_checksum = "file:http://download.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/CHECKSUM"
111
- iso_url = "http://download.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso"
110
+ iso_checksum = "file:https://download.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/CHECKSUM"
111
+ iso_urls = [
112
+ "https://download.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso",
113
+ "https://dl.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso",
114
+ "https://mirrors.edge.kernel.org/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso"
115
+ ]
112
116
  iso_target_path = "packer_cache/Rocky-9-latest-${local.iso_arch}-boot.iso"
113
117
  memory = 2048
114
118
  cores = 4
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/make -f
2
+
3
+ include ../../scripts/check.mk
4
+
5
+ PACKER ?= packer
6
+ PACKER_LOG ?= 0
7
+ TIMEOUT ?= 1h
8
+ ARCH ?= arm64
9
+
10
+ # Detect if running on ARM host
11
+ ifeq ($(shell uname -m),aarch64)
12
+ HOST_IS_ARM = true
13
+ else
14
+ HOST_IS_ARM = false
15
+ endif
16
+
17
+ ifeq ($(wildcard /usr/share/OVMF/OVMF_CODE.fd),)
18
+ OVMF_SFX ?= _4M
19
+ else
20
+ OVMF_SFX ?=
21
+ endif
22
+
23
+ export PACKER_LOG
24
+
25
+ # Fallback
26
+ ifeq ($(strip $(ARCH)),amd64)
27
+ ARCH = x86_64
28
+ endif
29
+ ifeq ($(strip $(ARCH)),arm64)
30
+ ARCH = aarch64
31
+ endif
32
+
33
+ .PHONY: all clean
34
+
35
+ all: rocky9.tar.gz
36
+
37
+ $(eval $(call check_packages_deps))
38
+
39
+ lint:
40
+ packer validate .
41
+ packer fmt -check -diff .
42
+
43
+ format:
44
+ packer fmt .
45
+
46
+ OVMF_VARS.fd:
47
+ ifeq ($(strip $(ARCH)),aarch64)
48
+ cp -v /usr/share/AAVMF/AAVMF_VARS.fd ${ARCH}_VARS.fd
49
+ else
50
+ cp -v /usr/share/OVMF/OVMF_VARS${OVMF_SFX}.fd ${ARCH}_VARS.fd
51
+ endif
52
+
53
+ SIZE_VARS.fd:
54
+ ifeq ($(strip $(ARCH)),aarch64)
55
+ truncate -s 64m ${ARCH}_VARS.fd
56
+ else
57
+ truncate -s 2m ${ARCH}_VARS.fd
58
+ endif
59
+
60
+ rocky9.tar.gz: check-deps clean OVMF_VARS.fd SIZE_VARS.fd
61
+ ${PACKER} init rocky9.pkr.hcl && ${PACKER} build \
62
+ -var architecture=${ARCH} \
63
+ -var host_is_arm=${HOST_IS_ARM} \
64
+ -var timeout=${TIMEOUT} \
65
+ -var ovmf_suffix=${OVMF_SFX} \
66
+ rocky9.pkr.hcl
67
+
68
+ clean:
69
+ ${RM} -rf *.fd output-rocky9 rocky9.tar.gz
@@ -0,0 +1,122 @@
1
+ # Rocky 9 Packer template for MAAS
2
+
3
+ ## Introduction
4
+
5
+ The Packer template in this directory creates a Rocky 9 AMD64/ARM64 image for use with MAAS.
6
+
7
+ ## Prerequisites to create the image
8
+
9
+ * A machine running Ubuntu 22.04+ with the ability to run KVM virtual machines.
10
+ * qemu-utils, libnbd-bin, nbdkit and fuse2fs
11
+ * qemu-system
12
+ * qemu-system-modules-spice (If building on Ubuntu 24.04 LTS "Noble")
13
+ * ovmf
14
+ * cloud-image-utils
15
+ * parted
16
+ * [Packer.](https://www.packer.io/intro/getting-started/install.html), v1.11.0 or newer
17
+
18
+ ## Requirements to deploy the image
19
+
20
+ * [MAAS](https://maas.io) 3.3 or later, as that version introduces support for Rocky
21
+ * [Curtin](https://launchpad.net/curtin) 22.1. If you have a MAAS with an earlier Curtin version, you can [patch](https://code.launchpad.net/~xnox/curtin/+git/curtin/+merge/415604) distro.py to deploy Rocky.
22
+
23
+ ## Customizing the image
24
+
25
+ You can customize the deployment image by modifying http/rocky.ks. See the [RHEL kickstart documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/performing_an_advanced_rhel_installation/kickstart-commands-and-options-reference_installing-rhel-as-an-experienced-user#part-or-partition_kickstart-commands-for-handling-storage) for more information.
26
+
27
+ ## Building the image using a proxy
28
+
29
+ The Packer template downloads the Rocky ISO image from the Internet. You can tell Packer to use a proxy by setting the HTTP_PROXY environment variable to point to your proxy server. You can also redefine rocky_iso_url to a local file. If you want to skip the base image integrity check, set iso_checksum_type to none and remove iso_checksum.
30
+
31
+ To use a proxy during the installation define the `KS_PROXY` variable in the environment, as bellow:
32
+
33
+ ```shell
34
+ export KS_PROXY="\"${HTTP_PROXY}\""
35
+ ```
36
+
37
+ # Building the image using a kickstart mirror
38
+
39
+ To tell Packer to use a specific mirror set the `KS_MIRROR` environment variable
40
+ poiniting to the mirror URL.
41
+
42
+ ```shell
43
+ export KS_MIRROR="https://dl.rockylinux.org/pub/rocky/9"
44
+ ```
45
+
46
+ ## Building an image
47
+
48
+ You can build the image using the Makefile:
49
+
50
+ ```shell
51
+ make
52
+ ```
53
+
54
+ You can also manually run packer. Set your current working directory to packer-maas/rocky9, where this file resides, and generate an image with:
55
+
56
+ ```shell
57
+ packer init
58
+ PACKER_LOG=1 packer build .
59
+ ```
60
+
61
+ The installation runs in a non-interactive mode.
62
+
63
+ Note: rocky9.pkr.hcl runs Packer in headless mode, with the serial port output from qemu redirected to stdio to give feedback on image creation process. If you wish to see more, change the value of `headless` to `false` in rocky9.pkr.hcl, remove `[ "-serial", "stdio" ]` from `qemuargs` section and select `View`, then `serial0` in the qemu window that appears during build. This lets you watch progress of the image build script. Press `ctrl-b 2` to switch to shell to explore more, and `ctrl-b 1` to go back to log view.
64
+
65
+ ### Makefile Parameters
66
+
67
+ #### ARCH
68
+
69
+ Defaults to x86_64 to build AMD64 compatible images. In order to build ARM64 images, use ARCH=aarch64
70
+
71
+ #### TIMEOUT
72
+
73
+ The timeout to apply when building the image. The default value is set to 1h.
74
+
75
+ ## Uploading an image to MAAS
76
+
77
+ ```shell
78
+ maas $PROFILE boot-resources create name='custom/rocky9' \
79
+ title='Rocky 9 Custom' architecture='amd64/generic' \
80
+ base_image='rhel/9' filetype='tgz' \
81
+ content@=rocky9.tar.gz
82
+ ```
83
+
84
+ For ARM64, use:
85
+
86
+ ```shell
87
+ maas $PROFILE boot-resources create name='custom/rocky9' \
88
+ title='Rocky 9 Custom' architecture='arm64/generic' \
89
+ base_image='rhel/9' filetype='tgz' \
90
+ content@=rocky9.tar.gz
91
+ ```
92
+
93
+ Please note that, currently due to lack of support in curtin, deploying ARM64 images needs a preseed file. This is due to [LP# 2090874](https://bugs.launchpad.net/curtin/+bug/2090874) and currently is in the process of getting fixed.
94
+
95
+ ```
96
+ #cloud-config
97
+ debconf_selections:
98
+ maas: |
99
+ {{for line in str(curtin_preseed).splitlines()}}
100
+ {{line}}
101
+ {{endfor}}
102
+
103
+ extract_commands:
104
+ grub_install: curtin in-target -- cp -v /boot/efi/EFI/rocky/shimaa64.efi /boot/efi/EFI/rocky/shimx64.efi
105
+
106
+ late_commands:
107
+ maas: [wget, '--no-proxy', '{{node_disable_pxe_url}}', '--post-data', '{{node_disable_pxe_data}}', '-O', '/dev/null']
108
+ bootloader_01: ["curtin", "in-target", "--", "cp", "-v", "/boot/efi/EFI/rocky/shimaa64.efi", "/boot/efi/EFI/BOOT/bootaa64.efi"]
109
+ bootloader_02: ["curtin", "in-target", "--", "cp", "-v", "/boot/efi/EFI/rocky/grubaa64.efi", "/boot/efi/EFI/BOOT/"]
110
+ ```
111
+
112
+ This file needs to be saved on Region Controllers under /var/snap/maas/current/preseeds/curtin_userdata_custom_arm64_generic_rocky9 or /etc/maas/preseeds/curtin_userdata_custom_arm64_generic_rocky9. The last portion of this file must match the image name uploaded in MAAS.
113
+
114
+ ## Default username
115
+
116
+ MAAS uses cloud-init to create ```cloud-user``` account using the ssh keys configured for the MAAS admin user (e.g. imported from Launchpad). Log in to the machine:
117
+
118
+ ```shell
119
+ ssh -i ~/.ssh/<your_identity_file> cloud-user@<machine-ip-address>
120
+ ```
121
+
122
+ Next to that, the kickstart script creates an account with both username and password set to ```rocky```. Note that the default sshd configuration in Rocky 9 disallows password-based authentication when logging in via ssh, so trying `ssh rocky@<machine-ip-address>` will fail. Password-based authentication can be enabled by having `PasswordAuthentication yes` in /etc/ssh/sshd_config after logging in with ```cloud-user```. Perhaps there is a way to make that change using kickstart script, but it is not obvious as ```anaconda```, the installer, makes its own changes to sshd_config file during installation. If you know how to do this, a PR is welcome.
@@ -0,0 +1,114 @@
1
+ url ${KS_OS_REPOS} ${KS_PROXY}
2
+ repo --name="AppStream" ${KS_APPSTREAM_REPOS} ${KS_PROXY}
3
+ repo --name="Extras" ${KS_EXTRAS_REPOS} ${KS_PROXY}
4
+
5
+ eula --agreed
6
+
7
+ # Turn off after installation
8
+ poweroff
9
+
10
+ # Do not start the Inital Setup app
11
+ firstboot --disable
12
+
13
+ # System language, keyboard and timezone
14
+ lang en_US.UTF-8
15
+ keyboard us
16
+ timezone UTC --utc
17
+
18
+ # Set the first NIC to acquire IPv4 address via DHCP
19
+ network --device eth0 --bootproto=dhcp
20
+ # Enable firewal, let SSH through
21
+ firewall --enabled --service=ssh
22
+ # Enable SELinux with default enforcing policy
23
+ selinux --enforcing
24
+
25
+ # Do not set up XX Window System
26
+ skipx
27
+
28
+ # Initial disk setup
29
+ # Use the first paravirtualized disk
30
+ ignoredisk --only-use=vda
31
+ # No need for bootloader
32
+ bootloader --disabled
33
+ # Wipe invalid partition tables
34
+ zerombr
35
+ # Erase all partitions and assign default labels
36
+ clearpart --all --initlabel
37
+ # Initialize the primary root partition with ext4 filesystem
38
+ part / --size=1 --grow --asprimary --fstype=ext4
39
+
40
+ # Set root password
41
+ rootpw --plaintext password
42
+
43
+ # Add a user named packer
44
+ user --groups=wheel --name=rocky --password=rocky --plaintext --gecos="rocky"
45
+
46
+ %post --erroronfail
47
+ # workaround anaconda requirements and clear root password
48
+ passwd -d root
49
+ passwd -l root
50
+
51
+ # Clean up install config not applicable to deployed environments.
52
+ for f in resolv.conf fstab; do
53
+ rm -f /etc/$f
54
+ touch /etc/$f
55
+ chown root:root /etc/$f
56
+ chmod 644 /etc/$f
57
+ done
58
+
59
+ rm -f /etc/sysconfig/network-scripts/ifcfg-[^lo]*
60
+
61
+ # Kickstart copies install boot options. Serial is turned on for logging with
62
+ # Packer which disables console output. Disable it so console output is shown
63
+ # during deployments
64
+ sed -i 's/^GRUB_TERMINAL=.*/GRUB_TERMINAL_OUTPUT="console"/g' /etc/default/grub
65
+ sed -i '/GRUB_SERIAL_COMMAND="serial"/d' /etc/default/grub
66
+ sed -ri 's/(GRUB_CMDLINE_LINUX=".*)\s+console=ttyAMA0(.*")/\1\2/' /etc/default/grub
67
+ sed -i 's/GRUB_ENABLE_BLSCFG=.*/GRUB_ENABLE_BLSCFG=false/g' /etc/default/grub
68
+
69
+ dnf clean all
70
+
71
+ # Passwordless sudo for the user 'rocky'
72
+ echo "rocky ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/rocky
73
+ chmod 440 /etc/sudoers.d/rocky
74
+
75
+ #---- Optional - Install your SSH key ----
76
+ # mkdir -m0700 /home/rocky/.ssh/
77
+ #
78
+ # cat <<EOF >/home/rocky/.ssh/authorized_keys
79
+ # ssh-rsa <your_public_key_here> you@your.domain
80
+ # EOF
81
+ #
82
+ ### set permissions
83
+ # chmod 0600 /home/rocky/.ssh/authorized_keys
84
+ #
85
+ #### fix up selinux context
86
+ # restorecon -R /home/rocky/.ssh/
87
+
88
+ %end
89
+
90
+ %packages --ignoremissing
91
+ @core
92
+ bash-completion
93
+ cloud-init
94
+ cloud-utils-growpart
95
+ rsync
96
+ tar
97
+ patch
98
+ yum-utils
99
+ grub2-pc
100
+ grub2-efi-*
101
+ shim-*
102
+ grub2-efi-*-modules
103
+ efibootmgr
104
+ dosfstools
105
+ lvm2
106
+ mdadm
107
+ device-mapper-multipath
108
+ iscsi-initiator-utils
109
+ -plymouth
110
+ # Remove ALSA firmware
111
+ -a*-firmware
112
+ # Remove Intel wireless firmware
113
+ -i*-firmware
114
+ %end
@@ -0,0 +1,171 @@
1
+ packer {
2
+ required_version = ">= 1.11.0"
3
+ required_plugins {
4
+ qemu = {
5
+ version = ">= 1.1.0, < 1.1.2"
6
+ source = "github.com/hashicorp/qemu"
7
+ }
8
+ }
9
+ }
10
+
11
+ variable "filename" {
12
+ type = string
13
+ default = "rocky9.tar.gz"
14
+ description = "The filename of the tarball to produce"
15
+ }
16
+
17
+ variable ks_proxy {
18
+ type = string
19
+ default = "${env("KS_PROXY")}"
20
+ }
21
+
22
+ variable ks_mirror {
23
+ type = string
24
+ default = "${env("KS_MIRROR")}"
25
+ }
26
+
27
+ variable "timeout" {
28
+ type = string
29
+ default = "1h"
30
+ description = "Timeout for building the image"
31
+ }
32
+
33
+ variable "architecture" {
34
+ type = string
35
+ default = "arm64"
36
+ description = "The architecture to build the image for (amd64 or arm64)"
37
+ }
38
+
39
+ variable "host_is_arm" {
40
+ type = bool
41
+ default = false
42
+ description = "The host architecture is aarch64"
43
+ }
44
+
45
+ variable "ovmf_suffix" {
46
+ type = string
47
+ default = ""
48
+ description = "Suffix for OVMF CODE and VARS files. Newer systems such as Noble use _4M."
49
+ }
50
+
51
+ variable "headless" {
52
+ type = bool
53
+ default = true
54
+ description = "Run packer in headless mode"
55
+ }
56
+
57
+ locals {
58
+ iso_arch_map = {
59
+ "amd64" = "x86_64"
60
+ "x86_64" = "x86_64"
61
+ "arm64" = "aarch64"
62
+ "aarch64" = "aarch64"
63
+ }
64
+ iso_arch = lookup(local.iso_arch_map, var.architecture, "aarch64")
65
+
66
+ iso_checksum_map = {
67
+ "amd64" = "sha256:3b5c87b2f9e62fdf0235d424d64c677906096965aad8a580e0e98fcb9f97f267"
68
+ "x86_64" = "sha256:3b5c87b2f9e62fdf0235d424d64c677906096965aad8a580e0e98fcb9f97f267"
69
+ "arm64" = "sha256:a9ba9ff1187300cecccfaea021eeac04b6408b1180071fb22ee73249f075485e"
70
+ "aarch64" = "sha256:a9ba9ff1187300cecccfaea021eeac04b6408b1180071fb22ee73249f075485e"
71
+ }
72
+
73
+ qemu_arch = {
74
+ "amd64" = "x86_64"
75
+ "x86_64" = "x86_64"
76
+ "arm64" = "aarch64"
77
+ "aarch64" = "aarch64"
78
+ }
79
+ uefi_imp = {
80
+ "amd64" = "OVMF"
81
+ "x86_64" = "OVMF"
82
+ "arm64" = "AAVMF"
83
+ "aarch64" = "AAVMF"
84
+ }
85
+ uefi_sfx = {
86
+ "amd64" = "${var.ovmf_suffix}"
87
+ "x86_64" = "${var.ovmf_suffix}"
88
+ "arm64" = ""
89
+ "aarch64" = ""
90
+ }
91
+ qemu_machine = {
92
+ "amd64" = "accel=kvm"
93
+ "x86_64" = "accel=kvm"
94
+ "arm64" = var.host_is_arm ? "virt,accel=kvm" : "virt"
95
+ "aarch64" = var.host_is_arm ? "virt,accel=kvm" : "virt"
96
+ }
97
+ qemu_cpu = {
98
+ "amd64" = "host"
99
+ "x86_64" = "host"
100
+ "arm64" = var.host_is_arm ? "host" : "max"
101
+ "aarch64" = var.host_is_arm ? "host" : "max"
102
+ }
103
+
104
+ ks_proxy = var.ks_proxy != "" ? "--proxy=${var.ks_proxy}" : ""
105
+ ks_os_repos = var.ks_mirror != "" ? "--url=${var.ks_mirror}/BaseOS/${local.iso_arch}/os" : "--mirrorlist='http://mirrors.rockylinux.org/mirrorlist?arch=${local.iso_arch}&repo=BaseOS-9'"
106
+ ks_appstream_repos = var.ks_mirror != "" ? "--baseurl=${var.ks_mirror}/AppStream/${local.iso_arch}/os" : "--mirrorlist='https://mirrors.rockylinux.org/mirrorlist?release=9&arch=${local.iso_arch}&repo=AppStream-9'"
107
+ ks_extras_repos = var.ks_mirror != "" ? "--baseurl=${var.ks_mirror}/extras/${local.iso_arch}/os" : "--mirrorlist='https://mirrors.rockylinux.org/mirrorlist?arch=${local.iso_arch}&repo=extras-9'"
108
+ }
109
+
110
+ source "qemu" "rocky9" {
111
+ boot_command = ["<up><wait>", "e", "<down><down><down><left>", " console=ttyAMA0 inst.cmdline inst.text inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/rocky9.ks <f10>"]
112
+ boot_wait = "5s"
113
+ communicator = "none"
114
+ disk_size = "45G"
115
+ format = "qcow2"
116
+ headless = var.headless
117
+ iso_checksum = lookup(local.iso_checksum_map, var.architecture, "file:https://download.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/CHECKSUM")
118
+ iso_urls = [
119
+ "https://download.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso",
120
+ "https://dl.rockylinux.org/pub/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso",
121
+ "https://mirrors.edge.kernel.org/rocky/9/isos/${local.iso_arch}/Rocky-9-latest-${local.iso_arch}-boot.iso"
122
+ ]
123
+ iso_target_path = "packer_cache/Rocky-9-latest-${local.iso_arch}-boot.iso"
124
+ memory = 2048
125
+ cores = 4
126
+ qemu_binary = "qemu-system-${lookup(local.qemu_arch, var.architecture, "")}"
127
+ qemuargs = [
128
+ ["-serial", "stdio"],
129
+ ["-boot", "strict=off"],
130
+ ["-device", "qemu-xhci"],
131
+ ["-device", "usb-kbd"],
132
+ ["-device", "virtio-net-pci,netdev=net0"],
133
+ ["-netdev", "user,id=net0"],
134
+ ["-device", "virtio-blk-pci,drive=drive0,bootindex=0"],
135
+ ["-device", "virtio-blk-pci,drive=cdrom0,bootindex=1"],
136
+ ["-machine", "${lookup(local.qemu_machine, var.architecture, "")}"],
137
+ ["-cpu", "${lookup(local.qemu_cpu, var.architecture, "")}"],
138
+ ["-device", "virtio-gpu-pci"],
139
+ ["-global", "driver=cfi.pflash01,property=secure,value=off"],
140
+ ["-drive", "if=pflash,format=raw,unit=0,id=ovmf_code,readonly=on,file=/usr/share/${lookup(local.uefi_imp, var.architecture, "")}/${lookup(local.uefi_imp, var.architecture, "")}_CODE${lookup(local.uefi_sfx, var.architecture, "")}.fd"],
141
+ ["-drive", "if=pflash,format=raw,unit=1,id=ovmf_vars,file=${local.iso_arch}_VARS.fd"],
142
+ ["-drive", "file=output-rocky9/packer-rocky9,if=none,id=drive0,cache=writeback,discard=ignore,format=qcow2"],
143
+ ["-drive", "file=packer_cache/Rocky-9-latest-${local.iso_arch}-boot.iso,if=none,id=cdrom0,media=cdrom"]
144
+ ]
145
+ shutdown_timeout = var.timeout
146
+ http_content = {
147
+ "/rocky9.ks" = templatefile("${path.root}/http/rocky9.ks.pkrtpl.hcl",
148
+ {
149
+ KS_PROXY = local.ks_proxy,
150
+ KS_OS_REPOS = local.ks_os_repos,
151
+ KS_APPSTREAM_REPOS = local.ks_appstream_repos,
152
+ KS_EXTRAS_REPOS = local.ks_extras_repos
153
+ }
154
+ )
155
+ }
156
+ }
157
+
158
+ build {
159
+ sources = ["source.qemu.rocky9"]
160
+
161
+ post-processor "shell-local" {
162
+ inline = [
163
+ "SOURCE=${source.name}",
164
+ "OUTPUT=${var.filename}",
165
+ "source ../../scripts/fuse-nbd",
166
+ "source ../../scripts/fuse-tar-root",
167
+ "rm -rf output-${source.name}",
168
+ ]
169
+ inline_shebang = "/bin/bash -e"
170
+ }
171
+ }
@@ -14,15 +14,25 @@ for image_dir in "$PACKER_DIR"/*; do
14
14
  echo "Checking UEFI VARS files for $image_name..."
15
15
 
16
16
  # Create x86_64 VARS file if it doesn't exist
17
- if [ -f /usr/share/edk2/ovmf/OVMF_VARS.fd ] && [ ! -f "$image_dir/x86_64_VARS.fd" ]; then
18
- cp /usr/share/edk2/ovmf/OVMF_VARS.fd "$image_dir/x86_64_VARS.fd"
19
- echo "Created $image_dir/x86_64_VARS.fd"
17
+ if [ ! -f "$image_dir/x86_64_VARS.fd" ]; then
18
+ for src in /usr/share/edk2/ovmf/OVMF_VARS.fd /usr/share/OVMF/OVMF_VARS.fd; do
19
+ if [ -f "$src" ]; then
20
+ cp "$src" "$image_dir/x86_64_VARS.fd"
21
+ echo "Created $image_dir/x86_64_VARS.fd from $src"
22
+ break
23
+ fi
24
+ done
20
25
  fi
21
26
 
22
27
  # Create aarch64 VARS file if it doesn't exist
23
- if [ -f /usr/share/edk2/aarch64/AAVMF_VARS.fd ] && [ ! -f "$image_dir/aarch64_VARS.fd" ]; then
24
- cp /usr/share/edk2/aarch64/AAVMF_VARS.fd "$image_dir/aarch64_VARS.fd"
25
- echo "Created $image_dir/aarch64_VARS.fd"
28
+ if [ ! -f "$image_dir/aarch64_VARS.fd" ]; then
29
+ for src in /usr/share/AAVMF/AAVMF_VARS.fd /usr/share/edk2/aarch64/AAVMF_VARS.fd /usr/share/edk2/aarch64/QEMU_VARS.fd; do
30
+ if [ -f "$src" ]; then
31
+ cp "$src" "$image_dir/aarch64_VARS.fd"
32
+ echo "Created $image_dir/aarch64_VARS.fd from $src"
33
+ break
34
+ fi
35
+ done
26
36
  fi
27
37
  fi
28
38
  done
@@ -1,52 +1,289 @@
1
1
  #!/usr/bin/env bash
2
- # Script to install Packer and libvirt/qemu tooling on Rocky Linux.
3
- # Usage: sudo ./scripts/packer-setup.sh
2
+ # packer-setup.sh
3
+ # RHEL/CentOS/Rocky helper to prepare a host for building both amd64 and aarch64 Packer images.
4
+ # Installs packer (repo fallback), libvirt/qemu tooling, NBD/filesystem tools, UEFI firmware for x86_64 and aarch64,
5
+ # registers qemu-user binfmt for cross-chroot use and compiles qemu-system-aarch64 when repo packages are missing.
6
+ # Usage: sudo ./packer-setup.sh
4
7
 
5
8
  set -euo pipefail
6
9
 
10
+ print(){ printf "[setup] %s\n" "$*"; }
11
+ err(){ printf "[setup] ERROR: %s\n" "$*" >&2; }
7
12
 
8
- # 1) Replace/add the correct HashiCorp repo for RHEL/Rocky
9
- sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
13
+ if [[ $(id -u) -ne 0 ]]; then
14
+ err "This script requires root. Run with sudo."; exit 3
15
+ fi
16
+
17
+ # Detect host arch
18
+ HOST_RAW_ARCH=$(uname -m)
19
+ case "$HOST_RAW_ARCH" in
20
+ x86_64) HOST_ARCH=amd64;;
21
+ aarch64) HOST_ARCH=arm64;;
22
+ *) HOST_ARCH="$HOST_RAW_ARCH";;
23
+ esac
24
+ print "Host architecture: $HOST_RAW_ARCH -> normalized: $HOST_ARCH"
25
+
26
+ # Ensure RHEL-family
27
+ if [[ -f /etc/os-release ]]; then
28
+ . /etc/os-release
29
+ ID_LC=${ID,,}
30
+ ID_LIKE=${ID_LIKE,,}
31
+ else
32
+ ID_LC=unknown; ID_LIKE=unknown
33
+ fi
34
+ if [[ $ID_LC != "rocky" && $ID_LC != "rhel" && $ID_LC != "centos" && $ID_LIKE != *"rhel"* ]]; then
35
+ err "This script targets RHEL/CentOS/Rocky family. Detected: $ID_LC (like: $ID_LIKE). Exiting."; exit 4
36
+ fi
37
+ print "Distro detected: $PRETTY_NAME"
38
+
39
+ # Enable helpful repos and install helpers
40
+ print "Installing dnf-plugins-core and enabling CRB/PowerTools (if available)"
41
+ set +e
42
+ dnf install -y dnf-plugins-core >/dev/null 2>&1 || true
43
+ dnf config-manager --set-enabled crb >/dev/null 2>&1 || true
44
+ dnf config-manager --set-enabled powertools >/dev/null 2>&1 || true
45
+ # EPEL
46
+ if ! rpm -q epel-release >/dev/null 2>&1; then
47
+ print "Installing epel-release"
48
+ dnf install -y epel-release || true
49
+ fi
50
+ set -e
51
+
52
+ # 1) Try to install packer from distro repos, otherwise add HashiCorp repo and install
53
+ print "Attempting to install packer from distro repos"
54
+ if dnf install -y packer >/dev/null 2>&1; then
55
+ print "Packer installed from distro repo"
56
+ else
57
+ print "packer not available in distro repos or install failed. Adding HashiCorp repo and retrying."
58
+ # HashiCorp RPM repo for RHEL/CentOS/Rocky family (works for EL8/EL9)
59
+ if ! rpm -q hashicorp >/dev/null 2>&1; then
60
+ cat >/etc/yum.repos.d/hashicorp.repo <<'EOF'
61
+ [hashicorp]
62
+ name=HashiCorp Stable - $basearch
63
+ baseurl=https://rpm.releases.hashicorp.com/RHEL/$releasever/$basearch/stable
64
+ enabled=1
65
+ gpgcheck=1
66
+ gpgkey=https://rpm.releases.hashicorp.com/gpg
67
+ EOF
68
+ fi
69
+ if dnf makecache >/dev/null 2>&1 && dnf install -y packer >/dev/null 2>&1; then
70
+ print "Packer installed from HashiCorp repo"
71
+ else
72
+ err "Packer install from repo failed. You can install packer manually from HashiCorp releases if needed.";
73
+ fi
74
+ fi
10
75
 
11
- # Refresh dnf metadata
12
- sudo dnf clean all
13
- sudo dnf makecache --refresh
76
+ # 2) Install libvirt, qemu tooling and enable libvirtd
77
+ print "Installing libvirt, qemu and related tooling (best-effort)"
78
+ LIBVIRT_PKGS=(libvirt libvirt-daemon qemu-kvm qemu-img virt-install bridge-utils)
79
+ # attempt to include qemu-system-aarch64 and qemu-system-x86_64 if available
80
+ LIBVIRT_PKGS+=(qemu-system-aarch64 qemu-system-x86_64 qemu-system-arm)
14
81
 
15
- # 2) Try to install packer from the repo
16
- sudo dnf install -y packer || echo "packer install from repo failed, try manual install (see below)"
82
+ # Some packages may not exist exactly with those names; install best-effort
83
+ for pkg in "${LIBVIRT_PKGS[@]}"; do
84
+ dnf install -y "$pkg" >/dev/null 2>&1 || print "Package $pkg not available via dnf (skipping)"
85
+ done
17
86
 
18
- # 3) Install libvirt/qemu tooling then start the service
19
- sudo dnf install -y libvirt libvirt-daemon qemu-kvm qemu-img virt-install bridge-utils
20
- sudo systemctl enable --now libvirtd
21
- sudo systemctl status libvirtd --no-pager
87
+ print "Enabling and starting libvirtd"
88
+ systemctl enable --now libvirtd || err "Failed to enable/start libvirtd"
89
+ systemctl status libvirtd --no-pager || true
22
90
 
23
- # 3a) Install NBD and filesystem tools required for MAAS image creation
24
- sudo dnf install -y libnbd nbdkit e2fsprogs kmod-kvdo kmod
91
+ # 3) Install NBD and filesystem tools required for MAAS image creation
92
+ print "Installing NBD and filesystem tooling: libnbd, nbdkit, e2fsprogs, kmod packages (best-effort)"
93
+ NBDS=(libnbd nbdkit e2fsprogs kmod-kvdo kmod)
94
+ for pkg in "${NBDS[@]}"; do
95
+ dnf install -y "$pkg" >/dev/null 2>&1 || print "Package $pkg not available via dnf (skipping)"
96
+ done
25
97
 
26
98
  # 4) Install UEFI firmware for x86_64 and ARM64
27
- sudo dnf install -y edk2-ovmf edk2-aarch64
99
+ print "Installing edk2 / OVMF UEFI firmware packages (x86_64 and aarch64)"
100
+ UEFI_PKGS=(edk2-ovmf edk2-aarch64 edk2-ovmf-aarch64)
101
+ for pkg in "${UEFI_PKGS[@]}"; do
102
+ dnf install -y "$pkg" >/dev/null 2>&1 || print "UEFI package $pkg not available (skipping)"
103
+ done
28
104
 
29
- # 5) Create symlinks for qemu-system-* binaries (Rocky/RHEL uses qemu-kvm instead)
30
- # Packer expects standard qemu-system-* names, but RHEL-based distros use qemu-kvm
31
- if [ -f /usr/libexec/qemu-kvm ] && [ ! -f /usr/bin/qemu-system-x86_64 ]; then
32
- echo "Creating symlink: /usr/bin/qemu-system-x86_64 -> /usr/libexec/qemu-kvm"
33
- sudo ln -sf /usr/libexec/qemu-kvm /usr/bin/qemu-system-x86_64
105
+ # 5) Ensure qemu-user-static for chroot/debootstrap cross-execution and register binfmt via podman
106
+ print "Installing qemu-user-static and registering binfmt handlers via podman"
107
+ if ! rpm -q qemu-user-static >/dev/null 2>&1; then
108
+ dnf install -y qemu-user-static >/dev/null 2>&1 || print "qemu-user-static not in repos (will extract from container)"
34
109
  fi
35
110
 
36
- if [ -f /usr/libexec/qemu-kvm ] && [ ! -f /usr/bin/qemu-system-aarch64 ]; then
37
- echo "Creating symlink: /usr/bin/qemu-system-aarch64 -> /usr/libexec/qemu-kvm"
38
- sudo ln -sf /usr/libexec/qemu-kvm /usr/bin/qemu-system-aarch64
111
+ if command -v podman >/dev/null 2>&1; then
112
+ # Register binfmt handlers
113
+ podman run --rm --privileged multiarch/qemu-user-static --reset -p yes || print "podman run for qemu-user-static failed (skip)"
114
+
115
+ # Extract static binaries to /usr/bin if not already present from RPM
116
+ if ! command -v qemu-aarch64-static >/dev/null 2>&1; then
117
+ print "Extracting qemu static binaries from multiarch/qemu-user-static container"
118
+ CONTAINER_ID=$(podman create multiarch/qemu-user-static:latest)
119
+
120
+ # Extract the binaries we need
121
+ for arch in aarch64 arm armeb; do
122
+ if podman cp "$CONTAINER_ID:/usr/bin/qemu-${arch}-static" "/usr/bin/qemu-${arch}-static" 2>/dev/null; then
123
+ chmod +x "/usr/bin/qemu-${arch}-static"
124
+ print "Installed qemu-${arch}-static to /usr/bin/"
125
+ fi
126
+ done
127
+
128
+ podman rm "$CONTAINER_ID" >/dev/null 2>&1 || true
129
+ fi
130
+ else
131
+ print "podman not available. Install podman to register binfmt for container/chroot convenience."
39
132
  fi
40
133
 
41
- # 6) Create symlinks for OVMF/AAVMF firmware files in expected locations
42
- # Rocky/RHEL stores OVMF in /usr/share/edk2/ovmf, but Packer expects /usr/share/OVMF
43
- if [ -f /usr/share/edk2/ovmf/OVMF_CODE.fd ] && [ ! -f /usr/share/OVMF/OVMF_CODE.fd ]; then
44
- echo "Creating symlink: /usr/share/OVMF/OVMF_CODE.fd -> /usr/share/edk2/ovmf/OVMF_CODE.fd"
45
- sudo ln -sf /usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/OVMF/OVMF_CODE.fd
134
+ # 6) Check qemu-system-aarch64 availability and 'virt' machine support; offer compile if missing
135
+ check_qemu_system_aarch64(){
136
+ # Explicitly check /usr/local/bin first (where compiled QEMU installs)
137
+ if [ -x /usr/local/bin/qemu-system-aarch64 ]; then
138
+ QBIN=/usr/local/bin/qemu-system-aarch64
139
+ elif command -v qemu-system-aarch64 >/dev/null 2>&1; then
140
+ QBIN=$(command -v qemu-system-aarch64)
141
+ else
142
+ return 1
143
+ fi
144
+
145
+ print "qemu-system-aarch64 found at $QBIN"
146
+ if ! $QBIN -machine help 2>/dev/null | grep -q '\bvirt\b'; then
147
+ err "qemu-system-aarch64 present but 'virt' not listed -> may be missing aarch64 softmmu"
148
+ return 2
149
+ fi
150
+
151
+ # Check for user networking (slirp) support.
152
+ # We specify -machine virt to avoid "No machine specified" errors on some QEMU versions.
153
+ if ! $QBIN -machine virt -netdev help 2>&1 | grep -q '\buser\b'; then
154
+ err "qemu-system-aarch64 present but 'user' network backend not listed -> missing libslirp support"
155
+ return 3
156
+ fi
157
+
158
+ print "qemu-system-aarch64 supports 'virt' and 'user' network -> good for full-system ARM emulation"
159
+ return 0
160
+ }
161
+
162
+ if check_qemu_system_aarch64; then
163
+ print "qemu-system-aarch64 is ready"
164
+ else
165
+ rc=$?
166
+ if [[ $rc -eq 1 ]]; then
167
+ print "qemu-system-aarch64 not found in installed packages"
168
+ else
169
+ print "qemu-system-aarch64 present but incomplete"
170
+ fi
171
+
172
+ # Try install from repo explicitly
173
+ print "Attempting to install qemu-system-aarch64 via dnf (best-effort)"
174
+ if dnf install -y qemu-system-aarch64 >/dev/null 2>&1; then
175
+ print "Installed qemu-system-aarch64 from repo"
176
+ else
177
+ print "qemu-system-aarch64 not available in enabled repos."
178
+ fi
179
+
180
+ if check_qemu_system_aarch64; then
181
+ print "qemu-system-aarch64 now available after package install"
182
+ else
183
+ print "Compiling QEMU with aarch64-softmmu target. Installing build deps..."
184
+ dnf groupinstall -y 'Development Tools' || true
185
+ dnf install -y git libaio-devel libgcrypt-devel libfdt-devel glib2-devel zlib-devel pixman-devel libseccomp-devel libusb1-devel openssl-devel bison flex python3 python3-pip ninja-build || true
186
+
187
+ # Enforce libslirp-devel for user networking
188
+ if ! dnf install -y libslirp-devel; then
189
+ err "Failed to install libslirp-devel. User networking will not work."
190
+ exit 1
191
+ fi
192
+
193
+ # Install required Python packages for QEMU build
194
+ print "Installing Python dependencies for QEMU build"
195
+ python3 -m pip install --upgrade pip || true
196
+ python3 -m pip install tomli meson || true
197
+
198
+ TMPDIR=$(mktemp -d)
199
+ print "Cloning QEMU source to $TMPDIR/qemu"
200
+ # Use a stable release tag (v9.0.0) to ensure consistency
201
+ git clone --depth 1 --branch v9.0.0 https://gitlab.com/qemu-project/qemu.git "$TMPDIR/qemu"
202
+ cd "$TMPDIR/qemu"
203
+
204
+ print "Configuring QEMU build"
205
+ if ./configure --target-list=aarch64-softmmu --enable-virtfs --enable-slirp; then
206
+ print "Configure successful, building QEMU..."
207
+ if make -j"$(nproc)"; then
208
+ print "Build successful, installing..."
209
+ make install || err "QEMU install failed"
210
+ # Update PATH to include /usr/local/bin where QEMU was installed
211
+ export PATH="/usr/local/bin:$PATH"
212
+ hash -r || true
213
+ else
214
+ err "QEMU build (make) failed"
215
+ fi
216
+ else
217
+ err "QEMU configure failed. Check dependencies."
218
+ fi
219
+
220
+ if check_qemu_system_aarch64; then
221
+ print "Successfully compiled and installed qemu-system-aarch64"
222
+ else
223
+ err "Compiled QEMU but qemu-system-aarch64 still missing or lacks 'virt'. Check logs in $TMPDIR/qemu"
224
+ fi
225
+
226
+ cd /
227
+ rm -rf "$TMPDIR" || true
228
+ print "Removed build directory $TMPDIR"
229
+ fi
46
230
  fi
47
231
 
48
- # Create AAVMF symlinks for ARM64 support
49
- if [ -d /usr/share/edk2/aarch64 ] && [ ! -d /usr/share/AAVMF ]; then
50
- echo "Creating symlink: /usr/share/AAVMF -> /usr/share/edk2/aarch64"
51
- sudo ln -sf /usr/share/edk2/aarch64 /usr/share/AAVMF
232
+ # 7) Summary and verification commands for the user
233
+ print "\n=== Summary / Quick verification commands ==="
234
+ if command -v packer >/dev/null 2>&1; then print "packer: $(command -v packer)"; else print "packer: NOT INSTALLED"; fi
235
+ # Check /usr/local/bin explicitly for compiled qemu
236
+ if [ -x /usr/local/bin/qemu-system-aarch64 ]; then
237
+ print "qemu-system-aarch64: /usr/local/bin/qemu-system-aarch64"
238
+ elif command -v qemu-system-aarch64 >/dev/null 2>&1; then
239
+ print "qemu-system-aarch64: $(command -v qemu-system-aarch64)"
240
+ else
241
+ print "qemu-system-aarch64: NOT INSTALLED"
52
242
  fi
243
+ if command -v qemu-aarch64-static >/dev/null 2>&1; then print "qemu-aarch64-static: $(command -v qemu-aarch64-static)"; else print "qemu-aarch64-static: NOT INSTALLED"; fi
244
+ print "libvirtd status:"
245
+ systemctl status libvirtd --no-pager || true
246
+
247
+ cat <<'EOF'
248
+
249
+ === Example Packer qemu builder snippets ===
250
+
251
+ # aarch64 on x86_64 host (use qemu-system-aarch64 with TCG):
252
+ {
253
+ "type": "qemu",
254
+ "qemu_binary": "/usr/local/bin/qemu-system-aarch64",
255
+ "accelerator": "tcg",
256
+ "format": "raw",
257
+ "disk_size": "8192",
258
+ "headless": true,
259
+ "qemuargs": [
260
+ ["-machine", "virt,highmem=on"],
261
+ ["-cpu", "cortex-a57"],
262
+ ["-bios", "/usr/share/edk2-aarch64/QEMU_EFI.fd"],
263
+ ["-device", "virtio-net-device,netdev=net0"],
264
+ ["-netdev", "user,id=net0,hostfwd=tcp::2222-:22"]
265
+ ]
266
+ }
267
+
268
+ # x86_64 on arm64 host (use kvm when available):
269
+ {
270
+ "type": "qemu",
271
+ "qemu_binary": "/usr/bin/qemu-system-x86_64",
272
+ "accelerator": "kvm",
273
+ "format": "raw",
274
+ "disk_size": "8192",
275
+ "headless": true,
276
+ "qemuargs": [
277
+ ["-machine", "pc,q35"],
278
+ ["-cpu", "host"],
279
+ ["-bios", "/usr/share/ovmf/OVMF_CODE.fd"],
280
+ ["-device", "virtio-net-pci,netdev=net0"],
281
+ ["-netdev", "user,id=net0,hostfwd=tcp::2223-:22"]
282
+ ]
283
+ }
284
+
285
+ EOF
286
+
287
+ print "Done. If any package failed to install due to repo availability, consider enabling CRB/powertools, adding a trusted COPR or rebuild repo for qemu, or compiling QEMU locally as the script offered."
288
+
289
+ exit 0
@@ -87,6 +87,7 @@ class UnderpostBaremetal {
87
87
  packerWorkflowId: '',
88
88
  packerMaasImageBuild: false,
89
89
  packerMaasImageUpload: false,
90
+ packerMaasImageCached: false,
90
91
  cloudInitUpdate: false,
91
92
  commission: false,
92
93
  nfsBuild: false,
@@ -141,8 +142,9 @@ class UnderpostBaremetal {
141
142
  }
142
143
 
143
144
  if (options.packerMaasImageTemplate) {
145
+ workflowId = options.packerWorkflowId;
144
146
  if (!workflowId) {
145
- throw new Error('workflow-id is required when using --packer-maas-image-template');
147
+ throw new Error('--packer-workflow-id is required when using --packer-maas-image-template');
146
148
  }
147
149
 
148
150
  const templatePath = options.packerMaasImageTemplate;
@@ -187,7 +189,7 @@ class UnderpostBaremetal {
187
189
  logger.info(`1. Review and customize the Packer template files in: ${targetDir}`);
188
190
  logger.info(`2. Review the workflow configuration in engine/baremetal/packer-workflows.json`);
189
191
  logger.info(
190
- `3. Build the image with: underpost baremetal ${workflowId} --packer-maas-image-build ${workflowId}`,
192
+ `3. Build the image with: underpost baremetal --packer-workflow-id ${workflowId} --packer-maas-image-build`,
191
193
  );
192
194
  } catch (error) {
193
195
  throw new Error(`Failed to extract template: ${error.message}`);
@@ -217,10 +219,26 @@ class UnderpostBaremetal {
217
219
  throw new Error('Packer is not installed. Please install Packer to proceed.');
218
220
  }
219
221
 
222
+ // Check for QEMU support if building for a different architecture (validator bots case)
223
+ UnderpostBaremetal.API.checkQemuCrossArchSupport(workflow);
224
+
220
225
  logger.info(`Building Packer image for ${workflowId} in ${packerDir}...`);
221
- const artifacts = ['output-rocky9', 'packer_cache', 'x86_64_VARS.fd', 'rocky9.tar.gz'];
222
- shellExec(`cd packer/images/${workflowId}
226
+
227
+ // Only remove artifacts if not using cached mode
228
+ if (!options.packerMaasImageCached) {
229
+ const artifacts = [
230
+ 'output-rocky9',
231
+ 'packer_cache',
232
+ 'x86_64_VARS.fd',
233
+ 'aarch64_VARS.fd',
234
+ workflow.maas.content,
235
+ ];
236
+ shellExec(`cd packer/images/${workflowId}
223
237
  rm -rf ${artifacts.join(' ')}`);
238
+ logger.info('Removed previous build artifacts');
239
+ } else {
240
+ logger.info('Cached mode: Keeping existing artifacts for incremental build');
241
+ }
224
242
  shellExec(`chmod +x ${underpostRoot}/scripts/packer-init-vars-file.sh`);
225
243
  shellExec(`${underpostRoot}/scripts/packer-init-vars-file.sh`);
226
244
 
@@ -229,10 +247,17 @@ rm -rf ${artifacts.join(' ')}`);
229
247
  throw new Error('Packer init failed');
230
248
  }
231
249
 
232
- const build = spawnSync('packer', ['build', '.'], {
250
+ const isArm = process.arch === 'arm64';
251
+ // Add /usr/local/bin to PATH so Packer can find compiled QEMU binaries
252
+ const packerEnv = {
253
+ ...process.env,
254
+ PACKER_LOG: '1',
255
+ PATH: `/usr/local/bin:${process.env.PATH || '/usr/bin:/bin'}`,
256
+ };
257
+ const build = spawnSync('packer', ['build', '-var', `host_is_arm=${isArm}`, '.'], {
233
258
  stdio: 'inherit',
234
259
  cwd: packerDir,
235
- env: { ...process.env, PACKER_LOG: '1' },
260
+ env: packerEnv,
236
261
  });
237
262
 
238
263
  if (build.status !== 0) {
@@ -244,7 +269,7 @@ rm -rf ${artifacts.join(' ')}`);
244
269
  if (!fs.existsSync(tarballPath)) {
245
270
  throw new Error(
246
271
  `Build artifact not found: ${tarballPath}\n` +
247
- `Please build first with: --packer-maas-image-build ${workflowId}`,
272
+ `Please build first with: --packer-workflow-id ${workflowId} --packer-maas-image-build`,
248
273
  );
249
274
  }
250
275
  const stats = fs.statSync(tarballPath);
@@ -1388,6 +1413,80 @@ udp-port = 32766
1388
1413
  logger.info('NFS server restarted.');
1389
1414
  },
1390
1415
 
1416
+ /**
1417
+ * @method checkQemuCrossArchSupport
1418
+ * @description Checks for QEMU support when building for a different architecture.
1419
+ * This is essential for validator bots that need to build images for architectures
1420
+ * different from the host system (e.g., building arm64 on x86_64 or vice versa).
1421
+ * @param {object} workflow - The workflow configuration object.
1422
+ * @param {object} workflow.maas - The MAAS configuration.
1423
+ * @param {string} workflow.maas.architecture - Target architecture (e.g., 'arm64/generic', 'amd64/generic').
1424
+ * @memberof UnderpostBaremetal
1425
+ * @throws {Error} If QEMU is not installed or doesn't support required machine types.
1426
+ * @returns {void}
1427
+ */
1428
+ checkQemuCrossArchSupport(workflow) {
1429
+ // Check for QEMU support if building for a different architecture (validator bots case)
1430
+ if (workflow.maas.architecture.startsWith('arm64') && process.arch !== 'arm64') {
1431
+ // Building arm64/aarch64 on x86_64 host
1432
+ // Check both /usr/local/bin (compiled) and system paths
1433
+ let qemuAarch64Path = null;
1434
+
1435
+ if (shellExec('test -x /usr/local/bin/qemu-system-aarch64', { silent: true }).code === 0) {
1436
+ qemuAarch64Path = '/usr/local/bin/qemu-system-aarch64';
1437
+ } else if (shellExec('which qemu-system-aarch64', { silent: true }).code === 0) {
1438
+ qemuAarch64Path = shellExec('which qemu-system-aarch64', { silent: true }).stdout.trim();
1439
+ }
1440
+
1441
+ if (!qemuAarch64Path) {
1442
+ throw new Error(
1443
+ 'qemu-system-aarch64 is not installed. Please install it to build ARM64 images on x86_64 hosts.\n' +
1444
+ 'Run: node bin baremetal --dev --install-packer',
1445
+ );
1446
+ }
1447
+
1448
+ logger.info(`Found qemu-system-aarch64 at: ${qemuAarch64Path}`);
1449
+
1450
+ // Verify that the installed qemu supports the 'virt' machine type (required for arm64)
1451
+ const machineHelp = shellExec(`${qemuAarch64Path} -machine help`, { silent: true }).stdout;
1452
+ if (!machineHelp.includes('virt')) {
1453
+ throw new Error(
1454
+ 'The installed qemu-system-aarch64 does not support the "virt" machine type.\n' +
1455
+ 'This usually happens if qemu-system-aarch64 is a symlink to qemu-kvm on x86_64.\n' +
1456
+ 'Run: node bin baremetal --dev --install-packer',
1457
+ );
1458
+ }
1459
+ } else if (workflow.maas.architecture.startsWith('amd64') && process.arch !== 'x64') {
1460
+ // Building amd64/x86_64 on aarch64 host
1461
+ // Check both /usr/local/bin (compiled) and system paths
1462
+ let qemuX86Path = null;
1463
+
1464
+ if (shellExec('test -x /usr/local/bin/qemu-system-x86_64', { silent: true }).code === 0) {
1465
+ qemuX86Path = '/usr/local/bin/qemu-system-x86_64';
1466
+ } else if (shellExec('which qemu-system-x86_64', { silent: true }).code === 0) {
1467
+ qemuX86Path = shellExec('which qemu-system-x86_64', { silent: true }).stdout.trim();
1468
+ }
1469
+
1470
+ if (!qemuX86Path) {
1471
+ throw new Error(
1472
+ 'qemu-system-x86_64 is not installed. Please install it to build x86_64 images on aarch64 hosts.\n' +
1473
+ 'Run: node bin baremetal --dev --install-packer',
1474
+ );
1475
+ }
1476
+
1477
+ logger.info(`Found qemu-system-x86_64 at: ${qemuX86Path}`);
1478
+
1479
+ // Verify that the installed qemu supports the 'pc' or 'q35' machine type (required for x86_64)
1480
+ const machineHelp = shellExec(`${qemuX86Path} -machine help`, { silent: true }).stdout;
1481
+ if (!machineHelp.includes('pc') && !machineHelp.includes('q35')) {
1482
+ throw new Error(
1483
+ 'The installed qemu-system-x86_64 does not support the "pc" or "q35" machine type.\n' +
1484
+ 'Run: node bin baremetal --dev --install-packer',
1485
+ );
1486
+ }
1487
+ }
1488
+ },
1489
+
1391
1490
  /**
1392
1491
  * @method bootConfFactory
1393
1492
  * @description Generates the boot configuration file for specific workflows,
package/src/cli/index.js CHANGED
@@ -625,6 +625,10 @@ program
625
625
  '--packer-maas-image-upload',
626
626
  'Uploads an existing MAAS image artifact without rebuilding for the workflow specified by --packer-workflow-id.',
627
627
  )
628
+ .option(
629
+ '--packer-maas-image-cached',
630
+ 'Continue last build without removing artifacts (used with --packer-maas-image-build).',
631
+ )
628
632
  .option('--commission', 'Init workflow for commissioning a physical machine.')
629
633
  .option('--nfs-build', 'Builds an NFS root filesystem for a workflow id config architecture using QEMU emulation.')
630
634
  .option('--nfs-mount', 'Mounts the NFS root filesystem for a workflow id config architecture.')
package/src/index.js CHANGED
@@ -36,7 +36,7 @@ class Underpost {
36
36
  * @type {String}
37
37
  * @memberof Underpost
38
38
  */
39
- static version = 'v2.96.0';
39
+ static version = 'v2.96.1';
40
40
  /**
41
41
  * Repository cli API
42
42
  * @static
@@ -193,6 +193,7 @@ export {
193
193
  UnderpostRootEnv,
194
194
  UnderpostFileStorage,
195
195
  UnderpostImage,
196
+ UnderpostStatic,
196
197
  UnderpostLxd,
197
198
  UnderpostMonitor,
198
199
  UnderpostRepository,
@@ -1,10 +0,0 @@
1
- apiVersion: v1
2
- kind: ConfigMap
3
- metadata:
4
- name: mariadb-config
5
- data:
6
- my.cnf: | # bind-address=0.0.0.0
7
- [mysqld]
8
- default_storage_engine=InnoDB
9
- innodb_file_per_table=1
10
- max_connections=1000
@@ -1,8 +0,0 @@
1
- apiVersion: v1
2
- kind: Secret
3
- metadata:
4
- name: mariadb-secret
5
- type: Opaque
6
- data:
7
- password:
8
- username: