underpost 2.95.8 → 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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/baremetal/commission-workflows.json +44 -0
  3. package/baremetal/packer-workflows.json +24 -0
  4. package/cli.md +29 -31
  5. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  6. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  7. package/package.json +1 -1
  8. package/packer/images/Rocky9Amd64/Makefile +62 -0
  9. package/packer/images/Rocky9Amd64/QUICKSTART.md +113 -0
  10. package/packer/images/Rocky9Amd64/README.md +122 -0
  11. package/packer/images/Rocky9Amd64/http/rocky9.ks.pkrtpl.hcl +114 -0
  12. package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +164 -0
  13. package/packer/images/Rocky9Arm64/Makefile +69 -0
  14. package/packer/images/Rocky9Arm64/README.md +122 -0
  15. package/packer/images/Rocky9Arm64/http/rocky9.ks.pkrtpl.hcl +114 -0
  16. package/packer/images/Rocky9Arm64/rocky9.pkr.hcl +171 -0
  17. package/packer/scripts/fuse-nbd +64 -0
  18. package/packer/scripts/fuse-tar-root +63 -0
  19. package/scripts/maas-setup.sh +13 -2
  20. package/scripts/maas-upload-boot-resource.sh +183 -0
  21. package/scripts/packer-init-vars-file.sh +40 -0
  22. package/scripts/packer-setup.sh +289 -0
  23. package/src/cli/baremetal.js +342 -55
  24. package/src/cli/cloud-init.js +1 -1
  25. package/src/cli/env.js +24 -3
  26. package/src/cli/index.js +19 -0
  27. package/src/cli/repository.js +164 -0
  28. package/src/index.js +2 -1
  29. package/manifests/mariadb/config.yaml +0 -10
  30. package/manifests/mariadb/secret.yaml +0 -8
  31. package/src/client/ssr/pages/404.js +0 -12
  32. package/src/client/ssr/pages/500.js +0 -12
  33. package/src/client/ssr/pages/maintenance.js +0 -14
  34. package/src/client/ssr/pages/offline.js +0 -21
