diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index 0aa39464..b82ddac0 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -68,39 +68,80 @@ jobs: sed -i -e "/HTTPS_VERSION/s/[0-9]*\.[0-9]*\.[0-9]*/"${HTTPS_VERSION}"/" .dappnode_profile cat .dappnode_profile - # ISO ATTENDED - - name: Build attended + # Debian ISO ATTENDED + - name: Build Debian attended run: | + sed -i -e "/BASE_OS/s/ubuntu/debian/" docker-compose.yml sed -i -e "/UNATTENDED/s/true/false/" docker-compose.yml docker compose build docker compose up - # Verify ISO attended created - - name: Check iso attended + - name: Check Debian ISO attended run: | - ls -lrt images/DAppNode-debian-bookworm-amd64.iso + ls -lrt images/Dappnode-debian-*.iso - # Set new name for the release asset - - name: Set DAppNode attended ISO name + - name: Set Debian Dappnode attended ISO name run: | - cp ./images/DAppNode-debian-bookworm-amd64.iso DAppNode-${CORE_VERSION}-debian-bookworm-amd64.iso + file=$(ls images/Dappnode-debian-*.iso) + attended_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + mv "$file" "$attended_filename" - # ISO UNATTENDED - - name: Build unattended + # Debian ISO UNATTENDED + - name: Build Debian unattended run: | + sed -i -e "/BASE_OS/s/ubuntu/debian/" docker-compose.yml sed -i -e "/UNATTENDED/s/false/true/" docker-compose.yml docker compose build docker compose up - # Verify ISO unattended was created - - name: Check iso unattended + - name: Check Debian ISO unattended run: | - ls -lrt images/DAppNode-debian-bookworm-amd64.iso + ls -lrt images/Dappnode-debian-*.iso # Set new name for the release asset - - name: Set DAppNode unttended ISO name + - name: Set Dappnode unttended ISO name + run: | + file=$(ls images/Dappnode-debian-*.iso) + + core_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + unattended_filename="${core_filename/%.iso/-unattended.iso}" + + mv "$file" "$unattended_filename" + + # Ubuntu ISO ATTENDED + - name: Build Ubuntu attended + run: | + sed -i -e "/BASE_OS/s/debian/ubuntu/" docker-compose.yml + sed -i -e "/UNATTENDED/s/true/false/" docker-compose.yml + docker-compose up --build + + - name: Check Ubuntu ISO attended + run: | + ls -lrt images/Dappnode-ubuntu-*.iso + + - name: Set Ubuntu Dappnode attended ISO name + run: | + file=$(ls images/Dappnode-ubuntu-*.iso) + attended_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + mv "$file" "$attended_filename" + + # Ubuntu ISO UNATTENDED + - name: Build Ubuntu unattended + run: | + sed -i -e "/BASE_OS/s/ubuntu/debian/" docker-compose.yml + sed -i -e "/UNATTENDED/s/false/true/" docker-compose.yml + docker-compose up --build + + - name: Check Ubuntu ISO unattended + run: | + ls -lrt images/Dappnode-ubuntu-*.iso + + - name: Set Ubuntu Dappnode unattended ISO name run: | - cp ./images/DAppNode-debian-bookworm-amd64.iso DAppNode-${CORE_VERSION}-debian-bookworm-amd64-unattended.iso + file=$(ls images/Dappnode-ubuntu-*.iso) + core_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + unattended_filename="${core_filename/%.iso/-unattended.iso}" + mv "$file" "$unattended_filename" - name: Create dappnode_profile.sh run: | @@ -112,8 +153,8 @@ jobs: with: name: test-artifact path: | - ./DAppNode-*-amd64.iso - ./DAppNode-*-amd64-unattended.iso + ./Dappnode-debian-*.iso + ./Dappnode-ubuntu-*.iso ./scripts/dappnode_install.sh ./scripts/dappnode_install_pre.sh ./scripts/dappnode_uninstall.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d289db8..e9014450 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,14 +39,13 @@ env: jobs: pre-release: - name: create pre release + name: Create pre-release artifacts runs-on: ubuntu-latest defaults: run: shell: bash steps: - # Regex for versions introduced - name: Check versions regex run: | [[ $BIND_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && [[ $IPFS_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && [[ $DAPPMANAGER_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && \ @@ -56,7 +55,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - # Edit the profile with the new versions introduced - name: Set new versions run: | sed -i -e "/BIND_VERSION/s/[0-9]*\.[0-9]*\.[0-9]*/"${BIND_VERSION}"/" .dappnode_profile @@ -68,82 +66,119 @@ jobs: sed -i -e "/HTTPS_VERSION/s/[0-9]*\.[0-9]*\.[0-9]*/"${HTTPS_VERSION}"/" .dappnode_profile cat .dappnode_profile - # ISO ATTENDED - - name: Build attended + - name: Build Debian attended run: | + sed -i -e "/BASE_OS/s/ubuntu/debian/" docker-compose.yml sed -i -e "/UNATTENDED/s/true/false/" docker-compose.yml docker compose build docker compose up - # Verify ISO attended created - - name: Check iso attended + - name: Check Debian ISO attended run: | - ls -lrt images/DAppNode-debian-bookworm-amd64.iso + ls -lrt images/Dappnode-debian-*.iso - # Set new name for the release asset - - name: Set DAppNode attended ISO name + - name: Set Debian Dappnode attended ISO name run: | - cp ./images/DAppNode-debian-bookworm-amd64.iso DAppNode-${CORE_VERSION}-debian-bookworm-amd64.iso + file=$(ls images/Dappnode-debian-*.iso) + attended_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + mv "$file" "$attended_filename" - # ISO UNATTENDED - - name: Build unattended + - name: Build Debian unattended run: | sed -i -e "/UNATTENDED/s/false/true/" docker-compose.yml docker compose build docker compose up - # Verify ISO unattended was created - - name: Check iso unattended + - name: Check Debian ISO unattended run: | - ls -lrt images/DAppNode-debian-bookworm-amd64.iso + ls -lrt images/Dappnode-debian-*.iso - # Set new name for the release asset - - name: Set DAppNode unttended ISO name + - name: Set Dappnode unattended ISO name run: | - cp ./images/DAppNode-debian-bookworm-amd64.iso DAppNode-${CORE_VERSION}-debian-bookworm-amd64-unattended.iso + file=$(ls images/Dappnode-debian-*.iso) + core_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + unattended_filename="${core_filename/%.iso/-unattended.iso}" + mv "$file" "$unattended_filename" + + - name: Build Ubuntu attended + run: | + sed -i -e "/BASE_OS/s/debian/ubuntu/" docker-compose.yml + sed -i -e "/UNATTENDED/s/true/false/" docker-compose.yml + docker-compose up --build + + - name: Check Ubuntu ISO attended + run: | + ls -lrt images/Dappnode-ubuntu-*.iso + + - name: Set Ubuntu Dappnode attended ISO name + run: | + file=$(ls images/Dappnode-ubuntu-*.iso) + attended_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + mv "$file" "$attended_filename" + + - name: Build Ubuntu unattended + run: | + sed -i -e "/BASE_OS/s/ubuntu/debian/" docker-compose.yml + sed -i -e "/UNATTENDED/s/false/true/" docker-compose.yml + docker-compose up --build + + - name: Check Ubuntu ISO unattended + run: | + ls -lrt images/Dappnode-ubuntu-*.iso + + - name: Set Ubuntu Dappnode unattended ISO name + run: | + file=$(ls images/Dappnode-ubuntu-*.iso) + core_filename="${file/images\/Dappnode-/Dappnode-${CORE_VERSION}-}" + unattended_filename="${core_filename/%.iso/-unattended.iso}" + mv "$file" "$unattended_filename" - # Create profile.sh script (not able to set dot (.) before the name in the gh release asset) - name: Create dappnode_profile.sh run: | cp .dappnode_profile dappnode_profile.sh # SHASUMs - - name: Get SHA-256 attended - id: shasum-attended + - name: Get SHA-256 Debian attended + id: shasum-debian-attended run: | - SHASUM_ATTENDED=$(shasum -a 256 DAppNode-${CORE_VERSION}-debian-bookworm-amd64.iso) - echo "::set-output name=SHASUM_ATTENDED::$SHASUM_ATTENDED" + file=$(find . -type f -name 'Dappnode-debian-*.iso' ! -name '*unattended*') + SHASUM_DEBIAN_ATTENDED=$(shasum -a 256 $file) + echo "::set-output name=SHASUM_DEBIAN_ATTENDED::$SHASUM_DEBIAN_ATTENDED" - - name: Get SHA-256 unattended - id: shasum-unattended + - name: Get SHA-256 Debian unattended + id: shasum-debian-unattended run: | - SHASUM_UNATTENDED=$(shasum -a 256 DAppNode-${CORE_VERSION}-debian-bookworm-amd64-unattended.iso) - echo "::set-output name=SHASUM_UNATTENDED::$SHASUM_UNATTENDED" + file=$(find . -type f -name 'Dappnode-debian-*unattended.iso') + SHASUM_DEBIAN_UNATTENDED=$(shasum -a 256 $file) + echo "::set-output name=SHASUM_DEBIAN_UNATTENDED::$SHASUM_DEBIAN_UNATTENDED" - # Release body - - name: Write release content + - name: Get SHA-256 Debian attended + id: shasum-ubuntu-attended run: | - echo -en "# Versions\n| Package | Version |\n|---|---|\nbind.dnp.dappnode.eth|${BIND_VERSION}|\n|ipfs.dnp.dappnode.eth|${IPFS_VERSION}|\n|vpn.dnp.dappnode.eth |${VPN_VERSION}|\n|dappmanager.dnp.dappnode.eth|${DAPPMANAGER_VERSION}|\n|wifi.dnp.dappnode.eth|${WIFI_VERSION}|\n|https.dnp.dappnode.eth|${HTTPS_VERSION}|\n|wireguard.dnp.dappnode.eth|${WIREGUARD_VERSION}|\n# Changes\nChanges implemented in release ${CORE_VERSION}\n# Attended version\nInstall and customize DAppNode using the attended ISO: **DAppNode-${CORE_VERSION}-debian-bookworm-amd64.iso**\n\n## ISO SHA-256 Checksum\n\`\`\`\nshasum -a 256 DAppNode-${CORE_VERSION}-debian-bookworm-amd64.iso\n${SHASUM_ATTENDED}\n\`\`\`\n# Unattended version\nInstall DAppNode easily using the unattended ISO: **DAppNode-${CORE_VERSION}-debian-bookworm-amd64-unattended.iso**\nDo a reboot right after the installation\n:warning: **Warning**: This ISO will install Dappnode automatically, deleting all existing partitions on the disk\n\ndefault login data:\n - **__user__**: dappnode\n - **__password__**: dappnode.s0\n## ISO SHA-256 Checksum\n\`\`\`\nshasum -a 256 DAppNode-${CORE_VERSION}-debian-bookworm-amd64-unattended.iso\n${SHASUM_UNATTENDED}\n\`\`\`\n# DAppNode for Raspberry Pi 4 64bit\n[Instructions](https://github.com/dappnode/DAppNode/wiki/DAppNodeARM-Installation-Guide)\n\ndefault login data:\n - **__user__**: dappnode\n - **__password__**: dappnodepi" > CHANGELOG.md - cat CHANGELOG.md - env: - SHASUM_ATTENDED: ${{ steps.shasum-attended.outputs.SHASUM_ATTENDED }} - SHASUM_UNATTENDED: ${{ steps.shasum-unattended.outputs.SHASUM_UNATTENDED }} + file=$(find . -type f -name 'Dappnode-ubuntu-*.iso' ! -name '*unattended*') + SHASUM_UBUNTU_ATTENDED=$(shasum -a 256 $file)s + echo "::set-output name=SHASUM_UBUNTU_ATTENDED::$SHASUM_UBUNTU_ATTENDED" + + - name: Get SHA-256 Debian unattended + id: shasum-ubuntu-unattended + run: | + file=$(find . -type f -name 'Dappnode-ubuntu-*unattended.iso') + SHASUM_UBUNTU_UNATTENDED=$(shasum -a 256 $file) + echo "::set-output name=SHASUM_UBUNTU_UNATTENDED::$SHASUM_UBUNTU_UNATTENDED" - # PRE-RELEASE ASSETS - - name: Pre release - uses: softprops/action-gh-release@v1 + # ARTIFACTS ASSETS + - name: Artifact + uses: actions/upload-artifact@v3 with: - tag_name: ${{ github.event.inputs.core }} - prerelease: true - files: | - ./DAppNode-*-amd64.iso - ./DAppNode-*-amd64-unattended.iso + name: test-artifact + path: | + ./Dappnode-debian-*.iso + ./Dappnode-ubuntu-*.iso ./scripts/dappnode_install.sh ./scripts/dappnode_install_pre.sh ./scripts/dappnode_uninstall.sh ./scripts/dappnode_access_credentials.sh dappnode_profile.sh - body_path: CHANGELOG.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 369d1b03..cd1f56ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,4 +45,4 @@ jobs: ls images/ - name: verify image run: | - ls -lrt images/DAppNode-debian-bookworm-amd64.iso + ls -lrt images/Dappnode-*.iso diff --git a/.github/workflows/validate_autoinstall.yml b/.github/workflows/validate_autoinstall.yml new file mode 100644 index 00000000..45b71ac0 --- /dev/null +++ b/.github/workflows/validate_autoinstall.yml @@ -0,0 +1,42 @@ +name: Validate Ubuntu autoinstall YAML Files + +on: + push: + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install check-jsonschema yamllint yq + + - name: Lint attended autoinstall.yaml + run: yamllint -c .yamllint iso/preseeds/ubuntu/autoinstall.yaml + + - name: Lint unattended autoinstall.yaml + run: yamllint -c .yamllint iso/preseeds/ubuntu/autoinstall_unattended.yaml + + - name: Download JSON schema + run: curl -o schema.json https://raw.githubusercontent.com/canonical/subiquity/main/autoinstall-schema.json + + - name: Extract autoinstall properties from YAML files + run: | + yq '.autoinstall' iso/preseeds/ubuntu/autoinstall.yaml > attended.yaml + yq '.autoinstall' iso/preseeds/ubuntu/autoinstall_unattended.yaml > unattended.yaml + + - name: Validate attended autoinstall.yaml + run: | + check-jsonschema --schemafile schema.json attended.yaml + + - name: Validate unattended autoinstall.yaml + run: | + check-jsonschema --schemafile schema.json unattended.yaml diff --git a/.yamllint b/.yamllint new file mode 100644 index 00000000..c9b624e1 --- /dev/null +++ b/.yamllint @@ -0,0 +1,6 @@ +# .yamllint +extends: default + +rules: + line-length: + max: 150 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 7eddc984..ed9ebe02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: - BUILD=false # In case you want to re-generate a all the images, not recommended - CLEAN=true # it remove the images directory - UNATTENDED=false # UNATTENDED version + - BASE_OS=debian # Base OS version (debian or ubuntu) volumes: - ./images:/images - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/iso/boot/splash.png b/iso/boot/splash.png index 835dc493..a6f4196e 100644 Binary files a/iso/boot/splash.png and b/iso/boot/splash.png differ diff --git a/iso/boot/ubuntu/grub.cfg b/iso/boot/ubuntu/grub.cfg new file mode 100644 index 00000000..88c8305b --- /dev/null +++ b/iso/boot/ubuntu/grub.cfg @@ -0,0 +1,21 @@ +if loadfont unicode ; then + set gfxmode=800x600 + insmod efi_gop + insmod efi_uga + insmod video_bochs + insmod video_cirrus + insmod gfxterm + insmod png + terminal_output gfxterm +fi + +set timeout=6 + +set theme=/boot/grub/themes/dappnode/theme.txt + +menuentry "Install Dappnode (over Ubuntu Server)" { + set background_color=black + set gfxpayload=keep # Maintain the graphical resolution through the booting + linux /casper/vmlinuz autoinstall vga=788 FRONTEND_BACKGROUND=dark --- # Added autoinstall to make it unattended + initrd /casper/initrd +} \ No newline at end of file diff --git a/iso/boot/ubuntu/themes/dappnode/splash.png b/iso/boot/ubuntu/themes/dappnode/splash.png new file mode 100644 index 00000000..236fa034 Binary files /dev/null and b/iso/boot/ubuntu/themes/dappnode/splash.png differ diff --git a/iso/boot/ubuntu/themes/dappnode/theme.txt b/iso/boot/ubuntu/themes/dappnode/theme.txt new file mode 100644 index 00000000..f096526d --- /dev/null +++ b/iso/boot/ubuntu/themes/dappnode/theme.txt @@ -0,0 +1,41 @@ +title-color: "white" +title-text: "Dappnode Installer" +title-font: "Sans Regular 16" +desktop-color: "black" +desktop-image: "/boot/grub/themes/dappnode/splash.png" +message-color: "white" +message-bg-color: "black" +terminal-font: "Sans Regular 12" + ++ boot_menu { + left = 18% + width = 50% + top = 200 + height = 200 + item_font = "Sans Regular 12" + item_color = #d3d3d3 + selected_item_color = "white" + item_height = 20 + item_padding = 15 + item_spacing = 5 +} + ++ vbox { + top = 100%-60 + left = 10% + + hbox { + top = 0 + left = 20% + + label {text = "Enter: " font = "Sans 10" color = "white" align = "left"} + + label {text = "Select " font = "Sans 10" color = "#d3d3d3" align = "left"} + } + + hbox { + top = 0 + left = 20% + + label {text = "E: " font = "Sans 10" color = "white" align = "left"} + + label {text = "Edit Selection " font = "Sans 10" color = "#d3d3d3" align = "left"} + + label {text = " " font = "Sans 10" color = "white" align = "left"} + + label {text = "C: " font = "Sans 10" color = "white" align = "left"} + + label {text = "GRUB Command line" font = "Sans 10" color = "#d3d3d3" align = "left"} + } +} diff --git a/iso/preseeds/ubuntu/autoinstall.yaml b/iso/preseeds/ubuntu/autoinstall.yaml new file mode 100644 index 00000000..1957f019 --- /dev/null +++ b/iso/preseeds/ubuntu/autoinstall.yaml @@ -0,0 +1,29 @@ +# cloud-config +--- +autoinstall: + version: 1 + interactive-sections: + - identity + - keyboard + - locale + - network + - storage + - timezone + - ssh + + packages: + - linux-generic + - wpasupplicant + - intel-microcode + - iucode-tool + - iptables + + late-commands: + - "curtin in-target --target=/target -- apt update && apt install -y chrony build-essential iw iwd avahi-utils" + - "mkdir -p /target/usr/src/dappnode" + - "cp -ar /cdrom/dappnode/* /target/usr/src/dappnode/" + - "cp -a /cdrom/dappnode/scripts/rc.local /target/etc/rc.local" + - "chmod +x /target/usr/src/dappnode/scripts/dappnode_install_pre.sh" + - "touch /target/usr/src/dappnode/.firstboot" + - "cp -ar /etc/netplan/* /target/etc/netplan/" # Necessary for prerequisites + - "curtin in-target --target=/target -- /usr/src/dappnode/scripts/dappnode_install_pre.sh UPDATE" diff --git a/iso/preseeds/ubuntu/autoinstall_unattended.yaml b/iso/preseeds/ubuntu/autoinstall_unattended.yaml new file mode 100644 index 00000000..bb43891a --- /dev/null +++ b/iso/preseeds/ubuntu/autoinstall_unattended.yaml @@ -0,0 +1,44 @@ +# cloud-config +--- +autoinstall: + version: 1 + + locale: en_US.UTF-8 + + keyboard: + layout: us + + # network left as default (DHCP in interfaces named en* or eth*) + storage: + layout: + name: lvm + sizing-policy: all + + identity: + hostname: dappnode + username: dappnode + password: "$6$insecur3$rnEv9Amdjn3ctXxPYOlzj/cwvLT43GjWzkPECIHNqd8Vvza5bMG8QqMwEIBKYqnj609D.4ngi4qlmt29dLE.71" + + ssh: + install-server: true + # By default, the password is allowed if no authorized keys are provided + + packages: + - linux-generic + - wpasupplicant + - intel-microcode + - iucode-tool + - iptables + + timezone: UTC + + late-commands: + - "curtin in-target --target=/target -- apt update && apt install -y chrony build-essential iw iwd avahi-utils" + - "mkdir -p /target/usr/src/dappnode" + - "cp -ar /cdrom/dappnode/* /target/usr/src/dappnode/" + - "cp -a /cdrom/dappnode/scripts/rc.local /target/etc/rc.local" + - "chmod +x /target/usr/src/dappnode/scripts/dappnode_install_pre.sh" + - "touch /target/usr/src/dappnode/.firstboot" + - "cp -ar /etc/netplan/* /target/etc/netplan/" # Necessary for prerequisites + - "curtin in-target --target=/target -- /usr/src/dappnode/scripts/dappnode_install_pre.sh UPDATE" + # TODO: Handle /etc/network/interfaces and /etc/network/devhotplug diff --git a/iso/scripts/common_iso_generation.sh b/iso/scripts/common_iso_generation.sh new file mode 100644 index 00000000..1cd65a0c --- /dev/null +++ b/iso/scripts/common_iso_generation.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +WORKDIR="/usr/src/app" +ISO_BUILD_PATH="${WORKDIR}/dappnode-iso" +DAPPNODE_ISO_PREFIX="Dappnode-" + +download_iso() { + local iso_path=$1 + local iso_name=$2 + local iso_url=$3 + + echo "[INFO] Downloading base ISO image: ${iso_name}..." + if [ ! -f "${iso_path}" ]; then + wget "${iso_url}" -O "${iso_path}" + fi + echo "[INFO] Download complete!" +} + +verify_download() { + local iso_path=$1 + local expected_shasum=$2 + + echo "[INFO] Verifying download..." + [[ "$(shasum -a 256 ${iso_path})" != "$expected_shasum" ]] && { + echo "[ERROR] Wrong shasum for ${iso_path}" + exit 1 + } + echo "[INFO] Verification complete!" +} + +clean_old_files() { + local iso_extraction_dir=$1 + local base_iso_prefix=$2 + + echo "[INFO] Cleaning old files..." + rm -rf "${iso_extraction_dir}º" + rm -rf "${base_iso_prefix}*" +} + +extract_iso() { + local iso_path=$1 + local extraction_target_dir=$2 + + echo "[INFO] Extracting the ISO..." + osirrox -indev "${iso_path}" -extract / "${extraction_target_dir}" +} + +# Using a 512-byte block size to ensure the entire Master Boot Record (MBR) is captured. +# The MBR contains boot code, the partition table, and a boot signature, all essential for creating bootable media. +# This ensures that the new ISO being created is bootable under different system setups +prepare_boot_process() { + local iso_path=$1 + local mbr_output_path=$2 + local block_size=512 + + echo "[INFO] Obtaining the MBR for hybrid ISO..." + dd if="${iso_path}" bs=${block_size} count=1 of="${mbr_output_path}" +} + +add_dappnode_files_to_iso_build() { + local iso_build_path=$1 + local workdir=$2 + + echo "[INFO] Creating necessary directories and copying files..." + mkdir -p ${iso_build_path}/dappnode + cp -r ${workdir}/scripts ${iso_build_path}/dappnode + cp -r ${workdir}/dappnode/* ${iso_build_path}/dappnode +} + +# TODO: Is this ok for Ubuntu? Check what this is for +handle_checksums() { + echo "Fix md5 sum..." + # shellcheck disable=SC2046 + md5sum $(find ! -name "md5sum.txt" ! -path "./isolinux/*" -type f) >md5sum.txt +} diff --git a/iso/scripts/generate_ISO.sh b/iso/scripts/generate_ISO.sh index 024e5397..bf241775 100755 --- a/iso/scripts/generate_ISO.sh +++ b/iso/scripts/generate_ISO.sh @@ -22,4 +22,8 @@ fi mkdir -p /usr/src/app/dappnode touch /usr/src/app/dappnode/iso_install.log -/usr/src/app/iso/scripts/generate_dappnode_iso_debian.sh +if [ "$BASE_OS" = "ubuntu" ]; then + /usr/src/app/iso/scripts/generate_dappnode_iso_ubuntu.sh +else + /usr/src/app/iso/scripts/generate_dappnode_iso_debian.sh +fi diff --git a/iso/scripts/generate_dappnode_iso_debian.sh b/iso/scripts/generate_dappnode_iso_debian.sh index 329995d2..2875a2cd 100755 --- a/iso/scripts/generate_dappnode_iso_debian.sh +++ b/iso/scripts/generate_dappnode_iso_debian.sh @@ -1,85 +1,98 @@ #!/bin/bash set -e +SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}") + +source ${SCRIPTS_DIR}/common_iso_generation.sh + # Source = https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.5.0-amd64-netinst.iso -ISO_NAME=debian-12.7.0-amd64-netinst.iso -ISO_PATH="/images/${ISO_NAME}" -ISO_URL=https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/ -SHASUM="8fde79cfc6b20a696200fc5c15219cf6d721e8feb367e9e0e33a79d1cb68fa83 ${ISO_PATH}" - -echo "Downloading debian ISO image: ${ISO_NAME}..." -if [ ! -f ${ISO_PATH} ]; then - wget ${ISO_URL}/${ISO_NAME} \ - -O ${ISO_PATH} -fi -echo "Done!" - -echo "Verifying download..." -[[ "$(shasum -a 256 ${ISO_PATH})" != "$SHASUM" ]] && { - echo "ERROR: wrong shasum" - exit 1 +BASE_ISO_NAME="debian-12.7.0-amd64-netinst.iso" +BASE_ISO_PATH="/images/${BASE_ISO_NAME}" +BASE_ISO_URL="https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/${BASE_ISO_NAME}" +BASE_ISO_SHASUM="8fde79cfc6b20a696200fc5c15219cf6d721e8feb367e9e0e33a79d1cb68fa83 ${BASE_ISO_PATH}" + +DAPPNODE_ISO_NAME="${DAPPNODE_ISO_PREFIX}${BASE_ISO_NAME}" +DAPPNODE_ISO_PATH="/images/${DAPPNODE_ISO_NAME}" + +customize_debian_preseed() { + local iso_build_path=$1 + local workdir=$2 + + echo "[INFO] Customizing preseed..." + + local tmp_initrd="/tmp/makeinitrd" + local install_dir="${iso_build_path}/install.amd" + local preseeds_dir="${workdir}/iso/preseeds" + + rm -rf "${tmp_initrd}" + mkdir -p "${tmp_initrd}" + + local preseed_name="preseed.cfg" + [[ $UNATTENDED == *"true"* ]] && preseed_name="preseed_unattended.cfg" + + local preseed_file="${preseeds_dir}/${preseed_name}" + + if [ ! -f "${preseed_file}" ]; then + echo "[ERROR] Preseed file not found: ${preseed_file}" + exit 1 + fi + + # Extract the initrd into a temporary directory + gunzip -c "${install_dir}/initrd.gz" | cpio -idum -D "${tmp_initrd}" || { + echo "[ERROR] Could not decompress and extract initrd" + exit 1 + } + + # Add the preseed file to the initrd + cp "${preseed_file}" "${tmp_initrd}/preseed.cfg" || { + echo "[ERROR] Could not copy preseed file" + exit 1 + } + + # 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" + exit 1 + } + + echo "[INFO] Preseed customization complete." +} + +configure_boot_menu() { + local iso_build_path=$1 + local workdir=$2 + + local boot_dir="${workdir}/iso/boot" + + echo "[INFO] Configuring the boot menu for Dappnode..." + cp ${boot_dir}/grub.cfg ${iso_build_path}/boot/grub/grub.cfg + cp ${boot_dir}/theme_1 ${iso_build_path}/boot/grub/theme/1 + cp ${boot_dir}/isolinux.cfg ${iso_build_path}/isolinux/isolinux.cfg + cp ${boot_dir}/menu.cfg ${iso_build_path}/isolinux/menu.cfg + cp ${boot_dir}/txt.cfg ${iso_build_path}/isolinux/txt.cfg + cp ${boot_dir}/splash.png ${iso_build_path}/isolinux/splash.png +} + +generate_debian_iso() { + local mbr_path=$1 + local iso_output_path=$2 + local iso_build_path=$3 + + echo "[INFO] Generating new ISO..." + + xorriso -as mkisofs -isohybrid-mbr ${mbr_path} \ + -c /isolinux/boot.cat -b /isolinux/isolinux.bin -no-emul-boot -boot-load-size 4 \ + -boot-info-table -eltorito-alt-boot -e /boot/grub/efi.img -no-emul-boot \ + -isohybrid-gpt-basdat -o "${iso_output_path}" ${iso_build_path} } -echo "Clean old files..." -rm -rf dappnode-isoº -rm -rf DappNode-debian-* - -echo "Extracting the iso..." -xorriso -osirrox on -indev /images/${ISO_NAME} \ - -extract / dappnode-iso - -echo "Obtaining the isohdpfx.bin for hybrid ISO..." -dd if=/images/${ISO_NAME} bs=432 count=1 \ - of=dappnode-iso/isolinux/isohdpfx.bin - -cd /usr/src/app/dappnode-iso # /usr/src/app/dappnode-iso - -echo "Downloading third-party packages..." -sed '1,/^\#\!ISOBUILD/!d' /usr/src/app/scripts/dappnode_install_pre.sh >/tmp/vars.sh -# shellcheck disable=SC1091 -source /tmp/vars.sh - -echo "Creating necessary directories and copying files..." -mkdir -p /usr/src/app/dappnode-iso/dappnode -cp -r /usr/src/app/scripts /usr/src/app/dappnode-iso/dappnode -cp -r /usr/src/app/dappnode/* /usr/src/app/dappnode-iso/dappnode - -echo "Customizing preseed..." -mkdir -p /tmp/makeinitrd -cd install.amd -cp initrd.gz /tmp/makeinitrd/ -if [[ $UNATTENDED == *"true"* ]]; then - cp /usr/src/app/iso/preseeds/preseed_unattended.cfg /tmp/makeinitrd/preseed.cfg -else - cp /usr/src/app/iso/preseeds/preseed.cfg /tmp/makeinitrd/preseed.cfg -fi -cd /tmp/makeinitrd -gunzip initrd.gz -cpio -id -H newc /tmp/list -echo "preseed.cfg" >>/tmp/list -rm initrd -cpio -o -H newc initrd -gzip initrd -cd - -mv /tmp/makeinitrd/initrd.gz ./initrd.gz -cd .. - -echo "Configuring the boot menu for DappNode..." -cp /usr/src/app/iso/boot/grub.cfg boot/grub/grub.cfg -cp /usr/src/app/iso/boot/theme_1 boot/grub/theme/1 -cp /usr/src/app/iso/boot/isolinux.cfg isolinux/isolinux.cfg -cp /usr/src/app/iso/boot/menu.cfg isolinux/menu.cfg -cp /usr/src/app/iso/boot/txt.cfg isolinux/txt.cfg -cp /usr/src/app/iso/boot/splash.png isolinux/splash.png - -echo "Fix md5 sum..." -# shellcheck disable=SC2046 -md5sum $(find ! -name "md5sum.txt" ! -path "./isolinux/*" -type f) >md5sum.txt - -echo "Generating new iso..." -xorriso -as mkisofs -isohybrid-mbr isolinux/isohdpfx.bin \ - -c isolinux/boot.cat -b isolinux/isolinux.bin -no-emul-boot -boot-load-size 4 \ - -boot-info-table -eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot \ - -isohybrid-gpt-basdat -o /images/DAppNode-debian-bookworm-amd64.iso . +download_iso "${BASE_ISO_PATH}" "${BASE_ISO_NAME}" "${BASE_ISO_URL}" +verify_download "${BASE_ISO_PATH}" "${BASE_ISO_SHASUM}" +clean_old_files "${ISO_BUILD_PATH}" "${DAPPNODE_ISO_PREFIX}" +extract_iso "${BASE_ISO_PATH}" "${ISO_BUILD_PATH}" +prepare_boot_process "${BASE_ISO_PATH}" "${ISO_BUILD_PATH}/isolinux/isohdpfx.bin" +add_dappnode_files_to_iso_build "${ISO_BUILD_PATH}" "${WORKDIR}" +customize_debian_preseed "${ISO_BUILD_PATH}" "${WORKDIR}" +configure_boot_menu "${ISO_BUILD_PATH}" "${WORKDIR}" +handle_checksums # TODO: Check if it fits both ubuntu and debian +generate_debian_iso "${ISO_BUILD_PATH}/isolinux/isohdpfx.bin" "${DAPPNODE_ISO_PATH}" "${ISO_BUILD_PATH}" diff --git a/iso/scripts/generate_dappnode_iso_ubuntu.sh b/iso/scripts/generate_dappnode_iso_ubuntu.sh new file mode 100755 index 00000000..574432b6 --- /dev/null +++ b/iso/scripts/generate_dappnode_iso_ubuntu.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -e + +SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}") + +source ${SCRIPTS_DIR}/common_iso_generation.sh + +BASE_ISO_NAME=ubuntu-24.04-live-server-amd64.iso +BASE_ISO_PATH="/images/${BASE_ISO_NAME}" +BASE_ISO_URL="https://labs.eif.urjc.es/mirror/ubuntu-releases/24.04/${BASE_ISO_NAME}" +BASE_ISO_SHASUM="8762f7e74e4d64d72fceb5f70682e6b069932deedb4949c6975d0f0fe0a91be3 ${BASE_ISO_PATH}" + +DAPPNODE_ISO_NAME="${DAPPNODE_ISO_PREFIX}${BASE_ISO_NAME}" +DAPPNODE_ISO_PATH="/images/${DAPPNODE_ISO_NAME}" + +get_efi_partition() { + local base_iso_path=$1 + local dest_efi_path=$2 + local block_size=512 + + local efi_start=$(fdisk -l ${base_iso_path} | grep 'Appended2' | awk '{print $2}') + local efi_end=$(fdisk -l ${base_iso_path} | grep 'Appended2' | awk '{print $3}') + local efi_size=$(expr ${efi_end} - ${efi_start} + 1) + + echo "[INFO] Obtaining the EFI partition image from ${efi_start} with size ${efi_size}..." + dd if=${base_iso_path} bs=${block_size} skip="$efi_start" count="$efi_size" of=${dest_efi_path} +} + +add_ubuntu_autoinstall() { + local preseeds_dir=$1 + local iso_build_path=$2 + + echo "[INFO] Adding preseed..." + if [[ $UNATTENDED == *"true"* ]]; then + cp ${preseeds_dir}/autoinstall_unattended.yaml ${iso_build_path}/autoinstall.yaml + else + cp ${preseeds_dir}/autoinstall.yaml ${iso_build_path}/autoinstall.yaml + fi +} + +configure_boot_menu() { + echo "[INFO] Configuring the boot menu for Dappnode..." + cp -r /usr/src/app/iso/boot/ubuntu/* ${ISO_BUILD_PATH}/boot/grub/ +} + +generate_ubuntu_iso() { + local mbr_path=$1 + local efi_path=$2 + local iso_output_path=$3 + local iso_build_path=$4 + + echo "[INFO] Creating the new Ubuntu ISO..." + mkisofs \ + -rational-rock -joliet -joliet-long -full-iso9660-filenames \ + -iso-level 3 -partition_offset 16 --grub2-mbr ${mbr_path} \ + --mbr-force-bootable -append_partition 2 0xEF ${efi_path} \ + -appended_part_as_gpt \ + -eltorito-catalog /boot.catalog \ + -eltorito-boot /boot/grub/i386-pc/eltorito.img -no-emul-boot -boot-load-size 4 \ + -boot-info-table --grub2-boot-info -eltorito-alt-boot --efi-boot '--interval:appended_partition_2:all::' -no-emul-boot \ + -o ${iso_output_path} ${iso_build_path} +} + +download_iso "${BASE_ISO_PATH}" "${BASE_ISO_NAME}" "${BASE_ISO_URL}" +verify_download "${BASE_ISO_PATH}" "${BASE_ISO_SHASUM}" +clean_old_files "${ISO_BUILD_PATH}" "${DAPPNODE_ISO_PREFIX}" +extract_iso "${BASE_ISO_PATH}" "${ISO_BUILD_PATH}" +prepare_boot_process "${BASE_ISO_PATH}" "${ISO_BUILD_PATH}/mbr" +get_efi_partition "${BASE_ISO_PATH}" "${ISO_BUILD_PATH}/efi" +add_dappnode_files_to_iso_build "${ISO_BUILD_PATH}" "${WORKDIR}" +add_ubuntu_autoinstall "/usr/src/app/iso/preseeds/ubuntu" "${ISO_BUILD_PATH}" +configure_boot_menu +handle_checksums +generate_ubuntu_iso "${ISO_BUILD_PATH}/mbr" "${ISO_BUILD_PATH}/efi" "${DAPPNODE_ISO_PATH}" "${ISO_BUILD_PATH}" diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 331a252f..b25c7bf5 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -11,6 +11,7 @@ LOGS_DIR="$DAPPNODE_DIR/logs" CONTENT_HASH_FILE="${DAPPNODE_CORE_DIR}/packages-content-hash.csv" LOGFILE="${LOGS_DIR}/dappnode_install.log" MOTD_FILE="/etc/motd" +UPDATE_MOTD_DIR="/etc/update-motd.d" DAPPNODE_PROFILE="${DAPPNODE_CORE_DIR}/.dappnode_profile" # Get URLs PROFILE_BRANCH=${PROFILE_BRANCH:-"master"} @@ -186,16 +187,48 @@ dappnode_core_load() { } customMotd() { - if [ -f ${MOTD_FILE} ]; then - cat <${MOTD_FILE} - ___ _ _ _ _ -| \ /_\ _ __ _ __| \| |___ __| |___ -| |) / _ \| '_ \ '_ \ . / _ \/ _ / -_) -|___/_/ \_\ .__/ .__/_|\_\___/\__,_\___| - |_| |_| -EOF - echo -e "$WELCOME_MESSAGE" >>"$MOTD_FILE" + + generateMotdText + + if [ -d "${UPDATE_MOTD_DIR}" ]; then + # Ubuntu configuration + modifyMotdGeneration + fi +} + +# Debian distros use /etc/motd plain text file +generateMotdText() { + # Check and create the MOTD file if it does not exist + if [ ! -f "${MOTD_FILE}" ]; then + touch "${MOTD_FILE}" fi + + # Write the ASCII art and welcome message as plain text + cat <<'EOF' >"${MOTD_FILE}" + ___ _ + | \ __ _ _ __ _ __ _ _ ___ __| |___ + | |) / _` | '_ \ '_ \ ' \/ _ \/ _` / -_) + |___/\__,_| .__/ .__/_||_\___/\__,_\___| + |_| |_| +EOF + echo -e "$WELCOME_MESSAGE" >>"${MOTD_FILE}" +} + +# Ubuntu distros use /etc/update-motd.d/ to generate the motd +modifyMotdGeneration() { + disabled_motd_dir="${UPDATE_MOTD_DIR}/disabled" + + mkdir -p "${disabled_motd_dir}" + + # Move all the files in /etc/update-motd.d/ to /etc/update-motd.d/disabled/ + # Except for the files listed in "files_to_keep" + files_to_keep="00-header 50-landscape-sysinfo 98-reboot-required" + for file in ${UPDATE_MOTD_DIR}/*; do + base_file=$(basename "${file}") + if [ -f "${file}" ] && ! echo "${files_to_keep}" | grep -qw "${base_file}"; then + mv "${file}" "${disabled_motd_dir}/" + fi + done } addSwap() { @@ -290,6 +323,30 @@ installExtraDpkg() { fi } +# The main user needs to be added to the docker group to be able to run docker commands without sudo +# Explained in: https://docs.docker.com/engine/install/linux-postinstall/ +addUserToDockerGroup() { + # UID is provided to the first regular user created in the system + USER=$(grep 1000 "/etc/passwd" | cut -f 1 -d:) + + # If USER is not found, warn the user and return + if [ -z "$USER" ]; then + echo -e "\e[33mWARN: Default user not found. Could not add it to the docker group.\e[0m" 2>&1 | tee -a $LOGFILE + return + fi + + if groups "$USER" | grep &>/dev/null '\bdocker\b'; then + echo -e "\e[32mUser $USER is already in the docker group\e[0m" 2>&1 | tee -a $LOGFILE + return + fi + + # This step is already done in the dappnode_install_pre.sh script, + # but it's not working in the Ubuntu ISO because the late-commands in the autoinstall.yaml + # file are executed before the user is created. + usermod -aG docker "$USER" + echo -e "\e[32mUser $USER added to the docker group\e[0m" 2>&1 | tee -a $LOGFILE +} + ############################################## #### SCRIPT START #### ############################################## @@ -315,9 +372,12 @@ if [ "$ARCH" == "amd64" ]; then installSgx echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE - installExtraDpkg + installExtraDpkg # TODO: Why is this being called twice? fi +echo -e "\e[32mAdding user to docker group...\e[0m" 2>&1 | tee -a $LOGFILE +addUserToDockerGroup + echo -e "\e[32mCreating dncore_network if needed...\e[0m" 2>&1 | tee -a $LOGFILE docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a $LOGFILE diff --git a/scripts/dappnode_install_pre.sh b/scripts/dappnode_install_pre.sh index 92594051..d6cf4739 100755 --- a/scripts/dappnode_install_pre.sh +++ b/scripts/dappnode_install_pre.sh @@ -6,8 +6,6 @@ DAPPNODE_DIR="/usr/src/dappnode" LOGS_DIR="$DAPPNODE_DIR/logs" lsb_dist="$(. /etc/os-release && echo "$ID")" -#!ISOBUILD Do not modify, variables above imported for ISO build - detect_installation_type() { if [ -f "${DAPPNODE_DIR}/iso_install.log" ]; then LOG_FILE="${LOGS_DIR}/iso_install.log" @@ -22,21 +20,30 @@ detect_installation_type() { add_docker_repo() { apt-get update -y apt-get remove -y docker docker-engine docker.io containerd runc | tee -a $LOG_FILE - apt-get install -y ca-certificates curl gnupg lsb-release | tee -a $LOG_FILE - mkdir -p /etc/apt/keyrings && chmod -R 0755 /etc/apt/keyrings - curl -fsSL "https://download.docker.com/linux/${lsb_dist}/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg + + # Add Docker GPG key + apt-get install -y ca-certificates curl lsb-release | tee -a $LOG_FILE + install -m 0755 -d /etc/apt/keyrings + curl -fsSL "https://download.docker.com/linux/${lsb_dist}/gpg" -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$lsb_dist $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list >/dev/null + + # Add the repository to APT sources + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$lsb_dist $(lsb_release -cs) stable" | + tee /etc/apt/sources.list.d/docker.list >/dev/null + + apt-get update -y } # DOCKER INSTALLATION install_docker() { apt-get update -y - apt-get install -y docker-ce docker-ce-cli containerd.io | tee -a $LOG_FILE + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin | tee -a $LOG_FILE # Ensure xz is installed [ -f "/usr/bin/xz" ] || (apt-get install -y xz-utils) + # Not working in Ubuntu ISO because the user is not created before executing late-commands USER=$(grep 1000 "/etc/passwd" | cut -f 1 -d:) [ -z "$USER" ] || usermod -aG docker "$USER" @@ -102,6 +109,34 @@ host_update() { apt-get -y upgrade 2>&1 | tee -a $LOG_FILE } +check_ubuntu_connectivity() { + { netplan get | grep "dhcp4: true" &>/dev/null; } || { + echo "Interfaces not found" + exit 1 + } +} + +check_debian_connectivity() { + { [ -f /etc/network/interfaces ] && grep "iface en.* inet dhcp" /etc/network/interfaces &>/dev/null; } || { + echo "Interfaces not found" + exit 1 + } +} + +add_debian_missing_interfaces() { + # shellcheck disable=SC2013 + for IFACE in $(grep "en.*" /usr/src/dappnode/hotplug); do + # shellcheck disable=SC2143 + if [[ $(grep -L "$IFACE" /etc/network/interfaces) ]]; then + { + echo "# $IFACE" + echo "allow-hotplug $IFACE" + echo "iface $IFACE inet dhcp" + } >>/etc/network/interfaces + fi + done +} + ############################################## #### SCRIPT START #### ############################################## @@ -147,23 +182,10 @@ else install_lsof 2>&1 | tee -a $LOG_FILE fi -#Check connectivity -{ [ -f /etc/network/interfaces ] && grep "iface en.* inet dhcp" /etc/network/interfaces &>/dev/null; } || { - echo "Interfaces not found" - exit 1 -} - -## Add missing interfaces -if [ -f /usr/src/dappnode/hotplug ]; then - # shellcheck disable=SC2013 - for IFACE in $(grep "en.*" /usr/src/dappnode/hotplug); do - # shellcheck disable=SC2143 - if [[ $(grep -L "$IFACE" /etc/network/interfaces) ]]; then - { - echo "# $IFACE" - echo "allow-hotplug $IFACE" - echo "iface $IFACE inet dhcp" - } >>/etc/network/interfaces - fi - done +## Add or Update Network Configuration Based on OS +if [ "$lsb_dist" = "ubuntu" ]; then + check_ubuntu_connectivity +else + check_debian_connectivity + add_debian_missing_interfaces fi