Skip to content

Instantly share code, notes, and snippets.

@d10r
Last active March 26, 2025 11:31
Show Gist options
  • Save d10r/523c315db414b128a085223ddd3f840b to your computer and use it in GitHub Desktop.
Save d10r/523c315db414b128a085223ddd3f840b to your computer and use it in GitHub Desktop.
#!/bin/bash
# Script for deploying nodejs applications to the SF cloud ™.
# Requirements:
# - ssh access
# - application name in package.json or provided as env var APP_NAME
# - git url in package.json or provided as env var GIT_URL
# - package-lock.json
# - npm scripts "build" and "start"
# Optional:
# - .nvmrc file signalling the wanted node version
# - .env.prod (or file specified in env var ENV_FILE) with env vars to be used in production
# - custom install command(s) in env var INSTALL_CMD (default: npm ci && npm run build)
# - NO_SERVICE env var to skip systemd service setup and start
# - cron.json file to set up cron jobs (see documentation for format)
#
# cron.json example:
# [
# {
# "schedule": "12 * * * *",
# "command": "API_KEY=123 ./do-something.sh"
# }
# ]
#
# Note that the command is executed with the project root as working directory.
#
# When running for an existing app, it will:
# - update the .env file based on the local .env.prod
# - update the repo
# - update dependencies (npm ci)
# - build the app (npm run build)
# - restart the service
# - update cron jobs (if cron.json exists)
#
# When running for a new app, it will additionally first:
# - clone the repo
# - create a user systemd unit
set -eu
[email protected]
APP_NAME=${APP_NAME:-$(cat package.json | jq -r '.name')}
GIT_URL=${GIT_URL:-$(cat package.json | jq -r '.repository.url')}
ENV_FILE=${ENV_FILE:-.env.prod}
INSTALL_CMD=${INSTALL_CMD:-"node --version && npm ci && npm run build"}
SERVICE_FILE=$APP_NAME.service
NO_SERVICE=${NO_SERVICE:-""}
CRON_FILE=${CRON_FILE:-"cron.json"}
echo "APP_NAME: $APP_NAME"
if [ -z "$APP_NAME" ] || [[ "$APP_NAME" =~ ' ' ]]; then
echo "Invalid app name"
exit 1
fi
echo "GIT_URL: $GIT_URL"
if [ -z "$GIT_URL" ]; then
echo "Invalid git url"
exit 1
fi
echo "ENV_FILE: $ENV_FILE"
if [ ! -f "$ENV_FILE" ]; then
echo "env file not found"
exit 1
fi
echo "INSTALL_CMD: $INSTALL_CMD"
# if there's a local env file (as specified), copy it to the server with the name .env.$APP_NAME
if [ -f "$ENV_FILE" ]; then
echo "Using $ENV_FILE for the production environment"
scp $ENV_FILE $SSH_HOST:~/.env.$APP_NAME
fi
# if there's a cron.json file, copy it to the server
if [ -f "$CRON_FILE" ]; then
echo "Found $CRON_FILE, copying to server"
scp $CRON_FILE $SSH_HOST:~/.cron.$APP_NAME.json
fi
# if any of the commands fails, don't continue
ssh -q -T $SSH_HOST "/bin/bash --noprofile --norc" <<EOF
set -e
set -u
. ./.profile
. .nvm/nvm.sh
# (conditional) first time setup
# if directory with app name doesn't exist, clone the repo
if [ ! -d "$APP_NAME" ]; then
echo "Cloning repo"
git clone $GIT_URL $APP_NAME
fi
# if service file doesn't exist and NO_SERVICE is not set, copy it from the template
if [ -z "$NO_SERVICE" ] && [ ! -f "services/$SERVICE_FILE" ]; then
echo "Copying service file"
cp template.service services/$SERVICE_FILE
sed -i "s|Description=|Description=$APP_NAME|" services/$SERVICE_FILE
sed -i "s|WorkingDirectory=|WorkingDirectory=/home/dapps/$APP_NAME|" services/$SERVICE_FILE
systemctl --user daemon-reload
sleep 1
systemctl --user enable $SERVICE_FILE
fi
# regular update
# if there's an .env.$APP_NAME, use it
if [ -f ".env.$APP_NAME" ]; then
echo "Using .env.$APP_NAME for the production environment"
cp .env.$APP_NAME $APP_NAME/.env
rm .env.$APP_NAME
else
echo "No .env.$APP_NAME found"
fi
cd $APP_NAME
git pull
if [ -f .nvmrc ]; then
nvm use
else
echo "no .nvmrc provided, using default node version"
fi
$INSTALL_CMD
cd
# Handle cron jobs setup if cron.json exists
if [ -f ".cron.$APP_NAME.json" ]; then
echo "Setting up cron jobs from .cron.$APP_NAME.json"
# Create new crontab: keep existing entries but remove this app's entries
(crontab -l 2>/dev/null | grep -v "# $APP_NAME job" || echo "") > /tmp/crontab
# Add new entries for this app
jq -r '.[] | .schedule + " cd /home/dapps/'$APP_NAME' && " + .command + " # '$APP_NAME' job"' ".cron.$APP_NAME.json" >> /tmp/crontab
# Install the new crontab
crontab /tmp/crontab
rm /tmp/crontab ".cron.$APP_NAME.json"
echo "Cron jobs updated successfully"
fi
# Only restart and check service status if NO_SERVICE is not set
if [ -z "$NO_SERVICE" ]; then
systemctl --user restart $SERVICE_FILE
systemctl --user -n 50 status $SERVICE_FILE
fi
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment