bard 0.69.1 → 1.0.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 +4 -4
- data/lib/bard/cli.rb +42 -25
- data/lib/bard/command.rb +23 -17
- data/lib/bard/config.rb +4 -77
- 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
|
@@ -44,7 +42,9 @@ module Bard
|
|
44
42
|
if path && File.exist?(path)
|
45
43
|
source = File.read(File.expand_path(path))
|
46
44
|
end
|
47
|
-
|
45
|
+
if source
|
46
|
+
instance_eval source
|
47
|
+
end
|
48
48
|
end
|
49
49
|
|
50
50
|
attr_reader :project_name, :servers
|
@@ -82,78 +82,5 @@ module Bard
|
|
82
82
|
raise ArgumentError
|
83
83
|
end
|
84
84
|
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
class Server < Struct.new(:project_name, :key, :ssh, :path, :ping, :gateway, :ssh_key, :env)
|
89
|
-
def self.setting *fields
|
90
|
-
fields.each do |field|
|
91
|
-
define_method field do |*args|
|
92
|
-
if args.length == 1
|
93
|
-
send :"#{field}=", args.first
|
94
|
-
elsif args.length == 0
|
95
|
-
super()
|
96
|
-
else
|
97
|
-
raise ArgumentError
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
setting :ssh, :path, :ping, :gateway, :ssh_key, :env
|
104
|
-
|
105
|
-
def ping(*args)
|
106
|
-
if args.length == 0
|
107
|
-
(super() || [nil]).map(&method(:normalize_ping))
|
108
|
-
else
|
109
|
-
self.ping = args
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
private def normalize_ping value
|
114
|
-
return value if value == false
|
115
|
-
uri = URI.parse("ssh://#{ssh}")
|
116
|
-
normalized = "https://#{uri.host}" # default if none specified
|
117
|
-
if value =~ %r{^/}
|
118
|
-
normalized += value
|
119
|
-
elsif value.to_s.length > 0
|
120
|
-
normalized = value
|
121
|
-
end
|
122
|
-
if normalized !~ /^http/
|
123
|
-
normalized = "https://#{normalized}"
|
124
|
-
end
|
125
|
-
normalized
|
126
|
-
end
|
127
|
-
|
128
|
-
def path(*args)
|
129
|
-
if args.length == 1
|
130
|
-
self.path = args.first
|
131
|
-
elsif args.length == 0
|
132
|
-
super() || project_name
|
133
|
-
else
|
134
|
-
raise ArgumentError
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def to_sym
|
139
|
-
key
|
140
|
-
end
|
141
|
-
|
142
|
-
def run! command, home: false, verbose: false
|
143
|
-
Bard::Command.run! command, on: self, home:, verbose:
|
144
|
-
end
|
145
|
-
|
146
|
-
def exec! command, home: false
|
147
|
-
Bard::Command.exec! command, on: self, home:
|
148
|
-
end
|
149
|
-
|
150
|
-
def copy_file path, to:, verbose: false
|
151
|
-
Bard::Copy.file path, from: self, to:, verbose:
|
152
|
-
end
|
153
|
-
|
154
|
-
def copy_dir
|
155
|
-
Bard::Copy.dir path, from: self, to:, verbose:
|
156
|
-
end
|
157
|
-
end
|
158
85
|
end
|
159
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
|