Last active
January 26, 2025 18:14
-
-
Save FTKhanFT/b87ef1bcfff51814b45815ad708fdcc1 to your computer and use it in GitHub Desktop.
Deploy NodeJs/TS/Any Js framework app in ubuntu using pm2. Also supports Prisma ORM. Uses git url or project directory. Uses ecosystem.config.js file in the project directory to handle the pm2 management.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Author: Tanvir Ahmed Khan | |
# License: MIT License | |
# Github: https://github.com/ftkhanft | |
# Website: https://rrad.ltd | |
# Before running this make sure you have the ecosystem.config.js file in your project directory you cloned or in the git repo that you want to deploy. Referance file added below this script. | |
# Use the setup script added below this script or directly run in terminal: | |
# curl -s -L https://gist.githubusercontent.com/FTKhanFT/b87ef1bcfff51814b45815ad708fdcc1/raw/4f47ef626f2865b601cb81d628e4c89f04db1604/setup_script.sh | bash | |
# Then you will see a deploy.sh script in the current directory which you can use to deploy. | |
# Colors for console outputs | |
ERROR='\033[0;31m' | |
WARNING='\033[1;33m' | |
SUCCESS='\033[0;32m' | |
PURPLE='\033[0;35m' | |
NC='\033[0m' | |
# Function to display usage | |
usage() { | |
echo "Usage: ./deploy.sh" | |
echo "Options:" | |
echo " --dir reletive/path Specify directory to work on." | |
echo " --f To force deployment." | |
echo " --gu git_username Specify the Git username." | |
echo " --gp git_password Specify the Git password or access token." | |
echo " --prisma true/false Enable or disable Prisma commands (true/false)." | |
echo " --git-url git_url Specify the Git repository URL." | |
echo " --pm2-id id Specify the PM2 process ID to stop/restart. If id is not provided, will try to use ecosystem.config.js file. If none is found, will do nothing." | |
exit 1 | |
} | |
stopPm2(){ | |
# Check if PM2 is installed | |
if ! command -v pm2 &> /dev/null; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} PM2 is not installed. Skipping stop process..." | |
return | |
fi | |
# Stop PM2 processes based on options | |
if [[ -n "$PM2_ID" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Stopping PM2 process with ID: $PM2_ID..." | |
pm2 stop "$PM2_ID" | |
elif [[ -f "ecosystem.config.js" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Stopping PM2 processes using ecosystem.config.js..." | |
pm2 stop ecosystem.config.js | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} PM2 id or ecosystem.config.js not found. Skipping pm2 stop process..." | |
fi | |
} | |
restartPm2(){ | |
# Check if PM2 is installed | |
if ! command -v pm2 &> /dev/null; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} PM2 is not installed. Skipping restart process..." | |
return | |
fi | |
# Restart PM2 processes based on options | |
if [[ -n "$PM2_ID" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Restarting PM2 process with ID: $PM2_ID..." | |
pm2 restart "$PM2_ID" --time --update-env || { echo -e "${ERROR}PM2 restart failed"; exit 1; } | |
pm2 save | |
elif [[ -f "ecosystem.config.js" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Restarting PM2 processes using ecosystem.config.js..." | |
pm2 restart ecosystem.config.js --time --update-env || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}PM2 restart failed"; exit 1; } | |
pm2 save | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} PM2 id or ecosystem.config.js not found. Skipping pm2 restart process..." | |
fi | |
} | |
transformRepo(){ | |
local REMOTE_URL="$1" # Use the passed argument as the repository URL | |
# Determine the correct repository URL based on SSH configuration | |
if $SSH_CONFIGURED; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} SSH is configured. Using SSH URL." | |
AUTH_REPO_URL=$(echo "$REMOTE_URL" | sed "s#https://github.com/#[email protected]:#") | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} SSH is not configured. Using HTTPS." | |
if [[ "$REMOTE_URL" == [email protected]:* ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Repository url is an SSH URL but ssh with github is not configured. Exiting..." | |
exit 1 | |
fi | |
# Prompt for username and password if not provided | |
GIT_USERNAME=${GIT_USERNAME:-$DEPLOY_GIT_USERNAME} | |
GIT_PASSWORD=${GIT_PASSWORD:-$DEPLOY_GIT_PASSWORD} | |
if [[ -z "$GIT_USERNAME" ]]; then | |
read -p "Enter your Git username: " GIT_USERNAME | |
fi | |
if [[ -z "$GIT_PASSWORD" ]]; then | |
read -s -p "Enter your Git password: " GIT_PASSWORD | |
echo | |
fi | |
# Set AUTH_REPO_URL based on GIT_USERNAME and GIT_PASSWORD | |
if [[ -z "$GIT_USERNAME" || -z "$GIT_PASSWORD" ]]; then | |
AUTH_REPO_URL="$REMOTE_URL" | |
else | |
AUTH_REPO_URL=$(echo "$REMOTE_URL" | sed "s#https://#https://$GIT_USERNAME:$GIT_PASSWORD@#") | |
fi | |
fi | |
} | |
installNodeJs(){ | |
# Check if npm is installed, if not install it | |
if ! command -v npm &> /dev/null; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${WARNING}NodeJs is not installed. Installing NodeJs 18.x${NC}" | |
sudo apt update | |
curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh | |
sudo bash nodesource_setup.sh | |
sudo apt install nodejs || { echo -e "${PURPLE}[Deploy.sh]:${NC} Failed to install npm"; exit 1; } | |
rm nodesource_setup.sh | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${SUCCESS}NodeJs already installed.${NC}" | |
fi | |
} | |
installPm2(){ | |
# Check if package.json exists | |
if [[ ! -f "package.json" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} package.json does not exist. Skipping pm2 install..." | |
return | |
fi | |
installNodeJs | |
# Check if PM2_ID exists or ecosystem.config.js file exists | |
if [[ -z "$PM2_ID" && ! -f "ecosystem.config.js" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Neither PM2_ID nor ecosystem.config.js found. Skipping PM2 installation..." | |
return | |
fi | |
# Check if PM2 is installed, if not install it | |
if ! command -v pm2 &> /dev/null; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} PM2 is not installed. Installing PM2..." | |
npm install pm2@latest -g || { echo -e "${PURPLE}[Deploy.sh]:${NC} Failed to install PM2"; exit 1; } | |
# Run PM2 startup command with current username | |
CURRENT_USER=$(whoami) | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Setting up PM2 to run on startup..." | |
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u "$CURRENT_USER" --hp /home/"$CURRENT_USER" | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} pm2 already installed..." | |
fi | |
} | |
runNpmInstall(){ | |
# Check if package.json exists | |
if [[ ! -f "package.json" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} package.json does not exist. Skipping npm install..." | |
return | |
fi | |
# Check if GIT_URL is provided | |
if [[ -n "$GIT_URL" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} GIT_URL is provided. Installing npm dependencies..." | |
npm install || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Install dependency failed"; exit 1; } | |
else | |
# Check if package.json has changed from the previous commit or FORCED is false | |
if [[ "$FORCED" == true ]] || git diff --name-only HEAD^ HEAD | grep -q "^package.json$"; then | |
npm install || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Install dependency failed"; exit 1; } | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} No changes in package.json. Skipping npm install..." | |
fi | |
fi | |
} | |
is_stash_required() { | |
if [[ -n $(git status --porcelain) ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} There are local changes checking if need to stash..." | |
PULL_DRY_OUTPUT=$(git pull "$AUTH_REPO_URL" --dry-run 2>&1) | |
if echo "$PULL_DRY_OUTPUT" | grep -q "CONFLICT"; then | |
echo -e "${ERROR}------------Dry Run Output------------\n$PULL_DRY_OUTPUT\n------------Dry Run Output------------${NC}" | |
return 0 # Stashing is required | |
else | |
return 1 # Stashing is not required | |
fi | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} There are no local changes." | |
return 1 # No uncommitted changes, stashing not required | |
fi | |
} | |
# Default values | |
PRISMA=false | |
FORCED=false | |
# Parse arguments | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--dir) | |
TARGET_DIR="$2" | |
shift | |
shift | |
;; | |
--git-url) | |
GIT_URL="$2" | |
shift | |
shift | |
;; | |
--gu) | |
GIT_USERNAME="$2" | |
shift | |
shift | |
;; | |
--gp) | |
GIT_PASSWORD="$2" | |
shift | |
shift | |
;; | |
--pm2-id) | |
PM2_ID="$2" | |
shift | |
shift | |
;; | |
--prisma) | |
PRISMA="$2" | |
shift | |
shift | |
;; | |
--f) | |
FORCED=true | |
shift | |
;; | |
*) | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Unknown argument: $1" | |
usage | |
;; | |
esac | |
done | |
# Validate the input arguments | |
if [[ -z "$TARGET_DIR" && -z "$GIT_URL" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Error: Either target directory or Git URL is required.${NC}" | |
usage | |
fi | |
if [[ -n "$TARGET_DIR" && -n "$GIT_URL" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Error: Please provide either target directory or Git URL, not both.${NC}" | |
usage | |
fi | |
# Check if SSH is configured | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Checking if SSH is set up for GitHub..." | |
SSH_CONFIGURED=false | |
if ssh -T [email protected] 2>&1 | grep -q "successfully authenticated"; then | |
SSH_CONFIGURED=true | |
fi | |
# If GIT_URL is provided, check SSH and clone the repository | |
if [[ -n "$GIT_URL" ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Using Git URL: $GIT_URL" | |
transformRepo "$GIT_URL" | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Cloning repository from Git URL: $AUTH_REPO_URL" | |
git clone "$AUTH_REPO_URL" || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Failed to clone repository"; exit 1; } | |
TARGET_DIR=$(basename "$GIT_URL" .git) | |
# Navigate to the target directory | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Navigating to target directory: $TARGET_DIR" | |
cd "$TARGET_DIR" || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Failed to navigate to directory: $TARGET_DIR"; exit 1; } | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Using $TARGET_DIR" | |
# Navigate to the target directory | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Navigating to target directory: $TARGET_DIR" | |
cd "$TARGET_DIR" || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Failed to navigate to directory: $TARGET_DIR"; exit 1; } | |
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then | |
# Get the Git repository URL | |
REPO_URL=$(git remote get-url origin) | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${WARNING}$TARGET_DIR is not a Git repository.${NC}" | |
fi | |
if [ -z "$REPO_URL" ]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Failed to get remote repository URL. Skipping git pull.." | |
else | |
transformRepo "$REPO_URL" | |
# Stash local changes | |
if is_stash_required; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Stashing is required before pulling." | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${WARNING}Stashing local changes...${NC}" | |
git stash || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Failed to stash changes"; exit 1; } | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${SUCCESS}No need to stash local changes.${NC}" | |
fi | |
# Pull latest code from git | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Pulling latest code from git..." | |
PULL_OUTPUT=$(git pull "$AUTH_REPO_URL" 2>&1) | |
if echo "$PULL_OUTPUT" | grep -q "Already up to date."; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} No updates available. Repository is already up to date." | |
echo -e "${WARNING}------------Git pull Output------------\n$PULL_OUTPUT\n------------Git pull Output------------${NC}" | |
if [[ "$FORCED" == false ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${SUCCESS}Deployment Stopped." | |
exit 0; | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Redeploying..." | |
fi | |
elif echo "$PULL_OUTPUT" | grep -q "Updating [0-9a-f]\{7\}\.\.[0-9a-f]\{7\}"; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Git pull successful." | |
echo -e "${SUCCESS}------------Git pull Output------------\n$PULL_OUTPUT\n------------Git pull Output------------${NC}" | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Updated code. Deploying..." | |
elif [[ $? -ne 0 ]]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Git pull failed" | |
echo -e "${ERROR}------------Git pull Output------------\n$PULL_OUTPUT\n------------Git pull Output------------${NC}" | |
while true; do | |
read -p "Do you want to proceed with the deployment anyway? (y/n): " choice | |
case "$choice" in | |
y|Y ) echo -e "${PURPLE}[Deploy.sh]:${NC} Proceeding with the deployment..."; break;; | |
n|N ) exit 1;; | |
* ) echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Invalid choice. Please enter 'y' or 'n'.${NC}";; | |
esac | |
done | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${SUCCESS}Git pull successful.${NC}" | |
fi | |
fi | |
installPm2 | |
stopPm2 | |
fi | |
runNpmInstall | |
# Run Prisma commands if required | |
if [[ "$PRISMA" == "true" ]]; then | |
# Check if Prisma is listed in package.json | |
if grep -q '"@prisma/client":' package.json && npm list @prisma/client &> /dev/null; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Running Prisma commands..." | |
npx prisma generate || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Prisma generate failed"; exit 1; } | |
npx prisma db push || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Prisma db push failed"; exit 1; } | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Prisma is not installed or not listed in package.json. Skipping Prisma commands." | |
fi | |
fi | |
# Build the project | |
if [ ! -f package.json ]; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} package.json not found. Skipping build step..." | |
elif grep -q '"build":' package.json; then | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Building the project..." | |
npm run build || { echo -e "${PURPLE}[Deploy.sh]:${NC} ${ERROR}Build failed"; exit 1; } | |
else | |
echo -e "${PURPLE}[Deploy.sh]:${NC} Build command not found in package.json. Skipping build step..." | |
fi | |
restartPm2 | |
echo -e "${PURPLE}[Deploy.sh]:${NC} ${SUCCESS}Deployment complete!" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a demo ecosystem file that runs "npm run start" when pm2 starts this. Modify it as per need. | |
// PM2 Doc: https://pm2.io/docs/runtime/reference/ecosystem-file/ | |
module.exports = { | |
apps: [ | |
{ | |
name: "Awesome API", | |
script: "npm", | |
args: "start", | |
namespace: "backend", | |
env: { | |
NODE_ENV: "production", | |
}, | |
}, | |
], | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Run this script in the root directory where you want to clone your projects from git or already cloned project directory exists. | |
# This will clone the deployment script from the gist and make it executable. | |
# Export git credentials so that you don't have to pass them as argument everytime. | |
# Uncomment the fields you want to setup. | |
# or run this directly from the url and the menualy set the credentials. | |
# curl -s -L https://gist.githubusercontent.com/FTKhanFT/b87ef1bcfff51814b45815ad708fdcc1/raw/4f47ef626f2865b601cb81d628e4c89f04db1604/setup_script.sh | bash | |
# Git Username | |
# export DEPLOY_GIT_USERNAME= | |
# Git personal access token | |
# export DEPLOY_GIT_PASSWORD= | |
GIT_URL=https://gist.github.com/b87ef1bcfff51814b45815ad708fdcc1.git | |
git clone "$GIT_URL" | |
TARGET_DIR=$(basename "$GIT_URL" .git) | |
mv "$TARGET_DIR/deploy.sh" . | |
rm -rf "$TARGET_DIR" | |
chmod +x deploy.sh |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment