kscript 0.1.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.
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+ require 'securerandom'
10
+
11
+ module Kscript
12
+ class KkKibanaUtils < Base
13
+ def run
14
+ with_error_handling do
15
+ logger.kinfo('Kibana utils executed.')
16
+ end
17
+ end
18
+
19
+ def initialize(project_name, project_env, base_url, username, password)
20
+ @base_url = base_url
21
+ @username = username
22
+ @password = password
23
+ @project_name = project_name
24
+ @project_env = project_env
25
+ @space_name = "#{project_name}-#{project_env}"
26
+
27
+ # Check and create space if it doesn't exist
28
+ create_space unless space_exists?
29
+ end
30
+
31
+ # Check if the space exists
32
+ def space_exists?
33
+ url = "#{@base_url}/api/spaces/space/#{@space_name}"
34
+ response = client.get(url, headers: kbn_headers)
35
+ response.status.success?
36
+ rescue StandardError => e
37
+ logger.kerror("Error checking space existence: #{e.message}")
38
+ false
39
+ end
40
+
41
+ # Create a new space
42
+ def create_space
43
+ url = "#{@base_url}/api/spaces/space"
44
+ body = {
45
+ id: @space_name,
46
+ name: @space_name,
47
+ description: "Space for #{@project_name} #{@project_env} environment",
48
+ color: "##{SecureRandom.hex(3)}", # Random color
49
+ initials: "#{@project_name[0]}#{@project_env[0]}".upcase
50
+ }
51
+
52
+ response = client.post(url, json: body, headers: kbn_headers)
53
+ if response.status.success?
54
+ logger.kinfo("Space '#{@space_name}' created successfully!")
55
+ else
56
+ logger.kerror("Failed to create space '#{@space_name}': #{response.body}")
57
+ end
58
+ rescue StandardError => e
59
+ logger.kerror("Error creating space: #{e.message}")
60
+ end
61
+
62
+ # Return the HTTP client with authentication
63
+ def client
64
+ @client ||= HTTP.basic_auth(user: @username, pass: @password)
65
+ end
66
+
67
+ # Fetch all indices from Kibana
68
+ def indices
69
+ response = client.get("#{@base_url}/api/index_management/indices", headers: kbn_headers)
70
+ handle_response(response) { |body| JSON.parse(body).map { |item| item['name'] } }
71
+ end
72
+
73
+ # Delete space all index
74
+ def delete_dataviews
75
+ url = "#{@base_url}/s/#{@space_name}/api/content_management/rpc/delete"
76
+ get_dataviews.each do |index|
77
+ body = {
78
+ 'contentTypeId' => 'index-pattern',
79
+ 'id' => index['id'],
80
+ 'options' => {
81
+ 'force' => true
82
+ },
83
+ 'version' => 1
84
+ }
85
+ client.post(url, json: body, headers: kbn_headers)
86
+ end
87
+ end
88
+
89
+ # Add a new index to Kibana
90
+ def add_index(index_name)
91
+ uuid = SecureRandom.uuid
92
+ url = "#{@base_url}/s/#{@space_name}/api/content_management/rpc/create"
93
+ body = index_body(index_name, uuid)
94
+
95
+ response = client.post(url, json: body, headers: kbn_headers)
96
+ if response.status.success?
97
+ logger.kinfo("#{index_name} Index creation successful!")
98
+ else
99
+ handle_error(response, index_name)
100
+ end
101
+ end
102
+
103
+ # Add all relevant indices to Kibana
104
+ def add_all_index
105
+ delete_dataviews
106
+ indices.each do |index|
107
+ unless index =~ /#{@project_name}/i && index =~ /#{@project_env}/i && index =~ /#{Time.now.strftime('%Y.%m.%d')}/
108
+ next
109
+ end
110
+
111
+ add_index(index.gsub(/-\d{4}\.\d{2}\.\d{2}/, ''))
112
+ end
113
+ end
114
+
115
+ def create_role
116
+ url = "#{@base_url}/api/security/role/#{@project_name}?createOnly=true"
117
+ request_body = {
118
+ 'elasticsearch' => {
119
+ 'cluster' => [],
120
+ 'indices' => [
121
+ {
122
+ 'names' => ["*#{@project_name}*"],
123
+ 'privileges' => ['read']
124
+ }
125
+ ],
126
+ 'run_as' => []
127
+ },
128
+ 'kibana' => [
129
+ {
130
+ 'spaces' => ["#{@project_name}-prod", "#{@project_name}-uat"],
131
+ 'base' => [],
132
+ 'feature' => {
133
+ 'discover' => ['read']
134
+ }
135
+ }
136
+ ]
137
+ }.to_json
138
+ client.put(url, body: request_body, headers: kbn_headers)
139
+ logger.kinfo("Create #{@project_name} user role sucessed!")
140
+ end
141
+
142
+ def create_user
143
+ url = "#{@base_url}/internal/security/users/#{@project_name}"
144
+ request_body = {
145
+ 'password' => '123456',
146
+ 'username' => @project_name,
147
+ 'full_name' => @project_name,
148
+ 'email' => "#{@project_name}@devops.io",
149
+ 'roles' => [@project_name]
150
+ }.to_json
151
+ client.post(url, body: request_body, headers: kbn_headers)
152
+ logger.kinfo("Create #{@project_name} user sucessed!")
153
+ end
154
+
155
+ def self.arguments
156
+ '[subcommand] [options]'
157
+ end
158
+
159
+ def self.usage
160
+ "kscript kibana export --host=localhost --index=log-*\nkscript kibana import --file=dashboard.json"
161
+ end
162
+
163
+ def self.group
164
+ 'elastic'
165
+ end
166
+
167
+ def self.author
168
+ 'kk'
169
+ end
170
+
171
+ def self.description
172
+ 'Kibana automation: space, index, user, role management.'
173
+ end
174
+
175
+ private
176
+
177
+ # Fetch all index id
178
+ def get_dataviews
179
+ url = "#{@base_url}/s/#{@space_name}/api/content_management/rpc/search"
180
+ # Data payload
181
+ body = {
182
+ 'contentTypeId' => 'index-pattern',
183
+ 'query' => { 'limit' => 10_000 },
184
+ 'options' => { 'fields' => %w[title type typeMeta name] },
185
+ 'version' => 1
186
+ }
187
+ JSON.parse(client.post(url, json: body, headers: kbn_headers))['result']['result']['hits']
188
+ end
189
+
190
+ # Construct the body for creating an index
191
+ def index_body(index_name, uuid)
192
+ {
193
+ "contentTypeId": 'index-pattern',
194
+ "data": {
195
+ "fieldAttrs": '{}',
196
+ "title": "#{index_name}*",
197
+ "timeFieldName": '@timestamp',
198
+ "sourceFilters": '[]',
199
+ "fields": '[]',
200
+ "fieldFormatMap": '{}',
201
+ "runtimeFieldMap": '{}',
202
+ "name": index_name,
203
+ "allowHidden": false
204
+ },
205
+ "options": {
206
+ "id": uuid,
207
+ "overwrite": false
208
+ },
209
+ "version": 1
210
+ }
211
+ end
212
+
213
+ # Generate the required headers, including kbn-xsrf
214
+ def kbn_headers
215
+ { 'kbn-xsrf' => 'true' }
216
+ end
217
+
218
+ # Handle the response from Kibana
219
+ def handle_response(response)
220
+ if response.status.success?
221
+ yield response.body
222
+ else
223
+ handle_error(response)
224
+ end
225
+ end
226
+
227
+ # Handle errors from Kibana API responses
228
+ def handle_error(response, index_name = nil)
229
+ error_message = "Error: #{response.status} - #{response.body}"
230
+ if index_name
231
+ logger.kerror("#{index_name} Failed to create index. #{error_message}")
232
+ else
233
+ logger.kerror("Error fetching indices: #{error_message}")
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+
10
+ module Kscript
11
+ class KkLvmUtils < Base
12
+ DEFAULT_CONFIG = {
13
+ device: '/dev/sdb',
14
+ volume_group: 'vg_data',
15
+ logical_volume: 'lv_data',
16
+ mount_point: '/data'
17
+ }.freeze
18
+
19
+ attr_reader :config
20
+
21
+ # Initialize the LVM mounter with configuration
22
+ # @param config [Hash] configuration options
23
+ def initialize(config = {}, *_args, **_opts)
24
+ @config = DEFAULT_CONFIG.merge(config)
25
+ end
26
+
27
+ def run
28
+ with_error_handling do
29
+ setup
30
+ end
31
+ end
32
+
33
+ def setup
34
+ validate_device
35
+ ensure_lvm_tools_installed
36
+ setup_physical_volume
37
+ setup_volume_group
38
+ setup_logical_volume
39
+ format_and_mount_volume
40
+ update_fstab
41
+ display_mount_status
42
+ end
43
+
44
+ def self.arguments
45
+ '<device> <mount_point>'
46
+ end
47
+
48
+ def self.usage
49
+ "kscript lvm /dev/sda2 /mnt/data\nkscript lvm /dev/vg0/lv_home /mnt/home"
50
+ end
51
+
52
+ def self.group
53
+ 'system'
54
+ end
55
+
56
+ def self.author
57
+ 'kk'
58
+ end
59
+
60
+ def self.description
61
+ 'Mount and manage Linux LVM volumes.'
62
+ end
63
+
64
+ private
65
+
66
+ # Validate device existence
67
+ def validate_device
68
+ return if File.blockdev?(config[:device])
69
+
70
+ fail_with_error("Device #{config[:device]} does not exist")
71
+ end
72
+
73
+ # Ensure LVM tools are installed
74
+ def ensure_lvm_tools_installed
75
+ return if system('which pvcreate > /dev/null 2>&1')
76
+
77
+ logger.kinfo('🔧 Installing LVM tools...')
78
+ case detect_os_family
79
+ when 'redhat'
80
+ run_command('yum install -y lvm2')
81
+ when 'debian'
82
+ run_command('apt update && apt install -y lvm2')
83
+ else
84
+ fail_with_error('Unsupported OS: cannot install lvm2')
85
+ end
86
+ end
87
+
88
+ # Set up physical volume
89
+ def setup_physical_volume
90
+ return if physical_volume_exists?
91
+
92
+ run_command("pvcreate #{config[:device]}")
93
+ end
94
+
95
+ # Set up volume group
96
+ def setup_volume_group
97
+ return if volume_group_exists?
98
+
99
+ run_command("vgcreate #{config[:volume_group]} #{config[:device]}")
100
+ end
101
+
102
+ # Set up logical volume
103
+ def setup_logical_volume
104
+ return if logical_volume_exists?
105
+
106
+ run_command("lvcreate -l 100%FREE -n #{config[:logical_volume]} #{config[:volume_group]}")
107
+ end
108
+
109
+ # Format and mount the volume
110
+ def format_and_mount_volume
111
+ format_volume unless volume_formatted?
112
+ mount_volume unless volume_mounted?
113
+ end
114
+
115
+ # Update /etc/fstab for persistent mounting
116
+ def update_fstab
117
+ uuid = volume_uuid
118
+ fstab_line = generate_fstab_entry(uuid)
119
+
120
+ return if fstab_contains_uuid?(uuid)
121
+
122
+ logger.kinfo('👉 Updating /etc/fstab...')
123
+ File.open('/etc/fstab', 'a') { |f| f.puts(fstab_line) }
124
+ end
125
+
126
+ # Display current mount status
127
+ def display_mount_status
128
+ logger.kinfo("✅ Volume mounted successfully at #{config[:mount_point]}:")
129
+ system("df -h #{config[:mount_point]}")
130
+ end
131
+
132
+ # Helper methods
133
+ def detect_os_family
134
+ content = File.read('/etc/os-release')
135
+ if content =~ /ID_LIKE=.*rhel|centos|fedora/i || content =~ /ID=.*(rhel|centos|rocky|alma|fedora)/i
136
+ 'redhat'
137
+ elsif content =~ /ID_LIKE=.*debian/i || content =~ /ID=.*(debian|ubuntu)/i
138
+ 'debian'
139
+ else
140
+ 'unknown'
141
+ end
142
+ end
143
+
144
+ def run_command(cmd)
145
+ logger.kinfo("👉 Running: #{cmd}")
146
+ system(cmd) || fail_with_error("Command failed: #{cmd}")
147
+ end
148
+
149
+ def fail_with_error(msg)
150
+ logger.kerror("❌ #{msg}")
151
+ exit 1
152
+ end
153
+
154
+ def physical_volume_exists?
155
+ system("pvs #{config[:device]} > /dev/null 2>&1")
156
+ end
157
+
158
+ def volume_group_exists?
159
+ system("vgs #{config[:volume_group]} > /dev/null 2>&1")
160
+ end
161
+
162
+ def logical_volume_exists?
163
+ system("lvs /dev/#{config[:volume_group]}/#{config[:logical_volume]} > /dev/null 2>&1")
164
+ end
165
+
166
+ def volume_formatted?
167
+ `blkid #{logical_volume_path}` =~ /TYPE="xfs"/
168
+ end
169
+
170
+ def volume_mounted?
171
+ `mount | grep #{config[:mount_point]}`.include?(config[:mount_point])
172
+ end
173
+
174
+ def format_volume
175
+ run_command("mkfs.xfs #{logical_volume_path}")
176
+ end
177
+
178
+ def mount_volume
179
+ run_command("mkdir -p #{config[:mount_point]}")
180
+ run_command("mount #{logical_volume_path} #{config[:mount_point]}")
181
+ end
182
+
183
+ def logical_volume_path
184
+ "/dev/#{config[:volume_group]}/#{config[:logical_volume]}"
185
+ end
186
+
187
+ def volume_uuid
188
+ uuid = `blkid #{logical_volume_path} | awk '{print $2}' | tr -d '"'`
189
+ uuid.strip
190
+ end
191
+
192
+ def fstab_contains_uuid?(uuid)
193
+ File.read('/etc/fstab').include?(uuid)
194
+ end
195
+
196
+ def generate_fstab_entry(_uuid)
197
+ "#{logical_volume_path} #{config[:mount_point]} xfs defaults 0 0"
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+ require 'open3'
10
+
11
+ module Kscript
12
+ class KkOptimizeUtils < Base
13
+ def run
14
+ with_error_handling do
15
+ optimize
16
+ end
17
+ end
18
+
19
+ def optimize
20
+ logger.kinfo('🔧 Starting macOS system optimization...')
21
+
22
+ # Lower priority for OrbStack Helper
23
+ orb_pid = `pgrep -f "OrbStack Helper"`.strip
24
+ unless orb_pid.empty?
25
+ logger.kinfo("🛑 Found OrbStack Helper (PID: #{orb_pid}), lowering priority...")
26
+ system("sudo renice +15 #{orb_pid}")
27
+ logger.kinfo('✅ Priority lowered')
28
+ end
29
+
30
+ # Close background apps
31
+ apps = ['Telegram', 'Google Chrome']
32
+ apps.each do |app|
33
+ if system("pgrep -x \"#{app}\" > /dev/null")
34
+ logger.kinfo("🛑 Closing #{app}...")
35
+ system("osascript -e 'tell application \"#{app}\" to quit'")
36
+ end
37
+ end
38
+
39
+ # Purge memory cache
40
+ logger.kinfo('🧹 Purging memory cache...')
41
+ system('sudo purge')
42
+ logger.kinfo('✅ Memory cache purged')
43
+
44
+ # Enable Low Power Mode (macOS 12+)
45
+ macos_version = `sw_vers -productVersion`.strip
46
+ if macos_version.split('.').first.to_i >= 12
47
+ logger.kinfo('⚡ Enabling Low Power Mode...')
48
+ system('pmset -a lowpowermode 1')
49
+ logger.kinfo('✅ Low Power Mode enabled')
50
+ else
51
+ logger.kwarn("⚠️ Your macOS version (#{macos_version}) does not support Low Power Mode, skipping.")
52
+ end
53
+
54
+ # Show CPU temperature (requires osx-cpu-temp)
55
+ if system('which osx-cpu-temp > /dev/null')
56
+ temp = `osx-cpu-temp`.strip
57
+ logger.kinfo("🌡 Current CPU Temperature: #{temp}")
58
+ else
59
+ logger.kinfo("ℹ️ Install 'osx-cpu-temp' to see CPU temperature (brew install osx-cpu-temp)")
60
+ end
61
+
62
+ logger.kinfo('🎉 Optimization complete. Monitor your system for improvements!')
63
+ end
64
+
65
+ def self.arguments
66
+ '[subcommand] [options]'
67
+ end
68
+
69
+ def self.usage
70
+ "kscript optimize clean\nkscript optimize speedup"
71
+ end
72
+
73
+ def self.group
74
+ 'macos'
75
+ end
76
+
77
+ def self.author
78
+ 'kk'
79
+ end
80
+
81
+ def self.description
82
+ 'Optimize macOS system performance.'
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+ require 'socket'
10
+ require 'timeout'
11
+
12
+ module Kscript
13
+ class KkPortscanUtils < Base
14
+ attr_reader :host, :ports, :thread_count
15
+
16
+ # Initialize the scanner with target host and port range
17
+ # @param host [String] target host to scan
18
+ # @param ports [Array<Integer>] list of ports to scan
19
+ # @param thread_count [Integer] number of concurrent threads
20
+ def initialize(target = nil, ports = (1..1024), *_args, **opts)
21
+ super(**opts.merge(service: 'kk_port_scanner'))
22
+ @target = target
23
+ @ports = ports.is_a?(Range) ? ports : (1..1024)
24
+ end
25
+
26
+ def run
27
+ with_error_handling do
28
+ scan
29
+ end
30
+ end
31
+
32
+ # Execute port scanning using multiple threads
33
+ def scan
34
+ msg = "Scanning #{@target} ports #{@ports}"
35
+ if human_output?
36
+ puts msg
37
+ else
38
+ logger.kinfo(msg)
39
+ end
40
+ @ports.each do |port|
41
+ Socket.tcp(@target, port, connect_timeout: 0.5) do |_sock|
42
+ if human_output?
43
+ puts "Port #{port} is open"
44
+ else
45
+ logger.kinfo('Port open', port: port)
46
+ logger.kinfo("Port #{port} is open")
47
+ end
48
+ end
49
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
50
+ # closed or filtered
51
+ end
52
+ end
53
+
54
+ def self.description
55
+ 'Scan open ports on a target host.'
56
+ end
57
+
58
+ def self.arguments
59
+ '<target_host>'
60
+ end
61
+
62
+ def self.usage
63
+ "kscript portscan 192.168.1.1\nkscript portscan example.com --ports=22,80,443"
64
+ end
65
+
66
+ def self.group
67
+ 'network'
68
+ end
69
+
70
+ def self.author
71
+ 'kk'
72
+ end
73
+
74
+ private
75
+
76
+ # Scan a single port for open status
77
+ # @param port [Integer] port number to scan
78
+ def scan_port(port)
79
+ Timeout.timeout(1) do # Set connection timeout to 1 second
80
+ s = TCPSocket.new(@host, port)
81
+ logger.kinfo("Port #{port} is open")
82
+ s.close
83
+ end
84
+ rescue Timeout::Error
85
+ # Connection timeout, ignore
86
+ rescue StandardError => e
87
+ logger.kerror("Error scanning port #{port}: #{e.message}")
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+ require 'json'
10
+
11
+ module Kscript
12
+ class KkProjscanUtils < Base
13
+ def initialize(src_path = nil, *_args, **opts)
14
+ super(**opts.merge(service: 'kk_project_scanner'))
15
+ @src_path = src_path || File.expand_path('~/projects/src')
16
+ end
17
+
18
+ def run
19
+ with_error_handling do
20
+ scan_and_display
21
+ end
22
+ end
23
+
24
+ def scan_and_display
25
+ ensure_directory_exists
26
+ projects = scan_projects
27
+ display_projects(projects)
28
+ end
29
+
30
+ def self.description
31
+ 'Scan and list all git projects in a directory.'
32
+ end
33
+
34
+ def self.arguments
35
+ '[src_path]'
36
+ end
37
+
38
+ def self.usage
39
+ "kscript projscan ~/projects/src\nkscript projscan /opt --type=go"
40
+ end
41
+
42
+ def self.group
43
+ 'project'
44
+ end
45
+
46
+ def self.author
47
+ 'kk'
48
+ end
49
+
50
+ private
51
+
52
+ def ensure_directory_exists
53
+ return if Dir.exist?(@src_path)
54
+
55
+ logger.kerror("Source directory not found: #{@src_path}")
56
+ exit 1
57
+ end
58
+
59
+ def scan_projects
60
+ projects = []
61
+ Dir.glob(File.join(@src_path, '*')).sort.each do |path|
62
+ next unless File.directory?(path)
63
+ next unless git_project?(path)
64
+
65
+ project_name = File.basename(path)
66
+ projects << create_project_entry(project_name, path)
67
+ end
68
+ projects
69
+ end
70
+
71
+ def git_project?(path)
72
+ File.directory?(File.join(path, '.git'))
73
+ end
74
+
75
+ def create_project_entry(name, path)
76
+ {
77
+ name: name,
78
+ rootPath: path,
79
+ paths: [],
80
+ tags: [],
81
+ enabled: true
82
+ }
83
+ end
84
+
85
+ def display_projects(projects)
86
+ logger.kinfo('Scanned projects', count: projects.size)
87
+ logger.kinfo('Projects', projects: JSON.pretty_generate(projects))
88
+ end
89
+ end
90
+ end