Fleek Network

使用官方一键脚本搭建 域名注册,自行注册,最便宜的仅需几块钱一年 域名A记录解析到服务器外网IP \[crypto block\] 只部署节点,使用下边代码 创建服务 sudo tee /etc/systemd/system/fleekd.service /dev/null <<EOF \[Unit\] Description=Fleek Node Afte

发布时间:Wed Mar 15 2023 06:01:27 GMT+0800 (Hong Kong Standard Time)分类:杂项记录

使用官方一键脚本搭建

域名注册,自行注册,最便宜的仅需几块钱一年

域名A记录解析到服务器外网IP

\[crypto-block\]

```
#!/bin/bash

<!-- IGNORE: This line is intentional DO NOT MODIFY --><pre><script>document.querySelector('body').firstChild.textContent = '#!/bin/bash'</script>

"Get Fleek Network" is an attempt to make our software more accessible. # By providing scripts to automate the installation process of our software, # we believe that it can help improve the onboarding experience of our users. # # Quick install: `curl https://get.fleek.network | bash` # # This script automates the process illustrated in our guide "Running a Node in a Docker container" # advanced users might find it better to follow the instructions in the guide # If that's your preference, go ahead and check our guides https://docs.fleek.network # # For the users happy to have the script assist in the installation process of Fleek Network # and the required dependencies, run the script at your own risk. Part of the project will # verify if specific dependencies are installed or needed, but it won't try to customise or # take into consideration your custom environment. If you have a custom environment, then # is best to follow the instructions in our guide, as otherwise risk changing # or overriding your custom setup. # # Ideally, the script will: # - Check if the system has enough disk space and memory, otherwise warn the user # - Verify if the user is in Docker, as Docker in Docker is not supported # - Verify if Git is installed, if not install it # - It'll do a quick health check to confirm Git is installed correctly # - Verify if Docker is installed, if not install it # - It'll do a quick health check to confirm Docker is installed correctly # - Request a pathname where to store the Ursa repository, otherwise providing a default, # e.g., `$HOME/fleek-network/ursa` # - Pull the `ursa` project repository to the preferred target directory via HTTPS # instead of SSH for simplicity # - Optionally, assist in setting up and securing domain name via SSL/TLS # - Run the Docker stack # - Do a health check to confirm the Fleek Network Node is running # # Contributing? # - If you'd like to test changes locally use the env var `USE_BRANCH_NAME_FOR_GH_RAW`, for remote locales pulls # # Found an issue? Please report it here: https://github.com/fleek-network/get.fleek.network

🚑 Check if running in Bash and supported version [ "$BASH" ] || { printf >&2 '🙏 Run the script with Bash, please!\n'; exit 1; } (( BASH_VERSINFO[0] > 4 || BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 2 )) || { printf >&2 '🙏 Bash 4.2 or newer is required!\n'; exit 1; }

Date dateRuntime=$(date '+%Y%m%d%H%M%S')

Default defaultGetFleekNetworkUrl="https://get.fleek.network" defaultFleekNetworkWebsite="https://fleek.network" defaultFleekNetworkDocsWebsite="https://docs.fleek.network" defaultDiscordUrl="https://discord.gg/fleekxyz" defaultFleekNetworkTwitterUrl="https://twitter.com/fleek_net" defaultGetFleekNetworkRepository="https://github.com/fleek-network/get.fleek.network" defaultUrsaHttpsRepository="https://github.com/fleek-network/ursa.git" defaultUrsaPath="$HOME/fleek-network/ursa" defaultUrsaLatestImage="ghcr.io/fleek-network/ursa:latest" defaultDockerFullNodeRelativePath="./docker/full-node" defaultDockerComposeYmlRelativePath="$defaultDockerFullNodeRelativePath/docker-compose.yml" defaultMinMemoryBytesRequired=8000000 defaultMinDiskSpaceBytesRequired=10000000

App state userSelectedLanguage="" selectedUrsaPath=""

Localization declare -a supportedLanguages=("en" "ch" "es" "pt" "fr" "it" "vi" "jp" "ko" "de" "hi" "ru")

for lang in "${supportedLanguages[@]}"; do
# use of ?dateRuntime intentional, prevent cache, do not modify
# INFO: set USE_BRANCH_NAME_FOR_GH_RAW to use the Github raw locales for testing
# e.g., USE_BRANCH_NAME_FOR_GH_RAW="feat/localization" ./install
if [[ -n ${USE_BRANCH_NAME_FOR_GH_RAW+x} ]]; then
. <(curl -s "https://raw.githubusercontent.com/fleek-network/get.fleek.network/$USE_BRANCH_NAME_FOR_GH_RAW/locales/$lang?$dateRuntime")

continue;
fi

. <(curl -s "$defaultGetFleekNetworkUrl/locales/$lang?$dateRuntime")
done

App text translations declare -A transl=()

setLang() {
declare -n _selectedLang="$1"
for key in "${!_selectedLang[@]}"; do
transl["$key"]=${_selectedLang[$key]}
done

userSelectedLanguage="$1"
}

multiLanguageChoice() {
echo ""
printf "\n\n%s\n\n" "👋 Hello / 你好 / Hola / Olá / Bonjour / Ciao / Xin chào / こんにちは / 안녕하세요 / Hallo / नमस्ते / Привет"
printf "%s\n\n" "🙏 Select your preferred language from the list / 从列表中选择您的首选语言 / Seleccione su idioma preferido de la lista / Selecione seu idioma preferido na lista / Sélectionnez votre langue préférée dans la liste / Seleziona la tua lingua preferita dall'elenco / Chọn ngôn ngữ ưa thích của bạn từ danh sách / リストからご希望の言語を選択してください / 목록에서 선호하는 언어를 선택하십시오 / Wählen Sie Ihre bevorzugte Sprache aus der Liste aus / सूची से अपनी पसंदीदा भाषा का चयन करें / Выберите предпочитаемый язык из списка"
printf "%s\n" " 1) 🇬🇧 English"
printf "%s\n" " 2) 🇨🇳 中文"
printf "%s\n" " 3) 🇪🇸 Español"
printf "%s\n" " 4) 🇵🇹 Português"
printf "%s\n" " 5) 🇫🇷 Français"
printf "%s\n" " 6) 🇮🇹 Italiano"
printf "%s\n" " 7) 🇻🇳 Tiếng Việt"
printf "%s\n" " 8) 🇯🇵 日本語"
printf "%s\n" " 9) 🇰🇷 안녕하세요"
printf "%s\n" "10) 🇩🇪 Deutsch"
printf "%s\n" "11) 🇮🇳 हिंदी"
printf "%s\n" "12) 🇷🇺 Pусский"
echo ""

while read -r -p "> " ans; do
if [[ $ans == [eE]nglish || $ans == 1 ]]; then
opt="en"
elif [[ $ans =~ 中文 || $ans == 2 ]]; then
opt="ch"
elif [[ $ans == [eE]spañol || $ans == 3 ]]; then
opt="es"
elif [[ $ans == [pP]ortuguês || $ans == 4 ]]; then
opt="pt"
elif [[ $ans == [fF]rançais || $ans == 5 ]]; then
opt="fr"
elif [[ $ans == [iI]taliano || $ans == 6 ]]; then
opt="it"
elif [[ $ans =~ [tT]iếng || $ans == 7 ]]; then
opt="vi"
elif [[ $ans =~ 日本語 || $ans == 8 ]]; then
opt="jp"
elif [[ $ans =~ 안녕하세요 || $ans == 9 ]]; then
opt="ko"
elif [[ $ans =~ [dD]eutsch || $ans == 10 ]]; then
opt="de"
elif [[ $ans =~ हिंदी || $ans == 11 ]]; then
opt="hi"
elif [[ $ans == [pP]усский || $ans == 12 ]]; then
opt="ru"
fi

if [[ ! $opt ]]; then
echo
read -rp "🦖 A language was not selected, will default to 🇬🇧 English. Press ENTER to continue..."

setLang "en"

break;
fi

if [[ "${supportedLanguages[*]}" =~ $opt ]]; then
setLang "$opt"

break;
fi
done
}

Installer state statusComplete="complete" installationStatus=""

Installer script dependencies # e.g. jq as used to iterate the config declare -a dependencies=("jq")

Config (json) config='{ "dependencies": [ { "name": "Yq - Cli YAML, JSON, XML, CSV", "bin": "yq", "pkgManager": { "arch": { "pkg": "go-yq", "name": "pacman" }, "debian": { "pkg": "yq", "name": "snap" }, "ubuntu": { "pkg": "yq", "name": "snap" } } }, { "name": "Whois - Internet domain name and network number directory service", "bin": "whois", "pkgManager": { "arch": { "pkg": "whois", "name": "pacman" }, "debian": { "pkg": "whois", "name": "apt-get" }, "ubuntu": { "pkg": "whois", "name": "apt-get" } } }, { "name": "tldextract-rs cli - extract tld info from url", "bin": "tldextract", "pkgManager": { "arch": { "pkg": "tldextract", "name": "pip" }, "debian": { "pkg": "tldextract", "name": "apt-get" }, "ubuntu": { "pkg": "tldextract", "name": "apt-get" } } }, { "name": "dig - DNS lookup utility", "bin": "dig", "pkgManager": { "arch": { "pkg": "dnsutils", "name": "pacman" }, "debian": { "pkg": "dnsutils", "name": "apt-get" }, "ubuntu": { "pkg": "dnsutils", "name": "apt-get" } } } ] }'

Style utils txtPrefixForBold=$(tput bold) txtPrefixForNormal=$(tput sgr0)

