├── .dockerignore ├── .gitignore ├── Package.swift ├── README.md ├── Sources └── main.swift ├── dev.yml ├── docker-compose.yml ├── dockerfiles ├── nginx │ ├── Dockerfile │ ├── admin │ │ ├── index.html │ │ └── style.css │ ├── entrypoint.sh │ └── nginx.conf └── swift │ ├── Dockerfile │ ├── Dockerfile-dev │ └── autoreload.sh ├── env └── admin.env └── static └── static.gif /.dockerignore: -------------------------------------------------------------------------------- 1 | env 2 | docker-compose.yml 3 | dev.yml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | Packages 4 | .build 5 | env -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "#PROJECT_NAME#", 5 | dependencies: [ 6 | .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 0, minor: 9), 7 | .Package(url: "https://github.com/Danappelxx/SwiftMongoDB", majorVersion: 0, minor: 5) 8 | // add your own dependencies here 9 | ]) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Server Side Swift Starter 2 | A starter template to develop and deploy swift 3 applications with docker. 3 | 4 | ### Features 5 | - Swift 3.0 (SNAPSHOT-2016-03-24-a) 6 | - Uses two environments for development and production. 7 | - Code autoreload during development 8 | - Kitura swift framework preinstalled (but can be replaced) 9 | - NGINX handles static files and acts as a reverse proxy for the swift application 10 | - Pre configured MongoDB database, comes with an admin interface to create/backup databases 11 | - Uses environment variables to store secrets 12 | 13 | ### Screenshots 14 | 15 | ![Hi from swift](https://dockify.io/content/images/2016/04/hi_from_swift.png) 16 | 17 | ![Swift JSON](https://dockify.io/content/images/2016/04/json.png) 18 | 19 | ![Admin Interface](https://dockify.io/content/images/2016/04/admin.png) 20 | 21 | ![Mongo Express](https://dockify.io/content/images/2016/04/mongo-express.png) 22 | 23 | ## Getting started 24 | ### Prerequisite 25 | 26 | You'll need to have 27 | 28 | - docker 29 | - docker-compose > 1.6.0 30 | - docker-machine (optional) 31 | 32 | installed on your mac. 33 | 34 | The easiest way to get them all is to download and install [docker-toolbox](https://www.docker.com/products/docker-toolbox). 35 | If you are completely new to docker, check out the [getting started with docker](https://docs.docker.com/mac/) guide. 36 | 37 | Make sure you can run docker commands like `docker run hello-world` on your terminal, or use the docker quickstart terminal. 38 | 39 | ### Installation 40 | 41 | Clone the template in a new directory (make sure to replace `NewProject` with your own project name) 42 | 43 | git clone https://github.com/jayfk/server-side-swift-starter.git NewProject 44 | cd NewProject/ 45 | 46 | Remove all upstream git files, we don't need them. 47 | 48 | rm -rf .git/ 49 | 50 | Change the defaults with sed (make sure to replace the values in brackets, e.g `` with `jayfk`) 51 | 52 | sed -i.tmp s/#ADMIN_USER#//g env/admin.env 53 | sed -i.tmp s/#ADMIN_PASSWORD#//g env/admin.env 54 | sed -i.tmp s/#PROJECT_NAME#//g dockerfiles/swift/autoreload.sh dockerfiles/swift/Dockerfile Package.swift 55 | find . -type f -name \*.tmp -exec rm -f {} \; 56 | 57 | ## Developing locally 58 | 59 | We are going to use docker to run our swift code during development and (later) in production. This 60 | has a couple of huge benefits and a few drawbacks, but the most important thing is that we have consistent 61 | environments. 62 | 63 | First, build and start the stack with 64 | 65 | docker-compose -f dev.yml up 66 | 67 | This will take *forever* on the first start. Docker has to pull all base images and has 68 | to build our own images in `dev.yml`. This includes a full swift runtime linked to Foundation and the MongoDB 69 | driver to name a few. Subsequent builds will be super fast, thanks to dockers caching. 70 | 71 | While you wait, you can check out the [docker-compose getting started](https://docs.docker.com/compose/gettingstarted/) 72 | section in dockers docs. Our development environment is defined in `dev.yml` and uses the 73 | dockerfiles `dockerfiles/swift/Dockerfile-dev` and `dockerfiles/nginx/Dockerfiles`. This will give 74 | you a quick overview of what's taking so long. 75 | 76 | As soon as your terminal stops spitting out text, you should see an output similiar to this 77 | 78 | ``` 79 | swift_1 | Compiling Swift Module 'LoggerAPI' (1 sources) 80 | swift_1 | Compiling Swift Module 'KituraTemplateEngine' (1 sources) 81 | swift_1 | Compiling Swift Module 'Socket' (3 sources) 82 | swift_1 | Compiling Swift Module 'SwiftyJSON' (2 sources) 83 | swift_1 | Compiling Swift Module 'BinaryJSON' (9 sources) 84 | swift_1 | Compiling Swift Module 'KituraSys' (4 sources) 85 | swift_1 | Compiling Swift Module 'MongoDB' (7 sources) 86 | swift_1 | Compiling Swift Module 'KituraNet' (12 sources) 87 | swift_1 | Compiling Swift Module 'Kitura' (13 sources) 88 | swift_1 | Compiling Swift Module 'NewProject' (1 sources) 89 | swift_1 | Linking .build/debug/NewProject 90 | swift_1 | Setting up watches. Beware: since -r was given, this may take a while! 91 | swift_1 | Watches established. 92 | ``` 93 | 94 | Open up your browser with 95 | 96 | open "http://$(docker-machine ip dev)" 97 | 98 | The code powering the application is in `Sources/main.swift`. Change the html string and hit save, you'll see 99 | that the application is rebuilt automatically. 100 | 101 | ``` 102 | rebuild code here 103 | ``` 104 | 105 | Refresh your browser and you should see your changes. 106 | 107 | *Pro tip: Add the IP `docker-machine ip dev` is putting out to `/etc/hosts` under the 108 | `docker.local` domain. For example: add a new line with `192.168.99.100 docker.local`. You will now 109 | be able to connect to your app at http://docker.local/* 110 | 111 | 112 | ### Where to go from here 113 | 114 | - Take a look at the admin interface to create and backup databases at `http://your-ip/admin/` 115 | 116 | - The code in `Sources/main.swift` uses the Kitura framework. For more examples, take a look at the 117 | [Kitura example project](https://github.com/IBM-Swift/Kitura-Sample/blob/master/Sources/KituraSample/main.swift). 118 | 119 | - You can use whatever framework you like (or code your own!). Just make sure your code starts some 120 | kind of server and speaks HTTP on port 8090. 121 | 122 | - Dependencies are pulled in automatically using the [swift package manager](https://github.com/apple/swift-package-manager). 123 | If you want to add a new dependency, add it to `Package.swift`. 124 | 125 | - [Create an Xcode Project](#creating-an-xcode-project) 126 | 127 | 128 | ## Deploying to production 129 | 130 | Note: In this example we are going to use docker-machine to create a remote server on DigitalOcean 131 | and install docker on it. The cool thing about docker-machine is that it works with a lot of 132 | [cloud providers](https://docs.docker.com/machine/drivers/) out of the box. I've found DigitalOcean 133 | to be the easiest to get started, but if you are a fan of AWS/Microsoft Azure/Google Compute 134 | Engine or whatnot, just use them. 135 | 136 | ### Creating a Droplet with Docker Machine 137 | 138 | You need an API access token to create a new server. Let's get one: 139 | 140 | - Go to the Digitalocean [Console](https://cloud.digitalocean.com/login) and log in. 141 | - Click on **API** in the header. 142 | - Click on **Generate new token** 143 | - Give the token a unique name, e.g *docker-machine*. Make sure that the write checkbox is checked. 144 | Otherwise docker-machine won't be able to create a new droplet. 145 | - Click on **Generate Token** 146 | - Copy the newly generated token and store it somewhere safe. 147 | 148 | ![DigitalOcean admin console](https://dockify.io/content/images/2016/04/digitalocean-api.png) 149 | 150 | Now, let's create the machine: 151 | 152 | docker-machine create swiftapp --driver=digitalocean --digitalocean-region=nyc3 --digitalocean-size=512mb --digitalocean-access-token=YOUR_TOKEN 153 | 154 | This will create a new 512MB Droplet in New York City 3 with the name *swiftapp*. If you want to 155 | create a Droplet of a different size or in a different datacenter, make sure to adjust 156 | `--digitalocean-size` and `--digitalocean-region` to your needs. 157 | 158 | The command takes a couple of minutes to finish, the output should be similiar to this: 159 | 160 | 161 | ``` 162 | Running pre-create checks... 163 | Creating machine... 164 | (swiftapp) Creating SSH key... 165 | (swiftapp) Creating Digital Ocean droplet... 166 | (swiftapp) Waiting for IP address to be assigned to the Droplet... 167 | Waiting for machine to be running, this may take a few minutes... 168 | Detecting operating system of created instance... 169 | Waiting for SSH to be available... 170 | Detecting the provisioner... 171 | Provisioning with ubuntu(systemd)... 172 | Installing Docker... 173 | Copying certs to the local machine directory... 174 | Copying certs to the remote machine... 175 | Setting Docker configuration on the remote daemon... 176 | Checking connection to Docker... 177 | Docker is up and running! 178 | To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env swiftapp 179 | ``` 180 | 181 | 182 | *If you get any errors, remove the machine by running `docker-machine rm -f swiftapp`. If this 183 | also produces errors, you can always delete the Droplet using the Digitalocean web console as a 184 | fallback.* 185 | 186 | ### First Production Deployment 187 | 188 | Select your production server with 189 | 190 | eval $(docker-machine env swiftapp) 191 | 192 | This will set a couple of environment variables telling the docker client on our machine to talk 193 | directly to the docker deamon on our server. 194 | 195 | Now, we can start our stack on the server with 196 | 197 | docker-compose up -d 198 | 199 | Don't worry, this will take a couple of minutes the first time. The docker deamon has to fetch all 200 | the images and install all the low level dependencies first. Subsequent builds will be a lot 201 | faster since the image build process will be cached on the server. 202 | 203 | If you are bored, you can connect to the server and and launch `htop` to see what's happening 204 | under the hood. 205 | 206 | docker-machine ssh swiftapp 207 | apt-get install htop && htop 208 | 209 | Once ready, check out your shiny swift powered web app by running 210 | 211 | open "http://$(docker-machine ip swiftapp)" 212 | 213 | 214 | ### Pushing new code 215 | 216 | If you want to push new code to the server, you'll need to rebuild the image, create a new container and restart the running container. 217 | 218 | docker-compose build swift 219 | docker-compose create swift 220 | docker-compose stop swift 221 | docker-compose start swift 222 | 223 | ## Useful things 224 | 225 | ### Creating an Xcode Project 226 | 227 | To Create an Xcode project and open it (make sure to replace `NewProject` with your own project name) 228 | 229 | docker-compose -f dev.yml run swift swift build --generate-xcodeproj . 230 | open NewProject.xcodeproj 231 | 232 | The generated project is mediocre at best. You'll probably see a lot of compile errors. To fix 233 | (most of) them, download and install the [March 24, 2016](https://swift.org/builds/development/xcode/swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a/swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a-osx.pkg) 234 | development snapshot and change the toolchain in Xcode at `Preferences` > `Components` > `Toolchains`. 235 | 236 | ![Xcode Toolchains](https://dockify.io/content/images/2016/04/xcode_toolchains.png) 237 | 238 | In order to get code highlighting and resolution to work, you need to select the modules manually. 239 | In Xcode, locate your `main.swift` file at `Sources/main.swift` and select every imported module under 240 | *Target Membership*. You'll still see *No such module* warnings, but the highlighting/resolution will 241 | work. 242 | 243 | ![Xcode dependencies](https://dockify.io/content/images/2016/04/xcode_dependencies.png) 244 | -------------------------------------------------------------------------------- /Sources/main.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * This example uses IBMs Kitura framework: https://github.com/IBM-Swift/Kitura 3 | * For more examples, see: https://github.com/IBM-Swift/Kitura-Sample/blob/master/Sources/KituraSample/main.swift 4 | * 5 | * You can replace this code and use whatever framework you like (or code your own!). 6 | * Just make sure your code starts some kind of server and speaks HTTP on port 8090. 7 | **/ 8 | import Kitura 9 | import KituraNet 10 | import KituraSys 11 | import MongoDB 12 | import SwiftyJSON 13 | import Foundation 14 | 15 | // initializes connection to the mongo database, see https://github.com/Danappelxx/SwiftMongoDB 16 | // for usage information 17 | let db = Database(client: try Client(host: "mongo", port: 27017), name: "db") 18 | 19 | let router = Router() 20 | 21 | // Endpoint for /, returns plain old html 22 | router.get("/") { request, response, next in 23 | response.setHeader("Content-Type", value: "text/html; charset=utf-8") 24 | let p1 = request.params["user"] ?? "(nil)" 25 | do { 26 | try response.status(HttpStatusCode.OK).send( 27 | "

