Skip to content

Instantly share code, notes, and snippets.

@andris9
Last active September 21, 2021 10:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andris9/e6635ec1c041a1fb6a258950c674c067 to your computer and use it in GitHub Desktop.
Save andris9/e6635ec1c041a1fb6a258950c674c067 to your computer and use it in GitHub Desktop.
Run node.js app as a systemd service
#!/bin/bash
SERVICE_NAME=$1
DOMAIN_NAME=$2
HTTP_PORT=$3
SERVICE_USER=${4:-www-data}
DEPLOY_KEY=$5
NODE_ENV=${6:-production}
DEPLOY_USER="deploy"
show_info () {
echo "Usage: $0 <service-name> <domain> <http-port> <service-user> <ssh-key> <node-env>"
echo "Where"
echo " <service-name> is the name of the service (required)"
echo " <http-port> is the port to bind to (required)"
echo " <domain> is the domain name of the service"
echo " <service-user> is optional useraname for the service (defaults to www-data)"
echo " <ssh-key> Optional SSH public key for the deploy user"
echo " <node-env> is optional node env for the service (defaults to production)"
}
if [[ -z $SERVICE_NAME ]]; then
show_info
exit
fi
if [[ -z $HTTP_PORT ]]; then
show_info
exit
fi
apt-get update
apt-get -q -y install lsb-release curl git nginx
NODEREPO="node_16.x"
CODENAME=`lsb_release -c -s`
# Install Node.js
if ! [ -x "$(command -v node)" ]; then
"Installing Node.js"
curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
echo "deb https://deb.nodesource.com/$NODEREPO $CODENAME main" > /etc/apt/sources.list.d/nodesource.list
echo "deb-src https://deb.nodesource.com/$NODEREPO $CODENAME main" >> /etc/apt/sources.list.d/nodesource.list
apt-get update
apt-get -q -y install nodejs
fi
# Ensure deploy user
if id "$DEPLOY_USER" >/dev/null 2>&1; then
echo "User $DEPLOY_USER already exists"
else
echo "Creating user $DEPLOY_USER"
useradd -s /bin/bash $DEPLOY_USER
mkdir -p /home/$DEPLOY_USER/.ssh
touch /home/$DEPLOY_USER/.ssh/authorized_keys
chown -R $DEPLOY_USER:$DEPLOY_USER /home/$DEPLOY_USER
fi
if [[ ! -z $DEPLOY_KEY ]]; then
if [ -f "/home/$DEPLOY_USER/.ssh/authorized_keys" ]; then
if grep -Fxq "$DEPLOY_KEY" "/home/$DEPLOY_USER/.ssh/authorized_keys"
then
echo "Key already exists in /home/$DEPLOY_USER/.ssh/authorized_keys"
else
echo $DEPLOY_KEY >> "/home/$DEPLOY_USER/.ssh/authorized_keys"
fi
else
echo "SSH key file not found for $DEPLOY_USER"
fi
fi
# Ensure service user
if id "$SERVICE_USER" >/dev/null 2>&1; then
echo "User $SERVICE_USER already exists"
else
echo "Creating user $SERVICE_USER"
useradd --system $SERVICE_USER
fi
# just in case the service already exists
systemctl stop $SERVICE_NAME 2>/dev/null || true
systemctl disable $SERVICE_NAME 2>/dev/null || true
systemctl daemon-reload
# delete old version if exists
rm -rf "/opt/${SERVICE_NAME}"
rm -rf "/var/opt/${SERVICE_NAME}.git"
rm -rf "/var/log/${SERVICE_NAME}"
rm -rf "/etc/systemd/system/${SERVICE_NAME}.service"
rm -rf "/etc/rsyslog.d/25-${SERVICE_NAME}.conf"
rm -rf "/etc/logrotate.d/${SERVICE_NAME}"
rm -rf "/etc/tmpfiles.d/${SERVICE_NAME}.conf"
mkdir -p "/opt/$SERVICE_NAME"
mkdir -p "/var/opt/${SERVICE_NAME}.git"
cd "/var/opt/${SERVICE_NAME}.git"
git init --bare
cat > "/var/opt/${SERVICE_NAME}.git/hooks/update" <<EOM
#!/bin/bash
NAME="${SERVICE_NAME}"
GIT_WORK_TREE="/opt/\$NAME"
GIT_WORK_TREE="\$GIT_WORK_TREE" git checkout "\$3" -f
cd "\$GIT_WORK_TREE"
if [ -f package.json ]; then
echo Installing npm packages
rm -rf package-lock.json
npm install --production --progress=false
else
echo npm package file not found
fi
sudo /bin/systemctl restart "\$NAME"
echo "Deployment complete"
EOM
chmod +x "/var/opt/${SERVICE_NAME}.git/hooks/update"
# add sudo rights for deploy user
echo "$DEPLOY_USER ALL = (root) NOPASSWD: /bin/systemctl start $SERVICE_NAME
$DEPLOY_USER ALL = (root) NOPASSWD: /bin/systemctl restart $SERVICE_NAME" > "/etc/sudoers.d/$SERVICE_NAME"
# Setup systemd service
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<EOM
[Unit]
Description=PostalSys ${SERVICE_NAME}
After=network.target
[Service]
Environment="HTTP_PORT=$HTTP_PORT"
Environment="NODE_CONFIG_PATH=/etc/${SERVICE_NAME}/${SERVICE_NAME}.toml"
Environment="NODE_ENV=production"
WorkingDirectory=/opt/${SERVICE_NAME}
User=$SERVICE_USER
Group=$SERVICE_USER
ExecStart=/usr/bin/npm start
ExecReload=/bin/kill -HUP $MAINPID
Type=simple
Restart=always
SyslogIdentifier=${SERVICE_NAME}
[Install]
WantedBy=multi-user.target
EOM
# Ensure required files and permissions
echo "d /var/log/${SERVICE_NAME} 0750 syslog adm
d /opt/${SERVICE_NAME} 0755 $DEPLOY_USER $DEPLOY_USER
d /var/opt/${SERVICE_NAME}.git 0755 $DEPLOY_USER $DEPLOY_USER
d /etc/${SERVICE_NAME} 0750 $SERVICE_USER $SERVICE_USER
f /etc/${SERVICE_NAME}/${SERVICE_NAME}.toml 0600 $SERVICE_USER $SERVICE_USER" > "/etc/tmpfiles.d/${SERVICE_NAME}.conf"
# Redirect log output from syslog to log file
echo "if ( \$programname startswith \"$SERVICE_NAME\" ) then {
action(type=\"omfile\" file=\"/var/log/${SERVICE_NAME}/${SERVICE_NAME}.log\")
stop
}" > "/etc/rsyslog.d/25-${SERVICE_NAME}.conf"
# Setup log rotate
echo "/var/log/${SERVICE_NAME}/${SERVICE_NAME}.log {
daily
ifempty
missingok
rotate 7
compress
create 640 syslog adm
su root root
sharedscripts
postrotate
systemctl kill --signal=SIGHUP --kill-who=main rsyslog.service 2>/dev/null || true
endscript
}" > "/etc/logrotate.d/${SERVICE_NAME}"
mkdir -p "/etc/${SERVICE_NAME}"
touch "/etc/${SERVICE_NAME}/${SERVICE_NAME}.toml"
# Run tmpfiles definitions to ensure required directories/files
systemd-tmpfiles --create --remove
# Restart rsyslog for the changes to take effect
systemctl restart rsyslog
# create dummy app
echo 'const http = require("http");
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World");
});
server.listen(' $HTTP_PORT ', () => {
console.log("Server listening on port %s", server.address().port);
});' > "/opt/${SERVICE_NAME}/server.js"
cat > "/opt/${SERVICE_NAME}/package.json" <<EOM
{
"name": "$SERVICE_NAME",
"private": true,
"version": "1.0.0",
"scripts": {
"start": "node server.js"
}
}
EOM
chown -R $DEPLOY_USER:$DEPLOY_USER "/opt/${SERVICE_NAME}"
chown -R $DEPLOY_USER:$DEPLOY_USER "/var/opt/${SERVICE_NAME}.git"
systemctl enable ${SERVICE_NAME}.service
# start the dummy HTTP service
systemctl restart ${SERVICE_NAME}.service
if [[ ! -z $DOMAIN_NAME ]]; then
cd ~
if [ ! -f /etc/ssl/certs/${DOMAIN_NAME}-fullchain.pem ]; then
# generate dummy certificate
openssl req -subj "/CN=${DOMAIN_NAME}/O=Postal Systems./C=EE" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout privkey.pem -out fullchain.pem
chmod 0600 privkey.pem
mv privkey.pem /etc/ssl/private/${DOMAIN_NAME}-privkey.pem
mv fullchain.pem /etc/ssl/certs/${DOMAIN_NAME}-fullchain.pem
fi
# generate nginx config
cat > "/etc/nginx/sites-available/${DOMAIN_NAME}.conf" <<EOM
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${DOMAIN_NAME};
ssl_certificate_key /etc/ssl/private/${DOMAIN_NAME}-privkey.pem;
ssl_certificate /etc/ssl/certs/${DOMAIN_NAME}-fullchain.pem;
#ssl_trusted_certificate /etc/ssl/certs/${DOMAIN_NAME}-chain.pem;
location / {
client_max_body_size 50M;
proxy_http_version 1.1;
proxy_redirect off;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Scheme \$scheme;
proxy_set_header Host \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:$HTTP_PORT;
}
# enforce HTTPS
if (\$scheme != "https") {
return 301 https://\$host\$request_uri;
}
}
EOM
# activate website
ln -s "/etc/nginx/sites-available/${DOMAIN_NAME}.conf" "/etc/nginx/sites-enabled/${DOMAIN_NAME}.conf"
nginx -t && systemctl reload nginx
#create certificate script
if ! [ -x "$(command -v /root/.acme.sh/acme.sh)" ]; then
# install acme.sh
curl https://get.acme.sh | sh -s email=andris@reinman.eu
fi
# create helper script to generate valid certificate using Let's Encrypt
cat > "certs-${DOMAIN_NAME}.sh" <<EOM
#!/bin/bash
/root/.acme.sh/acme.sh --issue --nginx --server letsencrypt \\
-d ${DOMAIN_NAME} \\
--key-file /etc/ssl/private/${DOMAIN_NAME}-privkey.pem \\
--ca-file /etc/ssl/certs/${DOMAIN_NAME}-chain.pem \\
--fullchain-file /etc/ssl/certs/${DOMAIN_NAME}-fullchain.pem \\
--reloadcmd "/bin/systemctl reload nginx" \\
--force
EOM
chmod +x "certs-${DOMAIN_NAME}.sh"
echo "Run ~/certs-${DOMAIN_NAME}.sh to activate Let's Encrypt certificate for ${DOMAIN_NAME}"
fi
#!/bin/bash
SERVICE_NAME=$1
DOMAIN_NAME=$2
DEPLOY_USER="deploy"
show_info () {
echo "Usage: $0 <service-name> <domain>"
echo "Where"
echo " <service-name> is the name of the service (required)"
echo " <domain> is the domain name of the service"
}
if [[ -z $SERVICE_NAME ]]; then
show_info
exit
fi
systemctl stop $SERVICE_NAME
systemctl disable $SERVICE_NAME
rm -rf "/opt/${SERVICE_NAME}"
rm -rf "/var/opt/${SERVICE_NAME}.git"
rm -rf "/var/log/${SERVICE_NAME}"
rm -rf "/etc/systemd/system/${SERVICE_NAME}.service"
rm -rf "/etc/rsyslog.d/25-${SERVICE_NAME}.conf"
rm -rf "/etc/logrotate.d/${SERVICE_NAME}"
rm -rf "/etc/tmpfiles.d/${SERVICE_NAME}.conf"
systemctl daemon-reload
if [[ ! -z $DOMAIN_NAME ]]; then
rm -rf "/etc/nginx/sites-available/${DOMAIN_NAME}.conf"
rm -rf "/etc/nginx/sites-enabled/${DOMAIN_NAME}.conf"
nginx -t && systemctl reload nginx
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment