gdkbox 0.1.8 → 0.1.10
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 +3 -0
- data/lib/gdkbox/box.rb +53 -0
- data/lib/gdkbox/cli.rb +69 -0
- data/lib/gdkbox/completion.rb +2 -1
- data/lib/gdkbox/config.rb +14 -0
- data/lib/gdkbox/provisioner.rb +41 -0
- data/lib/gdkbox/ssh_config.rb +5 -0
- 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: 75c5c8ed1f89cb877856f68e45c39f4b5460131ea5a312e5af10e7cb7de2688b
|
|
4
|
+
data.tar.gz: a0eb3697bb1b630665be5799d33ffb8bb4aec0999858e075861e3469bd312431
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 344bba38a105dcf4bf899893d18eb7ef9f82ef03ef6d2229af810346e27f18b24c46e09c02c7f83e406255f79e8d033ff37c159ced868aad284e540c6cf7948c
|
|
7
|
+
data.tar.gz: ce33e8b7bd1aa403ea5fe77127b7c46fd1c4eb7079736689cc6447b6b0649bd270b880db82d31a33bb0d090f818366b1e2de847e6b3d70f918dbae257200014a
|
data/README.md
CHANGED
|
@@ -90,6 +90,8 @@ Or run it straight from the checkout without installing:
|
|
|
90
90
|
| `gdkbox claude NAME` | Install Claude Code inside the box. |
|
|
91
91
|
| `gdkbox set-key NAME` | Seed/rotate the Anthropic API key in the box for unattended dispatch. |
|
|
92
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`. |
|
|
93
|
+
| `gdkbox set-git NAME` | Seed your git identity (`user.name`/`user.email`) into the box so `git commit` works. Also runs during `up`. |
|
|
94
|
+
| `gdkbox set-remote NAME [REMOTE]` | Point the box's GitLab checkout at a different remote (URL or `namespace/project`, e.g. `gitlab-org/gitlab`); default from config.yml. |
|
|
93
95
|
| `gdkbox start NAME` | Start a stopped box (and re-enable SSH). |
|
|
94
96
|
| `gdkbox stop NAME` | Stop a running box. |
|
|
95
97
|
| `gdkbox rm NAME` | Remove a box: container, metadata, and SSH entry. |
|
|
@@ -110,6 +112,7 @@ Or run it straight from the checkout without installing:
|
|
|
110
112
|
| `--harness ID` | `claude` (or config.yml) | Agent harness to install: claude, codex, opencode, pi. |
|
|
111
113
|
| `--no-agent` | (agent installed) | Skip installing the agent harness. |
|
|
112
114
|
| `--skill NAME [NAME...]` | (none) | Seed skill(s) into the box at creation for dispatched agents. |
|
|
115
|
+
| `--gitlab-remote REMOTE` | config.yml, else image default | Point the GitLab checkout at this remote (URL or `namespace/project`); the image ships pointing at the community mirror. |
|
|
113
116
|
|
|
114
117
|
## Typical workflow
|
|
115
118
|
|
data/lib/gdkbox/box.rb
CHANGED
|
@@ -137,6 +137,15 @@ module GDKBox
|
|
|
137
137
|
)
|
|
138
138
|
@store.save(@data)
|
|
139
139
|
|
|
140
|
+
# Best-effort: a host without a git identity (or a transient exec
|
|
141
|
+
# failure) should not abort the box; `gdkbox set-git` can seed it later.
|
|
142
|
+
begin
|
|
143
|
+
git_name, git_email = host_git_identity
|
|
144
|
+
provisioner.setup_git_identity(cname, name: git_name, email: git_email) if git_name || git_email
|
|
145
|
+
rescue StandardError
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
|
|
140
149
|
provisioner.setup_agent(cname, harness) if install_agent
|
|
141
150
|
@data["agent_installed"] = install_agent
|
|
142
151
|
|
|
@@ -166,6 +175,41 @@ module GDKBox
|
|
|
166
175
|
@store.delete(name)
|
|
167
176
|
end
|
|
168
177
|
|
|
178
|
+
# The full git URL for a remote given as a URL or a "namespace/project"
|
|
179
|
+
# shorthand (expanded against gitlab.com over SSH, so pushes ride the
|
|
180
|
+
# forwarded agent).
|
|
181
|
+
def self.expand_remote(token)
|
|
182
|
+
return token if token.include?("://") || token.include?(":")
|
|
183
|
+
|
|
184
|
+
"git@gitlab.com:#{token}.git"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Point the box's GitLab checkout at a different remote (URL or
|
|
188
|
+
# "namespace/project"). Returns the URL that origin now uses.
|
|
189
|
+
def set_gitlab_remote!(remote)
|
|
190
|
+
url = self.class.expand_remote(remote)
|
|
191
|
+
Provisioner.new(docker: @docker, config: @config)
|
|
192
|
+
.setup_gitlab_remote(container_name, url)
|
|
193
|
+
url
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Seed a git identity into the box so `git commit` works there. Falls back
|
|
197
|
+
# to the host's `git config` when name/email are not given; raises when
|
|
198
|
+
# neither source has anything to seed. Returns the [name, email] seeded.
|
|
199
|
+
def set_git_identity!(git_name: nil, git_email: nil)
|
|
200
|
+
host_name, host_email = host_git_identity
|
|
201
|
+
git_name ||= host_name
|
|
202
|
+
git_email ||= host_email
|
|
203
|
+
if git_name.nil? && git_email.nil?
|
|
204
|
+
raise Error, "No git identity found. Pass --name/--email or set " \
|
|
205
|
+
"`git config --global user.name/user.email` on the host."
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
Provisioner.new(docker: @docker, config: @config)
|
|
209
|
+
.setup_git_identity(container_name, name: git_name, email: git_email)
|
|
210
|
+
[git_name, git_email]
|
|
211
|
+
end
|
|
212
|
+
|
|
169
213
|
# (Re)install this box's agent harness inside the container.
|
|
170
214
|
def install_agent!
|
|
171
215
|
Provisioner.new(docker: @docker, config: @config).setup_agent(container_name, harness)
|
|
@@ -267,6 +311,15 @@ module GDKBox
|
|
|
267
311
|
|
|
268
312
|
private
|
|
269
313
|
|
|
314
|
+
# The host's git identity as [name, email], each nil when unset.
|
|
315
|
+
def host_git_identity
|
|
316
|
+
read = lambda do |key|
|
|
317
|
+
value = @shell.run("git", "config", "--get", key).stdout.strip
|
|
318
|
+
value.empty? ? nil : value
|
|
319
|
+
end
|
|
320
|
+
[read.call("user.name"), read.call("user.email")]
|
|
321
|
+
end
|
|
322
|
+
|
|
270
323
|
# Choose free host ports and persist a preliminary record claiming them,
|
|
271
324
|
# all while holding an exclusive lock so concurrent `create!` calls in
|
|
272
325
|
# separate processes serialize and cannot pick the same ports. Returns the
|
data/lib/gdkbox/cli.rb
CHANGED
|
@@ -37,6 +37,9 @@ module GDKBox
|
|
|
37
37
|
desc: "Skill name(s) or path(s) to install into the box for dispatched agents"
|
|
38
38
|
option :default_skills, type: :boolean, default: true,
|
|
39
39
|
desc: "Also install the default skills from ~/.gdkbox/config.yml"
|
|
40
|
+
option :gitlab_remote, type: :string,
|
|
41
|
+
desc: "Point the GitLab checkout at this remote (URL or namespace/project; " \
|
|
42
|
+
"default from config.yml, else the image's community mirror)"
|
|
40
43
|
def up(name)
|
|
41
44
|
ensure_docker!
|
|
42
45
|
box = build_box(name)
|
|
@@ -55,6 +58,7 @@ module GDKBox
|
|
|
55
58
|
)
|
|
56
59
|
rewrite_ssh_config
|
|
57
60
|
seed_skills(box)
|
|
61
|
+
repoint_gitlab_remote(box)
|
|
58
62
|
|
|
59
63
|
if options[:json]
|
|
60
64
|
puts JSON.generate(box.summary)
|
|
@@ -236,6 +240,54 @@ module GDKBox
|
|
|
236
240
|
end
|
|
237
241
|
map "set-key" => :set_key
|
|
238
242
|
|
|
243
|
+
desc "set-git NAME", "Seed your git identity (user.name/email) into the box"
|
|
244
|
+
long_desc <<~DESC
|
|
245
|
+
Copies your git identity into the box's global git config so `git
|
|
246
|
+
commit` works there. Defaults to the host's `git config user.name` and
|
|
247
|
+
`user.email`; override either with --name/--email. Runs automatically
|
|
248
|
+
during `gdkbox up`, so this is mainly for boxes created before that or
|
|
249
|
+
for changing the identity later.
|
|
250
|
+
|
|
251
|
+
Pushing uses your host's ssh-agent, forwarded into interactive sessions
|
|
252
|
+
(`gdkbox ssh` / VS Code) by the generated SSH config — no keys or tokens
|
|
253
|
+
are stored in the box.
|
|
254
|
+
DESC
|
|
255
|
+
option :name, type: :string, desc: "git user.name to seed (defaults to the host's)"
|
|
256
|
+
option :email, type: :string, desc: "git user.email to seed (defaults to the host's)"
|
|
257
|
+
def set_git(name)
|
|
258
|
+
box = load_box!(name)
|
|
259
|
+
git_name, git_email = box.set_git_identity!(
|
|
260
|
+
git_name: options[:name], git_email: options[:email]
|
|
261
|
+
)
|
|
262
|
+
say "Seeded git identity into '#{name}': #{[git_name, git_email].compact.join(' <')}#{'>' if git_email}", :green
|
|
263
|
+
end
|
|
264
|
+
map "set-git" => :set_git
|
|
265
|
+
|
|
266
|
+
desc "set-remote NAME [REMOTE]", "Point the box's GitLab checkout at a different remote"
|
|
267
|
+
long_desc <<~DESC
|
|
268
|
+
The GDK-in-a-box image clones GitLab from the community mirror
|
|
269
|
+
(gitlab-community/gitlab-org/gitlab). This repoints the checkout's
|
|
270
|
+
origin — pass a full git URL or a namespace/project shorthand (expanded
|
|
271
|
+
to git@gitlab.com:namespace/project.git), or set `gitlab_remote:` in
|
|
272
|
+
~/.gdkbox/config.yml and omit REMOTE. Runs `git remote set-url` plus a
|
|
273
|
+
fetch inside the box; `gdkbox up` does the same automatically when a
|
|
274
|
+
remote is configured. Pushing uses your forwarded ssh-agent and your own
|
|
275
|
+
permissions — no credentials are stored in the box.
|
|
276
|
+
DESC
|
|
277
|
+
def set_remote(name, remote = nil)
|
|
278
|
+
box = load_box!(name)
|
|
279
|
+
remote ||= config.default_gitlab_remote
|
|
280
|
+
unless remote
|
|
281
|
+
raise Error, "No remote. Pass REMOTE (URL or namespace/project) " \
|
|
282
|
+
"or set `gitlab_remote:` in ~/.gdkbox/config.yml."
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
say "Pointing '#{name}' GitLab checkout at #{Box.expand_remote(remote)} (fetching)...", :green
|
|
286
|
+
url = box.set_gitlab_remote!(remote)
|
|
287
|
+
say "origin now #{url}.", :green
|
|
288
|
+
end
|
|
289
|
+
map "set-remote" => :set_remote
|
|
290
|
+
|
|
239
291
|
desc "set-host", "Add (default) or remove the gdk.local entry in /etc/hosts"
|
|
240
292
|
long_desc <<~DESC
|
|
241
293
|
GDK generates URLs and redirects that use the `gdk.local` hostname, so
|
|
@@ -412,6 +464,23 @@ module GDKBox
|
|
|
412
464
|
end
|
|
413
465
|
end
|
|
414
466
|
|
|
467
|
+
# Point the new box's GitLab checkout at the configured remote, when one
|
|
468
|
+
# is set (--gitlab-remote wins over config.yml; no setting keeps the
|
|
469
|
+
# image's community mirror). Best-effort like seed_skills: the box is
|
|
470
|
+
# already up, so a bad remote should warn, not abort. Muted in JSON mode.
|
|
471
|
+
def repoint_gitlab_remote(box)
|
|
472
|
+
remote = options[:gitlab_remote] || config.default_gitlab_remote
|
|
473
|
+
return unless remote
|
|
474
|
+
|
|
475
|
+
url = box.set_gitlab_remote!(remote)
|
|
476
|
+
say "Pointed the GitLab checkout at #{url}.", :green unless options[:json]
|
|
477
|
+
rescue StandardError => e
|
|
478
|
+
unless options[:json]
|
|
479
|
+
say "Could not repoint the GitLab checkout: #{e.message}", :yellow
|
|
480
|
+
say " Retry later with: gdkbox set-remote #{box.name} #{remote}", :yellow
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
415
484
|
# Ensure gdk.local resolves on the host so the box's web UI is reachable
|
|
416
485
|
# (GDK redirects to that hostname). Adding the entry needs sudo, so warn
|
|
417
486
|
# before any password prompt appears. Failures are non-fatal: the box is
|
data/lib/gdkbox/completion.rb
CHANGED
|
@@ -11,7 +11,8 @@ module GDKBox
|
|
|
11
11
|
SHELLS = %w[bash zsh].freeze
|
|
12
12
|
|
|
13
13
|
# Subcommands whose first positional argument is an existing box.
|
|
14
|
-
BOX_COMMANDS = %w[status dispatch ssh code install-agent set-key
|
|
14
|
+
BOX_COMMANDS = %w[status dispatch ssh code install-agent set-key set-git set-remote start
|
|
15
|
+
stop rm add-skill].freeze
|
|
15
16
|
|
|
16
17
|
def initialize(cli_class = CLI)
|
|
17
18
|
@cli = cli_class
|
data/lib/gdkbox/config.rb
CHANGED
|
@@ -14,6 +14,7 @@ module GDKBox
|
|
|
14
14
|
#
|
|
15
15
|
# # ~/.gdkbox/config.yml
|
|
16
16
|
# image: registry.example.com/my-gdk:latest # default image for `up`
|
|
17
|
+
# gitlab_remote: gitlab-org/gitlab # repoint the GitLab checkout
|
|
17
18
|
# skills: # transferred into every box
|
|
18
19
|
# - gdkbox-fleet
|
|
19
20
|
# - code-history
|
|
@@ -102,6 +103,19 @@ module GDKBox
|
|
|
102
103
|
key.empty? ? Harness.default : key
|
|
103
104
|
end
|
|
104
105
|
|
|
106
|
+
# The git remote to point each box's GitLab checkout at, from config.yml
|
|
107
|
+
# (`gitlab_remote:`), or nil to keep the image's default (the community
|
|
108
|
+
# mirror). A URL or a "namespace/project" shorthand.
|
|
109
|
+
def default_gitlab_remote
|
|
110
|
+
remote = file_settings["gitlab_remote"].to_s.strip
|
|
111
|
+
remote.empty? ? nil : remote
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Where the GitLab repository lives inside the box.
|
|
115
|
+
def gitlab_checkout_path
|
|
116
|
+
File.join(remote_path, "gitlab")
|
|
117
|
+
end
|
|
118
|
+
|
|
105
119
|
# An API key read from config.yml under `config_key` (e.g.
|
|
106
120
|
# "anthropic_api_key", "openai_api_key"), or nil. This is a plaintext
|
|
107
121
|
# secret on disk, so it is the lowest-priority source (a flag or the
|
data/lib/gdkbox/provisioner.rb
CHANGED
|
@@ -131,6 +131,27 @@ module GDKBox
|
|
|
131
131
|
done
|
|
132
132
|
BASH
|
|
133
133
|
|
|
134
|
+
# Seeds the GDK user's global git identity so `git commit` works inside
|
|
135
|
+
# the box. Values arrive via the environment (dodging shell quoting) and
|
|
136
|
+
# blanks are skipped, so a partial host config seeds what it can.
|
|
137
|
+
GIT_IDENTITY_SETUP = <<~'BASH'
|
|
138
|
+
set -e
|
|
139
|
+
[ -n "$GDKBOX_GIT_NAME" ] && git config --global user.name "$GDKBOX_GIT_NAME"
|
|
140
|
+
[ -n "$GDKBOX_GIT_EMAIL" ] && git config --global user.email "$GDKBOX_GIT_EMAIL"
|
|
141
|
+
true
|
|
142
|
+
BASH
|
|
143
|
+
|
|
144
|
+
# Points the GitLab checkout's origin at a different repository. The image
|
|
145
|
+
# hardcodes the community mirror (gitlab-community/gitlab-org/gitlab);
|
|
146
|
+
# team members typically want the canonical gitlab-org/gitlab. The mirror
|
|
147
|
+
# shares its history, so a set-url plus fetch is all it takes (the
|
|
148
|
+
# checkout is treeless, keeping the fetch cheap).
|
|
149
|
+
GITLAB_REMOTE_SETUP = <<~'BASH'
|
|
150
|
+
set -e
|
|
151
|
+
git remote set-url origin "$GDKBOX_GITLAB_REMOTE"
|
|
152
|
+
git fetch --quiet origin
|
|
153
|
+
BASH
|
|
154
|
+
|
|
134
155
|
def initialize(docker:, config:)
|
|
135
156
|
@docker = docker
|
|
136
157
|
@config = config
|
|
@@ -168,6 +189,26 @@ module GDKBox
|
|
|
168
189
|
)
|
|
169
190
|
end
|
|
170
191
|
|
|
192
|
+
def setup_git_identity(container_name, name:, email:)
|
|
193
|
+
@docker.exec(
|
|
194
|
+
container_name, GIT_IDENTITY_SETUP,
|
|
195
|
+
user: @config.ssh_user,
|
|
196
|
+
env: {
|
|
197
|
+
"GDKBOX_GIT_NAME" => name.to_s,
|
|
198
|
+
"GDKBOX_GIT_EMAIL" => email.to_s
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def setup_gitlab_remote(container_name, url)
|
|
204
|
+
@docker.exec(
|
|
205
|
+
container_name, GITLAB_REMOTE_SETUP,
|
|
206
|
+
user: @config.ssh_user,
|
|
207
|
+
workdir: @config.gitlab_checkout_path,
|
|
208
|
+
env: { "GDKBOX_GITLAB_REMOTE" => url }
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
171
212
|
def setup_api_key(container_name, api_key, key_env)
|
|
172
213
|
@docker.exec(
|
|
173
214
|
container_name, API_KEY_SETUP,
|
data/lib/gdkbox/ssh_config.rb
CHANGED
|
@@ -32,6 +32,11 @@ module GDKBox
|
|
|
32
32
|
lines << " User #{box['ssh_user']}"
|
|
33
33
|
lines << " IdentityFile #{@config.private_key_path}"
|
|
34
34
|
lines << " IdentitiesOnly yes"
|
|
35
|
+
# Forward the host's ssh-agent so `git push` inside the box uses the
|
|
36
|
+
# user's own keys — which never enter the container, and are only
|
|
37
|
+
# reachable while a human session is connected (dispatched agents run
|
|
38
|
+
# via `docker exec` and never see the agent).
|
|
39
|
+
lines << " ForwardAgent yes"
|
|
35
40
|
lines << " StrictHostKeyChecking no"
|
|
36
41
|
lines << " UserKnownHostsFile /dev/null"
|
|
37
42
|
lines << ""
|
data/lib/gdkbox/version.rb
CHANGED