opswalrus 1.0.5 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 727948ba554b3da5510261c8ddc8983a8113b7df7c30fb769a2291bc0af77195
4
- data.tar.gz: 70a553c15a613d41300775b0f063cb2c34fcc9998d2f8d26d310a1c88fec3a17
3
+ metadata.gz: b0c1c00f88ff00a4f7eb735a634ea35e1e8818b44fffdccea7a2347bac78e199
4
+ data.tar.gz: fe54254076c753cfeaf26906fa0f7475bd11807c0ac70d4067da03653b0f774c
5
5
  SHA512:
6
- metadata.gz: d1d4198f362160b5d0f1baba4fe0999f382b3cf1250cd8a608859901e55bb8f307673761c6699f60c81a70814d8eec3a67b66de241e94f95856dbdf69f703dc2
7
- data.tar.gz: 3fd8074d05b9c50976ef81a700fe95217046e7e7ced70799e6f65eac46b5d61f14f1ee267191e64264663a4d11bb3b61d202d4eab32c80e08e92b0d9769ca7ff
6
+ metadata.gz: 28244a51a465941731663e9db3901049d36d1fa385a16b4c5ed9254c31fa62ca44012c2d776ef5b6d1d06906c012802b1352e166d72d28b3ec0aa6f9e27073f5
7
+ data.tar.gz: 39ccb70ca9dc5b2ecf67b1ab29adb7ccc79cb43ad57d256be0deb68b49909bc4c5cd4ddc2871e68aeeb374ed3d0202c365094536e7933f61c3ecf700a2178076
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opswalrus (1.0.4)
5
- bcrypt_pbkdf
6
- citrus
7
- ed25519
8
- git
9
- gli
10
- rubyzip
11
- sshkit
4
+ opswalrus (1.0.7)
5
+ bcrypt_pbkdf (~> 1.1)
6
+ citrus (~> 3.0)
7
+ ed25519 (~> 1.3)
8
+ git (~> 1.18)
9
+ gli (~> 2.21)
10
+ rubyzip (~> 2.3)
11
+ sshkit (~> 1.21)
12
12
 
13
13
  GEM
14
14
  remote: https://rubygems.org/
data/build.ops ADDED
@@ -0,0 +1,40 @@
1
+ params:
2
+ version: string
3
+
4
+ imports:
5
+ core: "https://github.com/opswalrus/core.git"
6
+ ...
7
+
8
+ version = params.version
9
+
10
+ exit 1, "version parameter must be specified" unless version
11
+
12
+ template = <<TEMPLATE
13
+ module OpsWalrus
14
+ VERSION = "{{ version }}"
15
+ end
16
+ TEMPLATE
17
+
18
+ puts "Write version.rb for version #{version}"
19
+ core.template.write(path: "./lib/opswalrus/version.rb", template: template, variables: {version: version})
20
+
21
+ sh("Build gem") { 'gem build opswalrus.gemspec' }
22
+ bw_status_output = sh("Check whether Bitwarden is locked or not") { 'bw status' }
23
+ # the status command currently exhibits an error in which it emits 'mac failed.' some number of times, so we need to filter that out
24
+ # see:
25
+ # - https://community.bitwarden.com/t/what-does-mac-failed-mean-exactly/29208
26
+ # - https://github.com/bitwarden/cli/issues/88
27
+ # - https://github.com/vwxyzjn/portwarden/issues/22
28
+ # ❯ bw status
29
+ # mac failed.
30
+ # {"serverUrl":"...","lastSync":"2023-08-17T19:14:09.384Z","userEmail":"...","userId":"...","status":"locked"}
31
+ bw_status_output = bw_status_output.gsub('mac failed.', '').strip
32
+ bw_status_json = bw_status_output.parse_json
33
+
34
+ if bw_status_json['status'] != 'unlocked'
35
+ exit 0, "Bitwarden is not unlocked. Please unlock bitwarden with: bw unlock"
36
+ end
37
+
38
+ totp = sh("Get Rubygems OTP") { 'bw get totp Rubygems' }
39
+ sh("Push gem", input: {"You have enabled multi-factor authentication. Please enter OTP code." => "#{totp}\n"}) { 'gem push opswalrus-{{ version }}.gem' }
40
+ sh("Build docker image") { 'docker build -t opswalrus/ops:{{ version }} .' }
data/lib/opswalrus/app.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "citrus"
2
- require "git"
3
2
  require "io/console"
4
3
  require "json"
5
4
  require "random/formatter"
@@ -8,27 +7,14 @@ require "socket"
8
7
  require "stringio"
9
8
  require "yaml"
10
9
  require "pathname"
10
+ require_relative "patches"
11
+ require_relative "git"
11
12
  require_relative "host"
12
13
  require_relative "hosts_file"
13
14
  require_relative "operation_runner"
14
15
  require_relative "bundler"
15
16
  require_relative "package_file"
16
17
 
17
- class String
18
- def escape_single_quotes
19
- gsub("'"){"\\'"}
20
- end
21
-
22
- def to_pathname
23
- Pathname.new(self)
24
- end
25
- end
26
-
27
- class Pathname
28
- def to_pathname
29
- self
30
- end
31
- end
32
18
 
33
19
  module OpsWalrus
34
20
  class Error < StandardError
@@ -131,6 +117,7 @@ module OpsWalrus
131
117
  def prompt_sudo_password
132
118
  password = IO::console.getpass(LOCAL_SUDO_PASSWORD_PROMPT)
133
119
  set_sudo_password(password)
120
+ # puts "sudo password = |#{password}|"
134
121
  nil
135
122
  end
136
123
 
@@ -158,24 +145,11 @@ module OpsWalrus
158
145
  ops_file = OpsFile.new(self, ops_file_path)
159
146
 
160
147
  # if the ops file is part of a package, then set the package directory as the app's pwd