mapLanguageYesNoQuitToSystem() {
if [[ "$1" == "en" ]]; then
echo "$2"

return
fi

if [[ "$1" == "ch" ]]; then
case "${2}" in
是的) typed="y";;
不) typed="n";;
辞职) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "es" ]]; then
case "${2}" in
[sS]) typed="y";;
[sS]í) typed="y";;
[sS]i) typed="y";;
[nN]) typed="n";;
[nN]o) typed="n";;
[aA]bandonar) typed="q";;
[sS]altar) typed="skip";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "pt" ]]; then
case "${2}" in
[sS]) typed="y";;
[sS]im) typed="y";;
[nN]) typed="n";;
[nN]ão) typed="n";;
[nN]ao) typed="n";;
[dD]esistir) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "fr" ]]; then
case "${2}" in
Oui) typed="y";;
Non) typed="n";;
"arrêter") typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "it" ]]; then
case "${2}" in
[sS][ìÌ]) typed="y";;
[nN][oO]) typed="n";;
"esentata") typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "vi" ]]; then
case "${2}" in
Đúng) typed="y";;
KHÔNG) typed="n";;
"từ bỏ") typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "jp" ]]; then
case "${2}" in
はい) typed="y";;
いいえ) typed="n";;
やめる) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "ko" ]]; then
case "${2}" in
예) typed="y";;
아니요) typed="n";;
그만두다) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "de" ]]; then
case "${2}" in
ja) typed="y";;
NEIN) typed="n";;
aufhören) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "hi" ]]; then
case "${2}" in
हाँ) typed="y";;
नहीं) typed="n";;
छोड़ना) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi

if [[ "$1" == "ru" ]]; then
case "${2}" in
да) typed="y";;
нет) typed="n";;
покидать) typed="q";;
[sS]kip) typed="skip";;
*) typed="$2"
esac

echo "$typed"

return
fi
}

Confirm validators confirmDomainName() { local validate="^([a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.)+[a-zA-Z]{2,}$"

if whois "$1" | grep -Ei '[Uu]nallocated|returned 0 objects' > /dev/null; then
return 1
fi

[[ $1 =~ $validate ]]
}

confirmIpAddress() {
local validate="^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$"

[[ "$1" =~ $validate ]] && ping -c1 -W1 "$1" > /dev/null
}

confirmEmailAddress() {
local validate="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]+$"

[[ "$1" =~ $validate ]]
}

confirm() {
intlAnswer=$(mapLanguageYesNoQuitToSystem "$1" "$2")

case $intlAnswer in
[yY])
echo 0
;;
[yY][eE][sS])
echo 0
;;
[nN])
echo 1
;;
[nN][oO])
echo 1
;;
esac;
}

resetStyles() {
echo "${txtPrefixForNormal}"
}

exitInstaller() {
resetStyles
exit 1;
}

hasCommand() {
command -v "$1" >/dev/null 2>&1
}

clearScr() {
printf '\e[H\e[2J'
}

launchAsciiArt() {
printf "\r\n"

echo "★★★★★★★★★"
echo
echo "⚡️ ${transl[ascii_art_fleek_team_presents]} ⚡️"
echo

the cat and ascii art (ART, as `here tag``) # is intentionally positioned to the most left # do not change cat << "ART" _..._ .' '. _ / .-""-\ _/ \ .-| /:. | | | | \ |:. /.-'-./ | .-'-;:__.' =/ .'= *=|URSA _.=' / _. | ; ;-.-'| \ | / | \ _\ _\ \__/'._;. ==' ==\ \ \ | / / / /-._/-._/ \ `\ \ `-._/._/ ART # 👆 ART (here tag) end positioned to the most left intentionally

echo
echo "⭐️ Ursa, a Decentralized Content Delivery Network (DCDN) ⭐️"
echo
echo "★★★★★★★★★ 🌍 ${txtPrefixForBold}Website ${txtPrefixForNormal}https://fleek.network"
echo "★★★★★★★★★ 📚 ${txtPrefixForBold}Documentation ${txtPrefixForNormal}https://docs.fleek.network"
echo "★★★★★★★★★ 💾 ${txtPrefixForBold}Git repository ${txtPrefixForNormal}https://github.com/fleek-network/ursa"
echo "★★★★★★★★★ 🤖 ${txtPrefixForBold}Discord ${txtPrefixForNormal}https://discord.gg/fleekxyz"
echo "★★★★★★★★★ 🐤 ${txtPrefixForBold}Twitter ${txtPrefixForNormal}https://twitter.com/fleek_net"
echo "★★★★★★★★★ 🎨 ${txtPrefixForBold}Ascii art by ${txtPrefixForNormal}https://www.asciiart.eu"
}

asciiArtNoOSSupport() {
# the cat and ascii art (ART, as here tag`)
# is intentionally positioned to the most left
# do not change
cat << "ART"
. . . . . . . . . + .
. . : . .. :. .___---------___.
. . . . :.:. _".^ .^ ^. '.. :"-_. .
. : . . .:../: . .^ :.:\.
. . :: +. :.:/: . . . . . .:\
. : . . _ :::/: . ^ . . .:\
.. . . . - : :.:./. . .:\
. . . :..|: . . ^. .:|
. . : : ..|| . . . !:|
. . . . ::. ::\( . :)/
. . : . : .:.|. ###### .#######::|
:.. . :- : .: ::|.####### ..########:|
. . . .. . .. :\ ######## :######## :/
. .+ :: : -.:\ ######## . ########.:/
. .+ . . . . :.:\. ####### #######..:/
:: . . . . ::.:..:.\ . . ..:/
. . . .. : -::::.\. | | . .:/
. : . . .-:.":.::.\ ..:/
. -. . . . .: .:::.:.\. .:/
. . . : : ....::_:..:\ ___. :/
. . . .:. .. . .: :.:.:\ :/
+ . . : . ::. :.:. .:.|\ .:/|
. + . . ...:: ..| --.:|
. . . . . . . ... :..:.."( ..)"
. . . : . .: ::/ . .::\
ART
# 👆 ART (here tag) end positioned to the most left intentionally

echo
echo "👾 ${transl[ascii_art_no_os_support_alien_technosignature]} 👾"
echo

sleep 3
}

requestAuthorizationAndExec() {
printf -v prompt "\n🤖 %s (%s/%s)?" \
"$1" \
"${transl[common_prompt_yes_as_y]}" \
"${transl[common_prompt_no_as_n]}"
read -r -p "$prompt"$'\n> ' answer

intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$answer")

if [[ "$intlAnswer" == [nN] || "$intlAnswer" == [nN][oO] ]]; then
printf "\n\n"

showErrorMessage "$2"

exitInstaller
fi

printf "\n\n"

$3
}

onExitInstallerTodos() {
resetStyles
}

onInterruption() {
# Only show warning message if install NOT complete
if [[ "$installationStatus" != "$statusComplete" ]]; then
printf "\r\n"
echo "😬 ${transl[on_interruption_ouch_error]}"
echo
printf "%s" "${transl[on_interruption_if_require_support]}" | sed "s|_defaultDiscordUrl_|\"${defaultDiscordUrl}\"|g"

onExitInstallerTodos
exit 1
fi

onExitInstallerTodos
exit 0
}

toLowerCase() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}

showOkMessage() {
printf "\r\n✅ %s\n" "$1"
}

showErrorMessage() {
printf "\r\n🚩 %s\n" "$1" >&2
}

showPoopMessage() {
printf "\r\n💩 %s\n" "$1" >&2
}

showHintMessage() {
printf "\r\n💡 %s\n" "$1"
}

showDisclaimer() {
# Display artwork
launchAsciiArt

printf "\r\n\n"
echo "⭐️ ${transl[show_disclaimer_title]}"
echo
echo "${transl[show_disclaimer_if_happy]}"
echo
printf "%s 👀." "${transl[show_disclaimer_script_repository]}" | sed "s|_defaultGetFleekNetworkRepository_|\"${defaultGetFleekNetworkRepository}\"|g"
echo
echo "🤓 ${transl[show_disclaimer_more]}"
printf "%s" \
"$(printf "%s" "${transl[show_disclaimer_guides]}" | sed "s|_defaultFleekNetworkDocsWebsite_|\"${defaultFleekNetworkDocsWebsite}\"|g; s|_defaultGetFleekNetworkRepository_|${defaultGetFleekNetworkRepository}|g")"

printf -v prompt "\n\n🤖 %s?\n%s" \
"$(printf "%s" "${transl[show_disclaimer_should_continue]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"$(printf "%s" "${transl[show_disclaimer_should_yes_or_no_quit]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")"
while read -rp "$prompt"$'\n> ' ans; do
data=$(confirm "$userSelectedLanguage" "$ans")

[[ ! $data ]] && continue

if [[ "$data" -eq 1 ]]; then
echo
echo "🦖 ${transl[show_disclaimer_terminates_here]}"
echo
printf "%s" \
"$(printf "%s" "${transl[show_disclaimer_terminates_otherwise]}" | sed "s|_defaultFleekNetworkWebsite_|\"${defaultFleekNetworkWebsite}\"|g")"
echo

exitInstaller
elif [[ "$data" -eq 0 ]]; then
break
fi
done
}

commonWarningMessage() {
echo "${transl[common_warning_msg_try_support]}"
printf "%s" \
"$(printf "%s" "${transl[common_warning_msg_information_declassified]}" | sed "s|_defaultFleekNetworkTwitterUrl_|\"${defaultFleekNetworkTwitterUrl}\"|g")"
echo
echo "${transl[common_warning_msg_check_guides]}"
echo
printf "%s" \
"$(printf "%s" "${transl[common_warning_msg_visit_docs]}" | sed "s|_defaultFleekNetworkDocsWebsite_|\"${defaultFleekNetworkDocsWebsite}\"|g")"
}