Hi from swift


" + 28 | "Give me some JSON").end() 29 | } catch { 30 | print("Failed to send response \(error)") 31 | } 32 | } 33 | 34 | // Endpoint for /about, returns JSON 35 | router.get("/about") { 36 | request, response, next in 37 | let json = JSON([ 38 | "hi": "from swift", 39 | "db": JSON([ 40 | "vendor": "mongo db", 41 | ]), 42 | "static": JSON([ 43 | "directory": "static/", 44 | "test-image": "/static/static.gif" 45 | ]), 46 | "admin": JSON([ 47 | "interface": "/admin/", 48 | "credentials": "env/admin.env" 49 | ]) 50 | ] 51 | ) 52 | response.status(HttpStatusCode.OK).sendJson(json) 53 | next() 54 | } 55 | 56 | let server = HttpServer.listen(8090, delegate: router) 57 | Server.run() 58 | -------------------------------------------------------------------------------- /dev.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | volumes: 4 | mongodb: {} 5 | 6 | services: 7 | 8 | nginx: 9 | build: 10 | dockerfile: dockerfiles/nginx/Dockerfile 11 | context: . 12 | ports: 13 | - "80:80" 14 | depends_on: 15 | - swift 16 | - mongo_express 17 | volumes: 18 | - "./static/:/var/www/static" 19 | env_file: 20 | - "./env/admin.env" 21 | 22 | swift: 23 | build: 24 | dockerfile: dockerfiles/swift/Dockerfile-dev 25 | context: . 26 | depends_on: 27 | - mongo 28 | volumes: 29 | - "./:/app" 30 | 31 | mongo: 32 | image: mongo 33 | volumes: 34 | - "mongodb:/data/db" 35 | 36 | mongo_express: 37 | image: mongo-express 38 | environment: 39 | - ME_CONFIG_SITE_BASEURL=/admin/mongo-express/ -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | volumes: 4 | mongodb: {} 5 | 6 | services: 7 | 8 | nginx: 9 | build: 10 | dockerfile: dockerfiles/nginx/Dockerfile 11 | context: . 12 | ports: 13 | - "80:80" 14 | # If you want to support TLS, enable port 443 and edit dockerfiles/nginx/nginx.conf 15 | #- "443:443" 16 | depends_on: 17 | - swift 18 | - mongo_express 19 | env_file: 20 | - "./env/admin.env" 21 | restart: always 22 | 23 | swift: 24 | build: 25 | dockerfile: dockerfiles/swift/Dockerfile 26 | context: . 27 | depends_on: 28 | - mongo 29 | restart: always 30 | 31 | mongo: 32 | image: mongo 33 | volumes: 34 | - "mongodb:/data/db" 35 | restart: always 36 | 37 | mongo_express: 38 | image: mongo-express 39 | environment: 40 | - ME_CONFIG_SITE_BASEURL=/admin/mongo-express/ 41 | restart: always -------------------------------------------------------------------------------- /dockerfiles/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.9 2 | COPY ./dockerfiles/nginx/nginx.conf /etc/nginx/nginx.conf 3 | COPY ./dockerfiles/nginx/admin /var/www/admin 4 | COPY ./static /var/www/static 5 | 6 | COPY ./dockerfiles/nginx/entrypoint.sh "/entrypoint.sh" 7 | ENTRYPOINT ["/entrypoint.sh"] 8 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /dockerfiles/nginx/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Admin 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
MongoDB Database
17 |

