Rebuilding gwynmorfey.com

06 Oct 2017

From zero to rails-app-deployed on Linode:

Backup

  • Dump old database with mysqldump
  • Check which sites are even running by inspecting /etc/apache2/sites-enabled
  • Run du --max-depth=2 -b | sort -rn | less to see what's on the disk
  • Have a look in /home
  • Tar up everything interesting, and copy it to my laptop with scp
  • Delete large and uninteresting things to free up disk, then resize the disk image in the Linode manager so that I can create a new one without deleting the old one yet.
  • Basic setup

  • In Linode's manager, Deploy image. We'll use Ubuntu 17 rather than 16 LTS because this is more of a 'playground' machine than a production host.
  • Generate strong root password using Keepass, and save it in Keepass.
  • Rename the disks and profiles to make everything clear
  • Boot the machine
  • Secure access

  • ssh in as root@ . Edit .ssh/known_hosts to make this possible.
  • Meanwhile, scp .ssh/id_rsa.pub root@gwynmorfey.com:~
  • On the server, mkdir .ssh && mv id_rsa.pub .ssh/authorized_keys && chmod -R go-rwx .ssh.
  • ssh root@gwynmorfey.com - now the password isn't needed and the certificate is used
  • edit /etc/ssh/ssh_config and set PasswordAuthentication to no to prevent ssh bruteforce attacks.
  • edit /etc/hostname and /etc/hosts to set hostname
  • Install mysql and load database file

  • apt-get update
  • apt-get install mysql-server mysql-client default-libmysqlclient-dev
  • (without the last one, you can't install the mysql gem later, because you're missing the development files)
  • cat d.sql.gz | gunzip | mysql -u root -p
  • Prepare user for the website app

  • useradd gwynmorf && mkdir /home/gwynmorf && cp -R ~/.ssh /home/gwynmorf && chown -R gwynmorf /home/gwynmorf
  • Install nginx and passenger

  • apt-get install nginx
  • apt-get install -y dirmngr gnupg
  • apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
  • sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger zesty main > /etc/apt/sources.list.d/passenger.list'
  • apt-get update
  • apt-get install -y libnginx-mod-http-passenger
  • Install ruby, node, npm

  • apt-get install ruby ruby-dev
  • (this will also install rubygems, but not bundler)
  • gem install bundler
  • we can't install node from apt - we get a very old version (4.7)
  • curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
  • apt-get install -y nodejs
  • apt-get install -y build-essential
  • npm install npm@latest -g
  • curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
  • echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get install yarn
  • Setup firewall

  • ufw status
  • ufw allow ssh/tcp
  • ufw logging on
  • ufw allow 'Nginx Full'
  • ufw enable
  • Deploy application

  • apt-get install git
  • Create ~/everpublish/shared/.env and ~/everpublish/shared/keys/firebase.json
  • gem install bundler
  • On laptop, bundle exec cap production deploy
  • On server, edit /etc/nginx/sites-enabled/gwynmorfey.com:
  • .

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
    
        root /home/gwynmorf/everpublish/current/public;
    
        server_name gwynmorfey.com;
    
        passenger_enabled on;
    
            rails_env production;
            passenger_user gwynmorf;
    
    }
    
    • service nginx restart

    Setup SSL

  • Copy certs back from laptop to /var/ssl

  • Add lines to sites-enabled/gwynmorfey.com:

    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    
    ssl_certificate /var/ssl/gwynmorfey.com.chained.pem;
    ssl_certificate_key /var/ssl/gwynmorfey.com.key;
    
  • Execute /var/ssl/renew_gwynmorfey.sh just to check that it works.

  • .

    mkdir -p /home/gwynmorf/everpublish/current/public/.well-known/acme-challenge
    
    chmod -R 777 /home/gwynmorf/everpublish/current/public/.well-known
    
    openssl req -new -sha256 -key gwynmorfey.com.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.gwynmorfey.com,DNS:gwynmorfey.com")) > gwynmorfey.com.csr
    
    python acme_tiny.py --account-key letsencrypt-account.key --csr ./gwynmorfey.com.csr  --acme-dir /home/gwynmorf/everpublish/current/public/.well-known/acme-challenge > gwynmorfey.com.crt
    
    cat gwynmorfey.com.crt intermediate.pem > gwynmorfey.com.chained.pem
    
    • Put renew_certs.sh in /etc/cron.monthly:

      !/bin/bash

      cd /var/ssl ./renew_gwynmorfey.com.sh

    • Force SSL on all connections by editing gwynmorfey.com:

    .

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name gwynmorfey.com;
        return 301 https://gwynmorfey.com$request_uri;
    }   
    server {
        listen 443 ssl http2;
       listen [::]:443 ssl http2;
    ...
    

    Setup DB backups to s3

  • wget https://github.com/s3tools/s3cmd/archive/master.zip && unzip master.zip
  • apt-get install python-dateutil
  • apt-get install python-setuptools
  • python setup.py install
  • create /etc/cron.daily/backup_db.sh

    !/bin/bash

    mysqldump -u root --password=somepassword --all-databases | gzip > /var/db_backups/date +%C%y%m%d-%H-%M.sql.gz s3cmd sync /var/db_backups/ s3://gwynmorfey-backup

  • Now we need to set up a user on s3 for the backups. We want to create an "IAM" user, not the "root" user (the amazon.com email/password that controls the entire account)

    Start here: https://console.aws.amazon.com/iam/home?region=us-west-2#/users

    User name: dbbackup Access type: programmatic (so it will provide an access key and secret key)

    Attach user to new group with policy amazons3fullaccess

    Now we need to restrict it further but let's check it works first.

    After Review you'll get the access key id and secret access key

    • s3cmd --configure

    Accept defaults and test backup_db.sh.

    Lock down S3 access to just the right bucket

  • Go to the dbbackupgroup group: https://console.aws.amazon.com/iam/home?region=us-west-2#/groups/dbbackupgroup
  • Create an Inline Policy:
  • .

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:GetBucketLocation",
            "s3:ListAllMyBuckets"
          ],
          "Resource": "arn:aws:s3:::*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": ["arn:aws:s3:::gwynmorfey-backup"]
        },
        {
          "Effect": "Allow",
          "Action": [
            "s3:PutObject",
            "s3:GetObject",
            "s3:DeleteObject"
          ],
          "Resource": ["arn:aws:s3:::gwynmorfey-backup/*"]
        }
      ]
    }
    
    • Detach the Managed Policy.