netcloak 1.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 450227d25f5ae1b6f518be99fcb4076d40a63a0cf28e32e55f5aa6d9c70c43b1
4
+ data.tar.gz: 34060bcf9d6d8228ea759d9613e42537bd3bfd52cb84c5a6614b22d440f65756
5
+ SHA512:
6
+ metadata.gz: f80a4da6e2a1ab18362427a3af1f780cf02975282fffd5878d1ab707a5c9263d95b1d4e273af772a0da9eb27e0f2ab1af192cad6b943421b8cc16b4ca280303f
7
+ data.tar.gz: 535f7fa7835badda8265a6ca6af1abd8b6e58e97881a55241ff5842433af2067e1c93fd1b1278a71e817af3b540ff7606f0e41d7044ce9b2acbbe07bcfac2728
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # NetCloak
2
+
3
+ NetCloak is a terminal-based user interface (TUI) wrapper for OpenVPN, providing a seamless and intuitive way to manage and monitor your VPN connections. Designed for efficiency, NetCloak enables users to select, connect, and monitor OpenVPN tunnels in real time with an interactive interface inspired by btop++.
4
+
5
+ ## Features
6
+
7
+ - **TUI-Based VPN Management**: Easily select and start OpenVPN configurations from a simple terminal interface.
8
+ - **Real-Time Monitoring**: Track VPN connection status, latency, and tunnel uptime dynamically.
9
+ - **Interactive Controls**:
10
+ - **[R]** Reconnect
11
+ - **[D]** Disconnect
12
+ - **[Q]** Quit
13
+ - **Latency Visualization**: Displays latency trends in a bar graph format for quick insights.
14
+ - **Minimal Dependencies**: Runs using Curses and OpenVPN, keeping the setup lightweight.
15
+ - **Future Proxy Support**: Proxy chains functionality will be added in future updates.
16
+
17
+ ## Installation
18
+
19
+ ### Prerequisites
20
+
21
+ Ensure you have the following installed on your system:
22
+
23
+ - **OpenVPN**
24
+ - **Ruby** (latest version recommended)
25
+ - **Curses** gem for Ruby
26
+
27
+ ### Install OpenVPN
28
+
29
+ #### Debian-based (Ubuntu, Debian, etc.)
30
+ ```sh
31
+ sudo apt install openvpn -y
32
+ ```
33
+
34
+ #### Fedora
35
+ ```sh
36
+ sudo dnf install openvpn -y
37
+ ```
38
+
39
+ #### CentOS
40
+ ```sh
41
+ sudo yum install openvpn -y
42
+ ```
43
+
44
+ #### Arch Linux
45
+ ```sh
46
+ sudo pacman -S openvpn --noconfirm
47
+ ```
48
+
49
+ #### openSUSE
50
+ ```sh
51
+ sudo zypper install openvpn
52
+ ```
53
+
54
+ ### Setup
55
+
56
+ ```sh
57
+ # Clone the repository
58
+ git clone https://github.com/cilliapwndev/NetCloak.git
59
+ cd netcloak
60
+
61
+ # Install dependencies
62
+ gem install curses
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ 1. **Navigate to your OpenVPN configuration directory**
68
+ ```sh
69
+ cd /path/to/ovpn/configs
70
+ ```
71
+ 2. **Run NetCloak**
72
+ ```sh
73
+ ruby netcloak.rb
74
+ ```
75
+ 3. **Select an OpenVPN configuration file**
76
+ 4. **Monitor VPN performance**
77
+ 5. **Use interactive keys to manage the connection**
78
+
79
+ ## Demo
80
+
81
+ <img src="https://i.imgur.com/UvHNBof.gif"/>
82
+
83
+ ## Roadmap
84
+
85
+ - Add support for WireGuard integration
86
+ - Improve UI with additional statistics
87
+ - Implement configuration profiles for quick VPN switching
88
+ - Enhance security features with automatic kill-switch
89
+ - In the future, NetCloak will be available as a Ruby gem for easier installation and updates.
90
+ - Future updates will include proxy chains support.
91
+
92
+ ## Contributing
93
+
94
+ We welcome contributions! Feel free to open issues or submit pull requests to improve NetCloak.
95
+
96
+ ## License
97
+
98
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
99
+
100
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
101
+
102
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
103
+
104
+ ---
105
+
106
+ **Stay anonymous, stay secure with NetCloak.**
107
+
data/bin/netcloak ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Add the lib directory to the load path
4
+ require_relative '../lib/netcloak'
5
+
6
+ begin
7
+ NetCloak.new.run
8
+ rescue Interrupt
9
+ puts "\nExiting NetCloak..."
10
+ ensure
11
+ Curses.close_screen if defined?(Curses)
12
+ end
@@ -0,0 +1,3 @@
1
+ module NetCloak
2
+ VERSION = "1.0.2"
3
+ end
data/lib/netcloak.rb ADDED
@@ -0,0 +1,488 @@
1
+ require 'curses'
2
+ require 'open3'
3
+
4
+ require_relative 'netcloak/version'
5
+
6
+ module NetCloak
7
+ class Error < StandardError; end
8
+
9
+ class NetCloak
10
+ COLORS = {
11
+ primary: 1,
12
+ success: 2,
13
+ danger: 3,
14
+ warning: 4,
15
+ accent: 5
16
+ }
17
+
18
+ def initialize
19
+ @vpn_pid = nil
20
+ @selected_ovpn = nil
21
+ @latencies = []
22
+ @start_time = Time.now
23
+ @running = true
24
+ init_ui
25
+ end
26
+
27
+ def init_ui
28
+ Curses.init_screen
29
+ Curses.curs_set(0) # Hide cursor
30
+ Curses.start_color
31
+ Curses.noecho
32
+ Curses.stdscr.keypad(true)
33
+ Curses.stdscr.nodelay = true
34
+
35
+ # Initialize color pairs
36
+ Curses.init_pair(COLORS[:primary], Curses::COLOR_CYAN, Curses::COLOR_BLACK)
37
+ Curses.init_pair(COLORS[:success], Curses::COLOR_GREEN, Curses::COLOR_BLACK)
38
+ Curses.init_pair(COLORS[:danger], Curses::COLOR_RED, Curses::COLOR_BLACK)
39
+ Curses.init_pair(COLORS[:warning], Curses::COLOR_YELLOW, Curses::COLOR_BLACK)
40
+ Curses.init_pair(COLORS[:accent], Curses::COLOR_MAGENTA, Curses::COLOR_BLACK)
41
+ end
42
+
43
+ def show_connecting_screen
44
+ win = Curses.stdscr
45
+ win.clear
46
+ print_header("Connecting to VPN...")
47
+
48
+ win.setpos(5, (Curses.cols - 20) / 2)
49
+ win.attron(Curses.color_pair(COLORS[:primary])) do
50
+ win.addstr("Establishing connection")
51
+ end
52
+
53
+ 3.times do |i|
54
+ win.setpos(7, (Curses.cols - 3) / 2)
55
+ win.addstr("#{'.' * (i + 1)} ")
56
+ win.refresh
57
+ sleep 0.5
58
+ end
59
+ end
60
+
61
+ def list_ovpn_files
62
+ files = Dir.glob("*.ovpn")
63
+ if files.empty?
64
+ show_error("No OpenVPN (.ovpn) files found!")
65
+ exit(1)
66
+ end
67
+ files
68
+ end
69
+
70
+ def choose_ovpn_file(files)
71
+ menu_win = Curses::Window.new(Curses.lines, Curses.cols, 0, 0)
72
+ menu_win.keypad = true
73
+
74
+ selection = 0
75
+ top_line = 0
76
+
77
+ loop do
78
+ # Clear only the menu area (lines 3 to bottom)
79
+ menu_win.setpos(3, 0)
80
+ (3..Curses.lines-1).each { menu_win.addstr(" " * Curses.cols) }
81
+
82
+ print_header_to_window(menu_win, "Select VPN Configuration")
83
+
84
+ visible_start = top_line
85
+ visible_end = [top_line + Curses.lines - 8, files.size - 1].min
86
+
87
+ (visible_start..visible_end).each do |index|
88
+ menu_win.setpos(4 + index - top_line, 4)
89
+ if index == selection
90
+ menu_win.attron(Curses.color_pair(COLORS[:accent]) | Curses::A_BOLD) { menu_win.addstr("> #{files[index]}") }
91
+ else
92
+ menu_win.addstr(" #{files[index]}")
93
+ end
94
+ end
95
+
96
+ menu_win.setpos(Curses.lines - 3, 4)
97
+ menu_win.addstr("↑/↓: Select ENTER: Confirm Q: Quit")
98
+
99
+ menu_win.refresh
100
+
101
+ case menu_win.getch
102
+ when Curses::Key::UP
103
+ if selection > 0
104
+ selection -= 1
105
+ top_line = selection if selection < top_line
106
+ end
107
+ when Curses::Key::DOWN
108
+ if selection < files.size - 1
109
+ selection += 1
110
+ top_line = selection - (Curses.lines - 8) + 1 if selection >= top_line + (Curses.lines - 8)
111
+ end
112
+ when 10 then return files[selection] # Enter
113
+ when 'q' then @running = false; return nil
114
+ end
115
+ end
116
+ ensure
117
+ menu_win.close if menu_win
118
+ end
119
+
120
+ def print_header_to_window(win, title)
121
+ cols = Curses.cols
122
+
123
+ win.setpos(1, 0)
124
+ win.attron(Curses.color_pair(COLORS[:primary]) | Curses::A_BOLD) do
125
+ win.addstr(" " * cols)
126
+ win.setpos(1, (cols - title.length) / 2)
127
+ win.addstr(title)
128
+ end
129
+
130
+ win.setpos(2, 0)
131
+ win.attron(Curses.color_pair(COLORS[:primary])) do
132
+ win.addstr("─" * cols)
133
+ end
134
+ end
135
+
136
+ def start_vpn(ovpn_file)
137
+ show_connecting_screen
138
+
139
+ log_file = "/tmp/openvpn_#{Time.now.to_i}.log"
140
+ @vpn_pid = `sudo openvpn --config #{ovpn_file} > #{log_file} 2>&1 & echo $!`.strip.to_i
141
+
142
+ win = Curses.stdscr
143
+ connected = false
144
+
145
+ 30.times do |i|
146
+ break unless @running
147
+
148
+ win.setpos(9, (Curses.cols - 20) / 2)
149
+ win.addstr("Attempt #{i+1}/30" + " " * 10)
150
+
151
+ if File.exist?(log_file)
152
+ log_content = File.read(log_file)
153
+
154
+ if log_content.include?("Initialization Sequence Completed")
155
+ connected = true
156
+ break
157
+ elsif log_content.include?("ERROR")
158
+ show_error("Connection failed! Check #{log_file}")
159
+ return false
160
+ end
161
+
162
+ last_line = log_content.lines.last.to_s.strip
163
+ win.setpos(11, (Curses.cols - [last_line.length, Curses.cols].min) / 2)
164
+ win.addstr(last_line[0..Curses.cols-1] + " " * 10)
165
+ end
166
+
167
+ win.refresh
168
+ sleep 1
169
+ end
170
+
171
+ if connected
172
+ win.setpos(13, (Curses.cols - 20) / 2)
173
+ win.attron(Curses.color_pair(COLORS[:success])) do
174
+ win.addstr("Connected successfully!")
175
+ end
176
+ win.refresh
177
+ sleep 1
178
+ true
179
+ else
180
+ show_error("Connection timeout after 30 seconds") if @running
181
+ false
182
+ end
183
+ end
184
+
185
+ def vpn_connected?
186
+ return false unless @vpn_pid
187
+ system("ps -p #{@vpn_pid} > /dev/null") &&
188
+ (system("ip a show tun0 > /dev/null") || system("ip a show tap0 > /dev/null"))
189
+ end
190
+
191
+ def get_latency
192
+ result = `ping -c 1 8.8.8.8 2>&1`
193
+ if result =~ /rtt min\/avg\/max\/mdev = ([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+) ms/
194
+ [$1.to_f, $2.to_f, $3.to_f]
195
+ else
196
+ nil
197
+ end
198
+ rescue
199
+ nil
200
+ end
201
+
202
+ def draw_graph(win, data, width, height, y, x)
203
+ valid_data = data.compact.select { |v| v.is_a?(Numeric) }
204
+ return if valid_data.empty?
205
+
206
+ max_value = valid_data.max.to_f
207
+ min_value = valid_data.min.to_f
208
+ range = (max_value - min_value) > 0 ? (max_value - min_value) : 1.0
209
+
210
+ scaled_data = valid_data.last(width - 4).map do |value|
211
+ ((height - 1) - (((value - min_value) / range) * (height - 1))).to_i
212
+ end
213
+
214
+ scaled_data.each_with_index do |y_pos, x_pos|
215
+ next unless y_pos.between?(0, height-1)
216
+ win.setpos(y + height - 1 - y_pos, x + x_pos)
217
+ win.attron(Curses.color_pair(COLORS[:accent])) { win.addstr("▄") }
218
+ end
219
+
220
+ win.setpos(y + height, x)
221
+ win.addstr("#{min_value.round}ms")
222
+ win.setpos(y + height, x + width - 5)
223
+ win.addstr("#{max_value.round}ms")
224
+ end
225
+
226
+ def print_header(title)
227
+ win = Curses.stdscr
228
+ cols = Curses.cols
229
+
230
+ win.setpos(1, 0)
231
+ win.attron(Curses.color_pair(COLORS[:primary]) | Curses::A_BOLD) do
232
+ win.addstr(" " * cols)
233
+ win.setpos(1, (cols - title.length) / 2)
234
+ win.addstr(title)
235
+ end
236
+
237
+ win.setpos(2, 0)
238
+ win.attron(Curses.color_pair(COLORS[:primary])) do
239
+ win.addstr("─" * cols)
240
+ end
241
+ end
242
+
243
+ def show_error(message)
244
+ win = Curses.stdscr
245
+ win.setpos(Curses.lines - 2, (Curses.cols - message.length) / 2)
246
+ win.attron(Curses.color_pair(COLORS[:danger]) | Curses::A_BOLD) do
247
+ win.addstr(message)
248
+ end
249
+ win.refresh
250
+ sleep 2
251
+ end
252
+
253
+ def show_disconnect_menu
254
+ menu_win = Curses::Window.new(Curses.lines, Curses.cols, 0, 0)
255
+ menu_win.keypad = true
256
+
257
+ selection = 0
258
+ options = [
259
+ "Reconnect to current config",
260
+ "Choose another VPN config",
261
+ "Exit application"
262
+ ]
263
+
264
+ loop do
265
+ # Clear only the menu area (lines 3 to bottom)
266
+ menu_win.setpos(3, 0)
267
+ (3..Curses.lines-1).each { menu_win.addstr(" " * Curses.cols) }
268
+
269
+ print_header_to_window(menu_win, "VPN Disconnected")
270
+
271
+ menu_win.setpos(5, 4)
272
+ menu_win.attron(Curses.color_pair(COLORS[:primary])) do
273
+ menu_win.addstr("What would you like to do?")
274
+ end
275
+
276
+ options.each_with_index do |option, index|
277
+ menu_win.setpos(7 + index, 4)
278
+ if index == selection
279
+ menu_win.attron(Curses.color_pair(COLORS[:accent]) | Curses::A_BOLD) do
280
+ menu_win.addstr("> #{option}")
281
+ end
282
+ else
283
+ menu_win.addstr(" #{option}")
284
+ end
285
+ end
286
+
287
+ menu_win.setpos(11, 4)
288
+ menu_win.addstr("↑/↓: Select ENTER: Confirm")
289
+
290
+ menu_win.refresh
291
+
292
+ case menu_win.getch
293
+ when Curses::Key::UP then selection = [selection - 1, 0].max
294
+ when Curses::Key::DOWN then selection = [selection + 1, options.size - 1].min
295
+ when 10 # Enter key
296
+ case selection
297
+ when 0 then return :reconnect_same
298
+ when 1 then return :reconnect_new
299
+ when 2 then return :exit
300
+ end
301
+ when 'q' then return :exit
302
+ end
303
+ end
304
+ ensure
305
+ menu_win.close if menu_win
306
+ end
307
+
308
+ def draw_monitor_screen
309
+ win = Curses.stdscr
310
+
311
+ # Only clear the content area (below header)
312
+ win.setpos(3, 0)
313
+ (3..Curses.lines-1).each { |i| win.addstr(" " * Curses.cols) }
314
+
315
+ print_header("NetCloak Monitor")
316
+
317
+ # Connection status
318
+ status = vpn_connected? ? "CONNECTED" : "DISCONNECTED"
319
+ color = vpn_connected? ? COLORS[:success] : COLORS[:danger]
320
+
321
+ win.setpos(4, 4)
322
+ win.attron(Curses.color_pair(COLORS[:primary])) do
323
+ win.addstr("Status:")
324
+ end
325
+ win.setpos(4, 12)
326
+ win.attron(Curses.color_pair(color) | Curses::A_BOLD) do
327
+ win.addstr(status)
328
+ end
329
+
330
+ # Connection info
331
+ elapsed_time = (Time.now - @start_time).round
332
+ latency = get_latency&.then { |_, avg, _| avg }
333
+
334
+ if latency
335
+ @latencies << latency
336
+ @latencies.shift if @latencies.size > 100
337
+ end
338
+
339
+ win.setpos(6, 4)
340
+ win.attron(Curses.color_pair(COLORS[:primary])) do
341
+ win.addstr("Uptime: #{elapsed_time}s")
342
+ end
343
+
344
+ win.setpos(7, 4)
345
+ win.addstr("Config: #{@selected_ovpn}")
346
+
347
+ # Current latency
348
+ win.setpos(9, 4)
349
+ win.attron(Curses.color_pair(COLORS[:primary])) do
350
+ win.addstr("Current Latency:")
351
+ end
352
+
353
+ if latency
354
+ status_color = if latency < 100
355
+ COLORS[:success]
356
+ elsif latency < 300
357
+ COLORS[:warning]
358
+ else
359
+ COLORS[:danger]
360
+ end
361
+
362
+ win.setpos(9, 22)
363
+ win.attron(Curses.color_pair(status_color) | Curses::A_BOLD) do
364
+ win.addstr("#{latency.round(2)} ms")
365
+ end
366
+ else
367
+ win.setpos(9, 22)
368
+ win.attron(Curses.color_pair(COLORS[:danger])) do
369
+ win.addstr("N/A")
370
+ end
371
+ end
372
+
373
+ # Stats
374
+ if !@latencies.empty?
375
+ avg = (@latencies.sum / @latencies.size).round(2)
376
+ min = @latencies.min.round(2)
377
+ max = @latencies.max.round(2)
378
+
379
+ win.setpos(11, 4)
380
+ win.attron(Curses.color_pair(COLORS[:primary])) do
381
+ win.addstr("Statistics (last #{@latencies.size} samples):")
382
+ end
383
+
384
+ win.setpos(12, 6)
385
+ win.addstr("Avg: #{avg} ms")
386
+
387
+ win.setpos(13, 6)
388
+ win.addstr("Min: #{min} ms")
389
+
390
+ win.setpos(14, 6)
391
+ win.addstr("Max: #{max} ms")
392
+ end
393
+
394
+ # Graph
395
+ if !@latencies.empty?
396
+ win.setpos(16, 4)
397
+ win.attron(Curses.color_pair(COLORS[:primary])) do
398
+ win.addstr("Latency Trend:")
399
+ end
400
+
401
+ graph_width = [Curses.cols - 10, 60].min
402
+ draw_graph(win, @latencies, graph_width, 8, 17, 6)
403
+ end
404
+
405
+ # Controls
406
+ win.setpos(Curses.lines - 4, 4)
407
+ win.attron(Curses.color_pair(COLORS[:primary])) do
408
+ win.addstr("Controls:")
409
+ end
410
+
411
+ win.setpos(Curses.lines - 3, 6)
412
+ win.addstr("[R] Reconnect [D] Disconnect [Q] Quit")
413
+
414
+ win.refresh
415
+ end
416
+
417
+ def monitor_loop
418
+ while @running
419
+ draw_monitor_screen
420
+
421
+ case Curses.stdscr.getch
422
+ when 'q', 'Q'
423
+ @running = false
424
+ when 'd', 'D'
425
+ stop_vpn
426
+ action = show_disconnect_menu
427
+ case action
428
+ when :reconnect_same
429
+ if start_vpn(@selected_ovpn)
430
+ @start_time = Time.now
431
+ @latencies.clear
432
+ else
433
+ @running = false
434
+ end
435
+ when :reconnect_new
436
+ ovpn_files = list_ovpn_files
437
+ @selected_ovpn = choose_ovpn_file(ovpn_files)
438
+ if @selected_ovpn && start_vpn(@selected_ovpn)
439
+ @start_time = Time.now
440
+ @latencies.clear
441
+ else
442
+ @running = false
443
+ end
444
+ when :exit
445
+ @running = false
446
+ end
447
+ when 'r', 'R'
448
+ stop_vpn
449
+ if start_vpn(@selected_ovpn)
450
+ @start_time = Time.now
451
+ @latencies.clear
452
+ end
453
+ end
454
+
455
+ sleep 0.1
456
+ end
457
+ end
458
+
459
+ def stop_vpn
460
+ return unless @vpn_pid
461
+ Process.kill('TERM', @vpn_pid) rescue nil
462
+ @vpn_pid = nil
463
+ end
464
+
465
+ def run
466
+ while @running
467
+ ovpn_files = list_ovpn_files
468
+ @selected_ovpn = choose_ovpn_file(ovpn_files)
469
+
470
+ if @selected_ovpn && start_vpn(@selected_ovpn)
471
+ @start_time = Time.now
472
+ @latencies.clear
473
+ monitor_loop
474
+ end
475
+ end
476
+ rescue => e
477
+ show_error("Error: #{e.message}")
478
+ ensure
479
+ stop_vpn
480
+ Curses.close_screen
481
+ puts "VPN disconnected" if @running
482
+ @running = false
483
+ end
484
+ end
485
+
486
+ end
487
+
488
+ NetCloak::NetCloak.new.run
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: netcloak
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Mark Angelo P. Santonil
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-15 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: curses
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.4'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.4'
26
+ - !ruby/object:Gem::Dependency
27
+ name: open3
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: bundler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ description: NetCloak provides a curses-based interface to manage VPN configurations,
83
+ monitor latency, and control connections.
84
+ email:
85
+ - cillia2203@gmail.com
86
+ executables:
87
+ - netcloak
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - README.md
92
+ - bin/netcloak
93
+ - lib/netcloak.rb
94
+ - lib/netcloak/version.rb
95
+ homepage: https://github.com/cilliapwndev/NetCloak
96
+ licenses:
97
+ - GNU v.3
98
+ metadata: {}
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 3.6.6
114
+ specification_version: 4
115
+ summary: A CLI tool to monitor and manage OpenVPN connections.
116
+ test_files: []