@underpostnet/underpost 2.95.8 → 2.96.0
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 +13 -0
- package/cli.md +28 -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 +160 -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 +30 -0
- package/scripts/packer-setup.sh +52 -0
- package/src/cli/baremetal.js +243 -55
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/env.js +24 -3
- package/src/cli/index.js +15 -0
- package/src/cli/repository.js +164 -0
- package/src/index.js +1 -1
- 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,160 @@
|
|
|
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 = "amd64"
|
|
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, "x86_64")
|
|
65
|
+
|
|
66
|
+
qemu_arch = {
|
|
67
|
+
"amd64" = "x86_64"
|
|
68
|
+
"x86_64" = "x86_64"
|
|
69
|
+
"arm64" = "aarch64"
|
|
70
|
+
"aarch64" = "aarch64"
|
|
71
|
+
}
|
|
72
|
+
uefi_imp = {
|
|
73
|
+
"amd64" = "OVMF"
|
|
74
|
+
"x86_64" = "OVMF"
|
|
75
|
+
"arm64" = "AAVMF"
|
|
76
|
+
"aarch64" = "AAVMF"
|
|
77
|
+
}
|
|
78
|
+
uefi_sfx = {
|
|
79
|
+
"amd64" = "${var.ovmf_suffix}"
|
|
80
|
+
"x86_64" = "${var.ovmf_suffix}"
|
|
81
|
+
"arm64" = ""
|
|
82
|
+
"aarch64" = ""
|
|
83
|
+
}
|
|
84
|
+
qemu_machine = {
|
|
85
|
+
"amd64" = "accel=kvm"
|
|
86
|
+
"x86_64" = "accel=kvm"
|
|
87
|
+
"arm64" = var.host_is_arm ? "virt,accel=kvm" : "virt"
|
|
88
|
+
"aarch64" = var.host_is_arm ? "virt,accel=kvm" : "virt"
|
|
89
|
+
}
|
|
90
|
+
qemu_cpu = {
|
|
91
|
+
"amd64" = "host"
|
|
92
|
+
"x86_64" = "host"
|
|
93
|
+
"arm64" = var.host_is_arm ? "host" : "max"
|
|
94
|
+
"aarch64" = var.host_is_arm ? "host" : "max"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ks_proxy = var.ks_proxy != "" ? "--proxy=${var.ks_proxy}" : ""
|
|
98
|
+
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'"
|
|
99
|
+
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'"
|
|
100
|
+
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'"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
source "qemu" "rocky9" {
|
|
104
|
+
boot_command = ["<up><wait>", "e", "<down><down><down><left>", " console=ttyS0 inst.cmdline inst.text inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/rocky9.ks <f10>"]
|
|
105
|
+
boot_wait = "5s"
|
|
106
|
+
communicator = "none"
|
|
107
|
+
disk_size = "45G"
|
|
108
|
+
format = "qcow2"
|
|
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"
|
|
112
|
+
iso_target_path = "packer_cache/Rocky-9-latest-${local.iso_arch}-boot.iso"
|
|
113
|
+
memory = 2048
|
|
114
|
+
cores = 4
|
|
115
|
+
qemu_binary = "qemu-system-${lookup(local.qemu_arch, var.architecture, "")}"
|
|
116
|
+
qemuargs = [
|
|
117
|
+
["-serial", "stdio"],
|
|
118
|
+
["-boot", "strict=off"],
|
|
119
|
+
["-device", "qemu-xhci"],
|
|
120
|
+
["-device", "usb-kbd"],
|
|
121
|
+
["-device", "virtio-net-pci,netdev=net0"],
|
|
122
|
+
["-netdev", "user,id=net0"],
|
|
123
|
+
["-device", "virtio-blk-pci,drive=drive0,bootindex=0"],
|
|
124
|
+
["-device", "virtio-blk-pci,drive=cdrom0,bootindex=1"],
|
|
125
|
+
["-machine", "${lookup(local.qemu_machine, var.architecture, "")}"],
|
|
126
|
+
["-cpu", "${lookup(local.qemu_cpu, var.architecture, "")}"],
|
|
127
|
+
["-device", "virtio-gpu-pci"],
|
|
128
|
+
["-global", "driver=cfi.pflash01,property=secure,value=off"],
|
|
129
|
+
["-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"],
|
|
130
|
+
["-drive", "if=pflash,format=raw,unit=1,id=ovmf_vars,file=${local.iso_arch}_VARS.fd"],
|
|
131
|
+
["-drive", "file=output-rocky9/packer-rocky9,if=none,id=drive0,cache=writeback,discard=ignore,format=qcow2"],
|
|
132
|
+
["-drive", "file=packer_cache/Rocky-9-latest-${local.iso_arch}-boot.iso,if=none,id=cdrom0,media=cdrom"]
|
|
133
|
+
]
|
|
134
|
+
shutdown_timeout = var.timeout
|
|
135
|
+
http_content = {
|
|
136
|
+
"/rocky9.ks" = templatefile("${path.root}/http/rocky9.ks.pkrtpl.hcl",
|
|
137
|
+
{
|
|
138
|
+
KS_PROXY = local.ks_proxy,
|
|
139
|
+
KS_OS_REPOS = local.ks_os_repos,
|
|
140
|
+
KS_APPSTREAM_REPOS = local.ks_appstream_repos,
|
|
141
|
+
KS_EXTRAS_REPOS = local.ks_extras_repos
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
build {
|
|
148
|
+
sources = ["source.qemu.rocky9"]
|
|
149
|
+
|
|
150
|
+
post-processor "shell-local" {
|
|
151
|
+
inline = [
|
|
152
|
+
"SOURCE=${source.name}",
|
|
153
|
+
"OUTPUT=${var.filename}",
|
|
154
|
+
"source ../../scripts/fuse-nbd",
|
|
155
|
+
"source ../../scripts/fuse-tar-root",
|
|
156
|
+
"rm -rf output-${source.name}",
|
|
157
|
+
]
|
|
158
|
+
inline_shebang = "/bin/bash -e"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -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,30 @@
|
|
|
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 /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"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# 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"
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
echo "Packer and QEMU setup complete!"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Script to install Packer and libvirt/qemu tooling on Rocky Linux.
|
|
3
|
+
# Usage: sudo ./scripts/packer-setup.sh
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
|
|
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
|
|
10
|
+
|
|
11
|
+
# Refresh dnf metadata
|
|
12
|
+
sudo dnf clean all
|
|
13
|
+
sudo dnf makecache --refresh
|
|
14
|
+
|
|
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)"
|
|
17
|
+
|
|
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
|
|
22
|
+
|
|
23
|
+
# 3a) Install NBD and filesystem tools required for MAAS image creation
|
|
24
|
+
sudo dnf install -y libnbd nbdkit e2fsprogs kmod-kvdo kmod
|
|
25
|
+
|
|
26
|
+
# 4) Install UEFI firmware for x86_64 and ARM64
|
|
27
|
+
sudo dnf install -y edk2-ovmf edk2-aarch64
|
|
28
|
+
|
|
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
|
|
34
|
+
fi
|
|
35
|
+
|
|
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
|
|
39
|
+
fi
|
|
40
|
+
|
|
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
|
|
46
|
+
fi
|
|
47
|
+
|
|
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
|
|
52
|
+
fi
|