bard 0.69.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bard/cli.rb +42 -25
- data/lib/bard/command.rb +23 -17
- data/lib/bard/config.rb +1 -76
- data/lib/bard/github.rb +4 -0
- data/lib/bard/provision/app.rb +10 -0
- data/lib/bard/provision/data.rb +27 -0
- data/lib/bard/provision/http.rb +28 -0
- data/lib/bard/provision/master_key.rb +16 -0
- data/lib/bard/provision/mysql.rb +23 -0
- data/lib/bard/provision/passenger.rb +39 -0
- data/lib/bard/provision/repo.rb +49 -0
- data/lib/bard/provision/rvm.rb +21 -0
- data/lib/bard/provision/ssh.rb +44 -0
- data/lib/bard/provision/user.rb +41 -0
- data/lib/bard/provision.rb +40 -0
- data/lib/bard/server.rb +94 -0
- data/lib/bard/version.rb +1 -1
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecbadd22fc4903fa9e779a515d0a7c9448392ac4fe2396ae2272bab7daeb4018
|
4
|
+
data.tar.gz: '068143adee00988e0506bb8e834ad5a15ebf65caf9e824d7a7f8e3923b9dbf18'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b12423bf86e5978f48a4864c0572585aa583af33d1b1d6cdcdfb3e466b511d00e7e887908720346b6b13e7f09baa4948e511d717dbea34f72883a27385f58d6b
|
7
|
+
data.tar.gz: 73451d824a62f1f8953f83d89055e87355018aebbf0b92d05741e17391db204be01d594afcedd113bbb347f065a3e42ed3dffc387ebcb94118670e9ce08f0316
|
data/lib/bard/cli.rb
CHANGED
@@ -8,6 +8,7 @@ require "bard/github"
|
|
8
8
|
require "bard/ping"
|
9
9
|
require "bard/config"
|
10
10
|
require "bard/command"
|
11
|
+
require "bard/provision"
|
11
12
|
require "term/ansicolor"
|
12
13
|
require "open3"
|
13
14
|
require "uri"
|
@@ -50,6 +51,15 @@ module Bard
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
54
|
+
desc "master_key --from=production --to=local", "copy master key from from to to"
|
55
|
+
option :from, default: "production"
|
56
|
+
option :to, default: "local"
|
57
|
+
def master_key
|
58
|
+
from = config[options[:from]]
|
59
|
+
to = config[options[:to]]
|
60
|
+
from.copy_file "config/master.key", to:
|
61
|
+
end
|
62
|
+
|
53
63
|
desc "stage [branch=HEAD]", "pushes current branch, and stages it"
|
54
64
|
def stage branch=Git.current_branch
|
55
65
|
unless config.servers.key?(:production)
|
@@ -159,18 +169,6 @@ module Bard
|
|
159
169
|
exec "xdg-open #{config[server].ping.first}"
|
160
170
|
end
|
161
171
|
|
162
|
-
desc "hurt <command>", "reruns a command until it fails"
|
163
|
-
def hurt *args
|
164
|
-
1.upto(Float::INFINITY) do |count|
|
165
|
-
puts "Running attempt #{count}"
|
166
|
-
system *args
|
167
|
-
unless $?.success?
|
168
|
-
puts "Ran #{count-1} times before failing"
|
169
|
-
break
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
172
|
option :home, type: :boolean
|
175
173
|
desc "ssh [to=production]", "logs into the specified server via SSH"
|
176
174
|
def ssh to=:production
|
@@ -185,11 +183,23 @@ module Bard
|
|
185
183
|
system "cp -R #{github_files_path} ./"
|
186
184
|
end
|
187
185
|
|
186
|
+
desc "provision [ssh_url]", "takes an ssh url to a raw ubuntu 22.04 install, and readies it in the shape of :production"
|
187
|
+
def provision ssh_url
|
188
|
+
Provision.call(config, ssh_url.dup) # dup unfreezes the string for later mutation
|
189
|
+
end
|
190
|
+
|
188
191
|
desc "setup", "installs app in nginx"
|
189
192
|
def setup
|
190
193
|
path = "/etc/nginx/sites-available/#{project_name}"
|
191
194
|
dest_path = path.sub("sites-available", "sites-enabled")
|
192
|
-
server_name = "
|
195
|
+
server_name = case ENV["RAILS_ENV"]
|
196
|
+
when "production"
|
197
|
+
(config[:production].ping.map do |str|
|
198
|
+
"*.#{URI.parse(str).host}"
|
199
|
+
end + ["_"]).join(" ")
|
200
|
+
when "staging" then "#{project_name}.botandrose.com"
|
201
|
+
else "#{project_name}.localhost"
|
202
|
+
end
|
193
203
|
|
194
204
|
system "sudo tee #{path} >/dev/null <<-EOF
|
195
205
|
server {
|
@@ -221,20 +231,27 @@ EOF"
|
|
221
231
|
exit 1 if down_urls.any?
|
222
232
|
end
|
223
233
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
234
|
+
# HACK: we don't use Thor::Base#run, so its okay to stomp on it here
|
235
|
+
original_verbose, $VERBOSE = $VERBOSE, nil
|
236
|
+
Thor::THOR_RESERVED_WORDS -= ["run"]
|
237
|
+
$VERBOSE = original_verbose
|
238
|
+
|
239
|
+
desc "run <command>", "run the given command on production"
|
240
|
+
def run *args
|
241
|
+
server = config[:production]
|
242
|
+
server.run! *args, verbose: true
|
229
243
|
end
|
230
244
|
|
231
|
-
desc "
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
245
|
+
desc "hurt <command>", "reruns a command until it fails"
|
246
|
+
def hurt *args
|
247
|
+
1.upto(Float::INFINITY) do |count|
|
248
|
+
puts "Running attempt #{count}"
|
249
|
+
system *args
|
250
|
+
unless $?.success?
|
251
|
+
puts "Ran #{count-1} times before failing"
|
252
|
+
break
|
253
|
+
end
|
254
|
+
end
|
238
255
|
end
|
239
256
|
|
240
257
|
desc "vim [branch=master]", "open all files that have changed since master"
|
data/lib/bard/command.rb
CHANGED
@@ -1,31 +1,36 @@
|
|
1
1
|
module Bard
|
2
2
|
class Command < Struct.new(:command, :on, :home)
|
3
|
-
def self.run! command, on: :local, home: false, verbose: false
|
4
|
-
new(command, on, home).run! verbose:
|
3
|
+
def self.run! command, on: :local, home: false, verbose: false, quiet: false
|
4
|
+
new(command, on, home).run! verbose:, quiet:
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.run command, on: :local, home: false, verbose: false, quiet: false
|
8
|
+
new(command, on, home).run verbose:, quiet:
|
5
9
|
end
|
6
10
|
|
7
11
|
def self.exec! command, on: :local, home: false
|
8
12
|
new(command, on, home).exec!
|
9
13
|
end
|
10
14
|
|
11
|
-
def run! verbose: false
|
12
|
-
|
15
|
+
def run! verbose: false, quiet: false
|
16
|
+
if !run(verbose:, quiet:)
|
17
|
+
raise "Running command failed: #{full_command}"
|
18
|
+
# puts red("!!! ") + "Running command failed: #{yellow(command)}"
|
19
|
+
# exit 1
|
20
|
+
end
|
21
|
+
end
|
13
22
|
|
23
|
+
def run verbose: false, quiet: false
|
14
24
|
if verbose
|
15
|
-
|
25
|
+
system full_command(quiet: quiet)
|
16
26
|
else
|
17
27
|
stdout, stderr, status = Open3.capture3(full_command)
|
18
28
|
failed = status.to_i.nonzero?
|
19
|
-
if failed
|
29
|
+
if failed && !quiet
|
20
30
|
$stdout.puts stdout
|
21
31
|
$stderr.puts stderr
|
22
32
|
end
|
23
|
-
|
24
|
-
|
25
|
-
if failed
|
26
|
-
raise "Running command failed: #{full_command}"
|
27
|
-
# puts red("!!! ") + "Running command failed: #{yellow(command)}"
|
28
|
-
# exit 1
|
33
|
+
!failed && stdout
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
@@ -35,16 +40,16 @@ module Bard
|
|
35
40
|
|
36
41
|
private
|
37
42
|
|
38
|
-
def full_command
|
43
|
+
def full_command quiet: false
|
39
44
|
if on.to_sym == :local
|
40
45
|
command
|
41
46
|
else
|
42
|
-
remote_command
|
47
|
+
remote_command quiet: false
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
46
|
-
def remote_command
|
47
|
-
uri =
|
51
|
+
def remote_command quiet: false
|
52
|
+
uri = on.ssh_uri
|
48
53
|
ssh_key = on.ssh_key ? "-i #{on.ssh_key} " : ""
|
49
54
|
cmd = command
|
50
55
|
if on.env
|
@@ -55,9 +60,10 @@ module Bard
|
|
55
60
|
end
|
56
61
|
cmd = "ssh -tt #{ssh_key}#{"-p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} '#{cmd}'"
|
57
62
|
if on.gateway
|
58
|
-
uri =
|
63
|
+
uri = on.ssh_uri(:gateway)
|
59
64
|
cmd = "ssh -tt #{" -p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} \"#{cmd}\""
|
60
65
|
end
|
66
|
+
cmd += " 2>&1" if quiet
|
61
67
|
cmd
|
62
68
|
end
|
63
69
|
end
|
data/lib/bard/config.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
require "
|
2
|
-
require "bard/command"
|
3
|
-
require "bard/copy"
|
1
|
+
require "bard/server"
|
4
2
|
|
5
3
|
module Bard
|
6
4
|
class Config
|
@@ -84,78 +82,5 @@ module Bard
|
|
84
82
|
raise ArgumentError
|
85
83
|
end
|
86
84
|
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
class Server < Struct.new(:project_name, :key, :ssh, :path, :ping, :gateway, :ssh_key, :env)
|
91
|
-
def self.setting *fields
|
92
|
-
fields.each do |field|
|
93
|
-
define_method field do |*args|
|
94
|
-
if args.length == 1
|
95
|
-
send :"#{field}=", args.first
|
96
|
-
elsif args.length == 0
|
97
|
-
super()
|
98
|
-
else
|
99
|
-
raise ArgumentError
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
setting :ssh, :path, :ping, :gateway, :ssh_key, :env
|
106
|
-
|
107
|
-
def ping(*args)
|
108
|
-
if args.length == 0
|
109
|
-
(super() || [nil]).map(&method(:normalize_ping))
|
110
|
-
else
|
111
|
-
self.ping = args
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
private def normalize_ping value
|
116
|
-
return value if value == false
|
117
|
-
uri = URI.parse("ssh://#{ssh}")
|
118
|
-
normalized = "https://#{uri.host}" # default if none specified
|
119
|
-
if value =~ %r{^/}
|
120
|
-
normalized += value
|
121
|
-
elsif value.to_s.length > 0
|
122
|
-
normalized = value
|
123
|
-
end
|
124
|
-
if normalized !~ /^http/
|
125
|
-
normalized = "https://#{normalized}"
|
126
|
-
end
|
127
|
-
normalized
|
128
|
-
end
|
129
|
-
|
130
|
-
def path(*args)
|
131
|
-
if args.length == 1
|
132
|
-
self.path = args.first
|
133
|
-
elsif args.length == 0
|
134
|
-
super() || project_name
|
135
|
-
else
|
136
|
-
raise ArgumentError
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def to_sym
|
141
|
-
key
|
142
|
-
end
|
143
|
-
|
144
|
-
def run! command, home: false, verbose: false
|
145
|
-
Bard::Command.run! command, on: self, home:, verbose:
|
146
|
-
end
|
147
|
-
|
148
|
-
def exec! command, home: false
|
149
|
-
Bard::Command.exec! command, on: self, home:
|
150
|
-
end
|
151
|
-
|
152
|
-
def copy_file path, to:, verbose: false
|
153
|
-
Bard::Copy.file path, from: self, to:, verbose:
|
154
|
-
end
|
155
|
-
|
156
|
-
def copy_dir
|
157
|
-
Bard::Copy.dir path, from: self, to:, verbose:
|
158
|
-
end
|
159
|
-
end
|
160
85
|
end
|
161
86
|
end
|
data/lib/bard/github.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
# copy data from production
|
2
|
+
|
3
|
+
class Bard::Provision::Data < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "Data:"
|
6
|
+
|
7
|
+
# print " Dumping #{server.key} database to file"
|
8
|
+
# server.run! "bin/rake db:dump"
|
9
|
+
|
10
|
+
# print " Transfering file from #{server.key},"
|
11
|
+
# server.copy_file "db/data.sql.gz", to: provision_server, verbose: false
|
12
|
+
|
13
|
+
# print " Loading file into database,"
|
14
|
+
# provision_server.run! "bin/rake db:load"
|
15
|
+
|
16
|
+
config.data.each do |path|
|
17
|
+
print " Synchronizing files in #{path},"
|
18
|
+
server.copy_dir path, to: provision_server, verbose: false
|
19
|
+
end
|
20
|
+
|
21
|
+
puts " ✓"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# test for existence
|
2
|
+
|
3
|
+
class Bard::Provision::HTTP < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "HTTP:"
|
6
|
+
target_host = URI.parse(server.ping.first).host
|
7
|
+
if system "curl -s --resolve #{target_host}:80:#{provision_server.ssh_uri.host} http://#{target_host} -I | grep -i \"x-powered-by: phusion passenger\""
|
8
|
+
puts " ✓"
|
9
|
+
else
|
10
|
+
puts " !!! not serving a rails app from #{provision_server.ssh_uri.host}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def ssh_available? ssh_uri, port: ssh_uri.port
|
17
|
+
system "nc -zv #{ssh_uri.host} #{port} 2>/dev/null"
|
18
|
+
end
|
19
|
+
|
20
|
+
def ssh_known_host? ssh_uri
|
21
|
+
system "grep -q \"$(ssh-keyscan -t ed25519 -p#{ssh_uri.port || 22} #{ssh_uri.host} 2>/dev/null | cut -d ' ' -f 2-3)\" ~/.ssh/known_hosts"
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_ssh_known_host! ssh_uri
|
25
|
+
system "ssh-keyscan -p#{ssh_uri.port || 22} -H #{ssh_uri.host} >> ~/.ssh/known_hosts"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# copy master key if missing
|
2
|
+
|
3
|
+
class Bard::Provision::MasterKey < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "Master Key:"
|
6
|
+
if File.exist?("config/master.key")
|
7
|
+
if !provision_server.run "[ -f config/master.key ]", quiet: true
|
8
|
+
print " Uploading config/master.key,"
|
9
|
+
Bard::Copy.new("config/master.key").scp_using_local(:to, provision_server)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
puts " ✓"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# install mysql
|
2
|
+
|
3
|
+
class Bard::Provision::MySQL < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "MySQL:"
|
6
|
+
if !mysql_responding?
|
7
|
+
print " Installing,"
|
8
|
+
provision_server.run! [
|
9
|
+
"sudo apt-get install -y mysql-server",
|
10
|
+
%(sudo mysql -uroot -e "ALTER USER \\"'\\"root\\"'\\"@\\"'\\"localhost\\"'\\" IDENTIFIED WITH mysql_native_password BY \\"'\\"\\"'\\", \\"'\\"root\\"'\\"@\\"'\\"localhost\\"'\\" PASSWORD EXPIRE NEVER; FLUSH PRIVILEGES;"),
|
11
|
+
].join("; "), home: true
|
12
|
+
end
|
13
|
+
|
14
|
+
puts " ✓"
|
15
|
+
end
|
16
|
+
|
17
|
+
def mysql_responding?
|
18
|
+
provision_server.run "sudo service mysql status | cat", quiet: true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# install nginx & passenger
|
2
|
+
|
3
|
+
class Bard::Provision::Passenger < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "Passenger:"
|
6
|
+
if !http_responding?
|
7
|
+
print " Installing nginx & Passenger,"
|
8
|
+
provision_server.run! [
|
9
|
+
%(echo "\\$nrconf{restart} = \\"a\\";" | sudo tee /etc/needrestart/conf.d/90-autorestart.conf),
|
10
|
+
%(grep -qxF "RAILS_ENV=production" /etc/environment || echo "RAILS_ENV=production" | sudo tee -a /etc/environment),
|
11
|
+
%(grep -qxF "EDITOR=vim" /etc/environment || echo "EDITOR=vim" | sudo tee -a /etc/environment),
|
12
|
+
"sudo apt-get update -y",
|
13
|
+
"sudo apt-get upgrade -y",
|
14
|
+
"sudo apt-get install -y vim dirmngr gnupg apt-transport-https ca-certificates curl",
|
15
|
+
"curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/phusion.gpg >/dev/null",
|
16
|
+
%(echo "deb https://oss-binaries.phusionpassenger.com/apt/passenger jammy main" | sudo tee /etc/apt/sources.list.d/passenger.list),
|
17
|
+
"sudo apt-get update -y",
|
18
|
+
"sudo apt-get install -y nginx libnginx-mod-http-passenger",
|
19
|
+
].join("; "), home: true
|
20
|
+
end
|
21
|
+
|
22
|
+
if !app_configured?
|
23
|
+
print " Creating nginx config for app,"
|
24
|
+
provision_server.run! "bard setup"
|
25
|
+
end
|
26
|
+
|
27
|
+
puts " ✓"
|
28
|
+
end
|
29
|
+
|
30
|
+
def http_responding?
|
31
|
+
system "nc -zv #{provision_server.ssh_uri.host} 80 2>/dev/null"
|
32
|
+
end
|
33
|
+
|
34
|
+
def app_configured?
|
35
|
+
provision_server.run "[ -f /etc/nginx/sites-enabled/#{server.project_name} ]", quiet: true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# generate and install ssh public key into deploy keys
|
2
|
+
# add repo to known hosts
|
3
|
+
# clone repo
|
4
|
+
|
5
|
+
class Bard::Provision::Repo < Bard::Provision
|
6
|
+
def call
|
7
|
+
print "Repo:"
|
8
|
+
if !already_cloned?
|
9
|
+
if !can_clone_project?
|
10
|
+
if !ssh_keypair?
|
11
|
+
print " Generating keypair in ~/.ssh,"
|
12
|
+
provision_server.run! "ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N \"\"", home: true
|
13
|
+
end
|
14
|
+
print " Add public key to GitHub repo deploy keys,"
|
15
|
+
title = "#{server.ssh_uri.user}@#{server.ssh_uri.host}"
|
16
|
+
key = provision_server.run "cat ~/.ssh/id_rsa.pub", home: true
|
17
|
+
Bard::Github.new(server.project_name).add_deploy_key title:, key:
|
18
|
+
end
|
19
|
+
print " Cloning repo,"
|
20
|
+
provision_server.run! "git clone git@github.com:botandrosedesign/#{project_name}", home: true
|
21
|
+
end
|
22
|
+
|
23
|
+
puts " ✓"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def ssh_keypair?
|
29
|
+
provision_server.run "[ -f ~/.ssh/id_rsa.pub ]", home: true, quiet: true
|
30
|
+
end
|
31
|
+
|
32
|
+
def already_cloned?
|
33
|
+
provision_server.run "[ -d ~/#{project_name}/.git ]", home: true, quiet: true
|
34
|
+
end
|
35
|
+
|
36
|
+
def can_clone_project?
|
37
|
+
github_url = "git@github.com:botandrosedesign/#{project_name}"
|
38
|
+
provision_server.run [
|
39
|
+
"needle=$(ssh-keyscan -t ed25519 github.com 2>/dev/null | cut -d \" \" -f 2-3)",
|
40
|
+
"grep -q \"$needle\" ~/.ssh/known_hosts || ssh-keyscan -H github.com >> ~/.ssh/known_hosts",
|
41
|
+
"git ls-remote #{github_url}",
|
42
|
+
].join("; "), home: true
|
43
|
+
end
|
44
|
+
|
45
|
+
def project_name
|
46
|
+
server.project_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# install rvm if missing
|
2
|
+
|
3
|
+
class Bard::Provision::RVM < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "RVM:"
|
6
|
+
if !provision_server.run "[ -d ~/.rvm ]", quiet: true
|
7
|
+
print " Installing RVM,"
|
8
|
+
provision_server.run! [
|
9
|
+
%(sed -i "1i[[ -s \\"$HOME/.rvm/scripts/rvm\\" ]] && source \\"$HOME/.rvm/scripts/rvm\\" # Load RVM into a shell session *as a function*" ~/.bashrc),
|
10
|
+
"gpg --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB",
|
11
|
+
"curl -sSL https://get.rvm.io | bash -s stable",
|
12
|
+
].join("; ")
|
13
|
+
print " Installing Ruby #{File.read(".ruby-version")},"
|
14
|
+
provision_server.run! "rvm install ."
|
15
|
+
end
|
16
|
+
|
17
|
+
puts " ✓"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# move ssh port
|
2
|
+
# add to known hosts
|
3
|
+
|
4
|
+
class Bard::Provision::SSH < Bard::Provision
|
5
|
+
def call
|
6
|
+
print "SSH:"
|
7
|
+
|
8
|
+
if !ssh_available?(provision_server.ssh_uri, port: server.ssh_uri.port)
|
9
|
+
if !ssh_available?(provision_server.ssh_uri)
|
10
|
+
raise "can't find SSH on port #{server.ssh_uri.port} or #{provision_server.ssh_uri.port}"
|
11
|
+
end
|
12
|
+
if !ssh_known_host?(provision_server.ssh_uri)
|
13
|
+
print " Adding known host,"
|
14
|
+
add_ssh_known_host!(provision_server.ssh_uri)
|
15
|
+
end
|
16
|
+
print " Reconfiguring port to #{server.ssh_uri.port},"
|
17
|
+
provision.server.run! %(echo "Port #{server.ssh_uri.port}" | sudo tee /etc/ssh/sshd_config.d/port_22022.conf; sudo service ssh restart), home: true
|
18
|
+
end
|
19
|
+
|
20
|
+
if !ssh_known_host?(provision_server.ssh_uri)
|
21
|
+
print " Adding known host,"
|
22
|
+
add_ssh_known_host!(provision_server.ssh_uri)
|
23
|
+
end
|
24
|
+
|
25
|
+
# provision with new port from now on
|
26
|
+
ssh_url.gsub!(/:\d+$/, "")
|
27
|
+
ssh_url << ":#{server.ssh_uri.port}"
|
28
|
+
puts " ✓"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def ssh_available? ssh_uri, port: ssh_uri.port
|
34
|
+
system "nc -zv #{ssh_uri.host} #{port} 2>/dev/null"
|
35
|
+
end
|
36
|
+
|
37
|
+
def ssh_known_host? ssh_uri
|
38
|
+
system "grep -q \"$(ssh-keyscan -t ed25519 -p#{ssh_uri.port || 22} #{ssh_uri.host} 2>/dev/null | cut -d ' ' -f 2-3)\" ~/.ssh/known_hosts"
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_ssh_known_host! ssh_uri
|
42
|
+
system "ssh-keyscan -p#{ssh_uri.port || 22} -H #{ssh_uri.host} >> ~/.ssh/known_hosts"
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# rename user
|
2
|
+
|
3
|
+
class Bard::Provision::User < Bard::Provision
|
4
|
+
def call
|
5
|
+
print "User:"
|
6
|
+
|
7
|
+
if !ssh_with_user?(provision_server.ssh_uri, user: new_user)
|
8
|
+
if !ssh_with_user?(provision_server.ssh_uri)
|
9
|
+
raise "can't ssh in with user #{new_user} or #{old_user}"
|
10
|
+
end
|
11
|
+
print " Adding user #{new_user},"
|
12
|
+
provision_server.run! [
|
13
|
+
"sudo useradd -m -s /bin/bash #{new_user}",
|
14
|
+
"sudo usermod -aG sudo #{new_user}",
|
15
|
+
"echo \"#{new_user} ALL=(ALL) NOPASSWD:ALL\" | sudo tee -a /etc/sudoers",
|
16
|
+
"sudo mkdir -p ~#{new_user}/.ssh",
|
17
|
+
"sudo cp ~/.ssh/authorized_keys ~#{new_user}/.ssh/authorized_keys",
|
18
|
+
"sudo chown -R #{new_user}:#{new_user} ~#{new_user}/.ssh",
|
19
|
+
].join("; "), home: true
|
20
|
+
end
|
21
|
+
|
22
|
+
# provision with new user from now on
|
23
|
+
ssh_url.gsub!("#{old_user}@", "#{new_user}@")
|
24
|
+
puts " ✓"
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def new_user
|
30
|
+
server.ssh_uri.user
|
31
|
+
end
|
32
|
+
|
33
|
+
def old_user
|
34
|
+
provision_server.ssh_uri.user
|
35
|
+
end
|
36
|
+
|
37
|
+
def ssh_with_user? ssh_uri, user: ssh_uri.user
|
38
|
+
system "ssh -o ConnectTimeout=2 -p#{ssh_uri.port || 22} #{user}@#{ssh_uri.host} exit >/dev/null 2>&1"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Bard
|
2
|
+
class Provision < Struct.new(:config, :ssh_url)
|
3
|
+
def self.call(...) = new(...).call
|
4
|
+
|
5
|
+
def call
|
6
|
+
SSH.call(*values)
|
7
|
+
User.call(*values)
|
8
|
+
MySQL.call(*values)
|
9
|
+
Repo.call(*values)
|
10
|
+
MasterKey.call(*values)
|
11
|
+
RVM.call(*values)
|
12
|
+
App.call(*values)
|
13
|
+
Passenger.call(*values)
|
14
|
+
Data.call(*values)
|
15
|
+
HTTP.call(*values)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def server
|
21
|
+
config[:production]
|
22
|
+
end
|
23
|
+
|
24
|
+
def provision_server
|
25
|
+
server.with(ssh: ssh_url)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require "bard/provision/ssh"
|
31
|
+
require "bard/provision/user"
|
32
|
+
require "bard/provision/mysql"
|
33
|
+
require "bard/provision/passenger"
|
34
|
+
require "bard/provision/repo"
|
35
|
+
require "bard/provision/master_key"
|
36
|
+
require "bard/provision/rvm"
|
37
|
+
require "bard/provision/app"
|
38
|
+
require "bard/provision/data"
|
39
|
+
require "bard/provision/http"
|
40
|
+
|
data/lib/bard/server.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "bard/command"
|
3
|
+
require "bard/copy"
|
4
|
+
|
5
|
+
module Bard
|
6
|
+
class Server < Struct.new(:project_name, :key, :ssh, :path, :ping, :gateway, :ssh_key, :env, :provision)
|
7
|
+
def self.setting *fields
|
8
|
+
fields.each do |field|
|
9
|
+
define_method field do |*args|
|
10
|
+
if args.length == 1
|
11
|
+
send :"#{field}=", args.first
|
12
|
+
elsif args.length == 0
|
13
|
+
super()
|
14
|
+
else
|
15
|
+
raise ArgumentError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
setting :ssh, :path, :ping, :gateway, :ssh_key, :env, :provision
|
22
|
+
|
23
|
+
def ping(*args)
|
24
|
+
if args.length == 0
|
25
|
+
(super() || [nil]).map(&method(:normalize_ping))
|
26
|
+
else
|
27
|
+
self.ping = args
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private def normalize_ping value
|
32
|
+
return value if value == false
|
33
|
+
uri = URI.parse("ssh://#{ssh}")
|
34
|
+
normalized = "https://#{uri.host}" # default if none specified
|
35
|
+
if value =~ %r{^/}
|
36
|
+
normalized += value
|
37
|
+
elsif value.to_s.length > 0
|
38
|
+
normalized = value
|
39
|
+
end
|
40
|
+
if normalized !~ /^http/
|
41
|
+
normalized = "https://#{normalized}"
|
42
|
+
end
|
43
|
+
normalized
|
44
|
+
end
|
45
|
+
|
46
|
+
def path(*args)
|
47
|
+
if args.length == 1
|
48
|
+
self.path = args.first
|
49
|
+
elsif args.length == 0
|
50
|
+
super() || project_name
|
51
|
+
else
|
52
|
+
raise ArgumentError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ssh_uri which=:ssh
|
57
|
+
value = send(which)
|
58
|
+
URI.parse("ssh://#{value}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def with(attrs)
|
62
|
+
dup.tap do |s|
|
63
|
+
attrs.each do |key, value|
|
64
|
+
s.send key, value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_sym
|
70
|
+
key
|
71
|
+
end
|
72
|
+
|
73
|
+
def run! command, home: false, verbose: false, quiet: false
|
74
|
+
Bard::Command.run! command, on: self, home:, verbose:, quiet:
|
75
|
+
end
|
76
|
+
|
77
|
+
def run command, home: false, verbose: false, quiet: false
|
78
|
+
Bard::Command.run command, on: self, home:, verbose:, quiet:
|
79
|
+
end
|
80
|
+
|
81
|
+
def exec! command, home: false
|
82
|
+
Bard::Command.exec! command, on: self, home:
|
83
|
+
end
|
84
|
+
|
85
|
+
def copy_file path, to:, verbose: false
|
86
|
+
Bard::Copy.file path, from: self, to:, verbose:
|
87
|
+
end
|
88
|
+
|
89
|
+
def copy_dir path, to:, verbose: false
|
90
|
+
Bard::Copy.dir path, from: self, to:, verbose:
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/bard/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micah Geisel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -144,6 +144,18 @@ files:
|
|
144
144
|
- lib/bard/git.rb
|
145
145
|
- lib/bard/github.rb
|
146
146
|
- lib/bard/ping.rb
|
147
|
+
- lib/bard/provision.rb
|
148
|
+
- lib/bard/provision/app.rb
|
149
|
+
- lib/bard/provision/data.rb
|
150
|
+
- lib/bard/provision/http.rb
|
151
|
+
- lib/bard/provision/master_key.rb
|
152
|
+
- lib/bard/provision/mysql.rb
|
153
|
+
- lib/bard/provision/passenger.rb
|
154
|
+
- lib/bard/provision/repo.rb
|
155
|
+
- lib/bard/provision/rvm.rb
|
156
|
+
- lib/bard/provision/ssh.rb
|
157
|
+
- lib/bard/provision/user.rb
|
158
|
+
- lib/bard/server.rb
|
147
159
|
- lib/bard/version.rb
|
148
160
|
- spec/bard/ci/github_actions_spec.rb
|
149
161
|
- spec/bard_spec.rb
|