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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +169 -0
- data/Rakefile +35 -0
- data/bin/kscript +6 -0
- data/kscript.gemspec +45 -0
- data/lib/kscript/banner.rb +12 -0
- data/lib/kscript/base.rb +50 -0
- data/lib/kscript/cli.rb +184 -0
- data/lib/kscript/logger.rb +94 -0
- data/lib/kscript/plugins/kk_apnic_utils.rb +87 -0
- data/lib/kscript/plugins/kk_cleaner_utils.rb +83 -0
- data/lib/kscript/plugins/kk_es_fingerprint_utils.rb +92 -0
- data/lib/kscript/plugins/kk_ffmpeg_utils.rb +140 -0
- data/lib/kscript/plugins/kk_ip_utils.rb +90 -0
- data/lib/kscript/plugins/kk_jenkins_utils.rb +143 -0
- data/lib/kscript/plugins/kk_kibana_utils.rb +237 -0
- data/lib/kscript/plugins/kk_lvm_utils.rb +200 -0
- data/lib/kscript/plugins/kk_optimize_utils.rb +85 -0
- data/lib/kscript/plugins/kk_portscan_utils.rb +90 -0
- data/lib/kscript/plugins/kk_projscan_utils.rb +90 -0
- data/lib/kscript/plugins/kk_rename_utils.rb +82 -0
- data/lib/kscript/plugins/kk_sh_utils.rb +112 -0
- data/lib/kscript/plugins/kk_syscheck_utils.rb +82 -0
- data/lib/kscript/plugins/kk_top_utils.rb +74 -0
- data/lib/kscript/plugins/kk_usd_utils.rb +71 -0
- data/lib/kscript/plugins/kk_wg_acl_utils.rb +95 -0
- data/lib/kscript/plugins/kk_wg_pass_utils.rb +50 -0
- data/lib/kscript/plugins.rb +32 -0
- data/lib/kscript/utils.rb +64 -0
- data/lib/kscript/version.rb +10 -0
- data/lib/kscript.rb +43 -0
- metadata +130 -0
@@ -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
|