Last active
March 26, 2025 11:31
-
-
Save d10r/523c315db414b128a085223ddd3f840b to your computer and use it in GitHub Desktop.
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 | |
# 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