Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/debian-iso-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Builds the unattended Debian ISO and installs it from UEFI USB media in QEMU.
# It completes the required reboots and verifies the installed DAppNode system.
name: Debian unattended USB ISO end-to-end

on:
workflow_dispatch:
pull_request:

permissions:
contents: read

concurrency:
group: debian-iso-e2e-${{ github.ref }}
cancel-in-progress: true

jobs:
install:
name: Install Debian unattended ISO via UEFI USB
runs-on: ubuntu-latest
timeout-minutes: 180

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Verify Debian release alignment
run: bash iso/scripts/check_debian_release_alignment.sh

- name: Build Debian unattended ISO
run: |
export BASE_OS=debian
export UNATTENDED=true
docker compose up --build
ls -lrt images/Dappnode-debian-*.iso

- name: Install QEMU test dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qemu-system-x86 qemu-utils ovmf socat sshpass
if [ -e /dev/kvm ]; then
sudo chmod a+rw /dev/kvm
fi

- name: Enable ICMP for QEMU user networking
run: sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"

- name: Complete Debian unattended UEFI USB installation
env:
E2E_BOOT_MODE: usb
E2E_FIRMWARE: uefi
run: |
debian_iso=$(find images -maxdepth 1 -type f -name 'Dappnode-debian-*-amd64-netinst.iso' -print -quit)
test -n "${debian_iso}"
bash test/e2e_debian_iso.sh "${debian_iso}"

- name: Upload installation logs
if: always()
uses: actions/upload-artifact@v4
with:
name: debian-unattended-usb-installation-logs
path: test-output/debian-e2e/
if-no-files-found: ignore
retention-days: 7
34 changes: 0 additions & 34 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,3 @@ jobs:
- name: Uninstall DAppNode
run: |
sudo /bin/bash ./scripts/dappnode_uninstall.sh y

iso:
name: test Debian and Ubuntu ISO
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

# Create Debian ISO
- name: create Debian ISO
run: |
export BASE_OS=debian
export UNATTENDED=true
docker compose up --build
ls images/

# Verify Debian ISO creation
- name: verify Debian ISO
run: |
ls -lrt images/Dappnode-debian-*.iso

# Create Ubuntu ISO
- name: create Ubuntu ISO
run: |
export BASE_OS=ubuntu
export UNATTENDED=true
docker compose up --build
ls images/

# Verify Ubuntu ISO creation
- name: verify Ubuntu ISO
run: |
ls -lrt images/Dappnode-ubuntu-*.iso
60 changes: 60 additions & 0 deletions .github/workflows/ubuntu-iso-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Builds the unattended Ubuntu ISO and installs it from UEFI USB media in QEMU.
# It completes the required reboots and verifies the installed DAppNode system.
name: Ubuntu unattended USB ISO end-to-end

on:
workflow_dispatch:
pull_request:

permissions:
contents: read

concurrency:
group: ubuntu-iso-e2e-${{ github.ref }}
cancel-in-progress: true

jobs:
install:
name: Install Ubuntu unattended ISO via UEFI USB
runs-on: ubuntu-latest
timeout-minutes: 180

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Build Ubuntu unattended ISO
run: |
export BASE_OS=ubuntu
export UNATTENDED=true
docker compose up --build
ls -lrt images/Dappnode-ubuntu-*.iso

- name: Install QEMU test dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qemu-system-x86 qemu-utils ovmf socat sshpass
if [ -e /dev/kvm ]; then
sudo chmod a+rw /dev/kvm
fi

- name: Enable ICMP for QEMU user networking
run: sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"

- name: Complete Ubuntu unattended UEFI USB installation
env:
E2E_BOOT_MODE: usb
E2E_FIRMWARE: uefi
run: |
ubuntu_iso=$(find images -maxdepth 1 -type f -name 'Dappnode-ubuntu-*-live-server-amd64.iso' -print -quit)
test -n "${ubuntu_iso}"
bash test/e2e_ubuntu_iso.sh "${ubuntu_iso}"

- name: Upload installation logs
if: always()
uses: actions/upload-artifact@v4
with:
name: ubuntu-unattended-usb-installation-logs
path: test-output/ubuntu-e2e/
if-no-files-found: ignore
retention-days: 7
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
docs/_build
images
images
test-output/
3 changes: 3 additions & 0 deletions iso/debian_release.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Debian release used by the ISO and its package repositories.
DEBIAN_SUITE="trixie"
DEBIAN_MAJOR_VERSION="13"
2 changes: 1 addition & 1 deletion iso/preseeds/preseed.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ d-i mirror/country string manual
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
d-i apt-setup/local0/repository string http://deb.debian.org/debian/ trixie main contrib non-free-firmware
d-i apt-setup/local0/repository string http://deb.debian.org/debian/ @DEBIAN_SUITE@ main contrib non-free-firmware
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-failed boolean false
Expand Down
2 changes: 1 addition & 1 deletion iso/preseeds/preseed_unattended.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ d-i mirror/country string manual
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
d-i apt-setup/local0/repository string http://deb.debian.org/debian/ trixie main contrib non-free-firmware
d-i apt-setup/local0/repository string http://deb.debian.org/debian/ @DEBIAN_SUITE@ main contrib non-free-firmware
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-failed boolean false
Expand Down
58 changes: 58 additions & 0 deletions iso/scripts/check_debian_release_alignment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/bash
set -euo pipefail

SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ISO_DIR=$(cd "${SCRIPTS_DIR}/.." && pwd)

source "${ISO_DIR}/debian_release.conf"

case "${DEBIAN_SUITE}" in
bullseye) suite_major_version="11" ;;
bookworm) suite_major_version="12" ;;
trixie) suite_major_version="13" ;;
forky) suite_major_version="14" ;;
*)
echo "[ERROR] Unknown Debian suite: ${DEBIAN_SUITE}"
exit 1
;;
esac

if [ "${suite_major_version}" != "${DEBIAN_MAJOR_VERSION}" ]; then
echo "[ERROR] Debian suite ${DEBIAN_SUITE} is Debian ${suite_major_version}, not Debian ${DEBIAN_MAJOR_VERSION}"
exit 1
fi

generator="${SCRIPTS_DIR}/generate_dappnode_iso_debian.sh"
base_iso_name=$(sed -n 's/^BASE_ISO_NAME="\([^"]*\)"$/\1/p' "${generator}")

if [[ ! "${base_iso_name}" =~ ^debian-([0-9]+)\. ]]; then
echo "[ERROR] Could not determine the Debian major version from BASE_ISO_NAME=${base_iso_name}"
exit 1
fi

iso_major_version="${BASH_REMATCH[1]}"
if [ "${iso_major_version}" != "${DEBIAN_MAJOR_VERSION}" ]; then
echo "[ERROR] Base ISO ${base_iso_name} is Debian ${iso_major_version}, but debian_release.conf expects Debian ${DEBIAN_MAJOR_VERSION} (${DEBIAN_SUITE})"
exit 1
fi

expected_repository="d-i apt-setup/local0/repository string http://deb.debian.org/debian/ @DEBIAN_SUITE@ main contrib non-free-firmware"
preseed_files=(
"${ISO_DIR}/preseeds/preseed.cfg"
"${ISO_DIR}/preseeds/preseed_unattended.cfg"
)

for preseed_file in "${preseed_files[@]}"; do
repository_count=$(grep -Ec '^d-i apt-setup/local[0-9]+/repository string ' "${preseed_file}" || true)
if [ "${repository_count}" -ne 1 ]; then
echo "[ERROR] Expected exactly one additional Debian repository in ${preseed_file}, found ${repository_count}"
exit 1
fi

if ! grep -Fqx "${expected_repository}" "${preseed_file}"; then
echo "[ERROR] Debian repository in ${preseed_file} must use the @DEBIAN_SUITE@ placeholder"
exit 1
fi
done

echo "[INFO] Debian release alignment is valid: Debian ${DEBIAN_MAJOR_VERSION} (${DEBIAN_SUITE}), ${base_iso_name}"
11 changes: 11 additions & 0 deletions iso/scripts/generate_dappnode_iso_debian.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -e
SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}")

source ${SCRIPTS_DIR}/common_iso_generation.sh
source ${SCRIPTS_DIR}/../debian_release.conf

BASE_ISO_NAME="debian-13.5.0-amd64-netinst.iso"
BASE_ISO_VERSION="${BASE_ISO_NAME#debian-}"
Expand All @@ -15,6 +16,9 @@ BASE_ISO_SHASUM="95838884f5ea6c82421dfe6baaa5a639dbbe6756c1e380f9fe7a7cb0c1949d2
DAPPNODE_ISO_NAME="${DAPPNODE_ISO_PREFIX}${BASE_ISO_NAME}"
DAPPNODE_ISO_PATH="/images/${DAPPNODE_ISO_NAME}"

# Fail before downloading or building if the ISO and repositories target different releases.
bash "${SCRIPTS_DIR}/check_debian_release_alignment.sh"

