Skip to content

Instantly share code, notes, and snippets.

@lambrospetrou
Created September 6, 2024 07:06
Show Gist options
  • Save lambrospetrou/aaaa13344f0026d810700f1bd2601cfd to your computer and use it in GitHub Desktop.
Save lambrospetrou/aaaa13344f0026d810700f1bd2601cfd to your computer and use it in GitHub Desktop.
Simple deployment on a VPS, Hetzner, EC2 with zero downtime. Uses Caddy and systemd.
#!/usr/bin/env bash
# Inspired from:
# - https://blog.wesleyac.com/posts/simple-deploy-script
# - https://gist.github.com/WesleyAC/b3aaa0292579158ad566c140415c875d
# - https://caddyserver.com/docs/running#using-the-service
set -e
# cd $(dirname $0)
if [ "$#" -ne 1 ]; then
echo "usage: $0 user@server-address"
exit 1
fi
SERVER_SSH=$1
SERVER_PATH=/opt/apps_workspace/monosource-server
BINARY_NAME="monosource-server"
SERVER_RESTART_COMMAND="systemctl restart $BINARY_NAME"
SYSTEMD_FILE="monosource-server.service"
SYSTEMD_DAEMONRELOAD_COMMAND="systemctl daemon-reload"
# https://caddyserver.com/docs/running#unit-files
CADDY_RESTART_COMMAND="systemctl reload caddy"
CADDYFILE="monosource-server-Caddyfile"
# Assume the script will be run inside the `server-src/` directory.
OUTFILE="./build/$BINARY_NAME"
ENVFILENAME=".env.prod.local"
ENVFILE="./build/$ENVFILENAME"
# COMMIT_HASH=$(git rev-parse HEAD)
COMMIT_HASH="commit_unknown"
BUILD_TIMESTAMP=$(TZ=UTC date -u +"%Y%m%d_%H%M%S")
FILE_HASH=$(b2sum $OUTFILE | cut -f1 -d' ')
REMOTE_FILENAME="$BINARY_NAME-$BUILD_TIMESTAMP-$COMMIT_HASH-$FILE_HASH"
echo "Deploying: $REMOTE_FILENAME"
# Copy necessary files from current version.
scp "$OUTFILE" "$SERVER_SSH:/tmp/$REMOTE_FILENAME"
scp "$ENVFILE" "$SERVER_SSH:/tmp/$REMOTE_FILENAME-$ENVFILENAME"
scp "_tools/files/etc/caddy/sites-enabled/$CADDYFILE" "$SERVER_SSH:/tmp/$REMOTE_FILENAME-$CADDYFILE"
scp "_tools/files/etc/systemd/system/$SYSTEMD_FILE" "$SERVER_SSH:/tmp/$REMOTE_FILENAME-$SYSTEMD_FILE"
# Put the latest files in the right directories and restart everything without downtime.
ssh -q -Tt $SERVER_SSH <<EOL
sudo nohup sh -c "\
mkdir -p $SERVER_PATH/versions/ $SERVER_PATH/current/ /etc/caddy/sites-enabled/ && \
mv "/tmp/$REMOTE_FILENAME-$CADDYFILE" "/etc/caddy/sites-enabled/$CADDYFILE" && \
$CADDY_RESTART_COMMAND && \
mv "/tmp/$REMOTE_FILENAME-$SYSTEMD_FILE" "/etc/systemd/system/$SYSTEMD_FILE" && \
$SYSTEMD_DAEMONRELOAD_COMMAND && \
mv "/tmp/$REMOTE_FILENAME-$ENVFILENAME" "$SERVER_PATH/current/$ENVFILENAME" && \
mv "/tmp/$REMOTE_FILENAME" "$SERVER_PATH/versions/$REMOTE_FILENAME" && \
chmod +x "$SERVER_PATH/versions/$REMOTE_FILENAME" && \
rm -f "$SERVER_PATH/current/$BINARY_NAME" && \
ln -s "$SERVER_PATH/versions/$REMOTE_FILENAME" "$SERVER_PATH/current/$BINARY_NAME" && \
$SERVER_RESTART_COMMAND"
EOL
echo "Deleting older versions, retaining the latest 10!"
# Cleanup old versions, and retain the last 10 deployed.
# For each version we copy the application binary in the /versions/ dir.
# So, in order to retain 10x versions we need to keep the top 10 lines when
# sorted with the latest files at the top, and start removing from line 11!
# Attention: If you have less than 10 deployments already this will fail, but it's fine.
ssh -q -Tt $SERVER_SSH <<EOL
sudo nohup sh -c "find "$SERVER_PATH/versions/" -type f -exec realpath {} \; | sort -r | tail -n +11 | sudo xargs rm"
EOL
# This Caddyfile assumes that the entry point Caddyfile is:
# $ cat /etc/caddy/Caddyfile
#
# import sites-enabled/*
#
# The above import line is the only thing needed in "/etc/caddy/Caddyfile".
:80, :443 {
reverse_proxy http://127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote}
# This gives 10s of buffering to allow zero downtime restarts of the service.
lb_try_duration 10s
}
request_body {
max_size 1M
}
}
[Unit]
Description=Monosource Server
After=network.target
[Service]
ExecStart=/opt/apps_workspace/monosource-server/current/monosource-server
User=appuser
Group=appadmins
WorkingDirectory=/opt/apps_workspace/monosource-server/current/
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment