Automated 1-click deployment of Swift projects to servers. Once set up, deploying your project is as simple as:
$ thunder deploy
Thunder will clone your project onto your server(s), build it, and start the application (and do anything else you want it to do).
Originally forked off of Flock.
brew install anthonycastelli/repo/thundergit clone https://github.com/anthonycastelli/ThunderCLI
cd ThunderCLI
swift build -c release
ln -s .build/release/ThunderCLI /usr/local/bin/thunderIf the symlink doesnt work, get the full path of the current working directory using pwd
To start using Thunder, run:
thunder --initAfter this command completes, you should follow the instructions Thunder prints. Following these instructions should be enough to get your project up and running. For more information about the files Flock creates, read on.
The Flockfile specifies which tasks and configurations you want Flock to use.
Thunder.Deploy includes the following tasks:
thunder deploy # Invokes deploy:git, deploy:build, and deploy:link
thunder deploy:git # Clones your project onto your server into a timestamped directory
thunder deploy:build # Builds your project
thunder deploy:link # Links your newly built project directory as the current directoryRunning thunder deploy will:
- Clone your project onto your server into a timestamped directory (e.g.
/home/deploy/www/VaporExample/releases/20161028211084) - Build your project
- Link your built project to the
currentdirectory (e.g./home/deploy/www/VaporExample/current)
FThunder.Tools includes tasks which assist in installing the necessary tools for your swift project to run on the server:
thunder tools # Invokes tools:dependencies, tools:swift
thunder tools:dependencies # Installs dependencies necessary for
thunder tools:account # Will setup the deploy accountThis file contains your Flock dependencies. To start this only contains Flock itself, but if you want to use third party tasks you can add their repositories here. You specify the repository's URL and version (there are three ways to specify version):
{
"dependencies" : [
{
"name" : "https://github.com/anthonycastelli/Thunder",
"major": 1
}
]
}See the dependencies section below for more information on third party dependencies.
This file contains configuration which will always be used. This is where config which is needed regardless of environment should be placed. Some fields you'll need to update before running any Flock tasks:
Config.projectName = "ProjectName"
Config.executableName = "ExecutableName"
Config.repoURL = "URL"
These files contain configuration specific to the production and staging environments, respectively. They will only be run when Flock is executed in their environment (using thunder task -e staging). Generally this is where you'll want to specify your production and staging servers. There are multiple ways to specify a server:
func configure() {
// For project-wide auth:
Config.SSHAuthMethod = .key("/path/to/my/key")
Servers.add(ip: "9.9.9.9", user: "deploy", roles: [.app, .db, .web])
// For server-specific auth:
Servers.add(ip: "9.9.9.9", user: "deploy", roles: [.app, .db, .web], authMethod: .key("/path/to/another/key"))
// Or, if you've added your server to your .ssh/config file, you can use this shorthand:
Servers.add(SSHHost: "NamedServer", roles: [.app, .db, .web])
}You can (in general) ignore all the files in this directory.
If you want to add additional configuration environments (beyond "staging" and "production), you can do that in the Thunderfile. To create a testing environment, for example, you would start by running thunder --add-env Testing and then modify the Thunderfile as such:
...
Thunder.configure(.always, with: Always()) // Located at config/deploy/Always.swift
Thunder.configure(.env("production"), with: Production()) // Located at config/deploy/Production.swift
Thunder.configure(.env("staging"), with: Staging()) // Located at config/deploy/Staging.swift
Thunder.configure(.env("testing"), with: Testing()) // Located at config/deploy/Testing.swift
...You can see the available tasks by running flock with no arguments. To run a task, just call flock <task>, such as:
thunder deploy # Run the deploy task
thunder deploy:build # Run the build task located under the deploy namespacePassing the -n flag tells Flock to do a dry-run, meaning to only print commands without actually executing any.
thunder vapor:start -n # Do a dry-run of the start task located under the vapor namespaceYou can also pass the -e <env> key, telling Flock to run the task in a certain environment:
thunder tools -e staging # Run the tools task in the staging environmentStart by running:
thunder --create db:migrate # Or whatever you want to call your new taskThis will create file at config/deploy/DbMigrateTask.swift with the following contents:
import Thunder
extension Thunder {
public static let <NameThisGroup>: [Task] = [
MigrateTask()
]
}
// Delete if no custom Config properties are needed
extension Config {
// public static var myVar = ""
}
class MigrateTask: Task {
let name = "migrate"
let namespace = "db"
func run(on server: Server) throws {
// Do work
}
}Some of Server's available methods are:
try server.execute("mysql -v") // Execute a command remotely
let contents = try server.capture("cat myFile") // Execute a command remotely and capture the output
// Execute all commands in this closure within Path.currentDirectory
try server.within(Path.currentDirectory) {
try server.execute("ls")
if server.fileExists("anotherFile.txt") { // Check the existence of a file on the server
try server.execute("cat anotherFile.txt")
}
}Check out Server.swift to see all of Server's available methods. Also take a look at Paths.swift to see the built-in paths for your server.within calls.
After running thunder --create, make sure you:
- Replace <NameThisGroup> at the top of your new file with a custom name
- In your Flockfile, add
Flock.use(WhateverINamedIt)
If you wish to hook your task onto another task (i.e. always run this task before/after another task, just add an array of hook times to your Task:
class MigrateTask: Task {
let name = "migrate"
let namespace = "db"
let hookTimes: [HookTime] = [.after("deploy:build")]
func run(on server: Server) throws {
// Do work
}
}func run(on server: Server) throws {
try invoke("other:task")
}In general, you should create a dedicated deploy user on your server. Authentication & Authorisation is a great resource for learning how to do this.
To ensure the deploy task succeeds, make sure:
- The deploy user has access to
Config.deployDirectory(default /home/deploy/www) - The deploy user has access to the
swiftexecutable
Some additional considerations if you are using Thunder.Server:
- The deploy user can run
supervisorctlcommands (see Using supervisorctl with linux permissions but without root or sudo for more info) - The deploy user has access to the
supervisorconfig file (default /etc/supervisor/conf.d/server.conf)
Running flock tools can take care of most of these things for you, but you must set Config.supervisordUser in config/deploy/Always.swift to your dedicated deploy user before running thunder tools.
The tools task must be run as the root user. This means that in config/deploy/Production.swift, in your Servers.add call you must pass user: "root". As mentioned above, it is not a good idea to deploy with user: "root", so you should only call flock tools with this configuration and then change it to make calls with your dedicated deploy user rather then the root user.
Don't forget to setup your vapor NGINX config file. A good example is The Vapor Docs
Here is a nice getting started setup for SSL certificates via LetsEncrypt GitHub Gist
Don't forget your sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
If you use LetsEncrypt, you'll want to setup some sort of renewal script.
- Edit your crontab: "sudo crontab -e" (This must be done on the root account. EC2 Instance is ubuntu via the certificate from AWS)
- Add this to your crontab
# Lets Encrypt SSL Renewal every Monday at 2:30 AM
30 2 * * 1 sudo certbot renew --noninteractive --renew-hook >> /home/deploy/logs/le-renew.log
35 2 * * 1 sudo systemctl reload nginx