@@ -0,0 +1,64 @@
1
+ #!/bin/bash -e
2
+ # vi: ts=4 expandtab
3
+ #
4
+ # Script to mount a qcow2 image using NBD (Network Block Device)
5
+ # This is part of the MAAS image build process
6
+
7
+ OUTPUT_DIR="output-${SOURCE}"
8
+ OUTPUT_QCOW2="${OUTPUT_DIR}/packer-${SOURCE}"
9
+
10
+ if [ ! -f "${OUTPUT_QCOW2}" ]; then
11
+ echo "ERROR: ${OUTPUT_QCOW2} not found"
12
+ exit 1
13
+ fi
14
+
15
+ # Load NBD kernel module if not already loaded
16
+ if ! lsmod | grep -q nbd; then
17
+ modprobe nbd
18
+ fi
19
+
20
+ # Find an available NBD device
21
+ for i in {0..15}; do
22
+ if [ ! -e "/sys/class/block/nbd${i}/pid" ]; then
23
+ NBD_DEV="/dev/nbd${i}"
24
+ break
25
+ fi
26
+ done
27
+
28
+ if [ -z "${NBD_DEV}" ]; then
29
+ echo "ERROR: No available NBD devices found"
30
+ exit 1
31
+ fi
32
+
33
+ # Connect the QCOW2 image to the NBD device
34
+ qemu-nbd -c "${NBD_DEV}" -f qcow2 -r "${OUTPUT_QCOW2}"
35
+
36
+ # Wait for the device to be ready
37
+ sleep 2
38
+ udevadm settle
39
+
40
+ # Find the root partition (usually the largest partition)
41
+ ROOT_PART=""
42
+ MAX_SIZE=0
43
+
44
+ for part in "${NBD_DEV}"p*; do
45
+ if [ -b "${part}" ]; then
46
+ SIZE=$(blockdev --getsize64 "${part}" 2>/dev/null || echo 0)
47
+ if [ "${SIZE}" -gt "${MAX_SIZE}" ]; then
48
+ MAX_SIZE=${SIZE}
49
+ ROOT_PART="${part}"
50
+ fi
51
+ fi
52
+ done
53
+
54
+ if [ -z "${ROOT_PART}" ]; then
55
+ # If no partitions found, try the whole device
56
+ ROOT_PART="${NBD_DEV}"
57
+ fi
58
+
59
+ echo "Using NBD device: ${NBD_DEV}"
60
+ echo "Root partition: ${ROOT_PART}"
61
+
62
+ # Export variables for use in subsequent scripts
63
+ export NBD_DEV
64
+ export ROOT_PART
@@ -0,0 +1,63 @@
1
+ #!/bin/bash -e
2
+ # vi: ts=4 expandtab
3
+ #
4
+ # Script to extract root filesystem from NBD device to tarball for MAAS
5
+ # This is part of the MAAS image build process
6
+
7
+ if [ -z "${NBD_DEV}" ]; then
8
+ echo "ERROR: NBD_DEV not set. Run fuse-nbd first."
9
+ exit 1
10
+ fi
11
+
12
+ if [ -z "${ROOT_PART}" ]; then
13
+ echo "ERROR: ROOT_PART not set. Run fuse-nbd first."
14
+ exit 1
15
+ fi
16
+
17
+ if [ -z "${OUTPUT}" ]; then
18
+ echo "ERROR: OUTPUT not set."
19
+ exit 1
20
+ fi
21
+
22
+ # Create temporary mount point
23
+ MOUNT_POINT=$(mktemp -d /tmp/packer-maas-mount.XXXXXX)
24
+
25
+ cleanup() {
26
+ echo "Cleaning up..."
27
+ if mountpoint -q "${MOUNT_POINT}"; then
28
+ umount "${MOUNT_POINT}" || true
29
+ fi
30
+ if [ -n "${NBD_DEV}" ] && [ -b "${NBD_DEV}" ]; then
31
+ qemu-nbd -d "${NBD_DEV}" || true
32
+ fi
33
+ if [ -d "${MOUNT_POINT}" ]; then
34
+ rmdir "${MOUNT_POINT}" || true
35
+ fi
36
+ }
37
+
38
+ trap cleanup EXIT
39
+
40
+ # Mount the root partition
41
+ echo "Mounting ${ROOT_PART} to ${MOUNT_POINT}..."
42
+ mount -o ro "${ROOT_PART}" "${MOUNT_POINT}"
43
+
44
+ # Create tarball from the mounted filesystem
45
+ echo "Creating tarball ${OUTPUT}..."
46
+ tar -czf "${OUTPUT}" -C "${MOUNT_POINT}" \
47
+ --exclude='./dev/*' \
48
+ --exclude='./proc/*' \
49
+ --exclude='./sys/*' \
50
+ --exclude='./tmp/*' \
51
+ --exclude='./run/*' \
52
+ --exclude='./mnt/*' \
53
+ --exclude='./media/*' \
54
+ --exclude='./lost+found' \
55
+ --exclude='./boot/efi/*' \
56
+ --numeric-owner \
57
+ .
58
+
59
+ echo "Tarball created successfully: ${OUTPUT}"
60
+ echo "Size: $(du -h "${OUTPUT}" | cut -f1)"
61
+
62
+ # Cleanup will be called by trap
63
+ exit 0
@@ -99,7 +99,18 @@ echo "Configuring DHCP for fabric-1 (untagged VLAN)..."
99
99
  SUBNET_CIDR="192.168.1.0/24"
100
100
  SUBNET_ID=$(maas "$MAAS_ADMIN_USERNAME" subnets read | jq -r '.[] | select(.cidr == "'"$SUBNET_CIDR"'") | .id')
101
101
  FABRIC_ID=$(maas "$MAAS_ADMIN_USERNAME" fabrics read | jq -r '.[] | select(.name == "fabric-1") | .id')
