diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d7c85e1..174f84c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: 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 fc58ff1..e6f4b2f 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -1,54 +1,287 @@ #!/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 -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 + +############################## +# 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 # +################## +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 + die "Unsupported operating system: $OS_TYPE" +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"} +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" 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" - -# 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} + +# 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 -# 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 +############################## +# Cross-platform Helpers # +############################## + +# Download a file: download_file +download_file() { + local dest="$1" + local url="$2" + 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 command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" + return + fi + wget -q -O- "$url" +} + +# 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 + 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" +} + +# TODO: remove once profile macos-compatibility published +# 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" +} + +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}" + + # 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() { + # 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 @@ -62,8 +295,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 @@ -86,94 +325,138 @@ 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 -} + log "Packages to be installed: ${PKGS[*]}" -function valid_ip() { - local ip=$1 - local stat=1 + # Debug: print all PKGS and their version variables + log "PKGS: ${PKGS[*]}" + for comp in "${PKGS[@]}"; do + local ver_var + ver_var="${comp}_VERSION" + log "$ver_var = ${!ver_var-}" + done +} - 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 +} -# 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} -# 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" - 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%:*}" - fi - eval "${comp}_URL=\"${DOWNLOAD_URL}/${comp,,}.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\"" -done +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 + + # 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}" +} + + + +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 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 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 } @@ -182,15 +465,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 } @@ -199,13 +495,18 @@ 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" + 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 } customMotd() { - generateMotdText if [ -d "${UPDATE_MOTD_DIR}" ]; then @@ -216,6 +517,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,11 +532,13 @@ generateMotdText() { |___/\__,_| .__/ .__/_||_\___/\__,_\___| |_| |_| EOF - echo -e "$WELCOME_MESSAGE" >>"${MOTD_FILE}" + 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}" } # 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}" @@ -241,8 +546,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 @@ -255,81 +561,105 @@ 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 - fallocate -l ${SWAP}k /swapfile + fallocate -l "${SWAP}k" /swapfile chmod 600 /swapfile mkswap /swapfile 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 } -dappnode_start() { - echo -e "\e[32mDAppNode starting...\e[0m" 2>&1 | tee -a $LOGFILE - # shellcheck disable=SC1090 - source "${DAPPNODE_PROFILE}" >/dev/null 2>&1 +# Add .dappnode_profile sourcing to the user's default shell configuration +add_profile_to_shell() { + local user_home + local shell_configs - # 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 - echo -e "\e[32mDAppNode started\e[0m" 2>&1 | tee -a $LOGFILE + if $IS_MACOS; then + user_home="$HOME" + # 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 + 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 - # 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 + 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 - # 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" - + [ ! -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" + if ! grep -q "${DAPPNODE_PROFILE}" "$config_path"; then + echo "######## DAPPNODE PROFILE ########" >> "$config_path" + echo "$source_line" >> "$config_path" + echo "" >> "$config_path" fi done +} + +dappnode_core_start() { + echo "DAppNode starting..." 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" - # Remove return from profile - sed -i '/return/d' $DAPPNODE_PROFILE | tee -a $LOGFILE + # Add profile sourcing to user's shell configuration + add_profile_to_shell + + # 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 - echo -e "Execute \e[32mdappnode_help\e[0m 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 + echo "Execute dappnode_help to see a full list with commands available" } 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 + 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 } @@ -338,15 +668,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/iso/extra_dpkg/*.deb 2>&1 | tee -a "$LOGFILE" fi } @@ -354,81 +684,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 - echo -e "\e[33mWARN: Default user not found. Could not add it to the docker group.\e[0m" 2>&1 | tee -a $LOGFILE + 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 -e "\e[32mUser $USER is already in the docker group\e[0m" 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 -e "\e[32mUser $USER added to the docker group\e[0m" 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 -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 +main() { + bootstrap_filesystem + check_prereqs + configure_static_ip + ensure_profile_loaded + resolve_packages -echo -e "\e[32mCreating swap memory...\e[0m" 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 -e "\e[32mCustomizing login...\e[0m" 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 -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE -installExtraDpkg + echo "Customizing login..." 2>&1 | tee -a "$LOGFILE" + customMotd -echo -e "\e[32mGrabbing latest content hashes...\e[0m" 2>&1 | tee -a $LOGFILE -grabContentHashes + echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" + installExtraDpkg -if [ "$ARCH" == "amd64" ]; then - echo -e "\e[32mInstalling SGX modules...\e[0m" 2>&1 | tee -a $LOGFILE - installSgx + echo "Grabbing latest content hashes..." 2>&1 | tee -a "$LOGFILE" + grabContentHashes - echo -e "\e[32mInstalling extra packages...\e[0m" 2>&1 | tee -a $LOGFILE - installExtraDpkg # TODO: Why is this being called twice? -fi + if [ "$ARCH" == "amd64" ]; then + echo "Installing SGX modules..." 2>&1 | tee -a "$LOGFILE" + installSgx -echo -e "\e[32mAdding user to docker group...\e[0m" 2>&1 | tee -a $LOGFILE -addUserToDockerGroup + echo "Installing extra packages..." 2>&1 | tee -a "$LOGFILE" + installExtraDpkg # TODO: Why is this being called twice? + fi -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 + echo "Adding user to docker group..." 2>&1 | tee -a "$LOGFILE" + addUserToDockerGroup + fi -echo -e "\e[32mBuilding DAppNode Core if needed...\e[0m" 2>&1 | tee -a $LOGFILE -dappnode_core_build + # --- 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 -e "\e[32mDownloading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE -dappnode_core_download + echo "Building DAppNode Core if needed..." 2>&1 | tee -a "$LOGFILE" + dappnode_core_build -echo -e "\e[32mLoading DAppNode Core...\e[0m" 2>&1 | tee -a $LOGFILE -dappnode_core_load + echo "Downloading DAppNode Core..." 2>&1 | tee -a "$LOGFILE" + dappnode_core_download -if [ ! -f "/usr/src/dappnode/.firstboot" ]; then - echo -e "\e[32mDAppNode installed\e[0m" 2>&1 | tee -a $LOGFILE - dappnode_start -fi + # Build compose args now that compose files exist + build_dncore_compose_args -# 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 -fi + 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 + + # 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 + + 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 "$@" 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