Skip to content

Instantly share code, notes, and snippets.

@ccjmne
Last active October 24, 2022 19:38
Show Gist options
  • Save ccjmne/3df97a118de564b36302ef4dc92eacfe to your computer and use it in GitHub Desktop.
Save ccjmne/3df97a118de564b36302ef4dc92eacfe to your computer and use it in GitHub Desktop.
Amhydro setup

Amhydro Setup

Procedure for deploying all the services that support Amhydro's administration website.

Prepare

  1. in EC2: launch an instance of type: Amazon Linux 2 AMI.
    • Make sure it has at least the ports 22, 80 and 443 open.
    • OPTIONAL: also open 3306 for direct database access.
    • OPTIONAL: in "Instance Details", set up its User Data as such:
      #!/bin/bash
      
      groupadd docker
      usermod -a -G docker ec2-user
      
  2. in EC2: associate an Elastic IP address to it.
  3. in Route 53: add two type A records (one for WordPress, one for WEBDEV) that point to the elastic IP.

Install

Connect to your machine:

ssh -i /path/to/your/key.pem ec2-user@<host>

Set up and deploy in a single command:

bash <(curl -s https://gist.githubusercontent.com/ccjmne/3df97a118de564b36302ef4dc92eacfe/raw/setup.sh)

#!/bin/bash
set -e
trap ctrl_c SIGINT
function ctrl_c {
echo -e "\e[0m"
exit 1;
}
function red {
echo -e "\e[91m$1\e[0m"
}
function green {
echo -e "\e[92m$1\e[0m"
}
function cyan {
echo -e "\e[96m$1\e[0m"
}
function u {
echo -e "\e[4;96m$1\e[0m"
}
OK=" [ `green ok` ] "
KO="[ `red fatal` ] "
NF=" [ `cyan info` ] "
IN="[ `cyan input` ] "
function separator {
echo " [ ... ] ======================================================================"
}
function newline {
echo " [ ... ]"
}
function ok {
echo -e "${OK}$1"
}
function ko {
echo -e "${KO}$1"
}
function info {
echo -e "${NF}$1"
}
# Usage: ask <prompt> <varname>
function ask {
read -re -p "$(echo -e "${IN}$1 \e[0;96m")" -i "${!2}" $2 && printf "\e[0m"
}
# Persists/update environment variable to /home/ec2-user/.bash_profile
# Usage: saveenv <varname>
function saveenv {
# Escape & and \1 through \9, which have special meaning in the sed replacement string
SED_REPLACEMENT=`echo ${!1} | sed 's/[&\]/\\\\&/g'`
grep -q "^export $1=" /home/ec2-user/.bash_profile \
&& sed -i "s/^export $1=.*/export $1=\'$SED_REPLACEMENT\'/g" /home/ec2-user/.bash_profile \
|| echo "export $1='${!1}'" >> /home/ec2-user/.bash_profile
}
function okdone {
echo -e "\e[1A\r${OK}\r\e[74C`green ' done'`."
}
function koerror {
echo -e "\e[1A\r${KO}\r\e[73C`red ' error'`."
}
trap 'catch $? $LINENO' ERR
catch() {
ko "Error $1 occurred on line $2"
}
################################################################################
# #
# CONFIGURATION #
# #
################################################################################
# Ensure current user belongs to 'docker' group
if ! id --groups --name --zero | grep --null-data --line-regexp --quiet docker
then
sudo groupadd --force docker
sudo usermod -a -G docker ec2-user
info "The UserData script was not properly set up for this machine."
info "Please run this script once more."
read -re -n 1 -p "$(echo -e "${IN}Press any key to continue...")"
relog
fi
ask "WEBDEV domain name?" WD_HOST_NAME
if [[ ! $WD_HOST_NAME =~ ^([a-z0-9-]+\.)+[a-z]{2,6}$ ]]; then
koerror && ko 'Invalid domain name. Have you included the TLD?'
exit 1
else
okdone && saveenv WD_HOST_NAME;
fi
ask "WEBDEV site name?" WD_SITE_NAME
if [[ ! $WD_SITE_NAME =~ ^[a-zA-Z0-9]+$ ]]; then
koerror && ko 'Invalid WEBDEV site name.'
exit 1
else
okdone && saveenv WD_SITE_NAME;
fi
ask "WordPress domain name?" WP_HOST_NAME
if [[ ! $WP_HOST_NAME =~ ^([a-z0-9-]+\.)+[a-z]{2,6}$ ]]; then
koerror && ko 'Invalid domain name. Have you included the TLD?'
exit 1
else
okdone && saveenv WP_HOST_NAME;
fi
ask "WordPress database name?" WP_DB_NAME
if [[ -z $WP_DB_NAME ]]; then
koerror && ko 'Database name must not be empty.'
exit 1
else
okdone && saveenv WP_DB_NAME;
fi
ask "Database server root password?" DB_PWD
if [[ -z $DB_PWD ]]; then
koerror && ko 'Password must not be empty.'
exit 1
else
okdone && saveenv DB_PWD $DB_PWD;
fi
ask "Admin e-mail for SSL notices?" ADMIN_EMAIL
if [[ -z $ADMIN_EMAIL ]]; then
koerror && ko "The admin's e-mail must be communicated."
exit 1
else
okdone && saveenv ADMIN_EMAIL;
fi
ask "Access token for pCloud storage?" PCLOUD_TOKEN
if [[ -z $PCLOUD_TOKEN ]]; then
koerror && ko "The access token for the pCloud storage is required."
exit 1
else
okdone && saveenv PCLOUD_TOKEN;
fi
trap relog EXIT
# Reload .bash_profile w/ persisted environment variables
function relog {
exec sudo su -l $USER
}
################################################################################
# #
# SERVICES INSTALL #
# #
################################################################################
separator
info 'Preparing installation...'
sudo yum update -y -q > /dev/null 2>&1
okdone
# Docker setup
info 'Setting up Docker...'
sudo amazon-linux-extras install docker -y -q > /dev/null 2>&1
sudo systemctl start docker.service
sudo systemctl enable docker
okdone
# docker-compose setup
info 'Installing docker-compose...'
sudo curl -Ls "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
tee /home/ec2-user/zzz-custom.php.ini > /dev/null <<EOF
post_max_size = 256M
upload_max_filesize = 256M
EOF
tee /home/ec2-user/docker-compose.yml > /dev/null <<EOF
version: "3"
services:
db:
image: mariadb:latest
container_name: mariadb
restart: always
volumes:
- db-data:/var/lib/mysql
ports:
- 3306:3306
environment:
# Escape dollar-signs in password w/ "$$"
# See https://docs.docker.com/compose/compose-file/compose-file-v3/#variable-substitution
MYSQL_ROOT_PASSWORD: `sed -re 's/\\$/$$/g' <<< ${DB_PWD}` #
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: phpmyadmin
restart: always
ports:
- 8000:80
environment:
PMA_ABSOLUTE_URI: https://${WD_HOST_NAME}/phpmyadmin/
UPLOAD_LIMIT: 256M
depends_on:
- db
webdev-server:
image: ccjmne/webdev-mariadb
container_name: webdev-server
restart: always
volumes:
- webdev-data:/var/lib/WEBDEV/26.0/
ports:
- 8001:80
depends_on:
- db
wordpress:
image: wordpress:latest
container_name: wordpress
restart: always
volumes:
- wordpress-data:/var/www/html
- /home/ec2-user/zzz-custom.php.ini:/usr/local/etc/php/conf.d/zzz-custom.php.ini
- /home/ec2-user/pcloud:/mnt/pcloud
ports:
- 8002:80
environment:
# Un-comment the next line to enable debugging
# WORDPRESS_DEBUG: 1
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: root
WORDPRESS_DB_NAME: ${WP_DB_NAME}
# Escape dollar-signs in password w/ "$$"
# See https://docs.docker.com/compose/compose-file/compose-file-v3/#variable-substitution
WORDPRESS_DB_PASSWORD: `sed -re 's/\\$/$$/g' <<< ${DB_PWD}`
depends_on:
- db
volumes:
db-data:
webdev-data:
wordpress-data:
EOF
okdone
# NGINX setup
# TODO: Migrate to a docker image for NGINX w/ certbot
# See https://hub.docker.com/r/staticfloat/nginx-certbot/
info 'Installing NGINX...'
sudo amazon-linux-extras install nginx1 -y -q > /dev/null 2>&1
sudo tee /etc/nginx/conf.d/default.conf > /dev/null <<EOF
server {
# WEBDEV server
server_name ${WD_HOST_NAME};
client_max_body_size 0;
gzip on;
gzip_types text/css text/plain application/javascript application/json image/png;
include /etc/nginx/proxy_params.conf;
location /wd-admin {
return 301 https://\$host/WDAdminWeb;
}
location = / {
return 301 https://\$host/${WD_SITE_NAME};
}
location /phpmyadmin/ {
proxy_pass http://127.0.0.1:8000/;
}
location / {
proxy_pass http://127.0.0.1:8001;
}
}
server {
# WordPress server
server_name ${WP_HOST_NAME};
server_name www.${WP_HOST_NAME};
client_max_body_size 0;
gzip on;
gzip_types text/css text/plain application/javascript application/json image/png;
include /etc/nginx/proxy_params.conf;
location /phpmyadmin/ {
proxy_pass http://127.0.0.1:8000/;
}
location / {
proxy_pass http://127.0.0.1:8002/;
}
}
EOF
sudo tee /etc/nginx/proxy_params.conf > /dev/null <<EOF
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_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Forwarded-Host \$host;
proxy_set_header X-Forwarded-Port \$server_port;
EOF
sudo systemctl enable nginx > /dev/null 2>&1
sudo systemctl restart nginx
okdone
# Certbot setup
info 'Installing Certbot...'
# Install extra EPEL repositories for certbot
sudo amazon-linux-extras install epel -y -q > /dev/null 2>&1
sudo yum install epel-release -y -q > /dev/null 2>&1
sudo yum install certbot python2-certbot-nginx -y -q > /dev/null 2>&1
okdone
info 'Requesting TLS certificates...'
sudo certbot --nginx --non-interactive --agree-tos --email $ADMIN_EMAIL --domains $WP_HOST_NAME --domains www.$WP_HOST_NAME --domains $WD_HOST_NAME --expand --quiet
sudo systemctl restart nginx
okdone
info 'Setting up auto-renewal of TLS certificates...'
# From https://certbot.eff.org/docs/using.html#setting-up-automated-renewal
SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}'); echo "0 0,12 * * * root sleep $SLEEPTIME && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
okdone
info 'Setting up 1GiB of swap...'
if ! free | awk '/^Swap:/ {exit !$2}'; then
# create swap
# https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-16-04
sudo fallocate -l 1G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
# make the swap file permanent
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
fi
okdone
# rclone setup
info 'Setting up rclone...'
sudo yum install rclone -y -q
sudo tee /etc/fuse.conf > /dev/null <<EOF
user_allow_other
EOF
sudo tee /home/ec2-user/.config/rclone/rclone.conf > /dev/null <<EOF
[pcloud]
type = pcloud
hostname = eapi.pcloud.com
token = {"access_token":"${PCLOUD_TOKEN}","token_type":"bearer","expiry":"0001-01-01T00:00:00Z"}
EOF
# create systemd service to mount on reboot
# see https://github.com/rclone/rclone/wiki/Systemd-rclone-mount#systemd
sudo tee /etc/systemd/system/rclone-pcloud.service > /dev/null <<EOF
[Unit]
Description=Mount pCloud storage with rclone
After=network-online.target
[Service]
Type=notify
KillMode=none
ExecStart=/usr/bin/rclone mount pcloud: /home/ec2-user/pcloud \
--allow-other \
--vfs-cache-mode full
ExecStop=/bin/fusermount -uz /home/ec2-user/pcloud
User=ec2-user
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
okdone
info 'Mounting pCloud disk...'
mkdir --parents /home/ec2-user/pcloud
sudo systemctl start rclone-pcloud
sudo systemctl enable rclone-pcloud
okdone
info 'Creating all services...'
# only time we use docker as superuser, before ec2-user relogs
docker-compose up --detach > /dev/null 2>&1
sudo tee /home/ec2-user/get-password.sh > /dev/null <<EOF
#!/bin/bash
docker logs webdev-server 2>&1 | sed -n 3p
EOF
sudo chmod +x /home/ec2-user/get-password.sh
okdone
################################################################################
# #
# POST-INSTALL #
# #
################################################################################
sudo tee /etc/update-motd.d/30-banner > /dev/null <<EOF
cat <<EOL
$(
separator
# Credit: https://manytools.org/hacker-tools/ascii-banner/
# Font: Georgia11
echo -e ""
echo -e " ,, ,, "
echo -e " db \\\`7MM \\\`7MM "
echo -e " ;MM: MM MM "
echo -e " ,V^MM. \\\`7MMpMMMb.pMMMb. MMpMMMb.\\\`7M' \\\`MF' ,M\"\"bMM \\\`7Mb,od8 ,pW\"Wq. "
echo -e " ,M \\\`MM MM MM MM MM MM VA ,V ,AP MM MM' \"'6W' \\\`Wb"
echo -e " AbmmmqMA MM MM MM MM MM VA ,V 8MI MM MM 8M M8"
echo -e " A' VML MM MM MM MM MM VVV \\\`Mb MM MM YA. ,A9"
echo -e ".AMA. .AMMA..JMML JMML JMML..JMML JMML. ,V \\\`Wbmd\"MML..JMML. \\\`Ybmd9' "
echo -e " ,V "
echo -e " OOb\" "
echo -e ""
separator
newline
info "Site `cyan $WD_SITE_NAME` published at `u "https://${WD_HOST_NAME}"`"
info "`cyan WordPress` site published at `u "https://${WP_HOST_NAME}"`"
info "Start/restart services with: `cyan 'docker-compose restart'`"
newline
separator
newline
info "WEBDEV remote admin console"
info " - Address : `u "https://${WD_HOST_NAME}/wd-admin"`"
info " - Username : `cyan webdevuser`"
info " - Password : \e[96m`/home/ec2-user/get-password.sh`\e[0m"
newline
info "WordPress remote admin console"
info " - Address : `u "https://${WP_HOST_NAME}/wp-admin"`"
info " - Database : `cyan $WP_DB_NAME`"
info " - Username : `cyan ?`"
info " - Password : `cyan ?`"
newline
info "phpMyAdmin console"
info " - Address : `u "https://${WD_HOST_NAME}/phpmyadmin"`"
info " - Username : `cyan root`"
info " - Password : `cyan $(sed -re 's/\\$/\\\\$/g' <<< $DB_PWD)`"
newline
info "TLS Certificates"
info " - Hostname #1 : `cyan $WD_HOST_NAME`"
info " - Hostname #2 : `cyan $WP_HOST_NAME`"
info " - Admin e-mail : `cyan $ADMIN_EMAIL`"
)
EOL
EOF
info 'Finishing up...'
sudo update-motd
okdone && ok 'All done!'
cat /etc/motd
newline
separator
newline
ask 'The system will restart now. Press any key to continue...'
okdone && sudo shutdown -r now
exit 0
#!/bin/bash
groupadd docker
usermod -a -G docker ec2-user
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment