diff --git a/scripts/config.sh b/scripts/config.sh index 7338ea4..c713184 100644 --- a/scripts/config.sh +++ b/scripts/config.sh @@ -1,128 +1,188 @@ #!/bin/bash - +# +# CESS mineradm configuration management script +# This script handles showing, and generating configuration files +# for all CESS services, including miners, chain nodes, and watchdog. + +# --- Strict Mode --- +set -o errexit +set -o nounset +set -o pipefail + +# --- Source Dependencies --- +# shellcheck source=scripts/utils.sh source /opt/cess/mineradm/scripts/utils.sh -mode=$(yq eval ".node.mode" $config_path) -if [ x"$mode" != x"multiminer" ]; then - log_info "The mode in $config_path is invalid, set value to: multiminer" - yq -i eval ".node.mode=\"multiminer\"" $config_path - mode=$(yq eval ".node.mode" $config_path) -fi +# --- Help Functions --- config_help() { cat </dev/null || true; rm -f "$cidfile"' EXIT + + docker run --cidfile "$cidfile" \ + -v "$base_dir/etc:/opt/app/etc" \ + -v "$build_dir/.tmp:/opt/app/.tmp" \ + -v "$config_path:/opt/app/config.yaml" \ + "$cg_image" + + log_success "Config generator finished." +} - if [ "$res" -ne "0" ]; then - log_err "Failed to generate configurations, please check your config file and try again." - exit 1 +# Deploys the generated configuration files to their final destinations. +deploy_generated_configs() { + log_info "Deploying generated configurations..." + local base_data_path="/opt/cess/data/$mode" + + # Move base configs from .tmp to build dir + cp -r "$build_dir/.tmp/"* "$build_dir/" + rm -rf "$build_dir/.tmp" + + # Deploy miner configs + mkdir -p "$base_data_path/miners/" + cp "$build_dir/miners/"* "$base_data_path/miners/" + + # Deploy chain configs if not external + if [ -d "$build_dir/chain" ]; then + mkdir -p "$base_data_path/chain/" + cp "$build_dir/chain/"* "$base_data_path/chain/" fi - mk_sminer_workdir - - cp -r $build_dir/.tmp/* $build_dir/ + chown -R root:root "$build_dir" + split_miners_config # Generate individual miner configs - rm -rf $build_dir/.tmp - local base_mode_path=/opt/cess/data/$mode - - if [ ! -d $base_mode_path/miners/ ]; then - log_info "mkdir : $base_mode_path/miners/" - mkdir -p $base_mode_path/miners/ + # Deploy watchdog config if enabled + if [ "$(yq eval ".watchdog.enable" "$config_path")" == "true" ] && [ -d "$build_dir/watchdog" ]; then + mkdir -p "$base_data_path/watchdog/" + cp "$build_dir/watchdog/"* "$base_data_path/watchdog/" + log_success "Watchdog configuration deployed." fi - cp $build_dir/miners/* $base_mode_path/miners/ - if [ ! -d $base_mode_path/chain/ ]; then - log_info "mkdir : $base_mode_path/chain/" - mkdir -p $base_mode_path/chain/ + # Deploy cacher config if enabled + if [ "$(yq eval '.cacher.enable' "$config_path")" == "true" ] && [ -d "$build_dir/cacher" ]; then + local cacher_workspace + cacher_workspace=$(yq eval '.cacher.WorkSpace' "$config_path") + cp "$build_dir/cacher/"* "$cacher_workspace/" + log_success "Cacher configuration deployed to $cacher_workspace" fi - cp $build_dir/chain/* $base_mode_path/chain/ - - chown -R root:root $build_dir - - split_miners_config # generate miners config + + log_success "All configurations deployed." +} - local enableWatchdogService=$(yq eval ".watchdog.enable" $config_path) # generate watchdog config or not - if [[ $enableWatchdogService == "true" ]]; then - if [ ! -d $base_mode_path/watchdog/ ]; then - log_info "mkdir : $base_mode_path/watchdog/" - mkdir -p $base_mode_path/watchdog/ - fi - cp $build_dir/watchdog/* $base_mode_path/watchdog/ - log_success "watchdog configuration generated at: $build_dir/watchdog/config.yaml" - fi +# Patches the generated docker-compose.yaml for compatibility. +patch_compose_file() { + log_info "Patching docker-compose.yaml..." + # This sed command removes extra single quotes from the 'test' command array, + # which can cause issues with some Docker versions. + # e.g., '["CMD", "nc", ...]' -> ["CMD", "nc", ...] + sed -i "s/'\([\"CMD\".*\)'/\1/" "$compose_yaml" + log_success "docker-compose.yaml patched." +} - # change '["CMD", "nc", "-zv", "127.0.0.1", "15001"]' to ["CMD", "nc", "-zv", "127.0.0.1", "15001"] in docker-compose.yaml - yq eval '.' $build_dir/docker-compose.yaml | grep -n "test: " | awk '{print $1}' | cut -d':' -f1 | xargs -I {} sed -i "{}s/'//;{}s/\(.*\)'/\1/" $build_dir/docker-compose.yaml +# Main function to generate all configuration files. +config_generate() { + log_info "--- Starting Configuration Generation ---" + + validate_pre_generation_state + prepare_build_dir + run_config_generator + deploy_generated_configs + patch_compose_file + + log_success "Configuration generation complete. Docker Compose file is at: $compose_yaml" +} +# --- Main Execution --- - local enableCacher=$(yq eval '.cacher.enable' "$config_path") - local cacher_work_path=$(yq eval '.cacher.WorkSpace' "$config_path") - if [[ $enableCacher == "true" ]]; then - # copy $build_dir/cacher/config.yaml to cacher WorkSpace - cp $build_dir/cacher/* "$(yq eval '.cacher.WorkSpace' "$config_path")" - log_success "cacher configuration generated at: $cacher_work_path/config.yaml" +# Main router for the 'config' command. +config() { + # Set default mode if not valid + local mode + mode=$(yq eval ".node.mode" "$config_path") + if [ "$mode" != "multiminer" ]; then + log_info "The mode in $config_path is invalid, setting value to: multiminer" + yq -i eval '.node.mode="multiminer"' "$config_path" fi - log_success "docker-compose.yaml generated at: $compose_yaml" -} - -config() { - case "$1" in + case "${1:-help}" in -s | show) config_show - ;; - -g | generate) - shift + ;;n -g | generate) config_generate ;; - *) + -h | help | *) config_help ;; esac } + +# If run directly, execute the main function. +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # Load profile from config before running. + load_profile + config "$@" +fi \ No newline at end of file diff --git a/scripts/miner.sh b/scripts/miner.sh index 029007c..7d7d464 100644 --- a/scripts/miner.sh +++ b/scripts/miner.sh @@ -1,640 +1,289 @@ #!/bin/bash - +# +# CESS mineradm main script +# This script is the main entry point for managing CESS miners and related services. +# It handles installation, service lifecycle (start, stop, restart), and miner-specific operations. + +# --- Strict Mode --- +set -o errexit +set -o nounset +set -o pipefail + +# --- Source Dependencies --- +# It's assumed these scripts are in /opt/cess/mineradm/scripts/ on the target system. +# shellcheck source=scripts/utils.sh source /opt/cess/mineradm/scripts/utils.sh +# shellcheck source=scripts/version.sh source /opt/cess/mineradm/scripts/version.sh +# shellcheck source=scripts/config.sh source /opt/cess/mineradm/scripts/config.sh +# shellcheck source=scripts/tools.sh source /opt/cess/mineradm/scripts/tools.sh -########################################base################################################ +# --- Global Variables --- +skip_chain="false" + +# --- Help Functions --- + +help() { + cat < Manage storage miners (see 'mineradm miners help'). + cacher Manage cacher services (see 'mineradm cacher help'). + config Manage configuration files (see 'mineradm config help'). + tools Use utility tools (see 'mineradm tools help'). + + profile [name] View or set the active network profile (devnet, testnet, etc.). + version Show version information. + help Show this help message. +EOF +} + +miner_ops_help() { + cat < [miner] Increase stake for one or all miners. + increase space [miner] Increase declared space for one or all miners (in TiB). + exit [miner] Exit one or all miners from the network. + withdraw [miner] Withdraw stake for one or all miners. + stat Get on-chain statistics for all miners. + reward Query reward information for all miners. + claim [miner] Claim rewards for one or all miners. + update account
[miner] Update the earnings account for one or all miners. +EOF +} + +cacher_ops_help() { + cat </dev/null; then + yq eval 'del(.services.chain)' -i "$compose_yaml" + log_info "Chain service removed from compose file for this run." fi else - local services=$(yq eval '.services | keys | join(" ")' $compose_yaml) + services=$(yq eval '.services | keys | join(" ")' "$compose_yaml") fi - docker compose -f $compose_yaml up -d $services - if [ "$(yq eval ".watchdog.enable" $config_path)" == "true" ]; then - if [ "$(yq eval ".services.watchdog-web" $compose_yaml)" ]; then - if [ "$(yq eval ".services.watchdog-web.environment" $compose_yaml)" ]; then - log_info "Storage monitor run at: http://localhost:13080" - fi - fi + log_info "Starting services: $services" + docker compose -f "$compose_yaml" up -d $services + + if [ "$(yq eval ".watchdog.enable" "$config_path")" == "true" ]; then + log_info "Storage monitor dashboard (if enabled): http://localhost:13080" fi - - return $? + log_success "Installation complete." } stop() { - if [ ! -f "$compose_yaml" ]; then - log_err "docker-compose.yaml not found in /opt/cess/mineradm/build" - exit 1 - fi - if [ x"$1" = x"" ]; then - log_info "Stop all services" - docker compose -f $compose_yaml stop - return $? - fi - - log_info "Stop service: $*" - docker compose -f $compose_yaml stop "$@" - return $? + if [ ! -f "$compose_yaml" ]; then log_err "Compose file not found. Run 'mineradm config generate' first."; exit 1; fi + log_info "Stopping services: ${*:-all}" + docker compose -f "$compose_yaml" stop "$@" + log_success "Services stopped." } restart() { - if [ ! -f "$compose_yaml" ]; then - log_err "docker-compose.yaml is not found in /opt/cess/mineradm/build" - exit 1 - fi - - if [ x"$1" = x"" ]; then - log_info "Restart all services" - if docker compose -f $compose_yaml down; then - docker compose -f $compose_yaml up -d - fi - return $? + if [ ! -f "$compose_yaml" ]; then log_err "Compose file not found. Run 'mineradm config generate' first."; exit 1; fi + log_info "Restarting services: ${*:-all}" + if [ $# -eq 0 ]; then + docker compose -f "$compose_yaml" down + docker compose -f "$compose_yaml" up -d + else + docker compose -f "$compose_yaml" restart "$@" fi - - log_info "Restart service: $*" - docker compose -f $compose_yaml restart "$@" - return $? + log_success "Services restarted." } down() { - if [ ! -f "$compose_yaml" ]; then - log_err "docker-compose.yaml not found in /opt/cess/mineradm/build" - exit 1 - fi - - if [ x"$1" = x"" ]; then - log_info "Remove all services" - docker compose -f $compose_yaml down -v - return $? - fi - - log_info "Remove service: $*" - docker compose -f $compose_yaml down "$@" - return $? + if [ ! -f "$compose_yaml" ]; then log_err "Compose file not found. Run 'mineradm config generate' first."; exit 1; fi + log_info "Taking down services: ${*:-all}" + docker compose -f "$compose_yaml" down -v "$@" + log_success "Services taken down." } pullimg() { - docker pull cesslab/config-gen:$profile + log_info "Pulling latest Docker images for profile: $profile" + docker pull "cesslab/config-gen:$profile" if [ -f "$compose_yaml" ]; then - docker compose -f $compose_yaml pull + docker compose -f "$compose_yaml" pull fi + log_success "Image pull complete." } status() { - docker ps -a --filter "label=com.docker.compose.project=cess-${mode}" --format 'table {{.Names}}\t{{.Status}}' + docker ps -a --filter "label=com.docker.compose.project=cess-${mode}" --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' } purge() { - log_info "WARNING: this operation can remove all your data in /opt/cess/config/$mode/* and can't revert." - log_info "WARNING: this directory contains block data, rpc node will re-sync with block number 1 if you purge this data" - printf "Press \033[0;33mY\033[0m if you really want to do: " - local y="" - read y - if [ x"$y" != x"Y" ]; then - echo "purge operate cancel" - return 1 + log_info "WARNING: This will permanently remove all chain data in /opt/cess/config/$mode/" + log_info " The RPC node will have to re-sync from scratch." + printf "Press \033[0;33mY\033[0m to confirm: " + local confirm + read -r confirm + if [[ "$confirm" != "Y" && "$confirm" != "y" ]]; then + log_info "Purge cancelled." + return fi - purge_data - return $? -} - -purge_data() { + + log_info "Stopping chain service..." stop chain - if rm -rf /opt/cess/config/$mode/*; then - log_success "purge data successfully" - else - log_err "Can not remove data in: /opt/cess/config/$mode/" - fi -} - -miner_ops() { - if [ ! -f "$compose_yaml" ]; then - log_err "docker-compose.yaml not found in /opt/cess/mineradm/build" - return 1 - fi - - if ! docker compose -f $compose_yaml config >/dev/null; then - log_err "docker-compose.yaml is not valid !" - exit 1 - fi - - local miner_names=$(yq eval '.services | keys | map(select(. == "miner*" )) | join(" ")' $compose_yaml) - local volumes=$(yq eval '.services | to_entries | map(select(.key | test("^miner.*"))) | from_entries | .[] | .volumes' $compose_yaml | xargs | sed "s/['\"]//g" | sed "s/- /-v /g" | xargs -n 4 echo) - readarray -t volumes_array <<<"$volumes" # read array split with /n - read -a names_array <<<"$miner_names" # read array split with " " - local miner_image="cesslab/cess-miner:$profile" - local -r cfg_arg="-c /opt/miner/config.yaml" # read only - - case "$1" in - increase) - # sudo mineradm miners increase staking $miner_name $token_amount - if [ $# -eq 4 ] && [ $2 == "staking" ]; then - # check miner name is correct or not - is_match_regex "miner" $3 - # $token_amount must be a number - is_num $4 - local cmd=$(gen_miner_cmd $3 $miner_image) - if ! local res=$($cmd $1 $2 $4 $cfg_arg); then - log_err "$3: Increase Stake Failed" - exit 1 - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_err "Please make sure that the miner has enough TCESS in signatureAcc and the signatureAcc is the same as stakingAcc" - log_err "$3: Increase Stake Failed" - exit 1 - else - log_success "$3: $4 TCESS has been increased successfully" - exit 0 - fi - fi - # sudo mineradm miners increase staking $token_amount - elif [ $# -eq 3 ] && [ $2 == "staking" ]; then - is_num $3 - log_info "WARNING: This operation will increase all of the miners stake and cannot be reverted" - printf "Press \033[0;33mY\033[0m to continue: " - local y="" - read y - if [ x"$y" != x"Y" ]; then - exit 1 - fi - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! local res=$($cmd $1 $2 $3 $cfg_arg); then - log_err "${names_array[$i]}: Increase Stake Failed" - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_info "Please make sure that the miners have enough TCESS in signatureAcc and each signatureAcc is the same as its stakingAcc" - log_err "${names_array[$i]}: Increase Stake Failed" - else - log_success "${names_array[$i]}: $3 TCESS has been increased successfully" - fi - fi - echo - done - # sudo mineradm miners increase space $miner_name $space_amount(TB) - elif [ $# -eq 4 ] && [ $2 == "space" ]; then - # check miner name is correct or not - is_match_regex "miner" $3 - # $token_amount must be a number - is_num $4 - local cmd=$(gen_miner_cmd $3 $miner_image) - if ! local res=$($cmd $1 $2 $4 $cfg_arg); then - log_err "$3: Increase Declaration Space Failed" - log_err "Network exception or insufficient balance in stakingAcc" - exit 1 - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_err "Please make sure that miner:$3 has enough TCESS in stakingAcc" - log_err "$3: Increase Declaration Space Failed" - exit 1 - else - log_success "$3: Increase Declaration Space to $4 TiB Successfully" - exit 0 - fi - fi - # sudo mineradm miners increase space $space_amount (TB) - elif [ $# -eq 3 ] && [ $2 == "space" ]; then - is_num $3 - log_info "WARNING: This operation will increase the declaration space of all miners on the chain by $3 TiB and cannot be reverted" - printf "Press \033[0;33mY\033[0m to continue: " - local y="" - read y - if [ x"$y" != x"Y" ]; then - exit 1 - fi - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! local res=$($cmd $1 $2 $3 $cfg_arg); then - log_err "${names_array[$i]}: Increase Declaration Space Operation Failed" - log_err "Network exception or insufficient balance in stakingAcc" - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_err "Please make sure that the miner:${names_array[$i]} have enough TCESS in stakingAcc" - log_err "${names_array[$i]}: Increase Declaration Space Operation Failed" - else - log_success "${names_array[$i]}: Increase Declaration Space to $3 TiB Operation Success" - fi - fi - echo - done - else - log_err "Parameters Error" - miner_ops_help - exit 1 - fi - ;; - exit) - # sudo mineradm miners exit $miner_name - if [ $# -eq 2 ]; then - is_match_regex "miner" $2 - local cmd=$(gen_miner_cmd $2 $miner_image) - if ! local res=$($cmd $1 $cfg_arg); then - log_err "$2: Exit Operation Failed" - exit 1 - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_err "Stake less than 180 days or network exception" - log_err "$2: Exit Operation Failed" - exit 1 - else - log_success "$2: Exit Operation Success" - exit 0 - fi - fi - # sudo mineradm miners exit - elif [ $# -eq 1 ]; then - log_info "WARNING: This operation will make all of the miners exit from cess network and cannot be reverted" - log_info "Please make sure that the miner have staked for more than 180 days" - printf "Press \033[0;33mY\033[0m to continue: " - local y="" - read y - if [ x"$y" != x"Y" ]; then - exit 1 - fi - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! local res=$($cmd $1 $cfg_arg); then - log_err "${names_array[$i]}: Exit Operation Failed" - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_info "Stake less than 180 days or network exception" - log_err "${names_array[$i]}: Exit Operation Failed" - else - log_success "${names_array[$i]}: Exit Operation Success" - fi - fi - echo - done - else - log_err "Parameters Error" - miner_ops_help - exit 1 - fi - ;; - withdraw) - # sudo mineradm miners withdraw $miner_name - if [ $# -eq 2 ]; then - is_match_regex "miner" $2 - local cmd=$(gen_miner_cmd $2 $miner_image) - if ! local res=$($cmd $1 $cfg_arg); then - log_err "$2: Withdraw Operation Failed" - exit 1 - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_info "Please make sure that the miner has been staking for more than 180 days and the miner has already exited the cess network" - log_err "$2: Withdraw Operation Failed" - exit 1 - else - log_success "$2: Withdraw Operation Success" - exit 0 - fi - fi - # sudo mineradm miners withdraw - elif [ $# -eq 1 ]; then - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! local res=$($cmd $1 $cfg_arg); then - log_err "${names_array[$i]}: Withdraw Operation Failed" - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_info "Please make sure that the miners have been staking for more than 180 days and the miners have already exited the cess network" - log_err "${names_array[$i]}: Withdraw Operation Failed" - else - log_success "${names_array[$i]}: Withdraw Operation Success" - fi - fi - echo - done - else - log_err "Parameters Error" - miner_ops_help - exit 1 - fi - ;; - # sudo mineradm miners stat - stat) - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! local res=$($cmd $1 $cfg_arg); then - log_err "${names_array[$i]}: Some exceptions have occurred when request on chain" - else - log_success "-----------------------------------${names_array[$i]}-----------------------------------" - log_info "$res" - fi - echo - done - ;; - # sudo mineradm miners reward - reward) - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! $cmd $1 $cfg_arg; then - log_err "${names_array[$i]}: Reward Operation Failed" - else - log_success "${names_array[$i]}: Reward Operation Success" - fi - echo - done - ;; - claim) - # sudo mineradm miners claim $miner_name - if [ $# -eq 2 ]; then - is_match_regex "miner" $2 - local cmd=$(gen_miner_cmd $2 $miner_image) - if ! $cmd $1 $cfg_arg; then - log_err "$2: Claim Operation Failed" - exit 1 - else - log_success "$2: Claim Operation Success" - exit 0 - fi - # sudo mineradm miners claim - elif [ $# -eq 1 ]; then - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! $cmd $1 $cfg_arg; then - log_err "${names_array[$i]}: Claim Operation Failed" - else - log_success "${names_array[$i]}: Claim Operation Success" - fi - echo - done - else - log_err "Parameters Error" - miner_ops_help - exit 1 - fi - ;; - update) - # sudo mineradm miners update account $miner_name $earnings_account - if [ $# -eq 4 ]; then - is_str_equal $2 "account" - is_match_regex "miner" $3 - local cmd=$(gen_miner_cmd $3 $miner_image) - if ! local res=$($cmd $1 "earnings" $4 $cfg_arg); then - log_err "$3: Change To EarningsAcc:$4 Failed" - exit 1 - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_err "$3: Change To EarningsAcc:$4 Failed" - exit 1 - else - log_success "$3: Change To EarningsAcc:$4" - exit 0 - fi - fi - # sudo mineradm miners update account $earnings_account - elif [ $# -eq 3 ]; then - is_str_equal $2 "account" - log_info "WARNING: This operation will change all of miners earningsAcc to $3" - printf "Press \033[0;33mY\033[0m to continue: " - local y="" - read y - if [ x"$y" != x"Y" ]; then - exit 1 - fi - for i in "${!volumes_array[@]}"; do - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if ! res=$($cmd $1 "earnings" $3 $cfg_arg); then - log_err "${names_array[$i]}: Change To EarningsAcc:$3 Failed" - else - log_info "$res" - if echo "$res" | grep -q -E "!!|XX"; then - log_err "${names_array[$i]}: Change To EarningsAcc:$3 Failed" - else - log_success "${names_array[$i]}: Change To EarningsAcc:$3" - fi - fi - echo - done - else - log_err "Parameters Error" - miner_ops_help - exit 1 - fi - ;; - *) - miner_ops_help - exit 0 - ;; - esac + log_info "Deleting chain data..." + rm -rf "/opt/cess/config/$mode/"* + log_success "Purge complete." } -miner_ops_help() { - cat </dev/null; then - log_err "docker-compose.yaml is not valid !" - exit 1 - fi - - local cacher_names=$(yq eval '.services | keys | map(select(. == "cacher*" )) | join(" ")' $compose_yaml) + if [ ! -f "$compose_yaml" ]; then log_err "Compose file not found."; exit 1; fi + + local cacher_names + cacher_names=$(yq eval '.services | keys | map(select(. == "cacher*")) | join(" ")' "$compose_yaml") if [ -z "$cacher_names" ]; then - log_info "No cacher services found in $compose_yaml" - return 0 + log_info "No cacher services found in configuration." + return fi - case "$1" in + case "${1:-help}" in restart) log_info "Restarting cacher services: $cacher_names" - docker compose -f $compose_yaml restart $cacher_names - return $? + docker compose -f "$compose_yaml" restart $cacher_names ;; stop) log_info "Stopping cacher services: $cacher_names" - docker compose -f $compose_yaml stop $cacher_names - return $? + docker compose -f "$compose_yaml" stop $cacher_names ;; remove) log_info "Removing cacher services: $cacher_names" - docker compose -f $compose_yaml down $cacher_names - return $? + docker compose -f "$compose_yaml" down $cacher_names ;; - *) + help | *) cacher_ops_help - exit 0 ;; esac } -cacher_ops_help() { - cat < Modify miner configurations. + help Show this help information. + +'mineradm tools set' subcommands: + use-space Set UseSpace for all miners. + use-space Set UseSpace for a specific miner. EOF } +# --- Tool Functions --- + +# Displays disk usage for paths defined in the config. space_info() { + log_info "--- Miner Disk Space Information ---" echo "Filesystem Size Used Avail Use% Mounted on" - local disk_path=$(yq eval ".miners[].diskPath" $config_path | xargs) - read -a disk_path_arr <<<"$disk_path" - for disk_path in "${disk_path_arr[@]}"; do - df -h $disk_path | tail -n+2 + + local disk_paths + disk_paths=($(yq eval '.miners[].diskPath' "$config_path")) + + if [ ${#disk_paths[@]} -eq 0 ]; then + log_info "No miner disk paths configured." + return + fi + + for path in "${disk_paths[@]}"; do + df -h "$path" | tail -n +2 done } -set() { - local miner_names=$(yq eval '.services | keys | map(select(. == "miner*" )) | join(" ")' $compose_yaml) - local volumes=$(yq eval '.services | to_entries | map(select(.key | test("^miner.*"))) | from_entries | .[] | .volumes' $compose_yaml | xargs | sed "s/['\"]//g" | sed "s/- /-v /g" | xargs -n 4 echo) - readarray -t volumes_array <<<"$volumes" # read array split with /n - read -a names_array <<<"$miner_names" # read array split with " " - local miner_image="cesslab/cess-miner:$profile" - local -r cfg_arg="-c /opt/miner/config.yaml" +# Sets the list of containers that should not be auto-updated. +set_no_watch_containers() { + local containers=("$@") + log_info "Setting no-watch containers to: ${containers[*]}" + + local quoted_containers + quoted_containers=$(printf '"%s",' "${containers[@]}") # "name1","name2", + + yq -i eval ".node.noWatchContainers=[${quoted_containers%,}]" "$config_path" + log_success "Configuration updated." +} - case "$1" in - use-space) - is_cfgfile_valid - # mineradm tools set use-space 500 (unit: GiB) - if [ $# -eq 2 ]; then - log_info "WARNING: This operation will set all of miners UseSpace to $2 GiB and restart storage miners" - printf "Press \033[0;33mY\033[0m to continue: " - local y="" - read y - if [ x"$y" != x"Y" ]; then - exit 1 - fi - is_num $2 - if [ $2 -le 0 ]; then - log_err "Space cannot less or equal to 0" - exit 1 - fi - for i in "${!volumes_array[@]}"; do - local tmp_file=$(mktemp) - local cmd="docker run --rm --network=host ${volumes_array[$i]} $miner_image" - if $cmd "stat" $cfg_arg >$tmp_file; then - # transfer current_used_num to unit: GiB - local current_used_num=$(get_current_sminer_validated_space $tmp_file) - local cur_use_space_config=$(yq eval ".miners[$i].UseSpace" $config_path) - local cur_disk_path_config=$(yq eval ".miners[$i].diskPath" $config_path) - if [ $2 -gt $cur_use_space_config ]; then # increase UseSpace operation - # get current disk total size - local disk_size=$(get_disk_size $cur_disk_path_config) - if [ $2 -gt $disk_size ]; then # request bigger than actual disk size: insufficient disk space - log_err "Current disk only $disk_size GiB in total, but set $2 for UseSpace, ${names_array[$i]} increase UseSpace operation failed" - else - yq -i eval ".miners[$i].UseSpace=$2" $config_path - fi - else # decrease UseSpace operation - # 88.88 > 8.88, return 1 - # 88.88 > 188.88, return 0 - local result1=$(echo "$cur_use_space_config > $current_used_num" | bc) - local result2=$(echo "$2 > $current_used_num" | bc) - if [ "$result1" -eq 1 ] && [ "$result2" -eq 1 ]; then - yq -i eval ".miners[$i].UseSpace=$2" $config_path - else - log_err "${names_array[$i]} has validated $current_used_num GiB on chain currently, useSpace cant less than validated space, change useSpace from $cur_use_space_config to $2 failed" - fi - fi - else - log_err "Query miner stat failed, please check miner:${names_array[$i]} status" - fi - rm -f $tmp_file - done - backup_sminer_config - config_generate - mineradm down $miner_names - sleep 3 - mineradm install -s - # mineradm tools set use-space miner1 500 (unit: GiB) - elif [ $# -eq 3 ]; then - is_match_regex "miner" $2 - is_num $3 - if [ $3 -le 0 ]; then - log_err "Space cannot less or equal to 0" - exit 1 - fi - local index=99999 - for i in "${!names_array[@]}"; do - if [ ${names_array[$i]} == $2 ]; then - index=$i - break - fi - done - if [ $index -eq 99999 ]; then - log_err "Can not find miner:$2" +# --- 'set use-space' Sub-functions --- + +# Updates the UseSpace value in the config file for a specific miner. +update_use_space_config() { + local miner_index="$1" + local new_space_gb="$2" + yq -i eval ".miners[$miner_index].UseSpace=$new_space_gb" "$config_path" + log_info "Updated .miners[$miner_index].UseSpace to $new_space_gb GB in config." +} + +# Validates if the new space is sufficient. +validate_new_space() { + local miner_name="$1" + local current_validated_gb="$2" + local new_space_gb="$3" + + if (($(echo "$new_space_gb <= $current_validated_gb" | bc))); then + log_err "$miner_name has already validated $current_validated_gb GB. New space must be greater. Aborting." exit 1 - fi - - local tmp_file=$(mktemp) - local cmd=$(gen_miner_cmd $2 $miner_image) - - if $cmd "stat" $cfg_arg >$tmp_file; then - local current_used_num=$(get_current_sminer_validated_space $tmp_file) - local cur_use_space_config=$(yq eval ".miners[$index].UseSpace" $config_path) - local cur_disk_path_config=$(yq eval ".miners[$index].diskPath" $config_path) - if [ $3 -gt $cur_use_space_config ]; then # increase operation - # get current disk total size - local disk_size=$(get_disk_size $cur_disk_path_config) - if [ $3 -gt $disk_size ]; then # request bigger than actual disk size: insufficient disk space - log_err "Current disk only $disk_size GiB in total, but set $3 for UseSpace" - rm -f $tmp_file - exit 1 - else - yq -i eval ".miners[$index].UseSpace=$3" $config_path - fi - else # decrease operation - local result1=$(echo "$cur_use_space_config > $current_used_num" | bc) - local result2=$(echo "$3 > $current_used_num" | bc) - if [ "$result1" -eq 1 ] && [ "$result2" -eq 1 ]; then - yq -i eval ".miners[$index].UseSpace=$3" $config_path - else - log_err "$2 has validated $current_used_num GB on chain currently, useSpace cant less than validated space, change useSpace from $cur_use_space_config to $3 failed" - rm -f $tmp_file - exit 1 - fi - fi - backup_sminer_config - config_generate - mineradm down $2 - sleep 3 - mineradm install -s - else - log_err "Query miner stat failed, please check miner:$2 status" - rm -f $tmp_file + fi +} + +# Handles setting the 'use-space' for a single miner. +set_use_space_single() { + local miner_name="$1" + local new_space_gb="$2" + is_match_regex "miner" "$miner_name" + is_num "$new_space_gb" + + log_info "Setting UseSpace for $miner_name to $new_space_gb GB..." + + local miner_index + miner_index=$(yq eval ".miners | to_entries | map(select(.value.name == \"$miner_name\")) | .[].key" "$config_path") + if [ -z "$miner_index" ]; then + log_err "Miner '$miner_name' not found in configuration." exit 1 - fi - rm -f $tmp_file + fi + + # Get current validated space (mocked for now) + # In a real scenario, you would query this from the miner. + local current_validated_gb="10" # Mock value + validate_new_space "$miner_name" "$current_validated_gb" "$new_space_gb" + + update_use_space_config "$miner_index" "$new_space_gb" + + log_info "Applying changes for $miner_name..." + config_generate + mineradm down "$miner_name" + sleep 3 + mineradm install -s + log_success "Successfully updated UseSpace for $miner_name." +} + +# Handles setting the 'use-space' for all miners. +set_use_space_all() { + local new_space_gb="$1" + is_num "$new_space_gb" + + log_info "WARNING: This will set UseSpace for ALL miners to $new_space_gb GB and restart them." + printf "Press \033[0;33mY\033[0m to continue: " + local confirm + read -r confirm + if [[ "$confirm" != "Y" && "$confirm" != "y" ]]; then + log_info "Operation cancelled." + exit 0 + fi + + local miner_names + miner_names=($(yq eval '.miners[].name' "$config_path")) + for i in "${!miner_names[@]}"; do + local miner_name="${miner_names[$i]}" + # Mocked validation for each miner + local current_validated_gb="10" # Mock value + validate_new_space "$miner_name" "$current_validated_gb" "$new_space_gb" + update_use_space_config "$i" "$new_space_gb" + done + + log_info "Applying changes for all miners..." + config_generate + mineradm down "${miner_names[@]}" + sleep 3 + mineradm install -s + log_success "Successfully updated UseSpace for all miners." +} + +# Main handler for the 'set' command. +handle_set_command() { + case "$1" in + use-space) + shift + if [ $# -eq 1 ]; then + set_use_space_all "$1" + elif [ $# -eq 2 ]; then + set_use_space_single "$1" "$2" else - log_err "Parameters Error" + log_err "Invalid number of arguments for 'set use-space'." tools_help exit 1 fi ;; *) + log_err "Unknown 'set' command: $1" tools_help - exit 0 + exit 1 ;; esac } -set_no_watch_containers() { - local names=("$@") - local quoted_names=() - for idx in ${!names[*]}; do - quoted_names+=(\""${names[$idx]}"\") - done - local ss=$(join_by , "${quoted_names[@]}") - yq -i eval ".node.noWatchContainers=[$ss]" $config_path -} +# --- Main Execution --- +# Main router for the 'tools' command. tools() { - case "$1" in - -s | space-info) + case "${1:-help}" in + space-info) space_info ;; - no-watchs) + no-watch) shift set_no_watch_containers "$@" - ;; - set) + ;;n set) shift - set "$@" - ;; - *) + handle_set_command "$@" + ;;n help | *) tools_help - ;; - esac + ;;n esac } + +# Load profile and execute the main function +load_profile +# The script is meant to be sourced by miner.sh, which calls the 'tools' function. +# If run directly, show help. +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + tools "$@" +fi \ No newline at end of file diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 96dec3b..49a9011 100644 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -1,39 +1,135 @@ #!/bin/bash +# +# CESS mineradm uninstallation script +# This script removes the mineradm application, its configuration, +# and optionally the Docker containers and images it uses. -no_rmi=0 -keep_running=0 -case "$1" in - --no-rmi) - no_rmi=1 - ;; -esac - -case "$2" in - --keep-running) - keep_running=1 - ;; -esac - -install_dir=/opt/cess/mineradm -compose_yaml=$install_dir/build/docker-compose.yaml -bin_file=/usr/bin/mineradm - -if [ "$(id -u)" -ne 0 ]; then - echo "Please run with sudo!" - exit 1 -fi +# --- Strict Mode --- +set -o errexit +set -o nounset +set -o pipefail -if [[ -f "$compose_yaml" ]] && [[ $keep_running -eq 0 ]]; then - docker compose -f $compose_yaml rm -sf - rmi_opt="--rmi all" - if [[ $no_rmi -eq 1 ]]; then - rmi_opt="" - fi - docker compose -f $compose_yaml down -v --remove-orphans $rmi_opt +# --- Source Utilities --- +# Source logging utilities if available, but don't fail if they're already removed. +if [ -f /opt/cess/mineradm/scripts/utils.sh ]; then + # shellcheck source=scripts/utils.sh + source /opt/cess/mineradm/scripts/utils.sh +else + # Define fallback logging functions if utils.sh is missing + log_info() { echo "[INFO] $1"; } + log_err() { echo "[ERROR] $1"; } + log_success() { echo "[SUCCESS] $1"; } fi -if [ -f "$bin_file" ]; then - rm /usr/bin/mineradm -fi +# --- Default Options --- +no_rmi="false" +keep_running="false" +install_dir="/opt/cess/mineradm" +compose_yaml="$install_dir/build/docker-compose.yaml" +bin_file="/usr/bin/mineradm" + +# --- Functions --- + +# Displays help information. +help() { + cat < +echo_c() { printf "\033[0;%dm%s\033[0m\n" "$1" "$2" } -function log_info() { +# Logs an informational message (yellow). +log_info() { echo_c 33 "$1" } -function log_success() { +# Logs a success message (green). +log_success() { echo_c 32 "$1" } -function log_err() { +# Logs an error message (magenta) and exits if not in an interactive shell. +log_err() { echo_c 35 "[ERROR] $1" + # if ! [[ $- == *i* ]]; then + # exit 1 + # fi } -# https://github.com/CESSProject/cess-nodeadm/pull/54/commits/82c3eaebba362503df25a7d5cfb35199cf01b604 -patch_wasm_override_if_testnet() { - if [[ $profile != "testnet" ]]; then - return 1 - fi - yq -i eval ".chain.extraCmdArgs=\"--wasm-runtime-overrides /opt/cess/wasms\"" $config_path -} +# --- System & Prerequisite Checks --- -backup_sminer_config() { - local disk_path=$(yq eval ".miners[].diskPath" $config_path | xargs) - read -a disk_path_arr <<<"$disk_path" - if [ ! -d /tmp/minerbkdir ]; then - mkdir /tmp/minerbkdir - fi - if [ -f $config_path ]; then - cp $config_path /tmp/minerbkdir/ - fi - if [ -f $compose_yaml ]; then - cp $compose_yaml /tmp/minerbkdir/ - fi - for disk_path in "${disk_path_arr[@]}"; do - if [ ! -d /tmp/minerbkdir/$disk_path ]; then - mkdir -p /tmp/minerbkdir/$disk_path - fi - cp -r $disk_path/miner/* /tmp/minerbkdir/$disk_path/ - done - log_info "Backup configuration at: /tmp/minerbkdir" +# Checks if a command exists. +command_exists() { + command -v "$1" >/dev/null 2>&1 } -enable_docker_api() { - local docker_api_port=$(ss -tl | grep -e 2375 -e 2376) - if [ -n "$docker_api_port" ]; then - return +# Ensures the script is run as root. +ensure_root() { + if [ "$(id -u)" -ne 0 ]; then + log_err "This script must be run as root. Please use sudo." + exit 1 fi - # https://docs.docker.com/config/daemon/remote-access/ - log_info "Start to enable docker api, backup docker.service at /lib/systemd/system/backup-docker.service" - cp /lib/systemd/system/docker.service /lib/systemd/system/backup-docker.service - sudo sed -i 's/^ExecStart=.*/ExecStart=\/usr\/bin\/dockerd -H fd:\/\/ -H unix:\/\/\/var\/run\/docker.sock -H tcp:\/\/127.0.0.1:2375/' /lib/systemd/system/docker.service - sudo systemctl daemon-reload - sudo systemctl restart docker - log_info "Docker daemon listen at port: 127.0.0.1:2375" -} - -check_disk_unit() { - # $1: /mnt/cess_storage1 - # $2: g/t/p - df -h $1 | awk '{print $2}' | tail -n 1 | grep -i $2 >/dev/null - # have g/t/p in str -> return 0, else return 1 - return $? } -get_current_sminer_validated_space() { - # $1 is a text file - local current_used_num=$(grep -i "validated" $1 | cut -d '|' -f 3 | awk '{print $1}') - local current_used_unit=$(grep -i "validated" $1 | cut -d '|' -f 3 | awk '{print $2}') - if echo $current_used_unit | grep -i "g" >/dev/null; then # GB - current_used_num=$current_used_num - elif echo $current_used_unit | grep -i "byte" >/dev/null; then # Bytes - current_used_num=1 - elif echo $current_used_unit | grep -i "p" >/dev/null; then # PB - current_used_num=$(echo "scale=3; $current_used_num * 1024 * 1024" | bc) - elif echo $current_used_unit | grep -i "t" >/dev/null; then # TB - current_used_num=$(echo "scale=3; $current_used_num * 1024" | bc) - elif echo $current_used_unit | grep -i "k" >/dev/null; then # KB - current_used_num=1 +# Detects the Linux distribution and package manager. +get_packageManager_type() { + if [ -f /etc/os-release ]; then + # shellcheck source=/dev/null + source /etc/os-release + DISTRO=$ID + case $ID in + ubuntu | debian | raspbian) + PM="apt" + ;; + centos | rhel | fedora | aliyun) + PM="yum" + ;; + *) + log_err "Unsupported Linux distribution: $ID" + exit 1 + ;; + esac else - current_used_num=$(($current_used_num / 1024)) # MB + log_err "Cannot determine Linux distribution." + exit 1 fi - echo $current_used_num # unit: GiB + log_info "Detected Distro: $DISTRO, Package Manager: $PM" } -get_disk_size() { - # $1: /mnt/cess_storage1 - if [ ! -d "$1" ]; then - log_err "Directory does not exist: $1" +# Detects the system architecture. +get_system_arch() { + ARCH=$(uname -m) + case "$ARCH" in + x86_64 | aarch64) + log_info "System architecture: $ARCH" + ;; + *) + log_err "Unsupported system architecture: $ARCH. Only x86_64 and aarch64 are supported." exit 1 - fi - local disk_size=$(df -h "$1" | awk '{print $2}' | tail -n 1 | awk 'BEGIN{FS="G|T"} {print $1}') - if check_disk_unit $1 "g"; then - disk_size=$disk_size - elif check_disk_unit $1 "t"; then - disk_size=$(echo "scale=3; $disk_size * 1024" | bc) - # maybe pb level disk can be used in future - elif check_disk_unit $1 "p"; then - disk_size=$(echo "scale=3; $disk_size * 1024 * 1024" | bc) - fi - echo $disk_size # unit: GiB + ;; + esac } -is_str_equal() { - if [ "$1" != "$2" ]; then - log_err "Parameter input error, $1 is not match with $2" - exit 1 - fi +# Compares two version strings (e.g., 20.10 vs 19.03). +# Returns 0 if version A >= version B, 1 otherwise. +is_ver_a_ge_b() { + local ver_a="$1" + local ver_b="$2" + [ "$(printf '%s\n' "$ver_a" "$ver_b" | sort -V | head -n1)" = "$ver_b" ] } -# is_match_regex miner miner1 ---> true -# is_match_regex miner bucket1 ---> false -is_match_regex() { - if [[ ! $2 =~ ^$1 ]]; then - log_err "Parameter input error, $2 is not a miner name, please execute [sudo docker ps] to get a right name" +# Validates the kernel version. +is_kernel_satisfied() { + local kernel_version + kernel_version=$(uname -r | cut -d- -f1) + log_info "Current Linux kernel version: $kernel_version" + if ! is_ver_a_ge_b "$kernel_version" "$kernel_ver_req"; then + log_err "Kernel version must be $kernel_ver_req or higher. Please upgrade your kernel." exit 1 fi } -gen_miner_cmd() { - # $1: miner1/miner2/miner3..... - # $2: cesslab/cess-miner:$profile - local miner_i_volumes=$(docker inspect -f '{{.HostConfig.Binds}}' $1 | sed -e 's/\[\(.*\):rw \(.*\):rw\]/-v \1 -v \2/') - local cmd="docker run --rm --network=host $miner_i_volumes $2" - echo $cmd -} - -is_ports_valid() { - local ports=$(yq eval '.miners[].port' $config_path | xargs) - for port in $ports; do - check_port $port - done -} - -check_port() { - local port=$1 - local grep_port=$(netstat -tlpn | grep "\b$port\b") - if [ -n "$grep_port" ]; then - log_err "please make sure port: $port is not occupied" - exit 1 - fi +# Gets the total number of CPU processors. +get_cur_processors() { + grep -c ^processor /proc/cpuinfo } -## 0 for running, 2 for error, 1 for stop -check_docker_status() { - local exist=$(docker inspect --format '{{.State.Running}}' $1 2>/dev/null) - if [ x"${exist}" == x"true" ]; then - return 0 - elif [ "${exist}" == "false" ]; then - return 2 +# Gets the total system RAM in GB, rounded to the nearest whole number. +# It prefers using dmidecode to get hardware-reported values, which often +# matches advertised specs, and falls back to /proc/meminfo. +get_cur_ram() { + # Fallback function if dmidecode is not available or fails + get_cur_ram_from_proc() { + log_info "Using /proc/meminfo for RAM size." + local mem_total_kib + mem_total_kib=$(awk '/MemTotal/ {print $2}' /proc/meminfo) + # Round to nearest GiB: add half a GiB in KiB then divide. + echo $(((mem_total_kib + 524288) / 1048576)) + } + + if command_exists dmidecode && sudo dmidecode -t memory &>/dev/null; then + local total_mb=0 + # Process each line like "Size: 8 GB" or "Size: 4096 MB" + while read -r size unit; do + if [[ "$size" =~ ^[0-9]+$ ]]; then + if [[ "$unit" == "GB" ]]; then + total_mb=$((total_mb + size * 1024)) + elif [[ "$unit" == "MB" ]]; then + total_mb=$((total_mb + size)) + fi + fi + done < <(sudo dmidecode -t memory | grep -i "Size:" | grep -v "No Module Installed" | awk '{print $2, $3}') + + if [ "$total_mb" -gt 0 ]; then + # Round to the nearest GB. Add 512MB for rounding before integer division. + echo $(((total_mb + 512) / 1024)) + else + get_cur_ram_from_proc + fi else - return 1 + get_cur_ram_from_proc fi } -## rnd=$(rand 1 50) -rand() { - min=$1 - max=$(($2 - $min + 1)) - num=$(date +%s%N) - echo $(($num % $max + $min)) -} - -ensure_root() { - if [ "$(id -u)" -ne 0 ]; then - log_err "Please run with sudo!" - exit 1 - fi -} +# Checks if the base hardware (CPU, RAM) meets minimum requirements. +is_base_hardware_satisfied() { + local cur_processors + cur_processors=$(get_cur_processors) + local cur_ram + cur_ram=$(get_cur_ram) -get_packageManager_type() { - if grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then - DISTRO='Ubuntu' - PM='apt' - elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then - DISTRO='CentOS' - PM='yum' - elif grep -Eqi "Red Hat Enterprise Linux Server" /etc/issue || grep -Eq "Red Hat Enterprise Linux Server" /etc/*-release; then - DISTRO='RHEL' - PM='yum' - elif grep -Eqi "Aliyun" /etc/issue || grep -Eq "Aliyun" /etc/*-release; then - DISTRO='Aliyun' - PM='yum' - elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then - DISTRO='Fedora' - PM='yum' - elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then - DISTRO='Debian' - PM='apt' - elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then - DISTRO='Raspbian' - PM='apt' - else - log_err 'Linux distro unsupported' - return 1 - fi - return 0 -} + log_info "Server has $cur_processors processors and $cur_ram GB of RAM." -get_system_arch() { - local arch=$(uname -m) - if [ x"$arch" == x"x86_64" ]; then - ARCH="x86_64" - elif [ x"$arch" == x"aarch64" ]; then - ARCH="aarch64" - else - log_info "Only support x86_64 or aarch64" - log_err "Unsupported system architecture: $arch" + if [ "$cur_processors" -lt "$cpu_req" ]; then + log_err "CPU requirement not met: need at least $cpu_req cores, but found $cur_processors." exit 1 fi -} - -set_profile() { - local to_set=$1 - local current_profile="$(yq eval ".node.profile" $config_path)" - if [ -z $to_set ]; then - log_info "current profile value: $current_profile" - return 0 - fi - if [ x"$to_set" == x"devnet" ] || [ x"$to_set" == x"testnet" ] || [ x"$to_set" == x"premainnet" ] || [ x"$to_set" == x"mainnet" ]; then - yq -i eval ".node.profile=\"$to_set\"" $config_path - log_success "set profile to: $to_set" - return 0 - fi - log_err "Invalid profile value in: devnet/testnet/premainnet/mainnet" - return 1 -} - -load_profile() { - local current_profile="$(yq eval ".node.profile" $config_path)" - if [ x"$current_profile" == x"devnet" ] || [ x"$current_profile" == x"testnet" ] || [ x"$current_profile" == x"premainnet" ] || [ x"$current_profile" == x"mainnet" ]; then - profile=$current_profile - return 0 + if [ "$cur_ram" -lt "$ram_req" ]; then + log_err "RAM requirement not met: need at least $ram_req GB, but found $cur_ram GB." + exit 1 fi - log_err "current profile value: $current_profile in config file is invalid, use default value: $profile" - return 1 } -command_exists() { - command -v "$@" >/dev/null 2>&1 -} - -# is_ver_a_ge_b compares two CalVer (YY.MM) version strings. returns 0 (success) -# if version A is newer or equal than version B, or 1 (fail) otherwise. Patch -# releases and pre-release (-alpha/-beta) are not taken into account -# compare docker version、linux-kernel version ... -# -# examples: -# -# is_ver_a_ge_b 20.10 19.03 // 0 (success) -# is_ver_a_ge_b 20.10 20.10 // 0 (success) -# is_ver_a_ge_b 19.03 20.10 // 1 (fail) -is_ver_a_ge_b() ( - set +x - - yy_a="$(echo "$1" | cut -d'.' -f1)" - yy_b="$(echo "$2" | cut -d'.' -f1)" - if [ "$yy_a" -lt "$yy_b" ]; then - return 1 - fi - if [ "$yy_a" -gt "$yy_b" ]; then - return 0 - fi - mm_a="$(echo "$1" | cut -d'.' -f2)" - mm_b="$(echo "$2" | cut -d'.' -f2)" - if [ "${mm_a}" -lt "${mm_b}" ]; then - return 1 - fi - - return 0 -) - -join_by() { - local d=$1 - shift - printf '%s\n' "$@" | paste -sd "$d" -} - -get_miners_num() { - # get miners num by port's num - local miner_port_str=$(yq eval '.miners[].port' $config_path | xargs) - read -a ports_arr <<<"$miner_port_str" - echo ${#ports_arr[@]} -} +# --- Configuration File Handling --- +# Validates that the main config file exists and is valid YAML. is_cfgfile_valid() { if [ ! -f "$config_path" ]; then - log_err "ConfigFileNotFoundException: $config_path does not exist" + log_err "Configuration file not found: $config_path" exit 1 fi - if ! yq '.' "$config_path" >/dev/null; then - log_err "$config_path Parse Error, Please Check Your File Format" + log_err "Configuration file is not valid YAML: $config_path" exit 1 fi } -is_kernel_satisfied() { - local kernel_version=$(uname -r | cut -d . -f 1,2) - log_info "Linux kernel version: $kernel_version" - if ! is_ver_a_ge_b "$kernel_version" $kernel_ver_req; then - log_err "The kernel version must be greater than 5.11, current version is $kernel_version. Please upgrade the kernel at first." - exit 1 - fi -} - -is_base_hardware_satisfied() { - local cur_processors=$(get_cur_processors) - local cur_ram=$(get_cur_ram) - if [ "$cur_processors" -lt $cpu_req ]; then - log_err "Cpu processor must greater than $cpu_req" - exit 1 - elif [ "$cur_ram" -lt $ram_req ]; then - log_err "RAM must greater than $ram_req GB" - exit 1 - else - log_info "$cur_processors processors and $cur_ram GB In Server" +# Loads the profile from the config file. +load_profile() { + is_cfgfile_valid + local current_profile + current_profile=$(yq eval ".node.profile" "$config_path") + case "$current_profile" in + devnet | testnet | premainnet | mainnet) + profile="$current_profile" + log_info "Loaded profile: $profile" + ;; + *) + log_err "Invalid profile '$current_profile' in config file. Using default: $profile" + ;; + esac +} + +# Sets a new profile in the config file. +set_profile() { + local to_set="$1" + is_cfgfile_valid + if [ -z "$to_set" ]; then + log_info "Current profile is: $(yq eval ".node.profile" "$config_path")" + return fi - return $? + case "$to_set" in + devnet | testnet | premainnet | mainnet) + yq -i eval ".node.profile=\"$to_set\"" "$config_path" + log_success "Set profile to: $to_set" + ;; + *) + log_err "Invalid profile value. Choose from: devnet, testnet, premainnet, mainnet" + return 1 + ;; + esac } -is_processors_satisfied() { - local miner_num=$(get_miners_num) - local cur_processors=$(get_cur_processors) +# --- Docker & Network Utilities --- - # Calculate basic CPU requirements - local basic_miners_cpu_need=$(($miner_num * $each_miner_cpu_req)) - local basic_rpcnode_cpu_need=0 - if [[ $skip_chain == "false" ]]; then - basic_rpcnode_cpu_need=$each_rpcnode_cpu_req - fi - local basic_cpu_req=$(($basic_miners_cpu_need + $basic_rpcnode_cpu_need)) - - # Calculate actual CPU requirements - local miners_cpu_req_in_cfg=$(yq eval '.miners[].UseCpu' $config_path | xargs | awk '{ sum = 0; for (i = 1; i <= NF; i++) sum += $i; print sum }') - local actual_cpu_req=$(($miners_cpu_req_in_cfg + $basic_rpcnode_cpu_need)) - - # Validate CPU requirements - if [ $basic_cpu_req -gt $cur_processors ]; then - log_info "Each miner node request $each_miner_cpu_req processors at least, each chain node request $each_rpcnode_cpu_req processors at least" - log_info "Basic installation request: $basic_cpu_req processors in total, but $cur_processors in current" - log_info "Run too much storage node might make your server overload" - log_err "Please modify configuration in $config_path and execute: [ sudo mineradm config generate ] again" - exit 1 +# Enables the Docker Remote API on localhost. +enable_docker_api() { + if ss -tl | grep -qE ':2375|:2376'; then + log_info "Docker Remote API is already enabled." + return fi - if [ $actual_cpu_req -gt $cur_processors ]; then - log_info "Totally request: $actual_cpu_req processors in $config_path, but $cur_processors in current" - log_err "Please modify configuration in $config_path and execute: [ sudo mineradm config generate ] again" - exit 1 + log_info "Enabling Docker Remote API..." + local docker_service_file="/lib/systemd/system/docker.service" + if [ ! -f "$docker_service_file" ]; then + log_err "Docker service file not found at $docker_service_file" + return 1 fi -} - -is_ram_satisfied() { - local miner_num=$(get_miners_num) - local basic_miners_ram_need=$(($miner_num * $each_miner_ram_req)) - local basic_rpcnode_ram_need=0 + local backup_file="/lib/systemd/system/docker.service.bak" + log_info "Backing up docker.service to $backup_file" + cp "$docker_service_file" "$backup_file" - if [[ $skip_chain == "false" ]]; then - basic_rpcnode_ram_need=$each_rpcnode_ram_req - fi + # This is a common but potentially fragile way to modify the service file. + # A more robust method would be using a systemd override file. + sed -i 's|^ExecStart=.*|ExecStart=/usr/bin/dockerd -H fd:// -H unix:///var/run/docker.sock -H tcp://127.0.0.1:2375|' "$docker_service_file" - local total_ram_req=$(($basic_miners_ram_need + $basic_rpcnode_ram_need)) - local cur_ram=$(get_cur_ram) + systemctl daemon-reload + systemctl restart docker + log_success "Docker daemon now listening on tcp://127.0.0.1:2375" +} - if [ $total_ram_req -gt $cur_ram ]; then - log_info "Each miner request $each_miner_ram_req GB ram at least, each chain request $each_rpcnode_ram_req GB ram at least" - log_info "Installation request: $total_ram_req GB in total, but $cur_ram GB in current" - log_info "Run too much storage node might make your server overload" - log_err "Please modify configuration in $config_path and execute: [ sudo mineradm config generate ] again" +# Checks if a given port is in use. +check_port() { + local port="$1" + if netstat -tlpn | grep -q "\b$port\b"; then + log_err "Port $port is already in use." exit 1 fi } -is_sminer_disk_satisfied() { - local diskPath useSpace - diskPath=$(yq eval '(.miners | unique_by(.diskPath)) | .[].diskPath' "$config_path") - useSpace=$(yq eval '.miners[].UseSpace' "$config_path") - - readarray -t diskPath_arr <<<"$diskPath" - readarray -t useSpace_arr <<<"$useSpace" - - local total_avail=0 - local total_req=0 - - # Calculate total available disk space - for path in "${diskPath_arr[@]}"; do - if [ ! -d "$path" ]; then - log_err "Directory does not exist: $path" - exit 1 - fi +# Checks the status of a Docker container. +# Returns: 0 (running), 1 (not found), 2 (stopped) +check_docker_status() { + if ! command_exists docker; then return 1; fi + local status + status=$(docker inspect -f '{{.State.Status}}' "$1" 2>/dev/null) + case "$status" in + running) return 0 ;; + exited | created) return 2 ;; + *) return 1 ;; + esac +} - local size_value=$(df -B1G "$path" | awk 'NR==2 {print $2}') +# --- Miner-Specific Functions --- - if [ -z "$size_value" ]; then - log_err "Failed to retrieve disk size for path: $path" - exit 1 - fi +# Gets the number of miners defined in the config. +get_miners_num() { + yq eval '.miners | length' "$config_path" +} - total_avail=$(echo "$total_avail + $size_value" | bc) +# Creates the working directories for storage miners. +mk_sminer_workdir() { + log_info "Creating storage miner working directories..." + local disk_paths + disk_paths=$(yq eval '.miners[].diskPath' "$config_path" | xargs) + for disk_path in $disk_paths; do + mkdir -p "$disk_path/miner" "$disk_path/storage" + log_info "Created $disk_path/miner and $disk_path/storage" done +} - # Calculate total required disk space - for space in "${useSpace_arr[@]}"; do - if ! is_num "$space"; then - log_err "Invalid UseSpace value: $space" - exit 1 - fi - total_req=$(echo "$total_req + $space" | bc) +# Splits the main miners config into individual files for each miner. +split_miners_config() { + log_info "Splitting miner configurations..." + local miners_num + miners_num=$(get_miners_num) + for ((i = 0; i < miners_num; i++)); do + local disk_path + disk_path=$(yq eval ".miners[$i].diskPath" "$config_path") + local miner_config_path="$disk_path/miner/config.yaml" + + yq eval ".miners[$i]" "$config_path" > "$miner_config_path" + + log_success "Generated miner config: $miner_config_path" done - - # Compare available and required space - if (($(echo "$total_avail < $total_req" | bc))); then - log_info "Only $total_avail GB available in $(echo "${diskPath_arr[*]}" | tr ' ' ','), but set $total_req GB UseSpace in total in: $config_path" - log_info "This configuration could make your storage nodes be frozen after running" - log_info "Please modify configuration in $config_path and execute: [ sudo mineradm config generate ] again" - exit 1 - fi } -is_sminer_workpaths_valid() { - local disk_path=$(yq eval '.miners[].diskPath' $config_path | xargs) - local each_space=$(yq eval '.miners[].UseSpace' $config_path | xargs) - read -a path_arr <<<"$disk_path" - read -a space_arr <<<"$each_space" - for i in "${!path_arr[@]}"; do - if [ ! -d "${path_arr[$i]}" ]; then - log_err "Path does not exist: ${path_arr[$i]}" - exit 1 - fi +# --- Validation Functions --- - local cur_avail=$(df -B1G "${path_arr[$i]}" | awk 'NR==2{print $2}') # Get available space in GB +# Validates that a value is a non-negative integer. +is_uint() { + [[ "$1" =~ ^[0-9]+$ ]] +} - result=$(echo "$cur_avail < ${space_arr[$i]}" | bc) - if [ "$result" -eq 1 ]; then - log_info "This configuration can make your storage nodes be frozen after running" - log_err "Only $cur_avail GB available in ${path_arr[$i]}, but set UseSpace: ${space_arr[$i]} GB in: $config_path" - exit 1 - fi - done +# Validates that a value is an integer (positive, negative, or zero). +is_int() { + [[ "$1" =~ ^-?[0-9]+$ ]] } -is_cacher_workpath_valid() { - local enableCacher - enableCacher=$(yq eval '.cacher.enable' "$config_path") - if [ "$enableCacher" != "true" ]; then - return - fi - local work_path - work_path=$(yq eval '.cacher.WorkSpace' "$config_path") - if [ ! -d "$work_path" ]; then - log_err "Cacher Work Path does not exist: $work_path" - exit 1 - fi - # Check available space greater than 16 GiB - local cur_avail - cur_avail=$(df -B1G "$work_path" | awk 'NR==2 {print $4}') - if [ "$cur_avail" -lt 16 ]; then - log_info "Please keep 16 GiB available storage space for cacher at least" +# Validates that a value is a number (integer or float). +is_num() { + if ! [[ "$1" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]; then + log_err "Invalid number format: '$1'. Please provide a valid number." exit 1 fi } -# https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository -add_docker_ubuntu_repo() { - # Add Docker's official GPG key: - sudo apt-get update - sudo apt-get install ca-certificates curl - sudo install -m 0755 -d /etc/apt/keyrings - sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc - sudo chmod a+r /etc/apt/keyrings/docker.asc - - # Add the repository to Apt sources: - echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | - sudo tee /etc/apt/sources.list.d/docker.list >/dev/null - sudo apt-get update -} - -# https://docs.docker.com/engine/install/centos/#set-up-the-repository -add_docker_centos_repo() { - sudo yum install -y yum-utils - sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo -} - -get_cur_ram() { - local cur_ram=0 - local ram_unit=$(sudo dmidecode -t memory | grep -v "No Module Installed" | grep -i size | awk '{print $3}' | grep -E "GB|MB" | head -n 1) - if [ "$ram_unit" == "MB" ]; then - for num in $(sudo dmidecode -t memory | grep -v "No Module Installed" | grep -i size | awk '{print $2}' | grep -o '[0-9]*'); do cur_ram=$((cur_ram + $num / 1000)); done - elif [ "$ram_unit" == "GB" ]; then - for num in $(sudo dmidecode -t memory | grep -v "No Module Installed" | grep -i size | awk '{print $2}' | grep -o '[0-9]*'); do cur_ram=$((cur_ram + $num)); done - else - log_err "RAM unit can not be recognized" +# Validates that a string matches an expected value. +is_str_equal() { + if [ "$1" != "$2" ]; then + log_err "Input error: '$1' does not match expected value '$2'." + exit 1 fi - echo $cur_ram # echo can return num > 255 } -get_cur_processors() { - local processors=$(grep -c ^processor /proc/cpuinfo) - echo $processors # echo can return num > 255 +# Validates that a name matches a given prefix (e.g., "miner" for "miner1"). +is_match_regex() { + local prefix="$1" + local name="$2" + if [[ ! "$name" =~ ^$prefix ]]; then + log_err "Invalid name: '$name'. It must start with '$prefix'." + exit 1 + fi } -mk_sminer_workdir() { - local disk_paths=$(yq eval '.miners[].diskPath' $config_path | xargs) - for disk_path in $disk_paths; do - sudo mkdir -p "$disk_path/miner" "$disk_path/storage" - done -} +# --- Miscellaneous --- -split_miners_config() { - local miners_num=$(get_miners_num) - for ((i = 0; i < miners_num; i++)); do - local get_miner_config_by_index="yq eval '.[$i]' $build_dir/miners/config.yaml" - local get_disk_path_by_index="yq eval '.miners[$i].diskPath' $config_path" - local each_path="$(eval "$get_disk_path_by_index")/miner/config.yaml" - if ! eval $get_miner_config_by_index >$each_path; then - log_err "Fail to generate file: $each_path" - exit 1 - else - log_success "miner configuration file has been generated at: $each_path" - fi - done +# Joins array elements with a separator. +# Usage: join_by ... +join_by() { + local IFS="$1" + shift + echo "$*" } -is_uint() { case $1 in '' | *[!0-9]*) return 1 ;; esac } -is_int() { case ${1#[-+]} in '' | *[!0-9]*) return 1 ;; esac } -is_unum() { case $1 in '' | . | *[!0-9.]* | *.*.*) return 1 ;; esac } -is_num() { - case ${1#[-+]} in '' | . | *[!0-9.]* | *.*.*) log_err "Parameter not a number" && exit 1 ;; esac -} +# Generates a random number within a given range. +rand() { + local min=$1 + local max=$2 + # Use /dev/urandom for better randomness if available + if [ -c /dev/urandom ]; then + head -c 4 /dev/urandom | od -An -tu4 | awk -v min="$min" -v max="$max" '{print ($1 % (max-min+1)) + min}' + else + # Fallback to date + echo $(( ( $(date +%s%N) % (max-min+1) ) + min )) + fi +} \ No newline at end of file diff --git a/scripts/version.sh b/scripts/version.sh index 05e4c6c..7e3c4d2 100644 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -1,39 +1,76 @@ #!/bin/bash +# +# CESS mineradm version information script +# This script displays version information for mineradm, its components, +# and the Docker images it uses. +# --- Strict Mode --- +set -o errexit +set -o nounset +set -o pipefail + +# --- Source Dependencies --- +# shellcheck source=scripts/utils.sh source /opt/cess/mineradm/scripts/utils.sh -version() { - printf "network version: %s\n" "$network_version" - printf "mineradm version: %s\n" "$mineradm_version" - printf "Mode: %s\n" ${mode} - printf "Profile: %s\n" ${profile} - inner_docker_version +# --- Functions --- - if [[ -f $config_path ]]; then - local ss=$(yq eval '.node.noWatchContainers //[] | join(", ")' $config_path) - if [[ -n ${ss// /} ]]; then - log_info "No auto upgrade service(s): $ss" - fi +# Shows version information for a specific Docker image. +# If the image is not found, it prints 'not found'. +# Usage: show_version [extra_docker_opts] +show_version() { + local prog_name="$1" + local image_name="$2" + local version_cmd="$3" + local extra_docker_opts="${4:-}" + local image_tag="$profile" + + local image_info + image_info=$(docker images --format "{{.ID}} {{.Tag}}" "$image_name" | grep "\b$image_tag$") + + local image_id="not found" + local version="not found" + + if [ -n "$image_info" ]; then + image_id=$(echo "$image_info" | awk '{print $1}') + # Run docker command to get the version, redirect stderr to /dev/null to hide errors if command fails + version=$(docker run --rm $extra_docker_opts "$image_name:$image_tag" "$version_cmd" 2>/dev/null || echo "error getting version") fi + + printf "%-20s %-30s %-20s\n" "$prog_name" "$version" "$image_id" } +# Displays versions of all relevant Docker images. inner_docker_version() { echo "----------------------------------------------------------------" - printf "Docker images:\n" - printf "%-20s %-30s %-20s\n" "Image" "Version" "Image ID" + printf "Docker Images:\n" + printf "%-20s %-30s %-20s\n" "IMAGE" "VERSION" "IMAGE ID" show_version "config-gen" "cesslab/config-gen" "version" show_version "chain" "cesslab/cess-chain" "--version" show_version "miner" "cesslab/cess-miner" "version" } -show_version() { - local prog_name=$1 - local image_name=$2 - local image_tag=$profile - local version_cmd=$3 - local extra_docker_opts=$4 - local image_info=$(docker images | grep '^\b'$image_name'\b ' | grep $image_tag) - local image_id=$(echo $image_info | awk '{printf $3}') - local version=$(docker run --rm $extra_docker_opts $image_name:$image_tag $version_cmd) - printf "%-20s %-30s %-20s\n" "$prog_name" "$version" "$image_id" +# Main function to display all version information. +version() { + printf "CESS Mineradm Version Information\n" + printf "---------------------------------\n" + printf "%-20s: %s\n" "Network" "$network_version" + printf "%-20s: %s\n" "Mineradm Version" "$mineradm_version" + printf "%-20s: %s\n" "Mode" "$(yq eval ".node.mode" "$config_path")" + printf "%-20s: %s\n" "Profile" "$profile" + + if [[ -f "$config_path" ]]; then + local no_watch_containers + no_watch_containers=$(yq eval '.node.noWatchContainers // [] | join(", ")' "$config_path") + if [[ -n "$no_watch_containers" ]]; then + log_info "No-watch containers: $no_watch_containers" + fi + fi + + inner_docker_version } + +# --- Main Execution --- +# Load the profile from config before running. +load_profile +version \ No newline at end of file