├── LICENSE ├── README.md └── erpnext_install.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Flexcom Systems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unattended Install Script for ERPNext 2 | Unattended script for ERPNext installation (Supports Versions 13, 14, 15, and Develop). 3 | 4 | This is a comprehensive, interactive script for installing ERPNext Versions 13, 14, 15, and the develop branch. You can set up either development or production environments with enhanced features including intelligent additional apps installation, comprehensive logging, and smart branch detection. 5 | 6 | ## 🆕 What's New in This Version 7 | 8 | ### Enhanced Features 9 | - **Develop Branch Support**: Added comprehensive warnings and support for the bleeding-edge develop branch 10 | - **Intelligent Additional Apps Installation**: Automated discovery and installation of 50+ compatible Frappe apps 11 | - **Smart Branch Detection**: Automatically detects and uses the best available branch for each app 12 | - **Improved Error Handling**: Multiple installation strategies and graceful fallbacks 13 | - **Better User Experience**: Clearer prompts, progress indicators, and informative messages 14 | 15 | ### Major Additions 16 | - **Additional Apps Marketplace**: Browse and install from 50+ curated Frappe applications 17 | - **Compatibility Checking**: Automatic validation for ERPNext v15/develop (pyproject.toml requirements) 18 | - **Branch Intelligence**: Frappe apps use appropriate version branches, third-party apps use default branches 19 | - **Installation Strategies**: Multiple fallback methods for app installation (setup.py extraction, name transformations, directory scanning) 20 | 21 | ## 🔧 How To Use 22 | 23 | ### Before Installation 24 | 25 | Make sure you install the latest package versions by updating system packages if you are running this script on a fresh Ubuntu machine. 26 | 27 | ```bash 28 | sudo apt update && sudo apt -y upgrade 29 | ``` 30 | and then reboot your machine 31 | 32 | ### ⚠️ Important! 33 | Do not run this script as root as it will fail when setting up the site. If your VPS default user is root, create a non-root user by following these steps: 34 | 35 | 1. Create a new user (you can change frappeuser to your preferred user name): 36 | ```bash 37 | sudo adduser frappeuser 38 | Full Name []: frappeuser 39 | Room Number []: 40 | Work Phone []: 41 | Home Phone []: 42 | Other []: 43 | Is the information correct? [Y] 44 | ``` 45 | 2. Add the user to sudoers: 46 | ```bash 47 | usermod -aG sudo frappeuser 48 | ``` 49 | 3. Switch to created user: 50 | ```bash 51 | su frappeuser 52 | ``` 53 | 4. Ensure you're on created user's home directory: 54 | ```bash 55 | cd /home/frappeuser 56 | ``` 57 | 5. Continue with the next steps below. 58 | 59 | ### Installation Steps 60 | 61 | 1. Clone the Repository: 62 | ```bash 63 | git clone https://github.com/flexcomng/erpnext_quick_install.git 64 | ``` 65 | 2. Navigate to the folder: 66 | ```bash 67 | cd erpnext_quick_install 68 | ``` 69 | 3. Make the script executable: 70 | ```bash 71 | chmod +x erpnext_install.sh 72 | ``` 73 | 4. Run the script: 74 | ```bash 75 | source erpnext_install.sh 76 | ``` 77 | 78 | ## 🖥️ Compatibility 79 | 80 | **Supported Operating Systems:** 81 | - Ubuntu 24.04 LTS 82 | - Ubuntu 23.04 LTS 83 | - Ubuntu 22.04 LTS 84 | - Ubuntu 20.04 LTS 85 | - Debian 12 (Bookworm) 86 | - Debian 11 (Bullseye) 87 | - Debian 10 (Buster) 88 | 89 | **Version-Specific Requirements:** 90 | - **ERPNext v15/Develop**: Ubuntu 22.04+ or Debian 12+ only 91 | - **ERPNext v13/v14**: Ubuntu 22.04 and below, Debian 11 and below 92 | 93 | ## 🚨 Important Disclaimers 94 | 95 | ### Version Compatibility & Installation Conflicts 96 | 97 | **⚠️ CRITICAL: Different ERPNext versions should NOT be installed on the same server!** 98 | 99 | Installing multiple ERPNext versions (e.g., v15 + develop, or v14 + v15) on the same server can cause: 100 | - **Redis port conflicts** (develop uses ports 11000, 12000, 13000 vs standard 6379) 101 | - **Node.js version conflicts** (v13/v14 use Node 16, v15/develop use Node 18/20) 102 | - **Supervisor configuration conflicts** (different service management approaches) 103 | - **Database schema incompatibilities** (version-specific database changes) 104 | - **Python dependency conflicts** (different package version requirements) 105 | - **System instability and service failures** 106 | 107 | **Recommended Approach:** 108 | - **One ERPNext version per server/container** 109 | - **Use separate servers** for production and development environments 110 | - **Backup and completely remove** existing installations before installing different versions 111 | - **Use Docker containers** for isolated testing of different versions 112 | 113 | The script automatically detects existing installations and warns about potential conflicts. 114 | 115 | ### Develop Branch Warning 116 | The **develop branch** contains bleeding-edge, experimental code that: 117 | - Changes daily and may be unstable 118 | - Can cause data corruption or system crashes 119 | - Is NOT suitable for production or important data 120 | - Has limited community support 121 | 122 | **Recommended for**: Experienced developers testing new features 123 | **Better alternatives**: Version 15 (stable) or Version 14 (proven) 124 | 125 | ### Additional Apps Installation 126 | When installing additional Frappe apps, please note: 127 | - App compatibility may vary between ERPNext versions 128 | - Some apps might fail to install due to version mismatches or missing dependencies 129 | - Third-party apps use their own versioning schemes 130 | - Installation success is not guaranteed for all environments 131 | 132 | ## 📦 Additional Apps Feature 133 | 134 | ### What's Included 135 | - **50+ Curated Apps**: Access to a comprehensive collection of Frappe applications 136 | - **Official Frappe Apps**: HRMS, CRM, LMS, Insights, Builder, Drive, Helpdesk, and more 137 | - **Third-Party Apps**: Community-developed applications for various business needs 138 | - **Smart Selection**: Interactive dialog for easy app selection 139 | - **Compatibility Checking**: Automatic validation for ERPNext v15/develop requirements 140 | 141 | ### How It Works 142 | 1. **Repository Scanning**: Fetches app list from awesome-frappe repository 143 | 2. **Compatibility Validation**: Checks for pyproject.toml requirements (v15/develop) 144 | 3. **Branch Detection**: Automatically selects the best available branch for each app 145 | 4. **Multiple Installation Strategies**: Falls back through different installation methods 146 | 5. **Comprehensive Reporting**: Detailed success/failure reporting with error details 147 | 148 | ### Supported App Categories 149 | - **HR & Payroll**: HRMS, Employee management tools 150 | - **Sales & CRM**: Customer relationship management, lead tracking 151 | - **Learning & Education**: LMS, course management systems 152 | - **Project Management**: Task tracking, time management 153 | - **E-commerce**: Online store integrations 154 | - **Accounting & Finance**: Additional financial modules 155 | - **Communication**: Chat, messaging, collaboration tools 156 | - **And many more!** 157 | 158 | ## 🙏 Acknowledgments 159 | 160 | ### Special Thanks 161 | **Gavin D'Souza (@gavindsouza)** - Creator and maintainer of [awesome-frappe](https://github.com/gavindsouza/awesome-frappe) 162 | 163 | The additional apps feature is powered by the awesome-frappe repository, a comprehensive collection of Frappe applications and resources that is community-maintained and regularly updated. This curated list makes discovering quality Frappe apps much easier for the entire community. 164 | 165 | **Repository**: https://github.com/gavindsouza/awesome-frappe 166 | 167 | Thank you Gavin for your valuable contribution to the Frappe ecosystem! 🎉 168 | 169 | ## 📋 Changelog 170 | 171 | ### Version 2.0 (Latest) 172 | **Major Features Added:** 173 | - ✨ Develop branch support with comprehensive warnings 174 | - 🏪 Additional apps marketplace with 50+ applications 175 | - 🧠 Intelligent branch detection for Frappe apps 176 | - 🔄 Multiple installation strategies with fallbacks 177 | - 🎯 Smart compatibility checking for v15/develop 178 | - 🖼️ Improved user interface with better prompts and progress indicators 179 | 180 | **Technical Improvements:** 181 | - 🔍 Git branch detection using `git ls-remote` 182 | - 📝 Setup.py parsing for accurate app names 183 | - 🏷️ Name transformation strategies for installation 184 | - 📁 Directory scanning for app detection 185 | - 🛡️ Enhanced error handling and recovery 186 | - 📋 Comprehensive installation reporting 187 | 188 | **User Experience:** 189 | - 💬 Clearer installation prompts and confirmations 190 | - ⚠️ Appropriate warnings for risky operations 191 | - 🎨 Better visual formatting and colors 192 | - 📈 Progress indicators during long operations 193 | - 📄 Detailed success/failure summaries 194 | 195 | ### Version 1.0 (Original) 196 | **Core Features:** 197 | - ✅ ERPNext versions 13, 14, 15 support 198 | - ✅ Production and development environment setup 199 | - ✅ MariaDB configuration and security 200 | - ✅ Node.js and Python environment management 201 | - ✅ SSL certificate installation with Certbot 202 | - ✅ Basic HRMS installation option 203 | 204 | ## 🛠️ Advanced Usage 205 | 206 | ### Manual App Installation 207 | You can always install additional apps manually after the initial setup: 208 | ```bash 209 | cd /path/to/frappe-bench 210 | bench get-app 211 | bench --site install-app 212 | ``` 213 | 214 | ### Troubleshooting Redis Issues 215 | If you encounter Redis connection errors (Error 111 connecting to 127.0.0.1:11001): 216 | 217 | **For ERPNext v15:** 218 | ```bash 219 | cd /path/to/frappe-bench 220 | bench setup socketio 221 | bench setup supervisor 222 | bench setup redis 223 | sudo supervisorctl reload 224 | ``` 225 | 226 | **For all versions:** 227 | ```bash 228 | # Restart Redis service 229 | sudo systemctl restart redis-server 230 | 231 | # Restart all supervisor services 232 | sudo supervisorctl restart all 233 | 234 | # Check Redis status 235 | sudo systemctl status redis-server 236 | ``` 237 | 238 | **Check Redis configuration:** 239 | ```bash 240 | # Verify Redis is running on default port 241 | redis-cli ping 242 | 243 | # Check what ports Redis is using 244 | sudo netstat -tlnp | grep redis 245 | ``` 246 | 247 | ## 📚 Additional Resources 248 | 249 | - **ERPNext Documentation**: https://docs.erpnext.com 250 | - **Frappe Framework Documentation**: https://frappeframework.com/docs 251 | - **Awesome Frappe**: https://github.com/gavindsouza/awesome-frappe 252 | - **ERPNext GitHub**: https://github.com/frappe/erpnext 253 | - **Frappe GitHub**: https://github.com/frappe/frappe 254 | 255 | ## 📄 License 256 | 257 | This script is provided as-is for the ERPNext community. Please ensure you comply with the licenses of ERPNext, Frappe Framework, and any additional apps you install. 258 | 259 | --- 260 | 261 | **Enjoy using ERPNext!** 🎉 262 | 263 | For support and issues, please check the ERPNext community forum or GitHub issues. -------------------------------------------------------------------------------- /erpnext_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | handle_error() { 4 | local line=$1 5 | local exit_code=$? 6 | echo "An error occurred on line $line with exit status $exit_code" 7 | exit $exit_code 8 | } 9 | 10 | trap 'handle_error $LINENO' ERR 11 | set -e 12 | 13 | server_ip=$(hostname -I | awk '{print $1}') 14 | 15 | YELLOW='\033[1;33m' 16 | GREEN='\033[0;32m' 17 | RED='\033[0;31m' 18 | LIGHT_BLUE='\033[1;34m' 19 | NC='\033[0m' 20 | 21 | SUPPORTED_DISTRIBUTIONS=("Ubuntu" "Debian") 22 | SUPPORTED_VERSIONS=("24.04" "23.04" "22.04" "20.04" "12" "11" "10" "9" "8") 23 | 24 | check_os() { 25 | local os_name=$(lsb_release -is) 26 | local os_version=$(lsb_release -rs) 27 | local os_supported=false 28 | local version_supported=false 29 | 30 | for i in "${SUPPORTED_DISTRIBUTIONS[@]}"; do 31 | if [[ "$i" = "$os_name" ]]; then 32 | os_supported=true 33 | break 34 | fi 35 | done 36 | 37 | for i in "${SUPPORTED_VERSIONS[@]}"; do 38 | if [[ "$i" = "$os_version" ]]; then 39 | version_supported=true 40 | break 41 | fi 42 | done 43 | 44 | if [[ "$os_supported" = false ]] || [[ "$version_supported" = false ]]; then 45 | echo -e "${RED}This script is not compatible with your operating system or its version.${NC}" 46 | exit 1 47 | fi 48 | } 49 | 50 | check_os 51 | 52 | OS="$(uname)" 53 | case $OS in 54 | 'Linux') 55 | OS='Linux' 56 | if [ -f /etc/redhat-release ] ; then 57 | DISTRO='CentOS' 58 | elif [ -f /etc/debian_version ] ; then 59 | if [ "$(lsb_release -si)" == "Ubuntu" ]; then 60 | DISTRO='Ubuntu' 61 | else 62 | DISTRO='Debian' 63 | fi 64 | fi 65 | ;; 66 | *) ;; 67 | esac 68 | 69 | ask_twice() { 70 | local prompt="$1" 71 | local secret="$2" 72 | local val1 val2 73 | 74 | while true; do 75 | if [ "$secret" = "true" ]; then 76 | read -rsp "$prompt: " val1 77 | echo >&2 78 | else 79 | read -rp "$prompt: " val1 80 | echo >&2 81 | fi 82 | 83 | if [ "$secret" = "true" ]; then 84 | read -rsp "Confirm password: " val2 85 | echo >&2 86 | else 87 | read -rp "Confirm password: " val2 88 | echo >&2 89 | fi 90 | 91 | if [ "$val1" = "$val2" ]; then 92 | printf "${GREEN}Password confirmed${NC}\n" >&2 93 | echo "$val1" 94 | break 95 | else 96 | printf "${RED}Inputs do not match. Please try again${NC}\n" >&2 97 | echo -e "\n" 98 | fi 99 | done 100 | } 101 | 102 | extract_app_name_from_setup() { 103 | local setup_file="$1" 104 | local app_name="" 105 | 106 | if [[ -f "$setup_file" ]]; then 107 | app_name=$(grep -oE 'name\s*=\s*["\047][^"\047]+["\047]' "$setup_file" 2>/dev/null | head -1 | sed -E 's/.*name\s*=\s*["\047]([^"\047]+)["\047].*/\1/') 108 | 109 | if [[ -z "$app_name" ]]; then 110 | app_name=$(grep -oE 'name\s*=\s*["\047][^"\047]*["\047]' "$setup_file" 2>/dev/null | head -1 | sed -E 's/.*["\047]([^"\047]+)["\047].*/\1/') 111 | fi 112 | 113 | if [[ -z "$app_name" ]]; then 114 | app_name=$(awk '/setup\s*\(/,/\)/ { if (/name\s*=/) { gsub(/.*name\s*=\s*["\047]/, ""); gsub(/["\047].*/, ""); print; exit } }' "$setup_file" 2>/dev/null | head -1 | tr -d ' \t') 115 | fi 116 | 117 | if [[ -z "$app_name" ]]; then 118 | app_name=$(grep "name.*=" "$setup_file" 2>/dev/null | head -1 | sed -E 's/.*["\047]([^"\047]+)["\047].*/\1/' | tr -d ' \t') 119 | fi 120 | 121 | if [[ -z "$app_name" ]]; then 122 | local app_base_dir=$(dirname "$setup_file") 123 | for subdir in "$app_base_dir"/*/; do 124 | if [[ -d "$subdir" && -f "$subdir/__init__.py" ]]; then 125 | local module_dir=$(basename "$subdir") 126 | if [[ -n "$module_dir" && "$module_dir" != "." && "$module_dir" != "tests" && "$module_dir" != "docs" ]]; then 127 | app_name="$module_dir" 128 | break 129 | fi 130 | fi 131 | done 132 | fi 133 | fi 134 | 135 | echo "$app_name" 136 | } 137 | 138 | check_existing_installations() { 139 | local existing_installations=() 140 | local installation_paths=() 141 | 142 | local search_paths=( 143 | "$HOME/frappe-bench" 144 | "/home/*/frappe-bench" 145 | "/opt/frappe-bench" 146 | "/var/www/frappe-bench" 147 | ) 148 | 149 | echo -e "${YELLOW}Checking for existing ERPNext installations...${NC}" 150 | 151 | for path in "${search_paths[@]}"; do 152 | if [[ -d "$path" ]] && [[ -f "$path/apps/frappe/frappe/__init__.py" ]]; then 153 | local version_info="" 154 | if [[ -f "$path/apps/frappe/frappe/__version__.py" ]]; then 155 | version_info=$(grep -o 'version.*=.*[0-9]' "$path/apps/frappe/frappe/__version__.py" 2>/dev/null || echo "unknown") 156 | fi 157 | 158 | local branch_info="" 159 | if [[ -d "$path/apps/frappe/.git" ]]; then 160 | branch_info=$(cd "$path/apps/frappe" && git branch --show-current 2>/dev/null || echo "unknown") 161 | fi 162 | 163 | existing_installations+=("$path") 164 | installation_paths+=("Path: $path | Version: $version_info | Branch: $branch_info") 165 | fi 166 | done 167 | 168 | if [[ ${#existing_installations[@]} -gt 0 ]]; then 169 | echo "" 170 | echo -e "${RED}⚠️ EXISTING ERPNEXT INSTALLATION(S) DETECTED ⚠️${NC}" 171 | echo "" 172 | echo -e "${YELLOW}Found the following ERPNext installation(s):${NC}" 173 | for info in "${installation_paths[@]}"; do 174 | echo -e "${LIGHT_BLUE}• $info${NC}" 175 | done 176 | echo "" 177 | echo -e "${RED}WARNING: Installing different ERPNext versions on the same server can cause:${NC}" 178 | echo -e "${YELLOW}• Port conflicts (Redis, Node.js services)${NC}" 179 | echo -e "${YELLOW}• Dependency version conflicts${NC}" 180 | echo -e "${YELLOW}• Supervisor configuration conflicts${NC}" 181 | echo -e "${YELLOW}• Database schema incompatibilities${NC}" 182 | echo -e "${YELLOW}• System instability${NC}" 183 | echo "" 184 | echo -e "${LIGHT_BLUE}Recommended actions:${NC}" 185 | echo -e "${GREEN}1. Use the existing installation if it meets your needs${NC}" 186 | echo -e "${GREEN}2. Backup and remove existing installation before installing new version${NC}" 187 | echo -e "${GREEN}3. Use a fresh server/container for the new installation${NC}" 188 | echo -e "${GREEN}4. Use different users/paths if you must have multiple versions${NC}" 189 | echo "" 190 | 191 | read -p "Do you want to continue anyway? (yes/no): " conflict_confirm 192 | conflict_confirm=$(echo "$conflict_confirm" | tr '[:upper:]' '[:lower:]') 193 | 194 | if [[ "$conflict_confirm" != "yes" && "$conflict_confirm" != "y" ]]; then 195 | echo -e "${GREEN}Installation cancelled. Good choice for system stability!${NC}" 196 | exit 0 197 | else 198 | echo -e "${YELLOW}Proceeding with installation despite existing installations...${NC}" 199 | echo -e "${RED}You've been warned about potential conflicts!${NC}" 200 | fi 201 | else 202 | echo -e "${GREEN}✓ No existing ERPNext installations found.${NC}" 203 | fi 204 | } 205 | 206 | detect_best_branch() { 207 | local repo_url="$1" 208 | local preferred_version="$2" 209 | local repo_name="$3" 210 | 211 | echo -e "${LIGHT_BLUE}🔍 Detecting available branches for $repo_name...${NC}" >&2 212 | 213 | local branches=$(git ls-remote --heads "$repo_url" 2>/dev/null | awk '{print $2}' | sed 's|refs/heads/||' | sort -V) 214 | 215 | if [[ -z "$branches" ]]; then 216 | echo -e "${RED}⚠ Could not fetch branches from $repo_url${NC}" >&2 217 | echo "" 218 | return 1 219 | fi 220 | 221 | local branch_priorities=() 222 | 223 | case "$repo_name" in 224 | "crm"|"helpdesk"|"builder"|"drive"|"gameplan") 225 | echo -e "${YELLOW}🎯 Using 'main' branch for Frappe $repo_name (recommended)${NC}" >&2 226 | if echo "$branches" | grep -q "^main$"; then 227 | echo -e "${GREEN}✅ Selected branch: main${NC}" >&2 228 | echo "main" 229 | return 0 230 | elif echo "$branches" | grep -q "^master$"; then 231 | echo -e "${YELLOW}⚠ 'main' not found, falling back to 'master'${NC}" >&2 232 | echo "master" 233 | return 0 234 | fi 235 | ;; 236 | "hrms"|"lms") 237 | echo -e "${YELLOW}🎯 Detecting best branch for Frappe $repo_name...${NC}" >&2 238 | ;; 239 | esac 240 | 241 | case "$preferred_version" in 242 | "version-15"|"develop") 243 | branch_priorities=("version-15" "develop" "main" "master" "version-14" "version-13") 244 | ;; 245 | "version-14") 246 | branch_priorities=("version-14" "main" "master" "develop" "version-15" "version-13") 247 | ;; 248 | "version-13") 249 | branch_priorities=("version-13" "main" "master" "version-14" "develop" "version-15") 250 | ;; 251 | *) 252 | branch_priorities=("main" "master" "develop") 253 | ;; 254 | esac 255 | 256 | for priority_branch in "${branch_priorities[@]}"; do 257 | if echo "$branches" | grep -q "^$priority_branch$"; then 258 | echo -e "${GREEN}✅ Selected branch: $priority_branch${NC}" >&2 259 | echo "$priority_branch" 260 | return 0 261 | fi 262 | done 263 | 264 | local fallback_branch=$(echo "$branches" | head -1) 265 | echo -e "${YELLOW}⚠ Using fallback branch: $fallback_branch${NC}" >&2 266 | echo "$fallback_branch" 267 | return 0 268 | } 269 | 270 | echo -e "${LIGHT_BLUE}Welcome to the ERPNext Installer...${NC}" 271 | echo -e "\n" 272 | sleep 3 273 | 274 | echo -e "${YELLOW}Please enter the number of the corresponding ERPNext version you wish to install:${NC}" 275 | 276 | versions=("Version 13" "Version 14" "Version 15" "Develop") 277 | select version_choice in "${versions[@]}"; do 278 | case $REPLY in 279 | 1) bench_version="version-13"; break;; 280 | 2) bench_version="version-14"; break;; 281 | 3) bench_version="version-15"; break;; 282 | 4) bench_version="develop"; 283 | echo "" 284 | echo -e "${RED}⚠️ WARNING: DEVELOP VERSION ⚠️${NC}" 285 | echo "" 286 | echo -e "${YELLOW}The develop branch contains bleeding-edge code that:${NC}" 287 | echo -e "${RED}• Changes daily and may be unstable${NC}" 288 | echo -e "${RED}• Can cause data corruption or system crashes${NC}" 289 | echo -e "${RED}• Is NOT suitable for production or important data${NC}" 290 | echo -e "${RED}• Has limited community support${NC}" 291 | echo "" 292 | echo -e "${GREEN}Recommended for: Experienced developers testing new features${NC}" 293 | echo -e "${GREEN}Better alternatives: Version 15 (stable) or Version 14 (proven)${NC}" 294 | echo "" 295 | read -p "Do you understand the risks and want to continue? (yes/no): " develop_confirm 296 | develop_confirm=$(echo "$develop_confirm" | tr '[:upper:]' '[:lower:]') 297 | 298 | if [[ "$develop_confirm" != "yes" && "$develop_confirm" != "y" ]]; then 299 | echo -e "${GREEN}Good choice! Please select a stable version.${NC}" 300 | continue 301 | else 302 | echo -e "${YELLOW}Proceeding with develop branch installation...${NC}" 303 | fi 304 | break;; 305 | *) echo -e "${RED}Invalid option. Please select a valid version.${NC}";; 306 | esac 307 | done 308 | 309 | echo -e "${GREEN}You have selected $version_choice for installation.${NC}" 310 | echo -e "${LIGHT_BLUE}Do you wish to continue? (yes/no)${NC}" 311 | read -p "Response: " continue_install 312 | continue_install=$(echo "$continue_install" | tr '[:upper:]' '[:lower:]') 313 | 314 | while [[ "$continue_install" != "yes" && "$continue_install" != "y" && "$continue_install" != "no" && "$continue_install" != "n" ]]; do 315 | echo -e "${RED}Invalid response. Please answer with 'yes' or 'no'.${NC}" 316 | echo -e "${LIGHT_BLUE}Do you wish to continue with the installation of $version_choice? (yes/no)${NC}" 317 | read -p "Response: " continue_install 318 | continue_install=$(echo "$continue_install" | tr '[:upper:]' '[:lower:]') 319 | done 320 | 321 | if [[ "$continue_install" == "no" || "$continue_install" == "n" ]]; then 322 | echo -e "${RED}Installation aborted by user.${NC}" 323 | exit 0 324 | else 325 | echo -e "${GREEN}Proceeding with the installation of $version_choice.${NC}" 326 | fi 327 | sleep 2 328 | 329 | check_existing_installations 330 | 331 | # 332 | # ─── OS COMPATIBILITY FOR VERSION-15 OR DEVELOP ──────────────────────────────────────── 333 | # 334 | if [[ "$bench_version" == "version-15" || "$bench_version" == "develop" ]]; then 335 | if [[ "$(lsb_release -si)" != "Ubuntu" && "$(lsb_release -si)" != "Debian" ]]; then 336 | echo -e "${RED}Your Distro is not supported for Version 15/Develop.${NC}" 337 | exit 1 338 | elif [[ "$(lsb_release -si)" == "Ubuntu" && "$(lsb_release -rs)" < "22.04" ]]; then 339 | echo -e "${RED}Your Ubuntu version is below the minimum required to support Version 15/Develop.${NC}" 340 | exit 1 341 | elif [[ "$(lsb_release -si)" == "Debian" && "$(lsb_release -rs)" < "12" ]]; then 342 | echo -e "${RED}Your Debian version is below the minimum required to support Version 15/Develop.${NC}" 343 | exit 1 344 | fi 345 | fi 346 | 347 | # 348 | # ─── OS COMPATIBILITY FOR OLDER VERSIONS (version-13, version-14) ─────────────────────── 349 | # 350 | if [[ "$bench_version" != "version-15" && "$bench_version" != "develop" ]]; then 351 | if [[ "$(lsb_release -si)" != "Ubuntu" && "$(lsb_release -si)" != "Debian" ]]; then 352 | echo -e "${RED}Your Distro is not supported for $version_choice.${NC}" 353 | exit 1 354 | elif [[ "$(lsb_release -si)" == "Ubuntu" && "$(lsb_release -rs)" > "22.04" ]]; then 355 | echo -e "${RED}Your Ubuntu version is not supported for $version_choice.${NC}" 356 | echo -e "${YELLOW}ERPNext v13/v14 only support Ubuntu up to 22.04. Please use ERPNext v15 for Ubuntu 24.04.${NC}" 357 | exit 1 358 | elif [[ "$(lsb_release -si)" == "Debian" && "$(lsb_release -rs)" > "11" ]]; then 359 | echo -e "${YELLOW}Warning: Your Debian version is above the tested range for $version_choice, but we'll continue.${NC}" 360 | sleep 2 361 | fi 362 | fi 363 | 364 | check_os 365 | 366 | cd "$(sudo -u $USER echo $HOME)" 367 | 368 | # 369 | # ─── SQL ROOT PASSWORD PROMPT ───────────────────────────────────────────────────────── 370 | # 371 | echo -e "${YELLOW}Now let's set some important parameters...${NC}" 372 | sleep 1 373 | echo -e "${YELLOW}We will need your required SQL root password${NC}" 374 | sleep 1 375 | sqlpasswrd=$(ask_twice "What is your required SQL root password" "true") 376 | echo -e "\n" 377 | sleep 1 378 | 379 | # 380 | # ─── SYSTEM PACKAGE UPDATES ──────────────────────────────────────────────────────────── 381 | # 382 | echo -e "${YELLOW}Updating system packages...${NC}" 383 | sleep 2 384 | sudo apt update 385 | sudo apt upgrade -y 386 | echo -e "${GREEN}System packages updated.${NC}" 387 | sleep 2 388 | 389 | # 390 | # ─── PRELIMINARY PACKAGE INSTALL ────────────────────────────────────────────────────── 391 | # 392 | echo -e "${YELLOW}Installing preliminary package requirements${NC}" 393 | sleep 3 394 | sudo apt install software-properties-common git curl whiptail -y 395 | 396 | # 397 | # ─── PYTHON AND REDIS INSTALL ─────────────────────────────────────────────────────────── 398 | # 399 | echo -e "${YELLOW}Installing python environment manager and other requirements...${NC}" 400 | sleep 2 401 | 402 | py_version=$(python3 --version 2>&1 | awk '{print $2}') 403 | py_major=$(echo "$py_version" | cut -d '.' -f 1) 404 | py_minor=$(echo "$py_version" | cut -d '.' -f 2) 405 | 406 | if [[ -z "$py_version" ]] || [[ "$py_major" -lt 3 ]] || [[ "$py_major" -eq 3 && "$py_minor" -lt 10 ]]; then 407 | echo -e "${LIGHT_BLUE}It appears this instance does not meet the minimum Python version required for ERPNext 14 (Python3.10)...${NC}" 408 | sleep 2 409 | echo -e "${YELLOW}Not to worry, we will sort it out for you${NC}" 410 | sleep 4 411 | echo -e "${YELLOW}Installing Python 3.10+...${NC}" 412 | sleep 2 413 | 414 | sudo apt -qq install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev \ 415 | libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev -y && \ 416 | wget https://www.python.org/ftp/python/3.10.11/Python-3.10.11.tgz && \ 417 | tar -xf Python-3.10.11.tgz && \ 418 | cd Python-3.10.11 && \ 419 | ./configure --prefix=/usr/local --enable-optimizations --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib" && \ 420 | make -j "$(nproc)" && \ 421 | sudo make altinstall && \ 422 | cd .. && \ 423 | sudo rm -rf Python-3.10.11 && \ 424 | sudo rm Python-3.10.11.tgz && \ 425 | pip3.10 install --user --upgrade pip && \ 426 | echo -e "${GREEN}Python3.10 installation successful!${NC}" 427 | sleep 2 428 | fi 429 | 430 | echo -e "\n" 431 | echo -e "${YELLOW}Installing additional Python packages and Redis Server${NC}" 432 | sleep 2 433 | sudo apt install git python3-dev python3-setuptools python3-venv python3-pip redis-server -y 434 | 435 | # 436 | # ─── WKHTMLTOPDF INSTALL ─────────────────────────────────────────────────────────────── 437 | # 438 | arch=$(uname -m) 439 | case $arch in 440 | x86_64) arch="amd64" ;; 441 | aarch64) arch="arm64" ;; 442 | *) echo -e "${RED}Unsupported architecture: $arch${NC}"; exit 1 ;; 443 | esac 444 | 445 | sudo apt install fontconfig libxrender1 xfonts-75dpi xfonts-base -y 446 | 447 | wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_"$arch".deb && \ 448 | sudo dpkg -i wkhtmltox_0.12.6.1-2.jammy_"$arch".deb || true && \ 449 | sudo cp /usr/local/bin/wkhtmlto* /usr/bin/ && \ 450 | sudo chmod a+x /usr/bin/wk* && \ 451 | sudo rm wkhtmltox_0.12.6.1-2.jammy_"$arch".deb && \ 452 | sudo apt --fix-broken install -y && \ 453 | sudo apt install fontconfig xvfb libfontconfig xfonts-base xfonts-75dpi libxrender1 -y 454 | 455 | echo -e "${GREEN}Done!${NC}" 456 | sleep 1 457 | echo -e "\n" 458 | 459 | # 460 | # ─── MARIADB + DEV LIBRARIES + PKG-CONFIG ───────────────────────────────────────────── 461 | # 462 | echo -e "${YELLOW}Now installing MariaDB and other necessary packages...${NC}" 463 | sleep 2 464 | sudo apt install mariadb-server mariadb-client -y 465 | 466 | echo -e "${YELLOW}Installing MySQL/MariaDB development libraries and pkg-config...${NC}" 467 | sleep 1 468 | sudo apt install pkg-config default-libmysqlclient-dev -y 469 | 470 | echo -e "${GREEN}MariaDB and development packages have been installed successfully.${NC}" 471 | sleep 2 472 | 473 | MARKER_FILE=~/.mysql_configured.marker 474 | if [ ! -f "$MARKER_FILE" ]; then 475 | echo -e "${YELLOW}Now we'll go ahead to apply MariaDB security settings...${NC}" 476 | sleep 2 477 | 478 | sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '$sqlpasswrd';" 479 | sudo mysql -u root -p"$sqlpasswrd" -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '$sqlpasswrd';" 480 | sudo mysql -u root -p"$sqlpasswrd" -e "DELETE FROM mysql.user WHERE User='';" 481 | sudo mysql -u root -p"$sqlpasswrd" -e "DROP DATABASE IF EXISTS test; DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';" 482 | sudo mysql -u root -p"$sqlpasswrd" -e "FLUSH PRIVILEGES;" 483 | 484 | echo -e "${YELLOW}...And add some settings to /etc/mysql/my.cnf:${NC}" 485 | sleep 2 486 | 487 | sudo bash -c 'cat << EOF >> /etc/mysql/my.cnf 488 | [mysqld] 489 | character-set-client-handshake = FALSE 490 | character-set-server = utf8mb4 491 | collation-server = utf8mb4_unicode_ci 492 | 493 | [mysql] 494 | default-character-set = utf8mb4 495 | EOF' 496 | 497 | sudo service mysql restart 498 | 499 | touch "$MARKER_FILE" 500 | echo -e "${GREEN}MariaDB settings done!${NC}" 501 | echo -e "\n" 502 | sleep 1 503 | fi 504 | 505 | # 506 | # ─── NVM / NODE / YARN INSTALL ───────────────────────────────────────────────────────── 507 | # 508 | echo -e "${YELLOW}Now to install NVM, Node, npm and yarn${NC}" 509 | sleep 2 510 | 511 | curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash 512 | 513 | nvm_init='export NVM_DIR="$HOME/.nvm" 514 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 515 | [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' 516 | 517 | grep -qxF 'export NVM_DIR="$HOME/.nvm"' ~/.profile 2>/dev/null || echo "$nvm_init" >> ~/.profile 518 | grep -qxF 'export NVM_DIR="$HOME/.nvm"' ~/.bashrc 2>/dev/null || echo "$nvm_init" >> ~/.bashrc 519 | 520 | export NVM_DIR="$HOME/.nvm" 521 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 522 | [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" 523 | 524 | 525 | os_version=$(lsb_release -rs) 526 | if [[ "$DISTRO" == "Ubuntu" && "$os_version" == "24.04" ]]; then 527 | nvm install 20 528 | nvm alias default 20 529 | node_version="20" 530 | elif [[ "$bench_version" == "version-15" || "$bench_version" == "develop" ]]; then 531 | nvm install 18 532 | nvm alias default 18 533 | node_version="18" 534 | else 535 | nvm install 16 536 | nvm alias default 16 537 | node_version="16" 538 | fi 539 | 540 | npm install -g yarn@1.22.19 541 | 542 | echo -e "${GREEN}nvm and Node (v${node_version}) have been installed and aliased as default.${NC}" 543 | echo -e "${GREEN}Yarn v$(yarn --version) (Classic) installed globally.${NC}" 544 | sleep 2 545 | 546 | if [[ -z "$py_version" ]] || [[ "$py_major" -lt 3 ]] || [[ "$py_major" -eq 3 && "$py_minor" -lt 10 ]]; then 547 | python3.10 -m venv "$USER" 548 | source "$USER/bin/activate" 549 | nvm use default 550 | fi 551 | 552 | # 553 | # ─── BENCH INSTALL ─────────────────────────────────────────────────────────────────────── 554 | # 555 | echo -e "${YELLOW}Now let's install bench${NC}" 556 | sleep 2 557 | 558 | externally_managed_file=$(find /usr/lib/python3.*/EXTERNALLY-MANAGED 2>/dev/null || true) 559 | if [[ -n "$externally_managed_file" ]]; then 560 | sudo python3 -m pip config --global set global.break-system-packages true 561 | fi 562 | 563 | sudo apt install python3-pip -y 564 | sudo pip3 install frappe-bench 565 | 566 | export NVM_DIR="$HOME/.nvm" 567 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 568 | nvm use default 569 | 570 | echo -e "${YELLOW}Initialising bench in frappe-bench folder.${NC}" 571 | echo -e "${LIGHT_BLUE}If you get a restart failed, don't worry, we will resolve that later.${NC}" 572 | bench init frappe-bench --version "$bench_version" --verbose 573 | echo -e "${GREEN}Bench installation complete!${NC}" 574 | sleep 1 575 | 576 | # 577 | # ─── NEW SITE CREATION ───────────────────────────────────────────────────────────────── 578 | # 579 | echo -e "${YELLOW}Preparing for Production installation. This could take a minute... or two so please be patient.${NC}" 580 | read -p "Enter the site name (If you wish to install SSL later, please enter a FQDN): " site_name 581 | sleep 1 582 | adminpasswrd=$(ask_twice "Enter the Administrator password" "true") 583 | echo -e "\n" 584 | sleep 2 585 | echo -e "${YELLOW}Now setting up your site. This might take a few minutes. Please wait...${NC}" 586 | sleep 1 587 | 588 | cd frappe-bench && \ 589 | sudo chmod -R o+rx "$(echo $HOME)" 590 | 591 | bench new-site "$site_name" \ 592 | --db-root-username root \ 593 | --db-root-password "$sqlpasswrd" \ 594 | --admin-password "$adminpasswrd" 595 | 596 | if [[ "$bench_version" == "develop" ]]; then 597 | echo -e "${YELLOW}Starting Redis instances for develop branch (queue, cache, and socketio)...${NC}" 598 | sleep 1 599 | redis-server --port 11000 --daemonize yes --bind 127.0.0.1 600 | redis-server --port 12000 --daemonize yes --bind 127.0.0.1 601 | redis-server --port 13000 --daemonize yes --bind 127.0.0.1 602 | echo -e "${GREEN}Redis instances started for develop branch.${NC}" 603 | sleep 1 604 | fi 605 | 606 | echo -e "${LIGHT_BLUE}Would you like to install ERPNext? (yes/no)${NC}" 607 | read -p "Response: " erpnext_install 608 | erpnext_install=$(echo "$erpnext_install" | tr '[:upper:]' '[:lower:]') 609 | 610 | case "$erpnext_install" in 611 | "yes"|"y") 612 | sleep 2 613 | bench get-app erpnext --branch "$bench_version" && \ 614 | bench --site "$site_name" install-app erpnext 615 | sleep 1 616 | ;; 617 | esac 618 | 619 | python_version=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") 620 | playbook_file="/usr/local/lib/python${python_version}/dist-packages/bench/playbooks/roles/mariadb/tasks/main.yml" 621 | sudo sed -i 's/- include: /- include_tasks: /g' "$playbook_file" 622 | 623 | echo -e "${LIGHT_BLUE}Would you like to continue with production install? (yes/no)${NC}" 624 | read -p "Response: " continue_prod 625 | continue_prod=$(echo "$continue_prod" | tr '[:upper:]' '[:lower:]') 626 | 627 | case "$continue_prod" in 628 | "yes"|"y") 629 | echo -e "${YELLOW}Installing packages and dependencies for Production...${NC}" 630 | sleep 2 631 | 632 | yes | sudo bench setup production "$USER" && \ 633 | echo -e "${YELLOW}Applying necessary permissions to supervisor...${NC}" 634 | sleep 1 635 | 636 | FILE="/etc/supervisor/supervisord.conf" 637 | SEARCH_PATTERN="chown=$USER:$USER" 638 | 639 | if grep -q "$SEARCH_PATTERN" "$FILE"; then 640 | echo -e "${YELLOW}User ownership already exists for supervisord. Updating it...${NC}" 641 | sudo sed -i "/chown=.*/c $SEARCH_PATTERN" "$FILE" 642 | else 643 | echo -e "${YELLOW}User ownership does not exist for supervisor. Adding it...${NC}" 644 | sudo sed -i "5a $SEARCH_PATTERN" "$FILE" 645 | fi 646 | 647 | sudo service supervisor restart && \ 648 | yes | sudo bench setup production "$USER" && \ 649 | echo -e "${YELLOW}Enabling Scheduler...${NC}" 650 | sleep 1 651 | 652 | bench --site "$site_name" scheduler enable && \ 653 | bench --site "$site_name" scheduler resume 654 | 655 | if [[ "$bench_version" == "version-15" || "$bench_version" == "develop" ]]; then 656 | echo -e "${YELLOW}Setting up Socketio, Redis and Supervisor for $bench_version...${NC}" 657 | sleep 1 658 | bench setup socketio 659 | yes | bench setup supervisor 660 | bench setup redis 661 | sudo supervisorctl reload 662 | fi 663 | 664 | echo -e "${YELLOW}Restarting bench to apply all changes and optimizing environment permissions.${NC}" 665 | sleep 1 666 | 667 | sudo chmod 755 "$(echo $HOME)" 668 | 669 | echo -e "${YELLOW}Configuring Redis services...${NC}" 670 | sudo systemctl restart redis-server 671 | sleep 2 672 | 673 | sudo supervisorctl restart all 674 | sleep 3 675 | 676 | printf "${GREEN}Production setup complete! " 677 | printf '\xF0\x9F\x8E\x86' 678 | printf "${NC}\n" 679 | sleep 3 680 | 681 | # 682 | # ─── ADDITIONAL APPS INSTALL SECTION ──────────────────────────────── 683 | # 684 | echo -e "${LIGHT_BLUE}Would you like to install additional Frappe apps? (yes/no)${NC}" 685 | read -p "Response: " extra_apps_install 686 | extra_apps_install=$(echo "$extra_apps_install" | tr '[:upper:]' '[:lower:]') 687 | 688 | case "$extra_apps_install" in 689 | "yes"|"y") 690 | echo "" 691 | echo -e "${YELLOW}⚠️ Additional Apps Installation${NC}" 692 | echo -e "${LIGHT_BLUE}Note: App compatibility may vary. Some apps might fail to install${NC}" 693 | echo -e "${LIGHT_BLUE}due to version mismatches or missing dependencies.${NC}" 694 | echo "" 695 | echo -e "${GREEN}Apps courtesy of awesome-frappe by Gavin D'Souza (@gavindsouza)${NC}" 696 | echo -e "${GREEN}Repository: https://github.com/gavindsouza/awesome-frappe${NC}" 697 | echo "" 698 | read -p "Continue with app installation? (yes/no): " apps_confirm 699 | apps_confirm=$(echo "$apps_confirm" | tr '[:upper:]' '[:lower:]') 700 | 701 | if [[ "$apps_confirm" != "yes" && "$apps_confirm" != "y" ]]; then 702 | echo -e "${GREEN}Apps installation cancelled.${NC}" 703 | else 704 | echo -e "${GREEN}Proceeding with additional apps installation...${NC}" 705 | echo "" 706 | 707 | echo -e "${YELLOW}Fetching available apps from awesome-frappe repository...${NC}" 708 | tmp_dir=$(mktemp -d) 709 | 710 | if ! git clone https://github.com/gavindsouza/awesome-frappe.git "$tmp_dir" --depth 1 2>/dev/null; then 711 | echo -e "${RED}Failed to clone awesome-frappe repository. Skipping additional apps installation.${NC}" 712 | rm -rf "$tmp_dir" 713 | else 714 | if [[ ! -f "$tmp_dir/README.md" ]]; then 715 | echo -e "${RED}README.md not found in awesome-frappe repository. Skipping additional apps installation.${NC}" 716 | rm -rf "$tmp_dir" 717 | else 718 | mapfile -t raw_entries < <( 719 | { 720 | grep -oE '\[([^]]+)\]\(https://github\.com/[^)]*\)' "$tmp_dir/README.md" 2>/dev/null || true 721 | grep -oE '\[([^]]+)\]\(https://frappecloud\.com/marketplace/[^)]*\)' "$tmp_dir/README.md" 2>/dev/null || true 722 | grep -oE '\[([^]]+)\]\(https://frappe\.io/[^)]*\)' "$tmp_dir/README.md" 2>/dev/null || true 723 | 724 | echo "[Frappe HR](https://github.com/frappe/hrms.git)" 725 | echo "[Frappe LMS](https://github.com/frappe/lms.git)" 726 | echo "[Frappe CRM](https://github.com/frappe/crm.git)" 727 | echo "[Frappe Helpdesk](https://github.com/frappe/helpdesk.git)" 728 | echo "[Frappe Builder](https://github.com/frappe/builder.git)" 729 | echo "[Frappe Drive](https://github.com/frappe/drive.git)" 730 | echo "[Frappe Gameplan](https://github.com/frappe/gameplan.git)" 731 | } | sort -u 732 | ) 733 | 734 | if [ "${#raw_entries[@]}" -eq 0 ]; then 735 | echo -e "${RED}No GitHub repository links found in awesome-frappe README. Skipping.${NC}" 736 | rm -rf "$tmp_dir" 737 | else 738 | declare -a display_names=() 739 | declare -a repo_names=() 740 | declare -a url_array=() 741 | 742 | if [[ "$bench_version" == "version-15" || "$bench_version" == "develop" ]]; then 743 | echo -e "${YELLOW}Checking app compatibility with $bench_version...${NC}" 744 | echo -e "${LIGHT_BLUE}This may take a moment, please wait...${NC}" 745 | 746 | total_apps=${#raw_entries[@]} 747 | current_app=0 748 | compatible_count=0 749 | 750 | for entry in "${raw_entries[@]}"; do 751 | current_app=$((current_app + 1)) 752 | 753 | echo -ne "\r${LIGHT_BLUE}Progress: $current_app/$total_apps apps checked...${NC}" 754 | 755 | display_name=$(echo "$entry" | sed -E 's/\[([^]]+)\]\(.*/\1/') 756 | 757 | url=$(echo "$entry" | sed -E 's/.*\(([^)]+)\).*/\1/') 758 | 759 | repo_url="" 760 | repo_name="" 761 | 762 | if [[ "$url" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then 763 | repo_url="$url" 764 | if [[ ! "$repo_url" =~ \.git$ ]]; then 765 | repo_url="${repo_url}.git" 766 | fi 767 | repo_name=$(basename "$repo_url" .git) 768 | elif [[ "$url" =~ ^https://frappecloud\.com/marketplace/ ]] || [[ "$url" =~ ^https://github\.com/frappe/ ]]; then 769 | case "$display_name" in 770 | "Frappe HR"|"HRMS") 771 | repo_url="https://github.com/frappe/hrms.git" 772 | repo_name="hrms" 773 | ;; 774 | "Frappe LMS") 775 | repo_url="https://github.com/frappe/lms.git" 776 | repo_name="lms" 777 | ;; 778 | "Frappe CRM") 779 | repo_url="https://github.com/frappe/crm.git" 780 | repo_name="crm" 781 | ;; 782 | "Frappe Helpdesk") 783 | repo_url="https://github.com/frappe/helpdesk.git" 784 | repo_name="helpdesk" 785 | ;; 786 | "Frappe Builder") 787 | repo_url="https://github.com/frappe/builder.git" 788 | repo_name="builder" 789 | ;; 790 | "Frappe Drive") 791 | repo_url="https://github.com/frappe/drive.git" 792 | repo_name="drive" 793 | ;; 794 | "Frappe Gameplan") 795 | repo_url="https://github.com/frappe/gameplan.git" 796 | repo_name="gameplan" 797 | ;; 798 | *) 799 | if [[ "$url" =~ ^https://github\.com/ ]]; then 800 | repo_url="$url" 801 | if [[ ! "$repo_url" =~ \.git$ ]]; then 802 | repo_url="${repo_url}.git" 803 | fi 804 | repo_name=$(basename "$repo_url" .git) 805 | else 806 | continue 807 | fi 808 | ;; 809 | esac 810 | else 811 | continue 812 | fi 813 | 814 | if [[ "$repo_name" == ".git" || "$repo_name" == "" ]]; then 815 | continue 816 | fi 817 | 818 | repo_check_dir=$(mktemp -d) 819 | 820 | if git clone "$repo_url" "$repo_check_dir" --depth 1 --quiet 2>/dev/null; then 821 | if [[ -f "$repo_check_dir/pyproject.toml" ]]; then 822 | display_names+=("$display_name") 823 | repo_names+=("$repo_name") 824 | url_array+=("$repo_url") 825 | compatible_count=$((compatible_count + 1)) 826 | fi 827 | fi 828 | 829 | rm -rf "$repo_check_dir" 830 | done 831 | 832 | echo -e "\r${GREEN}✓ Compatibility check complete: $compatible_count/$total_apps apps are compatible with $bench_version${NC}" 833 | 834 | else 835 | echo -e "${YELLOW}Processing available apps for $bench_version...${NC}" 836 | 837 | for entry in "${raw_entries[@]}"; do 838 | display_name=$(echo "$entry" | sed -E 's/\[([^]]+)\]\(.*/\1/') 839 | 840 | url=$(echo "$entry" | sed -E 's/.*\(([^)]+)\).*/\1/') 841 | 842 | repo_url="" 843 | repo_name="" 844 | 845 | if [[ "$url" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then 846 | repo_url="$url" 847 | if [[ ! "$repo_url" =~ \.git$ ]]; then 848 | repo_url="${repo_url}.git" 849 | fi 850 | repo_name=$(basename "$repo_url" .git) 851 | elif [[ "$url" =~ ^https://frappecloud\.com/marketplace/ ]] || [[ "$url" =~ ^https://github\.com/frappe/ ]]; then 852 | case "$display_name" in 853 | "Frappe HR"|"HRMS") 854 | repo_url="https://github.com/frappe/hrms.git" 855 | repo_name="hrms" 856 | ;; 857 | "Frappe LMS") 858 | repo_url="https://github.com/frappe/lms.git" 859 | repo_name="lms" 860 | ;; 861 | "Frappe CRM") 862 | repo_url="https://github.com/frappe/crm.git" 863 | repo_name="crm" 864 | ;; 865 | "Frappe Helpdesk") 866 | repo_url="https://github.com/frappe/helpdesk.git" 867 | repo_name="helpdesk" 868 | ;; 869 | "Frappe Builder") 870 | repo_url="https://github.com/frappe/builder.git" 871 | repo_name="builder" 872 | ;; 873 | "Frappe Drive") 874 | repo_url="https://github.com/frappe/drive.git" 875 | repo_name="drive" 876 | ;; 877 | "Frappe Gameplan") 878 | repo_url="https://github.com/frappe/gameplan.git" 879 | repo_name="gameplan" 880 | ;; 881 | *) 882 | if [[ "$url" =~ ^https://github\.com/ ]]; then 883 | repo_url="$url" 884 | if [[ ! "$repo_url" =~ \.git$ ]]; then 885 | repo_url="${repo_url}.git" 886 | fi 887 | repo_name=$(basename "$repo_url" .git) 888 | else 889 | continue 890 | fi 891 | ;; 892 | esac 893 | else 894 | continue 895 | fi 896 | 897 | if [[ "$repo_name" == ".git" || "$repo_name" == "" ]]; then 898 | continue 899 | fi 900 | 901 | display_names+=("$display_name") 902 | repo_names+=("$repo_name") 903 | url_array+=("$repo_url") 904 | done 905 | 906 | echo -e "${GREEN}✓ Found ${#display_names[@]} apps available for $bench_version${NC}" 907 | fi 908 | 909 | declare -a unique_display_names=() 910 | declare -a unique_repo_names=() 911 | declare -a unique_urls=() 912 | declare -A seen_repos=() 913 | 914 | for i in "${!repo_names[@]}"; do 915 | if [[ -z "${seen_repos[${repo_names[$i]}]}" ]]; then 916 | seen_repos["${repo_names[$i]}"]=1 917 | unique_display_names+=("${display_names[$i]}") 918 | unique_repo_names+=("${repo_names[$i]}") 919 | unique_urls+=("${url_array[$i]}") 920 | fi 921 | done 922 | 923 | declare -a sorted_indices=() 924 | readarray -t sorted_indices < <( 925 | for i in "${!unique_display_names[@]}"; do 926 | echo "$i ${unique_display_names[$i]}" 927 | done | sort -k2 | cut -d' ' -f1 928 | ) 929 | 930 | declare -a final_display_names=() 931 | declare -a final_repo_names=() 932 | declare -a final_urls=() 933 | 934 | for i in "${sorted_indices[@]}"; do 935 | final_display_names+=("${unique_display_names[$i]}") 936 | final_repo_names+=("${unique_repo_names[$i]}") 937 | final_urls+=("${unique_urls[$i]}") 938 | done 939 | 940 | display_names=("${final_display_names[@]}") 941 | repo_names=("${final_repo_names[@]}") 942 | url_array=("${final_urls[@]}") 943 | 944 | if [ "${#display_names[@]}" -eq 0 ]; then 945 | if [[ "$bench_version" == "version-15" || "$bench_version" == "develop" ]]; then 946 | echo -e "${RED}No apps with pyproject.toml found that are compatible with $bench_version.${NC}" 947 | echo -e "${YELLOW}ERPNext v15/develop requires apps to have pyproject.toml files.${NC}" 948 | else 949 | echo -e "${RED}No valid Frappe apps found in awesome-frappe README.${NC}" 950 | fi 951 | rm -rf "$tmp_dir" 952 | else 953 | if [[ "$bench_version" == "version-15" || "$bench_version" == "develop" ]]; then 954 | echo -e "${GREEN}Found ${#display_names[@]} compatible apps with pyproject.toml for $bench_version.${NC}" 955 | else 956 | echo -e "${GREEN}Found ${#display_names[@]} available apps for $bench_version.${NC}" 957 | fi 958 | 959 | terminal_height=$(tput lines 2>/dev/null || echo 24) 960 | terminal_width=$(tput cols 2>/dev/null || echo 80) 961 | 962 | max_dialog_height=$((terminal_height - 4)) 963 | max_dialog_width=$((terminal_width - 10)) 964 | 965 | max_display_len=0 966 | for name in "${display_names[@]}"; do 967 | if (( ${#name} > 50 )); then 968 | name="${name:0:47}..." 969 | fi 970 | if (( ${#name} > max_display_len )); then 971 | max_display_len=${#name} 972 | fi 973 | done 974 | 975 | dialog_width=$((max_display_len + 25)) 976 | if (( dialog_width < 60 )); then 977 | dialog_width=60 978 | elif (( dialog_width > max_dialog_width )); then 979 | dialog_width=$max_dialog_width 980 | fi 981 | 982 | item_count=${#display_names[@]} 983 | dialog_height=$((item_count + 8)) 984 | if (( dialog_height > max_dialog_height )); then 985 | dialog_height=$max_dialog_height 986 | fi 987 | 988 | OPTIONS=() 989 | for i in "${!display_names[@]}"; do 990 | display_name="${display_names[$i]}" 991 | 992 | if (( ${#display_name} > 50 )); then 993 | display_name="${display_name:0:47}..." 994 | fi 995 | 996 | OPTIONS+=("$display_name" "" OFF) 997 | done 998 | 999 | CHOICES=$(whiptail --title "Additional Frappe Apps (${#display_names[@]} available)" \ 1000 | --checklist "Choose apps to install (Space=toggle, Enter=confirm):" \ 1001 | "$dialog_height" "$dialog_width" "$((dialog_height - 8))" \ 1002 | "${OPTIONS[@]}" 3>&1 1>&2 2>&3) || { 1003 | echo -e "${RED}No apps selected or dialog cancelled. Skipping additional apps installation.${NC}" 1004 | rm -rf "$tmp_dir" 1005 | } 1006 | 1007 | if [ -z "$CHOICES" ]; then 1008 | echo -e "${RED}No apps selected. Skipping additional apps installation.${NC}" 1009 | rm -rf "$tmp_dir" 1010 | else 1011 | eval "selected_display_names=($CHOICES)" 1012 | 1013 | echo -e "${GREEN}Selected ${#selected_display_names[@]} apps for installation.${NC}" 1014 | 1015 | installation_errors=() 1016 | successful_installations=() 1017 | 1018 | for selected_display_name in "${selected_display_names[@]}"; do 1019 | selected_repo="" 1020 | selected_url="" 1021 | 1022 | for idx in "${!display_names[@]}"; do 1023 | if [[ "${display_names[$idx]}" == "$selected_display_name" ]]; then 1024 | selected_repo="${repo_names[$idx]}" 1025 | selected_url="${url_array[$idx]}" 1026 | break 1027 | fi 1028 | done 1029 | 1030 | if [[ -z "$selected_url" ]]; then 1031 | echo -e "${RED}Could not find URL for \"$selected_display_name\". Skipping.${NC}" 1032 | installation_errors+=("$selected_display_name: URL not found") 1033 | continue 1034 | fi 1035 | 1036 | echo -e "${YELLOW}Installing \"$selected_display_name\" ($selected_repo)...${NC}" 1037 | echo -e "${LIGHT_BLUE}Repository: $selected_url${NC}" 1038 | 1039 | echo -e "${YELLOW}Step 1/2: Downloading app...${NC}" 1040 | 1041 | echo -e "${LIGHT_BLUE}🔄 Detecting optimal branch for $selected_repo...${NC}" 1042 | best_branch=$(detect_best_branch "$selected_url" "$bench_version" "$selected_repo") 1043 | 1044 | if [[ -z "$best_branch" ]]; then 1045 | echo -e "${RED}⚠ Could not detect any branches for $selected_repo. Skipping.${NC}" 1046 | installation_errors+=("$selected_display_name: No branches detected") 1047 | continue 1048 | fi 1049 | 1050 | echo -e "${GREEN}📌 Will install using branch: $best_branch${NC}" 1051 | echo "" 1052 | 1053 | download_success=false 1054 | 1055 | echo -e "${YELLOW}🔽 Downloading from branch '$best_branch'...${NC}" 1056 | if bench get-app "$selected_url" --branch "$best_branch" --skip-assets 2>/tmp/bench_error_$.log; then 1057 | download_success=true 1058 | echo -e "${GREEN}✅ Successfully downloaded \"$selected_display_name\" from branch '$best_branch'.${NC}" 1059 | else 1060 | echo -e "${RED}❌ Failed to download from branch '$best_branch'.${NC}" 1061 | if [[ -f /tmp/bench_error_$.log ]]; then 1062 | echo -e "${LIGHT_BLUE}Error details:${NC}" 1063 | tail -2 /tmp/bench_error_$.log 1064 | fi 1065 | fi 1066 | 1067 | if [ "$download_success" = true ]; then 1068 | echo -e "${YELLOW}Step 2/2: Installing to site...${NC}" 1069 | app_installed=false 1070 | 1071 | app_dir="apps/$selected_repo" 1072 | setup_py_path="$app_dir/setup.py" 1073 | 1074 | if [[ -f "$setup_py_path" ]]; then 1075 | extracted_app_name=$(extract_app_name_from_setup "$setup_py_path") 1076 | 1077 | if [[ -n "$extracted_app_name" ]]; then 1078 | echo -e "${LIGHT_BLUE}Found app name in setup.py: \"$extracted_app_name\"${NC}" 1079 | if bench --site "$site_name" install-app "$extracted_app_name" 2>/dev/null; then 1080 | echo -e "${GREEN}✓ Successfully installed using setup.py name.${NC}" 1081 | successful_installations+=("$selected_display_name (branch: $best_branch)") 1082 | app_installed=true 1083 | else 1084 | echo -e "${YELLOW}⚠ Setup.py name failed, trying alternatives...${NC}" 1085 | fi 1086 | else 1087 | echo -e "${YELLOW}⚠ Could not extract name from setup.py, trying alternatives...${NC}" 1088 | fi 1089 | fi 1090 | 1091 | if [[ "$app_installed" == false ]]; then 1092 | echo -e "${LIGHT_BLUE}Trying repo name: \"$selected_repo\"${NC}" 1093 | if bench --site "$site_name" install-app "$selected_repo" 2>/dev/null; then 1094 | echo -e "${GREEN}✓ Successfully installed using repo name.${NC}" 1095 | successful_installations+=("$selected_display_name (branch: $best_branch)") 1096 | app_installed=true 1097 | fi 1098 | fi 1099 | 1100 | if [[ "$app_installed" == false ]]; then 1101 | transformed_name=$(echo "$selected_repo" | sed -E 's/^(frappe[-_]?|erpnext[-_]?)//' | tr '-' '_' | tr '[:upper:]' '[:lower:]') 1102 | 1103 | if [[ "$transformed_name" != "$selected_repo" ]]; then 1104 | echo -e "${LIGHT_BLUE}Trying transformed name: \"$transformed_name\"${NC}" 1105 | if bench --site "$site_name" install-app "$transformed_name" 2>/dev/null; then 1106 | echo -e "${GREEN}✓ Successfully installed using transformed name.${NC}" 1107 | successful_installations+=("$selected_display_name (branch: $best_branch)") 1108 | app_installed=true 1109 | fi 1110 | fi 1111 | fi 1112 | 1113 | if [[ "$app_installed" == false ]]; then 1114 | lowercase_name=$(echo "$selected_repo" | tr '[:upper:]' '[:lower:]') 1115 | if [[ "$lowercase_name" != "$selected_repo" ]]; then 1116 | echo -e "${LIGHT_BLUE}Trying lowercase: \"$lowercase_name\"${NC}" 1117 | if bench --site "$site_name" install-app "$lowercase_name" 2>/dev/null; then 1118 | echo -e "${GREEN}✓ Successfully installed using lowercase name.${NC}" 1119 | successful_installations+=("$selected_display_name (branch: $best_branch)") 1120 | app_installed=true 1121 | fi 1122 | fi 1123 | fi 1124 | 1125 | if [[ "$app_installed" == false && -d "$app_dir" ]]; then 1126 | for subdir in "$app_dir"/*/; do 1127 | if [[ -d "$subdir" && -f "$subdir/__init__.py" ]]; then 1128 | potential_app_name=$(basename "$subdir") 1129 | if [[ "$potential_app_name" != "tests" && "$potential_app_name" != "docs" && "$potential_app_name" != "__pycache__" ]]; then 1130 | echo -e "${LIGHT_BLUE}Trying directory name: \"$potential_app_name\"${NC}" 1131 | if bench --site "$site_name" install-app "$potential_app_name" 2>/dev/null; then 1132 | echo -e "${GREEN}✓ Successfully installed using directory name.${NC}" 1133 | successful_installations+=("$selected_display_name (branch: $best_branch)") 1134 | app_installed=true 1135 | break 1136 | fi 1137 | fi 1138 | fi 1139 | done 1140 | fi 1141 | 1142 | if [[ "$app_installed" == false ]]; then 1143 | echo -e "${RED}✗ Failed to install \"$selected_display_name\" after trying all strategies.${NC}" 1144 | echo -e "${YELLOW}This app may have compatibility issues with ERPNext $bench_version or missing dependencies.${NC}" 1145 | installation_errors+=("$selected_display_name (branch: $best_branch): Installation failed (compatibility/dependency issues)") 1146 | fi 1147 | 1148 | rm -f /tmp/bench_error_$.log 1149 | else 1150 | if [[ -d "apps/$selected_repo" ]]; then 1151 | echo -e "${YELLOW}⚠ App was cloned but failed during pip install phase.${NC}" 1152 | echo -e "${RED}✗ \"$selected_display_name\" has dependency/compatibility issues with ERPNext $bench_version.${NC}" 1153 | 1154 | if [[ -f /tmp/bench_error_$.log ]]; then 1155 | echo -e "${LIGHT_BLUE}Error details:${NC}" 1156 | tail -3 /tmp/bench_error_$.log | grep -E "(ERROR|Failed|returned non-zero)" || echo "Check app requirements and compatibility." 1157 | fi 1158 | 1159 | installation_errors+=("$selected_display_name (branch: $best_branch): Dependency/compatibility issues") 1160 | else 1161 | echo -e "${RED}✗ Failed to clone \"$selected_display_name\" from repository.${NC}" 1162 | installation_errors+=("$selected_display_name (branch: $best_branch): Git clone failed") 1163 | fi 1164 | 1165 | rm -f /tmp/bench_error_$.log 1166 | fi 1167 | 1168 | echo -e "\n${LIGHT_BLUE}────────────────────────────────────────${NC}\n" 1169 | done 1170 | 1171 | echo -e "${GREEN}╔══════════════════════════════════════╗${NC}" 1172 | echo -e "${GREEN}║ Installation Summary ║${NC}" 1173 | echo -e "${GREEN}╚══════════════════════════════════════╝${NC}" 1174 | 1175 | if [ "${#successful_installations[@]}" -gt 0 ]; then 1176 | echo -e "${GREEN}✓ Successfully installed ${#successful_installations[@]} apps:${NC}" 1177 | for app in "${successful_installations[@]}"; do 1178 | echo -e " ${GREEN}✓${NC} $app" 1179 | done 1180 | echo "" 1181 | fi 1182 | 1183 | if [ "${#installation_errors[@]}" -gt 0 ]; then 1184 | echo -e "${RED}✗ Failed to install ${#installation_errors[@]} apps:${NC}" 1185 | for error in "${installation_errors[@]}"; do 1186 | echo -e " ${RED}✗${NC} $error" 1187 | done 1188 | echo "" 1189 | echo -e "${YELLOW}Note: Some apps may not be compatible with ERPNext $bench_version${NC}" 1190 | echo -e "${YELLOW}or may require specific dependencies that are not installed.${NC}" 1191 | fi 1192 | 1193 | rm -rf "$tmp_dir" 1194 | 1195 | if [ "${#successful_installations[@]}" -gt 0 ]; then 1196 | echo -e "${YELLOW}Restarting services to apply changes...${NC}" 1197 | sudo supervisorctl restart all 2>/dev/null || true 1198 | echo -e "${GREEN}Services restarted successfully.${NC}" 1199 | fi 1200 | fi 1201 | fi 1202 | fi 1203 | fi 1204 | fi 1205 | fi 1206 | ;; 1207 | *) 1208 | echo -e "${RED}Skipping additional apps installation.${NC}" 1209 | ;; 1210 | esac 1211 | 1212 | # 1213 | # ─── SSL SECTION ──────────────────────────────────────────────────────────────── 1214 | # 1215 | echo -e "${YELLOW}Would you like to install SSL? (yes/no)${NC}" 1216 | read -p "Response: " continue_ssl 1217 | continue_ssl=$(echo "$continue_ssl" | tr '[:upper:]' '[:lower:]') 1218 | 1219 | case "$continue_ssl" in 1220 | "yes"|"y") 1221 | echo -e "${YELLOW}Make sure your domain name is pointed to the IP of this instance and is reachable before you proceed.${NC}" 1222 | sleep 3 1223 | 1224 | if ! command -v certbot >/dev/null 2>&1; then 1225 | read -p "Enter your email address: " email_address 1226 | 1227 | echo -e "${YELLOW}Installing Certbot...${NC}" 1228 | sleep 1 1229 | if [ "$DISTRO" == "Debian" ]; then 1230 | echo -e "${YELLOW}Fixing openssl package on Debian...${NC}" 1231 | sleep 4 1232 | sudo pip3 uninstall cryptography -y 1233 | yes | sudo pip3 install pyopenssl==22.0.0 cryptography==36.0.0 1234 | echo -e "${GREEN}Package fixed${NC}" 1235 | sleep 2 1236 | fi 1237 | 1238 | sudo apt install snapd -y && \ 1239 | sudo snap install core && \ 1240 | sudo snap refresh core && \ 1241 | sudo snap install --classic certbot && \ 1242 | sudo ln -s /snap/bin/certbot /usr/bin/certbot 1243 | 1244 | echo -e "${GREEN}Certbot installed successfully.${NC}" 1245 | else 1246 | echo -e "${GREEN}Certbot is already installed. Skipping installation.${NC}" 1247 | sleep 1 1248 | read -p "Enter your email address: " email_address 1249 | fi 1250 | 1251 | echo -e "${YELLOW}Obtaining and installing SSL certificate...${NC}" 1252 | sleep 2 1253 | sudo certbot --nginx --non-interactive --agree-tos --email "$email_address" -d "$site_name" 1254 | echo -e "${GREEN}SSL certificate installed successfully.${NC}" 1255 | sleep 2 1256 | ;; 1257 | *) 1258 | echo -e "${RED}Skipping SSL installation...${NC}" 1259 | ;; 1260 | esac 1261 | 1262 | if [[ -z "$py_version" ]] || [[ "$py_major" -lt 3 ]] || [[ "$py_major" -eq 3 && "$py_minor" -lt 10 ]]; then 1263 | deactivate 1264 | fi 1265 | 1266 | echo -e "${GREEN}--------------------------------------------------------------------------------" 1267 | echo -e "Congratulations! You have successfully installed ERPNext $version_choice." 1268 | echo -e "You can start using your new ERPNext installation by visiting https://$site_name" 1269 | echo -e "(if you have enabled SSL and used a Fully Qualified Domain Name" 1270 | echo -e "during installation) or http://$server_ip to begin." 1271 | echo -e "Install additional apps as required. Visit https://docs.erpnext.com for Documentation." 1272 | echo -e "Enjoy using ERPNext!" 1273 | echo -e "--------------------------------------------------------------------------------${NC}" 1274 | ;; 1275 | *) 1276 | 1277 | echo -e "${YELLOW}Getting your site ready for development...${NC}" 1278 | sleep 2 1279 | source ~/.profile 1280 | if [[ "$bench_version" == "version-15" ]]; then 1281 | nvm alias default 18 1282 | else 1283 | nvm alias default 16 1284 | fi 1285 | bench use "$site_name" 1286 | bench build 1287 | echo -e "${GREEN}Done!${NC}" 1288 | sleep 5 1289 | 1290 | echo -e "${GREEN}-----------------------------------------------------------------------------------------------" 1291 | echo -e "Congratulations! You have successfully installed Frappe and ERPNext $version_choice Development Environment." 1292 | echo -e "Start your instance by running bench start to start your server and visiting http://$server_ip:8000" 1293 | echo -e "Install additional apps as required. Visit https://frappeframework.com for Developer Documentation." 1294 | echo -e "Enjoy development with Frappe!" 1295 | echo -e "-----------------------------------------------------------------------------------------------${NC}" 1296 | ;; 1297 | esac --------------------------------------------------------------------------------