customize_debian_preseed() {
local iso_build_path=$1
local workdir=$2
Expand Down Expand Up @@ -50,6 +54,13 @@ customize_debian_preseed() {
exit 1
}

# Keep package repositories aligned with the Debian release used by the ISO.
sed -i "s/@DEBIAN_SUITE@/${DEBIAN_SUITE}/g" "${tmp_initrd}/preseed.cfg"
if grep -q "@DEBIAN_SUITE@" "${tmp_initrd}/preseed.cfg"; then
echo "[ERROR] Could not render the Debian suite in the preseed file"
exit 1
fi

# Recreate (and recompress) the initrd
(cd "${tmp_initrd}" && find . -print0 | cpio -0 -ov -H newc | gzip >"${install_dir}/initrd.gz") || {
echo "[ERROR] Could not create new initrd"
Expand Down
12 changes: 11 additions & 1 deletion scripts/check-disks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,14 @@ for DISK in ${DEVICE}; do
DISKS="${DISKS} ${DISK}";
done;
DISKS=$(echo "${DISKS}" | sed "s/^ //g");
debconf-set partman-auto/disk "$DISKS";
debconf-set partman-auto/disk "$DISKS";

# grub-installer/bootdev is preseeded to "default", which marks the question as
# already seen. grub-installer then resolves "default" to the first entry of
# grub-mkdevicemap, i.e. (hd0) -- the installer USB itself. Its safeguard against
# installing onto the installation media only runs for grub-pc, so on UEFI it
# happily targets the stick and then dies reading its partition table:
# Can't read partition table from /dev/sda
# Point it at the same non-USB disks partman is using. grub-installer accepts a
# space separated list and installs to each entry in turn.
debconf-set grub-installer/bootdev "$DISKS";
11 changes: 10 additions & 1 deletion scripts/dappnode_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,16 @@ normalize_ipfs_version_ref() {
local cid_path="$ref"
local manifest_url="${IPFS_ENDPOINT%/}${cid_path}/dappnode_package.json"
local manifest
manifest="$(download_stdout "$manifest_url" 2>/dev/null || true)"
local attempt
for attempt in 1 2 3 4 5; do
manifest="$(download_stdout "$manifest_url" 2>/dev/null || true)"
if [[ -n "$manifest" ]]; then
break
fi
if [[ "$attempt" -lt 5 ]]; then
sleep 2
fi
done
if [[ -z "$manifest" ]]; then
error "Could not fetch IPFS manifest for ${comp} from: $manifest_url"
error "Provide ${comp}_VERSION as /ipfs/<cid>:<version> (example: /ipfs/Qm...:0.2.11)"
Expand Down
26 changes: 25 additions & 1 deletion scripts/dappnode_test_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,33 @@ detect_installation_type() {
fi
}

# ICMP is filtered on many corporate, hotel and cloud networks, so a failed
# ping alone does not mean the machine is offline. Confirm over HTTPS before
# declaring the installation broken.
check_connectivity() {
ping -c 1 -q google.com >/dev/null 2>&1 && return 0

if command -v curl >/dev/null 2>&1; then
curl -fsS --max-time 10 -o /dev/null https://www.google.com/generate_204 && return 0
fi

if command -v wget >/dev/null 2>&1; then
wget -q --timeout=10 -O /dev/null https://www.google.com/generate_204 && return 0
fi

# Neither downloader is guaranteed to be present this early, so fall back to
# a plain TCP connect, which bash can do on its own.
if (exec 3<>/dev/tcp/www.google.com/443) 2>/dev/null; then
exec 3<&- 3>&-
return 0
fi

return 1
}

components=(BIND IPFS VPN DAPPMANAGER WIFI)
detect_installation_type
if ping -c 1 -q google.com >&/dev/null; then
if check_connectivity; then
echo -e "\e[32m Connectivity OK\n \e[0m"
else
error_exit
Expand Down
17 changes: 17 additions & 0 deletions test/e2e_debian_iso.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
# Runs the generated unattended Debian ISO through the complete UEFI USB test.
# It adds Debian release and package-alignment checks to the shared harness.
set -Eeuo pipefail

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)

source "${REPO_ROOT}/iso/debian_release.conf"

E2E_DISTRO="debian"
E2E_EXPECTED_ID="debian"
E2E_EXPECTED_VERSION="${DEBIAN_MAJOR_VERSION}"
E2E_EXPECTED_CODENAME="${DEBIAN_SUITE}"

source "${SCRIPT_DIR}/e2e_iso_install.sh"
run_e2e_iso_install "$@"
Loading
Loading