In this article, I’m going describe how to create a Linux environment where we can deploy a Swift-backed server-side app. In a past article, I created a Notes app with both a client and server component and shared business logic, all written in Swift. Now, we need to create a remote (or virtual) environment where we can safely and automatically deploy the server-side app, have it compile and run.

For this project, I’ve chosen Ubuntu 17.04 as our Linux distribution. This version is not an LTS version, but it’s recent, easy to work with and includes a numbre of online tutorials. I have also broken down the process into four logical steps for easy reference:

Provision the Linux machine Create the user accounts Customizing the environment Deploying the app

The first three steps are fairly manual with one exception: in Vagrant, provisioning is free. The NotesApp project includes a Vagrant file and a script for easy reference.

The application, the Vagrant configuration and all scripts can be found the NotesApp repo.

Without further ado, let’s begin the process.

1. Set up the machine

Time to configure the machine with all the packages we’ll need to build and run our Swift app. Fortunately, most of the tools we need for Swift on the server side are already present on an Ubuntu box. All we are missing are a few packages for OpenSSH, Swift’s compiler and HTTP server packages.

Login as the admin user and run the following commands. If you are the root user, you probably don’t need sudo .

# Install additional packages sudo apt update

sudo apt-get install -y git libssl-dev libcurl4-openssl-dev nginx supervisor clang libicu-dev # Download and install swift curl -sSL https://swift.org/builds/swift-4.0-release/ubuntu1610/swift-4.0-RELEASE/swift-4.0-RELEASE-ubuntu16.10.tar.gz > swift.tar.gz tar xzf swift.tar.gz

sudo mv swift-4.0-RELEASE-ubuntu16.10/usr /usr/lib/swift

sudo chown -R root:root /usr/lib/swift