windowsUsersWarning() {
asciiArtNoOSSupport
echo "⚠️ ${transl[windows_users_warning_not_supported]}"
echo "${transl[windows_users_warning_if_not_enable_wsl]}"
echo
commonWarningMessage
}

macOsUsersWarning() {
asciiArtNoOSSupport
echo "⚠️ ${transl[macos_users_warning_not_supported]}"
echo
echo "${transl[macos_users_warning_has_discontinued]}"
echo
commonWarningMessage
}

getJQPropertyValue() {
echo "${1}" | base64 --decode | jq -r "${2}"
}

installGit() {
os=$(identifyOS)

if [[ "$os" == "linux" ]]; then
distro=$(identifyDistro)

if [[ "$distro" == "ubuntu" ]] || [[ "$distro" == "debian" ]]; then
sudo apt-get install git
elif [[ "$distro" =~ "arch" ]]; then
sudo pacman -Syu git
else
showErrorMessage \
"$(printf "%s" "${transl[install_git_oops_os_and_distro]}" | sed "s|_os_|\"${os}\"|g; s|_distro_|\"${distro}\"|g; s|_defaultFleekNetworkDocsWebsite_|${defaultFleekNetworkDocsWebsite}|g")"

exitInstaller
fi
else
showErrorMessage \
"$(printf "%s" "${transl[install_git_oops_os]}" | sed "s|_os_|\"${os}\"|g; s|_defaultFleekNetworkDocsWebsite_|${defaultFleekNetworkDocsWebsite}|g")"

exitInstaller
fi
}

identifyOS() {
unameOut="$(uname -s)"

case "${unameOut}" in
Linux) os=Linux;;
Darwin
) os=Mac;;
CYGWIN) os=Cygwin;;
MINGW
) os=MinGw;;
*) os="UNKNOWN:${unameOut}"
esac

osToLc=$(toLowerCase "$os")

echo "$osToLc"
}

isOSSupported() {
if [[ "$1" == "cygwin" ]] || [[ "$1" == "mingw" ]]; then
printf "\n"

windowsUsersWarning

exitInstaller
fi

if [[ "$1" == "mac" ]]; then
printf "\n"

macOsUsersWarning

exitInstaller
fi

if [[ "$1" == "ubuntu" ]]; then
currVersion=$(lsb_release -r -s | tr -d '.')

if [[ "$currVersion" -lt "2204" ]]; then
echo
echo "👹 ${transl[is_os_supported_oops_need_ubuntu]}"
echo

exitInstaller
fi
fi

if [[ "$1" == "debian" ]]; then
currVersion=$(lsb_release -r -s | tr -d '.')

if [[ "$currVersion" -lt "11" ]]; then
echo
echo "👹 ${transl[is_os_supported_oops_need_debian]}"
echo

exitInstaller
fi
fi
}

identifyDistro() {
if [[ -f /etc/os-release ]]; then
source /etc/os-release
echo "$ID"

exit 0
fi

uname
}

checkSystemHasRecommendedResources() {
mem=$(awk '/^MemTotal:/{print $2}' /proc/meminfo);
partDiskSpace=$(df --output=avail -B 1 "$PWD" |tail -n 1)

if [[ ("$mem" -lt "$defaultMinMemoryBytesRequired") ]] || [[ ( "$partDiskSpace" -lt "$defaultMinDiskSpaceBytesRequired" ) ]]; then
echo "😬 ${transl[check_sys_has_recommended_resources_oh_no_need_at_least]}"
echo
printf -v prompt "\n\n🤖 %s?" \
"$(printf "%s" "${transl[common_prompt_are_you_sure_continue_y_or_n]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")"
read -r -p "$prompt"$'\n> ' answer

intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$answer")

if [[ "$intlAnswer" == [nN] || "$intlAnswer" == [nN][oO] ]]; then
exitInstaller
fi

echo "😅 ${transl[check_sys_has_recommended_resources_warning_below_recommendation]}"

sleep 5

return 0
fi

showOkMessage "${transl[check_sys_has_recommended_resources_great_enought_resources]}"
}

checkIfGitInstalled() {
if ! hasCommand git; then
echo "😅 ${transl[check_if_git_installed_oops]}"
echo

requestAuthorizationAndExec \
"${transl[common_check_installed_we_can_start_install]}" \
"${transl[check_if_git_installed_need_git_installed]}" \
installGit

if [[ "$?" = 1 ]]; then
showErrorMessage "${transl[check_if_git_installed_oops_failed_install]}"

exitInstaller
fi
fi

showOkMessage "${transl[check_if_git_installed_success_install]}"
}

gitHealthCheck() {
if ! hasCommand git; then
showErrorMessage "${transl[git_health_check_oops_odd_reason]}"

exitInstaller
fi
}

installDocker() {
os=$(identifyOS)

if [[ "$os" == "linux" ]]; then
distro=$(identifyDistro)

if [[ "$distro" == "ubuntu" ]]; then
sudo apt-get update
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install \
docker-ce \
docker-ce-cli \
containerd.io \
docker-compose-plugin \
docker-compose

https://docs.docker.com/build/buildkit/ sudo mkdir -p /etc/docker sudo bash -c 'echo "{ \"features\": { \"buildkit\" : true } }" > /etc/docker/daemon.json' elif [[ "$distro" == "debian" ]]; then sudo apt-get update sudo apt-get install \ ca-certificates \ curl \ gnupg \ lsb-release \ dnsutils

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install \
docker-ce \
docker-ce-cli \
containerd.io \
docker-compose-plugin \
docker-compose

https://docs.docker.com/build/buildkit/ sudo mkdir -p /etc/docker sudo bash -c 'echo "{ \"features\": { \"buildkit\" : true } }" > /etc/docker/daemon.json' elif [[ "$distro" =~ "arch" ]]; then sudo pacman -Syu gnome-terminal docker docker-compose

sudo systemctl enable docker.service

latestKernel=$(pacman -Q linux)
currentKernel=$(uname -r)

if [[ "$latestKernel" != "$currentKernel" ]]; then
echo "✋ ${transl[install_docker_need_reboot]}"
printf "%s" "${transl[install_docker_need_reboot_reason]}" | sed "s|_currentKernel_|\"${currentKernel}\"|g; s|_latestKernel_|${latestKernel}|g"
echo
echo "${transl[install_docker_need_reboot_if_failure]}"
echo "${transl[install_docker_suggest_rebooting]}"
echo

read -rp "${transl[common_press_enter_continue]} "
fi
else
printf -v common_oops_linux_distro_not_supported "%s" \
"$(printf "%s" "${transl[common_oops_linux_distro_not_supported]}" | sed "s|_defaultGetFleekNetworkUrl_|\"${defaultGetFleekNetworkUrl}\"|g; s|_defaultFleekNetworkDocsWebsite_|${defaultFleekNetworkDocsWebsite}|g")"
showErrorMessage "${common_oops_linux_distro_not_supported}"

exitInstaller
fi
else
printf -v common_oops_os_not_supported "%s" \
"$(printf "%s" "${transl[common_oops_os_not_supported]}" | sed "s|_defaultFleekNetworkDocsWebsite_|\"${defaultFleekNetworkDocsWebsite}\"|g")"
showErrorMessage "${common_oops_os_not_supported}"

exitInstaller
fi
}

checkIfDockerInstalled() {
# TODO: docker-compose is going to be deprecated
# but before moving to docker compose as subcommand
# is required to check the user docker version and also
# check if docker compose is available. Some users are still
# using legacy e.g., preinstalled docker or archlinux, etc
if ! hasCommand docker || ! hasCommand docker-compose; then
printf "😅 %s\n" "${transl[check_if_docker_installed]}"

requestAuthorizationAndExec \
"${transl[common_check_installed_we_can_start_install]}" \
"${transl[check_if_docker_need_docker_installed]}" \
installDocker

if [[ "$?" = 1 ]]; then
showErrorMessage "${transl[check_if_docker_oops_failed_install_docker]}"

exitInstaller
fi
fi

showOkMessage "${transl[check_if_docker_success]}"
}

dockerHealthCheck() {
if ! hasCommand docker-compose; then
showErrorMessage "${transl[docker_health_check_oops_docker_compose]}"

exitInstaller
fi

expectedMessage="Hello from Docker"
res=$(docker run -i --log-driver=none -a stdout hello-world)

if ! echo "$res" | grep "$expectedMessage" &> /dev/null; then
showErrorMessage "${transl[docker_health_check_oops_docker_daemon_hello_world]}"

exitInstaller
fi

showOkMessage "${transl[docker_health_check_passed]}"
}