102
- RACK_CONTROLLER_ID=$(maas "$MAAS_ADMIN_USERNAME" rack-controllers read | jq -r '.[] | select(.ip_addresses[] == "'"$IP_ADDRESS"'") | .system_id')
102
+ RACK_CONTROLLER_ID=$(maas "$MAAS_ADMIN_USERNAME" rack-controllers read | jq -r '[.[] | select(.ip_addresses[] == "'"$IP_ADDRESS"'") | .system_id] | .[0]')
103
+
104
+ if [ -z "$RACK_CONTROLLER_ID" ] || [ "$RACK_CONTROLLER_ID" == "null" ]; then
105
+ echo "Warning: Could not find Rack Controller by IP $IP_ADDRESS. Attempting to use the first available Rack Controller..."
106
+ RACK_CONTROLLER_ID=$(maas "$MAAS_ADMIN_USERNAME" rack-controllers read | jq -r '.[0].system_id')
107
+ fi
108
+
109
+ if [ -z "$RACK_CONTROLLER_ID" ] || [ "$RACK_CONTROLLER_ID" == "null" ]; then
110
+ echo "Error: Could not find any Rack Controller."
111
+ exit 1
112
+ fi
113
+
103
114
  START_IP="192.168.1.191"
104
115
  END_IP="192.168.1.254"
105
116
 
@@ -110,7 +121,7 @@ fi
110
121
 
111
122
  # Create a Dynamic IP Range for enlistment, commissioning, and deployment
112
123
  echo "Creating dynamic IP range from $START_IP to $END_IP..."
113
- maas "$MAAS_ADMIN_USERNAME" ipranges create type=dynamic start_ip="$START_IP" end_ip="$END_IP"
124
+ maas "$MAAS_ADMIN_USERNAME" ipranges create type=dynamic start_ip="$START_IP" end_ip="$END_IP" || echo "Dynamic IP range likely already exists or conflicts. Proceeding..."
114
125
 
115
126
  # Enable DHCP on the untagged VLAN (VLAN tag 0)