This project comes with a web-based MongoDB admin interface.

18 |

Access MongoDB Database

19 | 20 |
Static Files
21 |
22 |

Static files are served by nginx.

23 |

To add a new static file, add it to the static/ folder in the root directory of your project.

24 |

Show Demo File

25 |
26 |
27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /dockerfiles/nginx/admin/style.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Milligram v1.1.0 3 | * http://milligram.github.io 4 | * 5 | * Copyright (c) 2016 CJ Patoilo 6 | * Licensed under the MIT license 7 | */ 8 | 9 | 10 | html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:"Roboto","Helvetica Neue","Helvetica","Arial",sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}*,*:after,*:before{box-sizing:inherit}blockquote{border-left:.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:hover,.button:focus,button:hover,button:focus,input[type='button']:hover,input[type='button']:focus,input[type='reset']:hover,input[type='reset']:focus,input[type='submit']:hover,input[type='submit']:focus{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button.button-disabled,.button[disabled],button.button-disabled,button[disabled],input[type='button'].button-disabled,input[type='button'][disabled],input[type='reset'].button-disabled,input[type='reset'][disabled],input[type='submit'].button-disabled,input[type='submit'][disabled]{opacity:.5;cursor:default}.button.button-disabled:hover,.button.button-disabled:focus,.button[disabled]:hover,.button[disabled]:focus,button.button-disabled:hover,button.button-disabled:focus,button[disabled]:hover,button[disabled]:focus,input[type='button'].button-disabled:hover,input[type='button'].button-disabled:focus,input[type='button'][disabled]:hover,input[type='button'][disabled]:focus,input[type='reset'].button-disabled:hover,input[type='reset'].button-disabled:focus,input[type='reset'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='submit'].button-disabled:hover,input[type='submit'].button-disabled:focus,input[type='submit'][disabled]:hover,input[type='submit'][disabled]:focus{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{color:#9b4dca;background-color:transparent}.button.button-outline:hover,.button.button-outline:focus,button.button-outline:hover,button.button-outline:focus,input[type='button'].button-outline:hover,input[type='button'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='submit'].button-outline:hover,input[type='submit'].button-outline:focus{color:#606c76;background-color:transparent;border-color:#606c76}.button.button-outline.button-disabled:hover,.button.button-outline.button-disabled:focus,.button.button-outline[disabled]:hover,.button.button-outline[disabled]:focus,button.button-outline.button-disabled:hover,button.button-outline.button-disabled:focus,button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,input[type='button'].button-outline.button-disabled:hover,input[type='button'].button-outline.button-disabled:focus,input[type='button'].button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='reset'].button-outline.button-disabled:hover,input[type='reset'].button-outline.button-disabled:focus,input[type='reset'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='submit'].button-outline.button-disabled:hover,input[type='submit'].button-outline.button-disabled:focus,input[type='submit'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus{color:#9b4dca;border-color:inherit}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{color:#9b4dca;background-color:transparent;border-color:transparent}.button.button-clear:hover,.button.button-clear:focus,button.button-clear:hover,button.button-clear:focus,input[type='button'].button-clear:hover,input[type='button'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='submit'].button-clear:hover,input[type='submit'].button-clear:focus{color:#606c76;background-color:transparent;border-color:transparent}.button.button-clear.button-disabled:hover,.button.button-clear.button-disabled:focus,.button.button-clear[disabled]:hover,.button.button-clear[disabled]:focus,button.button-clear.button-disabled:hover,button.button-clear.button-disabled:focus,button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,input[type='button'].button-clear.button-disabled:hover,input[type='button'].button-clear.button-disabled:focus,input[type='button'].button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='reset'].button-clear.button-disabled:hover,input[type='reset'].button-clear.button-disabled:focus,input[type='reset'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='submit'].button-clear.button-disabled:hover,input[type='submit'].button-clear.button-disabled:focus,input[type='submit'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;padding:.2rem .5rem;margin:0 .2rem;white-space:nowrap}pre{background:#f4f5f6;border-left:.3rem solid #9b4dca;font-family:"Menlo","Consolas","Bitstream Vera Sans Mono","DejaVu Sans Mono","Monaco",monospace}pre>code{background:transparent;border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:.1rem solid #f4f5f6;margin-bottom:3.5rem;margin-top:3rem}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{appearance:none;background-color:transparent;border:.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;height:3.8rem;padding:.6rem 1rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border:.1rem solid #9b4dca;outline:0}select{padding:.6rem 3rem .6rem 1rem;background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjkgMTQiICAgaGVpZ2h0PSIxNHB4IiAgIGlkPSJMYXllcl8xIiAgIHZlcnNpb249IjEuMSIgICB2aWV3Qm94PSIwIDAgMjkgMTQiICAgd2lkdGg9IjI5cHgiICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ4LjQgcjk5MzkiICAgc29kaXBvZGk6ZG9jbmFtZT0iY2FyZXQtZ3JheS5zdmciPjxtZXRhZGF0YSAgICAgaWQ9Im1ldGFkYXRhMzAzOSI+PHJkZjpSREY+PGNjOldvcmsgICAgICAgICByZGY6YWJvdXQ9IiI+PGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+PGRjOnR5cGUgICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+PC9jYzpXb3JrPjwvcmRmOlJERj48L21ldGFkYXRhPjxkZWZzICAgICBpZD0iZGVmczMwMzciIC8+PHNvZGlwb2RpOm5hbWVkdmlldyAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiICAgICBib3JkZXJvcGFjaXR5PSIxIiAgICAgb2JqZWN0dG9sZXJhbmNlPSIxMCIgICAgIGdyaWR0b2xlcmFuY2U9IjEwIiAgICAgZ3VpZGV0b2xlcmFuY2U9IjEwIiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSI5MDMiICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI1OTQiICAgICBpZD0ibmFtZWR2aWV3MzAzNSIgICAgIHNob3dncmlkPSJ0cnVlIiAgICAgaW5rc2NhcGU6em9vbT0iMTIuMTM3OTMxIiAgICAgaW5rc2NhcGU6Y3g9Ii00LjExOTMxODJlLTA4IiAgICAgaW5rc2NhcGU6Y3k9IjciICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNTAyIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjMwMiIgICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIj48aW5rc2NhcGU6Z3JpZCAgICAgICB0eXBlPSJ4eWdyaWQiICAgICAgIGlkPSJncmlkMzA0MSIgLz48L3NvZGlwb2RpOm5hbWVkdmlldz48cG9seWdvbiAgICAgcG9pbnRzPSIwLjE1LDAgMTQuNSwxNC4zNSAyOC44NSwwICIgICAgIGlkPSJwb2x5Z29uMzAzMyIgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAuMzU0MTEzODcsMCwwLDAuNDgzMjkxMSw5LjMyNDE1NDUsMy42MjQ5OTkyKSIgICAgIHN0eWxlPSJmaWxsOiNkMWQxZDE7ZmlsbC1vcGFjaXR5OjEiIC8+PC9zdmc+) center right no-repeat}select:focus{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjkgMTQiICAgaGVpZ2h0PSIxNHB4IiAgIGlkPSJMYXllcl8xIiAgIHZlcnNpb249IjEuMSIgICB2aWV3Qm94PSIwIDAgMjkgMTQiICAgd2lkdGg9IjI5cHgiICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ4LjQgcjk5MzkiICAgc29kaXBvZGk6ZG9jbmFtZT0iY2FyZXQuc3ZnIj48bWV0YWRhdGEgICAgIGlkPSJtZXRhZGF0YTMwMzkiPjxyZGY6UkRGPjxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcyAgICAgaWQ9ImRlZnMzMDM3IiAvPjxzb2RpcG9kaTpuYW1lZHZpZXcgICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIgICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IiAgICAgYm9yZGVyb3BhY2l0eT0iMSIgICAgIG9iamVjdHRvbGVyYW5jZT0iMTAiICAgICBncmlkdG9sZXJhbmNlPSIxMCIgICAgIGd1aWRldG9sZXJhbmNlPSIxMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iOTAzIiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iNTk0IiAgICAgaWQ9Im5hbWVkdmlldzMwMzUiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIGlua3NjYXBlOnpvb209IjEyLjEzNzkzMSIgICAgIGlua3NjYXBlOmN4PSItNC4xMTkzMTgyZS0wOCIgICAgIGlua3NjYXBlOmN5PSI3IiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjUwMiIgICAgIGlua3NjYXBlOndpbmRvdy15PSIzMDIiICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0iTGF5ZXJfMSI+PGlua3NjYXBlOmdyaWQgICAgICAgdHlwZT0ieHlncmlkIiAgICAgICBpZD0iZ3JpZDMwNDEiIC8+PC9zb2RpcG9kaTpuYW1lZHZpZXc+PHBvbHlnb24gICAgIHBvaW50cz0iMjguODUsMCAwLjE1LDAgMTQuNSwxNC4zNSAiICAgICBpZD0icG9seWdvbjMwMzMiICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLjM1NDExMzg3LDAsMCwwLjQ4MzI5MTEsOS4zMjQxNTUzLDMuNjI1KSIgICAgIHN0eWxlPSJmaWxsOiM5YjRkY2Y7ZmlsbC1vcGFjaXR5OjEiIC8+PC9zdmc+)}textarea{padding-bottom:.6rem;padding-top:.6rem;min-height:6.5rem}label,legend{font-size:1.6rem;font-weight:700;display:block;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{font-weight:normal;display:inline-block;margin-left:.5rem}.container{margin:0 auto;max-width:112rem;padding:0 2rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row .row-wrap{flex-wrap:wrap}.row .row-no-padding{padding:0}.row .row-no-padding>.column{padding:0}.row .row-top{align-items:flex-start}.row .row-bottom{align-items:flex-end}.row .row-center{align-items:center}.row .row-stretch{align-items:stretch}.row .row-baseline{align-items:baseline}.row .column{display:block;flex:1;margin-left:0;max-width:100%;width:100%}.row .column .col-top{align-self:flex-start}.row .column .col-bottom{align-self:flex-end}.row .column .col-center{align-self:center}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1rem}}a{color:#9b4dca;text-decoration:none}a:hover{color:#606c76}dl,ol,ul{margin-top:0;padding-left:0}dl ul,dl ol,ol ul,ol ol,ul ul,ul ol{font-size:90%;margin:1.5rem 0 1.5rem 3rem}dl{list-style:none}ul{list-style:circle inside}ol{list-style:decimal inside}dt,dd,li{margin-bottom:1rem}.button,button{margin-bottom:1rem}input,textarea,select,fieldset{margin-bottom:1.5rem}pre,blockquote,dl,figure,table,p,ul,ol,form{margin-bottom:2.5rem}table{width:100%}th,td{border-bottom:.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}th:first-child,td:first-child{padding-left:0}th:last-child,td:last-child{padding-right:0}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;margin-bottom:2rem;margin-top:0}h1{font-size:4rem;letter-spacing:-0.1rem;line-height:1.2}h2{font-size:3.6rem;letter-spacing:-0.1rem;line-height:1.25}h3{font-size:3rem;letter-spacing:-0.1rem;line-height:1.3}h4{font-size:2.4rem;letter-spacing:-0.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-0.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}@media (min-width: 40rem){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}.float-right{float:right}.float-left{float:left}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both} 11 | -------------------------------------------------------------------------------- /dockerfiles/nginx/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # creates the .htpasswd file for the admin interface 4 | printf "${ADMIN_USER}:$(openssl passwd -crypt ${ADMIN_PASSWORD})\n" > /etc/nginx/admin.htpasswd 5 | exec "$@" 6 | -------------------------------------------------------------------------------- /dockerfiles/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | '$status $body_bytes_sent "$http_referer" ' 19 | '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | access_log /var/log/nginx/access.log main; 22 | 23 | sendfile on; 24 | 25 | keepalive_timeout 65; 26 | 27 | upstream app { 28 | server swift:8090; 29 | } 30 | 31 | upstream mongo-express { 32 | server mongo_express:8081; 33 | } 34 | 35 | server { 36 | # if you want to support TLS, see https://mozilla.github.io/server-side-tls/ssl-config-generator/ 37 | # for a neat generator 38 | listen 80; 39 | charset utf-8; 40 | 41 | location /admin/ { 42 | 43 | auth_basic "Restricted"; 44 | auth_basic_user_file /etc/nginx/admin.htpasswd; 45 | 46 | alias /var/www/admin/; 47 | 48 | location /admin/mongo-express/ { 49 | 50 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 51 | proxy_set_header Host $http_host; 52 | proxy_redirect off; 53 | proxy_pass http://mongo-express; 54 | } 55 | 56 | } 57 | 58 | location /static { 59 | alias /var/www/static; 60 | } 61 | 62 | location / { 63 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 64 | proxy_set_header Host $http_host; 65 | proxy_redirect off; 66 | proxy_pass http://app; 67 | 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /dockerfiles/swift/Dockerfile: -------------------------------------------------------------------------------- 1 | # This image uses serversideswift/swift as a base image. Take a look at the dockerfile at 2 | # https://github.com/serversideswift/swift-docker/blob/master/Dockerfile 3 | FROM serversideswift/swift:DEVELOPMENT-SNAPSHOT-2016-03-24-a 4 | 5 | # installs the mongoc driver and libbson 6 | RUN curl -L https://github.com/mongodb/mongo-c-driver/releases/download/1.3.0/mongo-c-driver-1.3.0.tar.gz > mongo-c-driver-1.3.0.tar.gz && \ 7 | tar xzf mongo-c-driver-1.3.0.tar.gz && \ 8 | cd mongo-c-driver-1.3.0 && \ 9 | ./configure && \ 10 | make && \ 11 | make install && \ 12 | cp -lf /usr/local/lib/libbson* /usr/lib && \ 13 | sed -i 's//"bson.h"/g' /usr/local/include/libbson-1.0/bcon.h && \ 14 | echo /usr/local/lib > /etc/ld.so.conf.d/mongoc.conf && \ 15 | ldconfig 16 | 17 | WORKDIR /app 18 | 19 | # copies Package.swift and fetches all packages. We do this here because this way we can use dockers 20 | # caching layer and don't have to fetch every dependency whenever a tiny piece of code changes. 21 | COPY ./Package.swift /app/Package.swift 22 | RUN swift build --fetch 23 | 24 | # copies the sources to /app and creates a new user called swift that owns the directory. 25 | COPY ./Sources /app 26 | RUN groupadd -r swift && useradd -r -g swift swift 27 | RUN chown -R swift /app 28 | 29 | # WARNING: SwiftyJSON takes around 30mins to build with release configuration, we are using the 30 | # debug configuration for now until https://github.com/apple/swift/commit/2cdd7d64e1e2add7bcfd5452d36e7f5fc6c86a03 31 | # is merged 32 | RUN swift build --configuration debug -Xcc -fblocks -Xlinker -ldispatch -Xcc -I/usr/local/include/libbson-1.0/ 33 | USER SWIFT 34 | CMD [".build/debug/#PROJECT_NAME#"] 35 | # ---------------------------------------------------------------------------- 36 | # build the code with the release configuration and link blocks, ldispatch and libbson 37 | #RUN swift build --configuration release -Xcc -fblocks -Xlinker -ldispatch -Xcc -I/usr/local/include/libbson-1.0/ 38 | 39 | #All commands that follow are run as the swift user. This is a layer of security. If someone manages 40 | # to break into the running container and execute a remote shell, it won't be a root shell at least. 41 | #USER swift 42 | #CMD [".build/release/#PROJECT_NAME#"] 43 | # ---------------------------------------------------------------------------- -------------------------------------------------------------------------------- /dockerfiles/swift/Dockerfile-dev: -------------------------------------------------------------------------------- 1 | # This image inherits from serversideswift/swift. Take a look at the dockerfile at 2 | # https://github.com/serversideswift/swift-docker/blob/master/Dockerfile 3 | FROM serversideswift/swift:DEVELOPMENT-SNAPSHOT-2016-03-24-a 4 | 5 | # installs the mongoc driver and libbson 6 | RUN curl -L https://github.com/mongodb/mongo-c-driver/releases/download/1.3.0/mongo-c-driver-1.3.0.tar.gz > mongo-c-driver-1.3.0.tar.gz && \ 7 | tar xzf mongo-c-driver-1.3.0.tar.gz && \ 8 | cd mongo-c-driver-1.3.0 && \ 9 | ./configure && \ 10 | make && \ 11 | make install && \ 12 | cp -lf /usr/local/lib/libbson* /usr/lib && \ 13 | sed -i 's//"bson.h"/g' /usr/local/include/libbson-1.0/bcon.h && \ 14 | echo /usr/local/lib > /etc/ld.so.conf.d/mongoc.conf && \ 15 | ldconfig 16 | 17 | # Install inotify-tools for autoreload 18 | RUN apt-get update && \ 19 | apt-get install -y \ 20 | inotify-tools && \ 21 | apt-get clean && \ 22 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 23 | 24 | WORKDIR /app 25 | 26 | COPY ./dockerfiles/swift/autoreload.sh /autoreload.sh 27 | CMD ["sh", "/autoreload.sh"] -------------------------------------------------------------------------------- /dockerfiles/swift/autoreload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | start () { 4 | # starts the build process in debug configuration and launches the binary in detached mode 5 | swift build -Xcc -fblocks -Xlinker -ldispatch -Xcc -I/usr/local/include/libbson-1.0/ 6 | /app/.build/debug/#PROJECT_NAME# & 7 | PID=$! 8 | } 9 | 10 | start 11 | 12 | # listens for code changes in /app/Sources and reruns start() whenever there is a change detected 13 | inotifywait -mr /app/Sources --format '%e %f' \ 14 | -e modify -e delete -e move -e create -e attrib \ 15 | --exclude '~.swift' \ 16 | | while read event file; do 17 | 18 | echo $event $file 19 | 20 | kill $PID 21 | start 22 | 23 | done -------------------------------------------------------------------------------- /env/admin.env: -------------------------------------------------------------------------------- 1 | ADMIN_USER=#ADMIN_USER# 2 | ADMIN_PASSWORD=#ADMIN_PASSWORD# -------------------------------------------------------------------------------- /static/static.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayfk/server-side-swift-starter/74b58a3ecac3347814415acc547b7c7cba4476f4/static/static.gif --------------------------------------------------------------------------------