mimas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +106 -0
- data/LICENSE.txt +22 -0
- data/README.md +346 -0
- data/Rakefile +4 -0
- data/bin/mimas +5 -0
- data/lib/mimas/archiver.rb +68 -0
- data/lib/mimas/base_command.rb +22 -0
- data/lib/mimas/cli.rb +22 -0
- data/lib/mimas/commands/console.rb +21 -0
- data/lib/mimas/commands/init.rb +63 -0
- data/lib/mimas/commands/install.rb +21 -0
- data/lib/mimas/commands/provision.rb +50 -0
- data/lib/mimas/commands/push.rb +28 -0
- data/lib/mimas/commands/setup/caddy.rb +18 -0
- data/lib/mimas/commands/setup/deploy_user.rb +16 -0
- data/lib/mimas/commands/setup/ruby.rb +17 -0
- data/lib/mimas/commands/setup.rb +10 -0
- data/lib/mimas/config.rb +124 -0
- data/lib/mimas/deployment/caddy.rb +39 -0
- data/lib/mimas/deployment/files.rb +51 -0
- data/lib/mimas/deployment/paths.rb +54 -0
- data/lib/mimas/deployment/ruby_app.rb +126 -0
- data/lib/mimas/deployment/static_site.rb +28 -0
- data/lib/mimas/deployment.rb +27 -0
- data/lib/mimas/hooks.rb +25 -0
- data/lib/mimas/plugins.rb +19 -0
- data/lib/mimas/scripts/configure_ruby.sh +19 -0
- data/lib/mimas/scripts/create_deploy_user.sh +30 -0
- data/lib/mimas/scripts/harden.sh +125 -0
- data/lib/mimas/scripts/install_caddy.sh +51 -0
- data/lib/mimas/scripts/install_ruby_prerequisites.sh +26 -0
- data/lib/mimas/server/caddy.rb +17 -0
- data/lib/mimas/server/console.rb +34 -0
- data/lib/mimas/server/deploy_user.rb +11 -0
- data/lib/mimas/server/harden.rb +20 -0
- data/lib/mimas/server/prerequisites.rb +25 -0
- data/lib/mimas/server/ruby.rb +37 -0
- data/lib/mimas/server.rb +37 -0
- data/lib/mimas/site.rb +24 -0
- data/lib/mimas/ssh.rb +78 -0
- data/lib/mimas/template.rb +28 -0
- data/lib/mimas/templates/Caddyfile.ruby.erb +12 -0
- data/lib/mimas/templates/Caddyfile.static.erb +15 -0
- data/lib/mimas/templates/caddy.service.erb +19 -0
- data/lib/mimas/templates/config.rb.erb +51 -0
- data/lib/mimas/templates/falcon.rb.erb +12 -0
- data/lib/mimas/templates/mimas.env.erb +2 -0
- data/lib/mimas/templates/puma.rb +11 -0
- data/lib/mimas/templates/systemd.service.erb +20 -0
- data/lib/mimas/terminal/ansi.rb +71 -0
- data/lib/mimas/terminal/printer.rb +14 -0
- data/lib/mimas/version.rb +3 -0
- data/lib/mimas.rb +48 -0
- data/mimas.gemspec +29 -0
- data/scripts/release +8 -0
- data/scripts/test +7 -0
- data/tmp/.keep +0 -0
- metadata +181 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Init < BaseCommand
|
|
5
|
+
desc "Create the Mimas config file"
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
say "Initialising Mimas. We need some information about your site."
|
|
9
|
+
say ""
|
|
10
|
+
|
|
11
|
+
say "Your site's domain will be the containing folder on the server."
|
|
12
|
+
domain = ask "What is your site's domain name?"
|
|
13
|
+
ip = ask "What is your production server's IP address?"
|
|
14
|
+
site_type = ask "Are you creating a \n 1. Ruby app \n 2. Static site \n (1 | 2)"
|
|
15
|
+
site_type = site_types[site_type]
|
|
16
|
+
|
|
17
|
+
app_server = nil
|
|
18
|
+
if site_type == :ruby
|
|
19
|
+
app_server = ask "Which Ruby application server are you using?"
|
|
20
|
+
app_server&.downcase!
|
|
21
|
+
|
|
22
|
+
unless ["falcon", "puma"].include?(app_server)
|
|
23
|
+
say "Mimas natively supports Falcon and Puma. To use other server, ensure you configure it to bind to a unix socket defined in `ENV['MIMAS_UNIX_SOCKET']`".white.bold
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Dir.mkdir("deploy") unless Dir.exist? "deploy"
|
|
28
|
+
|
|
29
|
+
destination_dir = File.join(Dir.pwd, Mimas::Config::DEFAULT_DIRECTORY)
|
|
30
|
+
|
|
31
|
+
configrb = template("config.rb", ip: ip, domain: domain, site_type: site_type, app_server: app_server)
|
|
32
|
+
File.write(File.join(destination_dir, "mimas.rb"), configrb)
|
|
33
|
+
|
|
34
|
+
if site_type == :ruby
|
|
35
|
+
copy_file(destination_dir, "Caddyfile.ruby.erb", rename: "Caddyfile.erb")
|
|
36
|
+
|
|
37
|
+
if app_server == "falcon"
|
|
38
|
+
falconrb = template("falcon.rb", domain: domain)
|
|
39
|
+
File.write(File.join(destination_dir, "falcon.rb"), falconrb)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
copy_file(destination_dir, "puma.rb") if app_server == "puma"
|
|
43
|
+
else
|
|
44
|
+
copy_file(destination_dir, "Caddyfile.static.erb", rename: "Caddyfile.erb")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Dir.mkdir("tmp") unless Dir.exist?("tmp")
|
|
48
|
+
|
|
49
|
+
say ""
|
|
50
|
+
say "Successfully created 'deploy/mimas.rb' and 'deploy/Caddyfile.erb'. Customise your deployment by editing these files."
|
|
51
|
+
say "Please ensure your DNS is correctly configured."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def site_types
|
|
55
|
+
{
|
|
56
|
+
"1" => :ruby,
|
|
57
|
+
"2" => :static
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Install < BaseCommand
|
|
5
|
+
desc "Install a new version of Ruby on the server."
|
|
6
|
+
|
|
7
|
+
argument :ruby_version, required: true, desc: "The version of Ruby to install."
|
|
8
|
+
argument :server_name, required: true, desc: "The name of the server to install Ruby on"
|
|
9
|
+
|
|
10
|
+
example [
|
|
11
|
+
"4.0.5 production"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
def call(ruby_version:, server_name:)
|
|
15
|
+
server = Config.current.servers[server_name.to_sym]
|
|
16
|
+
server.install_ruby(version: ruby_version)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Provision < BaseCommand
|
|
5
|
+
desc "Provision the remote server by applying security settings, creating users, and installing Caddy and Ruby. Requires root access."
|
|
6
|
+
|
|
7
|
+
argument :server_name, required: true, desc: "The name of the server to provision"
|
|
8
|
+
|
|
9
|
+
option :user, default: "admin", aliases: ["-u"], desc: "A user with sudo access"
|
|
10
|
+
option :skip_ruby, default: false, type: :flag, desc: "Skip the installation of Ruby and its prerequisites. Use when only serving static sites"
|
|
11
|
+
|
|
12
|
+
example [
|
|
13
|
+
"production # Provision the production server",
|
|
14
|
+
"production -u administrator # Specify the sudo user as 'administrator'",
|
|
15
|
+
"--skip-ruby # Skip Ruby and install only Caddy"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
def call(server_name:, **options)
|
|
19
|
+
server = Config.current.servers[server_name.to_sym]
|
|
20
|
+
sudo_user = options[:user]
|
|
21
|
+
skip_ruby = options[:skip_ruby]
|
|
22
|
+
|
|
23
|
+
server.harden
|
|
24
|
+
|
|
25
|
+
say "Waiting for server to come back online ..."
|
|
26
|
+
sleep 30
|
|
27
|
+
|
|
28
|
+
connection_retries = 0
|
|
29
|
+
begin
|
|
30
|
+
connection_retries += 1
|
|
31
|
+
Net::SSH.start(server.host, sudo_user, **server.ssh_options)&.close
|
|
32
|
+
rescue
|
|
33
|
+
sleep 5
|
|
34
|
+
retry if connection_retries < 5
|
|
35
|
+
say "Unable to re-connect to server."
|
|
36
|
+
say "Wait for the server to come online, then run `bin/mimas setup`"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
server.setup_deploy_user(sudo_user: sudo_user)
|
|
40
|
+
server.install_caddy(sudo_user: sudo_user)
|
|
41
|
+
|
|
42
|
+
unless skip_ruby
|
|
43
|
+
server.install_ruby_prerequisites(sudo_user: sudo_user)
|
|
44
|
+
server.install_ruby
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Push < BaseCommand
|
|
5
|
+
desc "Push your Ruby app to the server"
|
|
6
|
+
|
|
7
|
+
argument :server_name, required: true, desc: "The name of the server to push to"
|
|
8
|
+
argument :site, desc: "The site to push. Uses the default site if unspecified"
|
|
9
|
+
|
|
10
|
+
example [
|
|
11
|
+
"production # Push the default site to the production server",
|
|
12
|
+
"production app # Push the `:app` site to the production server"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
def call(server_name:, site: :default)
|
|
16
|
+
server = Config.current.servers[server_name.to_sym]
|
|
17
|
+
site = Config.current.sites[site.to_sym]
|
|
18
|
+
|
|
19
|
+
@hooks[:before_push].each { it.call }
|
|
20
|
+
|
|
21
|
+
Deployment.new(site: site, server: server).deploy
|
|
22
|
+
|
|
23
|
+
@hooks[:after_push].each { it.call }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Setup::Caddy < Setup
|
|
5
|
+
desc "Setup Caddy on a server"
|
|
6
|
+
|
|
7
|
+
option :user, default: "admin", aliases: ["-u"], desc: "A user with sudo access. Defaults to 'admin'"
|
|
8
|
+
|
|
9
|
+
def call(server_name:, **options)
|
|
10
|
+
server = Config.current.servers[server_name.to_sym]
|
|
11
|
+
sudo_user = options[:user]
|
|
12
|
+
|
|
13
|
+
server.install_caddy(sudo_user: sudo_user)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Setup::DeployUser < Setup
|
|
5
|
+
desc "Create a deployment user as defined in your config file on a server"
|
|
6
|
+
|
|
7
|
+
def call(server_name:, **options)
|
|
8
|
+
server = Config.current.servers[server_name.to_sym]
|
|
9
|
+
sudo_user = options[:user]
|
|
10
|
+
|
|
11
|
+
server.setup_deploy_user(sudo_user: sudo_user)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class Setup::Ruby < Setup
|
|
5
|
+
desc "Install Ruby prerequisites and build a Ruby binary on a server"
|
|
6
|
+
|
|
7
|
+
def call(server_name:, **options)
|
|
8
|
+
server = Config.current.servers[server_name.to_sym]
|
|
9
|
+
sudo_user = options[:user]
|
|
10
|
+
|
|
11
|
+
server.install_ruby_prerequisites(sudo_user: sudo_user)
|
|
12
|
+
server.install_ruby
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/mimas/config.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Config
|
|
3
|
+
DEFAULT_DIRECTORY = "deploy"
|
|
4
|
+
|
|
5
|
+
def self.current
|
|
6
|
+
@current ||= load
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.load(path = File.join(Dir.pwd, DEFAULT_DIRECTORY, "mimas.rb"))
|
|
10
|
+
if File.exist?(path)
|
|
11
|
+
source = File.read(path)
|
|
12
|
+
@current = build(source)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.open(&block)
|
|
17
|
+
DSL.new(&block).config
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.build(source)
|
|
21
|
+
DSL.new(source).config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :sites, :servers, :hooks
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@sites = {}
|
|
28
|
+
@servers = {}
|
|
29
|
+
@hooks = Hooks.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ruby_version
|
|
33
|
+
@ruby_version ||= begin
|
|
34
|
+
File.read(File.join(Dir.pwd, ".ruby-version")).chomp
|
|
35
|
+
rescue Errno::ENOENT
|
|
36
|
+
RUBY_VERSION
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
class DSL
|
|
43
|
+
attr_reader :config
|
|
44
|
+
|
|
45
|
+
def initialize(source = nil, &block)
|
|
46
|
+
@config = Config.new
|
|
47
|
+
|
|
48
|
+
if source
|
|
49
|
+
instance_eval(source)
|
|
50
|
+
else
|
|
51
|
+
instance_exec(&block)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def site(name = :default, &block)
|
|
56
|
+
name = name.to_sym
|
|
57
|
+
args = Site.evaluate(&block)
|
|
58
|
+
@config.sites[name] = Mimas::Site.new(name, args)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def server(name, &block)
|
|
62
|
+
name = name.to_sym
|
|
63
|
+
args = Options.evaluate(&block)
|
|
64
|
+
@config.servers[name] = Mimas::Server.new(name, args)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def hooks(&block)
|
|
68
|
+
@config.hooks.instance_exec(&block)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Options
|
|
73
|
+
def self.evaluate(&block)
|
|
74
|
+
evaluator = self.new
|
|
75
|
+
evaluator.instance_exec(&block)
|
|
76
|
+
evaluator.options
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attr_reader :options
|
|
80
|
+
|
|
81
|
+
def initialize
|
|
82
|
+
@options = {}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def method_missing(name, *args, &block)
|
|
86
|
+
if block
|
|
87
|
+
@options[name.to_sym] = block.call
|
|
88
|
+
else
|
|
89
|
+
@options[name.to_sym] = args.first
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Site < Options
|
|
95
|
+
def services(&block)
|
|
96
|
+
@options[:services] = Services.parse(&block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def deploy(&block)
|
|
100
|
+
@options[:deploy] = block.call.to_s
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class Services
|
|
105
|
+
def self.parse(&block)
|
|
106
|
+
services_parser = self.new
|
|
107
|
+
services_parser.instance_exec(&block)
|
|
108
|
+
services_parser.services
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
attr_reader :services
|
|
112
|
+
def initialize
|
|
113
|
+
@services = {}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def method_missing(name, *args, &block)
|
|
117
|
+
@services[name] = Options.evaluate do
|
|
118
|
+
instance_eval(&block)
|
|
119
|
+
end
|
|
120
|
+
@services[name][:name] = name
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Deployment
|
|
3
|
+
module Caddy
|
|
4
|
+
def refreshing_caddy(session, &block)
|
|
5
|
+
session.run "mkdir -p #{remote_destination}"
|
|
6
|
+
session.upload StringIO.new(commit_hash), remote_destination.join("commit-sha")
|
|
7
|
+
|
|
8
|
+
remote_caddyfile = ""
|
|
9
|
+
begin
|
|
10
|
+
remote_caddyfile = session.run "cat #{caddyfile_path}", capture: true
|
|
11
|
+
rescue SSH::Session::NonZeroExitCode
|
|
12
|
+
# Ignore non-zero exit
|
|
13
|
+
end
|
|
14
|
+
caddyfile_modified = remote_caddyfile != caddyfile
|
|
15
|
+
|
|
16
|
+
if caddyfile_modified
|
|
17
|
+
say "Uploading new Caddyfile"
|
|
18
|
+
begin
|
|
19
|
+
session.upload StringIO.new(caddyfile), new_caddyfile_path
|
|
20
|
+
session.run "caddy validate --config #{new_caddyfile_path}", capture: true
|
|
21
|
+
say "New Caddyfile validated"
|
|
22
|
+
rescue SSH::Session::NonZeroExitCode
|
|
23
|
+
session.run "rm #{new_caddyfile_path}"
|
|
24
|
+
say caddyfile.bold.red
|
|
25
|
+
raise "Your Caddyfile is invalid"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
yield
|
|
30
|
+
|
|
31
|
+
if caddyfile_modified
|
|
32
|
+
say "Updating Caddyfile and reloading Caddy"
|
|
33
|
+
session.run "mv #{new_caddyfile_path} #{caddyfile_path}"
|
|
34
|
+
session.run "sudo systemctl reload caddy"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Deployment
|
|
3
|
+
module Files
|
|
4
|
+
CADDYFILE_PATH = Pathname(Dir.pwd).join(Mimas::Config::DEFAULT_DIRECTORY)
|
|
5
|
+
|
|
6
|
+
def caddyfile
|
|
7
|
+
@caddyfile ||= begin
|
|
8
|
+
file_name = ["Caddyfile", site.name, "erb"].reject { it == :default }.join(".")
|
|
9
|
+
ERB.new(File.read(CADDYFILE_PATH.join(file_name)))
|
|
10
|
+
.result_with_hash(
|
|
11
|
+
site: site,
|
|
12
|
+
server: server,
|
|
13
|
+
domain: site.domain,
|
|
14
|
+
site_root: site_root,
|
|
15
|
+
unix_socket: unix_socket
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def systemd_service_name(service, version: self.version)
|
|
21
|
+
return nil if site.static?
|
|
22
|
+
|
|
23
|
+
"#{site.domain}--#{service[:name]}--#{version}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def systemd(service)
|
|
27
|
+
return nil if site.static?
|
|
28
|
+
|
|
29
|
+
template("systemd.service",
|
|
30
|
+
domain: site.domain,
|
|
31
|
+
version: version,
|
|
32
|
+
user: server.user,
|
|
33
|
+
site_root: site_root,
|
|
34
|
+
service_name: service[:name],
|
|
35
|
+
command: service[:command],
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def mimas_env
|
|
40
|
+
return nil if site.static?
|
|
41
|
+
|
|
42
|
+
@mimas_env ||=
|
|
43
|
+
template("mimas.env", unix_socket: unix_socket, env: site.env)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def deploy_script
|
|
47
|
+
".mimas_deploy.sh"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Deployment
|
|
3
|
+
module Paths
|
|
4
|
+
def remote_destination
|
|
5
|
+
@remote_destination ||= releases_path.join(version)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def archive_name
|
|
9
|
+
@archive_name ||= "#{site.name}--#{version}.tar.gz"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def archive_path
|
|
13
|
+
@archive_path ||= File.join(Dir.pwd, "tmp", archive_name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def site_root
|
|
17
|
+
server.site_root(site)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def current_path
|
|
21
|
+
server.current_path(site)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def caddyfile_path
|
|
25
|
+
site_root.join("Caddyfile")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def new_caddyfile_path
|
|
29
|
+
site_root.join("Caddyfile.new")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def mimas_path
|
|
33
|
+
site_root.join("mimas")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def releases_path
|
|
37
|
+
site_root.join("releases")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unix_socket(version: self.version)
|
|
41
|
+
return nil if site.static?
|
|
42
|
+
mimas_path.join("application--#{version}.sock")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ruby_version
|
|
46
|
+
Config.current.ruby_version
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def deploy_script_path
|
|
50
|
+
remote_destination.join(deploy_script)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Deployment
|
|
3
|
+
module RubyApp
|
|
4
|
+
def deploy_ruby_app
|
|
5
|
+
say "Starting deployment for #{site.domain}"
|
|
6
|
+
|
|
7
|
+
archive(output: archive_path, files: site.files)
|
|
8
|
+
|
|
9
|
+
ssh(server) do |session|
|
|
10
|
+
begin
|
|
11
|
+
session.run "test -d ~/.rubies/ruby-#{ruby_version}"
|
|
12
|
+
say "Ruby #{ruby_version} found on the server..."
|
|
13
|
+
rescue SSH::Session::NonZeroExitCode
|
|
14
|
+
raise "Ruby version not installed on server"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Create the release folder
|
|
18
|
+
session.run "mkdir -p #{releases_path}"
|
|
19
|
+
|
|
20
|
+
# Get expired releases. Mimas will only retain the last 5 releases.
|
|
21
|
+
releases = session.run "ls #{releases_path}", capture: true
|
|
22
|
+
expired_releases = releases.split("\n").sort.reverse.drop(5)
|
|
23
|
+
|
|
24
|
+
if expired_releases.any?
|
|
25
|
+
say "Deleting versions: #{expired_releases.join(", ")}"
|
|
26
|
+
session.run "cd #{releases_path} && rm -rf #{expired_releases.join(" ")}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Check `current` symlink to get currently live version
|
|
30
|
+
remote_current_path = session.run("readlink #{current_path}", capture: true, silent: true).chomp
|
|
31
|
+
remote_current_version = Pathname.new(remote_current_path).basename.to_s.chomp
|
|
32
|
+
|
|
33
|
+
say "Current live version is #{remote_current_version}" if remote_current_version.size > 0
|
|
34
|
+
|
|
35
|
+
# Make systemd path and the site's mimas folder
|
|
36
|
+
session.run "mkdir -p #{server.user_systemd_path}"
|
|
37
|
+
session.run "mkdir -p #{mimas_path}"
|
|
38
|
+
|
|
39
|
+
refreshing_caddy(session) do
|
|
40
|
+
# Upload the mimas.env and touch an `.env` file in the site's root
|
|
41
|
+
session.upload StringIO.new(mimas_env), mimas_path.join("mimas.env")
|
|
42
|
+
session.run "touch #{site_root.join(".env")}"
|
|
43
|
+
|
|
44
|
+
say "Uploading version: #{version}"
|
|
45
|
+
session.upload archive_path, "#{remote_destination}/"
|
|
46
|
+
|
|
47
|
+
say "Extracting version: #{version}"
|
|
48
|
+
session.run "cd #{remote_destination} && tar -xf #{archive_name} && rm #{archive_name}", capture: true
|
|
49
|
+
|
|
50
|
+
say "Running deploy script"
|
|
51
|
+
session.upload StringIO.new(site.deploy), deploy_script_path
|
|
52
|
+
session.run "cd #{remote_destination} && /usr/bin/env bash -l #{deploy_script}"
|
|
53
|
+
session.run "rm #{deploy_script_path}"
|
|
54
|
+
|
|
55
|
+
say "Setting current symlink"
|
|
56
|
+
session.run "ln -sfn #{remote_destination} #{current_path}"
|
|
57
|
+
say "Current symlink set"
|
|
58
|
+
|
|
59
|
+
site.services.each do |_, service|
|
|
60
|
+
say "Installing service: #{service[:name]}"
|
|
61
|
+
systemd_service_name = systemd_service_name(service)
|
|
62
|
+
system_contents = systemd(service)
|
|
63
|
+
|
|
64
|
+
session.upload StringIO.new(system_contents), server.user_systemd_path.join("#{systemd_service_name}.service")
|
|
65
|
+
session.run "systemctl --user enable --now #{systemd_service_name}", capture: true
|
|
66
|
+
say "Started #{service[:name]} service using as #{systemd_service_name}"
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
say "Health checking new service"
|
|
70
|
+
session.run "curl --max-time 1 --silent --fail --unix-socket #{unix_socket} http://localhost#{site.healthcheck_path}", capture: true
|
|
71
|
+
say "Health check succeeded"
|
|
72
|
+
rescue SSH::Session::NonZeroExitCode => e
|
|
73
|
+
say "Health check failed".red
|
|
74
|
+
say e.to_s.red.bold
|
|
75
|
+
raise e
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
rescue Exception => e
|
|
79
|
+
say "Error raised during deploy: #{e}".red.bold
|
|
80
|
+
|
|
81
|
+
# Revert symlink if there's an existing live release, otherwise delete it
|
|
82
|
+
if remote_current_path.size > 0
|
|
83
|
+
say "Reverting symlink ..."
|
|
84
|
+
session.run "ln -sfn #{remote_current_path} #{current_path}"
|
|
85
|
+
else
|
|
86
|
+
say "Deleting symlink ..."
|
|
87
|
+
session.run "rm -f #{current_path}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
say "Deleting released files ..."
|
|
91
|
+
|
|
92
|
+
site.services.each do |_, service|
|
|
93
|
+
systemd_service_name = systemd_service_name(service)
|
|
94
|
+
session.run "systemctl --user stop #{systemd_service_name}", silent: true, capture: true
|
|
95
|
+
session.run "systemctl --user disable #{systemd_service_name}", silent: true, capture: true
|
|
96
|
+
session.run "rm #{server.user_systemd_path.join("#{systemd_service_name}.service")}", silent: true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
session.run "rm -f #{unix_socket(version: version)}"
|
|
100
|
+
session.run "rm -f #{site_root.join("Caddyfile.new")}"
|
|
101
|
+
session.run "rm -rf #{remote_destination}"
|
|
102
|
+
|
|
103
|
+
exit 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Delete the old service if one exists, and the new service started up
|
|
107
|
+
if remote_current_version.size > 0
|
|
108
|
+
site.services.each do |_, service|
|
|
109
|
+
systemd_service_name = systemd_service_name(service, version: remote_current_version)
|
|
110
|
+
|
|
111
|
+
say "Stopping old service: #{systemd_service_name}"
|
|
112
|
+
session.run "systemctl --user stop #{systemd_service_name}"
|
|
113
|
+
session.run "systemctl --user disable #{systemd_service_name}"
|
|
114
|
+
session.run "rm #{server.user_systemd_path.join("#{systemd_service_name}.service")}"
|
|
115
|
+
say "#{systemd_service_name} stopped and deleted."
|
|
116
|
+
session.run "rm -f #{unix_socket(version: remote_current_version)}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
say "\n"
|
|
121
|
+
say "Deployment complete! #{version} is now live.".bold.white
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Deployment
|
|
3
|
+
module StaticSite
|
|
4
|
+
def deploy_static_site
|
|
5
|
+
say "Starting deployment for #{site.domain}"
|
|
6
|
+
|
|
7
|
+
archive(root: site.root_dir, output: archive_path, archive_root: "root", glob: site.glob)
|
|
8
|
+
|
|
9
|
+
ssh(server) do |session|
|
|
10
|
+
refreshing_caddy(session) do
|
|
11
|
+
say "Uploading version: #{version}"
|
|
12
|
+
session.upload archive_path, "#{remote_destination}/"
|
|
13
|
+
|
|
14
|
+
say "Extracting version: #{version}"
|
|
15
|
+
session.run "cd #{remote_destination} && tar -xf #{archive_name} && rm #{archive_name}"
|
|
16
|
+
|
|
17
|
+
say "Updating current symlink"
|
|
18
|
+
session.run "ln -sfn #{remote_destination} #{current_path}"
|
|
19
|
+
say "Current symlink updated"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
say "\n\n"
|
|
24
|
+
say "Deployment complete! #{version} is now live.".bold.white
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Deployment
|
|
3
|
+
include Terminal::Printer, Template, Archive, SSH
|
|
4
|
+
|
|
5
|
+
include Paths, Files
|
|
6
|
+
include StaticSite, RubyApp, Caddy
|
|
7
|
+
|
|
8
|
+
attr_reader :site, :server, :version, :commit_hash
|
|
9
|
+
|
|
10
|
+
def initialize(site:, server:)
|
|
11
|
+
@site = site
|
|
12
|
+
@server = server
|
|
13
|
+
@version = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
14
|
+
@commit_hash = `git rev-parse HEAD`
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def deploy
|
|
18
|
+
if site.static?
|
|
19
|
+
deploy_static_site
|
|
20
|
+
else
|
|
21
|
+
deploy_ruby_app
|
|
22
|
+
end
|
|
23
|
+
ensure
|
|
24
|
+
File.delete(archive_path)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|