vpn-gateway-tool 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1f44715eb6f0c87473d0297f982c74dee1c55cde282d169ff91c49fb98975f59
4
+ data.tar.gz: af89091182c7215e1bc5a39d3e124a63e0e75c6668f44aa54f37346c64b22451
5
+ SHA512:
6
+ metadata.gz: 9ab6d06f6c66f3d2827ebfefe9a9a815a19f9f0b6f7041defaddf74cebd037e53f4388ba22d51419edf5e6ddf03bd992d1b72f59eab6eca58acf6740107074b9
7
+ data.tar.gz: 5cdfd97efbc66dfb67b49e9ab0a8bdbc4b2debf8e401ec5339ab69570b06ba8cbc66e8be9ee7c930145ec180f0804fb359f3f2a93b29a1da09baf5b94f5aab68
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # vpn-gateway-tool
2
+
3
+ Ruby CLI for the VPN Gateway Manager.
4
+
5
+ ## API
6
+
7
+ ```text
8
+ GET /api/healthz
9
+ GET /api/vpn/countries
10
+
11
+ POST /api/vpn/create?countryCode=
12
+ POST /api/vpn/connect?countryCode=
13
+ POST /api/vpn/disconnect?countryCode=
14
+
15
+ GET /api/vpn/status?countryCode=
16
+ GET /api/vpn/test/sleep?ms=100
17
+
18
+ POST /api/vpn/ip?countryCode=&count=10
19
+ ```
20
+
21
+ ## Commands
22
+
23
+ ```bash
24
+ ./vpn-tool.rb --help
25
+ ./vpn-tool.rb --version
26
+ ./vpn-tool.rb auth register
27
+ ./vpn-tool.rb auth login
28
+ ./vpn-tool.rb auth status
29
+ ./vpn-tool.rb healthz
30
+ ./vpn-tool.rb countries
31
+ ./vpn-tool.rb create --country MD
32
+ ./vpn-tool.rb connect --country MD
33
+ ./vpn-tool.rb disconnect --country MD
34
+ ./vpn-tool.rb status --country MD
35
+ ./vpn-tool.rb test --ms 100
36
+ ./vpn-tool.rb ip --country MD --count 10
37
+ ```
38
+
39
+ ## RubyGem
40
+
41
+ ```bash
42
+ gem build vpn-gateway-tool.gemspec
43
+ gem install ./vpn-gateway-tool-1.0.0.gem
44
+ gem signin
45
+ gem push vpn-gateway-tool-1.0.0.gem
46
+ ```
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/vpn_tool"
data/lib/vpn_tool.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../vpn-tool"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/vpn_tool"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "vpn-gateway-tool"
7
+ spec.version = VpnTool::VERSION
8
+
9
+ spec.authors = ["j"]
10
+ spec.email = ["dodi664@gk.com"]
11
+
12
+ spec.summary = "VPN Gateway Manager CLI"
13
+ spec.description = "Ruby CLI for a VPN gateway manager."
14
+ spec.homepage = "https://vpn-gateway-manager--jjajajjajahsh.replit.app"
15
+ spec.license = "MIT"
16
+
17
+ spec.required_ruby_version = ">= 3.0"
18
+
19
+ spec.files = [
20
+ "vpn-tool.rb",
21
+ "lib/vpn_tool.rb",
22
+ "bin/vpn-gateway-tool",
23
+ "vpn-gateway-tool.gemspec",
24
+ "README.md"
25
+ ]
26
+
27
+ spec.bindir = "bin"
28
+ spec.executables = ["vpn-gateway-tool"]
29
+ spec.require_paths = ["lib"]
30
+ end
data/vpn-tool.rb ADDED
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "net/http"
5
+ require "uri"
6
+ require "json"
7
+ require "openssl"
8
+ require "optparse"
9
+ require "io/console"
10
+
11
+ module VpnTool
12
+ VERSION = "1.0.0"
13
+ API_BASE = "https://vpn-gateway-manager--jjajajjajahsh.replit.app"
14
+
15
+ CONFIG_DIR = File.join(Dir.home, ".vpn-tool")
16
+ AUTH_FILE = File.join(CONFIG_DIR, "auth.json")
17
+ SESSION_FILE = File.join(CONFIG_DIR, "session")
18
+
19
+ module_function
20
+
21
+ def ensure_config
22
+ Dir.mkdir(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
23
+ end
24
+
25
+ def hash_pin(pin)
26
+ OpenSSL::Digest::SHA256.hexdigest(pin)
27
+ end
28
+
29
+ def read_pin(prompt)
30
+ printf "%s", prompt
31
+
32
+ pin =
33
+ if STDIN.tty?
34
+ STDIN.noecho(&:gets)
35
+ else
36
+ STDIN.gets
37
+ end
38
+
39
+ puts
40
+ pin.to_s.strip
41
+ end
42
+
43
+ def registered?
44
+ File.file?(AUTH_FILE)
45
+ end
46
+
47
+ def logged_in?
48
+ File.file?(SESSION_FILE) &&
49
+ File.read(SESSION_FILE).strip == "logged_in"
50
+ end
51
+
52
+ def load_auth
53
+ return nil unless File.file?(AUTH_FILE)
54
+
55
+ JSON.parse(File.read(AUTH_FILE))
56
+ rescue JSON::ParserError
57
+ nil
58
+ end
59
+
60
+ def save_auth(pin)
61
+ ensure_config
62
+
63
+ File.write(
64
+ AUTH_FILE,
65
+ JSON.pretty_generate(
66
+ {
67
+ "version" => 1,
68
+ "pin_hash" => hash_pin(pin)
69
+ }
70
+ )
71
+ )
72
+
73
+ File.chmod(0o600, AUTH_FILE)
74
+ rescue StandardError
75
+ nil
76
+ end
77
+
78
+ def require_login
79
+ abort "Not registered. Run: vpn-gateway-tool auth register" unless registered?
80
+ abort "Not logged in. Run: vpn-gateway-tool auth login" unless logged_in?
81
+ end
82
+
83
+ def auth_help
84
+ puts <<~HELP
85
+ vpn-gateway-tool auth
86
+
87
+ Commands:
88
+ auth register
89
+ auth login
90
+ auth logout
91
+ auth status
92
+ auth reset
93
+ auth remove
94
+ auth help
95
+ HELP
96
+ end
97
+
98
+ def auth_register
99
+ abort "PIN already registered." if registered?
100
+
101
+ pin = read_pin("Enter new PIN: ")
102
+ abort "PIN cannot be empty." if pin.empty?
103
+
104
+ confirm = read_pin("Confirm new PIN: ")
105
+ abort "PINs do not match." unless pin == confirm
106
+
107
+ save_auth(pin)
108
+ ensure_config
109
+ File.write(SESSION_FILE, "logged_out")
110
+
111
+ puts "PIN registered successfully."
112
+ puts "Run: vpn-gateway-tool auth login"
113
+ end
114
+
115
+ def auth_login
116
+ abort "No PIN registered. Run: vpn-gateway-tool auth register" unless registered?
117
+
118
+ auth = load_auth
119
+ pin = read_pin("Enter PIN: ")
120
+
121
+ abort "Invalid PIN." unless auth &&
122
+ auth["pin_hash"] == hash_pin(pin)
123
+
124
+ ensure_config
125
+ File.write(SESSION_FILE, "logged_in")
126
+
127
+ puts "Login successful."
128
+ end
129
+
130
+ def auth_logout
131
+ abort "No PIN registered." unless registered?
132
+
133
+ auth = load_auth
134
+ pin = read_pin("Enter PIN: ")
135
+
136
+ abort "Invalid PIN." unless auth &&
137
+ auth["pin_hash"] == hash_pin(pin)
138
+
139
+ ensure_config
140
+ File.write(SESSION_FILE, "logged_out")
141
+
142
+ puts "Logout successful."
143
+ end
144
+
145
+ def auth_status
146
+ puts "Registered: #{registered? ? 'yes' : 'no'}"
147
+ puts "Logged in: #{logged_in? ? 'yes' : 'no'}"
148
+ end
149
+
150
+ def auth_reset
151
+ abort "No PIN registered." unless registered?
152
+
153
+ auth = load_auth
154
+ current = read_pin("Enter current PIN: ")
155
+
156
+ abort "Invalid current PIN." unless auth &&
157
+ auth["pin_hash"] == hash_pin(current)
158
+
159
+ new_pin = read_pin("Enter new PIN: ")
160
+ abort "New PIN cannot be empty." if new_pin.empty?
161
+
162
+ confirm = read_pin("Confirm new PIN: ")
163
+ abort "PINs do not match." unless new_pin == confirm
164
+
165
+ save_auth(new_pin)
166
+ ensure_config
167
+ File.write(SESSION_FILE, "logged_out")
168
+
169
+ puts "PIN reset successfully."
170
+ end
171
+
172
+ def auth_remove
173
+ abort "No PIN registered." unless registered?
174
+
175
+ auth = load_auth
176
+ pin = read_pin("Enter PIN: ")
177
+
178
+ abort "Invalid PIN." unless auth &&
179
+ auth["pin_hash"] == hash_pin(pin)
180
+
181
+ File.delete(AUTH_FILE) if File.file?(AUTH_FILE)
182
+ File.delete(SESSION_FILE) if File.file?(SESSION_FILE)
183
+
184
+ puts "Authentication removed."
185
+ end
186
+
187
+ def api_request(method, path)
188
+ uri = URI.join(API_BASE, path)
189
+
190
+ request =
191
+ case method
192
+ when :get
193
+ Net::HTTP::Get.new(uri)
194
+ when :post
195
+ Net::HTTP::Post.new(uri)
196
+ else
197
+ raise "Unsupported HTTP method"
198
+ end
199
+
200
+ request["Accept"] = "application/json"
201
+
202
+ http = Net::HTTP.new(uri.host, uri.port)
203
+ http.use_ssl = uri.scheme == "https"
204
+ http.open_timeout = 15
205
+ http.read_timeout = 60
206
+
207
+ response = http.request(request)
208
+
209
+ body =
210
+ begin
211
+ JSON.parse(response.body)
212
+ rescue JSON::ParserError
213
+ response.body
214
+ end
215
+
216
+ {
217
+ "status" => response.code.to_i,
218
+ "body" => body
219
+ }
220
+ rescue StandardError => e
221
+ {
222
+ "status" => 0,
223
+ "body" => {
224
+ "error" => e.message
225
+ }
226
+ }
227
+ end
228
+
229
+ def print_response(result)
230
+ puts JSON.pretty_generate(result)
231
+ end
232
+
233
+ def country_option(args)
234
+ options = {}
235
+
236
+ parser = OptionParser.new do |opts|
237
+ opts.banner = "Usage: vpn-gateway-tool COMMAND --country CODE"
238
+
239
+ opts.on("--country CODE", "Country code") do |value|
240
+ options[:country] = value
241
+ end
242
+ end
243
+
244
+ parser.parse!(args)
245
+
246
+ abort "Missing --country CODE" unless options[:country]
247
+
248
+ options[:country]
249
+ rescue OptionParser::ParseError => e
250
+ abort e.message
251
+ end
252
+
253
+ def healthz
254
+ require_login
255
+ print_response(api_request(:get, "/api/healthz"))
256
+ end
257
+
258
+ def countries
259
+ require_login
260
+ print_response(api_request(:get, "/api/vpn/countries"))
261
+ end
262
+
263
+ def vpn_command(command, args)
264
+ require_login
265
+
266
+ country = country_option(args)
267
+ country = URI.encode_www_form_component(country)
268
+
269
+ method = command == "status" ? :get : :post
270
+
271
+ print_response(
272
+ api_request(
273
+ method,
274
+ "/api/vpn/#{command}?countryCode=#{country}"
275
+ )
276
+ )
277
+ end
278
+
279
+ def test_command(args)
280
+ require_login
281
+
282
+ options = { ms: 100 }
283
+
284
+ parser = OptionParser.new do |opts|
285
+ opts.banner = "Usage: vpn-gateway-tool test --ms N"
286
+
287
+ opts.on("--ms N", Integer, "Sleep milliseconds") do |value|
288
+ options[:ms] = value
289
+ end
290
+ end
291
+
292
+ parser.parse!(args)
293
+
294
+ print_response(
295
+ api_request(
296
+ :get,
297
+ "/api/vpn/test/sleep?ms=#{options[:ms]}"
298
+ )
299
+ )
300
+ rescue OptionParser::ParseError => e
301
+ abort e.message
302
+ end
303
+
304
+ def ip_command(args)
305
+ require_login
306
+
307
+ options = {
308
+ country: nil,
309
+ count: 10
310
+ }
311
+
312
+ parser = OptionParser.new do |opts|
313
+ opts.banner = "Usage: vpn-gateway-tool ip --country CODE --count N"
314
+
315
+ opts.on("--country CODE", "Country code") do |value|
316
+ options[:country] = value
317
+ end
318
+
319
+ opts.on("--count N", Integer, "Number of IPs") do |value|
320
+ options[:count] = value
321
+ end
322
+ end
323
+
324
+ parser.parse!(args)
325
+
326
+ abort "Missing --country CODE" unless options[:country]
327
+
328
+ country = URI.encode_www_form_component(options[:country])
329
+
330
+ print_response(
331
+ api_request(
332
+ :post,
333
+ "/api/vpn/ip?countryCode=#{country}&count=#{options[:count]}"
334
+ )
335
+ )
336
+ rescue OptionParser::ParseError => e
337
+ abort e.message
338
+ end
339
+
340
+ def help
341
+ puts <<~HELP
342
+ vpn-gateway-tool #{VERSION}
343
+
344
+ Usage:
345
+ vpn-gateway-tool COMMAND
346
+
347
+ Authentication:
348
+ auth register
349
+ auth login
350
+ auth logout
351
+ auth status
352
+ auth reset
353
+ auth remove
354
+
355
+ VPN:
356
+ healthz
357
+ countries
358
+ create --country CODE
359
+ connect --country CODE
360
+ disconnect --country CODE
361
+ status --country CODE
362
+ test --ms N
363
+ ip --country CODE --count N
364
+ HELP
365
+ end
366
+
367
+ def run(argv = ARGV)
368
+ parser = OptionParser.new do |opts|
369
+ opts.on("--version") do
370
+ puts "vpn-gateway-tool #{VERSION}"
371
+ exit
372
+ end
373
+
374
+ opts.on("-h", "--help") do
375
+ help
376
+ exit
377
+ end
378
+ end
379
+
380
+ parser.parse!(argv)
381
+
382
+ command = argv.shift
383
+
384
+ case command
385
+ when "auth"
386
+ case argv.shift
387
+ when "register" then auth_register
388
+ when "login" then auth_login
389
+ when "logout" then auth_logout
390
+ when "status" then auth_status
391
+ when "reset" then auth_reset
392
+ when "remove" then auth_remove
393
+ else auth_help
394
+ end
395
+ when "healthz"
396
+ healthz
397
+ when "countries"
398
+ countries
399
+ when "create", "connect", "disconnect", "status"
400
+ vpn_command(command, argv)
401
+ when "test"
402
+ test_command(argv)
403
+ when "ip"
404
+ ip_command(argv)
405
+ when "version"
406
+ puts "vpn-gateway-tool #{VERSION}"
407
+ else
408
+ help
409
+ end
410
+ end
411
+ end
412
+
413
+ VpnTool.run
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vpn-gateway-tool
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - j
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-08-17 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Ruby CLI for a VPN gateway manager.
13
+ email:
14
+ - dodi664@gk.com
15
+ executables:
16
+ - vpn-gateway-tool
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - bin/vpn-gateway-tool
22
+ - lib/vpn_tool.rb
23
+ - vpn-gateway-tool.gemspec
24
+ - vpn-tool.rb
25
+ homepage: https://vpn-gateway-manager--jjajajjajahsh.replit.app
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '3.0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubygems_version: 3.6.2
44
+ specification_version: 4
45
+ summary: VPN Gateway Manager CLI
46
+ test_files: []