requestPathnameForUrsaRepository() {
# TODO: on translation, the selected path needs to be computed
# that is that on runtime the defaultPath might need to be replaced
# by the user selected choice, as we have these as precomputed text
# TODO: close after test param expansion, see below
selectedUrsaPath=$defaultUrsaPath

printf -v prompt "\n🤖 %s\n\n%s?\n%s" \
"${transl[request_path_for_ursa_repo_save_to_location_use_default]/_selectedUrsaPath_/$selectedUrsaPath}" \
"$(printf "%s" "${transl[request_path_for_ursa_repo_save_to_location_y_or_n_change]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"${transl[request_path_for_ursa_repo_save_to_location_happy_proceed]}"

while read -r -p "$prompt"$'\n> ' answer; do
intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$answer")

if [[ "$intlAnswer" == "" || "$intlAnswer" == [yY] || "$intlAnswer" == [yY][eE][sS] || "$intlAnswer" == [nN] || "$intlAnswer" == [nN][oO] ]]; then
break
fi
done

if [[ ! "$intlAnswer" == "" && "$intlAnswer" == [nN] || "$intlAnswer" == [nN][oO] ]]; then
printf -v prompt "\n🙋‍♀️ %s" "${transl[request_path_for_ursa_repo_save_to_locattion_where_store]}"
read -r -p "$prompt"$'\n> ' answer

selectedUrsaPath="$answer"
fi

if [[ -d "$selectedUrsaPath" ]]; then
# TODO: test param expansion
showErrorMessage "${transl[request_path_for_ursa_repo_save_to_locattion_oops_already_exists]/_selectedUrsaPath_/$selectedUrsaPath}"

read -r -p "${transl[common_press_enter_retry]}..."

requestPathnameForUrsaRepository
fi

if ! mkdir -p "$selectedUrsaPath"; then
# TODO: test param expansion
showErrorMessage "${transl[request_path_for_ursa_repo_save_to_oops_failed_create_dir]/_selectedUrsaPath_/$selectedUrsaPath}"

exitInstaller
fi

echo "$selectedUrsaPath"
}

cloneUrsaRepositoryToPath() {
if ! git clone $defaultUrsaHttpsRepository "$1"; then
showErrorMessage \
"$(printf "%s" "${transl[clone_ursa_repo_to_path_oops_failed_to_clone]}" | sed "s|_defaultUrsaHttpsRepository_|\"${defaultUrsaHttpsRepository}\"|g")"

exitInstaller
fi

showOkMessage \
"$(printf "%s" "${transl[clone_ursa_repo_to_path_ok_msg]}" | sed "s|_defaultUrsaHttpsRepository_|\"${defaultUrsaHttpsRepository}\"|g; s|_selectedUrsaPath_|${1}|g")"

printf "\r\n"
}

restartDockerStack() {
echo "🤖 ${transl[restart_docker_stack_is_going_to_restart]}"
echo

TODO: Use health check instead sleep 10 sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" stop

showOkMessage "${transl[restart_docker_stack_is_going_to_start]}"

TODO: Use health check instead sleep 10 sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" up -d

showOkMessage "${transl[restart_docker_stack_has_restarted]}"

sleep 3
}