116
127
  echo "Enabling DHCP on VLAN 0 for fabric-1 (ID: $FABRIC_ID)..."
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env bash
2
+ # Script to upload boot resources to MAAS using the REST API with OAuth
3
+ # Usage: ./maas-upload-boot-resource.sh <profile> <name> <title> <architecture> <base_image> <filetype> <file_path>
4
+
5
+ set -euo pipefail
6
+
7
+ if [ $# -lt 7 ]; then
8
+ echo "Usage: $0 <profile> <name> <title> <architecture> <base_image> <filetype> <file_path>"
9
+ echo ""
10
+ echo "Example:"
11
+ echo " $0 maas custom/rocky9 'Rocky 9 Custom' amd64/generic rhel/9 tgz rocky9.tar.gz"
12
+ exit 1
13
+ fi
14
+
15
+ PROFILE="$1"
16
+ NAME="$2"
17
+ TITLE="$3"
18
+ ARCHITECTURE="$4"
19
+ BASE_IMAGE="$5"
20
+ FILETYPE="$6"
21
+ FILE_PATH="$7"
22
+
23
+ # Verify file exists
24
+ if [ ! -f "$FILE_PATH" ]; then
25
+ echo "Error: File not found: $FILE_PATH"
26
+ exit 1
27
+ fi
28
+
29
+ # Verify jq is installed
30
+ if ! command -v jq &> /dev/null; then
31
+ echo "Error: jq is required for this script but not installed."
32
+ exit 1
33
+ fi
34
+
35
+ # Get MAAS API URL and credentials from profile
36
+ MAAS_INFO=$(maas list | grep "^${PROFILE}" || true)
37
+ if [ -z "$MAAS_INFO" ]; then
38
+ echo "Error: MAAS profile '${PROFILE}' not found"
39
+ echo "Available profiles:"
40
+ maas list
41
+ exit 1
42
+ fi
43
+
44
+ API_URL=$(echo "$MAAS_INFO" | awk '{print $2}')
45
+ API_KEY=$(echo "$MAAS_INFO" | awk '{print $3}')
46
+
47
+ # Parse OAuth credentials
48
+ CONSUMER_KEY=$(echo "$API_KEY" | cut -d: -f1)
49
+ TOKEN_KEY=$(echo "$API_KEY" | cut -d: -f2)
50
+ TOKEN_SECRET=$(echo "$API_KEY" | cut -d: -f3)
51
+
52
+ # Calculate file hash and size
53
+ echo "Calculating SHA256 checksum..."
54
+ SHA256=$(sha256sum "$FILE_PATH" | awk '{print $1}')
55
+ SIZE=$(stat -c%s "$FILE_PATH")
56
+
57
+ echo "File: $FILE_PATH"
58
+ echo "Size: $SIZE bytes ($(numfmt --to=iec-i --suffix=B $SIZE))"
59
+ echo "SHA256: $SHA256"
60
+ echo ""
61
+
62
+ # Endpoint for boot resources
63
+ ENDPOINT="${API_URL}boot-resources/"
64
+
65
+ # Generate OAuth timestamp and nonce
66
+ TIMESTAMP=$(date +%s)
67
+ NONCE=$(openssl rand -hex 16)
68
+
69
+ # OAuth parameters
70
+ OAUTH_VERSION="1.0"
71
+ OAUTH_SIGNATURE_METHOD="PLAINTEXT"
72
+ OAUTH_SIGNATURE="&${TOKEN_SECRET}"
73
+
74
+ echo "Initiating upload to MAAS..."
75
+ echo "API URL: $ENDPOINT"
76
+ echo "Name: $NAME"
77
+ echo "Title: $TITLE"
78
+ echo "Architecture: $ARCHITECTURE"
79
+ echo "Base Image: $BASE_IMAGE"
80
+ echo "Filetype: $FILETYPE"
81
+ echo ""
82
+
83
+ # 1. Initiate Upload (POST metadata)
84
+ # We do NOT send the content here, just the metadata to create the resource and get the upload URI.
85
+ RESPONSE=$(curl -s -X POST "${ENDPOINT}" \
86
+ -H "Authorization: OAuth oauth_version=\"${OAUTH_VERSION}\", oauth_signature_method=\"${OAUTH_SIGNATURE_METHOD}\", oauth_consumer_key=\"${CONSUMER_KEY}\", oauth_token=\"${TOKEN_KEY}\", oauth_signature=\"${OAUTH_SIGNATURE}\", oauth_timestamp=\"${TIMESTAMP}\", oauth_nonce=\"${NONCE}\"" \
87
+ -F "name=${NAME}" \
88
+ -F "title=${TITLE}" \
89
+ -F "architecture=${ARCHITECTURE}" \
90
+ -F "base_image=${BASE_IMAGE}" \
91
+ -F "filetype=${FILETYPE}" \
92
+ -F "sha256=${SHA256}" \
93
+ -F "size=${SIZE}")
94
+ CURL_RET=$?
95
+
96
+ if [ $CURL_RET -ne 0 ]; then
97
+ echo "Error: curl failed with exit code $CURL_RET"
98
+ exit 1
99
+ fi
100
+
101
+ # Validate JSON before parsing
102
+ if ! echo "$RESPONSE" | jq . >/dev/null 2>&1; then
103
+ echo "Error: MAAS returned invalid JSON."
104
+ echo "Raw response:"
105
+ echo "$RESPONSE"
106
+ exit 1
107
+ fi
108
+
109
+ # Extract Upload URI
110
+ UPLOAD_URI=$(echo "$RESPONSE" | jq -r '.sets | to_entries | sort_by(.key) | reverse | .[0].value.files | to_entries | .[0].value.upload_uri // empty')
111
+
112
+ if [ -z "$UPLOAD_URI" ]; then
113
+ echo "✗ Failed to get upload URI from MAAS response."
114
+ echo "Response:"
115
+ echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
116
+ exit 1
117
+ fi
118
+
119
+ echo "Upload URI obtained: $UPLOAD_URI"
120
+
121
+ # Construct the full upload URL
122
+ if [[ "$UPLOAD_URI" == http* ]]; then
123
+ FULL_UPLOAD_URL="$UPLOAD_URI"
124
+ else
125
+ # Extract scheme and authority from API_URL
126
+ # e.g. http://192.168.1.5:5240/MAAS/api/2.0/ -> http://192.168.1.5:5240
127
+ MAAS_ROOT=$(echo "$API_URL" | sed -E 's|^(https?://[^/]+).*|\1|')
128
+
129
+ # Ensure UPLOAD_URI starts with /
130
+ [[ "$UPLOAD_URI" != /* ]] && UPLOAD_URI="/$UPLOAD_URI"
131
+
132
+ FULL_UPLOAD_URL="${MAAS_ROOT}${UPLOAD_URI}"
133
+ fi
134
+
135
+ echo "Full Upload URL: $FULL_UPLOAD_URL"
136
+
137
+ # 2. Split file into chunks
138
+ CHUNK_SIZE=$((4 * 1024 * 1024)) # 4MB
139
+ TMP_DIR=$(mktemp -d)
140
+ echo "Splitting file into 4MB chunks in $TMP_DIR..."
141
+ split -b ${CHUNK_SIZE} "${FILE_PATH}" "${TMP_DIR}/chunk_"
142
+
143
+ # 3. Upload chunks
144
+ echo "Starting chunked upload..."
145
+ CHUNK_COUNT=$(ls "${TMP_DIR}"/chunk_* | wc -l)
146
+ CURRENT_CHUNK=0
147
+
148
+ for chunk in "${TMP_DIR}"/chunk_*; do
149
+ CURRENT_CHUNK=$((CURRENT_CHUNK + 1))
150
+ CHUNK_SIZE_BYTES=$(stat -c%s "$chunk")
151
+
152
+ # Progress indicator
153
+ echo -ne "Uploading chunk $CURRENT_CHUNK of $CHUNK_COUNT ($CHUNK_SIZE_BYTES bytes)...\r"
154
+
155
+ # Generate new nonce/timestamp for each request
156
+ TIMESTAMP=$(date +%s)
157
+ NONCE=$(openssl rand -hex 16)
158
+
159
+ # Upload chunk
160
+ CHUNK_RESPONSE=$(curl -s -X PUT "${FULL_UPLOAD_URL}" \
161
+ -H "Content-Type: application/octet-stream" \
162
+ -H "Content-Length: ${CHUNK_SIZE_BYTES}" \
163
+ -H "Authorization: OAuth oauth_version=\"${OAUTH_VERSION}\", oauth_signature_method=\"${OAUTH_SIGNATURE_METHOD}\", oauth_consumer_key=\"${CONSUMER_KEY}\", oauth_token=\"${TOKEN_KEY}\", oauth_signature=\"${OAUTH_SIGNATURE}\", oauth_timestamp=\"${TIMESTAMP}\", oauth_nonce=\"${NONCE}\"" \
164
+ --data-binary @"${chunk}" \
165
+ -w "%{http_code}")
166
+
167
+ HTTP_CODE="${CHUNK_RESPONSE: -3}"
168
+
169
+ if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
170
+ echo ""
171
+ echo "✗ Chunk upload failed with status: $HTTP_CODE"
172
+ echo "Response: ${CHUNK_RESPONSE::-3}"
173
+ rm -r "${TMP_DIR}"
174
+ exit 1
175
+ fi
176
+
177
+ rm "$chunk"
178
+ done
179
+
180
+ echo ""
181
+ echo "✓ Upload complete!"
182
+ rm -r "${TMP_DIR}"
183
+ exit 0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # Script to Initialize VARS files for Packer builds
3
+ # Usage: sudo ./scripts/packer-setup.sh
4
+
5
+ set -euo pipefail
6
+
7
+ # Packer needs writable VARS files for UEFI boot
8
+ PACKER_DIR="$(dirname "$0")/../packer/images"
9
+
10
+ # Find all packer image directories and create VARS files
11
+ for image_dir in "$PACKER_DIR"/*; do
12
+ if [ -d "$image_dir" ]; then
13
+ image_name=$(basename "$image_dir")
14
+ echo "Checking UEFI VARS files for $image_name..."
15
+
16
+ # Create x86_64 VARS file if it doesn't exist
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
25
+ fi
26
+
27
+ # Create aarch64 VARS file if it doesn't exist
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
36
+ fi
37
+ fi
38
+ done
39
+
40
+ echo "Packer and QEMU setup complete!"
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env bash
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
7
+
8
+ set -euo pipefail
9
+
10
+ print(){ printf "[setup] %s\n" "$*"; }
11
+ err(){ printf "[setup] ERROR: %s\n" "$*" >&2; }
12
+
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
75
+
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)
81
+
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
86
+
87
+ print "Enabling and starting libvirtd"
88
+ systemctl enable --now libvirtd || err "Failed to enable/start libvirtd"
89
+ systemctl status libvirtd --no-pager || true
90
+
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
97
+
98
+ # 4) Install UEFI firmware for x86_64 and ARM64
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
104
+
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)"
109
+ fi
110
+
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."
132
+ fi
133
+
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
230
+ fi
231
+
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"
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