Last active
September 21, 2021 10:51
-
-
Save andris9/e6635ec1c041a1fb6a258950c674c067 to your computer and use it in GitHub Desktop.
Run node.js app as a systemd service
This file contains 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 | |
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 protected] | |
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 |
This file contains 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 | |
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