#!/bin/bash # Minecraft Everywhere - One-liner Deployment Script # Repository: https://github.com/zardoy/minecraft-everywhere set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' # No Color # Configuration REPO_URL="https://github.com/zardoy/minecraft-web-client" API_URL="https://api.github.com/repos/zardoy/minecraft-web-client" INSTALL_DIR="/opt/minecraft-web-client" STATIC_DIR="/var/www/html/minecraft" SERVICE_NAME="mwc-server" # Utility functions print_header() { echo -e "${PURPLE}" echo "███╗ ███╗ ██████╗██████╗ █████╗ ███████╗████████╗" echo "████╗ ████║██╔════╝██╔══██╗██╔══██╗██╔════╝╚══██╔══╝" echo "██╔████╔██║██║ ██████╔╝███████║█████╗ ██║ " echo "██║╚██╔╝██║██║ ██╔══██╗██╔══██║██╔══╝ ██║ " echo "██║ ╚═╝ ██║╚██████╗██║ ██║██║ ██║██║ ██║ " echo "╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ " echo -e "${NC}" echo -e "${CYAN}🎮 MCRAFT.FUN Project: Minecraft Everywhere - Self-hosted Web Client Deployment${NC}" echo -e "${WHITE}Repository: https://github.com/zardoy/minecraft-everywhere${NC}" echo "" } print_step() { echo -e "${GREEN}[STEP]${NC} $1" } print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARN]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } # Check if running as root check_root() { if [[ $EUID -eq 0 ]]; then print_warning "Running as root. This is not recommended for security reasons." while true; do read -p "Continue anyway? [y/N] " yn case $yn in [Yy]* ) break;; [Nn]* | "" ) exit 1;; * ) echo "Please answer y or n.";; esac done fi } # Check if running interactively check_interactive() { if [[ ! -t 0 ]]; then print_error "This script must be run interactively." print_info "Please download and run the script directly:" print_info " curl -O https://raw.githubusercontent.com/zardoy/minecraft-everywhere/main/deploy.sh" print_info " chmod +x deploy.sh" print_info " ./deploy.sh" exit 1 fi } # Check system requirements check_system() { print_step "Checking system requirements..." # Check OS if [[ "$OSTYPE" != "linux-gnu"* ]]; then print_error "This script is designed for Linux systems only." exit 1 fi # Check required tools for tool in curl wget; do if ! command -v $tool &> /dev/null; then print_error "$tool is required but not installed." exit 1 fi done # Check for unzip and auto-install on Ubuntu/Debian if ! command -v unzip &> /dev/null; then print_warning "unzip is not installed. Attempting to install..." # Detect package manager and install unzip if command -v apt-get &> /dev/null; then print_info "Installing unzip using apt-get..." sudo apt-get update sudo apt-get install -y unzip elif command -v yum &> /dev/null; then print_info "Installing unzip using yum..." sudo yum install -y unzip elif command -v dnf &> /dev/null; then print_info "Installing unzip using dnf..." sudo dnf install -y unzip elif command -v zypper &> /dev/null; then print_info "Installing unzip using zypper..." sudo zypper install -y unzip else print_error "Could not detect package manager. Please install unzip manually." exit 1 fi # Verify installation if ! command -v unzip &> /dev/null; then print_error "Failed to install unzip. Please install it manually." exit 1 fi print_success "unzip installed successfully." fi print_success "System requirements met." } # Interactive menu selection show_menu() { echo -e "${WHITE}Choose Deployment Type:${NC}" echo -e " ${GREEN}1)${NC} Minecraft Web Client" echo -e " ${YELLOW}2)${NC} Minecraft Websocket Proxy (coming soon)" echo -e " ${YELLOW}3)${NC} Pixel Client 1.12.2 (coming soon)" echo "" } show_deployment_menu() { echo -e "${WHITE}How to deploy?${NC}" echo -e " ${GREEN}1)${NC} Deploy Outside Docker" echo -e " ${YELLOW}2)${NC} Deploy using Docker (coming soon)" echo "" } show_hosting_menu() { echo -e "${WHITE}What to provide?${NC}" echo -e " ${GREEN}1)${NC} Node.js proxy server hosting (with PM2)" echo -e " ${GREEN}2)${NC} Only static files (Apache/Nginx)" echo "" } # Get latest release info get_latest_release() { print_step "Fetching latest release information..." LATEST_RELEASE=$(curl -s "$API_URL/releases/latest") if [[ $? -ne 0 ]]; then print_error "Failed to fetch release information." exit 1 fi LATEST_VERSION=$(echo "$LATEST_RELEASE" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') DOWNLOAD_URL=$(echo "$LATEST_RELEASE" | grep '"browser_download_url":.*self-host\.zip"' | sed -E 's/.*"([^"]+)".*/\1/') if [[ -z "$DOWNLOAD_URL" ]]; then print_error "Could not find self-host.zip in latest release." exit 1 fi print_info "Latest version: $LATEST_VERSION" print_info "Download URL: $DOWNLOAD_URL" } # Download and extract release download_release() { print_step "Downloading and extracting release..." TEMP_DIR=$(mktemp -d) cd "$TEMP_DIR" wget -q --show-progress "$DOWNLOAD_URL" -O self-host.zip if [[ $? -ne 0 ]]; then print_error "Failed to download release." rm -rf "$TEMP_DIR" exit 1 fi unzip -q self-host.zip if [[ $? -ne 0 ]]; then print_error "Failed to extract release." rm -rf "$TEMP_DIR" exit 1 fi print_success "Release downloaded and extracted." } # Deploy static files deploy_static() { print_step "Deploying static files..." sudo mkdir -p "$STATIC_DIR" sudo cp -r dist/* "$STATIC_DIR/" sudo chown -R www-data:www-data "$STATIC_DIR" 2>/dev/null || true print_success "Static files deployed to $STATIC_DIR" print_info "Configure your web server to serve files from $STATIC_DIR" } # Check Node.js version check_nodejs() { if ! command -v node &> /dev/null; then print_warning "Node.js not found. Installing..." curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs fi NODE_VERSION=$(node --version | sed 's/v//') MAJOR_VERSION=$(echo $NODE_VERSION | cut -d. -f1) if [[ $MAJOR_VERSION -lt 18 ]]; then print_error "Node.js 18.x or higher is required. Current version: $NODE_VERSION" exit 1 fi print_success "Node.js $NODE_VERSION is installed." } # Check and install PM2 check_pm2() { if ! command -v pm2 &> /dev/null; then print_warning "PM2 not found. Installing..." sudo npm install -g pm2 fi print_success "PM2 is installed." } # Find first available port starting from given port find_available_port() { local port=$1 while netstat -tna | grep -q ":${port}.*LISTEN"; do port=$((port + 1)) done echo $port } # Deploy Node.js server deploy_nodejs() { print_step "Deploying Node.js server..." check_nodejs check_pm2 # Find available port, starting from 8080 DEFAULT_PORT=$(find_available_port 8080) echo read -p "Enter port number for Node.js server [$DEFAULT_PORT]: " PORT PORT=${PORT:-$DEFAULT_PORT} # Validate port number if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then print_error "Invalid port number. Using default port $DEFAULT_PORT" PORT=$DEFAULT_PORT fi # Check if chosen port is available if [ "$PORT" != "$DEFAULT_PORT" ] && netstat -tna | grep -q ":${PORT}.*LISTEN"; then print_warning "Port $PORT is already in use. Using next available port." PORT=$(find_available_port $PORT) fi print_info "Using port: $PORT" sudo mkdir -p "$INSTALL_DIR" sudo cp -r * "$INSTALL_DIR/" cd "$INSTALL_DIR" # Install dependencies if package.json exists if [[ -f package.json ]]; then sudo npm install --production fi # Stop existing service if running pm2 delete "$SERVICE_NAME" 2>/dev/null || true # Start the service with specified port pm2 start server.js --name "$SERVICE_NAME" -- --port $PORT pm2 startup pm2 save print_success "Node.js server deployed and started with PM2." print_info "Service name: $SERVICE_NAME" print_info "Installation directory: $INSTALL_DIR" print_info "Server is running on: http://localhost:$PORT" } # Setup automatic updates setup_auto_update() { print_step "Setting up automatic updates..." # Create version directory for both deployment types if [[ "$DEPLOYMENT_TYPE" == "static" ]]; then VERSION_DIR="/var/lib/minecraft-web-client" sudo mkdir -p "$VERSION_DIR" else VERSION_DIR="$INSTALL_DIR" fi # Create update script UPDATE_SCRIPT="/usr/local/bin/mwc-update.sh" sudo tee "$UPDATE_SCRIPT" > /dev/null << EOF #!/bin/bash # Minecraft Web Client Auto-Update Script API_URL="$API_URL" INSTALL_DIR="$INSTALL_DIR" STATIC_DIR="$STATIC_DIR" SERVICE_NAME="$SERVICE_NAME" DEPLOYMENT_TYPE="$DEPLOYMENT_TYPE" VERSION_DIR="$VERSION_DIR" # Get current and latest versions if [[ -f "\$VERSION_DIR/version" ]]; then CURRENT_VERSION=\$(cat "\$VERSION_DIR/version") else CURRENT_VERSION="unknown" fi LATEST_RELEASE=\$(curl -s "\$API_URL/releases/latest") LATEST_VERSION=\$(echo "\$LATEST_RELEASE" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE" | grep '"browser_download_url":.*self-host\.zip"' | sed -E 's/.*"([^"]+)".*/\1/') RELEASE_BODY=\$(echo "\$LATEST_RELEASE" | grep '"body":' | sed -E 's/.*"body":"([^"]+)".*/\1/') if [[ "\$CURRENT_VERSION" != "\$LATEST_VERSION" ]]; then echo "Updating from \$CURRENT_VERSION to \$LATEST_VERSION" # Download and extract TEMP_DIR=\$(mktemp -d) cd "\$TEMP_DIR" wget -q "\$DOWNLOAD_URL" -O self-host.zip unzip -q self-host.zip # Update files based on deployment type if [[ "\$DEPLOYMENT_TYPE" == "static" ]]; then # Backup config and custom files if [ -f "\$STATIC_DIR/config.json" ]; then cp "\$STATIC_DIR/config.json" "\$TEMP_DIR/" fi # Backup all custom* files find "\$STATIC_DIR" -name "custom*" -exec cp {} "\$TEMP_DIR/" \; # Update files sudo cp -r dist/* "\$STATIC_DIR/" # Restore config and custom files if [ -f "\$TEMP_DIR/config.json" ]; then sudo cp "\$TEMP_DIR/config.json" "\$STATIC_DIR/" fi # Restore all custom* files find "\$TEMP_DIR" -name "custom*" -exec sudo cp {} "\$STATIC_DIR/" \; sudo chown -R www-data:www-data "\$STATIC_DIR" 2>/dev/null || true else sudo cp -r dist/* "\$INSTALL_DIR/dist/" # Check if server restart is required if echo "\$RELEASE_BODY" | grep -q "requires server restart"; then pm2 restart "\$SERVICE_NAME" 2>/dev/null || true fi fi # Update version file echo "\$LATEST_VERSION" | sudo tee "\$VERSION_DIR/version" > /dev/null echo "Update completed successfully" rm -rf "\$TEMP_DIR" else echo "Already up to date (\$CURRENT_VERSION)" fi EOF sudo chmod +x "$UPDATE_SCRIPT" # Save current version echo "$LATEST_VERSION" | sudo tee "$VERSION_DIR/version" > /dev/null # Setup cron job CRON_JOB="0 2 * * * $UPDATE_SCRIPT >> /var/log/mwc-update.log 2>&1" # Create a temporary file for the new crontab TEMP_CRONTAB=$(mktemp) # Get existing crontab if any, excluding old update script entries (crontab -l 2>/dev/null || echo "") | grep -v mwc-update > "$TEMP_CRONTAB" # Add new update script entry echo "$CRON_JOB" >> "$TEMP_CRONTAB" # Install new crontab crontab "$TEMP_CRONTAB" # Clean up rm -f "$TEMP_CRONTAB" print_success "Automatic updates configured." print_info "Updates will check daily at 2:00 AM" print_info "Update logs: /var/log/mwc-update.log" } # Check and configure Apache configure_apache() { # Ask for domain configuration echo read -p "Enter domain name to configure Apache (leave empty to skip): " DOMAIN_NAME if [[ -n "$DOMAIN_NAME" ]]; then print_step "Configuring Apache web server..." # Check if Apache is installed if ! command -v apache2 &> /dev/null; then print_warning "Apache not found. Installing..." sudo apt-get update sudo apt-get install -y apache2 fi # Enable required modules sudo a2enmod proxy sudo a2enmod proxy_http sudo a2enmod proxy_wstunnel sudo a2enmod rewrite sudo a2enmod ssl # Create Apache configuration file APACHE_CONF="/etc/apache2/sites-available/${DOMAIN_NAME}.conf" if [[ "$DEPLOYMENT_TYPE" == "static" ]]; then # Static files configuration sudo tee "$APACHE_CONF" > /dev/null << EOF ServerName ${DOMAIN_NAME} ServerAdmin webmaster@localhost DocumentRoot ${STATIC_DIR} Options Indexes FollowSymLinks AllowOverride All Require all granted ErrorLog \${APACHE_LOG_DIR}/${DOMAIN_NAME}_error.log CustomLog \${APACHE_LOG_DIR}/${DOMAIN_NAME}_access.log combined EOF else # Node.js proxy configuration sudo tee "$APACHE_CONF" > /dev/null << EOF ServerName ${DOMAIN_NAME} ServerAdmin webmaster@localhost # Enable proxy settings ProxyPreserveHost On # Main application proxy rules ProxyPass / http://localhost:${PORT}/ ProxyPassReverse / http://localhost:${PORT}/ RewriteEngine On RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/?(.*) "ws://localhost:${PORT}/\$1" [P,L] # Log files ErrorLog \${APACHE_LOG_DIR}/${DOMAIN_NAME}_error.log CustomLog \${APACHE_LOG_DIR}/${DOMAIN_NAME}_access.log combined EOF fi # Enable the site sudo a2ensite "${DOMAIN_NAME}.conf" sudo a2dissite 000-default.conf # Setup SSL with Certbot echo read -p "Would you like to setup SSL with Let's Encrypt? (skip if using Cloudflare) [y/N]: " setup_ssl setup_ssl=${setup_ssl:-N} if [[ $setup_ssl =~ ^[Yy]$ ]]; then # Check if certbot is installed if ! command -v certbot &> /dev/null; then print_warning "Certbot not found. Installing..." sudo apt-get update sudo apt-get install -y certbot python3-certbot-apache fi # Get SSL certificate print_step "Obtaining SSL certificate from Let's Encrypt..." sudo certbot --apache -d "$DOMAIN_NAME" --non-interactive --agree-tos --email "webmaster@${DOMAIN_NAME}" --redirect print_success "SSL certificate installed successfully" fi # Restart Apache sudo systemctl restart apache2 print_success "Apache configured successfully for ${DOMAIN_NAME}" if [[ "$DEPLOYMENT_TYPE" == "static" ]]; then print_info "Static files will be served from ${STATIC_DIR}" else print_info "Requests will be proxied to Node.js server on port ${PORT}" fi else print_info "Skipping Apache domain configuration" fi } # Share proxy URL to public listing share_proxy() { if [[ "$DEPLOYMENT_TYPE" != "nodejs" ]]; then return 0 fi echo read -p "Would you like to share your deployed proxy URL in the public listing? [Y/n]: " share_choice share_choice=${share_choice:-Y} if [[ $share_choice =~ ^[Yy]$ ]]; then print_step "Sharing proxy URL to public listing..." # Get the public URL (domain or localhost with port) if [[ -n "$DOMAIN_NAME" ]]; then PROXY_URL="https://${DOMAIN_NAME}" else PROXY_URL="http://localhost:${PORT}" fi # Make request to share the proxy SHARE_RESPONSE=$(curl -s -w "%{http_code}" -X POST "https://proxy.mcraft.fun/share-proxy" \ -H "Content-Type: application/json" \ -d "{\"url\":\"${PROXY_URL}\"}" 2>/dev/null) HTTP_CODE="${SHARE_RESPONSE: -3}" if [[ "$HTTP_CODE" == "200" ]]; then print_success "Proxy URL successfully shared to public listing!" print_info "Your proxy is now available at: $PROXY_URL" else print_warning "Failed to share proxy URL (HTTP $HTTP_CODE). This won't affect your deployment." print_info "Your proxy is still available at: $PROXY_URL" fi else print_info "Proxy URL not shared to public listing." fi } # Cleanup cleanup() { if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" fi } # Setup cron job setup_cron() { set -x # Enable debug output print_step "Setting up cron job..." CRON_JOB="0 2 * * * /usr/local/bin/mwc-update.sh >> /var/log/mwc-update.log 2>&1" # Create a temporary file for the new crontab TEMP_CRONTAB=$(mktemp) if [ $? -ne 0 ]; then print_error "Failed to create temporary file" return 1 fi print_info "Created temp file: $TEMP_CRONTAB" # Get existing crontab if any, excluding old update script entries # First save current crontab or empty string if none exists (crontab -l 2>/dev/null || echo "") > "$TEMP_CRONTAB" if [ $? -ne 0 ]; then print_error "Failed to get current crontab" rm -f "$TEMP_CRONTAB" return 1 fi # Remove any existing update entries if [ -s "$TEMP_CRONTAB" ]; then grep -v "mwc-update" "$TEMP_CRONTAB" > "$TEMP_CRONTAB.tmp" && mv "$TEMP_CRONTAB.tmp" "$TEMP_CRONTAB" || true fi print_info "Current crontab saved (excluding old update entries)" # Add new update script entry echo "$CRON_JOB" >> "$TEMP_CRONTAB" if [ $? -ne 0 ]; then print_error "Failed to add new cron job" rm -f "$TEMP_CRONTAB" return 1 fi print_info "Added new cron job entry" # Show what we're about to install print_info "New crontab contents:" cat "$TEMP_CRONTAB" # Install new crontab crontab "$TEMP_CRONTAB" if [ $? -ne 0 ]; then print_error "Failed to install new crontab" rm -f "$TEMP_CRONTAB" return 1 fi print_info "Installed new crontab" # Clean up rm -f "$TEMP_CRONTAB" set +x # Disable debug output print_success "Cron job setup completed." print_info "Updates will check daily at 2:00 AM" print_info "Update logs: /var/log/mwc-update.log" return 0 } # Main deployment flow main() { trap cleanup EXIT # Handle --update-cron flag if [[ "$1" == "--update-cron" ]]; then print_header setup_cron exit $? fi print_header check_interactive check_root check_system # Main menu show_menu while true; do read -p "Select option [1]: " choice choice=${choice:-1} case $choice in 1) print_success "Selected: Minecraft Web Client" break ;; 2|3) print_warning "This option is coming soon!" ;; *) print_error "Invalid option. Please select 1." ;; esac done echo # Deployment method menu show_deployment_menu while true; do read -p "Select deployment method [1]: " deploy_choice deploy_choice=${deploy_choice:-1} case $deploy_choice in 1) print_success "Selected: Deploy Outside Docker" break ;; 2) print_warning "Docker deployment is coming soon!" ;; *) print_error "Invalid option. Please select 1." ;; esac done echo # Hosting type menu show_hosting_menu while true; do read -p "Select hosting type [1]: " hosting_choice hosting_choice=${hosting_choice:-1} case $hosting_choice in 1) DEPLOYMENT_TYPE="nodejs" print_success "Selected: Node.js proxy server hosting" break ;; 2) DEPLOYMENT_TYPE="static" print_success "Selected: Static files hosting" break ;; *) print_error "Invalid option. Please select 1 or 2." ;; esac done echo # Auto-update option read -p "Enable automatic updates from GitHub releases? [Y/n]: " auto_update auto_update=${auto_update:-Y} if [[ $auto_update =~ ^[Yy]$ ]]; then ENABLE_AUTO_UPDATE=true print_success "Automatic updates enabled" else ENABLE_AUTO_UPDATE=false print_info "Automatic updates disabled" fi echo # Execute deployment get_latest_release download_release if [[ "$DEPLOYMENT_TYPE" == "static" ]]; then deploy_static else deploy_nodejs fi if [[ "$ENABLE_AUTO_UPDATE" == true ]]; then setup_auto_update fi echo # almost done print_success "Almost done..." if [[ "$DEPLOYMENT_TYPE" == "static" ]]; then print_info "📁 Static files are available at: $STATIC_DIR" print_info "🌐 Configure your web server to serve these files" else print_info "🚀 Node.js server is running with PM2" print_info "📊 Check status with: pm2 status" print_info "📝 View logs with: pm2 logs $SERVICE_NAME" print_info "🔗 The server is running on: http://localhost:$PORT" fi if [[ "$ENABLE_AUTO_UPDATE" == true ]]; then print_info "🔄 Automatic updates are configured" print_info "📅 Updates check daily at 2:00 AM" fi echo # Configure Apache as the last step configure_apache # Share proxy URL if Node.js deployment share_proxy echo print_success "🎉 Deployment completed successfully! 🎮 Happy Gaming! ⛏️" } # Run main function main "$@"