Skip to content

Instantly share code, notes, and snippets.

@gutschilla
Last active March 6, 2021 12:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gutschilla/6674c7c4d69047ae8a3fb1854472dcb3 to your computer and use it in GitHub Desktop.
Save gutschilla/6674c7c4d69047ae8a3fb1854472dcb3 to your computer and use it in GitHub Desktop.
provisioning server for elixir
#! /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="platform-team@your.org"
export MAIL_HOST=smtp.your.org
export MAIL_USER=status-robot@your.org
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:somefancypassword@gitlab.com/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
Copy link

thenrio commented Apr 4, 2019

you may remove a subshell

~/tmp $ cat > bar <<EOF
> foo
> EOF
~/tmp $ cat bar
foo
~/tmp $ cat <<EOF > bar
> foobar
> EOF
~/tmp $ cat bar
foobar

@gutschilla
Copy link
Author

@thenrio Thanks a lot! Something learned, today

@gutschilla
Copy link
Author

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