161
- # puts "run1: #{ops_file.ops_file_path}"
162
148
  if ops_file.package_file && ops_file.package_file.dirname.to_s !~ /#{Bundler::BUNDLE_DIR}/
163
- # puts "set pwd: #{ops_file.package_file.dirname}"
164
- set_pwd(ops_file.package_file.dirname)
165
- rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(ops_file.package_file.dirname)
166
- # note: rebased_ops_file_relative_path is a relative path that is relative to ops_file.package_file.dirname
167
- # puts "rebased path: #{rebased_ops_file_relative_path}"
168
- absolute_ops_file_path = ops_file.package_file.dirname.join(rebased_ops_file_relative_path)
169
- # puts "absolute path: #{absolute_ops_file_path}"
170
- ops_file = OpsFile.new(self, absolute_ops_file_path)
149
+ ops_file = set_pwd_to_ops_file_package_directory(ops_file)
171
150
  end
172
- # puts "run2: #{ops_file.ops_file_path}"
173
151
 
174
152
  op = OperationRunner.new(self, ops_file)
175
- # if op.requires_sudo?
176
- # prompt_sudo_password unless sudo_password
177
- # end
178
- # exit_status, out, err, script_output_structure = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
179
153
  result = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
180
154
  exit_status = result.exit_status
181
155
 
@@ -183,14 +157,7 @@ module OpsWalrus
183
157
  puts "Op exit_status"
184
158
  puts exit_status
185
159
 
186
- # puts "Op stdout"
187
- # puts out
188
-
189
- # puts "Op stderr"
190
- # puts err
191
-
192
160
  puts "Op output"
193
- # puts script_output_structure ? JSON.pretty_generate(script_output_structure) : nil.inspect
194
161
  puts JSON.pretty_generate(result.value)
195
162
  end
196
163
 
@@ -203,6 +170,19 @@ module OpsWalrus
203
170
  FileUtils.remove_entry(tmp_dir) if tmp_dir
204
171
  end
205
172
 
173
+ # sets the App's pwd to the ops file's package directory and
174
+ # returns a new OpsFile that points at the revised pathname with considered as relative to the package file's directory
175
+ def set_pwd_to_ops_file_package_directory(ops_file)
176
+ # puts "set pwd: #{ops_file.package_file.dirname}"
177
+ set_pwd(ops_file.package_file.dirname)
178
+ rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(ops_file.package_file.dirname)
179
+ # note: rebased_ops_file_relative_path is a relative path that is relative to ops_file.package_file.dirname
180
+ # puts "rebased path: #{rebased_ops_file_relative_path}"
181
+ absolute_ops_file_path = ops_file.package_file.dirname.join(rebased_ops_file_relative_path)
182
+ # puts "absolute path: #{absolute_ops_file_path}"
183
+ OpsFile.new(self, absolute_ops_file_path)
184
+ end
185
+
206
186
  # package_operation_and_args can take one of the following forms:
207
187
  # - ["github.com/davidkellis/my-package", "operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
208
188
  # - ["foo.zip", "foo/myfile.ops", "arg1:val1", "arg2:val2", "arg3:val3"]
@@ -244,7 +224,7 @@ module OpsWalrus
244
224
 
245
225
  # operation_kv_args = package_operation_and_args
246
226
  [ops_file_path, operation_kv_args, tmp_dir]
247
- when repo_url = git_repo?(package_or_ops_file_reference)
227
+ when repo_url = Git.repo?(package_or_ops_file_reference)
248
228
  destination_package_path = bundler.download_git_package(repo_url)
249
229
 
250
230
  ops_file_path, operation_kv_args = find_entry_point_ops_file_in_dir(destination_package_path, package_operation_and_args)
@@ -301,22 +281,6 @@ module OpsWalrus
301
281
  [ops_file_path, operation_kv_args]
302
282
  end
303
283
 
304
- # git_repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
305
- # returns the repo URL
306
- def git_repo?(repo_reference)
307
- candidate_repo_references = [
308
- repo_reference,
309
- repo_reference =~ /(\.(com|net|org|dev|io|local))\// && "https://#{repo_reference}",
310
- repo_reference !~ /github\.com\// && repo_reference =~ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/([\w\.@\:\-~]+)$/i && "https://github.com/#{repo_reference}" # this regex is from https://www.npmjs.com/package/github-username-regex and https://www.debuggex.com/r/H4kRw1G0YPyBFjfm
311
- ].compact
312
- working_repo_reference = candidate_repo_references.find {|reference| Git.ls_remote(reference) rescue nil }
313
- working_repo_reference
314
- end
315
-
316
- # def is_dir_git_repo?(dir_path)
317
- # Git.ls_remote(reference) rescue nil
318
- # end
319
-
320
284
  def bundle_status
321
285
  end
322
286
 
@@ -22,8 +22,8 @@ module OpsWalrus
22
22
  @bundle_dir
23
23
  end
24
24
 
25
- def ensure_package_bundle_directory_exists
26
- FileUtils.mkdir_p(@bundle_dir)
25
+ def ensure_pwd_bundle_directory_exists
26
+ FileUtils.mkdir_p(@bundle_dir) unless @bundle_dir.exist?
27
27
  end
28
28
 
29
29
  # # returns the OpsFile within the bundle directory that represents the given ops_file (which is outside of the bundle directory)
@@ -50,7 +50,7 @@ module OpsWalrus
50
50
  # end
51
51
 
52
52
  def update
53
- ensure_package_bundle_directory_exists
53
+ ensure_pwd_bundle_directory_exists
54
54
 
55
55
  package_yaml_files = pwd.glob("./**/package.yaml") - pwd.glob("./**/#{BUNDLE_DIR}/**/package.yaml")
56
56
  package_files_within_pwd = package_yaml_files.map {|path| PackageFile.new(path.realpath) }
@@ -83,7 +83,7 @@ module OpsWalrus
83
83
 
84
84
  # returns the self_pkg directory within the bundle directory
85
85
  # def include_directory_in_bundle_as_self_pkg(dirname = pwd)
86
- # ensure_package_bundle_directory_exists
86
+ # ensure_pwd_bundle_directory_exists
87
87
 
88
88
  # destination_package_path = @bundle_dir.join("self_pkg")
89
89
 
@@ -103,7 +103,7 @@ module OpsWalrus
103
103
  # This method downloads a package_url that is a dependency referenced in the specified package_file
104
104
  # returns the destination directory that the package was downloaded to
105
105
  def download_package(package_file, package_reference)
106
- ensure_package_bundle_directory_exists
106
+ ensure_pwd_bundle_directory_exists
107
107
 
108
108
  local_name = package_reference.local_name
109
109
  package_url = package_reference.package_uri
@@ -144,19 +144,26 @@ module OpsWalrus
144
144
  end
145
145
 
146
146
  def download_git_package(package_url, version = nil, destination_package_path = nil)
147
- destination_package_path ||= begin
148
- package_reference_dirname = sanitize_path(package_url)
149
- bundle_dir.join(package_reference_dirname)
150
- end
151
- FileUtils.remove_dir(destination_package_path) if File.exist?(destination_package_path)
147
+ ensure_pwd_bundle_directory_exists
148
+
149
+ destination_package_path ||= dynamic_package_path_for_git_package(package_url, version)
150
+
151
+ return destination_package_path if destination_package_path.exist?
152
+
152
153
  if version
153
- Git.clone(package_url, destination_package_path, branch: version, config: ['submodule.recurse=true'])
154
+ ::Git.clone(package_url, destination_package_path, branch: version, config: ['submodule.recurse=true'])
154
155
  else
155
- Git.clone(package_url, destination_package_path, config: ['submodule.recurse=true'])
156
+ ::Git.clone(package_url, destination_package_path, config: ['submodule.recurse=true'])
156
157
  end
158
+
157
159
  destination_package_path
158
160
  end
159
161
 
162
+ def dynamic_package_path_for_git_package(package_url, version = nil)
163
+ package_reference_dirname = sanitize_path(package_url)
164
+ bundle_dir.join(package_reference_dirname)
165
+ end
166
+
160
167
  def sanitize_path(path)
161
168
  # found this at https://apidock.com/rails/v5.2.3/ActiveStorage/Filename/sanitized
162
169
  path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
@@ -0,0 +1,22 @@
1
+ require "git"
2
+
3
+ module OpsWalrus
4
+ class Git
5
+ # repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
6
+ # returns the repo URL or directory path
7
+ def self.repo?(repo_reference)
8
+ if Dir.exist?(repo_reference)
9
+ ::Git.ls_remote(repo_reference) && repo_reference rescue nil
10
+ else
11
+ candidate_repo_references = [
12
+ repo_reference,
13
+ repo_reference =~ /(\.(com|net|org|dev|io|local))\// && "https://#{repo_reference}",
14
+ repo_reference !~ /github\.com\// && repo_reference =~ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/([\w\.@\:\-~]+)$/i && "https://github.com/#{repo_reference}" # this regex is from https://www.npmjs.com/package/github-username-regex and https://www.debuggex.com/r/H4kRw1G0YPyBFjfm
15
+ ].compact
16
+ working_repo_reference = candidate_repo_references.find {|reference| ::Git.ls_remote(reference) rescue nil }
17
+ working_repo_reference
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -7,18 +7,18 @@ module OpsWalrus
7
7
 
8
8
  module HostDSL
9
9
  # returns the stdout from the command
10
- def sh(desc_or_cmd = nil, cmd = nil, stdin: nil, &block)
11
- out, err, status = *shell!(desc_or_cmd, cmd, block, stdin: stdin)
10
+ def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
11
+ out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
12
12
  out
13
13
  end
14
14
 
15
15
  # returns the tuple: [stdout, stderr, exit_status]
16
- def shell(desc_or_cmd = nil, cmd = nil, stdin: nil, &block)
17
- shell!(desc_or_cmd, cmd, block, stdin: stdin)
16
+ def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
17
+ shell!(desc_or_cmd, cmd, block, input: input)
18
18
  end
19
19
 
20
20
  # returns the tuple: [stdout, stderr, exit_status]
21
- def shell!(desc_or_cmd = nil, cmd = nil, block = nil, stdin: nil)
21
+ def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
22
22
  # description = nil
23
23
 
24
24
  return ["", "", 0] if !desc_or_cmd && !cmd && !block # we were told to do nothing; like hitting enter at the bash prompt; we can do nothing successfully
@@ -27,6 +27,10 @@ module OpsWalrus
27
27
  cmd = block.call if block
28
28
  cmd ||= desc_or_cmd
29
29
 
30
+ cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
31
+
32
+ #cmd = Shellwords.escape(cmd)
33
+
30
34
  if self.alias
31
35
  print "[#{self.alias} | #{host}] "
32
36
  else
@@ -35,16 +39,13 @@ module OpsWalrus
35
39
  print "#{description}: " if description
36
40
  puts cmd
37
41
 
38
- cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
39
42
  return unless cmd && !cmd.strip.empty?
40
43
 
41
- #cmd = Shellwords.escape(cmd)
42
-
43
44
  # puts "shell: #{cmd}"
44
45
  # puts "shell: #{cmd.inspect}"
45
46
  # puts "sudo_password: #{sudo_password}"
46
47
 
47
- sshkit_cmd = execute_cmd(cmd)
48
+ sshkit_cmd = execute_cmd(cmd, input: input)
48
49
 
49
50
  [sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
50
51
  end
@@ -202,6 +203,10 @@ module OpsWalrus
202
203
  })
203
204
  end
204
205
 
206
+ def set_runtime_env(runtime_env)
207
+ @runtime_env = runtime_env
208
+ end
209
+
205
210
  def set_ssh_session_connection(sshkit_backend)
