使用官方一键脚本搭建
域名注册,自行注册,最便宜的仅需几块钱一年
域名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
通过文本编辑器上传或打开文件并将其保存在安全的地方
检查日志
sudo journalctl -u fleekd -f -o cat
很多时候会出现WARN,正如开发者所说,这是正常的
节点更新,部署不需要执行
如果有更新
systemctl stop fleekd
cd ursa
git pull origin main
make install
systemctl restart fleekd
sudo journalctl -u fleekd -f -o cat
[/crypto-block]
建立一个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_URL
和METAMASK_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应用程序的API密钥。请注意HTTP URL。我在下面的图片中编辑了这个信息。
在你的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_URL
和METAMASK_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
参数,创建收养合同实例,并使用createEthContractInstance
和instantiateWeb3
辅助函数检索用户的地址。
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仪表板的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_ID
、TEAM_ID
和SITE_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
- 在右边看到一个部署预览图
点击网站URL,在新的浏览器标签中打开DApp。DApp启动后,会立即提示您连接一个MetaMask钱包。钱包连接后,你将能够通过点击 “收养 “按钮,收养16只狗中的任何一只。
这就是了!你的宠物领养DApp样本已经被部署到Fleek。
总结
在本教程中,我们重点介绍了通过Fleek在IPFS上构建和托管一个示例DApp。这个过程的开始与Truffle指南中的宠物收养智能合约类似。然后,你通过构建一个DApp来与宠物收养智能合约进行互动,从而更进一步。
如果你想利用本教程中的步骤来托管一个可生产的DApp,我强烈建议你考虑以下几点。
首先,确保将Fleek连接到代码主机提供商,如GitHub,并从其存储库中的生产分支部署DApp。这将允许Fleek在你向部署的分支推送新的代码提交时自动重新部署DApp。
第二,如果你使用.fleek.json
文件来存储环境变量,在你的.gitignore
文件中包括.fleek.json
的文件名。这样做将确保.fleek.json
文件不会被推送,你的环境变量也不会被暴露。