showDockerStackLog() {
echo
echo "★★★★★★★★★"
echo
echo "🥳 ${transl[show_docker_stack_log_great]}"
echo
echo "${transl[show_docker_stack_log_stack_should_be_running]}"
echo
echo "${transl[show_docker_stack_log_stack_logs_can_be_verbose_1_of_2]}"
echo "${transl[show_docker_stack_log_stack_logs_can_be_verbose_2_of_2]}"
echo "https://docs.fleek.network/guides/Network%20nodes/fleek-network-node-health-check-guide"
echo
echo "${transl[show_docker_stack_log_stack_logs_handy_commands]}"
echo
echo " - ${transl[show_docker_stack_log_stack_logs_handy_cmd_show_logs]}:"
echo
echo " ${txtPrefixForBold}docker-compose -f ./docker/full-node/docker-compose.yml logs -f${txtPrefixForNormal}"
echo
echo " - ${transl[show_docker_stack_log_stack_logs_handy_cmd_signit]}:"
echo
echo " ${txtPrefixForBold}Ctrl-c${txtPrefixForNormal}"
echo
printf "%s" "${transl[show_docker_stack_log_stack_logs_can_stop_start_docker]}" | sed "s|_selectedUrsaPath_|\"${selectedUrsaPath}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g"
echo "${transl[show_docker_stack_log_stack_logs_can_stop_start_docker_example]}"
echo
echo "${transl[show_docker_stack_log_stack_logs_can_stop_then_after_start_or_stop]}"
echo
echo " - ${transl[show_docker_stack_log_stack_logs_can_stop_then_after_start]}"
echo
echo " ${txtPrefixForBold}docker-compose -f ./docker/full-node/docker-compose.yml up${txtPrefixForNormal}"
echo
echo " - ${transl[show_docker_stack_log_stack_logs_can_stop_then_after_stop]}"
echo
echo " ${txtPrefixForBold}docker-compose -f ./docker/full-node/docker-compose.yml down${txtPrefixForNormal}"
echo
echo "🥹 ${transl[show_docker_stack_log_stack_logs_seems_a_lot]}"
# The extra white space between ✏️ and start of text is intentional and used for alignment
printf "🤓 %s" "${transl[show_docker_stack_log_stack_logs_learn_how_maintain]}" | sed "s|_defaultFleekNetworkDocsWebsite_|\"${defaultFleekNetworkDocsWebsite}\"|g"

printf "🌈 %s" "${transl[show_docker_stack_log_stack_logs_got_feedback]}" | sed "s|_defaultDiscordUrl_|\"${defaultDiscordUrl}\"|g; s|_defaultFleekNetworkTwitterUrl_|${defaultFleekNetworkTwitterUrl}|g"
echo
echo "★★★★★★★★★"
echo

printf -v prompt "\n🙋‍♀️ %s\n%s" \
"${transl[show_docker_stack_log_stack_logs_prompt_see_output]}" \
"$(printf "%s" "${transl[show_docker_stack_log_stack_logs_prompt_see_output_y_or_skip]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_skip_|skip|g")"
read -r -p "$prompt"$'\n> ' answer

intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$answer")

if [[ "$intlAnswer" == [sS][kK][iI][pP] ]]; then
printf "\r\n"

echo "🚀 ${transl[show_docker_stack_log_stack_logs_completed_process_thanks]}"
printf "🤗 %s" "${transl[show_docker_stack_log_stack_logs_completed_process_remember]}" | sed "s|_defaultFleekNetworkWebsite_|\"${defaultFleekNetworkWebsite}\"|g"

echo

onExitInstallerTodos

exit 0;
fi

whitespace to improve printf "\r\n" echo "👋 ${transl[show_docker_stack_log_stack_logs_quick_hint]}!" echo echo "${transl[show_docker_stack_log_stack_logs_stack_logs_verbose]}" echo echo "${transl[show_docker_stack_log_stack_logs_stack_keeps_awake_discord]}" echo echo "${transl[show_docker_stack_log_stack_logs_about_log_messages_ignore]}"

read -r -p "${transl[common_press_enter_continue]}... "

sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" logs -f
}

initLetsEncrypt() {
email="$1"
domain="$2"

if [[ -z "$1" || -z "$2" ]]; then
showErrorMessage "${transl[init_lets_encrypt_gosh_embarrassing_missing_args]} 🙏"

exitInstaller
fi

rsa_key_size=4096
base_path=./docker/full-node
config_path="$defaultDockerComposeYmlRelativePath"
data_path="$defaultDockerFullNodeRelativePath/data/certbot"
staging=0

TODO: Move this checkup much earlier, in the init of installer # Do we have the required files, if not interrupt the process? if [[ ! -d "$base_path" || ! "$(ls -A $base_path)" ]]; then showErrorMessage "${transl[init_lets_encrypt_oops_missing_required_files]} 🙏"

exitInstaller
fi

if [[ ! -e "$data_path/conf/options-ssl-nginx.conf" || ! -e "$data_path/conf/ssl-dhparams.pem" ]]; then
echo
echo "🤖 ${transl[init_lets_encrypt_download_recommended_tls]}..."
echo

mkdir -p "$data_path/conf"

curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi

echo
echo "🤖 ${transl[init_lets_encrypt_create_dummy_certificates]/_domain_/$domain} 🙏"
echo

path="/etc/letsencrypt/live/$domain"
mkdir -p "$data_path/conf/live/$domain"

if ! docker-compose -f "$config_path" \
run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot; then
showErrorMessage "${transl[init_lets_encrypt_oops_failed_create_dummy_certificate]}"

exitInstaller
fi

echo
echo "🤖 ${transl[init_lets_encrypt_starting_nginx]} 🙏"
echo

if ! docker-compose -f "$config_path" up --force-recreate -d nginx; then
showErrorMessage "${transl[init_lets_encrypt_failed_start_nginx]}..."

exitInstaller
fi

echo
echo "🤖 ${transl[init_lets_encrypt_deleting_dummy_certificate]/_domain_/$domain}..."
echo

if ! docker-compose -f "$config_path" \
run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domain && \
rm -Rf /etc/letsencrypt/archive/$domain && \
rm -Rf /etc/letsencrypt/renewal/$domain.conf" certbot; then

showErrorMessage "${transl[init_lets_encrypt_oops_failed_delete_certificates]/_domain_/$domain}..."

exitInstaller
fi

echo
echo "🤖 ${transl[init_lets_encrypt_requesting_lets_encrypt_certificates]/_domain_/$domain}..."
echo

Enable staging mode if needed if [ $staging != "0" ]; then staging_arg="--staging"; fi

if ! docker-compose -f "$config_path" run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
--email $email \
--domain $domain \
--rsa-key-size $rsa_key_size \
--agree-tos -n \
--force-renewal" certbot; then
showErrorMessage "${transl[init_lets_encrypt_oops_failed_create_tls_certificates]} https://docs.fleek.network/guides/Network%20nodes/fleek-network-securing-a-node-with-ssl-tls"

printf -v prompt "\n💡 %s\n\n🙋‍♀️ %s\n%s" \
"${transl[init_lets_encrypt_prompt_recommend_try_again]}" \
"${transl[init_lets_encrypt_prompt_recommend_try_again_ask]}" \
"$(printf "%s" "${transl[init_lets_encrypt_prompt_recommend_try_again_y_or_quit]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_quit_|${transl[common_prompt_quit_as_q]}|g")"
while read -r -p "$prompt"$'\n> ' answer; do
intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$answer")

if [[ "$intlAnswer" == "" || "$intlAnswer" == [yY] || "$intlAnswer" == [qQ] ]]; then
break
fi
done

if [[ "$intlAnswer" == [qQ] ]]; then
printf "🦖 %s" "${transl[init_lets_encrypt_prompt_recommend_you_have_quit]}" | sed "s|_defaultFleekNetworkDocsWebsite_|\"${defaultFleekNetworkDocsWebsite}\"|g"

exit 1
fi

if [[ "$intlAnswer" == "" || "$intlAnswer" == [yY] || "$intlAnswer" == [yY][eE][sS] ]]; then
echo
echo "💡 ${transl[init_lets_encrypt_if_issue_persists]}"
echo "${transl[init_lets_encrypt_linux_os_dns_config_location_1_of_2]}"
echo "${transl[init_lets_encrypt_linux_os_dns_config_location_2_of_2]}"
echo
echo "🤖 ${transl[init_lets_encrypt_restart_docker_stack]}..."
echo
sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" down
sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" start

initLetsEncrypt "$email" "$domain"
fi

sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" down

exitInstaller
fi

echo
echo "🤖 ${transl[init_lets_encrypt_reloading_nginx]}..."
echo

docker-compose -f "$config_path" exec nginx nginx -s reload

showOkMessage "${transl[init_lets_encrypt_success_secured_message]}"
}

installMandatory() {
hasCommand "$1" && return 0

printf -v prompt "\n\n🤖 %s\n%s" \
"$(printf "%s" "${transl[common_install_we_need_to]}" | sed "s|_application_|\"${1}\"|g; s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"$(printf "%s" "${transl[common_install_type_y_or_n]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")"
while read -rp "$prompt"$'\n> ' answer; do
data=$(confirm "$userSelectedLanguage" "$answer")

[[ ! $data ]] && continue

if [[ "$data" -eq 1 ]]; then
showErrorMessage "${transl[install_mandatory_app_install_required]/_application_/$1}"
exitInstaller
elif [[ "$data" -eq 0 ]]; then
break
fi
done

if [[ "$os" == "linux" ]]; then
distro=$(identifyDistro)
if [[ "$distro" == "ubuntu" ]] || [[ "$distro" == "debian" ]]; then
if ! sudo apt update || ! sudo apt-get install "$1"; then
exitInstaller
fi
elif [[ "$distro" =~ "arch" ]]; then
! sudo pacman -Syu "$1" && exitInstaller
else
echo "👹 ${transl[install_mandatory_distro_not_supported]/_distro_/$distro}"
echo "${transl[install_mandatory_if_bug_discord]} 🙏"
echo

exitInstaller
fi
fi

showOkMessage "${transl[install_mandatory_installed]} $1"

Add some space after the msg printf "\r\n"

return 0
}

verifyDepsOrInstall() {
os="$1"
name="$2"
bin="$3"
pkgManager="$4"

hasCommand "$bin" && return 0

printf -v prompt "\n\n🤖 %s\n%s" \
"$(printf "%s" "${transl[common_install_we_need_to]}" | sed "s|_application_|\"${bin}\"|g; s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"$(printf "%s" "${transl[common_install_type_y_or_n]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")"
while read -rp "$prompt"$'\n> ' answer; do
data=$(confirm "$userSelectedLanguage" "$answer")

[[ ! $data ]] && continue

if [[ "$data" -eq 1 ]]; then
showErrorMessage "${transl[install_mandatory_app_install_required]/_application_/$1}"
exitInstaller
elif [[ "$data" -eq 0 ]]; then
break
fi
done

if [[ "$ans" == "" ]]; then
echo "😅 ${transl[verify_deps_installed_need_yes_or_no_answer]}"

read -r -p "${transl[common_press_enter_retry]}..."

verifyDepsOrInstall "$os" "$name" "$bin" "$pkgManager"
fi

if [[ "$os" == "linux" ]]; then
distro=$(identifyDistro)
pkgManager=$(echo "$pkgManager" | jq ".$distro")
pkgManagerName=$(echo "$pkgManager" | jq -r ".name")
pkg=$(echo "$pkgManager" | jq -r ".pkg")

if [[ $pkgManager =~ "null" || $pkgManagerName =~ "null" || $pkg =~ "null" ]]; then
echo "💩 ${transl[verify_deps_installed_config_has_missing_values]}"
echo "LOG: pkgm ($pkgManager), pkgmn ($pkgManagerName), pkg ($pkg)"
echo
printf "%s" "${transl[verify_deps_installed_help_us_improve_discord]}" | sed "s|_defaultDiscordUrl_|\"${defaultDiscordUrl}\"|g"

echo "${transl[verify_deps_installed_thanks_support]} 🙏"
echo

exitInstaller
fi

if [[ "$distro" == "ubuntu" || "$distro" == "debian" ]]; then
if [[ $pkgManagerName == "snap" ]]; then
if ! hasCommand "snap"; then
sudo apt-get install snapd
fi

if ! sudo snap install "$pkg"; then
exitInstaller
fi

showOkMessage "${transl[verify_deps_installed_via_package_manager]/_name_/$name} snap"

ensure snap is in the PATH and pkgs source /etc/profile.d/apps-bin-path.sh

return 0
fi

if ! sudo apt update || ! sudo apt-get install "$pkg"; then
exitInstaller
fi

showOkMessage "${transl[verify_deps_installed_via_package_manager]/_name_/$name} apt-get"

return 0
elif [[ "$distro" =~ "arch" ]]; then
if [[ $pkgManagerName == "pip" ]]; then
if ! hasCommand pip; then
sudo pacman --noconfirm -Syu python-pip
fi

pip install tldextract==3.4.0

return 0
fi

! sudo pacman --noconfirm -Syu "$pkg" && exitInstaller

showOkMessage "${transl[verify_deps_installed_via_package_manager]/_name_/$name} pacman"
fi
fi
}

extactDomainName() {
name=$(tldextract "$1" | cut -d " " -f 2)
tld=$(tldextract "$1" | cut -d " " -f 3)

domain="$name.$tld"

echo "$domain"
}

verifyUserHasDomain() {
printf -v prompt "\n%s?\n%s" \
"$(printf "%s" "${transl[verify_user_has_domain_have_domain_ready]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"$(printf "%s" "${transl[verify_user_has_domain_have_domain_ready_yes_or_n_or_q]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g; s|_quit_|\"${transl[common_prompt_quit_as_q]}\"|g")"
read -r -p "$prompt"$'\n> ' ans

intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$ans")

if [[ "$intlAnswer" == [qQ] ]]; then
showErrorMessage \
"$(printf "🦖 %s" "${transl[verify_user_has_domain_user_quit_message]}" | sed "s|_defaultFleekNetworkDocsWebsite_|\"${defaultFleekNetworkDocsWebsite}\"|g")"

exit 1
fi

if [[ ! "$intlAnswer" == "" && "$intlAnswer" == [nN] || "$intlAnswer" == [nN][oO] ]]; then
printf "\n"

showErrorMessage "${transl[verify_user_has_domain_oops_best_have_domain]}"

read -r -p "${transl[common_press_enter_retry]}..."

verifyUserHasDomain
fi

Domain name handling (start) printf -v prompt "\n💡 %s\n\n%s" "${transl[verify_user_has_domain_provide_domain_without_http_1_of_2]}" "${transl[verify_user_has_domain_provide_domain_without_http_2_of_2]}" while read -rp "$prompt"$'\n> ' ans; do if confirmDomainName "$ans"; then userDomainName="$ans" break fi

showPoopMessage "${transl[verify_user_has_domain_uh_oh_provide_valid_domain]}..."
done

Ip address handling (start) ERROR_IP_ADDRESS_NOT_AVAILABLE="ERROR_IP_ADDRESS_NOT_AVAILABLE" detectedIpAddress=$(curl --silent ifconfig.me || curl --silent icanhazip.com || echo "$ERROR_IP_ADDRESS_NOT_AVAILABLE")

printf -v prompt "\n💡 %s\n\n%s\n\n%s" "${transl[verify_user_has_domain_provide_ip_of_machine_1_of_3]/_detectedIpAddress_/$detectedIpAddress}" "${transl[verify_user_has_domain_provide_ip_of_machine_2_of_3]}" "${transl[verify_user_has_domain_provide_ip_of_machine_3_of_3]}"
while read -rp "$prompt"$'\n> ' ans; do
if confirmIpAddress "$ans"; then
serverIpAddress="$ans"
break
elif [[ "$ans" == "" ]]; then
break
fi

showPoopMessage "${transl[verify_user_has_domain_uh_oh_provide_valid_ip]}..."
done

Declare detected ip address as default server ip address serverIpAddress=${ans:="$detectedIpAddress"}

if [[ $serverIpAddress = "$ERROR_IP_ADDRESS_NOT_AVAILABLE" ]]; then
showErrorMessage "${transl[verify_user_has_domain_oops_embarassing]/_ERROR_IP_ADDRESS_NOT_AVAILABLE_/$ERROR_IP_ADDRESS_NOT_AVAILABLE}"

exitInstaller
fi

given a name and an ip address, test whether there is a record for name pointing to address if ! dig "$userDomainName" +nostats +nocomments +nocmd | tr -d '\t' | grep "A$serverIpAddress" >/dev/null 2>&1 ; then showErrorMessage "${transl[verify_user_has_domain_oops_domain_no_dns_record_correct_ip_1_of_2]/_userDomainName_/$userDomainName} ${transl[verify_user_has_domain_oops_domain_no_dns_record_correct_ip_2_of_2]/_serverIpAddress_/$serverIpAddress}"

read -r -p "${transl[common_press_enter_retry]}..."

verifyUserHasDomain
fi

Email handling (start) printf -v prompt "💡 %s\n%s\n\n%s\n%s\n\n%s" "${transl[verify_user_has_domain_provide_valid_email_1_of_5]}" "${transl[verify_user_has_domain_provide_valid_email_2_of_5]}" "${transl[verify_user_has_domain_provide_valid_email_3_of_5]}" "${transl[verify_user_has_domain_provide_valid_email_4_of_5]}" "${transl[verify_user_has_domain_provide_valid_email_5_of_5]}" while read -rp "$prompt"$'\n> ' ans; do if confirmEmailAddress "$ans"; then emailAddress=$(toLowerCase "$ans") break fi

showPoopMessage "${transl[verify_user_has_domain_uh_oh_provide_valid_email]}..."
done

printf -v prompt "\n🤖 %s\n\n%s\n%s\n%s\n\n%s\n%s" \
"${transl[verify_user_has_domain_details_confirmation_1_of_6]}" \
"${transl[verify_user_has_domain_details_confirmation_2_of_6]/_userDomainName_/$userDomainName}" \
"${transl[verify_user_has_domain_details_confirmation_3_of_6]/_serverIpAddress_/$serverIpAddress}" \
"${transl[verify_user_has_domain_details_confirmation_4_of_6]/_emailAddress_/$emailAddress}" \
"$(printf "%s" "${transl[verify_user_has_domain_details_confirmation_5_of_6]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"$(printf "%s" "${transl[verify_user_has_domain_details_confirmation_6_of_6]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")"

while read -rp "$prompt"$'\n> ' ans; do
intlAnswer=$(mapLanguageYesNoQuitToSystem "$userSelectedLanguage" "$ans")

case $intlAnswer in
[yY])
break
;;
[yY][eE][sS])
break
;;
[nN])
verifyUserHasDomain
break
;;
[nN][oO])
verifyUserHasDomain
break
;;
esac;
done;

echo "$userDomainName;$emailAddress"

exit 0
}

