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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d9c5210ae4aa4a4b61bcbba7431b83759e39de9d2621dd93f27f51d7ead4b7b
4
- data.tar.gz: d60b1306ff0df5e95c32437574e4caa9be879b8ad735bae3af2092cc854d4d61
3
+ metadata.gz: ee5748a17847e4b6d1390b5a12d52c964cc5ce5a5ec4cf2f4ede5dbc093d39c6
4
+ data.tar.gz: 000363cc6e8a0c5ef47bc26ca86556a16188c8cf7ab91aeae64698db6c0f01d7
5
5
  SHA512:
6
- metadata.gz: 14b31086f6ae0df58074b07236dd6824afd1f0bba5bc76c5e5311e1847f4d962a7e989599805d72ea547a641f7afeac79b5c68321aedee5beafe8d025077327a
7
- data.tar.gz: 79423220439fd62c3d16cd0d266c8583f11696bb538e1c26cee8fbd20813cf623a2c027b80b9903dddf765091e74c279f9a54e18d45f78fefae9428ffceec802
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, harness: Harness.default,
89
- install_agent: true, api_key: nil, claim_owner: nil, claim_ttl: nil)
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, claim_owner: claim_owner, claim_ttl: claim_ttl
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 }.each do |flag, 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.web_url, claim)
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GDKBox
4
- VERSION = "0.1.15"
4
+ VERSION = "0.1.16"
5
5
  end
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.15
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - jotolo