Last active
November 8, 2024 22:09
-
-
Save gutschilla/6674c7c4d69047ae8a3fb1854472dcb3 to your computer and use it in GitHub Desktop.
provisioning server for elixir
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 | |
# I am exporting variables to have them in my shell after I ran the script. | |
# I find that convenient. Of course, those can/should be removed in "serious" deployments. | |
# create a time string between "1:00" and "5:59" | |
export REBOOT_TIME=`perl -e "print scalar(int 1 + 5 * rand) . q[:] . scalar(int 6 * rand) . scalar(int 9 * rand)"` | |
export HOSTNAME=`hostname` | |
export NETWORK_DEVICE=eth0 # might be different in cloud servers | |
## BE SURE SET THE VARIABLES BELOW! | |
# where and how to send system update emails | |
export EMAIL="[email protected]" | |
export MAIL_HOST=smtp.your.org | |
export [email protected] | |
export MAIL_PASS=s3cr3t | |
# how this shall be exposed behind nginx | |
export DOMAIN="super-awesome.developer" | |
export PROJECT=my-awesome-project | |
export IP=`ifconfig $NETWORK_DEVICE | grep "inet " | sed -E 's/^.*inet\s+([^ ]+).*$/\1/'` | |
# Many GIT services allow to creates deploy-tokens which are user/pass credentials that only allow read access. | |
export GIT_URL="https://gitlab+deploy-token-12345:[email protected]/gutschilla/$PROJECT.git" | |
## AND LET'S GO... | |
# add Erlang, Elixir repositories from Erlang Solutions => will install latest Elixir | |
echo "deb http://binaries.erlang-solutions.com/debian bionic contrib" >> /etc/apt/sources.list.d/erlang-solutions.list | |
wget https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc | |
sudo apt-key add erlang_solutions.asc | |
# add NodeJS repo => will install Node 8.x | |
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - | |
# upgrade system before we do anything | |
apt-get -y update | |
apt-get -y dist-upgrade | |
# UNATTENDED UPGRADES, as we don't want to do it ourselves | |
apt-get -y install unattended-upgrades s-nail | |
# tell APT to update | |
tee /etc/apt/apt.conf.d/10periodic <<'EOF' | |
APT::Periodic::Update-Package-Lists "1"; | |
APT::Periodic::Download-Upgradeable-Packages "1"; | |
APT::Periodic::AutocleanInterval "7"; | |
APT::Periodic::Unattended-Upgrade "1"; | |
EOF | |
# tell apt to only upgrade main and security | |
tee -a /etc/apt/apt.conf.d/50unattended-upgrades <<'EOF' | |
Unattended-Upgrade::Allowed-Origins { | |
"${distro_id}:${distro_codename}"; | |
"${distro_id}:${distro_codename}-security"; | |
"${distro_id}ESM:${distro_codename}"; | |
"${distro_id}:${distro_codename}-updates"; | |
}; | |
EOF | |
# allow reboots send results to some email address | |
tee -a /etc/apt/apt.conf.d/50unattended-upgrades <<EOF | |
Unattended-Upgrade::MinimalSteps "true"; | |
Unattended-Upgrade::Mail "$EMAIL"; | |
Unattended-Upgrade::Automatic-Reboot "true"; | |
Unattended-Upgrade::Automatic-Reboot-Time "$REBOOT_TIME"; | |
EOF | |
# confugure s-nail to send update result to email address | |
tee -a /etc/s-nail.rc <<EOF | |
set smtp-use-starttls | |
set smtp=smtp://$MAIL_HOST | |
set smtp-auth=login | |
set smtp-auth-user=$MAIL_USER | |
set smtp-auth-password=$MAIL_PASS | |
set from="$HOSTNAME <$MAIL_USER>" | |
EOF | |
# link to mail program, still need to figure why _sometimes_ mail and sometimes sendmail is used | |
ln -s $(which s-nail) /usr/local/bin/mail | |
ln -s $(which s-nail) /usr/bin/sendmail | |
# hint: run `unattended-upgrade -d` to see if it works | |
# install Elicir and NodeJS (used by Phoenix frontend) | |
apt-get -y install nodejs | |
apt-get -y install erlang-dev erlang-parsetools erlang-inets erlang-dev elixir | |
apt-get -y install nginx inotify-tools fish screen | |
# install app => /var/www/$PROJECT | |
cd /var/www | |
git clone $GIT_URL | |
cd $PROJECT | |
# get deps and compile | |
mix local.hex --force | |
mix local.rebar --force | |
mix deps.get | |
MIX_ENV=prod PORT=5000 mix compile | |
# Javascript dependencies and transpilation/minification | |
cd apps/signage_web/assets | |
npm install | |
# is this fails, use node_modules/webpack-cli/bin/webpack.js (older webpacks) | |
nodejs ./node_modules/webpack-cli/bin/cli.js --mode production | |
cd .. | |
mkdir priv/static | |
cd ../.. | |
MIX_ENV=prod mix phx.digest | |
# configure nginx frontend server for TLS | |
cd /root | |
git clone https://github.com/letsencrypt/letsencrypt | |
# disallow outdated ciphers to get a nice TLS rating at https://www.ssllabs.com/ssltest/ | |
tee /etc/nginx/snippets/ssl.include <<'EOF' | |
ssl_protocols TLSv1.2 TLSv1.1 TLSv1; | |
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:EECDH:EDH:!MD5:!RC4:!LOW:!MEDIUM:!CAMELLIA:!ECDSA:!DES:!DSS:!3DES:!NULL; | |
ssl_prefer_server_ciphers on; | |
ssl_ecdh_curve secp384r1; | |
ssl_session_cache shared:SSL:10m; | |
ssl_session_timeout 10m; | |
EOF | |
# these proxy settings serve me well but shall be tweaked to your needs | |
tee /etc/nginx/snippets/proxy-settings.include <<'EOF' | |
proxy_cache_revalidate on; | |
proxy_cache_min_uses 3; | |
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; | |
proxy_cache_background_update on; | |
proxy_cache_lock on; | |
EOF | |
# service to host letsencrypt domain validation files | |
mkdir /var/www/letsencrypt/ | |
mkdir /var/www/letsencrypt/root | |
tee /etc/nginx/conf.d/letsencrypt.conf <<'EOF' | |
server { | |
listen 80; | |
listen [::]:80; #IPv6 | |
server_name $DOMAIN; | |
location /.well-known { | |
root /var/www/letsencrypt/root; | |
} | |
} | |
EOF | |
systemctl reload nginx.service | |
# obtain TLS certificate for $DOMAIN | |
/root/letsencrypt/letsencrypt-auto certonly --non-interactive --agree-tos --email $EMAIL --domain $DOAIN --renew-by-default --webroot -w /var/www/letsencrypt/root/ | |
# renew our certificates every week. They are usable for 3 months and won't actually be renewed if not due. Remember to donate to EFF for this! | |
tee /etc/cron.weekly/renew-certs <<EOF | |
#!/bin/bash | |
/root/letsencrypt/letsencrypt-auto renew | |
systemctl reload nginx | |
EOF | |
chmod ug+x /etc/cron.weekly/renew-certs | |
# service to show a self-reloading down page, keeping the URL path to re-show right page when backend comes back | |
tee /etc/nginx/conf.d/down.conf <<EOF | |
server { | |
server_name 127.0.0.1; | |
listen 9000; | |
error_page 503 @down; | |
location / { | |
return 503; | |
} | |
location @down { | |
root /var/www/; | |
# do this for browser html pages only | |
if ( \$http_accept ~ html){ | |
rewrite ^ /down.html break; | |
} | |
return 503; | |
} | |
} | |
# plain http://$IP shall show down page | |
upstream down_test_backend { | |
server 127.0.0.1:9001 fail_timeout=1s; # is always down | |
server 127.0.0.1:9000 backup; # our backup service | |
} | |
server { | |
listen 80; | |
server_name $IP; | |
location / { | |
proxy_pass http://down_test_backend; | |
} | |
} | |
EOF | |
# a plain "down" page that tries to reload every 10 seconds, you might want something prettier. | |
tee /var/www/down.html <<EOF | |
<!DOCTYPE html> | |
<html> | |
<head><title>Uh - oh, a bad gateway</title><meta http-equiv="refresh" content="10" /></head> | |
<body><h1>$PROJECT is down for maintenance</h1><p>We will be back soon!</p></body> | |
</html> | |
EOF | |
# setup reverse proxy to phoenix backend, let nginx handle TLS | |
tee /etc/nginx/conf.d/backend.conf <<EOF | |
upstream phoenix_backend { | |
server 127.0.0.1:5000; | |
server 127.0.0.1:9000 backup; # "backup" shows the down page | |
} | |
server { | |
listen 443 ssl; | |
listen [::]:443 ssl; | |
server_name $DOMAIN; | |
ssl on; | |
include /etc/nginx/snippets/ssl.include; | |
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; | |
location / { | |
include /etc/nginx/snippets/proxy-settings.include; | |
add_header X-Cache-Status \$upstream_cache_status; | |
proxy_set_header Host \$host; | |
proxy_set_header X-Real-IP \$remote_addr; | |
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | |
proxy_pass http://phoenix_backend; | |
} | |
# let nginx directly deliver static content | |
location ~ /(:?images|js|css|favicon.ico|robots.txt) { | |
root /var/www/digital-signage-displays/apps/signage_web/priv/static; | |
} | |
# if you use websockets | |
location /socket { | |
proxy_pass http://phoenix_backend; | |
proxy_http_version 1.1; | |
proxy_set_header Upgrade \$http_upgrade; | |
proxy_set_header Connection "Upgrade"; | |
} | |
} | |
EOF | |
systemctl reload nginx.service | |
# link to direct subdir for easy access after login | |
cd /root | |
ln -s /var/www/$PROJECT | |
cd $PROJECT | |
# build release. Omit this line if you do not want a release | |
MIX_ENV=prod PORT=5000 mix release | |
# make release's var directory ownded by www-data (the user that runs our service). Not needed w/o release | |
chown -R www-data:www-data /var/www/$PROJECT/_build/prod/rel/$PROJECT/var | |
# install systemd service | |
tee /lib/systemd/system/$PROJECT.service <<'EOF' | |
[Unit] | |
Description=$PROJECT | |
[Service] | |
Type=simple | |
User=www-data | |
Group=www-data | |
Restart=on-failure | |
Environment=LANG=en_US.UTF-8 | |
Environment=HOME=/var/www/$PROJECT | |
# the following two Environment lines are only required when NOT using a release: | |
# Environment=MIX_ENV=prod | |
# Environment=PORT=5000 | |
WorkingDirectory=/var/www/$PROJECT | |
# No releases? Use this line instead and !REMOVE the ExecStop line! | |
# ExecStart=/usr/bin/iex -S mix phx.server | |
ExecStart=/var/www/$PROJECT/_build/prod/rel/$PROJECT/bin/$PROJECT foreground | |
ExecStop=/var/www/$PROJECT/_build/prod/rel/$PROJECT/bin/$PROJECT stop | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
systemctl enable $PROJECT.service | |
systemctl start $PROJECT.service | |
# done |
@thenrio Thanks a lot! Something learned, today
I just used tee. Reason is that I can prefix this (in other scripts) with sudo
which doesn't work with cat
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
you may remove a subshell