From bf455c6e14385cc2fae67bd30a10452d0d181d1b Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Fri, 27 Feb 2026 09:01:11 +0100 Subject: [PATCH 01/11] Enhance macOS compatibility in installation script - Added checks to ensure the script is executed, not sourced. - Improved OS detection and handling for macOS and Linux. - Updated directory paths for macOS. - Introduced cross-platform helper functions for downloading files and modifying files. - Added functionality to patch the .dappnode_profile for macOS compatibility. - Enhanced user shell configuration to source the profile correctly. - Cleaned up rc.local for Linux installations. - Improved logging and user feedback during installation. --- scripts/dappnode_install.sh | 335 ++++++++++++++++++++++++++++-------- 1 file changed, 266 insertions(+), 69 deletions(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index fc58ff1..0ecf75c 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -1,18 +1,59 @@ #!/bin/bash +# This installer is written for bash. It's safe to *run it from zsh* (it will execute via bash +# thanks to the shebang), but users sometimes invoke it as `zsh ./script.sh` or `source ./script.sh`. +# - If sourced, bail out (sourcing would pollute the current shell and can break it). +# - If invoked by a non-bash shell, re-exec with bash before hitting bash-specific builtins. +if (return 0 2>/dev/null); then + echo "This script must be executed, not sourced. Run: bash $0" + return 1 +fi + +if [ -z "${BASH_VERSION:-}" ]; then + exec /usr/bin/env bash "$0" "$@" +fi + +set -eo pipefail + +# Enable alias expansion in non-interactive bash scripts. +# Required so commands like `dappnode_wireguard` (defined as aliases in `.dappnode_profile`) work. +shopt -s expand_aliases + +################## +# OS DETECTION # +################## +OS_TYPE="$(uname -s)" +IS_MACOS=false +IS_LINUX=false +if [[ "$OS_TYPE" == "Darwin" ]]; then + IS_MACOS=true +elif [[ "$OS_TYPE" == "Linux" ]]; then + IS_LINUX=true +else + echo "Unsupported operating system: $OS_TYPE" + exit 1 +fi + ############# # VARIABLES # ############# -# Dirs -DAPPNODE_DIR="/usr/src/dappnode" +# Dirs - macOS uses $HOME/dappnode, Linux uses /usr/src/dappnode +if $IS_MACOS; then + DAPPNODE_DIR="$HOME/dappnode" +else + DAPPNODE_DIR="/usr/src/dappnode" +fi DAPPNODE_CORE_DIR="${DAPPNODE_DIR}/DNCORE" LOGS_DIR="$DAPPNODE_DIR/logs" # Files 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" +# Linux-only paths +if $IS_LINUX; then + MOTD_FILE="/etc/motd" + UPDATE_MOTD_DIR="/etc/update-motd.d" +fi # Get URLs PROFILE_BRANCH=${PROFILE_BRANCH:-"master"} IPFS_ENDPOINT=${IPFS_ENDPOINT:-"http://ipfs.io"} @@ -20,12 +61,75 @@ IPFS_ENDPOINT=${IPFS_ENDPOINT:-"http://ipfs.io"} PROFILE_URL=${PROFILE_URL:-"https://github.com/dappnode/DAppNode/releases/latest/download/dappnode_profile.sh"} DAPPNODE_ACCESS_CREDENTIALS="${DAPPNODE_DIR}/scripts/dappnode_access_credentials.sh" DAPPNODE_ACCESS_CREDENTIALS_URL="https://github.com/dappnode/DAppNode/releases/latest/download/dappnode_access_credentials.sh" -WGET="wget -q --show-progress --progress=bar:force" -SWGET="wget -q -O-" # Other -CONTENT_HASH_PKGS=(geth besu nethermind erigon prysm teku lighthouse nimbus lodestar) -ARCH=$(dpkg --print-architecture) -WELCOME_MESSAGE="\nChoose a way to connect to your DAppNode, then go to \e[1mhttp://my.dappnode\e[0m\n\n\e[1m- Wifi\e[0m\t\tScan and connect to DAppNodeWIFI. Get wifi credentials with \e[32mdappnode_wifi\e[0m\n\n\e[1m- Local Proxy\e[0m\tConnect to the same router as your DAppNode. Then go to \e[1mhttp://dappnode.local\e[0m\n\n\e[1m- Wireguard\e[0m\tDownload Wireguard app on your device. Get your dappnode wireguard credentials with \e[32mdappnode_wireguard\e[0m\n\n\e[1m- Open VPN\e[0m\tDownload OPen VPN app on your device. Get your openVPN creds with \e[32mdappnode_openvpn\e[0m\n\n\nTo see a full list of commands available execute \e[32mdappnode_help\e[0m\n" + +# Architecture detection (cross-platform) +if $IS_MACOS; then + ARCH=$(uname -m) + [[ "$ARCH" == "x86_64" ]] && ARCH="amd64" + # arm64 is already correct for Apple Silicon +else + ARCH=$(dpkg --print-architecture) +fi + +############################## +# Cross-platform Helpers # +############################## + +# Download a file: download_file +download_file() { + local dest="$1" + local url="$2" + if $IS_MACOS; then + curl -sL -o "$dest" "$url" + else + wget -q --show-progress --progress=bar:force -O "$dest" "$url" + fi +} + +# Download content to stdout: download_stdout +download_stdout() { + local url="$1" + if $IS_MACOS; then + curl -sL "$url" + else + wget -q -O- "$url" + fi +} + +# Cross-platform in-place sed (macOS requires '' after -i) +sed_inplace() { + if $IS_MACOS; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +############################## +# Compose Patching Helpers # +############################## + +# Remove journald logging from compose files (not supported on macOS Docker Desktop) +remove_logging_section() { + local file="$1" + sed_inplace '/logging/d;/journald/d' "$file" +} + +# Replace Linux paths with macOS paths in compose files +patch_compose_paths() { + local file="$1" + sed_inplace "s|/usr/src/dappnode|${DAPPNODE_DIR}|g" "$file" +} + +# Patch .dappnode_profile for macOS compatibility +patch_profile_for_macos() { + local profile="$1" + # Replace GNU find -printf with POSIX-compatible -exec printf + sed_inplace 's/-printf "-f %p "/-exec printf -- "-f %s " {} \\;/' "$profile" + # Replace hardcoded Linux paths with $HOME-based paths + sed_inplace 's|/usr/src/dappnode|\$HOME/dappnode|g' "$profile" +} # Clean if update if [ "$UPDATE" = true ]; then @@ -49,6 +153,11 @@ mkdir -p $LOGS_DIR # TEMPORARY: think a way to integrate flags instead of use files to detect installation type is_iso_install() { + # ISO installs are Linux-only + if $IS_MACOS; then + IS_ISO_INSTALL=false + return + fi # Check old and new location of iso_install.log if [ -f "${DAPPNODE_DIR}/iso_install.log" ] || [ -f "${DAPPNODE_DIR}/logs/iso_install.log" ]; then IS_ISO_INSTALL=true @@ -126,7 +235,13 @@ if [[ -n "$STATIC_IP" ]]; then fi # Loads profile, if not exists it means it is script install so the versions will be fetched from the latest profile -[ -f $DAPPNODE_PROFILE ] || ${WGET} -O ${DAPPNODE_PROFILE} ${PROFILE_URL} +[ -f "$DAPPNODE_PROFILE" ] || download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" + +# Patch profile for macOS compatibility (replace GNU-isms and hardcoded Linux paths) +if $IS_MACOS; then + patch_profile_for_macos "$DAPPNODE_PROFILE" +fi + # shellcheck disable=SC1090 source "${DAPPNODE_PROFILE}" @@ -153,6 +268,10 @@ dappnode_core_build() { for comp in "${PKGS[@]}"; do ver="${comp}_VERSION" if [[ ${!ver} == dev:* ]]; then + if $IS_MACOS; then + echo "Development builds (dev:*) are not supported on macOS." + exit 1 + fi echo "Cloning & building DNP_${comp}..." if ! dpkg -s git >/dev/null 2>&1; then apt-get install -y git @@ -182,15 +301,28 @@ dappnode_core_download() { for comp in "${PKGS[@]}"; do ver="${comp}_VERSION" if [[ ${!ver} != dev:* ]]; then - # Download DAppNode Core Images if it's needed + local file_var="${comp}_FILE" + local url_var="${comp}_URL" + local yml_file_var="${comp}_YML_FILE" + local yml_var="${comp}_YML" + local manifest_file_var="${comp}_MANIFEST_FILE" + local manifest_var="${comp}_MANIFEST" + + # Download DAppNode Core Images if needed echo "Downloading ${comp} tar..." - eval "[ -f \$${comp}_FILE ] || $WGET -O \$${comp}_FILE \$${comp}_URL || exit 1" - # Download DAppNode Core docker-compose yml files if it's needed + [ -f "${!file_var}" ] || download_file "${!file_var}" "${!url_var}" || exit 1 + # Download DAppNode Core docker-compose yml files if needed echo "Downloading ${comp} yml..." - eval "[ -f \$${comp}_YML_FILE ] || $WGET -O \$${comp}_YML_FILE \$${comp}_YML || exit 1" - # Download DAppNode Core manifest files if it's needed + [ -f "${!yml_file_var}" ] || download_file "${!yml_file_var}" "${!yml_var}" || exit 1 + # Download DAppNode Core manifest files if needed echo "Downloading ${comp} manifest..." - eval "[ -f \$${comp}_MANIFEST_FILE ] || $WGET -O \$${comp}_MANIFEST_FILE \$${comp}_MANIFEST || exit 1" + [ -f "${!manifest_file_var}" ] || download_file "${!manifest_file_var}" "${!manifest_var}" || exit 1 + + # macOS: patch compose files for Docker Desktop compatibility + if $IS_MACOS; then + remove_logging_section "${!yml_file_var}" + patch_compose_paths "${!yml_file_var}" + fi fi done } @@ -205,7 +337,6 @@ dappnode_core_load() { } customMotd() { - generateMotdText if [ -d "${UPDATE_MOTD_DIR}" ]; then @@ -216,6 +347,8 @@ customMotd() { # Debian distros use /etc/motd plain text file generateMotdText() { + local welcome_message + # Check and create the MOTD file if it does not exist if [ ! -f "${MOTD_FILE}" ]; then touch "${MOTD_FILE}" @@ -229,7 +362,8 @@ generateMotdText() { |___/\__,_| .__/ .__/_||_\___/\__,_\___| |_| |_| EOF - echo -e "$WELCOME_MESSAGE" >>"${MOTD_FILE}" + welcome_message="\nChoose a way to connect to your DAppNode, then go to \e[1mhttp://my.dappnode\e[0m\n\n\e[1m- Wifi\e[0m\t\tScan and connect to DAppNodeWIFI. Get wifi credentials with \e[32mdappnode_wifi\e[0m\n\n\e[1m- Local Proxy\e[0m\tConnect to the same router as your DAppNode. Then go to \e[1mhttp://dappnode.local\e[0m\n\n\e[1m- Wireguard\e[0m\tDownload Wireguard app on your device. Get your dappnode wireguard credentials with \e[32mdappnode_wireguard\e[0m\n\n\e[1m- Open VPN\e[0m\tDownload OPen VPN app on your device. Get your openVPN creds with \e[32mdappnode_openvpn\e[0m\n\n\nTo see a full list of commands available execute \e[32mdappnode_help\e[0m\n" + printf "%b" "$welcome_message" >>"${MOTD_FILE}" } # Ubuntu distros use /etc/update-motd.d/ to generate the motd @@ -269,6 +403,50 @@ addSwap() { fi } +# Add .dappnode_profile sourcing to the user's default shell configuration +add_profile_to_shell() { + local user_home + local shell_configs + + if $IS_MACOS; then + user_home="$HOME" + # macOS defaults to zsh + shell_configs=(".zshrc" ".zprofile") + else + # Linux: determine user home from /etc/passwd + local user_name + user_name=$(grep 1000 /etc/passwd | cut -f 1 -d:) + if [ -n "$user_name" ]; then + user_home="/home/$user_name" + else + user_home="/root" + fi + shell_configs=(".profile" ".bashrc") + fi + + for config_file in "${shell_configs[@]}"; do + local config_path="${user_home}/${config_file}" + local source_line + + # .profile may be evaluated by /bin/sh (dash on Debian/Ubuntu) where `source` is not valid. + # Use POSIX '.' there; use `source` elsewhere (bash/zsh). + if [ "$config_file" = ".profile" ]; then + source_line="[ -f \"${DAPPNODE_PROFILE}\" ] && . \"${DAPPNODE_PROFILE}\"" + else + source_line="[ -f \"${DAPPNODE_PROFILE}\" ] && source \"${DAPPNODE_PROFILE}\"" + fi + + # Create config file if it doesn't exist + [ ! -f "$config_path" ] && touch "$config_path" + # Add profile sourcing if not already present + if ! grep -q "${DAPPNODE_PROFILE}" "$config_path"; then + echo "######## DAPPNODE PROFILE ########" >> "$config_path" + echo "$source_line" >> "$config_path" + echo "" >> "$config_path" + fi + done +} + dappnode_start() { echo -e "\e[32mDAppNode starting...\e[0m" 2>&1 | tee -a $LOGFILE # shellcheck disable=SC1090 @@ -282,33 +460,20 @@ dappnode_start() { done echo -e "\e[32mDAppNode started\e[0m" 2>&1 | tee -a $LOGFILE - # Show credentials to the user on login - USER=$(grep 1000 /etc/passwd | cut -f 1 -d:) - [ -n "$USER" ] && USER_HOME=/home/$USER || USER_HOME=/root + # Add profile sourcing to user's shell configuration + add_profile_to_shell - # Add profile sourcing to both .profile and .bashrc for maximum compatibility - for config_file in .profile .bashrc; do - CONFIG_PATH="$USER_HOME/$config_file" - - # Create config file if it doesn't exist - [ ! -f "$CONFIG_PATH" ] && touch "$CONFIG_PATH" - - # Add profile sourcing if not already present - if ! grep -q "${DAPPNODE_PROFILE}" "$CONFIG_PATH"; then - echo "######## DAPPNODE PROFILE ########" >>"$CONFIG_PATH" - echo -e "source ${DAPPNODE_PROFILE}\n" >>"$CONFIG_PATH" - fi - done - - # Remove return from profile - sed -i '/return/d' $DAPPNODE_PROFILE | tee -a $LOGFILE + # Remove return from profile so it can be sourced in login shells + sed_inplace '/return/d' "$DAPPNODE_PROFILE" # Download access_credentials script - [ -f $DAPPNODE_ACCESS_CREDENTIALS ] || ${WGET} -O ${DAPPNODE_ACCESS_CREDENTIALS} ${DAPPNODE_ACCESS_CREDENTIALS_URL} + [ -f "$DAPPNODE_ACCESS_CREDENTIALS" ] || download_file "${DAPPNODE_ACCESS_CREDENTIALS}" "${DAPPNODE_ACCESS_CREDENTIALS_URL}" - # Delete dappnode_install.sh execution from rc.local if exists, and is not the unattended firstboot - if [ -f "/etc/rc.local" ] && [ ! -f "/usr/src/dappnode/.firstboot" ]; then - sed -i '/\/usr\/src\/dappnode\/scripts\/dappnode_install.sh/d' /etc/rc.local 2>&1 | tee -a $LOGFILE + # Linux-only: clean up rc.local + if $IS_LINUX; then + if [ -f "/etc/rc.local" ] && [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then + sed_inplace '/\/usr\/src\/dappnode\/scripts\/dappnode_install.sh/d' /etc/rc.local 2>&1 | tee -a $LOGFILE + fi fi # Display help message to the user @@ -323,8 +488,9 @@ installExtraDpkg() { grabContentHashes() { if [ ! -f "${CONTENT_HASH_FILE}" ]; then - for comp in "${CONTENT_HASH_PKGS[@]}"; do - CONTENT_HASH=$(eval "${SWGET}" https://github.com/dappnode/DAppNodePackage-"${comp}"/releases/latest/download/content-hash) + local content_hash_pkgs=(geth besu nethermind erigon prysm teku lighthouse nimbus lodestar) + for comp in "${content_hash_pkgs[@]}"; do + CONTENT_HASH=$(download_stdout "https://github.com/dappnode/DAppNodePackage-${comp}/releases/latest/download/content-hash") if [ -z "$CONTENT_HASH" ]; then echo "ERROR! Failed to find content hash of ${comp}." 2>&1 | tee -a $LOGFILE exit 1 @@ -382,31 +548,35 @@ echo -e "\e[32m\n##############################################\e[0m" 2>&1 | tee echo -e "\e[32m#### DAPPNODE INSTALLER ####\e[0m" 2>&1 | tee -a $LOGFILE echo -e "\e[32m##############################################\e[0m" 2>&1 | tee -a $LOGFILE -echo -e "\e[32mCreating swap memory...\e[0m" 2>&1 | tee -a $LOGFILE -addSwap +# --- Linux-only setup steps --- +if $IS_LINUX; then + echo -e "\e[32mCreating swap memory...\e[0m" 2>&1 | tee -a $LOGFILE + addSwap + + echo -e "\e[32mCustomizing login...\e[0m" 2>&1 | tee -a $LOGFILE + customMotd -echo -e "\e[32mCustomizing login...\e[0m" 2>&1 | tee -a $LOGFILE -customMotd + echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE + installExtraDpkg -echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE -installExtraDpkg + echo -e "\e[32mGrabbing latest content hashes...\e[0m" 2>&1 | tee -a $LOGFILE + grabContentHashes -echo -e "\e[32mGrabbing latest content hashes...\e[0m" 2>&1 | tee -a $LOGFILE -grabContentHashes + if [ "$ARCH" == "amd64" ]; then + echo -e "\e[32mInstalling SGX modules...\e[0m" 2>&1 | tee -a $LOGFILE + installSgx -if [ "$ARCH" == "amd64" ]; then - echo -e "\e[32mInstalling SGX modules...\e[0m" 2>&1 | tee -a $LOGFILE - installSgx + echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE + installExtraDpkg # TODO: Why is this being called twice? + fi - echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE - installExtraDpkg # TODO: Why is this being called twice? + echo -e "\e[32mAdding user to docker group...\e[0m" 2>&1 | tee -a $LOGFILE + addUserToDockerGroup fi -echo -e "\e[32mAdding user to docker group...\e[0m" 2>&1 | tee -a $LOGFILE -addUserToDockerGroup - +# --- Common steps (Linux and macOS) --- 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 +docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a $LOGFILE || true echo -e "\e[32mBuilding DAppNode Core if needed...\e[0m" 2>&1 | tee -a $LOGFILE dappnode_core_build @@ -417,18 +587,45 @@ dappnode_core_download echo -e "\e[32mLoading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE dappnode_core_load -if [ ! -f "/usr/src/dappnode/.firstboot" ]; then +# --- Start DAppNode --- +if $IS_LINUX; then + if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then + echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE + dappnode_start + fi + + # Run test in interactive terminal (first boot only) + if [ -f "${DAPPNODE_DIR}/.firstboot" ]; then + apt-get update + apt-get install -y kbd + openvt -s -w -- sudo -u root "${DAPPNODE_DIR}/scripts/dappnode_test_install.sh" + exit 0 + fi +fi + +if $IS_MACOS; then echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE dappnode_start -fi -# Run test in interactive terminal -if [ -f "/usr/src/dappnode/.firstboot" ]; then - # ensure openvt is installed prior to using it - apt-get update - apt-get install -y kbd - openvt -s -w -- sudo -u root /usr/src/dappnode/scripts/dappnode_test_install.sh - exit 0 + echo -e "\n\e[33mWaiting for VPN initialization...\e[0m" + sleep 10 + + echo -e "\n\e[32m##############################################\e[0m" + echo -e "\e[32m# DAppNode VPN Access Credentials #\e[0m" + echo -e "\e[32m##############################################\e[0m" + echo -e "\n\e[1mYour DAppNode is ready! Connect using your preferred VPN client.\e[0m" + echo -e "\e[1mChoose either Wireguard (recommended) or OpenVPN and import the\e[0m" + echo -e "\e[1mcredentials below into your VPN app to access your DAppNode.\e[0m\n" + + echo -e "\e[1m--- Wireguard ---\e[0m" + dappnode_wireguard --localhost 2>&1 || \ + echo -e "\e[33mWireguard credentials not yet available. Try later with: dappnode_wireguard --localhost\e[0m" + + echo -e "\n\e[1m--- OpenVPN ---\e[0m" + dappnode_openvpn_get dappnode_admin --localhost 2>&1 || \ + echo -e "\e[33mOpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost\e[0m" + + echo -e "\n\e[32mImport the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode\e[0m" fi exit 0 From f4b69b96cba83e4eb04dcc149c54f73181f466fc Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Fri, 27 Feb 2026 10:15:21 +0100 Subject: [PATCH 02/11] Add macOS support for installation scripts and update DAppNode core start function --- .github/workflows/test.yml | 15 ++++++++++++++- scripts/dappnode_install.sh | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d7c85e1..70395d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,13 +31,26 @@ jobs: run: | sudo /bin/bash ./scripts/dappnode_uninstall.sh y + scripts-macos: + name: test scripts (macOS) + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install Docker + uses: docker-practice/actions-setup-docker@master # TODO: use pre-install script instead + - name: Install DAppNode + run: | + /bin/bash ./scripts/dappnode_install.sh + iso: name: test Debian and Ubuntu ISO runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Create Debian ISO - name: create Debian ISO diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 0ecf75c..2d09102 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -122,6 +122,7 @@ patch_compose_paths() { sed_inplace "s|/usr/src/dappnode|${DAPPNODE_DIR}|g" "$file" } +# TODO: remove once profile macos-compatibility published # Patch .dappnode_profile for macOS compatibility patch_profile_for_macos() { local profile="$1" @@ -238,6 +239,7 @@ fi [ -f "$DAPPNODE_PROFILE" ] || download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" # Patch profile for macOS compatibility (replace GNU-isms and hardcoded Linux paths) +# TODO: remove once profile macos-compatibility published if $IS_MACOS; then patch_profile_for_macos "$DAPPNODE_PROFILE" fi @@ -447,17 +449,11 @@ add_profile_to_shell() { done } -dappnode_start() { +dappnode_core_start() { echo -e "\e[32mDAppNode starting...\e[0m" 2>&1 | tee -a $LOGFILE - # shellcheck disable=SC1090 - source "${DAPPNODE_PROFILE}" >/dev/null 2>&1 - - # Execute `compose-up` independently - # To execute `compose-up` against more than 1 compose, composes files must share compose file version (e.g 3.5) - for comp in "${DNCORE_YMLS_ARRAY[@]}"; do - docker compose -f "$comp" up -d 2>&1 | tee -a $LOGFILE - echo "${comp} started" 2>&1 | tee -a $LOGFILE - done + + # Use DNCORE_YMLS from the profile (populated after re-sourcing post-download) + docker compose $DNCORE_YMLS up -d 2>&1 | tee -a $LOGFILE echo -e "\e[32mDAppNode started\e[0m" 2>&1 | tee -a $LOGFILE # Add profile sourcing to user's shell configuration @@ -584,6 +580,10 @@ dappnode_core_build echo -e "\e[32mDownloading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE dappnode_core_download +# Re-source profile now that compose files exist, so DNCORE_YMLS is populated +# shellcheck disable=SC1090 +source "${DAPPNODE_PROFILE}" + echo -e "\e[32mLoading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE dappnode_core_load @@ -591,7 +591,7 @@ dappnode_core_load if $IS_LINUX; then if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE - dappnode_start + dappnode_core_start fi # Run test in interactive terminal (first boot only) @@ -605,7 +605,7 @@ fi if $IS_MACOS; then echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE - dappnode_start + dappnode_core_start echo -e "\n\e[33mWaiting for VPN initialization...\e[0m" sleep 10 From 09ffb8130f4af3878226d979a283f8d1412290e3 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Fri, 27 Feb 2026 10:25:30 +0100 Subject: [PATCH 03/11] Normalize component names to lowercase in download URL and file paths --- scripts/dappnode_install.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 2d09102..b8ec11d 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -258,12 +258,13 @@ for comp in "${PKGS[@]}"; do if [[ ${!ver} == /ipfs/* ]]; then DOWNLOAD_URL="${IPFS_ENDPOINT}/api/v0/cat?arg=${!ver%:*}" fi - eval "${comp}_URL=\"${DOWNLOAD_URL}/${comp,,}.dnp.dappnode.eth_${!ver##*:}_linux-${ARCH}.txz\"" + comp_lower=$(echo "$comp" | tr '[:upper:]' '[:lower:]') + eval "${comp}_URL=\"${DOWNLOAD_URL}/${comp_lower}.dnp.dappnode.eth_${!ver##*:}_linux-${ARCH}.txz\"" eval "${comp}_YML=\"${DOWNLOAD_URL}/docker-compose.yml\"" eval "${comp}_MANIFEST=\"${DOWNLOAD_URL}/dappnode_package.json\"" - eval "${comp}_YML_FILE=\"${DAPPNODE_CORE_DIR}/docker-compose-${comp,,}.yml\"" - eval "${comp}_FILE=\"${DAPPNODE_CORE_DIR}/${comp,,}.dnp.dappnode.eth_${!ver##*:}_linux-${ARCH}.txz\"" - eval "${comp}_MANIFEST_FILE=\"${DAPPNODE_CORE_DIR}/dappnode_package-${comp,,}.json\"" + eval "${comp}_YML_FILE=\"${DAPPNODE_CORE_DIR}/docker-compose-${comp_lower}.yml\"" + eval "${comp}_FILE=\"${DAPPNODE_CORE_DIR}/${comp_lower}.dnp.dappnode.eth_${!ver##*:}_linux-${ARCH}.txz\"" + eval "${comp}_MANIFEST_FILE=\"${DAPPNODE_CORE_DIR}/dappnode_package-${comp_lower}.json\"" done dappnode_core_build() { From 84871ee7944b454c46ec63ded70855bd4d3111c5 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Fri, 27 Feb 2026 10:29:21 +0100 Subject: [PATCH 04/11] Update macOS test script to install Docker using Homebrew --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70395d2..bf4cc5f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,10 @@ jobs: - name: Checkout uses: actions/checkout@v6 - name: Install Docker - uses: docker-practice/actions-setup-docker@master # TODO: use pre-install script instead + run: | + brew install --cask docker + open -a Docker + while ! docker info > /dev/null 2>&1; do sleep 2; done # TODO: use pre-install script instead - name: Install DAppNode run: | /bin/bash ./scripts/dappnode_install.sh From 4d4d36375cfe3cdecd60acbf35c455a6f14d340c Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Fri, 27 Feb 2026 10:29:52 +0100 Subject: [PATCH 05/11] Normalize component names to lowercase in Docker image loading logic --- scripts/dappnode_install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index b8ec11d..0b5a3f9 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -334,7 +334,8 @@ dappnode_core_load() { for comp in "${PKGS[@]}"; do ver="${comp}_VERSION" if [[ ${!ver} != dev:* ]]; then - eval "[ ! -z \$(docker images -q ${comp,,}.dnp.dappnode.eth:${!ver##*:}) ] || docker load -i \$${comp}_FILE 2>&1 | tee -a \$LOGFILE" + comp_lower=$(echo "$comp" | tr '[:upper:]' '[:lower:]') + eval "[ ! -z \$(docker images -q ${comp_lower}.dnp.dappnode.eth:${!ver##*:}) ] || docker load -i \$${comp}_FILE 2>&1 | tee -a \$LOGFILE" fi done } From b4229ef779097ed10f9b6ad8f4c90fa7bc2e4530 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Fri, 27 Feb 2026 10:46:31 +0100 Subject: [PATCH 06/11] Remove macOS test scripts from workflow configuration --- .github/workflows/test.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bf4cc5f..174f84c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,22 +31,6 @@ jobs: run: | sudo /bin/bash ./scripts/dappnode_uninstall.sh y - scripts-macos: - name: test scripts (macOS) - runs-on: macos-latest - - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Install Docker - run: | - brew install --cask docker - open -a Docker - while ! docker info > /dev/null 2>&1; do sleep 2; done # TODO: use pre-install script instead - - name: Install DAppNode - run: | - /bin/bash ./scripts/dappnode_install.sh - iso: name: test Debian and Ubuntu ISO runs-on: ubuntu-latest From 68d27362eeb2b4ec9d25c59a18ff85aedb1e448f Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Mon, 2 Mar 2026 10:24:30 +0100 Subject: [PATCH 07/11] Update IPFS endpoint and enhance logging in installation script --- scripts/dappnode_install.sh | 177 +++++++++++++++++++++++++++--------- 1 file changed, 135 insertions(+), 42 deletions(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 0b5a3f9..1a6d95b 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -56,7 +56,7 @@ if $IS_LINUX; then fi # Get URLs PROFILE_BRANCH=${PROFILE_BRANCH:-"master"} -IPFS_ENDPOINT=${IPFS_ENDPOINT:-"http://ipfs.io"} +IPFS_ENDPOINT=${IPFS_ENDPOINT:-"https://ipfs-gateway-dev.dappnode.net"} # PROFILE_URL env is used to fetch the core packages versions that will be used to build the release in script install method PROFILE_URL=${PROFILE_URL:-"https://github.com/dappnode/DAppNode/releases/latest/download/dappnode_profile.sh"} DAPPNODE_ACCESS_CREDENTIALS="${DAPPNODE_DIR}/scripts/dappnode_access_credentials.sh" @@ -72,6 +72,21 @@ else ARCH=$(dpkg --print-architecture) fi +# Color output helpers +color_echo() { + local color="$1"; shift + if $IS_LINUX; then + case "$color" in + green) code="\e[32m" ;; + yellow) code="\e[33m" ;; + *) code="" ;; + esac + echo -e "${code}$*\e[0m" + else + echo "$*" + fi +} + ############################## # Cross-platform Helpers # ############################## @@ -80,6 +95,7 @@ fi download_file() { local dest="$1" local url="$2" + echo "[DEBUG] Downloading from $url to $dest" 2>&1 | tee -a $LOGFILE if $IS_MACOS; then curl -sL -o "$dest" "$url" else @@ -91,12 +107,66 @@ download_file() { download_stdout() { local url="$1" if $IS_MACOS; then - curl -sL "$url" + curl -fsSL "$url" else wget -q -O- "$url" fi } +# Normalize IPFS refs and (if needed) infer the missing : from dappnode_package.json +# Accepts: +# - /ipfs/: +# - /ipfs/ (version inferred) +# - ipfs/[:] (leading slash normalized) +normalize_ipfs_version_ref() { + local raw_ref="$1" + local comp="$2" + local ref="$raw_ref" + + if [[ "$ref" == ipfs/* ]]; then + ref="/$ref" + fi + + # If it already has :, we're done + if [[ "$ref" == /ipfs/*:* ]]; then + echo "$ref" + return 0 + fi + + # If it's an IPFS ref without a :, infer it from the manifest in the CID + if [[ "$ref" == /ipfs/* ]]; then + 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)" + if [[ -z "$manifest" ]]; then + echo "[ERROR] Could not fetch IPFS manifest for ${comp} from: $manifest_url" 1>&2 + echo "[ERROR] Provide ${comp}_VERSION as /ipfs/: (example: /ipfs/Qm...:0.2.11)" 1>&2 + return 1 + fi + + local inferred_version + inferred_version="$( + echo "$manifest" | + tr -d '\r' | + grep -m1 '"version"' | + sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^\"]+)".*/\1/' + )" + + if [[ -z "$inferred_version" || "$inferred_version" == "$manifest" ]]; then + echo "[ERROR] Could not infer version for ${comp} from IPFS manifest: $manifest_url" 1>&2 + echo "[ERROR] Provide ${comp}_VERSION as /ipfs/:" 1>&2 + return 1 + fi + + echo "${cid_path}:${inferred_version}" + return 0 + fi + + # Not an IPFS ref; return as-is + echo "$raw_ref" +} + # Cross-platform in-place sed (macOS requires '' after -i) sed_inplace() { if $IS_MACOS; then @@ -196,18 +266,25 @@ determine_packages() { is_port_used if [ "$IS_ISO_INSTALL" == "false" ]; then if [ "$IS_PORT_USED" == "true" ]; then - PKGS=(BIND IPFS VPN WIREGUARD DAPPMANAGER WIFI) + PKGS=(BIND IPFS VPN WIREGUARD DAPPMANAGER) else - PKGS=(HTTPS BIND IPFS WIREGUARD DAPPMANAGER WIFI) + PKGS=(HTTPS BIND IPFS VPN WIREGUARD DAPPMANAGER) fi else if [ "$IS_PORT_USED" == "true" ]; then - PKGS=(BIND IPFS WIREGUARD DAPPMANAGER WIFI) + PKGS=(BIND IPFS VPN WIREGUARD DAPPMANAGER) else - PKGS=(HTTPS BIND IPFS WIREGUARD DAPPMANAGER WIFI) + PKGS=(HTTPS BIND IPFS VPN WIREGUARD DAPPMANAGER) fi fi - echo -e "\e[32mPackages to be installed: ${PKGS[*]}\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Packages to be installed: ${PKGS[*]}" 2>&1 | tee -a $LOGFILE + + # Debug: print all PKGS and their version variables + echo "[DEBUG] PKGS: ${PKGS[*]}" 2>&1 | tee -a $LOGFILE + for comp in "${PKGS[@]}"; do + ver_var="${comp}_VERSION" + echo "[DEBUG] $ver_var = ${!ver_var}" 2>&1 | tee -a $LOGFILE + done } function valid_ip() { @@ -235,8 +312,14 @@ if [[ -n "$STATIC_IP" ]]; then fi fi -# Loads profile, if not exists it means it is script install so the versions will be fetched from the latest profile -[ -f "$DAPPNODE_PROFILE" ] || download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" + +# If LOCAL_PROFILE_PATH is set, use it as the profile source instead of downloading +if [ -n "$LOCAL_PROFILE_PATH" ]; then + echo "Using local profile: $LOCAL_PROFILE_PATH" | tee -a $LOGFILE + cp "$LOCAL_PROFILE_PATH" "$DAPPNODE_PROFILE" +elif [ ! -f "$DAPPNODE_PROFILE" ]; then + download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" +fi # Patch profile for macOS compatibility (replace GNU-isms and hardcoded Linux paths) # TODO: remove once profile macos-compatibility published @@ -254,16 +337,26 @@ source "${DAPPNODE_PROFILE}" determine_packages for comp in "${PKGS[@]}"; do ver="${comp}_VERSION" - DOWNLOAD_URL="https://github.com/dappnode/DNP_${comp}/releases/download/v${!ver}" - if [[ ${!ver} == /ipfs/* ]]; then - DOWNLOAD_URL="${IPFS_ENDPOINT}/api/v0/cat?arg=${!ver%:*}" + echo "[DEBUG] Processing $comp: ${!ver}" 2>&1 | tee -a $LOGFILE + + raw_version_ref="${!ver}" + if [[ "$raw_version_ref" == /ipfs/* || "$raw_version_ref" == ipfs/* ]]; then + resolved_ref="$(normalize_ipfs_version_ref "$raw_version_ref" "$comp")" || exit 1 + eval "${comp}_VERSION=\"${resolved_ref}\"" + raw_version_ref="$resolved_ref" + echo "[DEBUG] Using IPFS for ${comp}: ${raw_version_ref%:*} (version ${raw_version_ref##*:})" 2>&1 | tee -a $LOGFILE + DOWNLOAD_URL="${IPFS_ENDPOINT%/}${raw_version_ref%:*}" + version_for_filenames="${raw_version_ref##*:}" + else + version_for_filenames="${raw_version_ref##*:}" + DOWNLOAD_URL="https://github.com/dappnode/DNP_${comp}/releases/download/v${version_for_filenames}" fi comp_lower=$(echo "$comp" | tr '[:upper:]' '[:lower:]') - eval "${comp}_URL=\"${DOWNLOAD_URL}/${comp_lower}.dnp.dappnode.eth_${!ver##*:}_linux-${ARCH}.txz\"" + eval "${comp}_URL=\"${DOWNLOAD_URL}/${comp_lower}.dnp.dappnode.eth_${version_for_filenames}_linux-${ARCH}.txz\"" eval "${comp}_YML=\"${DOWNLOAD_URL}/docker-compose.yml\"" eval "${comp}_MANIFEST=\"${DOWNLOAD_URL}/dappnode_package.json\"" eval "${comp}_YML_FILE=\"${DAPPNODE_CORE_DIR}/docker-compose-${comp_lower}.yml\"" - eval "${comp}_FILE=\"${DAPPNODE_CORE_DIR}/${comp_lower}.dnp.dappnode.eth_${!ver##*:}_linux-${ARCH}.txz\"" + eval "${comp}_FILE=\"${DAPPNODE_CORE_DIR}/${comp_lower}.dnp.dappnode.eth_${version_for_filenames}_linux-${ARCH}.txz\"" eval "${comp}_MANIFEST_FILE=\"${DAPPNODE_CORE_DIR}/dappnode_package-${comp_lower}.json\"" done @@ -542,57 +635,57 @@ addUserToDockerGroup() { #### SCRIPT START #### ############################################## -echo -e "\e[32m\n##############################################\e[0m" 2>&1 | tee -a $LOGFILE -echo -e "\e[32m#### DAPPNODE INSTALLER ####\e[0m" 2>&1 | tee -a $LOGFILE -echo -e "\e[32m##############################################\e[0m" 2>&1 | tee -a $LOGFILE +color_echo green "\n##############################################" 2>&1 | tee -a $LOGFILE +color_echo green "#### DAPPNODE INSTALLER ####" 2>&1 | tee -a $LOGFILE +color_echo green "##############################################" 2>&1 | tee -a $LOGFILE # --- Linux-only setup steps --- if $IS_LINUX; then - echo -e "\e[32mCreating swap memory...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Creating swap memory..." 2>&1 | tee -a $LOGFILE addSwap - echo -e "\e[32mCustomizing login...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Customizing login..." 2>&1 | tee -a $LOGFILE customMotd - echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Installing extra packages..." 2>&1 | tee -a $LOGFILE installExtraDpkg - echo -e "\e[32mGrabbing latest content hashes...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Grabbing latest content hashes..." 2>&1 | tee -a $LOGFILE grabContentHashes if [ "$ARCH" == "amd64" ]; then - echo -e "\e[32mInstalling SGX modules...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Installing SGX modules..." 2>&1 | tee -a $LOGFILE installSgx - echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Installing extra packages..." 2>&1 | tee -a $LOGFILE installExtraDpkg # TODO: Why is this being called twice? fi - echo -e "\e[32mAdding user to docker group...\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "Adding user to docker group..." 2>&1 | tee -a $LOGFILE addUserToDockerGroup fi # --- Common steps (Linux and macOS) --- -echo -e "\e[32mCreating dncore_network if needed...\e[0m" 2>&1 | tee -a $LOGFILE +color_echo green "Creating dncore_network if needed..." 2>&1 | tee -a $LOGFILE docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a $LOGFILE || true -echo -e "\e[32mBuilding DAppNode Core if needed...\e[0m" 2>&1 | tee -a $LOGFILE +color_echo green "Building DAppNode Core if needed..." 2>&1 | tee -a $LOGFILE dappnode_core_build -echo -e "\e[32mDownloading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE +color_echo green "Downloading DAppNode Core..." 2>&1 | tee -a $LOGFILE dappnode_core_download # Re-source profile now that compose files exist, so DNCORE_YMLS is populated # shellcheck disable=SC1090 source "${DAPPNODE_PROFILE}" -echo -e "\e[32mLoading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE +color_echo green "Loading DAppNode Core..." 2>&1 | tee -a $LOGFILE dappnode_core_load # --- Start DAppNode --- if $IS_LINUX; then if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then - echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "DAppNode installed" 2>&1 | tee -a $LOGFILE dappnode_core_start fi @@ -606,28 +699,28 @@ if $IS_LINUX; then fi if $IS_MACOS; then - echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE + color_echo green "DAppNode installed" 2>&1 | tee -a $LOGFILE dappnode_core_start - echo -e "\n\e[33mWaiting for VPN initialization...\e[0m" + color_echo yellow "\nWaiting for VPN initialization..." sleep 10 - echo -e "\n\e[32m##############################################\e[0m" - echo -e "\e[32m# DAppNode VPN Access Credentials #\e[0m" - echo -e "\e[32m##############################################\e[0m" - echo -e "\n\e[1mYour DAppNode is ready! Connect using your preferred VPN client.\e[0m" - echo -e "\e[1mChoose either Wireguard (recommended) or OpenVPN and import the\e[0m" - echo -e "\e[1mcredentials below into your VPN app to access your DAppNode.\e[0m\n" + color_echo green "\n##############################################" + color_echo green "# DAppNode VPN Access Credentials #" + color_echo green "##############################################" + echo -e "\nYour DAppNode is ready! Connect using your preferred VPN client." + echo -e "Choose either Wireguard (recommended) or OpenVPN and import the" + echo -e "credentials below into your VPN app to access your DAppNode.\n" - echo -e "\e[1m--- Wireguard ---\e[0m" + echo -e "--- Wireguard ---" dappnode_wireguard --localhost 2>&1 || \ - echo -e "\e[33mWireguard credentials not yet available. Try later with: dappnode_wireguard --localhost\e[0m" + color_echo yellow "Wireguard credentials not yet available. Try later with: dappnode_wireguard --localhost" - echo -e "\n\e[1m--- OpenVPN ---\e[0m" + echo -e "\n--- OpenVPN ---" dappnode_openvpn_get dappnode_admin --localhost 2>&1 || \ - echo -e "\e[33mOpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost\e[0m" + color_echo yellow "OpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost" - echo -e "\n\e[32mImport the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode\e[0m" + echo -e "\nImport the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode" fi exit 0 From eea27846324e67458b6a26a4afd9435a1eef5883 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Mon, 2 Mar 2026 13:42:13 +0100 Subject: [PATCH 08/11] Refactor logging in installation script to remove color output and improve readability --- scripts/dappnode_install.sh | 134 +++++++++++++++++------------------- 1 file changed, 64 insertions(+), 70 deletions(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 1a6d95b..2db78af 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -72,20 +72,7 @@ else ARCH=$(dpkg --print-architecture) fi -# Color output helpers -color_echo() { - local color="$1"; shift - if $IS_LINUX; then - case "$color" in - green) code="\e[32m" ;; - yellow) code="\e[33m" ;; - *) code="" ;; - esac - echo -e "${code}$*\e[0m" - else - echo "$*" - fi -} + ############################## # Cross-platform Helpers # @@ -95,7 +82,7 @@ color_echo() { download_file() { local dest="$1" local url="$2" - echo "[DEBUG] Downloading from $url to $dest" 2>&1 | tee -a $LOGFILE + echo "Downloading from $url to $dest" 2>&1 | tee -a "$LOGFILE" if $IS_MACOS; then curl -sL -o "$dest" "$url" else @@ -277,13 +264,13 @@ determine_packages() { PKGS=(HTTPS BIND IPFS VPN WIREGUARD DAPPMANAGER) fi fi - color_echo green "Packages to be installed: ${PKGS[*]}" 2>&1 | tee -a $LOGFILE + echo "Packages to be installed: ${PKGS[*]}" 2>&1 | tee -a "$LOGFILE" # Debug: print all PKGS and their version variables - echo "[DEBUG] PKGS: ${PKGS[*]}" 2>&1 | tee -a $LOGFILE + echo "PKGS: ${PKGS[*]}" 2>&1 | tee -a "$LOGFILE" for comp in "${PKGS[@]}"; do ver_var="${comp}_VERSION" - echo "[DEBUG] $ver_var = ${!ver_var}" 2>&1 | tee -a $LOGFILE + echo "$ver_var = ${!ver_var}" 2>&1 | tee -a "$LOGFILE" done } @@ -315,7 +302,7 @@ fi # If LOCAL_PROFILE_PATH is set, use it as the profile source instead of downloading if [ -n "$LOCAL_PROFILE_PATH" ]; then - echo "Using local profile: $LOCAL_PROFILE_PATH" | tee -a $LOGFILE + echo "Using local profile: $LOCAL_PROFILE_PATH" | tee -a "$LOGFILE" cp "$LOCAL_PROFILE_PATH" "$DAPPNODE_PROFILE" elif [ ! -f "$DAPPNODE_PROFILE" ]; then download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" @@ -337,14 +324,14 @@ source "${DAPPNODE_PROFILE}" determine_packages for comp in "${PKGS[@]}"; do ver="${comp}_VERSION" - echo "[DEBUG] Processing $comp: ${!ver}" 2>&1 | tee -a $LOGFILE + echo "Processing $comp: ${!ver}" 2>&1 | tee -a "$LOGFILE" raw_version_ref="${!ver}" if [[ "$raw_version_ref" == /ipfs/* || "$raw_version_ref" == ipfs/* ]]; then resolved_ref="$(normalize_ipfs_version_ref "$raw_version_ref" "$comp")" || exit 1 eval "${comp}_VERSION=\"${resolved_ref}\"" raw_version_ref="$resolved_ref" - echo "[DEBUG] Using IPFS for ${comp}: ${raw_version_ref%:*} (version ${raw_version_ref##*:})" 2>&1 | tee -a $LOGFILE + echo "Using IPFS for ${comp}: ${raw_version_ref%:*} (version ${raw_version_ref##*:})" 2>&1 | tee -a "$LOGFILE" DOWNLOAD_URL="${IPFS_ENDPOINT%/}${raw_version_ref%:*}" version_for_filenames="${raw_version_ref##*:}" else @@ -428,7 +415,7 @@ dappnode_core_load() { ver="${comp}_VERSION" if [[ ${!ver} != dev:* ]]; then comp_lower=$(echo "$comp" | tr '[:upper:]' '[:lower:]') - eval "[ ! -z \$(docker images -q ${comp_lower}.dnp.dappnode.eth:${!ver##*:}) ] || docker load -i \$${comp}_FILE 2>&1 | tee -a \$LOGFILE" + eval "[ ! -z \$(docker images -q ${comp_lower}.dnp.dappnode.eth:${!ver##*:}) ] || docker load -i \$${comp}_FILE 2>&1 | tee -a \"\$LOGFILE\"" fi done } @@ -459,7 +446,7 @@ generateMotdText() { |___/\__,_| .__/ .__/_||_\___/\__,_\___| |_| |_| EOF - welcome_message="\nChoose a way to connect to your DAppNode, then go to \e[1mhttp://my.dappnode\e[0m\n\n\e[1m- Wifi\e[0m\t\tScan and connect to DAppNodeWIFI. Get wifi credentials with \e[32mdappnode_wifi\e[0m\n\n\e[1m- Local Proxy\e[0m\tConnect to the same router as your DAppNode. Then go to \e[1mhttp://dappnode.local\e[0m\n\n\e[1m- Wireguard\e[0m\tDownload Wireguard app on your device. Get your dappnode wireguard credentials with \e[32mdappnode_wireguard\e[0m\n\n\e[1m- Open VPN\e[0m\tDownload OPen VPN app on your device. Get your openVPN creds with \e[32mdappnode_openvpn\e[0m\n\n\nTo see a full list of commands available execute \e[32mdappnode_help\e[0m\n" + welcome_message="\nChoose a way to connect to your DAppNode, then go to http://my.dappnode\n\n- Wifi\t\tScan and connect to DAppNodeWIFI. Get wifi credentials with dappnode_wifi\n\n- Local Proxy\tConnect to the same router as your DAppNode. Then go to http://dappnode.local\n\n- Wireguard\tDownload Wireguard app on your device. Get your dappnode wireguard credentials with dappnode_wireguard\n\n- Open VPN\tDownload Open VPN app on your device. Get your openVPN creds with dappnode_openvpn\n\n\nTo see a full list of commands available execute dappnode_help\n" printf "%b" "$welcome_message" >>"${MOTD_FILE}" } @@ -486,7 +473,7 @@ addSwap() { # if not then create it if [ "$IS_SWAP" -eq 0 ]; then - echo -e '\e[32mSwap not found. Adding swapfile.\e[0m' + echo 'Swap not found. Adding swapfile.' #RAM=$(awk '/MemTotal/ {print $2}' /proc/meminfo) #SWAP=$(($RAM * 2)) SWAP=8388608 @@ -496,7 +483,7 @@ addSwap() { swapon /swapfile echo '/swapfile none swap defaults 0 0' >>/etc/fstab else - echo -e '\e[32mSwap found. No changes made.\e[0m' + echo 'Swap found. No changes made.' fi } @@ -545,11 +532,11 @@ add_profile_to_shell() { } dappnode_core_start() { - echo -e "\e[32mDAppNode starting...\e[0m" 2>&1 | tee -a $LOGFILE + echo "DAppNode starting..." 2>&1 | tee -a "$LOGFILE" # Use DNCORE_YMLS from the profile (populated after re-sourcing post-download) - docker compose $DNCORE_YMLS up -d 2>&1 | tee -a $LOGFILE - echo -e "\e[32mDAppNode started\e[0m" 2>&1 | tee -a $LOGFILE + docker compose $DNCORE_YMLS up -d 2>&1 | tee -a "$LOGFILE" + echo "DAppNode started" 2>&1 | tee -a "$LOGFILE" # Add profile sourcing to user's shell configuration add_profile_to_shell @@ -563,17 +550,17 @@ dappnode_core_start() { # Linux-only: clean up rc.local if $IS_LINUX; then if [ -f "/etc/rc.local" ] && [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then - sed_inplace '/\/usr\/src\/dappnode\/scripts\/dappnode_install.sh/d' /etc/rc.local 2>&1 | tee -a $LOGFILE + sed_inplace '/\/usr\/src\/dappnode\/scripts\/dappnode_install.sh/d' /etc/rc.local 2>&1 | tee -a "$LOGFILE" fi fi # Display help message to the user - echo -e "Execute \e[32mdappnode_help\e[0m to see a full list with commands available" + echo "Execute dappnode_help to see a full list with commands available" } installExtraDpkg() { if [ -d "/usr/src/dappnode/extra_dpkg" ]; then - dpkg -i /usr/src/dappnode/iso/extra_dpkg/*.deb 2>&1 | tee -a $LOGFILE + dpkg -i /usr/src/dappnode/iso/extra_dpkg/*.deb 2>&1 | tee -a "$LOGFILE" fi } @@ -583,7 +570,7 @@ grabContentHashes() { for comp in "${content_hash_pkgs[@]}"; do CONTENT_HASH=$(download_stdout "https://github.com/dappnode/DAppNodePackage-${comp}/releases/latest/download/content-hash") if [ -z "$CONTENT_HASH" ]; then - echo "ERROR! Failed to find content hash of ${comp}." 2>&1 | tee -a $LOGFILE + echo "ERROR! Failed to find content hash of ${comp}." 2>&1 | tee -a "$LOGFILE" exit 1 fi echo "${comp}.dnp.dappnode.eth,${CONTENT_HASH}" >>${CONTENT_HASH_FILE} @@ -595,15 +582,15 @@ grabContentHashes() { installSgx() { if [ -d "/usr/src/dappnode/iso/sgx" ]; then # from sgx_linux_x64_driver_2.5.0_2605efa.bin - /usr/src/dappnode/iso/sgx/sgx_linux_x64_driver.bin 2>&1 | tee -a $LOGFILE - /usr/src/dappnode/iso/sgx/enable_sgx 2>&1 | tee -a $LOGFILE + /usr/src/dappnode/iso/sgx/sgx_linux_x64_driver.bin 2>&1 | tee -a "$LOGFILE" + /usr/src/dappnode/iso/sgx/enable_sgx 2>&1 | tee -a "$LOGFILE" fi } # /extra_dpkg will only be installed on ISO's dappnode not on standalone script installExtraDpkg() { if [ -d "/usr/src/dappnode/iso/extra_dpkg" ]; then - dpkg -i /usr/src/dappnode/extra_dpkg/*.deb 2>&1 | tee -a $LOGFILE + dpkg -i /usr/src/dappnode/extra_dpkg/*.deb 2>&1 | tee -a "$LOGFILE" fi } @@ -615,12 +602,12 @@ addUserToDockerGroup() { # 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 + echo "WARN: Default user not found. Could not add it to the docker group." 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 + echo "User $USER is already in the docker group" 2>&1 | tee -a "$LOGFILE" return fi @@ -628,64 +615,65 @@ addUserToDockerGroup() { # 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 + echo "User $USER added to the docker group" 2>&1 | tee -a "$LOGFILE" } ############################################## #### SCRIPT START #### ############################################## -color_echo green "\n##############################################" 2>&1 | tee -a $LOGFILE -color_echo green "#### DAPPNODE INSTALLER ####" 2>&1 | tee -a $LOGFILE -color_echo green "##############################################" 2>&1 | tee -a $LOGFILE +echo "" 2>&1 | tee -a "$LOGFILE" +echo "##############################################" 2>&1 | tee -a "$LOGFILE" +echo "#### DAPPNODE INSTALLER ####" 2>&1 | tee -a "$LOGFILE" +echo "##############################################" 2>&1 | tee -a "$LOGFILE" # --- Linux-only setup steps --- if $IS_LINUX; then - color_echo green "Creating swap memory..." 2>&1 | tee -a $LOGFILE + echo "Creating swap memory..." 2>&1 | tee -a "$LOGFILE" addSwap - color_echo green "Customizing login..." 2>&1 | tee -a $LOGFILE + echo "Customizing login..." 2>&1 | tee -a "$LOGFILE" customMotd - color_echo green "Installing extra packages..." 2>&1 | tee -a $LOGFILE + echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" installExtraDpkg - color_echo green "Grabbing latest content hashes..." 2>&1 | tee -a $LOGFILE + echo "Grabbing latest content hashes..." 2>&1 | tee -a "$LOGFILE" grabContentHashes if [ "$ARCH" == "amd64" ]; then - color_echo green "Installing SGX modules..." 2>&1 | tee -a $LOGFILE + echo "Installing SGX modules..." 2>&1 | tee -a "$LOGFILE" installSgx - color_echo green "Installing extra packages..." 2>&1 | tee -a $LOGFILE + echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" installExtraDpkg # TODO: Why is this being called twice? fi - color_echo green "Adding user to docker group..." 2>&1 | tee -a $LOGFILE + echo "Adding user to docker group..." 2>&1 | tee -a "$LOGFILE" addUserToDockerGroup fi # --- Common steps (Linux and macOS) --- -color_echo green "Creating dncore_network if needed..." 2>&1 | tee -a $LOGFILE -docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a $LOGFILE || true +echo "Creating dncore_network if needed..." 2>&1 | tee -a "$LOGFILE" +docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a "$LOGFILE" || true -color_echo green "Building DAppNode Core if needed..." 2>&1 | tee -a $LOGFILE +echo "Building DAppNode Core if needed..." 2>&1 | tee -a "$LOGFILE" dappnode_core_build -color_echo green "Downloading DAppNode Core..." 2>&1 | tee -a $LOGFILE +echo "Downloading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" dappnode_core_download # Re-source profile now that compose files exist, so DNCORE_YMLS is populated # shellcheck disable=SC1090 source "${DAPPNODE_PROFILE}" -color_echo green "Loading DAppNode Core..." 2>&1 | tee -a $LOGFILE +echo "Loading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" dappnode_core_load # --- Start DAppNode --- if $IS_LINUX; then if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then - color_echo green "DAppNode installed" 2>&1 | tee -a $LOGFILE + echo "DAppNode installed" 2>&1 | tee -a "$LOGFILE" dappnode_core_start fi @@ -699,28 +687,34 @@ if $IS_LINUX; then fi if $IS_MACOS; then - color_echo green "DAppNode installed" 2>&1 | tee -a $LOGFILE + echo "DAppNode installed" 2>&1 | tee -a "$LOGFILE" dappnode_core_start - color_echo yellow "\nWaiting for VPN initialization..." - sleep 10 - - color_echo green "\n##############################################" - color_echo green "# DAppNode VPN Access Credentials #" - color_echo green "##############################################" - echo -e "\nYour DAppNode is ready! Connect using your preferred VPN client." - echo -e "Choose either Wireguard (recommended) or OpenVPN and import the" - echo -e "credentials below into your VPN app to access your DAppNode.\n" - - echo -e "--- Wireguard ---" + echo "" + echo "Waiting for VPN initialization..." + sleep 20 + + echo "" + echo "##############################################" + echo "# DAppNode VPN Access Credentials #" + echo "##############################################" + echo "" + echo "Your DAppNode is ready! Connect using your preferred VPN client." + echo "Choose either Wireguard (recommended) or OpenVPN and import the" + echo "credentials below into your VPN app to access your DAppNode." + echo "" + + echo "--- Wireguard ---" dappnode_wireguard --localhost 2>&1 || \ - color_echo yellow "Wireguard credentials not yet available. Try later with: dappnode_wireguard --localhost" + echo "Wireguard credentials not yet available. Try later with: dappnode_wireguard --localhost" - echo -e "\n--- OpenVPN ---" + echo "" + echo "--- OpenVPN ---" dappnode_openvpn_get dappnode_admin --localhost 2>&1 || \ - color_echo yellow "OpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost" + echo "OpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost" - echo -e "\nImport the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode" + echo "" + echo "Import the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode" fi exit 0 From a511f2c456d1bccd14eee5c637cbde5326437ada Mon Sep 17 00:00:00 2001 From: Marc Font <36164126+Marketen@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:46:29 +0100 Subject: [PATCH 09/11] uninstall script in macos (#695) * uninstall script in macos * comment reexec under bash * comment * remove colors logic --------- Co-authored-by: pablomendezroyo <41727368+pablomendezroyo@users.noreply.github.com> --- scripts/dappnode_uninstall.sh | 111 +++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/scripts/dappnode_uninstall.sh b/scripts/dappnode_uninstall.sh index 66bc9f3..29da4a5 100755 --- a/scripts/dappnode_uninstall.sh +++ b/scripts/dappnode_uninstall.sh @@ -1,21 +1,72 @@ #!/usr/bin/env bash -DAPPNODE_DIR="/usr/src/dappnode" + +# This uninstaller is written for bash. It's safe to *run it from zsh* (it will execute via bash +# thanks to the shebang), but users sometimes invoke it as `zsh ./script.sh` or `source ./script.sh`. +# - If sourced, bail out (sourcing would pollute the current shell and can break it). +# - If invoked by a non-bash shell, re-exec with bash before hitting bash-specific builtins. +if (return 0 2>/dev/null); then + echo "This script must be executed, not sourced. Run: bash $0" + return 1 +fi + +if [ -z "${BASH_VERSION:-}" ]; then + exec /usr/bin/env bash "$0" "$@" +fi + +################## +# OS DETECTION # +################## +OS_TYPE="$(uname -s)" +IS_MACOS=false +IS_LINUX=false +if [[ "$OS_TYPE" == "Darwin" ]]; then + IS_MACOS=true +elif [[ "$OS_TYPE" == "Linux" ]]; then + IS_LINUX=true +else + echo "Unsupported operating system: $OS_TYPE" + exit 1 +fi + +############# +# VARIABLES # +############# +# Dirs — macOS uses $HOME/dappnode, Linux uses /usr/src/dappnode (mirrors install script) +if $IS_MACOS; then + DAPPNODE_DIR="$HOME/dappnode" +else + DAPPNODE_DIR="/usr/src/dappnode" +fi DAPPNODE_CORE_DIR="${DAPPNODE_DIR}/DNCORE" PROFILE_FILE="${DAPPNODE_CORE_DIR}/.dappnode_profile" input=$1 # Allow to call script with argument (must be Y/N) -[ -f $PROFILE_FILE ] || ( - echo "Error: DAppNode profile does not exist." +############################## +# Cross-platform Helpers # +############################## + +# Cross-platform in-place sed (macOS requires '' after -i) +sed_inplace() { + if $IS_MACOS; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +[ -f "$PROFILE_FILE" ] || { + echo "Error: DAppNode profile does not exist at ${PROFILE_FILE}." exit 1 -) +} uninstall() { - echo -e "\e[32mUninstalling DAppNode\e[0m" + echo "Uninstalling DAppNode" # shellcheck disable=SC1090 source "${PROFILE_FILE}" &>/dev/null DAPPNODE_CONTAINERS="$(docker ps -a --format '{{.Names}}' | grep DAppNode)" - echo -e "\e[32mRemoving DAppNode containers: \e[0m\n${DAPPNODE_CONTAINERS}" + echo "Removing DAppNode containers: " + echo "${DAPPNODE_CONTAINERS}" for container in $DAPPNODE_CONTAINERS; do # Stop DAppNode container docker stop "$container" &>/dev/null @@ -24,40 +75,58 @@ uninstall() { done DAPPNODE_IMAGES="$(docker image ls -a | grep "dappnode")" - echo -e "\e[32mRemoving DAppNode images: \e[0m\n${DAPPNODE_IMAGES}" + echo "Removing DAppNode images: " + echo "${DAPPNODE_IMAGES}" for image in $DAPPNODE_IMAGES; do # Remove DAppNode images docker image rm "$image" &>/dev/null done DAPPNODE_VOLUMES="$(docker volume ls | grep "dappnode\|dncore")" - echo -e "\e[32mRemoving DAppNode volumes: \e[0m\n${DAPPNODE_VOLUMES}" + echo "Removing DAppNode volumes: " + echo "${DAPPNODE_VOLUMES}" for volume in $DAPPNODE_VOLUMES; do # Remove DAppNode volumes docker volume rm "$volume" &>/dev/null done # Remove dncore_network - echo -e "\e[32mRemoving docker dncore_network\e[0m" + echo "Removing docker dncore_network" docker network remove dncore_network || echo "dncore_network already removed" - # Remove dir - echo -e "\e[32mRemoving DAppNode directory\e[0m" - rm -rf /usr/src/dappnode + # Remove DAppNode directory + echo "Removing DAppNode directory: ${DAPPNODE_DIR}" + rm -rf "${DAPPNODE_DIR}" # Remove profile file references from shell config files - USER=$(grep 1000 /etc/passwd | cut -f 1 -d:) - [ -n "$USER" ] && USER_HOME=/home/$USER || USER_HOME=/root - - for config_file in .profile .bashrc; do - CONFIG_PATH="$USER_HOME/$config_file" - if [ -f "$CONFIG_PATH" ]; then - sed -i '/######## DAPPNODE PROFILE ########/d' "$CONFIG_PATH" - sed -i '/.*dappnode_profile/d' "$CONFIG_PATH" + local user_home + local shell_configs + + if $IS_MACOS; then + user_home="$HOME" + # macOS defaults to zsh — matches install script + shell_configs=(".zshrc" ".zprofile") + else + local user_name + user_name=$(grep 1000 /etc/passwd | cut -f 1 -d:) + if [ -n "$user_name" ]; then + user_home="/home/$user_name" + else + user_home="/root" + fi + shell_configs=(".profile" ".bashrc") + fi + + # Remove Dappnode profile references from shell config files + for config_file in "${shell_configs[@]}"; do + local config_path="${user_home}/${config_file}" + if [ -f "$config_path" ]; then + sed_inplace '/######## DAPPNODE PROFILE ########/d' "$config_path" + sed_inplace '/.*dappnode_profile/d' "$config_path" fi done - echo -e "\e[32mDAppNode uninstalled!\e[0m" + echo "DAppNode uninstalled!" } if [ $# -eq 0 ]; then From d2c17832d6877a4346e481afc7373a8f9d23632d Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Mon, 2 Mar 2026 14:18:08 +0100 Subject: [PATCH 10/11] Enhance installation script with improved error handling, logging, and CLI wrappers for VPN commands --- scripts/dappnode_install.sh | 528 ++++++++++++++++++++++-------------- 1 file changed, 322 insertions(+), 206 deletions(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 2db78af..7c92566 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -13,12 +13,95 @@ if [ -z "${BASH_VERSION:-}" ]; then exec /usr/bin/env bash "$0" "$@" fi -set -eo pipefail +set -Eeuo pipefail + +# Optional env inputs (avoid unbound-variable errors under `set -u`) +: "${UPDATE:=false}" +: "${STATIC_IP:=}" +: "${LOCAL_PROFILE_PATH:=}" # Enable alias expansion in non-interactive bash scripts. # Required so commands like `dappnode_wireguard` (defined as aliases in `.dappnode_profile`) work. shopt -s expand_aliases +############################## +# Core CLI wrappers (script) # +############################## + +# Note: aliases sourced from `.dappnode_profile` are not reliably usable inside this installer. +# Bash parses scripts before those aliases are defined, so later calls can become "command not found". +# Provide function wrappers so the installer can always run these commands. + +dappnode_wireguard() { + docker exec -i DAppNodeCore-api.wireguard.dnp.dappnode.eth getWireguardCredentials "$@" +} + +dappnode_openvpn_get() { + docker exec -i DAppNodeCore-vpn.dnp.dappnode.eth vpncli get "$@" +} + +dappnode_openvpn() { + docker exec -i DAppNodeCore-vpn.dnp.dappnode.eth getAdminCredentials "$@" +} + +############################## +# Logging / Errors # +############################## + +log() { + # LOGFILE is created after dir bootstrap; until then we just print to stdout. + if [[ -n "${LOGFILE:-}" && -d "${LOGS_DIR:-}" ]]; then + printf '%s\n' "$*" | tee -a "$LOGFILE" + else + printf '%s\n' "$*" + fi +} + +warn() { + log "[WARN] $*" +} + +die() { + log "[ERROR] $*" + exit 1 +} + +require_cmd() { + local cmd="$1" + command -v "$cmd" >/dev/null 2>&1 || die "Missing required command: $cmd" +} + +require_downloader() { + if command -v curl >/dev/null 2>&1; then + return 0 + fi + if command -v wget >/dev/null 2>&1; then + return 0 + fi + die "Missing required downloader: install curl or wget" +} + +check_prereqs() { + require_cmd docker + require_downloader + + # Ensure compose is available (Docker Desktop / modern docker engine) + if ! docker compose version >/dev/null 2>&1; then + die "Docker Compose not available (expected: 'docker compose'). Update Docker or install the compose plugin." + fi +} + +# Build docker compose "-f " args from downloaded compose files. +# This avoids depending on alias expansion or profile-generated strings. +build_dncore_compose_args() { + DNCORE_COMPOSE_ARGS=() + local file + while IFS= read -r file; do + [[ -n "$file" ]] || continue + DNCORE_COMPOSE_ARGS+=( -f "$file" ) + done < <(find "${DAPPNODE_CORE_DIR}" -name 'docker-compose-*.yml' -print 2>/dev/null | sort) +} + ################## # OS DETECTION # ################## @@ -30,8 +113,7 @@ if [[ "$OS_TYPE" == "Darwin" ]]; then elif [[ "$OS_TYPE" == "Linux" ]]; then IS_LINUX=true else - echo "Unsupported operating system: $OS_TYPE" - exit 1 + die "Unsupported operating system: $OS_TYPE" fi ############# @@ -65,11 +147,11 @@ DAPPNODE_ACCESS_CREDENTIALS_URL="https://github.com/dappnode/DAppNode/releases/l # Architecture detection (cross-platform) if $IS_MACOS; then - ARCH=$(uname -m) + ARCH="$(uname -m)" [[ "$ARCH" == "x86_64" ]] && ARCH="amd64" # arm64 is already correct for Apple Silicon else - ARCH=$(dpkg --print-architecture) + ARCH="$(dpkg --print-architecture)" fi @@ -82,22 +164,23 @@ fi download_file() { local dest="$1" local url="$2" - echo "Downloading from $url to $dest" 2>&1 | tee -a "$LOGFILE" - if $IS_MACOS; then - curl -sL -o "$dest" "$url" - else - wget -q --show-progress --progress=bar:force -O "$dest" "$url" + log "Downloading from $url to $dest" + mkdir -p "$(dirname "$dest")" + if command -v curl >/dev/null 2>&1; then + curl -fsSL -o "$dest" "$url" + return fi + wget -q --show-progress --progress=bar:force -O "$dest" "$url" } # Download content to stdout: download_stdout download_stdout() { local url="$1" - if $IS_MACOS; then + if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" - else - wget -q -O- "$url" + return fi + wget -q -O- "$url" } # Normalize IPFS refs and (if needed) infer the missing : from dappnode_package.json @@ -189,25 +272,30 @@ patch_profile_for_macos() { sed_inplace 's|/usr/src/dappnode|\$HOME/dappnode|g' "$profile" } -# Clean if update -if [ "$UPDATE" = true ]; then - echo "Cleaning for update..." - rm -rf $LOGFILE - rm -rf ${DAPPNODE_CORE_DIR}/docker-compose-*.yml - rm -rf ${DAPPNODE_CORE_DIR}/dappnode_package-*.json - rm -rf ${DAPPNODE_CORE_DIR}/*.tar.xz - rm -rf ${DAPPNODE_CORE_DIR}/*.txz - rm -rf ${DAPPNODE_CORE_DIR}/.dappnode_profile - rm -rf ${CONTENT_HASH_FILE} -fi +bootstrap_filesystem() { + # Clean if update + if [[ "${UPDATE}" == "true" ]]; then + echo "Cleaning for update..." + rm -f "${LOGFILE}" || true + rm -f "${DAPPNODE_CORE_DIR}"/docker-compose-*.yml || true + rm -f "${DAPPNODE_CORE_DIR}"/dappnode_package-*.json || true + rm -f "${DAPPNODE_CORE_DIR}"/*.tar.xz || true + rm -f "${DAPPNODE_CORE_DIR}"/*.txz || true + rm -f "${DAPPNODE_CORE_DIR}/.dappnode_profile" || true + rm -f "${CONTENT_HASH_FILE}" || true + fi -# Create necessary directories -mkdir -p $DAPPNODE_DIR -mkdir -p $DAPPNODE_CORE_DIR -mkdir -p "${DAPPNODE_DIR}/scripts" -mkdir -p "${DAPPNODE_CORE_DIR}/scripts" -mkdir -p "${DAPPNODE_DIR}/config" -mkdir -p $LOGS_DIR + # Create necessary directories + mkdir -p "${DAPPNODE_DIR}" + mkdir -p "${DAPPNODE_CORE_DIR}" + mkdir -p "${DAPPNODE_DIR}/scripts" + mkdir -p "${DAPPNODE_CORE_DIR}/scripts" + mkdir -p "${DAPPNODE_DIR}/config" + mkdir -p "${LOGS_DIR}" + + # Ensure the log file path exists before first use by helpers. + touch "${LOGFILE}" || true +} # TEMPORARY: think a way to integrate flags instead of use files to detect installation type is_iso_install() { @@ -229,8 +317,14 @@ is_iso_install() { is_port_used() { # Check if port 80 or 443 is in use at all local port80_used port443_used - lsof -i -P -n | grep ":80 (LISTEN)" &>/dev/null && port80_used=true || port80_used=false - lsof -i -P -n | grep ":443 (LISTEN)" &>/dev/null && port443_used=true || port443_used=false + if command -v lsof >/dev/null 2>&1; then + lsof -i -P -n | grep ":80 (LISTEN)" &>/dev/null && port80_used=true || port80_used=false + lsof -i -P -n | grep ":443 (LISTEN)" &>/dev/null && port443_used=true || port443_used=false + else + warn "lsof not found; assuming ports 80/443 are in use (HTTPS will be skipped)" + IS_PORT_USED=true + return + fi if [ "$port80_used" = false ] && [ "$port443_used" = false ]; then IS_PORT_USED=false @@ -264,88 +358,93 @@ determine_packages() { PKGS=(HTTPS BIND IPFS VPN WIREGUARD DAPPMANAGER) fi fi - echo "Packages to be installed: ${PKGS[*]}" 2>&1 | tee -a "$LOGFILE" + log "Packages to be installed: ${PKGS[*]}" # Debug: print all PKGS and their version variables - echo "PKGS: ${PKGS[*]}" 2>&1 | tee -a "$LOGFILE" + log "PKGS: ${PKGS[*]}" for comp in "${PKGS[@]}"; do + local ver_var ver_var="${comp}_VERSION" - echo "$ver_var = ${!ver_var}" 2>&1 | tee -a "$LOGFILE" + log "$ver_var = ${!ver_var-}" done } -function valid_ip() { - local ip=$1 - local stat=1 - - if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - OIFS=$IFS - IFS='.' - ip=("$ip") - IFS=$OIFS - [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && - ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] - stat=$? +valid_ip() { + local ip="$1" + if [[ ! "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then + return 1 fi - return $stat + + local IFS='.' + # shellcheck disable=SC2206 + local octets=( $ip ) + [[ ${#octets[@]} -eq 4 ]] || return 1 + [[ ${octets[0]} -le 255 && ${octets[1]} -le 255 && ${octets[2]} -le 255 && ${octets[3]} -le 255 ]] } -if [[ -n "$STATIC_IP" ]]; then +configure_static_ip() { + if [[ -z "${STATIC_IP}" ]]; then + return 0 + fi + if valid_ip "$STATIC_IP"; then - echo "$STATIC_IP" >${DAPPNODE_DIR}/config/static_ip + echo "$STATIC_IP" >"${DAPPNODE_DIR}/config/static_ip" else - echo "The static IP provided: ${STATIC_IP} is not valid." - exit 1 + die "The static IP provided (${STATIC_IP}) is not valid." fi -fi +} +ensure_profile_loaded() { + # If LOCAL_PROFILE_PATH is set, use it as the profile source instead of downloading + if [[ -n "${LOCAL_PROFILE_PATH}" ]]; then + log "Using local profile: ${LOCAL_PROFILE_PATH}" + cp "$LOCAL_PROFILE_PATH" "$DAPPNODE_PROFILE" + elif [[ ! -f "$DAPPNODE_PROFILE" ]]; then + download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" + fi -# If LOCAL_PROFILE_PATH is set, use it as the profile source instead of downloading -if [ -n "$LOCAL_PROFILE_PATH" ]; then - echo "Using local profile: $LOCAL_PROFILE_PATH" | tee -a "$LOGFILE" - cp "$LOCAL_PROFILE_PATH" "$DAPPNODE_PROFILE" -elif [ ! -f "$DAPPNODE_PROFILE" ]; then - download_file "${DAPPNODE_PROFILE}" "${PROFILE_URL}" -fi + # Patch profile for macOS compatibility (replace GNU-isms and hardcoded Linux paths) + # TODO: remove once profile macos-compatibility published + if $IS_MACOS; then + patch_profile_for_macos "$DAPPNODE_PROFILE" + fi + + # shellcheck disable=SC1090 + source "${DAPPNODE_PROFILE}" +} -# Patch profile for macOS compatibility (replace GNU-isms and hardcoded Linux paths) -# TODO: remove once profile macos-compatibility published -if $IS_MACOS; then - patch_profile_for_macos "$DAPPNODE_PROFILE" -fi -# shellcheck disable=SC1090 -source "${DAPPNODE_PROFILE}" - -# The indirect variable expansion used in ${!ver##*:} allows us to use versions like 'dev:development' -# If such variable with 'dev:'' suffix is used, then the component is built from specified branch or commit. -# you can also specify an IPFS version like /ipfs/QmWg8P2b9JKQ8thAVz49J8SbJbCoi2MwkHnUqMtpzDTtxR:0.2.7, it's important -# to include the exact version also in the IPFS hash format since it's needed to be able to download it -determine_packages -for comp in "${PKGS[@]}"; do - ver="${comp}_VERSION" - echo "Processing $comp: ${!ver}" 2>&1 | tee -a "$LOGFILE" - - raw_version_ref="${!ver}" - if [[ "$raw_version_ref" == /ipfs/* || "$raw_version_ref" == ipfs/* ]]; then - resolved_ref="$(normalize_ipfs_version_ref "$raw_version_ref" "$comp")" || exit 1 - eval "${comp}_VERSION=\"${resolved_ref}\"" - raw_version_ref="$resolved_ref" - echo "Using IPFS for ${comp}: ${raw_version_ref%:*} (version ${raw_version_ref##*:})" 2>&1 | tee -a "$LOGFILE" - DOWNLOAD_URL="${IPFS_ENDPOINT%/}${raw_version_ref%:*}" - version_for_filenames="${raw_version_ref##*:}" - else - version_for_filenames="${raw_version_ref##*:}" - DOWNLOAD_URL="https://github.com/dappnode/DNP_${comp}/releases/download/v${version_for_filenames}" - fi - comp_lower=$(echo "$comp" | tr '[:upper:]' '[:lower:]') - eval "${comp}_URL=\"${DOWNLOAD_URL}/${comp_lower}.dnp.dappnode.eth_${version_for_filenames}_linux-${ARCH}.txz\"" - eval "${comp}_YML=\"${DOWNLOAD_URL}/docker-compose.yml\"" - eval "${comp}_MANIFEST=\"${DOWNLOAD_URL}/dappnode_package.json\"" - eval "${comp}_YML_FILE=\"${DAPPNODE_CORE_DIR}/docker-compose-${comp_lower}.yml\"" - eval "${comp}_FILE=\"${DAPPNODE_CORE_DIR}/${comp_lower}.dnp.dappnode.eth_${version_for_filenames}_linux-${ARCH}.txz\"" - eval "${comp}_MANIFEST_FILE=\"${DAPPNODE_CORE_DIR}/dappnode_package-${comp_lower}.json\"" -done + +resolve_packages() { + # The indirect variable expansion used in ${!ver##*:} allows us to use versions like 'dev:development' + # If such variable with 'dev:'' suffix is used, then the component is built from specified branch or commit. + # you can also specify an IPFS version like /ipfs/: (the exact version is required). + determine_packages + for comp in "${PKGS[@]}"; do + ver="${comp}_VERSION" + log "Processing $comp: ${!ver-}" + + raw_version_ref="${!ver-}" + if [[ "$raw_version_ref" == /ipfs/* || "$raw_version_ref" == ipfs/* ]]; then + resolved_ref="$(normalize_ipfs_version_ref "$raw_version_ref" "$comp")" || exit 1 + printf -v "${comp}_VERSION" '%s' "$resolved_ref" + raw_version_ref="$resolved_ref" + log "Using IPFS for ${comp}: ${raw_version_ref%:*} (version ${raw_version_ref##*:})" + DOWNLOAD_URL="${IPFS_ENDPOINT%/}${raw_version_ref%:*}" + version_for_filenames="${raw_version_ref##*:}" + else + version_for_filenames="${raw_version_ref##*:}" + DOWNLOAD_URL="https://github.com/dappnode/DNP_${comp}/releases/download/v${version_for_filenames}" + fi + comp_lower="$(echo "$comp" | tr '[:upper:]' '[:lower:]')" + printf -v "${comp}_URL" '%s' "${DOWNLOAD_URL}/${comp_lower}.dnp.dappnode.eth_${version_for_filenames}_linux-${ARCH}.txz" + printf -v "${comp}_YML" '%s' "${DOWNLOAD_URL}/docker-compose.yml" + printf -v "${comp}_MANIFEST" '%s' "${DOWNLOAD_URL}/dappnode_package.json" + printf -v "${comp}_YML_FILE" '%s' "${DAPPNODE_CORE_DIR}/docker-compose-${comp_lower}.yml" + printf -v "${comp}_FILE" '%s' "${DAPPNODE_CORE_DIR}/${comp_lower}.dnp.dappnode.eth_${version_for_filenames}_linux-${ARCH}.txz" + printf -v "${comp}_MANIFEST_FILE" '%s' "${DAPPNODE_CORE_DIR}/dappnode_package-${comp_lower}.json" + done +} dappnode_core_build() { for comp in "${PKGS[@]}"; do @@ -359,23 +458,27 @@ dappnode_core_build() { if ! dpkg -s git >/dev/null 2>&1; then apt-get install -y git fi - TMPDIR=$(mktemp -d) - pushd "$TMPDIR" || { + local tmpdir + tmpdir="$(mktemp -d)" + pushd "$tmpdir" >/dev/null || { echo "Error on pushd" exit 1 } git clone -b "${!ver##*:}" https://github.com/dappnode/DNP_"${comp}" # Change version in YAML to the custom one - DOCKER_VER=$(echo "${!ver##*:}" | sed 's/\//_/g') - sed -i "s~^\(\s*image\s*:\s*\).*~\1${comp,,}.dnp.dappnode.eth:${DOCKER_VER}~" DNP_"${comp}"/docker-compose.yml + local docker_ver comp_lower + docker_ver="$(echo "${!ver##*:}" | sed 's/\//_/g')" + comp_lower="$(echo "$comp" | tr '[:upper:]' '[:lower:]')" + sed_inplace "s~^\(\s*image\s*:\s*\).*~\1${comp_lower}.dnp.dappnode.eth:${docker_ver}~" "DNP_${comp}/docker-compose.yml" docker compose -f ./DNP_"${comp}"/docker-compose.yml build - cp ./DNP_"${comp}"/docker-compose.yml "${DAPPNODE_CORE_DIR}"/docker-compose-"${comp,,}".yml - cp ./DNP_"${comp}"/dappnode_package.json "${DAPPNODE_CORE_DIR}"/dappnode_package-"${comp,,}".json - rm -r ./DNP_"${comp}" - popd || { + cp "./DNP_${comp}/docker-compose.yml" "${DAPPNODE_CORE_DIR}/docker-compose-${comp_lower}.yml" + cp "./DNP_${comp}/dappnode_package.json" "${DAPPNODE_CORE_DIR}/dappnode_package-${comp_lower}.json" + rm -rf "./DNP_${comp}" + popd >/dev/null || { echo "Error on popd" exit 1 } + rm -rf "$tmpdir" fi done } @@ -414,8 +517,13 @@ dappnode_core_load() { for comp in "${PKGS[@]}"; do ver="${comp}_VERSION" if [[ ${!ver} != dev:* ]]; then - comp_lower=$(echo "$comp" | tr '[:upper:]' '[:lower:]') - eval "[ ! -z \$(docker images -q ${comp_lower}.dnp.dappnode.eth:${!ver##*:}) ] || docker load -i \$${comp}_FILE 2>&1 | tee -a \"\$LOGFILE\"" + local comp_lower image file_var + comp_lower="$(echo "$comp" | tr '[:upper:]' '[:lower:]')" + image="${comp_lower}.dnp.dappnode.eth:${!ver##*:}" + file_var="${comp}_FILE" + if [[ -z "$(docker images -q "$image" 2>/dev/null)" ]]; then + docker load -i "${!file_var}" 2>&1 | tee -a "$LOGFILE" + fi fi done } @@ -452,6 +560,7 @@ EOF # Ubuntu distros use /etc/update-motd.d/ to generate the motd modifyMotdGeneration() { + local disabled_motd_dir disabled_motd_dir="${UPDATE_MOTD_DIR}/disabled" mkdir -p "${disabled_motd_dir}" @@ -459,8 +568,9 @@ modifyMotdGeneration() { # 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}") + local file base_file + 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 @@ -477,7 +587,7 @@ addSwap() { #RAM=$(awk '/MemTotal/ {print $2}' /proc/meminfo) #SWAP=$(($RAM * 2)) SWAP=8388608 - fallocate -l ${SWAP}k /swapfile + fallocate -l "${SWAP}k" /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile @@ -494,8 +604,8 @@ add_profile_to_shell() { if $IS_MACOS; then user_home="$HOME" - # macOS defaults to zsh - shell_configs=(".zshrc" ".zprofile") + # macOS defaults to zsh, but some users still run bash. + shell_configs=(".zshrc" ".zprofile" ".bashrc" ".bash_profile") else # Linux: determine user home from /etc/passwd local user_name @@ -534,8 +644,12 @@ add_profile_to_shell() { dappnode_core_start() { echo "DAppNode starting..." 2>&1 | tee -a "$LOGFILE" - # Use DNCORE_YMLS from the profile (populated after re-sourcing post-download) - docker compose $DNCORE_YMLS up -d 2>&1 | tee -a "$LOGFILE" + if [[ ${#DNCORE_COMPOSE_ARGS[@]:-0} -eq 0 ]]; then + build_dncore_compose_args + fi + [[ ${#DNCORE_COMPOSE_ARGS[@]} -gt 0 ]] || die "No docker-compose-*.yml files found in ${DAPPNODE_CORE_DIR}" + + docker compose "${DNCORE_COMPOSE_ARGS[@]}" up -d 2>&1 | tee -a "$LOGFILE" echo "DAppNode started" 2>&1 | tee -a "$LOGFILE" # Add profile sourcing to user's shell configuration @@ -558,12 +672,6 @@ dappnode_core_start() { echo "Execute dappnode_help to see a full list with commands available" } -installExtraDpkg() { - if [ -d "/usr/src/dappnode/extra_dpkg" ]; then - dpkg -i /usr/src/dappnode/iso/extra_dpkg/*.deb 2>&1 | tee -a "$LOGFILE" - fi -} - grabContentHashes() { if [ ! -f "${CONTENT_HASH_FILE}" ]; then local content_hash_pkgs=(geth besu nethermind erigon prysm teku lighthouse nimbus lodestar) @@ -573,7 +681,7 @@ grabContentHashes() { echo "ERROR! Failed to find content hash of ${comp}." 2>&1 | tee -a "$LOGFILE" exit 1 fi - echo "${comp}.dnp.dappnode.eth,${CONTENT_HASH}" >>${CONTENT_HASH_FILE} + echo "${comp}.dnp.dappnode.eth,${CONTENT_HASH}" >>"${CONTENT_HASH_FILE}" done fi } @@ -590,7 +698,7 @@ installSgx() { # /extra_dpkg will only be installed on ISO's dappnode not on standalone script installExtraDpkg() { if [ -d "/usr/src/dappnode/iso/extra_dpkg" ]; then - dpkg -i /usr/src/dappnode/extra_dpkg/*.deb 2>&1 | tee -a "$LOGFILE" + dpkg -i /usr/src/dappnode/iso/extra_dpkg/*.deb 2>&1 | tee -a "$LOGFILE" fi } @@ -598,123 +706,131 @@ installExtraDpkg() { # 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:) + local user + user=$(grep 1000 "/etc/passwd" | cut -f 1 -d:) # If USER is not found, warn the user and return - if [ -z "$USER" ]; then + if [ -z "$user" ]; then echo "WARN: Default user not found. Could not add it to the docker group." 2>&1 | tee -a "$LOGFILE" return fi - if groups "$USER" | grep &>/dev/null '\bdocker\b'; then - echo "User $USER is already in the docker group" 2>&1 | tee -a "$LOGFILE" + if groups "$user" | grep &>/dev/null '\bdocker\b'; then + echo "User $user is already in the docker group" 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 "User $USER added to the docker group" 2>&1 | tee -a "$LOGFILE" + usermod -aG docker "$user" + echo "User $user added to the docker group" 2>&1 | tee -a "$LOGFILE" } ############################################## #### SCRIPT START #### ############################################## -echo "" 2>&1 | tee -a "$LOGFILE" -echo "##############################################" 2>&1 | tee -a "$LOGFILE" -echo "#### DAPPNODE INSTALLER ####" 2>&1 | tee -a "$LOGFILE" -echo "##############################################" 2>&1 | tee -a "$LOGFILE" +main() { + bootstrap_filesystem + check_prereqs + configure_static_ip + ensure_profile_loaded + resolve_packages -# --- Linux-only setup steps --- -if $IS_LINUX; then - echo "Creating swap memory..." 2>&1 | tee -a "$LOGFILE" - addSwap + echo "" 2>&1 | tee -a "$LOGFILE" + echo "##############################################" 2>&1 | tee -a "$LOGFILE" + echo "#### DAPPNODE INSTALLER ####" 2>&1 | tee -a "$LOGFILE" + echo "##############################################" 2>&1 | tee -a "$LOGFILE" - echo "Customizing login..." 2>&1 | tee -a "$LOGFILE" - customMotd + # --- Linux-only setup steps --- + if $IS_LINUX; then + echo "Creating swap memory..." 2>&1 | tee -a "$LOGFILE" + addSwap - echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" - installExtraDpkg + echo "Customizing login..." 2>&1 | tee -a "$LOGFILE" + customMotd - echo "Grabbing latest content hashes..." 2>&1 | tee -a "$LOGFILE" - grabContentHashes + echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" + installExtraDpkg - if [ "$ARCH" == "amd64" ]; then - echo "Installing SGX modules..." 2>&1 | tee -a "$LOGFILE" - installSgx + echo "Grabbing latest content hashes..." 2>&1 | tee -a "$LOGFILE" + grabContentHashes - echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" - installExtraDpkg # TODO: Why is this being called twice? + if [ "$ARCH" == "amd64" ]; then + echo "Installing SGX modules..." 2>&1 | tee -a "$LOGFILE" + installSgx + + echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" + installExtraDpkg # TODO: Why is this being called twice? + fi + + echo "Adding user to docker group..." 2>&1 | tee -a "$LOGFILE" + addUserToDockerGroup fi - echo "Adding user to docker group..." 2>&1 | tee -a "$LOGFILE" - addUserToDockerGroup -fi + # --- Common steps (Linux and macOS) --- + echo "Creating dncore_network if needed..." 2>&1 | tee -a "$LOGFILE" + docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a "$LOGFILE" || true -# --- Common steps (Linux and macOS) --- -echo "Creating dncore_network if needed..." 2>&1 | tee -a "$LOGFILE" -docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a "$LOGFILE" || true + echo "Building DAppNode Core if needed..." 2>&1 | tee -a "$LOGFILE" + dappnode_core_build -echo "Building DAppNode Core if needed..." 2>&1 | tee -a "$LOGFILE" -dappnode_core_build + echo "Downloading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" + dappnode_core_download -echo "Downloading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" -dappnode_core_download + # Build compose args now that compose files exist + build_dncore_compose_args -# Re-source profile now that compose files exist, so DNCORE_YMLS is populated -# shellcheck disable=SC1090 -source "${DAPPNODE_PROFILE}" + echo "Loading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" + dappnode_core_load -echo "Loading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" -dappnode_core_load + # --- Start DAppNode --- + if $IS_LINUX; then + if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then + echo "DAppNode installed" 2>&1 | tee -a "$LOGFILE" + dappnode_core_start + fi -# --- Start DAppNode --- -if $IS_LINUX; then - if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then + # Run test in interactive terminal (first boot only) + if [ -f "${DAPPNODE_DIR}/.firstboot" ]; then + apt-get update + apt-get install -y kbd + openvt -s -w -- sudo -u root "${DAPPNODE_DIR}/scripts/dappnode_test_install.sh" + exit 0 + fi + fi + + if $IS_MACOS; then echo "DAppNode installed" 2>&1 | tee -a "$LOGFILE" dappnode_core_start - fi - # Run test in interactive terminal (first boot only) - if [ -f "${DAPPNODE_DIR}/.firstboot" ]; then - apt-get update - apt-get install -y kbd - openvt -s -w -- sudo -u root "${DAPPNODE_DIR}/scripts/dappnode_test_install.sh" - exit 0 + echo "" + echo "Waiting for VPN initialization..." + sleep 20 + + echo "" + echo "##############################################" + echo "# DAppNode VPN Access Credentials #" + echo "##############################################" + echo "" + echo "Your DAppNode is ready! Connect using your preferred VPN client." + echo "Choose either Wireguard (recommended) or OpenVPN and import the" + echo "credentials below into your VPN app to access your DAppNode." + echo "" + + echo "--- Wireguard ---" + dappnode_wireguard --localhost 2>&1 || \ + echo "Wireguard credentials not yet available. Try later with: dappnode_wireguard --localhost" + + echo "" + echo "--- OpenVPN ---" + dappnode_openvpn_get dappnode_admin --localhost 2>&1 || \ + echo "OpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost" + + echo "" + echo "Import the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode" fi -fi - -if $IS_MACOS; then - echo "DAppNode installed" 2>&1 | tee -a "$LOGFILE" - dappnode_core_start - - echo "" - echo "Waiting for VPN initialization..." - sleep 20 - - echo "" - echo "##############################################" - echo "# DAppNode VPN Access Credentials #" - echo "##############################################" - echo "" - echo "Your DAppNode is ready! Connect using your preferred VPN client." - echo "Choose either Wireguard (recommended) or OpenVPN and import the" - echo "credentials below into your VPN app to access your DAppNode." - echo "" - - echo "--- Wireguard ---" - dappnode_wireguard --localhost 2>&1 || \ - echo "Wireguard credentials not yet available. Try later with: dappnode_wireguard --localhost" - - echo "" - echo "--- OpenVPN ---" - dappnode_openvpn_get dappnode_admin --localhost 2>&1 || \ - echo "OpenVPN credentials not yet available. Try later with: dappnode_openvpn_get dappnode_admin --localhost" - - echo "" - echo "Import the configuration above into your VPN client of choice to access your DAppNode at http://my.dappnode" -fi +} -exit 0 +main "$@" From e541023eb472c5f9b17e215ae824dfa7a887a4ad Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Mon, 2 Mar 2026 16:07:06 +0100 Subject: [PATCH 11/11] Remove core CLI wrappers from installation script to streamline execution --- scripts/dappnode_install.sh | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 7c92566..e6f4b2f 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -24,26 +24,6 @@ set -Eeuo pipefail # Required so commands like `dappnode_wireguard` (defined as aliases in `.dappnode_profile`) work. shopt -s expand_aliases -############################## -# Core CLI wrappers (script) # -############################## - -# Note: aliases sourced from `.dappnode_profile` are not reliably usable inside this installer. -# Bash parses scripts before those aliases are defined, so later calls can become "command not found". -# Provide function wrappers so the installer can always run these commands. - -dappnode_wireguard() { - docker exec -i DAppNodeCore-api.wireguard.dnp.dappnode.eth getWireguardCredentials "$@" -} - -dappnode_openvpn_get() { - docker exec -i DAppNodeCore-vpn.dnp.dappnode.eth vpncli get "$@" -} - -dappnode_openvpn() { - docker exec -i DAppNodeCore-vpn.dnp.dappnode.eth getAdminCredentials "$@" -} - ############################## # Logging / Errors # ############################## @@ -154,8 +134,6 @@ else ARCH="$(dpkg --print-architecture)" fi - - ############################## # Cross-platform Helpers # ##############################