sudo chmod a+x /usr/lib/swift/bin/*

sudo chmod -R a+r /usr/lib/swift/lib

sudo ln -s ../lib/swift/bin/swift /usr/bin/swift rm -fr swift*

swift --version

As I mentioned earlier, if you are running this step on a Vagrant virtual machine, these calls can be easily added to a script and bootstraped as the provision script in the configuration. I have already done so in Scripts/provision.sh file.

2. Creating the user accounts

We’ll be creating three accounts on the server:

admin: The account responsible for administration (also a sudo user)

deploy: An account responsible for deploying code to the server and compiling it

app: The account under which the application is run

The deploy account is added to the app’s group because we will be using the app group as a way for the deploy account to share the compiled executable with the app.

sudo adduser --gecos "" admin

sudo adduser --gecos "" deploy

sudo adduser --gecos "" --disabled-login --disabled-password app # Prevents the welcome screen from appearing

sudo -i -u admin touch /home/admin/.hushlogin

sudo -i -u deploy touch /home/deploy/.hushlogin # Makes the deploy user part of the app group and gives it write access to the app's home directory. sudo usermod -aG sudo admin

sudo usermod -aG app deploy

sudo chmod -R g+rwx /home/app

If you have a personal SSH key, this will be a good time to add it to the authorized keys for both the admin and deploy. Without this step, you will need to enter your credentials every time you wish to SSH into the server.

Setting up an SSH key If you do not have an SSH key on your local machine, this is a good time to generate one so that you may use it in the future with your server or services like Github. First, run the command below on your computer. [ -f ~/.ssh/id_rsa ] || ssh-keygen -f ~/.ssh/id_rsa echo ~/.ssh/id_rsa.pub The output of the echo command will be your public SSH key. It will look like ssh-rsa [various numbers and letters] [you@yourmachine] Use this string anywhere you wish to connect via SSH without the use of credentials.

Paste your SSH key in the string section of this first command.

# Run this on the remote server

PUB_SSH_KEY="paste the key here"

And run the following commands to allow for password-less entry as either the admin user or the deploy user.

sudo -i -u admin mkdir /home/admin/.ssh

sudo -i -u admin touch /home/admin/.ssh/authorized_keys

sudo bash -c "echo \"$PUB_SSH_KEY\" >> /home/admin/.ssh/authorized_keys" sudo -i -u deploy mkdir /home/deploy/.ssh

sudo -i -u deploy touch /home/deploy/.ssh/authorized_keys

sudo bash -c "echo \"$PUB_SSH_KEY\" >> /home/deploy/.ssh/authorized_keys"

PUB_SSH_KEY=""

You should now be able to freely SSH from your computer as either the admin or the deploy user.

3. Customizing the environment

This next step is all about customizing the Ubuntu machine to host our app. This step is a series of customizations of the configurations for the various services we’ll need to secure the machine and enable HTTP hosting.

If are you unfamiliar with editing text files through the command line, you may want to look up nano and vi online. The first is a rather simple editor for text files. The second is a more powerful, albeit more complex editor. Either will suffice for our needs.

First, I edit the SSH configuration file to secure access to the server. I do not modify the default SSH port, but it is recommended for production machines.

The configuration below only allows the vagrant, admin, and deploy users SSH privilege. Root-level access should in no way be allowed via SSH.

> sudo vim /etc/ssh/sshd_config PermitRootLogin no

AllowUsers vagrant admin deploy

DenyUsers root

DenyGroups root

Next, I configure supervisor. This tool allows the execution of the Notes application as a user of our choosing and the ability to run that application in the background. Without supervisor, or a similar tool, a user will manually have to log into the server and start the application from the command line.

You’ll note that the ownership of the supervisor HTTP server includes the deploy group. This is done so that our deploy user may restart the service once a new version of the app is pushed to the server.

> sudo vim /etc/supervisor/supervisord.conf [unix_http_server]

file=/var/run/supervisor.sock

chmod=0770

chown=root:deploy

Next, I add a server supervisor configuration for the app we are running. Note that the user is app. I am restricting the application’s access to the permissions afforded to this user.

> sudo vim /etc/supervisor/conf.d/notes_app.conf [program:NotesServer]

autostart=true

autorestart=true

command=/home/app/build/release/NotesServer

user=app

stdout_logfile=/var/log/supervisor/NotesServer-stdout.log

stderr_logfile=/var/log/supervisor/NotesServer-stderr.log

Finally, I configure NGINX. This famous webserver will proxy all requests from the outside world to the application.

> sudo vim /etc/nginx/sites-enabled/notes_app.conf upstream app {

server localhost:8080;

} server {

listen 80;

server_name 127.0.0.1; # for production use the name of the server instead. location / {

proxy_set_header HOST $host;

proxy_set_header X-Forwarded-Proto $scheme;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://app;

proxy_redirect off;

}

}

Once all is done, I restart all the services we customized. This is the last step. It will prevent further access to the server for the root account, so it’s important that the admin user is also a sudo user. Otherwise, there will be no way to further adminstrate the machine.

sudo service nginx restart

sudo service ssh restart

sudo service supervisor restart

4. Deploy to an environment

With all the accounts created, it’s now time to start deploying the app to the remote server. I have moved all of the commands to a single script, Scripts/deploy.sh that can be run from the command line on your machine.

In short, here is what the script does:

It copies all of NotesServer and NotesShared on the current machine to the server using rsync It instantiates a git repo in the NotesShared directory (necessary to get Swift’s package system working when compiling) It modifies the NotesServer’s Package.swift file to include the NotesShared directory It compiles the NotesServer application It links the compiled application to the app’s home directory It restarts supervisor to use the new application’s executable

Here is the full script.

#

# Configuration variables

#

DEPLOY_USER=deploy

SSH_HOST=127.0.0.1

SSH_PORT=2222 while getopts :h:p: opts; do

case ${opts} in

h)

SSH_HOST=${OPTARG}

;;

p)

SSH_PORT=${OPTARG}

;;

\?)

echo "Invalid option -$OPTARG" >&2

exit 1

;;

esac

done # Directory with the different code bases

DEPLOY_HOME=/home/deploy

DEPLOY_DIR=`date +%Y%m%dT%H%M%S`

DEPLOY_ROOT="$DEPLOY_HOME/$DEPLOY_DIR"

DEPLOY_SHARED_ROOT="$DEPLOY_ROOT/NotesShared"

DEPLOY_SERVER_ROOT="$DEPLOY_ROOT/NotesServer"

DEPLOY_BUILD_PATH="$DEPLOY_SERVER_ROOT/.build" APP_HOME=/home/app

APP_LINK_PATH="$APP_HOME/build" echo "Deploying to $SSH_HOST:$SSH_PORT" # Copies both the shared and server code to the remote server

rsync -av -e "ssh -p $SSH_PORT" NotesShared NotesServer \

--exclude=*.xcodeproj \

--exclude=.build \

$DEPLOY_USER@$SSH_HOST:$DEPLOY_ROOT # Compiles the code on the remote server.

# The git code is necessary because of the way swift packages work: # they require the presence of a git repo to link one package

# to another. So this script creates a v1.0.0 git release for the

# shared code on the server and links that package to the server's

# package. ssh -T $DEPLOY_USER@$SSH_HOST -p$SSH_PORT << EOSSH echo "-- Initializing Git in the shared code root"

cd $DEPLOY_SHARED_ROOT

git config --global user.email "$DEPLOY_USER@localhost"

git config --global user.name "deploy"

git init

git add .

git commit -am "NotesShared"

git tag 1.0.0 echo "-- Building the server"

cd $DEPLOY_SERVER_ROOT

sed -i 's/dependencies:\s*\[/dependencies: [

.Package(url: "\.\.\/NotesShared", majorVersion: 1),/g' Package.swift

swift build -c release echo "-- Stopping supervisor"

supervisorctl stop all echo "-- Linking the app"

[ -e $APP_LINK_PATH ] && rm $APP_LINK_PATH

ln -s $DEPLOY_BUILD_PATH $APP_LINK_PATH

chown -R deploy:app $DEPLOY_BUILD_PATH

chmod 0750 $DEPLOY_BUILD_PATH echo "-- Restarting supervisor"

supervisorctl start all exit

EOSSH

Once the deploy script is successfully run, you can access the notes app via it’s IP address/domain name (127.0.0.1:8000 if using the Vagrant configuration)

In Closing

There are a few things that I left out for another post. Specifically, there is currently no way of storing data to the server in a permanent way (e.g. using a relational database or document store), there is also no SSL on the connection between the app and the server, and there is no directory on the server for binary assets.

The real challenge of this process was linking the NotesServer directory to the NotesShared directory for compilation. Using the right packages for sharing NotesShared between app and server also proved tricky as they had to use no more than Foundation.

Having said all that, I am happy with the outcome and the ease with which the deployment takes place.

If you like this post, make sure to subscribe or to follow me on Twitter: @dimitryz