vps_cli 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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