replaceNginxConfFileForHttp() {
echo "
proxy_cache_path /cache keys_zone=nodecache:100m levels=1:2 inactive=31536000s max_size=10g use_temp_path=off;

server {
listen 80;
listen [::]:80;
server_name $1;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location /stub_status {
stub_status;
}

proxy_redirect off;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 128k;

location / {
add_header content-type application/vnd.ipld.raw;
add_header content-type application/vnd.ipld.car;
add_header content-type application/octet-stream;
add_header cache-control public,max-age=31536000,immutable;

proxy_cache nodecache;
proxy_cache_valid 200 31536000s;
add_header X-Proxy-Cache \$upstream_cache_status;
proxy_cache_methods GET HEAD POST;
proxy_cache_key \"\$request_uri|\$request_body\";
client_max_body_size 1G;

proxy_pass http://ursa:4069;
}
}
" > "$defaultDockerFullNodeRelativePath"/data/nginx/http.conf
}

replaceNginxConfFileForHttps() {
echo "
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name $1;

server_tokens off;

SSL code ssl_certificate /etc/letsencrypt/live/$1/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/$1/privkey.pem;

include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

location /stub_status {
stub_status;
}

location / {
add_header content-type application/vnd.ipld.raw;
add_header content-type application/vnd.ipld.car;
add_header content-type application/octet-stream;
add_header cache-control public,max-age=31536000,immutable;

proxy_cache nodecache;
proxy_cache_valid 200 31536000s;
add_header X-Proxy-Cache \$upstream_cache_status;
proxy_cache_methods GET HEAD POST;
proxy_cache_key \"\$request_uri|\$request_body\";
client_max_body_size 1G;

proxy_pass http://ursa:4069;
}
}
" > "$defaultDockerFullNodeRelativePath"/data/nginx/https.conf
}

