gdkbox 0.1.15 → 0.1.16
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 +1 -0
- data/lib/gdkbox/box.rb +35 -8
- data/lib/gdkbox/cli.rb +14 -2
- data/lib/gdkbox/config.rb +8 -0
- data/lib/gdkbox/provisioner.rb +31 -0
- data/lib/gdkbox/store.rb +1 -1
- data/lib/gdkbox/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee5748a17847e4b6d1390b5a12d52c964cc5ce5a5ec4cf2f4ede5dbc093d39c6
|
|
4
|
+
data.tar.gz: 000363cc6e8a0c5ef47bc26ca86556a16188c8cf7ab91aeae64698db6c0f01d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cbe190b3bebaeca9e0ae149d1e3f3ce542168f757a808c2f6efc005773393463fc4b8cc9f5079a1cd072ff1102fb6109691efcd76072251f7e05bbfb32cc4b5
|
|
7
|
+
data.tar.gz: 8274f14772efd5630fd685366c22c34c08ef3561af0faf0e563d145616a6a789bb715edfc631db6ae0d096ef60d758a0d0dced17c141288c09a421458831a702
|
data/README.md
CHANGED
|
@@ -112,6 +112,7 @@ Or run it straight from the checkout without installing:
|
|
|
112
112
|
| `--image` | official GDK image | Override the container image. |
|
|
113
113
|
| `--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). |
|
|
114
114
|
| `--web-port` | next free from 3000 | Host port to publish the GDK web UI on. Auto-allocation skips ports in use on the host. |
|
|
115
|
+
| `--vite-port` | next free from 3038 | Port for vite assets. Published with the **same number inside and outside** the box (asset URLs embed it); GDK in the box is reconfigured to match. |
|
|
115
116
|
| `--harness ID` | `claude` (or config.yml) | Agent harness to install: claude, codex, opencode, pi. |
|
|
116
117
|
| `--no-agent` | (agent installed) | Skip installing the agent harness. |
|
|
117
118
|
| `--skill NAME [NAME...]` | (none) | Seed skill(s) into the box at creation for dispatched agents. |
|
data/lib/gdkbox/box.rb
CHANGED
|
@@ -56,6 +56,10 @@ module GDKBox
|
|
|
56
56
|
data && data["web_port"]
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
def vite_port
|
|
60
|
+
data && data["vite_port"]
|
|
61
|
+
end
|
|
62
|
+
|
|
59
63
|
def web_url
|
|
60
64
|
"http://127.0.0.1:#{web_port}"
|
|
61
65
|
end
|
|
@@ -85,8 +89,9 @@ module GDKBox
|
|
|
85
89
|
# fields go into the same locked store write as the port reservation, so
|
|
86
90
|
# there is no window in which another orchestrator's `claim` can grab a
|
|
87
91
|
# box this one is still provisioning.
|
|
88
|
-
def create!(image: nil, ssh_port: nil, web_port: nil,
|
|
89
|
-
|
|
92
|
+
def create!(image: nil, ssh_port: nil, web_port: nil, vite_port: nil,
|
|
93
|
+
harness: Harness.default, install_agent: true, api_key: nil,
|
|
94
|
+
claim_owner: nil, claim_ttl: nil)
|
|
90
95
|
raise Error, "Box '#{name}' already exists" if exists?
|
|
91
96
|
|
|
92
97
|
harness = Harness[harness]
|
|
@@ -104,8 +109,9 @@ module GDKBox
|
|
|
104
109
|
# port and collide at `docker run`. Holding an exclusive lock while we
|
|
105
110
|
# choose ports *and* persist a preliminary record makes each sibling see
|
|
106
111
|
# the others' reservations.
|
|
107
|
-
ssh_port, web_port = reserve_ports!(
|
|
108
|
-
cname, ssh_port, web_port,
|
|
112
|
+
ssh_port, web_port, vite_port = reserve_ports!(
|
|
113
|
+
cname, ssh_port, web_port, vite_port,
|
|
114
|
+
claim_owner: claim_owner, claim_ttl: claim_ttl
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
begin
|
|
@@ -118,7 +124,10 @@ module GDKBox
|
|
|
118
124
|
hostname: Config::GDK_HOSTNAME,
|
|
119
125
|
publish: [
|
|
120
126
|
"127.0.0.1:#{ssh_port}:#{Config::SSH_CONTAINER_PORT}",
|
|
121
|
-
"127.0.0.1:#{web_port}:#{Config::GDK_WEB_CONTAINER_PORT}"
|
|
127
|
+
"127.0.0.1:#{web_port}:#{Config::GDK_WEB_CONTAINER_PORT}",
|
|
128
|
+
# Same number on both sides: asset URLs embed this port, so what
|
|
129
|
+
# the browser dials must be where vite listens (see VITE_PORT_BASE).
|
|
130
|
+
"127.0.0.1:#{vite_port}:#{vite_port}"
|
|
122
131
|
],
|
|
123
132
|
labels: { "gdkbox" => "true", "gdkbox.name" => name }
|
|
124
133
|
)
|
|
@@ -145,6 +154,17 @@ module GDKBox
|
|
|
145
154
|
)
|
|
146
155
|
@store.save(@data)
|
|
147
156
|
|
|
157
|
+
# Align vite's in-container listen port with the published one so asset
|
|
158
|
+
# URLs work from the host browser. Best-effort — the box is fully
|
|
159
|
+
# usable for SSH/agent work without it — but the outcome is recorded
|
|
160
|
+
# so `status`/`ls --json` can surface a box whose assets won't load.
|
|
161
|
+
@data["vite_port_configured"] = begin
|
|
162
|
+
provisioner.setup_vite_port(cname, vite_port)
|
|
163
|
+
true
|
|
164
|
+
rescue StandardError
|
|
165
|
+
false
|
|
166
|
+
end
|
|
167
|
+
|
|
148
168
|
# Best-effort: a host without a git identity (or a transient exec
|
|
149
169
|
# failure) should not abort the box; `gdkbox set-git` can seed it later.
|
|
150
170
|
begin
|
|
@@ -454,6 +474,10 @@ module GDKBox
|
|
|
454
474
|
# Back-compat: orchestrators predating multi-harness check this field.
|
|
455
475
|
"claude_installed" => (harness.id == "claude" && (data && data["agent_installed"])) || false,
|
|
456
476
|
"api_key_set" => (data && data["api_key_set"]) || false,
|
|
477
|
+
# Vite assets: the published port (nil on boxes created before vite
|
|
478
|
+
# support) and whether the in-box alignment step succeeded.
|
|
479
|
+
"vite_port" => vite_port,
|
|
480
|
+
"vite_port_configured" => (data && data["vite_port_configured"]) || false,
|
|
457
481
|
# Active claim (nil when free; an expired lease counts as free).
|
|
458
482
|
"claimed_by" => claimed_by,
|
|
459
483
|
"claim_expires_at" => (claimed_by ? data["claim_expires_at"] : nil)
|
|
@@ -494,8 +518,9 @@ module GDKBox
|
|
|
494
518
|
# than the cryptic bind error `docker run` would produce later.
|
|
495
519
|
# A claim_owner is written into this same record, so the box is claimed
|
|
496
520
|
# from the instant it becomes visible to other processes.
|
|
497
|
-
def reserve_ports!(cname, ssh_port, web_port, claim_owner: nil, claim_ttl: nil)
|
|
498
|
-
{ "--ssh-port" => ssh_port, "--web-port" => web_port
|
|
521
|
+
def reserve_ports!(cname, ssh_port, web_port, vite_port, claim_owner: nil, claim_ttl: nil)
|
|
522
|
+
{ "--ssh-port" => ssh_port, "--web-port" => web_port,
|
|
523
|
+
"--vite-port" => vite_port }.each do |flag, port|
|
|
499
524
|
if port && Ports.bound?(port)
|
|
500
525
|
raise Error, "Port #{port} (#{flag}) is already in use on 127.0.0.1."
|
|
501
526
|
end
|
|
@@ -504,11 +529,13 @@ module GDKBox
|
|
|
504
529
|
with_create_lock do
|
|
505
530
|
ssh_port ||= next_port(Config::SSH_PORT_BASE)
|
|
506
531
|
web_port ||= next_port(Config::WEB_PORT_BASE, exclude: [ssh_port])
|
|
532
|
+
vite_port ||= next_port(Config::VITE_PORT_BASE, exclude: [ssh_port, web_port])
|
|
507
533
|
@data = {
|
|
508
534
|
"name" => name,
|
|
509
535
|
"container_name" => cname,
|
|
510
536
|
"ssh_port" => ssh_port,
|
|
511
537
|
"web_port" => web_port,
|
|
538
|
+
"vite_port" => vite_port,
|
|
512
539
|
"agent_installed" => false,
|
|
513
540
|
"api_key_set" => false
|
|
514
541
|
}
|
|
@@ -518,7 +545,7 @@ module GDKBox
|
|
|
518
545
|
@data["claim_expires_at"] = (Time.now.utc + claim_ttl).iso8601 if claim_ttl
|
|
519
546
|
end
|
|
520
547
|
@store.save(@data)
|
|
521
|
-
[ssh_port, web_port]
|
|
548
|
+
[ssh_port, web_port, vite_port]
|
|
522
549
|
end
|
|
523
550
|
end
|
|
524
551
|
|
data/lib/gdkbox/cli.rb
CHANGED
|
@@ -24,6 +24,9 @@ module GDKBox
|
|
|
24
24
|
option :image, type: :string, desc: "Override the GDK image to use"
|
|
25
25
|
option :ssh_port, type: :numeric, desc: "Host port to publish SSH on"
|
|
26
26
|
option :web_port, type: :numeric, desc: "Host port to publish the GDK web UI on"
|
|
27
|
+
option :vite_port, type: :numeric,
|
|
28
|
+
desc: "Port for vite assets (same number inside and outside the box; " \
|
|
29
|
+
"asset URLs embed it)"
|
|
27
30
|
option :harness, type: :string,
|
|
28
31
|
desc: "Agent harness to install (see `gdkbox harnesses`; default from config.yml or claude)"
|
|
29
32
|
option :agent, type: :boolean, default: true,
|
|
@@ -60,6 +63,7 @@ module GDKBox
|
|
|
60
63
|
image: options[:image],
|
|
61
64
|
ssh_port: options[:ssh_port],
|
|
62
65
|
web_port: options[:web_port],
|
|
66
|
+
vite_port: options[:vite_port],
|
|
63
67
|
harness: harness.id,
|
|
64
68
|
install_agent: options[:agent],
|
|
65
69
|
api_key: api_key,
|
|
@@ -78,6 +82,12 @@ module GDKBox
|
|
|
78
82
|
setup_host
|
|
79
83
|
|
|
80
84
|
say "\nBox '#{name}' is up.", :green
|
|
85
|
+
unless box.data["vite_port_configured"]
|
|
86
|
+
say "\n Could not align the box's vite port with the published " \
|
|
87
|
+
"#{box.vite_port} — browser asset loading may fail.", :yellow
|
|
88
|
+
say " Retry inside the box: gdk config set vite.port #{box.vite_port} " \
|
|
89
|
+
"&& gdk reconfigure && gdk restart vite", :yellow
|
|
90
|
+
end
|
|
81
91
|
print_connection_details(box)
|
|
82
92
|
unless api_key
|
|
83
93
|
say "\n No #{harness.key_env} seeded. Before unattended dispatch, run:", :yellow
|
|
@@ -135,8 +145,9 @@ module GDKBox
|
|
|
135
145
|
boxes.each do |box|
|
|
136
146
|
status = docker.available? ? box.state : "unknown"
|
|
137
147
|
claim = box.claimed_by ? " claimed:#{box.claimed_by}" : ""
|
|
138
|
-
say format("%-20s %-10s ssh:%-6s web:%-6s %s%s",
|
|
139
|
-
box.name, status, box.ssh_port, box.web_port, box.
|
|
148
|
+
say format("%-20s %-10s ssh:%-6s web:%-6s vite:%-6s %s%s",
|
|
149
|
+
box.name, status, box.ssh_port, box.web_port, box.vite_port || "-",
|
|
150
|
+
box.web_url, claim)
|
|
140
151
|
end
|
|
141
152
|
end
|
|
142
153
|
|
|
@@ -663,6 +674,7 @@ module GDKBox
|
|
|
663
674
|
say " SSH: ssh #{box.ssh_host_alias}", :cyan
|
|
664
675
|
say " VS Code: gdkbox code #{box.name}", :cyan
|
|
665
676
|
say " Web UI: #{box.web_url} (http://#{HostsFile::HOSTNAME}:#{box.web_port})", :cyan
|
|
677
|
+
say " Assets: vite on port #{box.vite_port}", :cyan if box.vite_port
|
|
666
678
|
say " Agent: ssh #{box.ssh_host_alias} -t #{box.harness.bin}", :cyan
|
|
667
679
|
end
|
|
668
680
|
end
|
data/lib/gdkbox/config.rb
CHANGED
|
@@ -40,6 +40,14 @@ module GDKBox
|
|
|
40
40
|
SSH_CONTAINER_PORT = 22
|
|
41
41
|
GDK_WEB_CONTAINER_PORT = 3000
|
|
42
42
|
|
|
43
|
+
# GDK's vite dev server default port. Unlike SSH/web, the vite port is
|
|
44
|
+
# published host:N -> container:N with the *same* number on both sides:
|
|
45
|
+
# GitLab embeds absolute asset URLs (http://gdk.local:<vite_port>/...) in
|
|
46
|
+
# its HTML, so the port the browser dials must be the port vite listens
|
|
47
|
+
# on inside the box. Each box gets the next free number from this base
|
|
48
|
+
# and its GDK is reconfigured to serve vite there.
|
|
49
|
+
VITE_PORT_BASE = 3038
|
|
50
|
+
|
|
43
51
|
# Starting points for the host-side published ports. Each new box claims
|
|
44
52
|
# the next free port at or above these bases.
|
|
45
53
|
SSH_PORT_BASE = 2222
|
data/lib/gdkbox/provisioner.rb
CHANGED
|
@@ -183,6 +183,28 @@ module GDKBox
|
|
|
183
183
|
git fetch --quiet origin
|
|
184
184
|
BASH
|
|
185
185
|
|
|
186
|
+
# Makes GDK serve vite on the box's published vite port. Asset URLs embed
|
|
187
|
+
# this port (http://gdk.local:<port>/assets/vite/...), so the in-container
|
|
188
|
+
# listen port must equal the host-published one. The reconfigure is
|
|
189
|
+
# skipped when the configured port already matches (the common case for
|
|
190
|
+
# the first box, which gets vite's default port); `gdk reconfigure` is
|
|
191
|
+
# what makes it slow otherwise.
|
|
192
|
+
#
|
|
193
|
+
# The restart must cover *all* services, not just vite: rails boots with
|
|
194
|
+
# the container and holds the old ViteRuby config, so with only vite
|
|
195
|
+
# restarted it probes the old port, decides the dev server is down, and
|
|
196
|
+
# every page 500s with ViteRuby::MissingEntrypointError. Services may not
|
|
197
|
+
# exist yet during first boot, hence the trailing `|| true`.
|
|
198
|
+
VITE_PORT_SETUP = <<~'BASH'
|
|
199
|
+
set -e
|
|
200
|
+
current=$(gdk config get vite.port 2>/dev/null || echo "")
|
|
201
|
+
if [ "$current" != "$GDKBOX_VITE_PORT" ]; then
|
|
202
|
+
gdk config set vite.port "$GDKBOX_VITE_PORT"
|
|
203
|
+
gdk reconfigure
|
|
204
|
+
gdk restart || true
|
|
205
|
+
fi
|
|
206
|
+
BASH
|
|
207
|
+
|
|
186
208
|
def initialize(docker:, config:)
|
|
187
209
|
@docker = docker
|
|
188
210
|
@config = config
|
|
@@ -220,6 +242,15 @@ module GDKBox
|
|
|
220
242
|
)
|
|
221
243
|
end
|
|
222
244
|
|
|
245
|
+
def setup_vite_port(container_name, port)
|
|
246
|
+
@docker.exec(
|
|
247
|
+
container_name, VITE_PORT_SETUP,
|
|
248
|
+
user: @config.ssh_user,
|
|
249
|
+
workdir: @config.remote_path,
|
|
250
|
+
env: { "GDKBOX_VITE_PORT" => port.to_s }
|
|
251
|
+
)
|
|
252
|
+
end
|
|
253
|
+
|
|
223
254
|
def setup_git_identity(container_name, name:, email:)
|
|
224
255
|
@docker.exec(
|
|
225
256
|
container_name, GIT_IDENTITY_SETUP,
|
data/lib/gdkbox/store.rb
CHANGED
|
@@ -41,7 +41,7 @@ module GDKBox
|
|
|
41
41
|
|
|
42
42
|
# Every host port already claimed by an existing box.
|
|
43
43
|
def used_ports
|
|
44
|
-
all.flat_map { |box| [box["ssh_port"], box["web_port"]] }.compact
|
|
44
|
+
all.flat_map { |box| [box["ssh_port"], box["web_port"], box["vite_port"]] }.compact
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
end
|
data/lib/gdkbox/version.rb
CHANGED