@underpostnet/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.
- package/README.md +2 -2
- package/baremetal/commission-workflows.json +44 -0
- package/baremetal/packer-workflows.json +24 -0
- package/cli.md +29 -31
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/packer/images/Rocky9Amd64/Makefile +62 -0
- package/packer/images/Rocky9Amd64/QUICKSTART.md +113 -0
- package/packer/images/Rocky9Amd64/README.md +122 -0
- package/packer/images/Rocky9Amd64/http/rocky9.ks.pkrtpl.hcl +114 -0
- package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +164 -0
- package/packer/images/Rocky9Arm64/Makefile +69 -0
- package/packer/images/Rocky9Arm64/README.md +122 -0
- package/packer/images/Rocky9Arm64/http/rocky9.ks.pkrtpl.hcl +114 -0
- package/packer/images/Rocky9Arm64/rocky9.pkr.hcl +171 -0
- package/packer/scripts/fuse-nbd +64 -0
- package/packer/scripts/fuse-tar-root +63 -0
- package/scripts/maas-setup.sh +13 -2
- package/scripts/maas-upload-boot-resource.sh +183 -0
- package/scripts/packer-init-vars-file.sh +40 -0
- package/scripts/packer-setup.sh +289 -0
- package/src/cli/baremetal.js +342 -55
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/env.js +24 -3
- package/src/cli/index.js +19 -0
- package/src/cli/repository.js +164 -0
- package/src/index.js +2 -1
- package/manifests/mariadb/config.yaml +0 -10
- package/manifests/mariadb/secret.yaml +0 -8
- package/src/client/ssr/pages/404.js +0 -12
- package/src/client/ssr/pages/500.js +0 -12
- package/src/client/ssr/pages/maintenance.js +0 -14
- 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
|
package/scripts/maas-setup.sh
CHANGED
|
@@ -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
|