#!/bin/bash # ======================================== # Pezkuwi Validator Setup Script v3.0.0 # ======================================== # Multi-network validator setup for distributed deployment # Usage: sudo ./setup.sh # Example: sudo ./setup.sh beta_testnet 8 set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Function to print section headers print_header() { echo -e "\n${BLUE}========================================${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}========================================${NC}\n" } # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to check dependency and display status check_dependency() { local cmd=$1 local name=$2 local version_cmd=$3 if command_exists "$cmd"; then local version=$($version_cmd 2>&1 | head -n 1 || echo "unknown") echo -e "${GREEN}✓${NC} $name: ${GREEN}Installed${NC} ($version)" return 0 else echo -e "${RED}✗${NC} $name: ${RED}Not installed${NC}" return 1 fi } # Global variables for dependencies missing_deps=() RUST_MISSING=0 NODE_MISSING=0 # Parse and validate arguments parse_arguments() { print_header "Pezkuwi Validator Setup v3.0.0" # Check if running with sudo if [ "$EUID" -ne 0 ]; then echo -e "${RED}Error: This script must be run with sudo${NC}" echo "Usage: sudo ./setup.sh " exit 1 fi # Check arguments if [ $# -lt 2 ]; then echo -e "${RED}Error: Missing arguments${NC}" echo "" echo "Usage: sudo ./setup.sh " echo "" echo "Networks:" echo " beta_testnet - Beta testnet (8 validators)" echo " staging - Staging network (20 validators)" echo " mainnet - Production network (100 validators)" echo "" echo "Example: sudo ./setup.sh beta_testnet 8" exit 1 fi NETWORK=$1 VALIDATOR_NUM=$2 # Validate network case "$NETWORK" in beta_testnet) MAX_VALIDATORS=8 CHAIN_SPEC="pezkuwichain-beta-testnet" ;; staging) MAX_VALIDATORS=20 CHAIN_SPEC="pezkuwichain-staging" ;; mainnet) MAX_VALIDATORS=100 CHAIN_SPEC="pezkuwichain-mainnet" ;; *) echo -e "${RED}Error: Invalid network '$NETWORK'${NC}" echo "Valid networks: beta_testnet, staging, mainnet" exit 1 ;; esac # Validate validator number if ! [[ "$VALIDATOR_NUM" =~ ^[0-9]+$ ]]; then echo -e "${RED}Error: Validator number must be a positive integer${NC}" exit 1 fi if [ "$VALIDATOR_NUM" -lt 1 ] || [ "$VALIDATOR_NUM" -gt "$MAX_VALIDATORS" ]; then echo -e "${RED}Error: Validator number must be between 1 and $MAX_VALIDATORS for $NETWORK${NC}" exit 1 fi # Detect actual user (not root) ACTUAL_USER=${SUDO_USER:-$USER} ACTUAL_HOME=$(eval echo ~$ACTUAL_USER) # Set paths NETWORK_DIR="$SCRIPT_DIR/$NETWORK" VALIDATOR_DIR="$NETWORK_DIR/validators/validator$VALIDATOR_NUM" SDK_DIR="$ACTUAL_HOME/pezkuwi-sdk" FRONTEND_DIR="$ACTUAL_HOME/DKSweb" echo -e "${GREEN}Network:${NC} $NETWORK" echo -e "${GREEN}Validator:${NC} $VALIDATOR_NUM / $MAX_VALIDATORS" echo -e "${GREEN}User:${NC} $ACTUAL_USER" echo -e "${GREEN}Home:${NC} $ACTUAL_HOME" } # Check system dependencies check_dependencies() { print_header "Checking System Dependencies" missing_deps=() # Essential build tools if ! check_dependency "git" "Git" "git --version"; then missing_deps+=("git") fi if ! check_dependency "curl" "Curl" "curl --version"; then missing_deps+=("curl") fi if ! check_dependency "wget" "Wget" "wget --version"; then missing_deps+=("wget") fi # Build essentials if ! check_dependency "gcc" "GCC" "gcc --version"; then missing_deps+=("build-essential") fi if ! check_dependency "make" "Make" "make --version"; then missing_deps+=("build-essential") fi # Check pkg-config if ! check_dependency "pkg-config" "pkg-config" "pkg-config --version"; then missing_deps+=("pkg-config") fi # Check libssl-dev if ! dpkg -l | grep -q libssl-dev; then echo -e "${RED}✗${NC} libssl-dev: ${RED}Not installed${NC}" missing_deps+=("libssl-dev") else echo -e "${GREEN}✓${NC} libssl-dev: ${GREEN}Installed${NC}" fi # Check clang if ! check_dependency "clang" "Clang" "clang --version"; then missing_deps+=("clang") fi # Check Rust if ! su - $ACTUAL_USER -c 'command -v rustc' >/dev/null 2>&1; then echo -e "${RED}✗${NC} Rust: ${RED}Not installed${NC}" RUST_MISSING=1 else local rust_version=$(su - $ACTUAL_USER -c 'rustc --version' 2>&1 | head -n 1) echo -e "${GREEN}✓${NC} Rust: ${GREEN}Installed${NC} ($rust_version)" RUST_MISSING=0 fi # Check Cargo if ! su - $ACTUAL_USER -c 'command -v cargo' >/dev/null 2>&1; then RUST_MISSING=1 fi # Check Node.js if ! check_dependency "node" "Node.js" "node --version"; then NODE_MISSING=1 else NODE_MISSING=0 fi # Check npm if ! check_dependency "npm" "npm" "npm --version"; then NODE_MISSING=1 fi # Check systemd if ! check_dependency "systemctl" "systemd" "systemctl --version"; then echo -e "${YELLOW}Warning: systemd not found. Service management may not work.${NC}" fi # Check nginx if ! check_dependency "nginx" "Nginx" "nginx -v"; then missing_deps+=("nginx") fi } # Install missing dependencies install_dependencies() { print_header "Installing Missing Dependencies" echo "Updating package list..." apt-get update # Install system packages if [ ${#missing_deps[@]} -gt 0 ]; then echo -e "\n${YELLOW}Installing system packages:${NC} ${missing_deps[*]}" apt-get install -y "${missing_deps[@]}" echo -e "${GREEN}✓ System packages installed${NC}" fi # Install Rust if missing if [ $RUST_MISSING -eq 1 ]; then echo -e "\n${YELLOW}Installing Rust for user: $ACTUAL_USER${NC}" echo "This may take a few minutes. Please wait..." # Install Rust as the actual user su - $ACTUAL_USER -c 'curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --verbose' # Configure Rust toolchain echo -e "\n${YELLOW}Configuring Rust toolchain...${NC}" su - $ACTUAL_USER -c 'source $HOME/.cargo/env && rustup default stable' su - $ACTUAL_USER -c 'source $HOME/.cargo/env && rustup update' echo -e "\n${YELLOW}Adding WebAssembly target...${NC}" su - $ACTUAL_USER -c 'source $HOME/.cargo/env && rustup target add wasm32-unknown-unknown' echo -e "${GREEN}✓ Rust installed successfully for $ACTUAL_USER${NC}" else # Ensure wasm target is installed echo -e "\n${YELLOW}Ensuring wasm32-unknown-unknown target...${NC}" su - $ACTUAL_USER -c 'source $HOME/.cargo/env && rustup target add wasm32-unknown-unknown 2>/dev/null' || true echo -e "${GREEN}✓ WebAssembly target verified${NC}" fi # Install Node.js if missing if [ $NODE_MISSING -eq 1 ]; then echo -e "\n${YELLOW}Installing Node.js...${NC}" curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y nodejs echo -e "${GREEN}✓ Node.js installed${NC}" fi echo -e "\n${GREEN}✓ All dependencies installed!${NC}" } # Clone and build Pezkuwi SDK setup_sdk() { print_header "Setting up Pezkuwi SDK" if [ -d "$SDK_DIR" ]; then echo -e "${YELLOW}SDK directory already exists at $SDK_DIR${NC}" echo -e "${YELLOW}Updating from GitHub...${NC}" su - $ACTUAL_USER -c "cd $SDK_DIR && git fetch origin && git pull origin main" else echo "Cloning Pezkuwi SDK from GitHub..." echo "This may take several minutes for initial clone..." su - $ACTUAL_USER -c "cd $ACTUAL_HOME && git clone --depth 1 https://github.com/pezkuwichain/pezkuwi-sdk.git" fi echo -e "\n${YELLOW}Building Pezkuwi SDK (Release mode)...${NC}" echo "This will take 15-30 minutes. Please be patient..." echo "" # Build as actual user with proper environment su - $ACTUAL_USER -c "cd $SDK_DIR && source \$HOME/.cargo/env && cargo build --release" echo -e "\n${GREEN}✓ Pezkuwi SDK built successfully${NC}" echo "Binary location: $SDK_DIR/target/release/pezkuwi-node" } # Clone and build DKSweb frontend setup_frontend() { print_header "Setting up DKSweb Frontend" if [ -d "$FRONTEND_DIR" ]; then echo -e "${YELLOW}Frontend directory already exists at $FRONTEND_DIR${NC}" echo -e "${YELLOW}Updating from GitHub...${NC}" su - $ACTUAL_USER -c "cd $FRONTEND_DIR && git fetch origin && git pull origin main" else echo "Cloning DKSweb from GitHub..." su - $ACTUAL_USER -c "cd $ACTUAL_HOME && git clone --depth 1 https://github.com/pezkuwichain/DKSweb.git" fi echo -e "\n${YELLOW}Installing frontend dependencies...${NC}" su - $ACTUAL_USER -c "cd $FRONTEND_DIR && npm install" echo -e "\n${YELLOW}Building frontend...${NC}" su - $ACTUAL_USER -c "cd $FRONTEND_DIR && npm run build" echo -e "\n${GREEN}✓ DKSweb frontend built successfully${NC}" echo "Frontend build: $FRONTEND_DIR/dist" } # Get bootnode information for validators > 1 get_bootnode_info() { if [ "$VALIDATOR_NUM" -eq 1 ]; then echo -e "${GREEN}This is validator 1 (bootnode)${NC}" BOOTNODE_ARG="" return fi print_header "Bootnode Configuration" echo -e "${YELLOW}This validator needs to connect to validator $((VALIDATOR_NUM - 1))${NC}" echo "" echo "Please obtain the bootnode multiaddr from validator $((VALIDATOR_NUM - 1))" echo "The multiaddr format looks like:" echo "/ip4/192.168.1.100/tcp/30333/p2p/12D3KooWAbCdEf..." echo "" read -p "Enter bootnode multiaddr (or press Enter to skip): " BOOTNODE_ADDR if [ -n "$BOOTNODE_ADDR" ]; then BOOTNODE_ARG="--bootnodes $BOOTNODE_ADDR" echo -e "${GREEN}✓ Bootnode configured${NC}" else echo -e "${YELLOW}Warning: No bootnode specified. Node will run in isolation.${NC}" BOOTNODE_ARG="" fi } # Configure validator settings configure_validator() { print_header "Configuring Validator $VALIDATOR_NUM" # Calculate ports P2P_PORT=$((30333 + ($VALIDATOR_NUM - 1))) RPC_PORT=$((9944 + ($VALIDATOR_NUM - 1))) WS_PORT=$((9944 + ($VALIDATOR_NUM - 1))) PROMETHEUS_PORT=$((9615 + ($VALIDATOR_NUM - 1))) echo "Validator $VALIDATOR_NUM ports:" echo " P2P Port: $P2P_PORT" echo " RPC Port: $RPC_PORT" echo " WebSocket Port: $WS_PORT" echo " Prometheus Port: $PROMETHEUS_PORT" # Create validator directories mkdir -p "$VALIDATOR_DIR"/{config,data,keys,logs} # Create validator config file CONFIG_FILE="$VALIDATOR_DIR/config/validator.conf" cat > "$CONFIG_FILE" << EOF # Validator $VALIDATOR_NUM Configuration # Network: $NETWORK # Generated: $(date) VALIDATOR_NUM=$VALIDATOR_NUM VALIDATOR_NAME="validator-$VALIDATOR_NUM" NETWORK=$NETWORK CHAIN_SPEC=$CHAIN_SPEC P2P_PORT=$P2P_PORT RPC_PORT=$RPC_PORT WS_PORT=$WS_PORT PROMETHEUS_PORT=$PROMETHEUS_PORT DATA_DIR=$VALIDATOR_DIR/data KEYS_DIR=$VALIDATOR_DIR/keys LOGS_DIR=$VALIDATOR_DIR/logs SDK_PATH=$SDK_DIR FRONTEND_PATH=$FRONTEND_DIR ACTUAL_USER=$ACTUAL_USER EOF chown -R $ACTUAL_USER:$ACTUAL_USER "$VALIDATOR_DIR" echo -e "${GREEN}✓ Configuration file created${NC}" # Check for validator keys KEYS_SCRIPT="$VALIDATOR_DIR/config/keys.sh" if [ -f "$KEYS_SCRIPT" ]; then echo -e "\n${GREEN}✓ Validator keys found${NC}" echo "Keys file: $KEYS_SCRIPT" else echo -e "\n${YELLOW}Warning: No keys.sh found${NC}" echo "You will need to add validator keys to: $KEYS_SCRIPT" fi } # Setup systemd service setup_service() { print_header "Setting up Systemd Service" SERVICE_NAME="pezkuwi-validator-$NETWORK-$VALIDATOR_NUM" SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service" # Build node arguments NODE_ARGS="--base-path $VALIDATOR_DIR/data" NODE_ARGS="$NODE_ARGS --chain $CHAIN_SPEC" NODE_ARGS="$NODE_ARGS --validator" NODE_ARGS="$NODE_ARGS --name validator-$VALIDATOR_NUM" NODE_ARGS="$NODE_ARGS --port $P2P_PORT" NODE_ARGS="$NODE_ARGS --rpc-port $RPC_PORT" NODE_ARGS="$NODE_ARGS --prometheus-port $PROMETHEUS_PORT" NODE_ARGS="$NODE_ARGS --rpc-cors all" NODE_ARGS="$NODE_ARGS --rpc-external" NODE_ARGS="$NODE_ARGS --unsafe-force-node-key-generation" if [ -n "$BOOTNODE_ARG" ]; then NODE_ARGS="$NODE_ARGS $BOOTNODE_ARG" fi tee "$SERVICE_FILE" > /dev/null << EOF [Unit] Description=Pezkuwi Validator $VALIDATOR_NUM ($NETWORK) After=network.target [Service] Type=simple User=$ACTUAL_USER WorkingDirectory=$VALIDATOR_DIR EnvironmentFile=$VALIDATOR_DIR/config/validator.conf ExecStart=$SDK_DIR/target/release/pezkuwi $NODE_ARGS Restart=always RestartSec=10 StandardOutput=append:$VALIDATOR_DIR/logs/validator.log StandardError=append:$VALIDATOR_DIR/logs/validator-error.log [Install] WantedBy=multi-user.target EOF # Reload systemd systemctl daemon-reload echo -e "${GREEN}✓ Systemd service created${NC}" echo "Service name: $SERVICE_NAME" } # Setup nginx for frontend setup_nginx() { print_header "Setting up Nginx" NGINX_CONF="/etc/nginx/sites-available/pezkuwi-frontend" if [ -f "$NGINX_CONF" ]; then echo -e "${YELLOW}Nginx already configured${NC}" echo "Skipping nginx setup..." return fi tee "$NGINX_CONF" > /dev/null << EOF server { listen 80; listen [::]:80; server_name _; root $FRONTEND_DIR/dist; index index.html; location / { try_files \$uri \$uri/ /index.html; } location /api { proxy_pass http://localhost:$RPC_PORT; proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host \$host; } } EOF # Enable site ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/ # Remove default site if exists rm -f /etc/nginx/sites-enabled/default # Test nginx configuration nginx -t # Restart nginx systemctl restart nginx systemctl enable nginx echo -e "${GREEN}✓ Nginx configured and restarted${NC}" } # Insert validator keys insert_keys() { KEYS_SCRIPT="$VALIDATOR_DIR/config/keys.sh" if [ ! -f "$KEYS_SCRIPT" ]; then echo -e "\n${YELLOW}Skipping key insertion - no keys.sh file found${NC}" return fi print_header "Inserting Validator Keys" # Source the keys source "$KEYS_SCRIPT" echo "Waiting for node to start..." sleep 5 # Insert each key type echo "Inserting BABE keys..." curl -s -H "Content-Type: application/json" \ -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"author_insertKey\",\"params\":[\"babe\",\"$BABE_SEED\",\"$BABE_PUBLIC_KEY\"]}" \ http://127.0.0.1:$RPC_PORT > /dev/null echo "Inserting GRANDPA keys..." curl -s -H "Content-Type: application/json" \ -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"author_insertKey\",\"params\":[\"gran\",\"$GRAN_SEED\",\"$GRAN_PUBLIC_KEY\"]}" \ http://127.0.0.1:$RPC_PORT > /dev/null echo "Inserting PARA keys..." curl -s -H "Content-Type: application/json" \ -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"author_insertKey\",\"params\":[\"para\",\"$PARA_SEED\",\"$PARA_PUBLIC_KEY\"]}" \ http://127.0.0.1:$RPC_PORT > /dev/null echo "Inserting ASGN keys..." curl -s -H "Content-Type: application/json" \ -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"author_insertKey\",\"params\":[\"asgn\",\"$ASGN_SEED\",\"$ASGN_PUBLIC_KEY\"]}" \ http://127.0.0.1:$RPC_PORT > /dev/null echo "Inserting AUDI keys..." curl -s -H "Content-Type: application/json" \ -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"author_insertKey\",\"params\":[\"audi\",\"$AUDI_SEED\",\"$AUDI_PUBLIC_KEY\"]}" \ http://127.0.0.1:$RPC_PORT > /dev/null echo "Inserting BEEF keys..." curl -s -H "Content-Type: application/json" \ -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"author_insertKey\",\"params\":[\"beef\",\"$BEEF_SEED\",\"$BEEF_PUBLIC_KEY\"]}" \ http://127.0.0.1:$RPC_PORT > /dev/null echo -e "${GREEN}✓ All keys inserted${NC}" } # Get peer ID for bootnode get_peer_id() { if [ "$VALIDATOR_NUM" -ne 1 ]; then return fi print_header "Bootnode Information" echo "Waiting for node to generate peer ID..." sleep 10 echo -e "\n${GREEN}This is validator 1 (bootnode)${NC}" echo "Other validators will need this information to connect." echo "" echo "To get your peer ID, check the validator logs:" echo -e "${BLUE}tail -f $VALIDATOR_DIR/logs/validator.log${NC}" echo "" echo "Look for a line containing: 'Local node identity is: '" echo "" echo "Your bootnode multiaddr will be:" echo "/ip4//tcp/$P2P_PORT/p2p/" } # Final instructions show_final_instructions() { print_header "Setup Complete!" echo -e "${GREEN}Validator $VALIDATOR_NUM is ready!${NC}\n" echo "Next steps:" echo "" echo "1. Start the validator:" echo -e " ${BLUE}sudo systemctl start pezkuwi-validator-$NETWORK-$VALIDATOR_NUM${NC}" echo "" echo "2. Check validator status:" echo -e " ${BLUE}sudo systemctl status pezkuwi-validator-$NETWORK-$VALIDATOR_NUM${NC}" echo "" echo "3. View logs:" echo -e " ${BLUE}tail -f $VALIDATOR_DIR/logs/validator.log${NC}" echo "" echo "4. Enable auto-start on boot:" echo -e " ${BLUE}sudo systemctl enable pezkuwi-validator-$NETWORK-$VALIDATOR_NUM${NC}" echo "" if [ "$VALIDATOR_NUM" -eq 1 ]; then echo "5. Get bootnode peer ID:" echo -e " ${BLUE}After starting, check logs for 'Local node identity'${NC}" echo -e " ${BLUE}Share this with other validators${NC}" echo "" fi echo "6. Access frontend:" echo -e " ${BLUE}http://localhost${NC} (local)" echo -e " ${BLUE}http://YOUR_SERVER_IP${NC} (network)" echo "" if [ ! -f "$VALIDATOR_DIR/config/keys.sh" ]; then echo -e "${YELLOW}IMPORTANT: Add validator keys to:${NC}" echo -e "${YELLOW}$VALIDATOR_DIR/config/keys.sh${NC}" echo "" fi echo -e "${GREEN}Installation completed successfully!${NC}" } # Main function main() { # Parse and validate arguments parse_arguments "$@" # Check dependencies check_dependencies || true # Install missing dependencies if [ ${#missing_deps[@]} -gt 0 ] || [ $RUST_MISSING -eq 1 ] || [ $NODE_MISSING -eq 1 ]; then echo -e "\n${YELLOW}Installing missing dependencies...${NC}" install_dependencies else echo -e "\n${GREEN}✓ All dependencies already installed${NC}" fi # Setup SDK setup_sdk # Setup frontend setup_frontend # Get bootnode info (for validators > 1) get_bootnode_info # Configure validator configure_validator # Setup systemd service setup_service # Setup nginx setup_nginx # Show final instructions show_final_instructions echo -e "\n${BLUE}========================================${NC}" echo -e "${BLUE}Setup script completed${NC}" echo -e "${BLUE}========================================${NC}\n" } # Run main function main "$@"