vps_cli 0.1.4

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.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'vps_cli'
4
+
5
+ VpsCli.configure do |config|
6
+ # Where items will be copied to
7
+ # For example, the local dir is where you would your dotfiles
8
+ # saved to
9
+ config.local_dir = Dir.home
10
+ config.backup_dir = File.join(Dir.home, 'backup_files')
11
+ config.local_sshd_config = File.join('/etc', 'ssh', 'sshd_config')
12
+
13
+ # You must set these values yourself
14
+
15
+ # Location of your config files
16
+ # config.config_files = File.join(Dir.home, 'config_files')
17
+ # This is just used for easier git pulling and git pushing
18
+
19
+ config.config_files = File.join(Dir.home, 'vps_setup')
20
+
21
+ # Location of your dotfiles
22
+ # config.dotfiles = File.join(Dir.home, 'vps_setup', 'dotfiles')
23
+ config.dotfiles = File.join(config.config_files, 'dotfiles')
24
+
25
+ # Location of your dotfiles
26
+ # config.dotfiles = File.join(Dir.home, 'config_files', 'dotfiles')
27
+ config.misc_files = File.join(config.config_files, 'misc_files')
28
+
29
+ # credentials.yaml file, wherever its located, for me I have it in the home dir
30
+ config.credentials = File.join(Dir.home, '.credentials.yaml')
31
+
32
+ # location of your .netrc file, usually ~/.netrc
33
+ config.netrc = File.join(Dir.home, '.netrc')
34
+
35
+ config.verbose = false
36
+
37
+ # Change to false if you dont want to be prompted
38
+ # about file creations / overwrites
39
+ config.interactive = true
40
+
41
+ # this is merely for testing purposes, dont worry about this
42
+ config.testing = false
43
+ end
44
+
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'vps_cli'
4
+
5
+ test_dir = File.expand_path('../../../test', __dir__)
6
+
7
+ VpsCli.configure do |config|
8
+ config.local_dir = File.join(test_dir, 'local_dir')
9
+ config.backup_dir = File.join(test_dir, 'backup_dir')
10
+ config.local_sshd_config = File.join(config.local_dir, 'sshd_config')
11
+ config.sshd_backup = File.join(config.backup_dir, 'sshd_config.orig')
12
+
13
+ config.config_files = File.join(test_dir, 'config_files')
14
+ config.misc_files = File.join(config.config_files, 'miscfiles')
15
+ config.dotfiles = File.join(config.config_files, 'dotfiles')
16
+ config.credentials = File.join(File.expand_path('../../../', __dir__), 'example_credentials.yaml')
17
+ config.netrc = File.join(test_dir, 'access_dir', 'netrc')
18
+
19
+
20
+ config.verbose = true
21
+ config.interactive = false
22
+ config.testing = true
23
+ end
24
+
25
+ # puts VpsCli.configuration.local_dir
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+
5
+ require 'vps_cli/helpers/file_helper'
6
+
7
+ module VpsCli
8
+ # Copies config from /vps_cli/config_files/dotfiles
9
+ # & vps_cli/config_files/miscfiles to your home dir
10
+ class Copy
11
+ extend FileHelper
12
+
13
+ # Top level method for copying all files
14
+ # Will use the configurations set by VpsCli.configuration
15
+ # unless passed a different config
16
+ # @raise [RuntimeError]
17
+ # Will raise this error if you run this method as root or sudo
18
+ def self.all(config = VpsCli.configuration)
19
+ # raises an error if the script is run as root
20
+ return unless root? == false
21
+
22
+ # fills in options that are not explicitly filled in
23
+ FileHelper.mkdirs(config.local_dir, config.backup_dir)
24
+
25
+ # copies dotfiles
26
+ dotfiles(config)
27
+
28
+ # copies gnome_settings
29
+ gnome_settings(config)
30
+
31
+ # copies sshd_config
32
+ sshd_config(config)
33
+
34
+ puts "dotfiles copied to #{config.local_dir}"
35
+ puts "backups created @ #{config.backup_dir}"
36
+ end
37
+
38
+ # Copy files from 'config_files/dotfiles' directory via the copy_all method
39
+ # Defaults are provided in the VpsCli.create_options method
40
+ # @see VpsCli::Configuration
41
+ def self.dotfiles(config = VpsCli.configuration)
42
+ Dir.each_child(config.dotfiles) do |file|
43
+ config_file = File.join(config.dotfiles, file)
44
+ local = File.join(config.local_dir, ".#{file}")
45
+ backup = File.join(config.backup_dir, "#{file}.orig")
46
+
47
+ files_and_dirs(config_file: config_file,
48
+ local_file: local,
49
+ backup_file: backup,
50
+ verbose: config.verbose,
51
+ interactive: config.interactive)
52
+ end
53
+ end
54
+
55
+ # Checks that sshd_config is able to be copied
56
+ # @param sshd_config [File] File containing your original sshd_config
57
+ # Defaults to /etc/ssh/sshd_config
58
+ # @return [Boolean] Returns true if the sshd_config exists
59
+ def self.sshd_copyable?(sshd_config = nil)
60
+ sshd_config ||= '/etc/ssh/sshd_config'
61
+
62
+ no_sshd_config = 'No sshd_config found. sshd_config not copied'
63
+
64
+ return true if File.exist?(sshd_config)
65
+
66
+ VpsCli.errors << Exception.new(no_sshd_config)
67
+ end
68
+
69
+ # Copies sshd_config to the VpsCli.configuration.local_sshd_config
70
+ # location
71
+ # Defaults to [/etc/ssh/sshd_config] if not set
72
+ # This is slightly different from other copy methods in this file
73
+ # It uses Rake.sh("sudo cp")
74
+ # Due to copying to /etc/ssh/sshd_config requiring root permissions
75
+ def self.sshd_config(config = VpsCli.configuration)
76
+
77
+ config.local_sshd_config ||= File.join('/etc', 'ssh', 'sshd_config')
78
+ return unless sshd_copyable?(config.local_sshd_config)
79
+
80
+ config.sshd_backup ||= File.join(config.backup_dir, 'sshd_config.orig')
81
+
82
+ misc_sshd_path = File.join(config.misc_files, 'sshd_config')
83
+
84
+ if File.exist?(config.local_sshd_config) && !File.exist?(config.sshd_backup)
85
+ Rake.cp(config.local_sshd_config, config.sshd_backup)
86
+ else
87
+ puts "#{config.sshd_backup} already exists. no backup created"
88
+ end
89
+
90
+ return Rake.cp(misc_sshd_path, config.local_sshd_config) if config.testing
91
+
92
+ # This method must be run this way due to it requiring root privileges
93
+ unless FileHelper.overwrite?(config.local_sshd_config, config.interactive)
94
+ return
95
+ end
96
+
97
+ Rake.sh("sudo cp #{misc_sshd_path} #{config.local_sshd_config}")
98
+ end
99
+
100
+ # Deciphers between files & directories
101
+ # Also utilizes the settings from your configuration to properly
102
+ # copy things
103
+ # @see VpsCli::FileHelper#copy_dirs
104
+ # @see VpsCli::FileHelper#copy_files
105
+ def self.files_and_dirs(opts = {})
106
+ if File.directory?(opts[:config_file])
107
+ FileHelper.copy_dirs(opts)
108
+ else
109
+ FileHelper.copy_files(opts)
110
+ end
111
+ end
112
+
113
+ # Copies gnome terminal via dconf
114
+ # @see https://wiki.gnome.org/Projects/dconf dconf wiki
115
+ # @param config [VpsCli::Configuration] Where to save the current gnome terminal settings
116
+ # @note This method will raise an error if dconf errors out
117
+ # The error will be saved to VpsCli.errors
118
+ def self.gnome_settings(config = VpsCli.configuration)
119
+ backup = "#{config.backup_dir}/gnome_terminal_settings.orig"
120
+
121
+ # This is the ONLY spot for gnome terminal
122
+ gnome_path = '/org/gnome/terminal/'
123
+ gnome_file = File.join(config.misc_files, 'gnome_terminal_settings')
124
+
125
+ raise RuntimeError if config.testing
126
+ raise RuntimeError unless File.exists?(gnome_file)
127
+
128
+ overwrite = proc { |file| FileHelper.overwrite?(file, config.interactive) }
129
+ Rake.sh("dconf dump #{gnome_path} > #{backup}") if overwrite.call(backup)
130
+
131
+ dconf_load = "dconf load #{gnome_path} < #{config.misc_files}/gnome_terminal_settings"
132
+ Rake.sh(dconf_load) if overwrite.call(gnome_path)
133
+ rescue RuntimeError => error
134
+ puts 'something went wrong with gnome, continuing on' if config.verbose
135
+ VpsCli.errors << error
136
+ end
137
+
138
+ def self.root?
139
+ root = (Process.uid.zero? || Dir.home == '/root')
140
+ root_msg = 'Do not run this as root or sudo. Run as a normal user'
141
+ raise root_msg if root == true
142
+
143
+ false
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @see https://ruby-doc.org/stdlib-2.6.0.preview2/libdoc/open3/rdoc/Open3.html
4
+ require 'open3'
5
+ require 'json'
6
+ require 'net/http'
7
+
8
+ # Helper methods to be used within Access to help reduce the file size
9
+ module AccessHelper
10
+ # retrieves the values of .netrc for heroku and creates a writable string
11
+ # @return [String] Returns the string to be written to netrc
12
+ def heroku_api_string(yaml_file:)
13
+ # initial tree traversal in the .yaml file
14
+ heroku_api = %i[heroku api]
15
+ heroku_api_keys = %i[machine login password]
16
+
17
+ make_string(base: heroku_api, keys: heroku_api_keys) do |path|
18
+ decrypt(yaml_file: yaml_file, path: path)
19
+ end
20
+ end
21
+
22
+ # retries the git values for heroku in your .yaml file
23
+ # @return [String] Returns the string to be written to your netrc file
24
+ def heroku_git_string(yaml_file:)
25
+ heroku_git = %i[heroku git]
26
+ heroku_git_keys = %i[machine login password]
27
+
28
+ make_string(base: heroku_git, keys: heroku_git_keys) do |path|
29
+ decrypt(yaml_file: yaml_file, path: path)
30
+ end
31
+ end
32
+
33
+ # my version of Enumerable#inject intended to return a string
34
+ # provides a count to know what # object youre on
35
+ # @param array [Array<#to_s>]
36
+ # For each element in the array, yield to the block given.
37
+ # @yieldparam accum [String]
38
+ # The value that will persist throughout the block
39
+ # @yieldparam element [String] The current element in the array
40
+ # @yield param count [Integer]
41
+ # @return [String] Returns the string returned by the block passed to it
42
+ def my_inject_with_count(array)
43
+ value = nil
44
+ count = 0
45
+ array.inject('') do |accum, element|
46
+ value = yield(accum, element, count)
47
+ count += 1
48
+ value # if not here, returns the value of count
49
+ end
50
+ end
51
+
52
+ # Creates a string to be used to write to .netrc
53
+ # @param base [String] Provides the base string from which to add to
54
+ # @param keys [Array<String>] An array of strings to append to base
55
+ # @return [String] Returns the string after concatenating them
56
+ def make_string(base:, keys:)
57
+ # iterates through the keys to provide a path to each array
58
+ # essentialy is the equivalent of Hash.dig(:heroku, :api, :key)
59
+ my_inject_with_count(keys) do |string, key, count|
60
+ path = dig_for_path(base, key)
61
+
62
+ value = yield(path)
63
+ value << "\n " if count < keys.length - 1
64
+ string + value
65
+ end
66
+ end
67
+
68
+ # Writes the value of string to netrc
69
+ # @param netrc_file [File] ($HOME/.netrc)
70
+ # The location of your .netrc file to be read by heroku
71
+ # @param string [String] The String to write to the netrc file
72
+ # @return void
73
+ def write_to_netrc(netrc_file: nil, string:)
74
+ netrc_file ||= File.join(Dir.home, '.netrc')
75
+ Rake.mkdir_p(File.dirname(netrc_file))
76
+ Rake.touch(netrc_file) unless File.exist?(netrc_file)
77
+
78
+ begin
79
+ File.write(netrc_file, string)
80
+ rescue Errno::EACCES => e
81
+ netrc_error(netrc_file: netrc_file, error: e)
82
+ end
83
+ end
84
+
85
+ # Adds the error to VpsCli#errors array
86
+ # @param [File] Location of netrc_file
87
+ # @param [Exception] The error to write to the array
88
+ # @return void
89
+ def netrc_error(netrc_file:, error:)
90
+ error_msg = "Unable to write to your #{netrc_file}."
91
+ VpsCli.errors << error.exception(error_msg)
92
+ end
93
+
94
+ # uses an access file via SOPS
95
+ # SOPS is an encryption tool
96
+ # @see https://github.com/mozilla/sops
97
+ # It will decrypt the file, please use a .yaml file
98
+ # @param file [File]
99
+ # The .yaml file encrypted with sops used to login to various accounts
100
+ # @path [String] JSON formatted string to traverse
101
+ # a yaml file tree
102
+ # Example: "[\"github\"][\"username\"]"
103
+ # @return [String] The value of key given in the .yaml file
104
+ def decrypt(yaml_file:, path:)
105
+ # puts all keys into a ["key"] within the array
106
+ sops_cmd = "sops -d --extract '#{path}' #{yaml_file}"
107
+
108
+ # this allows you to enter your passphrase
109
+ export_tty
110
+ # this will return in the string form the value you were looking for
111
+ stdout, _stderr, _status = Open3.capture3(sops_cmd)
112
+
113
+ stdout
114
+ end
115
+
116
+ # @param [#to_s, Array<#to_s>] The ordered path to traverse
117
+ # @return [String] Returns a path string to be able to traverse a yaml file
118
+ # @see VpsCli::Access#decrypt
119
+ def dig_for_path(*path)
120
+ path.flatten.inject('') do |final_path, node|
121
+ final_path + "[#{node.to_s.to_json}]"
122
+ end
123
+ end
124
+
125
+ # I noticed needing to export $(tty) while troubleshooting
126
+ # issues with gpg keys. It is here just in case its not in
127
+ # your zshrc / bashrc file
128
+ # @return void
129
+ def export_tty
130
+ Rake.sh('GPG_TTY=$(tty) && export GPG_TTY')
131
+ end
132
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+
5
+ # Used for copying files, making directories, copying directories etc
6
+ module FileHelper
7
+ # Helper method for making multiple directories
8
+ # @param [Dir, Array<Dir>] Creates either one, or multiple directories
9
+ def self.mkdirs(*dirs)
10
+ dirs.flatten.each { |dir| Rake.mkdir_p(dir) unless Dir.exist?(dir) }
11
+ end
12
+
13
+ # Copies files, called by copy_all
14
+ # @see VpsCli::Copy#all
15
+ # @param [Hash] opts The options to copy with
16
+ # @option opts [File] :config_file The file from the repo to be copied locally
17
+ # @option opts [File] :local_file The file that is currently present locally
18
+ # @option opts [File] :backup_file
19
+ # The file to which to save the currently present local file
20
+ # @option opts [Boolean] :interactive
21
+ # Will prompt yes or no for each file it creates
22
+ # @option opts [Boolean] :verbose Will print more info to terminal if true
23
+ def self.copy_files(opts = {})
24
+ # if there is an original dot file & no backup file in the backupdir
25
+ # Copy the dot file to the backup dir
26
+ if create_backup?(opts)
27
+ copy_file(opts[:local_file], opts[:backup_file], opts[:interactive])
28
+ end
29
+
30
+ # Copies from vps_cli/dotfiles to the location of the dot_file
31
+ copy_file(opts[:config_file], opts[:local_file], opts[:interactive])
32
+ end
33
+
34
+ # Copies directories instead of files, called by copy_all
35
+ # @see VpsCli::Copy#all
36
+ # @param [Hash] opts The options to copy with
37
+ # @option opts [File] :config_file The file from the repo to be copied locally
38
+ # @option opts [File] :dot_file The file that is currently present locally
39
+ # @option opts [File] :backup_file
40
+ # The file to which to save the currently present local file
41
+ # @option opts [Boolean] :interactive
42
+ # Will prompt yes or no for each file it creates
43
+ # @option opts [Boolean] :verbose Will print more info to terminal if true
44
+ def self.copy_dirs(opts = {})
45
+ mkdirs(opts[:local_file])
46
+
47
+ if create_backup?(opts)
48
+ copy_dir(opts[:local_file], opts[:backup_file], opts[:interactive])
49
+ end
50
+
51
+ Dir.each_child(opts[:config_file]) do |dir|
52
+ dir = File.join(opts[:config_file], dir)
53
+
54
+ # copies to local dir
55
+ copy_dir(dir, opts[:local_file], opts[:interactive])
56
+ end
57
+ end
58
+
59
+ # Checks that a backup file does not exist
60
+ # @param file [File] File to be searched for
61
+ # @param verbose [Boolean] Will print to console if verbose == true
62
+ # @return [Boolean] Returns true if the file is not found
63
+ def self.backup_file_not_found?(file, verbose = false)
64
+ return true unless File.exist?(file)
65
+
66
+ puts "#{file} exists already. No backup created." if verbose
67
+ false
68
+ end
69
+
70
+ # Helper method for determining whether or not to create a backup file
71
+ # @param opts [Hash] options hash
72
+ # @option [File] :local_file current dot file
73
+ # @option [File] :backup_file Where to back the dot file up to
74
+ # @option [Boolean] :verbose Will print to terminal if verbose == true
75
+ # @return [Boolean] Returns true if there is a dotfile that exists
76
+ # And there is no current backup_file found
77
+ def self.create_backup?(opts = {})
78
+ return false unless file_found?(opts[:local_file], opts[:verbose])
79
+ unless backup_file_not_found?(opts[:backup_file], opts[:verbose])
80
+ return false
81
+ end
82
+
83
+ true
84
+ end
85
+
86
+ # Default way of checking if the dotfile already exists
87
+ # @param file [File] File to be searched for
88
+ # @param verbose [Boolean] Will print to console if verbose == true
89
+ # @return [Boolean] Returns true if the file exists
90
+ def self.file_found?(file, verbose = false)
91
+ return true if File.exist?(file)
92
+
93
+ puts "#{file} does not exist. No backup created." if verbose
94
+ false
95
+ end
96
+
97
+ def self.retrieve_file(directory, name)
98
+ Dir.children(directory).select { |file| name == file }
99
+ end
100
+
101
+ # base method to copy a file and ask for permission prior to copying
102
+ # @see copy_files
103
+ # @see ask_permission
104
+ # @param from [File] File to copy from
105
+ # @param to [File] File to copy to
106
+ # @param config [VpsCli::Configuration] (VpsCli#configuration)
107
+ # uses the default config provided by the VpsCli module
108
+ def self.copy_file(from, to, interactive = false)
109
+ Rake.cp(from, to) if overwrite?(to, interactive)
110
+ end
111
+
112
+ # base method to copy a dir and ask for permission prior to copying
113
+ # @see copy_dirs
114
+ # @see ask_permission
115
+ # @param from [Dir] Directory to copy from
116
+ # @param to [Dir] Directory to copy to
117
+ # @param interactive [Boolean] (false) asks whether or not to create the file
118
+ def self.copy_dir(from, to, interactive = false)
119
+ mkdirs(to)
120
+ to_path = File.join(to, File.basename(from))
121
+ Rake.cp_r(from, to) if overwrite?(to_path, interactive)
122
+ end
123
+
124
+ # asks permission to copy a file
125
+ # @param file [File] The file to copy to
126
+ # @param interactive [Boolean] (false) If interactive equals false
127
+ # then automatically overwrite
128
+ # @return [Boolean]
129
+ # Decides whether or not the file will be created / overwritted
130
+ def self.overwrite?(file, interactive = false)
131
+ return true if interactive == false
132
+
133
+ string = "Attempting to overwrite file #{file}" if File.exist?(file)
134
+ string ||= "Attempting to create file #{file}"
135
+
136
+ # +string is equivalent to string.dup
137
+ string = +string + ' Is this okay? (Y/N)'
138
+
139
+ loop do
140
+ puts string
141
+ input = $stdin.gets.chomp.downcase.to_sym
142
+ return true if input == :y
143
+ return false if input == :n
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module VpsCli
7
+ # An http wrapper for github api request
8
+ class GithubHTTP
9
+ attr_accessor :uri, :title
10
+ attr_writer :token, :ssh_file
11
+
12
+ # @param uri [URI] the url to hit
13
+ # @param token [String] Github API token to use
14
+ # @param ssh_key [String] Your ssh file IE: ~/.ssh/id_rsa.pub
15
+ # @param title [String] The title of your ssh key
16
+ # Ensure it is in the format: "token 14942842859"
17
+ def initialize(uri:, token:, ssh_file:, title:)
18
+ @uri = uri
19
+ @token = token
20
+ @ssh_file = ssh_file
21
+ @ssh_key = File.read(ssh_file)
22
+ @headers = headers(token: token)
23
+ @title = title
24
+ end
25
+
26
+ # The headers need for authorization of a request
27
+ # @param token [String] (nil) Your github API token
28
+ # @see https://github.com/settings/keys
29
+ # make sure your token has write:public_key and read:public_key access
30
+ # @return [Hash] Returns a hash of headers
31
+ def headers(token:)
32
+ json = 'application/json'
33
+
34
+ { 'Content-Type' => json,
35
+ 'Accepts' => json,
36
+ 'Authorization' => token }
37
+ end
38
+
39
+ # Returns the appropriate json string to write an ssh key
40
+ # @return [String] Returns a json formatted string to write an ssh key
41
+ def ssh_json_string
42
+ { 'title' => @title,
43
+ 'key' => @ssh_key }.to_json
44
+ end
45
+
46
+ # base method for an http connection
47
+ # @yieldparam http [Net::HTTP] yields the http class
48
+ # @return Whatever the value of yield is
49
+ def start_connection
50
+ Net::HTTP.start(@uri.host, @uri.port, use_ssl: true) do |http|
51
+ yield(http)
52
+ end
53
+ end
54
+
55
+ # Pushes your public key to github
56
+ # to push your ssh key to the github
57
+ # @return Net::HTTPResponse
58
+ def push_ssh_key
59
+ get = get_request
60
+
61
+ post = post_request(data: ssh_json_string)
62
+
63
+ response = start_connection do |http|
64
+ get_response = http.request(get)
65
+
66
+ ssh_keys_json = get_response.body
67
+ return ssh_key_found_msg if ssh_key_exist?(json_string: ssh_keys_json)
68
+
69
+ http.request(post)
70
+ end
71
+
72
+ VpsCli.errors << response if response != Net::HTTPCreated
73
+
74
+ puts 'ssh key pushed to github' if response.class == Net::HTTPCreated
75
+ response
76
+ end
77
+
78
+ # @param token [String] Your github api token
79
+ # @return [Net::HTTP::Get] Returns a new get request class
80
+ def get_request
81
+ Net::HTTP::Get.new(@uri, @headers)
82
+ end
83
+
84
+ # @param data [String] The data to send in the post request, must be json
85
+ # @return [Net::HTTP::Post] Returns a new post request class
86
+ def post_request(data:)
87
+ post = Net::HTTP::Post.new(@uri, @headers)
88
+ post.body = data
89
+ post
90
+ end
91
+
92
+ # Checks if the ssh key is already found
93
+ def ssh_key_exist?(json_string:)
94
+ # just in case your ssh key has a comment in it
95
+ # keys pulled from github will not have comments
96
+ ssh_key = @ssh_key.split('==')[0].concat('==')
97
+
98
+ JSON.parse(json_string).any? do |data|
99
+ data['key'] == ssh_key
100
+ end
101
+ end
102
+
103
+ def ssh_key_found_msg
104
+ puts 'The ssh key provided is already on github, no post request made.'
105
+ end
106
+ end
107
+ end