Server side SWIFT: Vapor, MySQL, and NGINX with SSL-cert running on Ubuntu instance from DigitalOcean
INTRODUCTION
This is an effort to bring together many of the useful resources that helped me get my Swift Vapor server up and running on a machine instance from DigitalOcean with https and MySQL using the NGINX webserver.
I’m going to set this up with 2 domains ( yourdomain.com
and beta.yourdomain.com
). One being the primary production
App and the other serving a beta
version so that you can test your development on a live site, setup exactly as your production
site.
If you don’t want to set up both repositories you can ignore the stuff about beta
but you’ll still have to conform to the production
name structure for file paths in order for the provided scripts to work.
References
I rely heavily on Robert Bojor’s guide for setting up Perfect 2.0, Swift, Ubuntu and Digital Ocean. Other sources I attempt to link to where appropriate.
Also, before you invest the time required with the process I outline here you may want to check out Flock. It may be a much better and more well supported tool for your needs. Though I’m not sure if it deals with all the services I try to address here.
Versions of Packages used in this tutorial
- Swift : 3.1
- MySQL : 14.14
- Vapor : 1.5
- Nginx : 1.10.0
- Ubuntu: 16.04.2 x64
Disclaimer
I believe I have fully tested the process after my edits, but it’s possible I missed something. There are also a couple areas I’d like to expand upon. I suspect people will have suggestions and possibly find bugs in my documentation. I will make every effort to incorporate public response.
Create Server instance on DigitalOcean
DigitalOcean is a great cloud computing platform for spinning up an instance of your internet accessible application. If you don’t already have an account you can signup here.
- Log in to your DigitalOcean account and, from the
Droplets
menu tab selectCreate Droplet
- Distribution: select
Ubuntu 16.04.2 x64
- Size: Probably select the smallest one (
512 MB/1 CPU — 20 GB SSD disk — 1000 GB transfer
). You’ll be able to change this later after you’re up and running. - We don’t need
Block Storage
for a basic setup. - Region: Select an option close to you.
- No additional options.
- SSH Key: Select an SSH key you’ve previously set up on DigitalOcean. If you haven’t done that yet, go ahead and do that now. You can use their tutorial for reference. This may not be entirely required, but this tutorial assumes you have that setup.
- You’ll create
1 Droplet
and you can use the default hostname it provides you. - Click on
Create
This should create something like what you see below…
Configure DNS
We’ll need to do this for the last step of getting SSL/HTTPS to work. Hopefully if you start the process now the DNS will refresh by the time you need to reference your domain name.
If you do not need or want to use SSL/HTTPS then you can skip this and always use the IP Address provided by DigitalOcean wherever I reference YOUR_DOMAIN.COM
.
In order for your domain name to correctly point to this new IP Address. You first need to visit your domain name registrar and change the Name Servers to reference:
NS1.DIGITALOCEAN.COM
NS2.DIGITALOCEAN.COM
NS3.DIGITALOCEAN.COM
Follow DigitalOcean’s instructions on adding your domain name(s) to the Droplet. This is done under the Networking
menu item in your DigitalOcean control panel.
Remember to add an A
record for both @
and beta
which are directed to your ubuntu
droplet you just setup.
Once this is done you may need to request your domain registrar to refresh DNS
. It may take some time for the changes to take effect.
Configure the remote server
If your DNS has refreshed and you can ping yourdomain.com
then you should be able to ssh into your DigitalOcean remote server. I have stored my DigitalOcean RSA key in the file: ~/.ssh/id_rsa_digitalOcean
you may have it saved as something different.
ssh -i ~/.ssh/id_rsa_digitalOcean root@YOUR_DOMAIN.COM -v
If the DNS has not yet refreshed you can still proceed by using the IP Address provided by DigitalOcean for your Droplet. Change YOUR_DROPLET_IP
to use the one for your own instance.
ssh -i ~/.ssh/id_rsa_digitalOcean root@YOUR_DROPLET_IP -v
The first time you login you will get a prompt questioning the authenticity of the host. This is okay, answer yes when it asks you if you are sure you want to continue connecting.
We’re going to start by creating a new user on your remote server and setting that user up with sudo
privileges. This will allow you to run the NGINX
server from this user’s home directory and bind the service to the correct port mappings.
Adjust USER_NAME
to the name you want to use for administering your remote server.
useradd -d /home/USER_NAME -m -s /bin/bash USER_NAME
echo "USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
Next, we’ll set this User up to use the same ssh keys that were set up when the droplet was created.
mkdir /home/USER_NAME/.ssh
cp /root/.ssh/authorized_keys /home/USER_NAME/.ssh
chown -R USER_NAME.USER_NAME /home/USER_NAME/.ssh
Now logout from your remote server and try to log back in as the new user you just created. Adjusting LOCAL_KEY_PATH
USERNAME
and YOUR_DROPLET_IP
accordingly.
ssh -i LOCAL_KEY_PATH USER_NAME@YOUR_DROPLET_IP -v
I’m going to suggest adding this to the User’s ~/.bash_profile
because I find it helpful. It’s not necessary though.
Edit ~/.bash_profile
(I use vi
but feel free to use whichever editor you’re comfortable with.
Now source that file so the changes take effect.
source ~/.bash_profile
Update packages installed by DigitalOcean
export LC_ALL="en_US.UTF-8"
export LC_CTYPE="en_US.UTF-8"
sudo dpkg-reconfigure locales
That will bring up a configuration page. You should be able to hit return through these next two pages…
Now update and upgrade…
sudo apt-get update
sudo apt-get upgrade -y
Next, install some of the packages required by Swift in order to compile, along with some other helpful utilities.
sudo apt-get install make clang libicu-dev pkg-config libssl-dev libsasl2-dev libcurl4-openssl-dev uuid-dev git curl wget unzip -y
Next we need to install Swift. First check https://swift.org/download
to make sure you know which is the latest version.
cd /usr/src
sudo wget https://swift.org/builds/swift-3.1-release/ubuntu1604/swift-3.1-RELEASE/swift-3.1-RELEASE-ubuntu16.04.tar.gz
sudo gunzip < swift-3.1-RELEASE-ubuntu16.04.tar.gz | sudo tar -C / -xv --strip-components 1
Let’s make sure that worked…
swift --version
That should return something like: Swift version 3.1 (swift-3.1-RELEASE)
If Swift has successfully installed then you can remove the tar.gz file:
sudo rm -f swift-3.1-RELEASE-ubuntu16.04.tar.gz
And before we get too far let’s just change back to our $HOME folder.
cd ~
ADD SWAP
I’ve had problems compiling release
builds on DigtalOcean and adding a swap partition seems to have solved that issue.
DigitalOcean provides reference for this.
Allocate 4 Gigabytes for Swap
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
Should return something like:
Setting up swapspace version 1, size = 4194300 KiB
no label, UUID=e2f1e9cf-c0a9-4ed4-b8ab-714b8a7d6944
Enable the Swap partition
sudo swapon /swapfile
Check to make sure this worked:
sudo swapon -s
You should get something like:
Filename Type Size Used Priority
/swapfile file 4194300 0 -1
Now Make the Swap partition permanent
sudo vi /etc/fstab
At the bottom of the file, you need to add a line that will tell the operating system to automatically use the file you created:
/swapfile none swap sw 0 0
SETUP MySQL
This How-To will set up MySQL to host the database on the same server as our app. If you are going to use a database provider as a service you can omit mysql-server
from the following line.
sudo apt-get install mysql-client libmysqlclient-dev mysql-server -y
That should bring up a prompt to create a password for the root MySQL user. Create and remember this password.
Next, edit mysqlclient.pc
located in /usr/lib/x86_64-linux-gnu/pkgconfig
sudo vi /usr/lib/x86_64-linux-gnu/pkgconfig/mysqlclient.pc
You’ll remove the flags: -fabi-version=2
and -fno-omit-frame-pointer
so that the final file looks like:
Now let’s secure the MySQL server installation…
sudo mysql_secure_installation
This will take you through a few prompts…
- enter root password. [Enter the password you recently created from the previous install step]
- Validate Password Plugin? This is probably a good idea. but not needed for this tutorial. [y]
- Change Root Password? We just created the root password so we probably don’t need to change it. [n]
- Remove Anonymous Users? [y]
- Disallow root login remotely? [y]
- Remove test database and access to it? [y]
- Reload privilege tables now? [y]
Now you should be able to check your setup.
mysql --version
That should give you something like: mysql Ver 14.14 Distrib 5.7.17, for Linux (x86_64) using EditLine wrapper
Next, lets add a new User to control MySQL so you don’t have to login as the root user. You can reference DigitalOcean’s tutorial on this if you like.
mysql -u root -p
Enter the password when prompted. That should bring up the MySQL control shell with a mysql>
prompt. Make sure to change NEW_USER
and USER_PASSWORD
to suit your needs.
CREATE USER 'NEW_USER'@'localhost' IDENTIFIED BY 'USER_PASSWORD';
Now we need to grant that user with permissions to work with the databases. I’m going to grant that user with total access. Ultimately it may be best to change these for security reasons. I’m not even remotely an expert with security.
GRANT ALL PRIVILEGES ON * . * TO 'NEW_USER'@'localhost';
Now reload the privileges.
FLUSH PRIVILEGES;
Use ctrl-d
to exit the MySQL shell.
CONFIGURE FOR VAPOR
Now we’re going to get the tools needed to insure your ubuntu server is up and running with Vapor. You can reference the Vapor Docs.
You should be able to verify that everything Vapor needs up to this point is configured correctly with:
curl -sL check.vapor.sh | bash
That should return: Compatible
Now we can install the Vapor Toolbox. I don’t think this is a required step for the server side, but I’m doing it just in case.
eval "$(curl -sL https://apt.vapor.sh)"
sudo apt-get install vapor
You can verify it worked with:
vapor --help
It should return: Usage: vapor <new|build|run|fetch|clean|test|xcode|version|self|heroku>
PREPARE USER’s HOME DIRECTORIES and GIT REPOSITORIES
You may want to customize some of these steps and locations to suit your own workflow. My process may not be considered “best practice”, and if you have suggestions on a better workflow I would very much like to incorporate it into this document.
I am going to set up directories which will be used to push our project’s git repositories, and some which will be used as our web-root for the NGINX server.
First the location for the git
repositories; One for the beta
repository and one for the production
repository.
Insure that you are in your User’s home directory. ( cd ~
or just cd
)
The APP_NAME
should be the name you’d like to use as your web or api app. In my example I’ll just call mine “myWebApp” but this can be whatever you want.
We create an empty Public
repository now because eventually that’s what Vapor will look for as the public webroot, but we also need it for the step of creating the SSL certificates.
mkdir -p src/APP_NAME/beta/.git src/APP_NAME/production/.git appServer/APP_NAME/production/bin appServer/APP_NAME/production/Public appServer/APP_NAME/beta/bin appServer/APP_NAME/beta/Public
git init ~/src/APP_NAME/beta/.git/. --bare
git init ~/src/APP_NAME/production/.git/. --bare
We’re going to create a post-receive
hook for the git repositories. We don’t need the .sample
hooks, so I’ll remove them but feel free to keep them if you want.
rm ~/src/APP_NAME/*/.git/hooks/*.sample
First we’ll set up the beta
hook.
cd ~/src/APP_NAME/beta/.git/hooks
Using vi
or your prefered editor create a post-receive
file which contains everything to build and move it over to the path NGINX will look for when serving the app. You should not need to change the values in this file unless you have changed the paths from what I’ve mentioned above.
Make this post-receive
hook executable.
chmod +x post-receive
Next let’s copy that post-receive
hook over to the production
repository.
cp ~/src/APP_NAME/beta/.git/hooks/post-receive ~/src/APP_NAME/production/.git/hooks/post-receive
CONFIGURE SUPERVISOR
supervisor
is a process that will run on the remote server and insure that your Swift App stays up and running.
sudo apt-get install supervisor -y
service supervisor restart
Now we need to setup the init scripts for your beta and production apps. Since we’re setting up for both a beta and production environment we’ll have a script for each of these.
cd /etc/supervisor/conf.d
Create a new file (you’ll need to use sudo) called APP_NAME.conf
and put the following lines in…
Now lets create another init script for the beta app. We’ll call this APP_NAME-beta.conf
Next, make supervisor aware of these new init scripts
sudo supervisorctl reread
CONFIGURE NGINX
On the remote DigtalOcean server instance… If everything has been set up correctly, you should be able to login by typing…
ssh REMOTE_USER_NAME@YOUR_DOMAIN.COM
Now…
sudo apt-get install nginx -y
After it has installed we need to set up the virtual hosts for the beta
and production
sites. We’ll also remove the default nginx site.
cd /etc/nginx/sites-available
sudo rm -rf default ../sites-enabled/default
Now, using your editor of choice (and sudo) create APP_NAME.vhost
which should contain…
Next, copy this file to APP_NAME-beta.vhost
and edit that; inside change the word production
to beta
and, on ONLY lines 5 and 6 (the logs), where it says APP_NAME
change that to APP_NAME-beta
. I would have thought that we would need to change line 26 for the beta
vhost to point at http://127.0.1:8181
because that’s the port the beta app is bound to, but it seems not to make a difference.
Let nginx know that these virtual hosts should be enabled…
sudo ln -s /etc/nginx/sites-available/APP_NAME.vhost /etc/nginx/sites-enabled/APP_NAME.vhostsudo ln -s /etc/nginx/sites-available/APP_NAME-beta.vhost /etc/nginx/sites-enabled/APP_NAME-beta.vhost
Next, edit the nginx.conf
file located in /etc/nginx
cd /etc/nginx
sudo mv nginx.conf nginx.conf.backup
Now (using sudo) create a new nginx.conf
file:
Next, restart the nginx server…
sudo /etc/init.d/nginx restart
ENABLE SSL/HTTPS
The https protocol is an extra layer of security for internet traffic. I won’t try to describe it any farther than that. It does become extra important if your webApp is serving as an API for iOS applications. It’s my understanding Apple has begun to enforce that calls made from your app will need to connect via https
and that regular old http
will no longer pass App Store requirements.
I’ll be referencing instructions for Securing Nginx with Let’s Encrypt from DigitalOcean.
Let’s go back to the User’s home directory to start: cd
This will Clone the Let’s Encrypt repository into /opt
sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
We’re going to request a certificate for both the beta
and production
sites. We’ll start with the production
. YOUR_DOMAIN.COM
should be whatever you called it way back in the beginning when you were registering the A
records with DigitalOcean.
cd /opt/letsencrypt
./letsencrypt-auto certonly -a webroot --webroot-path=/home/USER_NAME/appServer/APP_NAME/production/Public -d YOUR_DOMAIN.COM
Note: The Let’s Encrypt software requires superuser privileges (sudo).
Once letsencrypt
initializes you will be prompted for some information.
When prompted, enter an email address to be used for notices and lost key recovery:
You must agree to the Subscriber Agreement…
If everything has worked then you should get a message:
Output:
IMPORTANT NOTES:
- If you lose your account credentials, you can recover through
e-mails sent to USER_NAME@MAIL_DOMAIN.com
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/BETA.YOUR_DOMAIN.COM/fullchain.pem. Your
cert will expire on 2016-03-15. To obtain a new version of the
certificate in the future, simply run Let's Encrypt again.
- Your account credentials have been saved in your Let's Encrypt
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Let's
Encrypt so making regular backups of this folder is ideal.
- If like Let's Encrypt, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Make note of the certificate path: /etc/letsencrypt/live/BETA.YOUR_DOMAIN.COM/fullchain.pem
and the expiration date.
Now repeat the process for the beta
site…
./letsencrypt-auto certonly -a webroot --webroot-path=/home/USER_NAME/appServer/APP_NAME/beta/Public -d BETA.YOUR_DOMAIN.COM
Insure that the certificates exist; you can check by listing their location:
sudo ls -aF /etc/letsencrypt/live
That should return something like:
./ ../ BETA.YOUR_DOMAIN.COM/ YOUR_DOMAIN.COM/
We now need to re-edit the nginx virtual host config and uncomment the lines about the certificate files.
cd /etc/nginx/sites-available
sudo vi YOUR_DOMAIN.vhost
It should now look like this…
Do the same for BETA.YOUR_DOMAIN.vhost
Now restart nignx:
sudo /etc/init.d/nginx restart
TK setup cron to regenerate the SSL cert?
LOCAL DEVELOPMENT
Okay, now let’s jump back to your local machine for a bit to setup the app for development if you don’t already have one.
These instructions assume you are working on a mac and already have Xcode, Swift, as well as Vapor up and working. If you need to install the Vapor toolbox please refer to the Vapor Docs.
In the terminal
app navigate to where you keep your project files. I’ll just use the desktop for this demonstration. Be sure to adjust the APP_NAME
cd ~/Desktop
vapor new APP_NAME
cd APP_NAME
Next, add the mysql-provider
to your Package dependencies. Your Package.swift
file should at least contain…
I use Xcode, so I’ll have vapor set up the .xcodeproj
file…
vapor xcode --mysql
Now the APP_NAME folder should contain…
APP_NAME/
.build/
.git/
.gitignore
.travis.yml
Config/
Localization/
Package.pins
Package.swift
Procfile
Public/
README.md
Resources/
Sources/
app.json
APP_NAME.xcodeproj/
license
My .gitignore file looks like this:
.DS_Store
.build
Packages
*.xcodeproj
xcuserdata
secrets
mysql.json
If you are using a secrets
folder to store Config files for things like MySQL you will have to copy/create those files on the remote server before your app will function properly.
You’ll likely have a Config
folder which looks like:
Config/
app.json
clients.json
crypto.json
droplet.json
servers.json
beta/
mysql.json
production/
mysql.json
secrets/
mysql.json
You should make sure to use a different database name between the production
and beta
environments. For Example the Config/production/mysql.json
should look like:
{
"host": "127.0.0.1",
"user": "MYSQL_USER_NAME",
"password": "MYSQL_PASSWORD",
"database": "APP_DATABASE"
}
and the Config/beta/mysql.json
:
{
"host": "127.0.0.1",
"user": "MYSQL_USER_NAME",
"password": "MYSQL_PASSWORD",
"database": "APP_DATABASE_beta"
}
In order to have the beta
and production
environments running at the same time you need to make sure each app is bound to a different port. To accomplish this we add a configuration to override the servers.json
Config, and change the port for the beta
. Add a custom Config/beta/servers.json
file.
{
"default": {
"port": "$PORT:8181",
"host": "0.0.0.0",
"securityLayer": "none"
}
}
TK also need to somehow configure the app to protect against public access if we’re running the beta
Adding the remote repositories. By now, hopefully, your DNS has refreshed and you are able to ping YOUR_DOMAIN.COM
Let’s make sure you’ll be able to push to the remote server. We can do that by letting ssh know that when you access that address you want to use a specific key. You do this by editing ~/.ssh/config
Add these lines (Words in ALL_CAPS need to be adjusted for your setup)…
Host YOUR_DOMAIN.COM
Hostname YOUR_DOMAIN.COM
user REMOTE_USER_NAME
IdentityFile=~/.ssh/YOUR_DIGITAL_OCEAN_ID_RSA_FILE
Next, insure you are still in your APP_NAME
folder and add the remote git repositories…
git remote add beta ssh://REMOTE_USER_NAME@YOUR_DOMAIN.COM/home/REMOTE_USER_NAME/src/APP_NAME/beta/.gitgit remote add production ssh://REMOTE_USER_NAME@YOUR_DOMAIN.COM/home/REMOTE_USER_NAME/src/APP_NAME/production/.git
Now you have a beta
and production
repository. You can push out to the beta
when you want to test your app in the real world. And push out to production
when you want it live.
Let’s push out the beta
to make sure everything is working.
git add .
git commit -m 'First commit to test pushing to beta server`
git push beta master
At this point the files should be transferred to the remote beta
repository and the post-receive
hook should run. The project will compile and you’ll see the output from post-receive
ending with remote: Restarting APP_NAME-beta
You should now be able to go to https://YOUR_DOMAIN.COM and connect with your app.