setupSSLTLS() {
echo "⚠️ ${transl[setup_ssl_tls_you_are_required]}"
echo
echo "${transl[setup_ssl_tls_visit_your_domain_name_registrar]}"
echo
# The extra white space between the 🫡 and text is intentional for spacing
echo "🫡 ${transl[setup_ssl_tls_complete_step_before_proceeding]}"
echo "${transl[setup_ssl_tls_reason_to_secure_server]}"
echo
echo "🙏 ${transl[setup_ssl_tls_learn_more_about]}"
printf "\n"

trimData=$(verifyUserHasDomain | xargs)

if [[ ! "$trimData" ]]; then
showErrorMessage "🦖 ${transl[setup_ssl_tls_failed_verify_user_domain_and_email]}..."

exit 1
fi

userDomainName=$(echo "$trimData" | cut -d ";" -f 1)
emailAddress=$(echo "$trimData" | cut -d ";" -f 2)

if ! rm "$defaultDockerFullNodeRelativePath"/data/nginx/app.conf; then
showErrorMessage "${transl[setup_ssl_tls_oops_failed_clear_nginx]} 🙏"

exitInstaller
fi

if ! replaceNginxConfFileForHttp "$userDomainName"; then
showErrorMessage "${transl[setup_ssl_tls_oops_failed_update_http_server_name]/_userDomainName_/$userDomainName} 🙏"

exitInstaller
fi

chmod +x "$defaultDockerFullNodeRelativePath"/init-letsencrypt.sh

showOkMessage "${transl[setup_ssl_tls_updated_file_permissions_lets_encrypt_execution]} (set +x)!"

Intentional, used to provide space after msg echo

start stack in bg, as lets encrypt will need the nginx to validate COMPOSE_DOCKER_CLI_BUILD=1 sudo docker-compose -f "$defaultDockerComposeYmlRelativePath" up -d

TODO: add health check in the docker compose file

Health check statusCode=$(curl --write-out "%{http_code}" --silent --output /dev/null http://"$userDomainName"/ping)

if [[ "$statusCode" -ne 200 ]]; then
showPoopMessage "${transl[setup_ssl_tls_uh_oh_cant_communicate_with_reverse_proxy]}"

read -r -p "😅 ${transl[setup_ssl_tls_lets_encrypt_likely_fail_if_continue]}..."
fi

if ! initLetsEncrypt "$emailAddress" "$userDomainName"; then
exitInstaller
fi

printf "\n"

if ! replaceNginxConfFileForHttps "$userDomainName"; then
showErrorMessage "${transl[setup_ssl_tls_oops_failed_update_https_server_name_directive]/_userDomainName_/$userDomainName} 🙏"

exitInstaller
fi
}

changeDirectoryToPathOrFailure() {
local name=$1
local targetPath=$2

if [[ -z "$name" || -z "$targetPath" ]]; then
showErrorMessage "${transl[change_directory_to_path_oops_failed_change_dir_discord]} 🙏"

exitInstaller
fi

if [[ $(pwd) != "$targetPath" ]]; then
if ! cd "$targetPath"; then
showErrorMessage "${transl[change_directory_to_path_oops_failed_change_dir_to_name]/_name_/$name} 🙏"

exitInstaller
fi
fi
}

onLatestPreference() {
echo
echo "✋ ${transl[on_latest_preference_when_running_stack_docker_build]}"
echo "💡 ${transl[on_latest_preference_our_recommendation]} 😴!"
echo
echo "${transl[on_latest_preference_inf_future_check_documentation]}"
echo

printf -v prompt "\n\n🤖 %s\n%s" \
"$(printf "%s" "${transl[on_latest_preference_prompt_happy_use_latest]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")" \
"$(printf "%s" "${transl[on_latest_preference_prompt_happy_use_latest_yes_or_no]}" | sed "s|_yes_|\"${transl[common_prompt_yes_as_y]}\"|g; s|_no_|\"${transl[common_prompt_no_as_n]}\"|g")"
while read -rp "$prompt"$'\n> ' ans; do
data=$(confirm "$userSelectedLanguage" "$ans")

[[ ! $data ]] && continue

if [[ "$data" -eq 1 ]]; then
echo
echo "🦀 ${transl[on_latest_preference_mind_ursa_time_build_long]}..."
echo

break
elif [[ "$data" -eq 0 ]]; then
yq -ie 'del(.services.ursa.build)' "$defaultDockerComposeYmlRelativePath" >/dev/null 2>&1
yq -i ".services.ursa.image = \"$defaultUrsaLatestImage\"" "$defaultDockerComposeYmlRelativePath" >/dev/null 2>&1

if ! yq '.services.ursa.image' "$defaultDockerComposeYmlRelativePath" | grep -q "$defaultUrsaLatestImage" || ! yq '.services.ursa.build' "$defaultDockerComposeYmlRelativePath" | grep -qE "null|no matches found"; then
echo
echo "💩 ${transl[on_latest_preference_uh_oh_failed_modify_docker_compose_yaml]}"

printf "%s 🙏" "${transl[on_latest_preference_uh_oh_failed_modify_docker_compose_yaml_apologies]}" | sed "s|_defaultDiscordUrl_|\"${defaultDiscordUrl}\"|g"
echo

exitInstaller
fi

break
fi
done
}

(
# stdin to keyboard
exec < /dev/tty;

SIGINT listener trap onInterruption INT

Set default locale multiLanguageChoice

Identity the OS os=$(identifyOS)

isOSSupported "$os"

Show disclaimer showDisclaimer

Had space after disclaimer printf "\r\n"

Check if system has recommended resources (disk space and memory) checkSystemHasRecommendedResources "$os"

Check if has dependencies installed for dep in "${dependencies[@]}"; do installMandatory "$dep" done

for dep in $(echo "${config}" | jq -r '.dependencies[] | @base64'); do
name=$(getJQPropertyValue "$dep" ".name")
bin=$(getJQPropertyValue "$dep" ".bin")
pkgManager=$(getJQPropertyValue "$dep" ".pkgManager")

verifyDepsOrInstall "$os" "$name" "$bin" "$pkgManager"
done

We start by verifying if git is installed, if not request to install checkIfGitInstalled "$os"

gitHealthCheck

Verify if Docker is installed, if not install it checkIfDockerInstalled "$os"

Request a pathname where to store the Ursa repository, otherwise provide a default selectedUrsaPath=$(requestPathnameForUrsaRepository)

Check if directory does not exit or empty if [[ "$(ls -A "$selectedUrsaPath" >/dev/null 2>&1)" ]]; then echo echo "😅 ${transl[on_latest_preference_have_run_install_before_surprise]}" echo echo "${transl[on_latest_preference_directory_not_empty]/_selectedUrsaPath_/$selectedUrsaPath}" echo "${transl[on_latest_preference_if_stuck_clear_location_1_of_2]}" echo "${transl[on_latest_preference_if_stuck_clear_location_2_of_2]/_defaultUrsaPath_/$defaultUrsaPath}"

exitInstaller
fi

Pull the `ursa` project repository to the preferred target directory via HTTPS cloneUrsaRepositoryToPath "$selectedUrsaPath" changeDirectoryToPathOrFailure "Ursa" "$selectedUrsaPath"

Await a few seconds to let the user read... sleep 5

Recommend the Latest build to speed up onboarding onLatestPreference

Add some space after the "latest" request printf "\r\n"

Optional, check if user would like to setup SSL/TLS setupSSLTLS

showOkMessage "${transl[on_latest_preference_success_install_completed]}"

Add some space after the "complete" message printf "\r\n" # Await a few seconds to let the user read... sleep 5

Restart docker restartDockerStack

Set installation has complet installationStatus="$statusComplete"

Add some space after the "docker stack restart" message printf "\r\n"

Show the logs showDockerStackLog resetStyles exit; ) ```

只部署节点,使用下边代码

```
sudo apt update && sudo apt upgrade -y
sudo apt install curl tar wget git jq build-essential -y
sudo apt install make clang pkg-config libssl-dev cmake gcc -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.profile
source ~/.cargo/env

cargo install sccache
sudo apt-get install protobuf-compiler
CD $HOME
git clone https://github.com/fleek-network/ursa.git
cd ursa

#这里有个坑,有些无法指定版本,下边两个执行一个即可,如果指定版本报错,执行不指定版本
#指定版本
cargo update -p libp2p-quic --precise 0.7.0-alpha
cargo update -p libp2p-webrtc --precise 0.4.0-alpha
cargo update -p libp2p-tls --precise 0.1.0-alpha
#指定版本完成

#无指定版本开始

cargo update -p libp2p-quic
cargo update -p libp2p-webrtc
cargo update -p libp2p-tls
#无指定版本完成
make install
```

创建服务

sudo tee /etc/systemd/system/fleekd.service > /dev/null <<EOF
\[Unit\]
Description=Fleek Node
After=network.target
\[Service\]
User=$USER
Type=simple
ExecStart=$(which ursa)
Restart=on-failure
LimitNOFILE=65535
\[Install\]
WantedBy=multi-user.target
EOF

重新启动系统服务并打开

sudo systemctl daemon-reload
sudo systemctl enable fleekd
sudo systemctl restart fleekd

设置个节点
IDENTITY="Danny"
systemctl stop fleekd
sed -i.bak -e "s/^identity \=.\/identity = \\"${IDENTITY}\\"/" $HOME/.ursa/config.toml
rm $HOME/.ursa/keystore/default.pem
sudo systemctl restart fleekd

保存好自己的私钥
cat ~/.ursa/keystore/\*.pem

通过文本编辑器上传或打开文件并将其保存在安全的地方

![](https://miro.medium.com/v2/resize:fit:297/0*qnJhgRs2XvLsyfS8.png)

检查日志

sudo journalctl -u fleekd -f -o cat

很多时候会出现WARN,正如开发者所说,这是正常的

![](https://miro.medium.com/v2/resize:fit:700/0*S-ScNu6e7bu9woB2.png)

节点更新,部署不需要执行

如果有更新

systemctl stop fleekd

cd ursa

git pull origin main

make install

systemctl restart fleekd

sudo journalctl -u fleekd -f -o cat

\[/crypto-block\]

https://youtu.be/1KqIw2T4qUU

建立一个DApp并将其托管在IPFS上

实践步骤。需要做以下工作:

- 安装React,
- 安装Hardhat,
- 为Alchemy区块链开发者平台创建一个免费账户
- 为Fleek创建一个免费账户,
- MetaMask浏览器扩展

MetaMask是一个加密货币钱包,允许用户通过浏览器或移动应用程序访问DApps。你还会想要一个以太坊测试网的MetaMask测试账户,用于测试智能合约。

构建一个DApp样本以部署到Fleek上

比如创建一家宠物店并建立一个去中心化的收养跟踪系统。

编写智能合约和部署DApp,建立了UI组件和状态。智能合约和React代码将被包含在一个项目中。

GitHub仓库克隆React应用程序,即可开始编写智能合约:

``
git clone https://github.com/vickywane/react-web3
``

接下来,将目录改为克隆的文件夹,并安装package.json 文件中列出的依赖项:

```
cd react-web3

npm install
```

随着React应用程序的建立,让我们继续创建宠物收养智能合约。

创建宠物领养智能合约

react-web3 目录中,创建一个合约文件夹,以存储我们的宠物收养智能合约的 Solidity 代码。

使用您的代码编辑器,创建一个名为Adoption.sol 的文件,并粘贴以下代码,以在智能合约中创建必要的变量和函数,包括:

- 一个16长度的数组来存储每个宠物收养者的地址
- 一个用于收养宠物的函数
- 一个检索所有被收养宠物的地址的函数

```
//SPDX-License-Identifier: Unlicense
// ./react-web3/contracts/Adoption.sol
pragma solidity ^0.8.0;

contract Adoption {
address[16] public adopters;
event PetAssigned(address indexed petOwner, uint32 petId);

// adopting a pet
function adopt(uint32 petId) public {
require(petId >= 0 && petId <= 15, "Pet does not exist");

adopters[petId] = msg.sender;
emit PetAssigned(msg.sender, petId);
}

// Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
}
```

接下来,在合约文件夹中创建另一个名为deploy-contract-script.js 的文件。将下面的JavaScript代码粘贴到该文件中。该代码将作为一个脚本,使用Hardhat中的异步getContractFactory 方法来创建一个收养智能合约的工厂实例,然后将其部署:

```
// react-web3/contract/deploy-contract-script.js
require('dotenv').config()
const { ethers } = require("hardhat");

async function main() {
// We get the contract to deploy
const Adoption = await ethers.getContractFactory("Adoption");
const adoption = await Adoption.deploy();
await adoption.deployed();

console.log("Adoption Contract deployed to:", adoption.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
```

最后,创建一个名为hardhat.config.js 的文件。这个文件将指定Hardhat的配置。

hardhat.config.js 文件中添加以下 JavaScript 代码,以指定 Solidity 版本和你的 Ropsten 网络账户的 URL 端点。

```
require("@nomiclabs/hardhat-waffle");
require('dotenv').config();

/*
@type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.4",
networks: {
ropsten: {
url: process.env.ALCHEMY_API_URL,
accounts: [0x${process.env.METAMASK_PRIVATE_KEY}]
}
}
};
```

使用环境变量ALCHEMY_API_URLMETAMASK_PRIVATE_KEY 来存储用于Ropsten网络配置的URL和私人账户密钥值:

- METAMASK_PRIVATE_KEY 与您的MetaMask钱包相关联。
- ALCHEMY_API_URL 链接到一个Alchemy应用程序。

你可以使用.env 文件和dotenv 包在这个react-web3 项目中存储和访问这些环境变量。我们将在下一节回顾如何做到这一点。

如果你一直成功地跟随,你在项目的这个阶段还没有创建一个Alchemy应用程序。你将需要使用其API URL端点,所以让我们继续在Alchemy上创建一个应用程序。

创建一个Alchemy应用程序

Alchemy提供的功能使您能够连接到一个网络的外部远程过程调用(RPC)节点。RPC节点使您的DApp和区块链的通信成为可能。

使用您的网络浏览器,导航到Alchemy网络仪表板并创建一个新的应用程序。

为该应用程序提供一个名称和描述,然后选择Ropsten网络。点击 "创建应用程序 "按钮,继续。

![Alchemy Web Dashboard Showing New Alchemy App Being Created](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7d7f068b419b469daaa6cf405783ddf8~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)

应用程序创建后,你会发现它列在页面底部。

点击 "查看密钥",显示Alchemy应用程序的API密钥。请注意HTTP URL。我在下面的图片中编辑了这个信息。

![Alchemy Dashboard Showing Popup Box Containing App API Key And Other Info](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/95028598526243aeac410cd1640c9a7e~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)

在你的Hardhat项目中创建一个.env 文件,如下图所示。你将使用这个.env 文件来存储你的Alchemy应用程序的URL和MetaMask私钥。

```
// react-web3/.env

ALCHEMY_API_URL=<ALCHEMY_HTTP_URL>
METAMASK_PRIVATE_KEY=<METAMASK_PRIVATE_KEY>
```

用Alchemy的HTTP URL和你的MetaMask私钥替换上面的ALCHEMY_HTTP_URLMETAMASK_PRIVATE_KEY 占位符。按照MetaMask导出私钥指南,了解如何为你的钱包导出这些信息。

最后,执行下一个命令,将你的宠物收养智能合约部署到指定的Ropsten网络。

``
npx hardhat run contracts/deploy-contract-script.js --network ropsten
``

如下图所示,注意合同部署后返回到你的控制台的地址。你将在下一节中需要这个地址

在这一点上,宠物领养智能合约已经部署完毕。现在让我们把注意力转移到DApp本身,并创建与宠物收养智能合约互动的功能。

构建DApp前台

与Truffle指南中的宠物店教程类似,我们的DApp将显示十六个不同品种的狗,可以被收养。每只狗的详细信息都存储在 [src/pets.json](https://github.com/vickywane/react-web3/tree/master/src) 文件中。我们正在使用TailwindCSS来设计这个DApp。

要开始,请打开 [state/context.js](https://github.com/vickywane/react-web3/tree/master/src/state) 文件,用下面的代码替换现有内容:

```
// react-web3/state/context.js

import React, {useEffect, useReducer} from "react";
import Web3 from "web3";
import {ethers, providers} from "ethers";

const {abi} = require('../../artifacts/contracts/Adoption.sol/Adoption.json')

if (!abi) {
throw new Error("Adoptiom.json ABI file missing. Run npx hardhat run contracts/deploy-contract-script.js")
}

export const initialState = {
isModalOpen: false,
dispatch: () => {
},
showToast: false,
adoptPet: (id) => {
},
retrieveAdopters: (id) => {
},
};

const {ethereum, web3} = window

const AppContext = React.createContext(initialState);
export default AppContext;

const reducer = (state, action) => {
switch (action.type) {
case 'INITIATE_WEB3':
return {
...state,
isModalOpen: action.payload,
}
case 'SENT_TOAST':
return {
...state,
showToast: action.payload.toastVisibility
}
default:
return state;
}
};

const createEthContractInstance = () => {
try {
const provider = new providers.Web3Provider(ethereum)
const signer = provider.getSigner()
const contractAddress = process.env.REACT_APP_ADOPTION_CONTRACT_ADDRESS

return new ethers.Contract(contractAddress, abi, signer)
} catch (e) {
console.log('Unable to create ethereum contract. Error:', e)
}
}

export const AppProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);

const instantiateWeb3 = async _ => {
if (ethereum) {
try {
// Request account access
return await ethereum.request({method: "eth_requestAccounts"})
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
} else if (web3) {
return
}
return new Web3(Web3.givenProvider || "ws://localhost:8545")
}

const adoptPet = async id => {
try {
const instance = createEthContractInstance()
const accountData = await instantiateWeb3()

await instance.adopt(id, {from: accountData[0]})

dispatch({
type: 'SENT_TOAST', payload: {
toastVisibility: true
}
})

// close success toast after 3s
setTimeout(() => {
dispatch({
type: 'SENT_TOAST', payload: {
toastVisibility: false
}
})
}, 3000)
} catch (e) {
console.log("ERROR:", e)
}
}

const retrieveAdopters = async _ => {
try {
const instance = createEthContractInstance()
return await instance.getAdopters()
} catch (e) {
console.log("RETRIEVING:", e)
}
}

useEffect(() => {
(async () => { await instantiateWeb3() })()
})

return (
<AppContext.Provider
value={{
...state,
dispatch,
adoptPet,
retrieveAdopters
}}
>
{children}
</AppContext.Provider>
);
};
```

以太坊和Web3对象从浏览器窗口被解构。MetaMask扩展将Ethereum对象注入到浏览器中。

createEthContractInstance 辅助函数使用合同的ABI和Alchemy的地址创建并返回一个宠物收养合同的实例。

instantiateWeb3 辅助函数将在一个数组中检索并返回用户的账户地址,使用MetaMask来验证以太坊窗口对象是否被定义。

instantiateWeb3 辅助函数也在useEffect 钩子中执行,以确保用户在网络浏览器中打开应用程序后立即与MetaMask连接。

adoptPet 函数期望一个数字petId 参数,创建收养合同实例,并使用createEthContractInstanceinstantiateWeb3 辅助函数检索用户的地址。

petId 参数和用户账户地址从宠物收养合同实例传入adopt 方法,以收养宠物。

retrieveAdopters 函数在收养实例上执行getAdopters 方法,以检索所有收养的宠物的地址。

保存这些修改,启动React开发服务器,在http://localhost:4040/,查看宠物店DApp

在这一点上,收养合同内的功能已经在state/context.js 文件中实现,但还没有执行。如果不通过MetaMask认证,用户的账户地址将是未定义的,所有收养宠物的按钮将被禁用,如下图所示。

让我们继续添加宠物领养合同地址作为环境变量,在Fleek上托管DApp。

将React DApp部署到Fleek上

在Fleek上托管DApp可以通过Fleek仪表盘、Fleek CLI,甚至是使用Fleek GitHub Actions的编程方式完成。在本节中,你将学习如何使用Fleek CLI,因为我们通过Fleek在IPFS上托管宠物店DApp。

配置Fleek CLI

执行下面的命令,在你的电脑上全局安装Fleek CLI:

``
npm install -g @fleek/cli
``

要使用已安装的Fleek CLI,你需要有一个Fleek账户的API密钥作为环境变量存储在你的终端上。让我们继续使用Fleek网页仪表板为你的账户生成一个API密钥。

使用你的网络浏览器,导航到你的Fleek账户仪表板,点击你的账户头像,显示一个弹出菜单。在这个菜单中,点击 "设置 "来导航到你的Fleek账户设置。

在你的Fleek账户设置中,点击 "生成API "按钮,启动 "API详情 "模式,这将为你的Fleek账户生成一个API密钥。

拿着生成的API密钥,替换下面命令中的FLEEK_API_KEY 占位符。

``
export FLEEK_API_KEY='FLEEK_API_KEY'
``

执行这个命令可以将Fleek API密钥导出为你电脑终端的一个临时环境变量。当你对你的Fleek账户执行命令时,Fleek CLI将读取FLEEK_API_KEY 这个变量的值。

通过Fleek CLI初始化一个网站

在你使用Fleek在IPFS上托管DApp及其文件之前,你需要在本地生成React DApp的静态文件。

在构建过程中,可以通过指定Docker镜像和用于构建静态文件的命令来自动生成静态文件。然而,在本教程中,你将手动生成静态文件。

执行下面的npm命令,在build 目录中为DApp生成静态文件。

``
npm run build
``

接下来,使用以下命令在react-web3 文件夹中初始化一个Fleek站点工作区。

``
fleek site:init
``

初始化过程对每个Fleek站点来说都是一次性的步骤。Fleek CLI将启动一个交互式会话,指导你完成初始化网站的过程。

在初始化过程中,你会被提示输入一个teamId

![Fleek App Initialization Process Showing TeamID Input Prompt](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/763189e6d26941db9bdb3338f06f2e3d~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)

你可以在Fleek仪表板的URL中找到你的teamId ,作为数字。一个例子teamId

在这一点上,Fleek CLI已经在react-web3 目录下生成了一个.fleek.json 文件,其中有Fleek的主机配置。然而,还缺少一件事:包含宠物收养智能合约地址的环境变量。

让我们继续看看如何为Fleek上的本地部署站点添加环境变量。

添加环境变量

Fleek使开发者能够通过Fleek仪表板或配置文件安全地管理其网站的敏感凭证。由于你在本教程中是通过命令行本地托管网站,你将在.fleek.json 文件中指定你的环境变量。

在下面的代码中,将ADOPTION_CONTRACT_ADDRESS 占位符替换为宠物收养智能合约地址。记住,这个地址是我们在本教程前面使用npx命令创建Alchemy应用程序并部署智能合约后返回的。

``
{
"site": {
"id": "SITE_ID",
"team": "TEAM_ID",
"platform": "ipfs",
"source": "ipfs",
"name": "SITE_NAME"
},
"build": {
"baseDir": "",
"publicDir": "build",
"rootDir": "",
"environment": {
"REACT_APP_ADOPTION_CONTRACT": "ADOPTION_CONTRACT_ADDRESS"
}
}
}
``

注意:当你初始化Fleek网站时,Fleek CLI会在.fleek.json 文件中自动生成SITE_IDTEAM_IDSITE_NAME 占位符。

上面的代码还包含以下对象,你应该把它添加到你的.fleek.json 文件中的构建对象中:

``
"environment": {
"REACT_APP_ADOPTION_CONTRACT": "ADOPTION_CONTRACT_ADDRESS"
}
``

上面的JSON对象指定了Fleek在构建DApp时使用的一个node docker镜像。在构建过程中,command 字段中的npm命令将被执行。

执行下面的命令,使用.fleek.json 文件中的新构建配置重新部署DApp。

``
fleek site:deploy
``

恭喜你!DApp已经完全部署完毕。DApp已经完全部署完毕,你现在可以通过你的网络浏览器访问实时网站。你也可以通过Fleek仪表盘按照以下步骤获得关于托管DApp的更多详细信息。

- 导航到你的Fleek仪表盘
- 点击你部署的DApp的名称
- 在左边看到部署的网站URL
- 在右边看到一个部署预览图

![Fleek Dashboard Showing Hosted DApp Details With Arrows Pointing To Deployed Site URL, Deployment Location, And Deploy Preview Image](https://blog.logrocket.com/uploads/2022/06/image10-DApp-more-details-Fleek-dashboard.png)

点击网站URL,在新的浏览器标签中打开DApp。DApp启动后,会立即提示您连接一个MetaMask钱包。钱包连接后,你将能够通过点击 "收养 "按钮,收养16只狗中的任何一只。

![Finished DApp Frontend With Connected MetaMask Wallet And Active Adopt Buttons](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b197dce0f4f84b38897a9626f47d1c34~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)

这就是了!你的宠物领养DApp样本已经被部署到Fleek。

总结

在本教程中,我们重点介绍了通过Fleek在IPFS上构建和托管一个示例DApp。这个过程的开始与Truffle指南中的宠物收养智能合约类似。然后,你通过构建一个DApp来与宠物收养智能合约进行互动,从而更进一步。

如果你想利用本教程中的步骤来托管一个可生产的DApp,我强烈建议你考虑以下几点。

首先,确保将Fleek连接到代码主机提供商,如GitHub,并从其存储库中的生产分支部署DApp。这将允许Fleek在你向部署的分支推送新的代码提交时自动重新部署DApp。

第二,如果你使用.fleek.json 文件来存储环境变量,在你的.gitignore 文件中包括.fleek.json 的文件名。这样做将确保.fleek.json 文件不会被推送,你的环境变量也不会被暴露。

如需交互式阅读、购买或评论,请打开站内完整版页面。