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,87 @@
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 KkApnicUtils < Base
12
+ attr_reader :country_sn, :cache_file
13
+
14
+ # Initialize class instance, set country code and cache file path
15
+ def initialize(country_sn = 'CN', *_args, **opts)
16
+ super(**opts.merge(service: 'kk_apnic'))
17
+ @country_sn = country_sn
18
+ @cache_file = RUBY_PLATFORM.match?(/(linux|darwin)/) ? '/tmp/apnic.txt' : 'apnic.txt'
19
+ end
20
+
21
+ # Download data from APNIC or read from cache
22
+ def download_data
23
+ if File.exist?(cache_file) && File.size?(cache_file)
24
+ logger.kinfo("Using cached data from #{cache_file}")
25
+ else
26
+ url = 'https://ftp.apnic.net/stats/apnic/delegated-apnic-latest'
27
+ response = HTTP.get(url)
28
+
29
+ raise "Failed to download the APNIC data. HTTP Status: #{response.status}" unless response.status.success?
30
+
31
+ File.write(cache_file, response.body.to_s)
32
+ logger.kinfo("Data downloaded and saved to #{cache_file}")
33
+ end
34
+ end
35
+
36
+ # Parse data and return IPv4 address ranges (CIDR format) for specified country
37
+ def parse_ip_ranges
38
+ download_data # Ensure data is downloaded first
39
+
40
+ pattern = /apnic\|#{country_sn}\|ipv4\|(?<ip>\d+\.\d+\.\d+\.\d+)\|(?<hosts>\d+)\|\d+\|allocated/mi
41
+ ip_ranges = []
42
+
43
+ File.readlines(cache_file).each do |line|
44
+ next unless line.match(pattern)
45
+
46
+ val = line.match(pattern)
47
+ netmask = calculate_netmask(val[:hosts].to_i)
48
+ ip_ranges << "#{val[:ip]}/#{netmask}"
49
+ end
50
+
51
+ logger.kinfo('IP ranges', ip_ranges: ip_ranges)
52
+ ip_ranges
53
+ end
54
+
55
+ # Calculate CIDR netmask based on number of hosts
56
+ def calculate_netmask(hosts)
57
+ # Calculate minimum CIDR netmask
58
+ 32 - Math.log2(hosts).to_i
59
+ end
60
+
61
+ def run
62
+ with_error_handling do
63
+ parse_ip_ranges
64
+ end
65
+ end
66
+
67
+ def self.arguments
68
+ '[country_code]'
69
+ end
70
+
71
+ def self.usage
72
+ "kscript apnic CN\nkscript apnic US"
73
+ end
74
+
75
+ def self.group
76
+ 'network'
77
+ end
78
+
79
+ def self.author
80
+ 'kk'
81
+ end
82
+
83
+ def self.description
84
+ 'Get APNIC IPv4 ranges for a country.'
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,83 @@
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 KkCleanerUtils < Base
12
+ DEFAULT_RETAIN_VERSIONS = 10
13
+
14
+ attr_reader :source_path, :retain_count
15
+
16
+ # Initialize the cleaner with path and retention settings
17
+ # @param source_path [String] path to source code directory
18
+ # @param retain_count [Integer] number of versions to keep
19
+ def initialize(source_path = '/data/sources/*/**', retain_count = DEFAULT_RETAIN_VERSIONS, *_args, **opts)
20
+ super(**opts.merge(service: 'kk_source_cleaner'))
21
+ @source_path = source_path
22
+ @retain_count = retain_count
23
+ end
24
+
25
+ def run
26
+ with_error_handling do
27
+ clean
28
+ end
29
+ end
30
+
31
+ # Clean old versions while keeping the specified number of recent versions
32
+ def clean
33
+ Dir.glob(@source_path).each do |app_path|
34
+ process_application(app_path)
35
+ end
36
+ end
37
+
38
+ def self.arguments
39
+ '[src_path]'
40
+ end
41
+
42
+ def self.usage
43
+ "kscript source_cleaner ~/projects/src\nkscript source_cleaner . --exclude=vendor"
44
+ end
45
+
46
+ def self.group
47
+ 'project'
48
+ end
49
+
50
+ def self.author
51
+ 'kk'
52
+ end
53
+
54
+ def self.description
55
+ 'Clean old source code versions, keep N latest.'
56
+ end
57
+
58
+ private
59
+
60
+ # Process a single application directory
61
+ # @param app_path [String] path to application directory
62
+ def process_application(app_path)
63
+ versions = Dir.glob("#{app_path}/*")
64
+ version_count = versions.length
65
+ return if version_count <= @retain_count
66
+
67
+ logger.info("Processing #{app_path}", version_count: version_count, retain: @retain_count)
68
+ cleanup_old_versions(versions, version_count)
69
+ end
70
+
71
+ # Remove old versions of an application
72
+ # @param versions [Array<String>] list of version directories
73
+ # @param total_count [Integer] total number of versions
74
+ def cleanup_old_versions(versions, total_count)
75
+ sorted_versions = versions.sort_by { |dir| File.mtime(dir) }
76
+ versions_to_delete = total_count - @retain_count
77
+ sorted_versions[0, versions_to_delete].each do |dir|
78
+ logger.info("Removing #{dir}", mtime: File.mtime(dir))
79
+ FileUtils.rm_rf(dir)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,92 @@
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 KkEsFingerprintUtils < Base
12
+ DEFAULT_CERT_PATH = 'elasticsearch.crt'
13
+
14
+ attr_reader :cert_path
15
+
16
+ # Initialize with certificate path
17
+ # @param cert_path [String] path to the certificate file
18
+ def initialize(cert_path = DEFAULT_CERT_PATH)
19
+ @cert_path = cert_path
20
+ end
21
+
22
+ def run
23
+ with_error_handling do
24
+ generate
25
+ end
26
+ end
27
+
28
+ def self.arguments
29
+ '<cert_file>'
30
+ end
31
+
32
+ def self.usage
33
+ "kscript es_fingerprint <cert_file>\nkscript es_fingerprint ./ca.crt"
34
+ end
35
+
36
+ def self.group
37
+ 'elastic'
38
+ end
39
+
40
+ def self.author
41
+ 'kk'
42
+ end
43
+
44
+ def self.description
45
+ 'Generate Elasticsearch certificate SHA256 fingerprint.'
46
+ end
47
+
48
+ private
49
+
50
+ # Generate and display the certificate fingerprint
51
+ def generate
52
+ validate_certificate_file
53
+ cert = load_certificate
54
+ fingerprint = calculate_fingerprint(cert)
55
+ display_fingerprint(fingerprint)
56
+ end
57
+
58
+ # Validate certificate file existence
59
+ def validate_certificate_file
60
+ return if File.exist?(cert_path)
61
+
62
+ raise "Certificate file not found: #{cert_path}"
63
+ end
64
+
65
+ # Load X509 certificate from file
66
+ # @return [OpenSSL::X509::Certificate] loaded certificate
67
+ def load_certificate
68
+ OpenSSL::X509::Certificate.new(File.read(cert_path))
69
+ end
70
+
71
+ # Calculate SHA256 fingerprint
72
+ # @param cert [OpenSSL::X509::Certificate] certificate to process
73
+ # @return [String] formatted fingerprint
74
+ def calculate_fingerprint(cert)
75
+ raw_fingerprint = OpenSSL::Digest::SHA256.hexdigest(cert.to_der)
76
+ format_fingerprint(raw_fingerprint)
77
+ end
78
+
79
+ # Format fingerprint with colons
80
+ # @param fingerprint [String] raw fingerprint
81
+ # @return [String] formatted fingerprint
82
+ def format_fingerprint(fingerprint)
83
+ fingerprint.scan(/../).join(':').upcase
84
+ end
85
+
86
+ # Display the formatted fingerprint
87
+ # @param fingerprint [String] formatted fingerprint to display
88
+ def display_fingerprint(fingerprint)
89
+ logger.kinfo(fingerprint)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,140 @@
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 KkFfmpegUtils < Base
12
+ def run
13
+ with_error_handling do
14
+ install
15
+ end
16
+ end
17
+
18
+ def install
19
+ logger.kinfo('FFmpeg installer executed.')
20
+ end
21
+
22
+ def self.arguments
23
+ '[version]'
24
+ end
25
+
26
+ def self.usage
27
+ "kscript ffmpeg 6.0\nkscript ffmpeg latest"
28
+ end
29
+
30
+ def self.group
31
+ 'media'
32
+ end
33
+
34
+ def self.author
35
+ 'kk'
36
+ end
37
+
38
+ def self.description
39
+ 'Install and verify FFmpeg on Linux.'
40
+ end
41
+
42
+ private
43
+
44
+ # Detect OS family and version
45
+ # @return [Hash] OS family and version information
46
+ def detect_os_info
47
+ content = File.read('/etc/os-release')
48
+ {
49
+ family: detect_os_family(content),
50
+ version: detect_os_version(content)
51
+ }
52
+ end
53
+
54
+ # Detect OS family from os-release content
55
+ # @param content [String] contents of os-release file
56
+ # @return [String] OS family name
57
+ def detect_os_family(content)
58
+ if content =~ /ID_LIKE=.*rhel|centos|fedora/i || content =~ /ID=.*(rhel|centos|rocky|alma|fedora)/i
59
+ 'redhat'
60
+ elsif content =~ /ID_LIKE=.*debian/i || content =~ /ID=.*(debian|ubuntu)/i
61
+ 'debian'
62
+ else
63
+ 'unknown'
64
+ end
65
+ end
66
+
67
+ # Detect OS version from os-release content
68
+ # @param content [String] contents of os-release file
69
+ # @return [Integer] major version number
70
+ def detect_os_version(content)
71
+ version_str = content[/VERSION_ID="?([\d.]+)"?/, 1] || '0'
72
+ version_str.split('.').first.to_i
73
+ end
74
+
75
+ # Update system package lists
76
+ def update_system
77
+ logger.kinfo('👉 Updating system packages...')
78
+ case @os_info[:family]
79
+ when 'redhat'
80
+ run_command('sudo yum update -y')
81
+ when 'debian'
82
+ run_command('sudo apt update -y')
83
+ end
84
+ end
85
+
86
+ # Install prerequisite repositories
87
+ def install_prerequisites
88
+ return unless @os_info[:family] == 'redhat'
89
+
90
+ install_epel
91
+ install_rpm_fusion if @os_info[:version].between?(7, 9)
92
+ end
93
+
94
+ # Install EPEL repository
95
+ def install_epel
96
+ logger.kinfo('👉 Installing EPEL repository...')
97
+ run_command('sudo yum install -y epel-release')
98
+ end
99
+
100
+ # Install RPM Fusion repository
101
+ def install_rpm_fusion
102
+ logger.kinfo("👉 Installing RPM Fusion repository for EL#{@os_info[:version]}...")
103
+ run_command("sudo yum install -y https://download1.rpmfusion.org/free/el/rpmfusion-free-release-#{@os_info[:version]}.noarch.rpm")
104
+ end
105
+
106
+ # Install FFmpeg packages
107
+ def install_ffmpeg
108
+ logger.kinfo('👉 Installing FFmpeg...')
109
+ case @os_info[:family]
110
+ when 'redhat'
111
+ run_command('sudo yum install -y ffmpeg ffmpeg-devel')
112
+ when 'debian'
113
+ run_command('sudo apt install -y ffmpeg')
114
+ else
115
+ fail_with_error('Unsupported OS')
116
+ end
117
+ end
118
+
119
+ # Verify FFmpeg installation
120
+ def verify_installation
121
+ logger.kinfo('👉 Verifying FFmpeg installation...')
122
+ run_command('ffmpeg -version')
123
+ logger.kinfo('✅ FFmpeg installation completed successfully!')
124
+ end
125
+
126
+ # Execute shell command
127
+ # @param cmd [String] command to execute
128
+ def run_command(cmd)
129
+ logger.kinfo("👉 Running: #{cmd}")
130
+ system(cmd) || fail_with_error("Command failed: #{cmd}")
131
+ end
132
+
133
+ # Display error and exit
134
+ # @param msg [String] error message
135
+ def fail_with_error(msg)
136
+ logger.kerror("❌ #{msg}")
137
+ exit 1
138
+ end
139
+ end
140
+ 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
+
10
+ require 'http'
11
+ require 'json'
12
+
13
+ module Kscript
14
+ class KkIpUtils < Base
15
+ IP_API_BASE_URL = 'http://ip-api.com/json'
16
+ IP_CHECK_URL = 'https://api.ipify.org?format=json'
17
+
18
+ attr_reader :ip_address
19
+
20
+ def initialize(ip_address = nil, *_args, **opts)
21
+ super(**opts.merge(service: 'kk_ip_api'))
22
+ @ip_address = ip_address || fetch_public_ip
23
+ end
24
+
25
+ def run
26
+ with_error_handling do
27
+ fetch_location
28
+ end
29
+ end
30
+
31
+ def fetch_location
32
+ validate_ip_address!
33
+ response = make_api_request
34
+ handle_response(response)
35
+ end
36
+
37
+ def self.arguments
38
+ '<ip_address>'
39
+ end
40
+
41
+ def self.usage
42
+ "kscript ip <ip_address>\nkscript ip 8.8.8.8"
43
+ end
44
+
45
+ def self.group
46
+ 'network'
47
+ end
48
+
49
+ def self.author
50
+ 'kk'
51
+ end
52
+
53
+ def self.description
54
+ 'Query IP geolocation and ISP info.'
55
+ end
56
+
57
+ private
58
+
59
+ def fetch_public_ip
60
+ response = HTTP.get(IP_CHECK_URL)
61
+ raise "Failed to detect public IP: #{response.status}" unless response.status.success?
62
+
63
+ data = JSON.parse(response.body.to_s)
64
+ logger.info("Detected public IP: #{data['ip']}")
65
+ data['ip']
66
+ end
67
+
68
+ def validate_ip_address!
69
+ return if valid_ip_format?
70
+
71
+ raise ArgumentError, 'Invalid IP address format'
72
+ end
73
+
74
+ def valid_ip_format?
75
+ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match?(@ip_address)
76
+ end
77
+
78
+ def make_api_request
79
+ HTTP.get("#{IP_API_BASE_URL}/#{@ip_address}")
80
+ end
81
+
82
+ def handle_response(response)
83
+ if response.status.success?
84
+ logger.kinfo('IP location result', data: response.parse(:json))
85
+ else
86
+ logger.kerror("API request failed: #{response.status}")
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,143 @@
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 'http'
10
+ require 'base64'
11
+ require 'rexml/document'
12
+ require 'json'
13
+ require 'fileutils'
14
+ require 'kscript/base'
15
+
16
+ module Kscript
17
+ class KkJenkinsUtils < Base
18
+ def run
19
+ with_error_handling do
20
+ logger.kinfo('Jenkins job manager executed.')
21
+ end
22
+ end
23
+
24
+ def initialize(jenkins_url, user, password)
25
+ @jenkins_url = jenkins_url
26
+ @user = user
27
+ @password = password
28
+ @auth_header = "Basic #{Base64.strict_encode64("#{@user}:#{@password}")}"
29
+ @output = $stdout
30
+ @output.sync = true
31
+ end
32
+
33
+ def export_all_jobs
34
+ FileUtils.mkdir_p('jobs')
35
+
36
+ job_names = get_all_job_names
37
+ job_names.each do |job_name|
38
+ config_xml = export_job(job_name)
39
+ if config_xml
40
+ File.write("jobs/#{job_name}.xml", config_xml)
41
+ logger.kinfo("Exported job: #{job_name}")
42
+ end
43
+ end
44
+ end
45
+
46
+ def import_job_from_file(job_name)
47
+ file_path = "jobs/#{job_name}.xml"
48
+ if File.exist?(file_path)
49
+ config_xml = File.read(file_path)
50
+ import_or_update_job(job_name, config_xml)
51
+ else
52
+ logger.kerror("Job file #{file_path} does not exist!")
53
+ end
54
+ end
55
+
56
+ def import_all_jobs_from_files
57
+ job_files = Dir.glob('jobs/*.xml')
58
+ job_files.each do |file_path|
59
+ job_name = File.basename(file_path, '.xml')
60
+ logger.kinfo("Importing or updating job: #{job_name}")
61
+ config_xml = File.read(file_path)
62
+ import_or_update_job(job_name, config_xml)
63
+ end
64
+ end
65
+
66
+ def get_all_job_names
67
+ url = "#{@jenkins_url}/api/json?tree=jobs[name]"
68
+ response = HTTP.get(url, headers: { 'Authorization' => @auth_header })
69
+ if response.status.success?
70
+ jobs = JSON.parse(response.body.to_s)['jobs']
71
+ jobs.map { |job| job['name'] }
72
+ else
73
+ logger.kerror("Error fetching job list: #{response.status}")
74
+ []
75
+ end
76
+ rescue StandardError => e
77
+ logger.kerror("Exception fetching job list: #{e.message}")
78
+ []
79
+ end
80
+
81
+ def export_job(job_name)
82
+ url = "#{@jenkins_url}/job/#{job_name}/config.xml"
83
+ response = HTTP.get(url, headers: { 'Authorization' => @auth_header })
84
+ return response.body.to_s if response.status.success?
85
+
86
+ logger.kerror("Error exporting job #{job_name}: #{response.status}")
87
+ nil
88
+ rescue StandardError => e
89
+ logger.kerror("Exception exporting job #{job_name}: #{e.message}")
90
+ nil
91
+ end
92
+
93
+ def import_or_update_job(job_name, config_xml)
94
+ url = "#{@jenkins_url}/job/#{job_name}/config.xml"
95
+ logger.kinfo(url)
96
+ begin
97
+ logger.kinfo("Creating new job #{job_name}")
98
+ create_new_job(job_name, config_xml)
99
+ rescue StandardError
100
+ logger.kinfo("Updating existing job #{job_name}")
101
+ HTTP.put(url, body: config_xml, headers: {
102
+ 'Authorization' => @auth_header,
103
+ 'Content-Type' => 'application/xml'
104
+ })
105
+ end
106
+ end
107
+
108
+ def self.arguments
109
+ '[subcommand] [options]'
110
+ end
111
+
112
+ def self.usage
113
+ "kscript jenkins list --host=jenkins.local\nkscript jenkins trigger --job=build"
114
+ end
115
+
116
+ def self.group
117
+ 'ci'
118
+ end
119
+
120
+ def self.author
121
+ 'kk'
122
+ end
123
+
124
+ def self.description
125
+ 'Jenkins job export/import automation.'
126
+ end
127
+
128
+ private
129
+
130
+ def create_new_job(job_name, config_xml)
131
+ url = "#{@jenkins_url}/createItem?name=#{job_name}"
132
+ response = HTTP.post(url, body: config_xml, headers: {
133
+ 'Authorization' => @auth_header,
134
+ 'Content-Type' => 'application/xml'
135
+ })
136
+ if response.status.success?
137
+ logger.kinfo("Successfully created new job #{job_name}")
138
+ else
139
+ logger.kerror("Failed to create job #{job_name}: #{response.status}")
140
+ end
141
+ end
142
+ end
143
+ end