gdkbox 0.1.3 → 0.1.8
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 +4 -4
- data/README.md +20 -7
- data/lib/gdkbox/box.rb +22 -3
- data/lib/gdkbox/cli.rb +65 -1
- data/lib/gdkbox/config.rb +6 -0
- data/lib/gdkbox/docker.rb +11 -2
- data/lib/gdkbox/hosts_file.rb +84 -0
- data/lib/gdkbox/ports.rb +24 -0
- data/lib/gdkbox/version.rb +1 -1
- data/lib/gdkbox.rb +2 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1c7b818ffd457d8f32f3d80073585222be0140ba3ae5c32a76b587bae5a98f5
|
|
4
|
+
data.tar.gz: 35b56df278015bbf0ad44cf175665c481cddd3939bc2130a5b72d193aec44c90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0fd71a1460e658cd975298666fc7afaa1110a2b154a0da005bc80c7c4e9bdb21545ab544091c5a2f2773eab48e2e9520311e24b39c16962a7ae2416b9024bcf
|
|
7
|
+
data.tar.gz: 44638a681108d58ddafdb1a9d4703f5a1f84cd6da764dae608d25ead66a3ebba8f4bb9057d57c38176ecd4f3c49e5c6809636c159c5c8ebbd0803c4b0ff2415d
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# gdkbox
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/gdkbox)
|
|
4
|
+
[](https://rubygems.org/gems/gdkbox)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
Spin up a [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit)
|
|
4
8
|
"in a box" with a single command, then connect to it from **VS Code** and run
|
|
5
9
|
**Claude Code** agents against a real GitLab development environment.
|
|
@@ -22,7 +26,7 @@ gdkbox ls # list your boxes and their status
|
|
|
22
26
|
┌──────────────┼─────────────────────────────────────────────┐
|
|
23
27
|
│ ▼ │
|
|
24
28
|
│ 1. docker pull <official GDK image> │
|
|
25
|
-
│ 2. docker run -p 127.0.0.1:2222:22 -p 127.0.0.1:3000:3000
|
|
29
|
+
│ 2. docker run --hostname gdk.local -p 127.0.0.1:2222:22 -p 127.0.0.1:3000:3000
|
|
26
30
|
│ 3. provision install + start sshd, authorize gdkbox key │
|
|
27
31
|
│ 4. provision npm install -g @anthropic-ai/claude-code │
|
|
28
32
|
│ 5. register Host gdkbox-demo -> ~/.gdkbox/ssh_config │
|
|
@@ -51,14 +55,22 @@ gdkbox ls # list your boxes and their status
|
|
|
51
55
|
|
|
52
56
|
## Install
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
`gdkbox` is published on [RubyGems](https://rubygems.org/gems/gdkbox):
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
gem install gdkbox
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### From source
|
|
65
|
+
|
|
66
|
+
To build and install from a checkout of this repository:
|
|
55
67
|
|
|
56
68
|
```sh
|
|
57
69
|
gem build gdkbox.gemspec
|
|
58
|
-
gem install ./gdkbox
|
|
70
|
+
gem install ./gdkbox-*.gem
|
|
59
71
|
```
|
|
60
72
|
|
|
61
|
-
Or run it straight from
|
|
73
|
+
Or run it straight from the checkout without installing:
|
|
62
74
|
|
|
63
75
|
```sh
|
|
64
76
|
./bin/gdkbox up myenv
|
|
@@ -68,7 +80,7 @@ Or run it straight from a checkout without installing:
|
|
|
68
80
|
|
|
69
81
|
| Command | Description |
|
|
70
82
|
| --- | --- |
|
|
71
|
-
| `gdkbox up NAME` | Pull the GDK image, start a box, enable SSH, install Claude Code, register VS Code host. |
|
|
83
|
+
| `gdkbox up NAME` | Pull the GDK image (only if not already local), start a box, enable SSH, install Claude Code, register VS Code host. |
|
|
72
84
|
| `gdkbox update-image` | Pull the latest GDK-in-a-box image, with a progress bar and download speed (`--image` to override). |
|
|
73
85
|
| `gdkbox ls` | List boxes and their container status (`--json` for orchestrators). |
|
|
74
86
|
| `gdkbox status NAME` | Show container state and connection details (`--json`). |
|
|
@@ -77,6 +89,7 @@ Or run it straight from a checkout without installing:
|
|
|
77
89
|
| `gdkbox ssh NAME` | Open an interactive SSH session into the box. |
|
|
78
90
|
| `gdkbox claude NAME` | Install Claude Code inside the box. |
|
|
79
91
|
| `gdkbox set-key NAME` | Seed/rotate the Anthropic API key in the box for unattended dispatch. |
|
|
92
|
+
| `gdkbox set-host` | Add the `gdk.local` entry to your `/etc/hosts` so box web UIs resolve (`--remove` to undo). Also runs during `up`. |
|
|
80
93
|
| `gdkbox start NAME` | Start a stopped box (and re-enable SSH). |
|
|
81
94
|
| `gdkbox stop NAME` | Stop a running box. |
|
|
82
95
|
| `gdkbox rm NAME` | Remove a box: container, metadata, and SSH entry. |
|
|
@@ -92,8 +105,8 @@ Or run it straight from a checkout without installing:
|
|
|
92
105
|
| Option | Default | Description |
|
|
93
106
|
| --- | --- | --- |
|
|
94
107
|
| `--image` | official GDK image | Override the container image. |
|
|
95
|
-
| `--ssh-port` | next free from 2222 | Host port to publish SSH on. |
|
|
96
|
-
| `--web-port` | next free from 3000 | Host port to publish the GDK web UI on. |
|
|
108
|
+
| `--ssh-port` | next free from 2222 | Host port to publish SSH on. Auto-allocation skips ports in use on the host (e.g. a local GDK). |
|
|
109
|
+
| `--web-port` | next free from 3000 | Host port to publish the GDK web UI on. Auto-allocation skips ports in use on the host. |
|
|
97
110
|
| `--harness ID` | `claude` (or config.yml) | Agent harness to install: claude, codex, opencode, pi. |
|
|
98
111
|
| `--no-agent` | (agent installed) | Skip installing the agent harness. |
|
|
99
112
|
| `--skill NAME [NAME...]` | (none) | Seed skill(s) into the box at creation for dispatched agents. |
|
data/lib/gdkbox/box.rb
CHANGED
|
@@ -88,7 +88,10 @@ module GDKBox
|
|
|
88
88
|
public_key = @ssh_key.ensure!
|
|
89
89
|
cname = @config.container_name(name)
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
# Only pull when the image is missing locally, so `gdkbox up` on a warm
|
|
92
|
+
# machine does not hit the registry; `gdkbox update-image` is the
|
|
93
|
+
# explicit way to refresh.
|
|
94
|
+
@docker.pull(image) unless @docker.image_exists?(image)
|
|
92
95
|
|
|
93
96
|
# Reserve the host ports atomically. `next_port` reads the store, so
|
|
94
97
|
# parallel `gdkbox up` runs would otherwise all pick the same "next free"
|
|
@@ -101,6 +104,10 @@ module GDKBox
|
|
|
101
104
|
@docker.run_container(
|
|
102
105
|
name: cname,
|
|
103
106
|
image: image,
|
|
107
|
+
# GDK's services resolve and redirect to this hostname; without it
|
|
108
|
+
# set inside the container, workhorse and the http-router crash-loop
|
|
109
|
+
# on DNS timeouts.
|
|
110
|
+
hostname: Config::GDK_HOSTNAME,
|
|
104
111
|
publish: [
|
|
105
112
|
"127.0.0.1:#{ssh_port}:#{Config::SSH_CONTAINER_PORT}",
|
|
106
113
|
"127.0.0.1:#{web_port}:#{Config::GDK_WEB_CONTAINER_PORT}"
|
|
@@ -263,8 +270,16 @@ module GDKBox
|
|
|
263
270
|
# Choose free host ports and persist a preliminary record claiming them,
|
|
264
271
|
# all while holding an exclusive lock so concurrent `create!` calls in
|
|
265
272
|
# separate processes serialize and cannot pick the same ports. Returns the
|
|
266
|
-
# chosen [ssh_port, web_port]. Caller-supplied ports are honored
|
|
273
|
+
# chosen [ssh_port, web_port]. Caller-supplied ports are honored, but fail
|
|
274
|
+
# fast with a clear message when something already holds them — better
|
|
275
|
+
# than the cryptic bind error `docker run` would produce later.
|
|
267
276
|
def reserve_ports!(cname, ssh_port, web_port)
|
|
277
|
+
{ "--ssh-port" => ssh_port, "--web-port" => web_port }.each do |flag, port|
|
|
278
|
+
if port && Ports.bound?(port)
|
|
279
|
+
raise Error, "Port #{port} (#{flag}) is already in use on 127.0.0.1."
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
268
283
|
@config.ensure_dirs!
|
|
269
284
|
File.open(@config.lock_path, File::RDWR | File::CREAT, 0o644) do |lock|
|
|
270
285
|
lock.flock(File::LOCK_EX)
|
|
@@ -283,10 +298,14 @@ module GDKBox
|
|
|
283
298
|
end
|
|
284
299
|
end
|
|
285
300
|
|
|
301
|
+
# The next port at or above base that is neither recorded by gdkbox nor
|
|
302
|
+
# actually bound on the host. The store only covers gdkbox's own boxes;
|
|
303
|
+
# a locally running GDK (typically on 2222/3000) is invisible to it, so
|
|
304
|
+
# each candidate is also probed with a real bind.
|
|
286
305
|
def next_port(base, exclude: [])
|
|
287
306
|
used = @store.used_ports + exclude
|
|
288
307
|
port = base
|
|
289
|
-
port += 1 while used.include?(port)
|
|
308
|
+
port += 1 while used.include?(port) || Ports.bound?(port)
|
|
290
309
|
port
|
|
291
310
|
end
|
|
292
311
|
end
|
data/lib/gdkbox/cli.rb
CHANGED
|
@@ -61,6 +61,8 @@ module GDKBox
|
|
|
61
61
|
return
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
setup_host
|
|
65
|
+
|
|
64
66
|
say "\nBox '#{name}' is up.", :green
|
|
65
67
|
print_connection_details(box)
|
|
66
68
|
unless api_key
|
|
@@ -234,6 +236,46 @@ module GDKBox
|
|
|
234
236
|
end
|
|
235
237
|
map "set-key" => :set_key
|
|
236
238
|
|
|
239
|
+
desc "set-host", "Add (default) or remove the gdk.local entry in /etc/hosts"
|
|
240
|
+
long_desc <<~DESC
|
|
241
|
+
GDK generates URLs and redirects that use the `gdk.local` hostname, so
|
|
242
|
+
your browser needs it mapped to loopback to reach a box's web UI. This
|
|
243
|
+
adds `#{HostsFile::ENTRY}` to /etc/hosts when no gdk.local entry exists
|
|
244
|
+
yet (writing needs sudo, so you may be prompted). `gdkbox up` runs the
|
|
245
|
+
same check automatically.
|
|
246
|
+
|
|
247
|
+
With --remove, the gdkbox-managed entry is removed again; lines you
|
|
248
|
+
added yourself are never touched.
|
|
249
|
+
DESC
|
|
250
|
+
option :add, type: :boolean, default: true, desc: "Add the entry (the default action)"
|
|
251
|
+
option :remove, type: :boolean, default: false,
|
|
252
|
+
desc: "Remove the gdkbox-managed entry instead"
|
|
253
|
+
def set_host
|
|
254
|
+
hosts = HostsFile.new
|
|
255
|
+
if options[:remove]
|
|
256
|
+
case hosts.remove
|
|
257
|
+
when :absent
|
|
258
|
+
say "No gdkbox-managed '#{HostsFile::HOSTNAME}' entry in #{hosts.path}. Nothing to do."
|
|
259
|
+
when :removed
|
|
260
|
+
say "Removed the '#{HostsFile::HOSTNAME}' entry from #{hosts.path}.", :green
|
|
261
|
+
when :manual
|
|
262
|
+
say "Could not write #{hosts.path}. Remove the line tagged " \
|
|
263
|
+
"'#{HostsFile::MARKER}' manually (e.g. with `sudoedit #{hosts.path}`).", :yellow
|
|
264
|
+
end
|
|
265
|
+
else
|
|
266
|
+
case hosts.add
|
|
267
|
+
when :present
|
|
268
|
+
say "'#{HostsFile::HOSTNAME}' already resolves via #{hosts.path}. Nothing to do."
|
|
269
|
+
when :added
|
|
270
|
+
say "Added '#{HostsFile::ENTRY}' to #{hosts.path}.", :green
|
|
271
|
+
when :manual
|
|
272
|
+
say "Could not write #{hosts.path}. Add the entry manually:", :yellow
|
|
273
|
+
say " echo '#{HostsFile::ENTRY}' | sudo tee -a #{hosts.path}", :yellow
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
map "set-host" => :set_host
|
|
278
|
+
|
|
237
279
|
desc "start NAME", "Start a stopped box (and re-enable SSH)"
|
|
238
280
|
def start(name)
|
|
239
281
|
box = load_box!(name)
|
|
@@ -370,6 +412,28 @@ module GDKBox
|
|
|
370
412
|
end
|
|
371
413
|
end
|
|
372
414
|
|
|
415
|
+
# Ensure gdk.local resolves on the host so the box's web UI is reachable
|
|
416
|
+
# (GDK redirects to that hostname). Adding the entry needs sudo, so warn
|
|
417
|
+
# before any password prompt appears. Failures are non-fatal: the box is
|
|
418
|
+
# already up, and the user can run `gdkbox set-host` later. Skipped in
|
|
419
|
+
# --json mode, where prompting would break unattended orchestrators.
|
|
420
|
+
def setup_host
|
|
421
|
+
hosts = HostsFile.new
|
|
422
|
+
return if hosts.entry?
|
|
423
|
+
|
|
424
|
+
say "\nAdding '#{HostsFile::HOSTNAME}' to #{hosts.path} so the web UI resolves " \
|
|
425
|
+
"(may prompt for sudo)...", :green
|
|
426
|
+
case hosts.add
|
|
427
|
+
when :added
|
|
428
|
+
say "Added '#{HostsFile::ENTRY}' to #{hosts.path}.", :green
|
|
429
|
+
when :manual
|
|
430
|
+
say "Could not write #{hosts.path}. Run `gdkbox set-host`, or add it manually:", :yellow
|
|
431
|
+
say " echo '#{HostsFile::ENTRY}' | sudo tee -a #{hosts.path}", :yellow
|
|
432
|
+
end
|
|
433
|
+
rescue StandardError => e
|
|
434
|
+
say "Skipped #{hosts.path} setup: #{e.message}", :yellow
|
|
435
|
+
end
|
|
436
|
+
|
|
373
437
|
# Show the discovered skills and let the user pick by number (or "all").
|
|
374
438
|
def prompt_for_skills
|
|
375
439
|
found = Skills.discover
|
|
@@ -432,7 +496,7 @@ module GDKBox
|
|
|
432
496
|
say ""
|
|
433
497
|
say " SSH: ssh #{box.ssh_host_alias}", :cyan
|
|
434
498
|
say " VS Code: gdkbox code #{box.name}", :cyan
|
|
435
|
-
say " Web UI: #{box.web_url}", :cyan
|
|
499
|
+
say " Web UI: #{box.web_url} (http://#{HostsFile::HOSTNAME}:#{box.web_port})", :cyan
|
|
436
500
|
say " Agent: ssh #{box.ssh_host_alias} -t #{box.harness.bin}", :cyan
|
|
437
501
|
end
|
|
438
502
|
end
|
data/lib/gdkbox/config.rb
CHANGED
|
@@ -22,6 +22,12 @@ module GDKBox
|
|
|
22
22
|
DEFAULT_IMAGE =
|
|
23
23
|
"registry.gitlab.com/gitlab-org/gitlab-development-kit/gitlab-gdk-in-a-box:main"
|
|
24
24
|
|
|
25
|
+
# The hostname GDK is configured for. It is set as the container's own
|
|
26
|
+
# hostname (so services inside the box can resolve it) and mapped to
|
|
27
|
+
# loopback in the host's /etc/hosts by HostsFile (so the host browser can
|
|
28
|
+
# follow GDK's redirects to it).
|
|
29
|
+
GDK_HOSTNAME = "gdk.local"
|
|
30
|
+
|
|
25
31
|
CONTAINER_PREFIX = "gdkbox-"
|
|
26
32
|
HOST_ALIAS_PREFIX = "gdkbox-"
|
|
27
33
|
|
data/lib/gdkbox/docker.rb
CHANGED
|
@@ -15,6 +15,11 @@ module GDKBox
|
|
|
15
15
|
!@shell.which("docker").nil?
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# Whether the image is already present locally.
|
|
19
|
+
def image_exists?(image)
|
|
20
|
+
@shell.run("docker", "image", "inspect", image).success?
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
# Pull an image. With no block the pull runs quietly and the (captured)
|
|
19
24
|
# Result is returned. With a block the pull is streamed and the block is
|
|
20
25
|
# called with a PullProgress after every line of `docker pull` output, so a
|
|
@@ -187,9 +192,13 @@ module GDKBox
|
|
|
187
192
|
end
|
|
188
193
|
end
|
|
189
194
|
|
|
190
|
-
# Start a detached container, returning its id.
|
|
191
|
-
|
|
195
|
+
# Start a detached container, returning its id. When a hostname is given,
|
|
196
|
+
# Docker also maps it to the container's IP in the container's /etc/hosts
|
|
197
|
+
# and re-applies that mapping on every start (a manual edit would be lost:
|
|
198
|
+
# Docker regenerates the file when the container starts).
|
|
199
|
+
def run_container(name:, image:, publish: [], labels: {}, env: {}, args: [], hostname: nil)
|
|
192
200
|
cmd = ["docker", "run", "-d", "--name", name]
|
|
201
|
+
cmd.push("--hostname", hostname) if hostname
|
|
193
202
|
labels.each { |key, value| cmd.push("--label", "#{key}=#{value}") }
|
|
194
203
|
env.each { |key, value| cmd.push("-e", "#{key}=#{value}") }
|
|
195
204
|
publish.each { |mapping| cmd.push("-p", mapping) }
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GDKBox
|
|
4
|
+
# Manages the gdk.local entry in the host's /etc/hosts.
|
|
5
|
+
#
|
|
6
|
+
# GDK generates URLs and redirects that use the `gdk.local` hostname, so the
|
|
7
|
+
# host's browser needs it mapped to loopback to reach a box's web UI. The
|
|
8
|
+
# line gdkbox adds is tagged with a marker comment so it can be recognized
|
|
9
|
+
# and removed later without touching entries the user wrote themselves.
|
|
10
|
+
#
|
|
11
|
+
# /etc/hosts is root-owned: writes are attempted directly first (covers
|
|
12
|
+
# tests and unusual setups), then via `sudo tee`. Callers get a symbol
|
|
13
|
+
# describing what happened — :present/:added/:removed/:absent/:manual —
|
|
14
|
+
# and decide how to talk to the user; :manual means nothing was changed
|
|
15
|
+
# and the user has to edit the file themselves.
|
|
16
|
+
class HostsFile
|
|
17
|
+
HOSTNAME = Config::GDK_HOSTNAME
|
|
18
|
+
MARKER = "# added by gdkbox"
|
|
19
|
+
ENTRY = "127.0.0.1 #{HOSTNAME} #{MARKER}"
|
|
20
|
+
|
|
21
|
+
attr_reader :path
|
|
22
|
+
|
|
23
|
+
def initialize(path: "/etc/hosts", shell: Shell.new)
|
|
24
|
+
@path = path
|
|
25
|
+
@shell = shell
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Whether any active (non-comment) line already maps HOSTNAME, regardless
|
|
29
|
+
# of who added it — a user's own entry counts, so we never duplicate it.
|
|
30
|
+
def entry?
|
|
31
|
+
return false unless File.readable?(path)
|
|
32
|
+
|
|
33
|
+
File.foreach(path).any? { |line| maps_hostname?(line) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Ensure the entry exists. Returns :present when it already does, :added
|
|
37
|
+
# on success, or :manual when the file could not be written (with sudo
|
|
38
|
+
# declined/failed or disabled).
|
|
39
|
+
def add(sudo: true)
|
|
40
|
+
return :present if entry?
|
|
41
|
+
|
|
42
|
+
File.open(path, "a") { |f| f.puts(ENTRY) }
|
|
43
|
+
:added
|
|
44
|
+
rescue Errno::EACCES
|
|
45
|
+
return :manual unless sudo
|
|
46
|
+
|
|
47
|
+
result = @shell.run("sudo", "tee", "-a", path, input: "#{ENTRY}\n")
|
|
48
|
+
result.success? ? :added : :manual
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Remove the gdkbox-managed entry (matched by MARKER; user-written lines
|
|
52
|
+
# are never touched). Returns :absent when there is nothing to remove,
|
|
53
|
+
# :removed on success, or :manual when the file could not be written.
|
|
54
|
+
def remove(sudo: true)
|
|
55
|
+
lines = File.readable?(path) ? File.readlines(path) : []
|
|
56
|
+
kept = lines.reject { |line| managed?(line) }
|
|
57
|
+
return :absent if kept.size == lines.size
|
|
58
|
+
|
|
59
|
+
content = kept.join
|
|
60
|
+
begin
|
|
61
|
+
File.write(path, content)
|
|
62
|
+
:removed
|
|
63
|
+
rescue Errno::EACCES
|
|
64
|
+
return :manual unless sudo
|
|
65
|
+
|
|
66
|
+
result = @shell.run("sudo", "tee", path, input: content)
|
|
67
|
+
result.success? ? :removed : :manual
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# Does this line actively map HOSTNAME? Comments are stripped first, so a
|
|
74
|
+
# commented-out entry does not count.
|
|
75
|
+
def maps_hostname?(line)
|
|
76
|
+
fields = line.sub(/#.*/, "").split
|
|
77
|
+
fields.length >= 2 && fields[1..].include?(HOSTNAME)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def managed?(line)
|
|
81
|
+
line.include?(MARKER) && line.include?(HOSTNAME)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/gdkbox/ports.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module GDKBox
|
|
6
|
+
# Answers whether a host port is actually usable.
|
|
7
|
+
#
|
|
8
|
+
# gdkbox's metadata store only knows about ports gdkbox itself allocated;
|
|
9
|
+
# anything else on the machine — typically a locally running GDK on 2222 and
|
|
10
|
+
# 3000 — is invisible to it. Binding is the authoritative test: gdkbox
|
|
11
|
+
# publishes container ports on 127.0.0.1, so try to bind exactly there.
|
|
12
|
+
module Ports
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# True when something on the host already holds the port (or the bind is
|
|
16
|
+
# not permitted, which is just as unusable for publishing).
|
|
17
|
+
def bound?(port)
|
|
18
|
+
TCPServer.new("127.0.0.1", port).close
|
|
19
|
+
false
|
|
20
|
+
rescue SystemCallError
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/gdkbox/version.rb
CHANGED
data/lib/gdkbox.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gdkbox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- jotolo
|
|
@@ -62,6 +62,8 @@ files:
|
|
|
62
62
|
- lib/gdkbox/config.rb
|
|
63
63
|
- lib/gdkbox/docker.rb
|
|
64
64
|
- lib/gdkbox/harness.rb
|
|
65
|
+
- lib/gdkbox/hosts_file.rb
|
|
66
|
+
- lib/gdkbox/ports.rb
|
|
65
67
|
- lib/gdkbox/progress_bar.rb
|
|
66
68
|
- lib/gdkbox/provisioner.rb
|
|
67
69
|
- lib/gdkbox/shell.rb
|
|
@@ -72,9 +74,14 @@ files:
|
|
|
72
74
|
- lib/gdkbox/version.rb
|
|
73
75
|
- lib/gdkbox/vscode.rb
|
|
74
76
|
- skills/gdkbox-fleet/SKILL.md
|
|
77
|
+
homepage: https://gitlab.com/jotolo_gl/gdkbox
|
|
75
78
|
licenses:
|
|
76
79
|
- MIT
|
|
77
|
-
metadata:
|
|
80
|
+
metadata:
|
|
81
|
+
source_code_uri: https://gitlab.com/jotolo_gl/gdkbox
|
|
82
|
+
bug_tracker_uri: https://gitlab.com/jotolo_gl/gdkbox/-/issues
|
|
83
|
+
documentation_uri: https://gitlab.com/jotolo_gl/gdkbox/-/blob/main/README.md
|
|
84
|
+
rubygems_mfa_required: 'true'
|
|
78
85
|
rdoc_options: []
|
|
79
86
|
require_paths:
|
|
80
87
|
- lib
|