206
211
  @sshkit_backend = sshkit_backend
207
212
  end
@@ -211,18 +216,27 @@ module OpsWalrus
211
216
  end
212
217
 
213
218
  def clear_ssh_session
219
+ @runtime_env = nil
214
220
  @sshkit_backend = nil
215
221
  @tmp_bundle_root_dir = nil
216
222
  end
217
223
 
218
- def execute(*args)
224
+ def execute(*args, input: nil)
219
225
  # puts "interaction handler responds with: #{ssh_password}"
220
- @sshkit_backend.capture(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
226
+ # @sshkit_backend.capture(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
221
227
  # @sshkit_backend.capture(*args, interaction_handler: SudoPromptInteractionHandler.new, verbosity: :info)
228
+
229
+ @runtime_env.handle_input(input, ssh_password) do |interaction_handler|
230
+ @sshkit_backend.capture(*args, interaction_handler: interaction_handler, verbosity: :info)
231
+ end
232
+
222
233
  end
223
234
 
224
- def execute_cmd(*args)
225
- @sshkit_backend.execute_cmd(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
235
+ def execute_cmd(*args, input: nil)
236
+ # @sshkit_backend.execute_cmd(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
237
+ @runtime_env.handle_input(input, ssh_password) do |interaction_handler|
238
+ @sshkit_backend.execute_cmd(*args, interaction_handler: interaction_handler, verbosity: :info)
239
+ end
226
240
  end
227
241
 
228
242
  def upload(local_path_or_io, remote_path)
@@ -2,6 +2,91 @@ require 'sshkit'
2
2
 
3
3
  module OpsWalrus
4
4
 
5
+ class ScopedMappingInteractionHandler
6
+ attr_accessor :input_mappings # Hash[ String | Regex => String ]
7
+
8
+ def initialize(mapping, log_level = nil)
9
+ @log_level = log_level
10
+ @input_mappings = mapping
11
+ end
12
+
13
+ # temporarily adds a sudo password mapping to the interaction handler while the given block is being evaluated
14
+ # when the given block returns, then the temporary mapping is removed from the interaction handler
15
+ # def with_sudo_password(password, &block)
16
+ # with_mapping({
17
+ # /\[sudo\] password for .*?:\s*/ => "#{password}\n",
18
+ # App::LOCAL_SUDO_PASSWORD_PROMPT => "#{password}\n",
19
+ # # /\s+/ => nil, # unnecessary
20
+ # }, &block)
21
+ # end
22
+
23
+ # sudo_password : String
24
+ def mapping_for_sudo_password(sudo_password)
25
+ {
26
+ /\[sudo\] password for .*?:\s*/ => "#{sudo_password}\n",
27
+ App::LOCAL_SUDO_PASSWORD_PROMPT => "#{sudo_password}\n",
28
+ # /\s+/ => nil, # unnecessary
29
+ }
30
+ end
31
+
32
+ # temporarily adds the specified input mapping to the interaction handler while the given block is being evaluated
33
+ # when the given block returns, then the temporary mapping is removed from the interaction handler
34
+ #
35
+ # mapping : Hash[ String | Regex => String ]
36
+ def with_mapping(mapping, sudo_password = nil)
37
+ mapping ||= {}
38
+
39
+ raise ArgumentError.new("mapping must be a Hash") unless mapping.is_a?(Hash)
40
+
41
+ if sudo_password
42
+ mapping.merge!(mapping_for_sudo_password(sudo_password))
43
+ end
44
+
45
+ if mapping.empty?
46
+ yield self
47
+ else
48
+ yield ScopedMappingInteractionHandler.new(@input_mappings.merge(mapping), @log_level)
49
+ end
50
+ end
51
+
52
+ # adds the specified input mapping to the interaction handler
53
+ #
54
+ # mapping : Hash[ String | Regex => String ]
55
+ def add_mapping(mapping)
56
+ @input_mappings.merge!(mapping)
57
+ end
58
+
59
+ def on_data(_command, stream_name, data, channel)
60
+ log("Looking up response for #{stream_name} message #{data.inspect}")
61
+
62
+ response_data = begin
63
+ first_matching_key_value_pair = @input_mappings.find {|k, _v| k === data }
64
+ first_matching_key_value_pair&.last
65
+ end
66
+
67
+ if response_data.nil?
68
+ log("Unable to find interaction handler mapping for #{stream_name}: #{data.inspect} so no response was sent")
69
+ else
70
+ log("Sending #{response_data.inspect}")
71
+ if channel.respond_to?(:send_data) # Net SSH Channel
72
+ channel.send_data(response_data)
73
+ elsif channel.respond_to?(:write) # Local IO
74
+ channel.write(response_data)
75
+ else
76
+ raise "Unable to write response data to channel #{channel.inspect} - does not support '#send_data' or '#write'"
77
+ end
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def log(message)
84
+ # puts message
85
+ SSHKit.config.output.send(@log_level, message) unless @log_level.nil?
86
+ end
87
+
88
+ end
89
+
5
90
  class PasswdInteractionHandler
6
91
  def on_data(command, stream_name, data, channel)
7
92
  # puts data
@@ -16,12 +16,16 @@ module SSHKit
16
16
  cmd.started = Time.now
17
17
  PTY.spawn(cmd.to_command) do |stdout, stdin, pid|
18
18
  stdout_thread = Thread.new do
19
+ # debug_log = StringIO.new
19
20
  buffer = ""
20
21
  partial_buffer = ""
21
22
  while !stdout.closed?
23
+ # debug_log.puts "!!!\nbuffer=#{buffer}|EOL|\npartial=#{partial_buffer}|EOL|"
22
24
  # puts "9" * 80
23
25
  begin
24
- stdout.read_nonblock(4096, partial_buffer)
26
+ # partial_buffer = ""
27
+ # stdout.read_nonblock(4096, partial_buffer)
28
+ partial_buffer = stdout.read_nonblock(4096)
25
29
  buffer << partial_buffer
26
30
  # puts "nonblocking1. buffer=#{buffer} partial_buffer=#{partial_buffer}"
27
31
  buffer = handle_data_for_stdout(output, cmd, buffer, stdin, false)
@@ -48,6 +52,10 @@ module SSHKit
48
52
  end
49
53
  end
50
54
  # puts "end!"
55
+ # debug_log.puts "!!!\nbuffer=#{buffer}|EOL|\npartial=#{partial_buffer}|EOL|"
56
+
57
+ # puts "*" * 80
58
+ # puts debug_log.string
51
59
 
52
60
  end
53
61
  stdout_thread.join
@@ -59,12 +67,14 @@ module SSHKit
59
67
 
60
68
  # returns [complete lines, new buffer]
61
69
  def split_buffer(buffer)
62
- lines = buffer.split(/(\r\n)\r|\n/)
70
+ lines = buffer.split(/(\r\n)|\r|\n/)
63
71
  buffer = lines.pop
64
72
  [lines, buffer]
65
73
  end
66
74
 
67
75
  def handle_data_for_stdout(output, cmd, buffer, stdin, is_blocked)
76
+ # puts "handling data for stdout: #{buffer}"
77
+
68
78
  # we're blocked on reading, so let's process the buffer
69
79
  lines, buffer = split_buffer(buffer)
70
80
  lines.each do |line|
@@ -72,7 +72,7 @@ module OpsWalrus
72
72
  Invocation::Error.new(e)
73
73
  end
74
74
 
75
- if verbose && result.failure?
75
+ if verbose == 2 && result.failure?
76
76
  puts "Ops script error details:"
77
77
  puts "Error: #{result.value}"
78
78
  puts "Status code: #{result.exit_status}"
@@ -1,5 +1,6 @@
1
1
  require 'pathname'
2
2
  require 'yaml'
3
+ require_relative 'git'
3
4
  require_relative 'ops_file_script'
4
5
 
5
6
  module OpsWalrus
@@ -91,12 +92,16 @@ module OpsWalrus
91
92
  imports_hash.map do |local_name, yaml_import_reference|
92
93
  local_name = local_name.to_s
93
94
  import_reference = case yaml_import_reference
95
+
96
+ # when the imports line says:
97
+ # imports:
98
+ # my_package: my_package
94
99
  in String => import_str
95
100
  case
96
101
  when package_reference = package_file&.dependency(import_str) # package dependency reference
97
102
  # in this context, import_str is the local package name documented in the package's dependencies
98
103
  PackageDependencyReference.new(local_name, package_reference)
99
- when import_str.to_pathname.exist? # path reference
104
+ when import_str.to_pathname.exist? # path reference
100
105
  path = import_str.to_pathname
101
106
  case
102
107
  when path.directory?
@@ -106,13 +111,26 @@ module OpsWalrus
106
111
  else
107
112
  raise Error, "Unknown import reference: #{local_name} -> #{import_str.inspect}"
108
113
  end
114
+ when Git.repo?(import_str) # ops file has imported an ad-hoc git repo
115
+ package_uri = import_str
116
+ destination_package_path = app.bundler.dynamic_package_path_for_git_package(package_uri)
117
+ # puts "DynamicPackageImportReference: #{local_name} -> #{destination_package_path}"
118
+ DynamicPackageImportReference.new(local_name, DynamicPackageReference.new(local_name, package_uri, nil))
119
+ else
120
+ raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
109
121
  end
110
- # in Hash
122
+
123
+ # when the imports line says:
124
+ # imports:
125
+ # my_package:
126
+ # url: https://...
127
+ # version: 2.1
128
+ # in Hash => package_defn
111
129
  # url = package_defn["url"]
112
130
  # version = package_defn["version"]
113
131
  # PackageReference.new(local_name, url, version&.to_s)
114
132
  else
115
- raise Error, "Unknown package reference: #{package_defn.inspect}"
133
+ raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
116
134
  end
117
135
  [local_name, import_reference]
118
136
  end.to_h
@@ -205,36 +223,5 @@ module OpsWalrus
205
223
  dirname.glob("*").select(&:directory?)
206
224
  end
207
225
 
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
- def supporting_library_include_dir_and_require_lib
216
- if Dir.exist?(ops_file_helper_library_directory)
217
- [ops_file_helper_library_directory, ops_file_helper_library_basename]
218
- elsif File.exist?(ops_file_sibling_helper_library_file)
219
- [dirname, ops_file_helper_library_basename]
220
- else
221
- [nil, nil]
222
- end
223
- end
224
-
225
- def ops_file_helper_library_basename
226
- basename.sub_ext(".rb")
227
- end
228
-
229
- # "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info"
230
- def ops_file_helper_library_directory
231
- File.join(dirname, basename)
232
- end
233
-
234
- # "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info.rb"
235
- def ops_file_sibling_helper_library_file
236
- "#{ops_file_helper_library_directory}.rb"
237
- end
238
-
239
226
  end
240
227
  end
@@ -20,6 +20,9 @@ module OpsWalrus
20
20
  def [](index, *args, **kwargs, &block)
21
21
  @obj.method(:[]).call(index, *args, **kwargs, &block)
22
22
  end
23
+ def respond_to_missing?(method, *)
24
+ @obj.is_a?(Hash) && @obj.respond_to?(method)
25
+ end
23
26
  def method_missing(name, *args, **kwargs, &block)
24
27
  case @obj
25
28
  when Array
@@ -118,13 +121,15 @@ module OpsWalrus
118
121
  # bootstrap_shell_script = BootstrapLinuxHostShellScript
119
122
  # on sshkit_hosts do |sshkit_host|
120
123
  SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
121
- host = sshkit_host_to_ops_host_map[sshkit_host]
122
124
 
125
+ # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
126
+
127
+ host = sshkit_host_to_ops_host_map[sshkit_host]
123
128
  # puts "#{host.alias} / #{host}:"
124
129
 
125
130
  begin
126
- # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
127
- host.set_ssh_session_connection(self)
131
+ host.set_runtime_env(runtime_env)
132
+ host.set_ssh_session_connection(self) # self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
128
133
 
129
134
  # copy over bootstrap shell script
130
135
  # io = StringIO.new(bootstrap_shell_script)
@@ -196,11 +201,14 @@ module OpsWalrus
196
201
  @runtime_env.app.inventory(tags)
197
202
  end
198
203
 
199
- def exit(exit_status)
204
+ def exit(exit_status, message = nil)
205
+ if message
206
+ puts message
207
+ end
200
208
  result = if exit_status == 0
201
- Success.new(nil)
209
+ Invocation::Success.new(nil)
202
210
  else
203
- Error.new(nil, exit_status)
211
+ Invocation::Error.new(nil, exit_status)
204
212
  end
205
213
  throw :exit_now, result
206
214
  end
@@ -214,6 +222,9 @@ module OpsWalrus
214
222
  end
215
223
  end
216
224
 
225
+ # currently, import may only be used to import a package that is referenced in the script's package file
226
+ # I may decide to extend this to work with dynamic package references
227
+ #
217
228
  # local_package_name is the local package name defined for the package dependency that is attempting to be referenced
218
229
  def import(local_package_name)
219
230
  local_package_name = local_package_name.to_s
@@ -234,18 +245,18 @@ module OpsWalrus
234
245
  end
235
246
 
236
247
  # returns the stdout from the command
237
- def sh(desc_or_cmd = nil, cmd = nil, stdin: nil, &block)
238
- out, err, status = *shell!(desc_or_cmd, cmd, block, stdin: stdin)
248
+ def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
249
+ out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
239
250
  out
240
251
  end
241
252
 
242
253
  # returns the tuple: [stdout, stderr, exit_status]
243
- def shell(desc_or_cmd = nil, cmd = nil, stdin: nil, &block)
244
- shell!(desc_or_cmd, cmd, block, stdin: stdin)
254
+ def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
255
+ shell!(desc_or_cmd, cmd, block, input: input)
245
256
  end
246
257
 
247
258
  # returns the tuple: [stdout, stderr, exit_status]
248
- def shell!(desc_or_cmd = nil, cmd = nil, block = nil, stdin: nil)
259
+ def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
249
260
  # description = nil
250
261
 
251
262
  return ["", "", 0] if !desc_or_cmd && !cmd && !block # we were told to do nothing; like hitting enter at the bash prompt; we can do nothing successfully
@@ -254,19 +265,20 @@ module OpsWalrus
254
265
  cmd = block.call if block
255
266
  cmd ||= desc_or_cmd
256
267
 
268
+ cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
269
+
270
+ #cmd = Shellwords.escape(cmd)
271
+
257
272
  # puts "shell! self: #{self.inspect}"
258
273
 
259
274
  print "[#{@runtime_env.local_hostname}] "
260
275
  print "#{description}: " if description
261
276
  puts cmd
262
277
 
263
- cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
264
278
  return unless cmd && !cmd.strip.empty?
265
279
 
266
- #cmd = Shellwords.escape(cmd)
267
-
268
- sudo_password = @runtime_env.sudo_password
269
- sudo_password &&= sudo_password.gsub(/\n+$/,'') # remove trailing newlines from sudo_password
280
+ # sudo_password = @runtime_env.sudo_password
281
+ # sudo_password &&= sudo_password.gsub(/\n+$/,'') # remove trailing newlines from sudo_password
270
282
 
271
283
  # puts "shell: #{cmd}"
272
284
  # puts "shell: #{cmd.inspect}"
@@ -274,10 +286,14 @@ module OpsWalrus
274
286
 
275
287
  # sshkit_cmd = SSHKit::Backend::LocalNonBlocking.new {
276
288
  # sshkit_cmd = SSHKit::Backend::LocalPty.new {
277
- sshkit_cmd = backend.execute_cmd(cmd, interaction_handler: SudoPasswordMapper.new(sudo_password).interaction_handler, verbosity: :info)
289
+ # sshkit_cmd = backend.execute_cmd(cmd, interaction_handler: SudoPasswordMapper.new(sudo_password).interaction_handler, verbosity: :info)
278
290
  # execute_cmd(cmd, interaction_handler: SudoPromptInteractionHandler.new, verbosity: :info)
279
291
  # }.run
280
292
 
293
+ sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
294
+ backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
295
+ end
296
+
281
297
  [sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
282
298
  end
283
299
 
@@ -5,6 +5,7 @@ require_relative 'bundler'
5
5
 
6
6
  module OpsWalrus
7
7
 
8
+ # these are static package references defined ahead of time in the package file
8
9
  class PackageReference
9
10
  attr_accessor :local_name
10
11
  attr_accessor :package_uri
@@ -23,7 +24,7 @@ module OpsWalrus
23
24
  path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
24
25
  end
25
26
 
26
- # important: the dirname implemented as the local_name is critical because Bundler#download_package downloads
27
+ # important: the import_resolution_dirname implemented as the local_name is critical because Bundler#download_package downloads
27
28
  # package dependencies to the name that this method returns, which must match the package reference's local name
28
29
  # so that later, when the package is being looked up on the load path (in LoadPath#resolve_import_reference),
29
30
  # the package reference's referenced git repo or file path may not exist or be available, and so the package
@@ -32,12 +33,20 @@ module OpsWalrus
32
33
  # If this implementation changes, then Bundler#download_package and LoadPath#resolve_import_reference must also
33
34
  # change in order for the three things to reconcile with respect to one another, since all three bits of logic are
34
35
  # what make bundling package dependencies and loading them function properly.
35
- def dirname
36
+ def import_resolution_dirname
36
37
  local_name
37
38
  end
38
39
 
39
40
  end
40
41
 
42
+ # these are dynamic package references defined at runtime when an OpsFile's imports are being evaluated.
43
+ # this will usually be the case when an ops file does not belong to a package
44
+ class DynamicPackageReference < PackageReference
45
+ def import_resolution_dirname
46
+ sanitized_package_uri
47
+ end
48
+ end
49
+
41
50
  class PackageFile
42
51
  attr_accessor :package_file_path
43
52
  attr_accessor :yaml
@@ -3,6 +3,7 @@ require 'shellwords'
3
3
  require 'socket'
4
4
  require 'sshkit'
5
5
 
6
+ require_relative 'interaction_handlers'
6
7
  require_relative 'traversable'
7
8
  require_relative 'walrus_lang'
8
9
 
@@ -32,6 +33,14 @@ module OpsWalrus
32
33
  end
33
34
  end
34
35
 
36
+ class DynamicPackageImportReference < ImportReference
37
+ attr_accessor :package_reference
38
+ def initialize(local_name, package_reference)
39
+ super(local_name)
40
+ @package_reference = package_reference
41
+ end
42
+ end
43
+
35
44
  class OpsFileReference < ImportReference
36
45
  attr_accessor :ops_file_path
37
46
  def initialize(local_name, ops_file_path)
@@ -86,6 +95,8 @@ module OpsWalrus
86
95
  @dir = dir
87
96
  @root_namespace = build_symbol_resolution_tree(@dir)
88
97
  @path_map = build_path_map(@root_namespace)
98
+
99
+ @dynamic_package_additions_memo = {}
89
100
  end
90
101
 
91
102
  # returns a tree of Namespace -> {Namespace* -> {Namespace* -> ..., OpsFile*}, OpsFile*}
@@ -129,6 +140,19 @@ module OpsWalrus
129
140
  path_map
130
141
  end
131
142
 
143
+ def dynamically_add_new_package_dir(new_package_dir)
144
+ # patch the symbol resolution (namespace) tree
145
+ dir_basename = new_package_dir.basename
146
+ unless @root_namespace.resolve_symbol(dir_basename)
147
+ new_child_namespace = build_symbol_resolution_tree(new_package_dir)
148
+ @root_namespace.add(dir_basename, new_child_namespace)
149
+
150
+ # patch the path map
151
+ new_partial_path_map = build_path_map(new_child_namespace)
152
+ @path_map.merge!(new_partial_path_map)
153
+ end
154
+ end
155
+
132
156
  # returns a Namespace
133
157
  def lookup_namespace(ops_file)
134
158
  @path_map[ops_file.dirname]
@@ -144,7 +168,16 @@ module OpsWalrus
144
168
  case import_reference
145
169
  when PackageDependencyReference
146
170
  # puts "root namespace: #{@root_namespace.symbol_table}"
147
- @root_namespace.resolve_symbol(import_reference.package_reference.local_name) # returns the Namespace associated with the bundled package dirname (i.e. the local name)
171
+ @root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package dirname (i.e. the local name)
172
+ when DynamicPackageImportReference
173
+ dynamic_package_reference = import_reference.package_reference
174
+ @dynamic_package_additions_memo[dynamic_package_reference] ||= begin
175
+ # puts "Downloading dynamic package: #{dynamic_package_reference.inspect}"
176
+ dynamically_added_package_dir = @runtime_env.app.bundler.download_git_package(dynamic_package_reference.package_uri, dynamic_package_reference.version)
177
+ dynamically_add_new_package_dir(dynamically_added_package_dir)
178
+ dynamically_added_package_dir
179
+ end
180
+ @root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package dirname (i.e. the sanitized package uri)
148
181
  when DirectoryReference
149
182
  @path_map[import_reference.dirname]
150
183
  when OpsFileReference
@@ -164,9 +197,27 @@ module OpsWalrus
164
197
  @bundle_load_path = LoadPath.new(self, @app.bundle_dir)
165
198
  @app_load_path = LoadPath.new(self, @app.pwd)
166
199
 
200
+ @interaction_handler = ScopedMappingInteractionHandler.new({
201
+ /\[sudo\] password for .*?:\s*/ => "#{sudo_password}\n",
202
+ })
203
+
167
204
  configure_sshkit
168
205
  end
169
206
 
207
+ # def with_sudo_password(password, &block)
208
+ # @interaction_handler.with_sudo_password(password, &block)
209
+ # end
210
+
211
+ # input_mapping : Hash[ String | Regex => String ]
212
+ # sudo_password : String
213
+ def handle_input(input_mapping, sudo_password = nil, &block)
214
+ @interaction_handler.with_mapping(input_mapping, sudo_password, &block)
215
+ end
216
+
217
+ # def handle_input_with_sudo_password(input_mapping, password, &block)
218
+ # @interaction_handler.with_mapping(input_mapping, password, &block)
219
+ # end
220
+
170
221
  # configure sshkit globally
171
222
  def configure_sshkit
172
223
  SSHKit.config.use_format :blackhole
@@ -244,7 +295,7 @@ module OpsWalrus
244
295
  case import_reference
245
296
 
246
297
  # we know we're dealing with a package dependency reference, so we want to do the lookup in the bundle load path, where package dependencies live
247
- when PackageDependencyReference
298
+ when PackageDependencyReference, DynamicPackageImportReference
248
299
  @bundle_load_path.resolve_import_reference(origin_ops_file, import_reference)
249
300
 
250
301
  # we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to do the lookup in the app load path
@@ -1,3 +1,3 @@
1
1
  module OpsWalrus
2
- VERSION = "1.0.5"
2
+ VERSION = "1.0.7"
3
3
  end
@@ -57,7 +57,7 @@ module WalrusLang
57
57
 
58
58
  # binding_obj : Binding | Hash
59
59
  def self.render(template, binding_obj)
60
- binding_obj = binding_obj.to_binding if binding_obj.is_a?(Hash)
60
+ binding_obj = binding_obj.to_binding if binding_obj.respond_to?(:to_binding)
61
61
  ast = WalrusLang::Parser.parse(template)
62
62
  ast.render(binding_obj)
63
63
  end
data/opswalrus.gemspec CHANGED
@@ -11,10 +11,10 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "opswalrus is a tool that runs scripts against a fleet of hosts"
12
12
  spec.description = "opswalrus is a tool that runs scripts against a fleet of hosts hosts. It's kind of like Ansible, but aims to be simpler to use."
13
13
  spec.homepage = "https://github.com/opswalrus/opswalrus"
14
- spec.license = 'EPL-2.0'
14
+ spec.license = "EPL-2.0"
15
15
  spec.required_ruby_version = ">= 2.6.0"
16
16
 
17
- # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server - https://example.com"
18
18
 
19
19
  spec.metadata["homepage_uri"] = spec.homepage
20
20
  spec.metadata["source_code_uri"] = "https://github.com/opswalrus/opswalrus"
@@ -33,13 +33,13 @@ Gem::Specification.new do |spec|
33
33
 
34
34
  # gem dependencies
35
35
  spec.add_dependency "citrus", "~> 3.0"
36
- spec.add_dependency "gli", '~> 2.21'
37
- spec.add_dependency "git", '~> 1.18'
38
- spec.add_dependency "rubyzip", '~> 2.3'
36
+ spec.add_dependency "gli", "~> 2.21"
37
+ spec.add_dependency "git", "~> 1.18"
38
+ spec.add_dependency "rubyzip", "~> 2.3"
39
39
 
40
- spec.add_dependency "bcrypt_pbkdf", '~> 1.1'
41
- spec.add_dependency "ed25519", '~> 1.3'
42
- spec.add_dependency "sshkit", '~> 1.21'
40
+ spec.add_dependency "bcrypt_pbkdf", "~> 1.1"
41
+ spec.add_dependency "ed25519", "~> 1.3"
42
+ spec.add_dependency "sshkit", "~> 1.21"
43
43
 
44
44
  # For more information and examples about making a new gem, check out our
45
45
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opswalrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Ellis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-16 00:00:00.000000000 Z
11
+ date: 2023-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: citrus
@@ -124,15 +124,14 @@ files:
124
124
  - LICENSE
125
125
  - README.md
126
126
  - Rakefile
127
+ - build.ops
127
128
  - exe/ops
128
129
  - lib/opswalrus.rb
129
130
  - lib/opswalrus/app.rb
130
131
  - lib/opswalrus/bootstrap.sh
131
- - lib/opswalrus/bootstrap_linux_host1.sh
132
- - lib/opswalrus/bootstrap_linux_host2.sh
133
- - lib/opswalrus/bootstrap_linux_host3.sh
134
132
  - lib/opswalrus/bundler.rb
135
133
  - lib/opswalrus/cli.rb
134
+ - lib/opswalrus/git.rb
136
135
  - lib/opswalrus/host.rb
137
136
  - lib/opswalrus/hosts_file.rb
138
137
  - lib/opswalrus/interaction_handlers.rb
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # update package list
4
- sudo apt update -y
5
-
6
- # update OS
7
- # sudo DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confold" -o Dpkg::Options::="--force-confdef" dist-upgrade -q -y --allow-downgrades --allow-remove-essential --allow-change-held-packages
8
-
9
- if [ -f /var/run/reboot-required ]; then
10
- echo 'A system reboot is required!'
11
- sudo reboot
12
- fi
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # there are probably some services that need restarting because they're using old libraries, so we'll just do the easy thing and reboot
4
- sudo DEBIAN_FRONTEND=noninteractive apt install -yq needrestart
5
-
6
- # install basic development tools
7
- sudo DEBIAN_FRONTEND=noninteractive apt install -yq build-essential
8
-
9
- # install ruby dependencies
10
- sudo DEBIAN_FRONTEND=noninteractive apt install -yq autoconf patch rustc libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev
11
-
12
- # restart services that need it
13
- sudo needrestart -q -r a
14
-
15
- # vagrant@ubuntu-jammy:~$ sudo needrestart -q -r a
16
- # systemctl restart unattended-upgrades.service
17
-
18
- # vagrant@ubuntu-jammy:~$ sudo needrestart -r l
19
- # Scanning processes...
20
- # Scanning candidates...
21
- # Scanning linux images...
22
- #
23
- # Running kernel seems to be up-to-date.
24
- #
25
- # Services to be restarted:
26
- #
27
- # Service restarts being deferred:
28
- # systemctl restart unattended-upgrades.service
29
- #
30
- # No containers need to be restarted.
31
- #
32
- # No user sessions are running outdated binaries.
33
- #
34
- # No VM guests are running outdated hypervisor (qemu) binaries on this host.
35
-
36
- # reboot just in case
37
- # sudo reboot
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # install homebrew
4
- NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
5
- eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
6
-
7
- # install gcc, rust, ruby
8
- brew install gcc
9
- brew install rust
10
- brew install ruby
11
-
12
- # brew install rtx
13
- # eval "$(rtx activate bash)" # register a shell hook
14
- # rtx use -g ruby@3.2 # install ruby via rtx
15
-
16
- # download frum for ruby version management
17
- # curl -L -o frum.tar.gz https://github.com/TaKO8Ki/frum/releases/download/v0.1.2/frum-v0.1.2-x86_64-unknown-linux-musl.tar.gz
18
- # tar -zxf frum.tar.gz
19
- # mv frum-v0.1.2-x86_64-unknown-linux-musl/frum ~/bin
20
- # chmod 755 ~/bin/frum
21
-