tests: validate installer ISO on XCP-ng
This commit is contained in:
@@ -24,6 +24,8 @@ Fruix currently has:
|
||||
- `fruix system install`
|
||||
- a bootable Fruix-managed installer environment:
|
||||
- `fruix system installer`
|
||||
- a bootable Fruix-managed installer ISO:
|
||||
- `fruix system installer-iso`
|
||||
|
||||
Validated boot modes still are:
|
||||
|
||||
@@ -36,45 +38,44 @@ The validated Phase 18 installation work currently uses:
|
||||
|
||||
## Latest completed achievement
|
||||
|
||||
### 2026-04-04 — Phase 18.2 completed
|
||||
### 2026-04-04 — Phase 18.3 completed
|
||||
|
||||
Fruix now boots a minimal installer environment and installs a target system from inside it.
|
||||
Fruix now builds a bootable installer ISO, boots it, installs from it, and boots the installed target successfully.
|
||||
|
||||
Highlights:
|
||||
|
||||
- added in `modules/fruix/system/freebsd.scm`:
|
||||
- `installer-operating-system`
|
||||
- `operating-system-installer-image-spec`
|
||||
- `materialize-installer-image`
|
||||
- `operating-system-installer-iso-spec`
|
||||
- `materialize-installer-iso`
|
||||
- added CLI support in `scripts/fruix.scm`:
|
||||
- `fruix system installer`
|
||||
- `--install-target-device DEVICE`
|
||||
- the installer image now carries:
|
||||
- its own installer closure
|
||||
- `fruix system installer-iso`
|
||||
- the installer ISO now carries:
|
||||
- a UEFI El Torito boot image
|
||||
- `/boot/root.img` as the installer mdroot payload
|
||||
- the installer closure
|
||||
- the selected target closure
|
||||
- the target store closure
|
||||
- a staged target rootfs payload
|
||||
- the target runtime store closure needed for installation
|
||||
- in-guest installer state/log/scripts
|
||||
- validated workflow:
|
||||
- boot installer image in QEMU/UEFI/TCG
|
||||
- reach installer over SSH
|
||||
- install target system onto second disk from inside the guest
|
||||
- boot the installed target successfully
|
||||
- validated workflows:
|
||||
- local QEMU/UEFI/TCG boot, install, and installed-target reboot
|
||||
- real XCP-ng VM boot, install, and installed-target reboot
|
||||
- a platform-specific installer detail is now recorded in-tree:
|
||||
- QEMU ISO path installs onto `/dev/vtbd0`
|
||||
- XCP-ng ISO path installs onto `/dev/ada0`
|
||||
|
||||
Validation:
|
||||
|
||||
- `PASS phase18-installer-environment`
|
||||
- regression re-checks:
|
||||
- `PASS phase18-system-install`
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
- `PASS phase18-installer-iso`
|
||||
- `PASS phase18-installer-iso-xcpng`
|
||||
|
||||
Report:
|
||||
|
||||
- `docs/reports/phase18-installer-environment-freebsd.md`
|
||||
- `docs/reports/phase18-installer-iso-freebsd.md`
|
||||
|
||||
Commit:
|
||||
Commits:
|
||||
|
||||
- `1d00907` — `Add Fruix bootable installer environment`
|
||||
- `1970c5c` — `system: add UEFI installer ISO builder`
|
||||
- `604ad82` — `system: validate UEFI installer ISO boot path`
|
||||
|
||||
## Recent major milestones
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ installer_iso_install=ok
|
||||
installed_target_boot=ok
|
||||
```
|
||||
|
||||
Notable ISO-specific validation detail:
|
||||
Notable QEMU-specific ISO validation detail:
|
||||
|
||||
- unlike the disk-image-style installer environment from Phase 18.2, the ISO boots from `cd0`, so the target virtio disk appears as:
|
||||
- `/dev/vtbd0`
|
||||
@@ -194,6 +194,75 @@ The harness verified all of the following:
|
||||
- `sshd` is running
|
||||
- activation completed successfully
|
||||
|
||||
### Real XCP-ng validation
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase18-installer-iso-xcpng.sh`
|
||||
|
||||
This harness validates the same installer-iso workflow on the approved real XCP-ng path:
|
||||
|
||||
- VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||
- ISO SR: `537a6219-8452-7cb5-8d56-5eed6910c7a2`
|
||||
- target VDIs:
|
||||
- `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||
- `7061d761-3639-4bec-87f7-2ba1af924eaa`
|
||||
|
||||
Because the current `xo-cli disk.import @=/path/to.iso` path returned an HTTP 500 error in this environment, the harness imports the ISO into the ISO SR via a temporary local HTTP URL, then inserts the resulting ISO VDI into the VM's CD drive.
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase18-installer-iso-xcpng`
|
||||
|
||||
Validated result summary:
|
||||
|
||||
```text
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
iso_id=<temporary-imported-iso-vdi>
|
||||
guest_ip=192.168.213.62
|
||||
installer_state=done
|
||||
installer_target_device=/dev/ada0
|
||||
kern_disks=cd0 ada1 ada0
|
||||
installer_run_current_system=/frx/store/16969e825dbb65b5c27180030d4a7d98821893460fb3dccdc863ff6156ed61e0-fruix-system-fruix-freebsd-installer
|
||||
installer_sshd_status=running
|
||||
target_run_current_system=/frx/store/a98d3af6a1afbc4a927d47cea6458d5a70747b051ed994e5d9ff1ae79c4f2b42-fruix-system-fruix-freebsd
|
||||
target_sshd_status=running
|
||||
target_shepherd_status=running
|
||||
```
|
||||
|
||||
Important XCP-ng-specific details:
|
||||
|
||||
- the installer ISO still boots from:
|
||||
- `cd0`
|
||||
- on this Xen HVM path, the primary target disk is exposed through Xen block front as `xbd0` and appears to FreeBSD as:
|
||||
- `/dev/ada0`
|
||||
- therefore the XCP-ng installer-iso path must target:
|
||||
- `/dev/ada0`
|
||||
rather than QEMU's:
|
||||
- `/dev/vtbd0`
|
||||
- the visible EFI console can appear to stop at:
|
||||
- `console vidconsole is unavailable`
|
||||
but boot still continues and the installer becomes reachable over SSH; that message was not the actual failure mode on XCP-ng
|
||||
|
||||
The harness verified all of the following on the real VM path:
|
||||
|
||||
1. `fruix system installer-iso` builds a bootable ISO with `--install-target-device /dev/ada0`
|
||||
2. the ISO can be imported into the operator-approved ISO SR and attached to the approved VM
|
||||
3. the VM boots the Fruix installer ISO successfully under UEFI
|
||||
4. the installer environment becomes reachable over SSH
|
||||
5. inside the installer guest:
|
||||
- `kern.disks` includes `cd0` and `ada0`
|
||||
- `/run/current-system` points at the installer closure
|
||||
- the installer reaches `state=done`
|
||||
6. the installed target on `ada0` is partitioned and formatted correctly
|
||||
7. after ejecting the ISO and rebooting, the installed target boots successfully on the same XCP-ng VM
|
||||
8. after target boot:
|
||||
- `/run/current-system` points at the target closure
|
||||
- shepherd is running
|
||||
- `sshd` is running
|
||||
- activation completed successfully
|
||||
- `/var/lib/fruix/install.scm` still records the materialized source store provenance
|
||||
|
||||
## Result
|
||||
|
||||
Phase 18.3 is complete.
|
||||
@@ -202,4 +271,7 @@ Fruix now has a validated bootable UEFI installer ISO on FreeBSD that can:
|
||||
|
||||
- boot into a Fruix-managed installer environment from ISO media
|
||||
- perform the non-interactive installation flow onto a target disk
|
||||
- and boot the installed target successfully
|
||||
- boot the installed target successfully
|
||||
- and do so on both:
|
||||
- local `QEMU/UEFI/TCG`
|
||||
- the approved real `XCP-ng` VM path
|
||||
|
||||
455
tests/system/run-phase18-installer-iso-xcpng.sh
Executable file
455
tests/system/run-phase18-installer-iso-xcpng.sh
Executable file
@@ -0,0 +1,455 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
fruix_cmd=$repo_root/bin/fruix
|
||||
vm_id=${VM_ID:-90490f2e-e8fc-4b7a-388e-5c26f0157289}
|
||||
iso_sr_id=${ISO_SR_ID:-537a6219-8452-7cb5-8d56-5eed6910c7a2}
|
||||
os_template=${OS_TEMPLATE:-$script_dir/phase18-installer-target-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase18-target-operating-system}
|
||||
store_dir=${STORE_DIR:-/frx/store}
|
||||
install_target_device=${INSTALL_TARGET_DEVICE:-/dev/ada0}
|
||||
installer_root_size=${INSTALLER_ROOT_SIZE:-}
|
||||
base_name=${BASE_NAME:-phase18-installer-iso-target}
|
||||
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-installer-iso-target}
|
||||
base_release=${BASE_RELEASE:-15.0-STABLE}
|
||||
base_branch=${BASE_BRANCH:-stable/15}
|
||||
source_name=${SOURCE_NAME:-stable15-installer-iso-target-source}
|
||||
source_ref=${SOURCE_REF:-stable/15}
|
||||
source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
|
||||
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-installer-iso-target}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
|
||||
root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519}
|
||||
iso_http_port=${ISO_HTTP_PORT:-$(jot -r 1 18080 18999)}
|
||||
keep_imported_iso=${KEEP_IMPORTED_ISO:-0}
|
||||
|
||||
[ -x "$fruix_cmd" ] || {
|
||||
echo "fruix command is not executable: $fruix_cmd" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$os_template" ] || {
|
||||
echo "missing operating-system template: $os_template" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$root_authorized_key_file" ] || {
|
||||
echo "missing root authorized key file: $root_authorized_key_file" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$root_ssh_private_key_file" ] || {
|
||||
echo "missing root SSH private key file: $root_ssh_private_key_file" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v node >/dev/null 2>&1 || {
|
||||
echo "node is required to serve the ISO for XO import" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v xo-cli >/dev/null 2>&1 || {
|
||||
echo "xo-cli is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
echo "jq is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase18-installer-iso-xcpng.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
target_os_file=$workdir/phase18-installer-iso-target-operating-system.scm
|
||||
installer_out=$workdir/installer-iso.txt
|
||||
metadata_file=$workdir/phase18-installer-iso-xcpng-metadata.txt
|
||||
vm_info_json=$workdir/vm-info.json
|
||||
vdi_info_json=$workdir/vdi-info.json
|
||||
installer_log_file=$workdir/installer.log
|
||||
installer_gpart_file=$workdir/installer-gpart.txt
|
||||
installer_kern_disks_file=$workdir/installer-kern-disks.txt
|
||||
target_install_metadata_file=$workdir/target-install.scm
|
||||
server_script=$workdir/serve-iso.mjs
|
||||
server_log=$workdir/serve-iso.log
|
||||
arp_scan_log=$workdir/arp-scan.log
|
||||
|
||||
server_pid=
|
||||
imported_iso_id=
|
||||
imported_iso_name=
|
||||
guest_ip=
|
||||
vm_mac=
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ -n "$server_pid" ]; then
|
||||
kill "$server_pid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
xo-cli vm.ejectCd id=$vm_id >/dev/null 2>&1 || true
|
||||
if [ -n "$imported_iso_id" ] && [ "$keep_imported_iso" -ne 1 ]; then
|
||||
xo-cli vdi.delete id=$imported_iso_id >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||
sed \
|
||||
-e "s|__BASE_NAME__|$base_name|g" \
|
||||
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
|
||||
-e "s|__BASE_RELEASE__|$base_release|g" \
|
||||
-e "s|__BASE_BRANCH__|$base_branch|g" \
|
||||
-e "s|__SOURCE_NAME__|$source_name|g" \
|
||||
-e "s|__SOURCE_REF__|$source_ref|g" \
|
||||
-e "s|__SOURCE_COMMIT__|$source_commit|g" \
|
||||
-e "s|__DECLARED_SOURCE_ROOT__|$declared_source_root|g" \
|
||||
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
|
||||
"$os_template" > "$target_os_file"
|
||||
|
||||
action_env() {
|
||||
sudo env \
|
||||
HOME="$HOME" \
|
||||
GUILE_AUTO_COMPILE=0 \
|
||||
FRUIX_FREEBSD_BUILD_JOBS="${FRUIX_FREEBSD_BUILD_JOBS:-8}" \
|
||||
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
|
||||
GUILE_BIN="${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}" \
|
||||
GUILE_EXTRA_PREFIX="${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}" \
|
||||
SHEPHERD_PREFIX="${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
if [ -n "$installer_root_size" ]; then
|
||||
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
|
||||
--system "$system_name" \
|
||||
--store "$store_dir" \
|
||||
--install-target-device "$install_target_device" \
|
||||
--root-size "$installer_root_size" >"$installer_out"
|
||||
else
|
||||
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
|
||||
--system "$system_name" \
|
||||
--store "$store_dir" \
|
||||
--install-target-device "$install_target_device" >"$installer_out"
|
||||
fi
|
||||
|
||||
field() {
|
||||
sed -n "s/^$1=//p" "$installer_out" | tail -n 1
|
||||
}
|
||||
|
||||
iso_store_path=$(field iso_store_path)
|
||||
installer_iso_image=$(field iso_image)
|
||||
installer_boot_efi_image=$(field boot_efi_image)
|
||||
installer_root_image=$(field root_image)
|
||||
installer_closure_path=$(field installer_closure_path)
|
||||
target_closure_path=$(field target_closure_path)
|
||||
installer_host_name=$(field installer_host_name)
|
||||
install_target_device_out=$(field install_target_device)
|
||||
installer_state_path=$(field installer_state_path)
|
||||
installer_log_path=$(field installer_log_path)
|
||||
iso_volume_label=$(field iso_volume_label)
|
||||
root_size_out=$(field root_size)
|
||||
freebsd_source_kind_out=$(field freebsd_source_kind)
|
||||
freebsd_source_ref_out=$(field freebsd_source_ref)
|
||||
freebsd_source_commit_out=$(field freebsd_source_commit)
|
||||
freebsd_source_file=$(field freebsd_source_file)
|
||||
freebsd_source_materializations_file=$(field freebsd_source_materializations_file)
|
||||
materialized_source_store_count=$(field materialized_source_store_count)
|
||||
materialized_source_stores=$(field materialized_source_stores)
|
||||
host_base_store_count=$(field host_base_store_count)
|
||||
native_base_store_count=$(field native_base_store_count)
|
||||
native_base_stores=$(field native_base_stores)
|
||||
store_item_count=$(field store_item_count)
|
||||
target_store_item_count=$(field target_store_item_count)
|
||||
installer_store_item_count=$(field installer_store_item_count)
|
||||
store_layout_file=$(field store_layout_file)
|
||||
|
||||
[ -d "$iso_store_path" ] || { echo "missing installer ISO store path: $iso_store_path" >&2; exit 1; }
|
||||
[ -f "$installer_iso_image" ] || { echo "missing installer ISO image: $installer_iso_image" >&2; exit 1; }
|
||||
[ -f "$installer_boot_efi_image" ] || { echo "missing installer EFI boot image: $installer_boot_efi_image" >&2; exit 1; }
|
||||
[ -f "$installer_root_image" ] || { echo "missing installer root image: $installer_root_image" >&2; exit 1; }
|
||||
[ -n "$installer_closure_path" ] || { echo "missing installer closure path" >&2; exit 1; }
|
||||
[ -n "$target_closure_path" ] || { echo "missing target closure path" >&2; exit 1; }
|
||||
[ "$install_target_device_out" = "$install_target_device" ] || { echo "unexpected install target device: $install_target_device_out" >&2; exit 1; }
|
||||
[ "$installer_host_name" = fruix-freebsd-installer ] || { echo "unexpected installer host name: $installer_host_name" >&2; exit 1; }
|
||||
[ -n "$iso_volume_label" ] || { echo "missing ISO volume label" >&2; exit 1; }
|
||||
[ "$freebsd_source_kind_out" = git ] || { echo "unexpected source kind: $freebsd_source_kind_out" >&2; exit 1; }
|
||||
[ "$freebsd_source_ref_out" = "$source_ref" ] || { echo "unexpected source ref: $freebsd_source_ref_out" >&2; exit 1; }
|
||||
[ "$freebsd_source_commit_out" = "$source_commit" ] || { echo "unexpected source commit: $freebsd_source_commit_out" >&2; exit 1; }
|
||||
[ "$materialized_source_store_count" = 1 ] || { echo "unexpected materialized source store count: $materialized_source_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 0 ] || { echo "expected zero host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||
[ "$native_base_store_count" = 3 ] || { echo "expected three native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ -f "$freebsd_source_file" ] || { echo "missing freebsd source file: $freebsd_source_file" >&2; exit 1; }
|
||||
[ -f "$freebsd_source_materializations_file" ] || { echo "missing source materializations file: $freebsd_source_materializations_file" >&2; exit 1; }
|
||||
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
|
||||
case "$materialized_source_stores" in
|
||||
/frx/store/*-freebsd-source-$source_name) : ;;
|
||||
*) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$store_item_count" -ge "$target_store_item_count" ] || { echo "combined store item count smaller than target store item count" >&2; exit 1; }
|
||||
[ "$installer_store_item_count" -ge 1 ] || { echo "expected installer store items" >&2; exit 1; }
|
||||
|
||||
xo-cli list-objects id=$vm_id >"$vm_info_json"
|
||||
vdi_id=$(xo-cli list-objects type=VBD | jq -r '.[] | select(.VM=="'$vm_id'" and .is_cd_drive==false and .position=="0") | .VDI' | head -n 1)
|
||||
secondary_vdi_id=$(xo-cli list-objects type=VBD | jq -r '.[] | select(.VM=="'$vm_id'" and .is_cd_drive==false and .position=="1") | .VDI' | head -n 1)
|
||||
[ -n "$vdi_id" ] || { echo "failed to discover primary target VDI for VM $vm_id" >&2; exit 1; }
|
||||
xo-cli list-objects type=VDI | jq '[.[] | select(.id=="'$vdi_id'")]' >"$vdi_info_json"
|
||||
vdi_size=$(jq -r '.[0].size' "$vdi_info_json")
|
||||
[ -n "$vdi_size" ] || { echo "failed to discover VDI size for $vdi_id" >&2; exit 1; }
|
||||
|
||||
vif_id=$(jq -r '.[0].VIFs[0]' "$vm_info_json")
|
||||
if [ -n "$vif_id" ] && [ "$vif_id" != null ]; then
|
||||
vm_mac=$(xo-cli list-objects type=VIF | jq -r '.[] | select(.id=="'$vif_id'") | .MAC' | tr 'A-Z' 'a-z')
|
||||
else
|
||||
vm_mac=
|
||||
fi
|
||||
[ -n "$vm_mac" ] || { echo "failed to discover VM MAC address" >&2; exit 1; }
|
||||
|
||||
host_interface=$(route -n get default | awk '/interface:/{print $2; exit}')
|
||||
host_ip=$(ifconfig "$host_interface" | awk '/inet /{print $2; exit}')
|
||||
subnet_prefix=${host_ip%.*}
|
||||
|
||||
cat >"$server_script" <<'EOF'
|
||||
import { createServer } from 'node:http'
|
||||
import { createReadStream, statSync } from 'node:fs'
|
||||
const file = process.argv[2]
|
||||
const port = Number(process.argv[3])
|
||||
const size = statSync(file).size
|
||||
createServer((req, res) => {
|
||||
if (req.url !== '/installer.iso') {
|
||||
res.statusCode = 404
|
||||
res.end('not found\n')
|
||||
return
|
||||
}
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Length': size,
|
||||
'Content-Disposition': 'attachment; filename="installer.iso"'
|
||||
})
|
||||
createReadStream(file).pipe(res)
|
||||
}).listen(port, '0.0.0.0', () => {
|
||||
console.log(`listening ${port}`)
|
||||
})
|
||||
EOF
|
||||
|
||||
node "$server_script" "$installer_iso_image" "$iso_http_port" >"$server_log" 2>&1 &
|
||||
server_pid=$!
|
||||
for _ in $(jot 50 1 50); do
|
||||
if grep -q 'listening' "$server_log"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
if ! kill -0 "$server_pid" >/dev/null 2>&1; then
|
||||
echo "temporary ISO HTTP server failed to start" >&2
|
||||
cat "$server_log" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
iso_import_url="http://$host_ip:$iso_http_port/installer.iso"
|
||||
imported_iso_name="fruix-installer-iso-ada0-$(date +%s).iso"
|
||||
imported_iso_id=$(xo-cli disk.import name="$imported_iso_name" sr="$iso_sr_id" type=iso url="$iso_import_url")
|
||||
kill "$server_pid" >/dev/null 2>&1 || true
|
||||
server_pid=
|
||||
|
||||
refresh_guest_ip() {
|
||||
guest_ip=$(arp -an | awk -v mac="$vm_mac" 'tolower($4)==mac {gsub(/[()]/,"",$2); print $2; exit}')
|
||||
}
|
||||
|
||||
ping_sweep() {
|
||||
: >"$arp_scan_log"
|
||||
for host in $(jot 254 1 254); do
|
||||
ip=$subnet_prefix.$host
|
||||
(
|
||||
ping -c 1 -W 1000 "$ip" >/dev/null 2>&1 && echo "$ip" >>"$arp_scan_log"
|
||||
) &
|
||||
done
|
||||
wait
|
||||
}
|
||||
|
||||
ssh_guest() {
|
||||
ssh -i "$root_ssh_private_key_file" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
-o ConnectTimeout=5 \
|
||||
root@"$guest_ip" "$@"
|
||||
}
|
||||
|
||||
wait_for_guest_command() {
|
||||
probe=$1
|
||||
attempts=$2
|
||||
delay=$3
|
||||
guest_ip=
|
||||
for attempt in $(jot "$attempts" 1 "$attempts"); do
|
||||
refresh_guest_ip
|
||||
if [ -z "$guest_ip" ]; then
|
||||
ping_sweep
|
||||
refresh_guest_ip
|
||||
fi
|
||||
if [ -n "$guest_ip" ]; then
|
||||
if ssh_guest "$probe" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep "$delay"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
xo-cli vm.stop id=$vm_id force=true >/dev/null 2>&1 || true
|
||||
xo-cli vm.insertCd id=$vm_id cd_id=$imported_iso_id force=true >"$workdir/insert-cd.out"
|
||||
xo-cli vm.setBootOrder vm=$vm_id order=dcn >"$workdir/set-boot-order.out"
|
||||
xo-cli vm.start id=$vm_id >"$workdir/vm-start-installer.out"
|
||||
|
||||
wait_for_guest_command 'test -e /var/lib/fruix/installer/state' 90 5 || {
|
||||
echo "installer ISO guest never became reachable over SSH" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
installer_state=missing
|
||||
for attempt in $(jot 180 1 180); do
|
||||
if ssh_guest 'test -e /var/lib/fruix/installer/state' >/dev/null 2>&1; then
|
||||
installer_state=$(ssh_guest "cat '$installer_state_path' 2>/dev/null || echo missing")
|
||||
[ "$installer_state" = done ] && break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
installer_run_current_system=$(ssh_guest 'readlink /run/current-system')
|
||||
installer_sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
installer_log=$(ssh_guest "cat '$installer_log_path' 2>/dev/null || true")
|
||||
installer_target_device=$(ssh_guest 'cat /var/lib/fruix/installer/target-device')
|
||||
installer_kern_disks=$(ssh_guest 'sysctl -n kern.disks')
|
||||
installer_gpart=$(ssh_guest 'gpart show ada0')
|
||||
installer_esp_fstype=$(ssh_guest 'fstyp /dev/ada0p1')
|
||||
installer_root_fstype=$(ssh_guest 'fstyp /dev/ada0p2')
|
||||
printf '%s\n' "$installer_log" >"$installer_log_file"
|
||||
printf '%s\n' "$installer_kern_disks" >"$installer_kern_disks_file"
|
||||
printf '%s\n' "$installer_gpart" >"$installer_gpart_file"
|
||||
|
||||
[ "$installer_state" = done ] || { echo "installer ISO environment did not finish installation: $installer_state" >&2; exit 1; }
|
||||
[ "$installer_run_current_system" = "/frx/store/$(basename "$installer_closure_path")" ] || { echo "unexpected installer current-system target: $installer_run_current_system" >&2; exit 1; }
|
||||
[ "$installer_sshd_status" = running ] || { echo "installer sshd is not running" >&2; exit 1; }
|
||||
[ "$installer_target_device" = "$install_target_device" ] || { echo "unexpected installer target device in guest: $installer_target_device" >&2; exit 1; }
|
||||
[ "$installer_esp_fstype" = msdosfs ] || { echo "unexpected target ESP filesystem: $installer_esp_fstype" >&2; exit 1; }
|
||||
[ "$installer_root_fstype" = ufs ] || { echo "unexpected target root filesystem: $installer_root_fstype" >&2; exit 1; }
|
||||
case "$installer_log" in
|
||||
*fruix-installer:done*) : ;;
|
||||
*) echo "installer log does not show completion" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$installer_kern_disks" in
|
||||
*cd0*ada0*) : ;;
|
||||
*) echo "unexpected installer kern.disks output: $installer_kern_disks" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$installer_gpart" in
|
||||
*ada0*GPT*) : ;;
|
||||
*) echo "unexpected gpart output for ada0" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
xo-cli vm.ejectCd id=$vm_id >"$workdir/eject-cd.out"
|
||||
xo-cli vm.restart id=$vm_id force=true >"$workdir/vm-restart-target.out"
|
||||
|
||||
wait_for_guest_command 'test -f /var/lib/fruix/ready' 120 5 || {
|
||||
echo "installed target never became reachable over SSH" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
target_run_current_system=$(ssh_guest 'readlink /run/current-system')
|
||||
target_shepherd_status=$(ssh_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
target_sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
target_activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||
target_install_metadata=$(ssh_guest 'cat /var/lib/fruix/install.scm')
|
||||
printf '%s\n' "$target_install_metadata" >"$target_install_metadata_file"
|
||||
|
||||
[ "$target_run_current_system" = "/frx/store/$(basename "$target_closure_path")" ] || { echo "unexpected booted target current-system: $target_run_current_system" >&2; exit 1; }
|
||||
[ "$target_shepherd_status" = running ] || { echo "fruix-shepherd is not running in booted target" >&2; exit 1; }
|
||||
[ "$target_sshd_status" = running ] || { echo "sshd is not running in booted target" >&2; exit 1; }
|
||||
case "$target_install_metadata" in
|
||||
*"$target_closure_path"*) : ;;
|
||||
*) echo "booted target metadata does not record target closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$target_install_metadata" in
|
||||
*"$materialized_source_stores"*) : ;;
|
||||
*) echo "booted target metadata does not record materialized source store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$target_install_metadata" in
|
||||
*"$install_target_device"*) : ;;
|
||||
*) echo "booted target metadata does not record install target device" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$target_activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "booted target activation log does not show success" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
vm_id=$vm_id
|
||||
primary_vdi_id=$vdi_id
|
||||
secondary_vdi_id=$secondary_vdi_id
|
||||
vdi_size=$vdi_size
|
||||
iso_sr_id=$iso_sr_id
|
||||
imported_iso_id=$imported_iso_id
|
||||
imported_iso_name=$imported_iso_name
|
||||
guest_ip=$guest_ip
|
||||
target_os_file=$target_os_file
|
||||
installer_iso_store_path=$iso_store_path
|
||||
installer_iso_image=$installer_iso_image
|
||||
installer_boot_efi_image=$installer_boot_efi_image
|
||||
installer_root_image=$installer_root_image
|
||||
install_target_device=$install_target_device
|
||||
installer_root_size=$root_size_out
|
||||
iso_volume_label=$iso_volume_label
|
||||
freebsd_source_kind=$freebsd_source_kind_out
|
||||
freebsd_source_ref=$freebsd_source_ref_out
|
||||
freebsd_source_commit=$freebsd_source_commit_out
|
||||
freebsd_source_file=$freebsd_source_file
|
||||
freebsd_source_materializations_file=$freebsd_source_materializations_file
|
||||
materialized_source_store_count=$materialized_source_store_count
|
||||
materialized_source_store=$materialized_source_stores
|
||||
installer_closure_path=$installer_closure_path
|
||||
target_closure_path=$target_closure_path
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
store_item_count=$store_item_count
|
||||
target_store_item_count=$target_store_item_count
|
||||
installer_store_item_count=$installer_store_item_count
|
||||
installer_state_path=$installer_state_path
|
||||
installer_log_path=$installer_log_path
|
||||
installer_state=$installer_state
|
||||
installer_run_current_system=$installer_run_current_system
|
||||
installer_sshd_status=$installer_sshd_status
|
||||
installer_target_device=$installer_target_device
|
||||
installer_kern_disks=$installer_kern_disks
|
||||
installer_log_file=$installer_log_file
|
||||
installer_gpart_file=$installer_gpart_file
|
||||
installer_kern_disks_file=$installer_kern_disks_file
|
||||
target_esp_fstype=$installer_esp_fstype
|
||||
target_root_fstype=$installer_root_fstype
|
||||
target_run_current_system=$target_run_current_system
|
||||
target_shepherd_status=$target_shepherd_status
|
||||
target_sshd_status=$target_sshd_status
|
||||
target_install_metadata_file=$target_install_metadata_file
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
installer_iso_boot=ok
|
||||
installer_iso_install=ok
|
||||
installed_target_boot=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase18-installer-iso-xcpng\n'
|
||||
printf 'Work directory: %s\n' "$workdir"
|
||||
printf 'Metadata file: %s\n' "$metadata_file"
|
||||
if [ -n "$metadata_target" ]; then
|
||||
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||
fi
|
||||
printf '%s\n' '--- metadata ---'
|
||||
cat "$metadata_file"
|
||||
Reference in New Issue
Block a user