boxdown 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.
- package/LICENSE +191 -0
- package/README.md +115 -0
- package/assets/devcontainer/README.md +177 -0
- package/assets/devcontainer/devcontainer.json +83 -0
- package/assets/devcontainer/hooks/initialize.sh +55 -0
- package/assets/devcontainer/hooks/post-create.sh +67 -0
- package/assets/devcontainer/hooks/post-start.sh +34 -0
- package/assets/devcontainer/ssh-config-install.sh +172 -0
- package/assets/devcontainer/start.sh +485 -0
- package/assets/devcontainer/utils/codex-cli-update.sh +9 -0
- package/assets/devcontainer/utils/coding-agent-cli-update.sh +360 -0
- package/assets/devcontainer/utils/deps-install.sh +87 -0
- package/assets/devcontainer/utils/ssh-bootstrap.sh +200 -0
- package/dist/bin/cli.cjs +12 -0
- package/dist/bin/cli.d.cts +1 -0
- package/dist/bin/cli.d.mts +1 -0
- package/dist/bin/cli.mjs +14 -0
- package/dist/bin/cli.mjs.map +1 -0
- package/dist/main-BuEptwlL.cjs +1707 -0
- package/dist/main-ZFTrSVgt.mjs +1685 -0
- package/dist/main-ZFTrSVgt.mjs.map +1 -0
- package/dist/main.cjs +7 -0
- package/dist/main.d.cts +24 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.mts +24 -0
- package/dist/main.d.mts.map +1 -0
- package/dist/main.mjs +3 -0
- package/docs/README.md +34 -0
- package/docs/architecture.md +75 -0
- package/docs/conventions.md +29 -0
- package/docs/development.md +59 -0
- package/docs/features/README.md +14 -0
- package/docs/features/generated-config-and-state.md +64 -0
- package/docs/features/github-auth-refresh.md +64 -0
- package/docs/features/lifecycle.md +64 -0
- package/docs/features/ssh-config-and-proxy.md +60 -0
- package/docs/features/start-and-shell.md +83 -0
- package/docs/testing.md +63 -0
- package/docs/todo.md +170 -0
- package/package.json +128 -0
- package/src/bin/cli.ts +9 -0
- package/src/coding-agents.ts +22 -0
- package/src/config.ts +81 -0
- package/src/constants.ts +9 -0
- package/src/devcontainer-cli.ts +57 -0
- package/src/devcontainer.ts +394 -0
- package/src/doctor.ts +139 -0
- package/src/github-git-auth.ts +143 -0
- package/src/jsonc.ts +123 -0
- package/src/list.ts +102 -0
- package/src/main.ts +362 -0
- package/src/metadata.ts +84 -0
- package/src/paths.ts +117 -0
- package/src/process.ts +94 -0
- package/src/shell.ts +60 -0
- package/src/ssh-config.ts +113 -0
- package/src/ssh-key.ts +52 -0
- package/src/status.ts +327 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# @file start.sh
|
|
4
|
+
# @summary Start the Dev Container for this workspace and attach an interactive shell or SSH proxy.
|
|
5
|
+
#
|
|
6
|
+
# Uses Boxdown's packaged @devcontainers/cli to run `devcontainer up` then either
|
|
7
|
+
# `devcontainer exec bash` or a portless SSH ProxyCommand backed by
|
|
8
|
+
# `docker exec ... sshd -i`.
|
|
9
|
+
# Host port publishing comes from **runArgs** in devcontainer.json (e.g. `-p` and
|
|
10
|
+
# `127.0.0.1::<containerPort>` for dynamic host binding). After `devcontainer up`, this
|
|
11
|
+
# script reads that container port from runArgs and prints the mapped URL via `docker port`
|
|
12
|
+
# in shell mode.
|
|
13
|
+
#
|
|
14
|
+
# @usage
|
|
15
|
+
# .devcontainer/start.sh [command] [options]
|
|
16
|
+
#
|
|
17
|
+
# @options
|
|
18
|
+
# --shell Start the dev container and attach an interactive shell (default).
|
|
19
|
+
# --ssh-proxy Start/reuse the dev container and proxy SSH over docker exec.
|
|
20
|
+
# ssh-config install
|
|
21
|
+
# Install/update the host SSH config alias and exit.
|
|
22
|
+
# --refresh-gh-token
|
|
23
|
+
# Start/reuse the dev container, then refresh container GitHub CLI
|
|
24
|
+
# auth from the host gh token when available.
|
|
25
|
+
# --refresh-gh-token-running
|
|
26
|
+
# Refresh container GitHub CLI auth from the host gh token, but only
|
|
27
|
+
# when the dev container is already running.
|
|
28
|
+
# --recreate Remove the existing dev container for this workspace before `up`, so
|
|
29
|
+
# changes to runArgs (or other create-time settings) take effect.
|
|
30
|
+
# --help, -h Print usage and exit.
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# .devcontainer/start.sh
|
|
34
|
+
# .devcontainer/start.sh ssh-config install
|
|
35
|
+
# .devcontainer/start.sh --ssh-proxy
|
|
36
|
+
# .devcontainer/start.sh --refresh-gh-token
|
|
37
|
+
# .devcontainer/start.sh --refresh-gh-token-running
|
|
38
|
+
# .devcontainer/start.sh --recreate
|
|
39
|
+
#
|
|
40
|
+
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
|
|
43
|
+
# GUI apps launched outside an interactive shell often do not inherit the user's
|
|
44
|
+
# shell PATH. Include the common macOS/Linux locations needed by Docker and Node.
|
|
45
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"
|
|
46
|
+
|
|
47
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
48
|
+
WORKSPACE_FOLDER="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
|
49
|
+
REPO_NAME="$(basename "$WORKSPACE_FOLDER")"
|
|
50
|
+
SSH_KEY_DIR="${DEVCONTAINER_SSH_KEY_DIR:-${SCRIPT_DIR}/.ssh}"
|
|
51
|
+
SSH_KEY_PATH="${DEVCONTAINER_SSH_KEY_PATH:-${SSH_KEY_DIR}/id_ed25519}"
|
|
52
|
+
MODE="shell"
|
|
53
|
+
RECREATE=false
|
|
54
|
+
CONTAINER_ID=""
|
|
55
|
+
|
|
56
|
+
usage() {
|
|
57
|
+
cat <<EOF
|
|
58
|
+
Usage: $(basename "$0") [command] [options]
|
|
59
|
+
|
|
60
|
+
Start the dev container for:
|
|
61
|
+
$WORKSPACE_FOLDER
|
|
62
|
+
|
|
63
|
+
By default, open an interactive bash session inside the container.
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
--shell Start the dev container and open an interactive shell (default).
|
|
67
|
+
--ssh-proxy Start/reuse the dev container and proxy SSH over docker exec.
|
|
68
|
+
Intended for SSH ProxyCommand; stdout is reserved for SSH traffic.
|
|
69
|
+
--refresh-gh-token
|
|
70
|
+
Start/reuse the dev container, then refresh container GitHub CLI
|
|
71
|
+
auth from the host gh token when available.
|
|
72
|
+
--refresh-gh-token-running
|
|
73
|
+
Refresh container GitHub CLI auth from the host gh token, but only
|
|
74
|
+
when the dev container is already running.
|
|
75
|
+
--recreate Remove the existing dev container for this workspace before starting,
|
|
76
|
+
so Docker picks up new settings (e.g. runArgs / port mappings).
|
|
77
|
+
--help, -h Show this help and exit.
|
|
78
|
+
|
|
79
|
+
Commands:
|
|
80
|
+
ssh-config install
|
|
81
|
+
Install/update the host SSH config alias and exit.
|
|
82
|
+
|
|
83
|
+
Notes:
|
|
84
|
+
Port publishing uses runArgs (Docker -p ...::<containerPort>). The script greps that
|
|
85
|
+
container port from devcontainer.json for the post-up URL hint.
|
|
86
|
+
|
|
87
|
+
The --ssh-proxy mode uses a repo-local identity at:
|
|
88
|
+
$SSH_KEY_PATH
|
|
89
|
+
|
|
90
|
+
The GitHub token refresh modes never start a host gh login flow. If host gh is
|
|
91
|
+
missing, logged out, or cannot provide a token, refresh is skipped.
|
|
92
|
+
EOF
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
log() {
|
|
96
|
+
if [ "$MODE" = "ssh-proxy" ]; then
|
|
97
|
+
printf '%s\n' "$*" >&2
|
|
98
|
+
else
|
|
99
|
+
printf '%s\n' "$*"
|
|
100
|
+
fi
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
die() {
|
|
104
|
+
printf '%s\n' "$*" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
devcontainer_cli() {
|
|
109
|
+
local cli_bin
|
|
110
|
+
local package_root
|
|
111
|
+
|
|
112
|
+
package_root="$(cd "$SCRIPT_DIR/../.." && pwd -P)"
|
|
113
|
+
cli_bin="$(
|
|
114
|
+
PACKAGE_ROOT="$package_root" node -e '
|
|
115
|
+
const { existsSync, readFileSync } = require("node:fs");
|
|
116
|
+
const { createRequire } = require("node:module");
|
|
117
|
+
const { dirname, resolve } = require("node:path");
|
|
118
|
+
|
|
119
|
+
const packageRoot = process.env.PACKAGE_ROOT;
|
|
120
|
+
const requireFromBoxdown = createRequire(`${packageRoot}/package.json`);
|
|
121
|
+
const packageJsonPath = requireFromBoxdown.resolve("@devcontainers/cli/package.json");
|
|
122
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
123
|
+
const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.devcontainer;
|
|
124
|
+
|
|
125
|
+
if (!bin) process.exit(1);
|
|
126
|
+
|
|
127
|
+
const cliPath = resolve(dirname(packageJsonPath), bin);
|
|
128
|
+
if (!existsSync(cliPath)) process.exit(1);
|
|
129
|
+
|
|
130
|
+
process.stdout.write(cliPath);
|
|
131
|
+
'
|
|
132
|
+
)" || die "Boxdown's packaged @devcontainers/cli dependency is missing. Reinstall boxdown."
|
|
133
|
+
|
|
134
|
+
node "$cli_bin" "$@"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Container-side TCP port published via runArgs, e.g. "127.0.0.1::3000" (Docker -p host::ctr).
|
|
138
|
+
publish_container_port_from_devcontainer_json() {
|
|
139
|
+
local f="$SCRIPT_DIR/devcontainer.json"
|
|
140
|
+
if [ ! -r "$f" ]; then
|
|
141
|
+
return 1
|
|
142
|
+
fi
|
|
143
|
+
sed 's|//.*||' "$f" | sed -nE 's/.*"[0-9.]+::([0-9]+)".*/\1/p' | sed -n '1p'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
parse_container_id_from_up_output() {
|
|
147
|
+
sed -nE 's/.*"containerId"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | sed -n '1p'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
find_running_container_id() {
|
|
151
|
+
docker ps --filter "label=devcontainer.local_folder=${WORKSPACE_FOLDER}" --format '{{.ID}}' | sed -n '1p'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
ensure_host_ssh_key() {
|
|
155
|
+
if ! command -v ssh-keygen >/dev/null 2>&1; then
|
|
156
|
+
die "ssh-keygen is required for devcontainer SSH setup."
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
mkdir -p "$SSH_KEY_DIR"
|
|
160
|
+
chmod 0700 "$SSH_KEY_DIR"
|
|
161
|
+
|
|
162
|
+
if [ ! -f "$SSH_KEY_PATH" ]; then
|
|
163
|
+
log "Generating devcontainer SSH identity: $SSH_KEY_PATH"
|
|
164
|
+
ssh-keygen -t ed25519 -f "$SSH_KEY_PATH" -N "" -C "${REPO_NAME}-devcontainer" >/dev/null
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
if [ ! -f "${SSH_KEY_PATH}.pub" ]; then
|
|
168
|
+
ssh-keygen -y -f "$SSH_KEY_PATH" > "${SSH_KEY_PATH}.pub"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
chmod 0600 "$SSH_KEY_PATH"
|
|
172
|
+
chmod 0644 "${SSH_KEY_PATH}.pub"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
install_ssh_config_alias() {
|
|
176
|
+
if [ ! -r "${SCRIPT_DIR}/ssh-config-install.sh" ]; then
|
|
177
|
+
die "Missing SSH config installer: ${SCRIPT_DIR}/ssh-config-install.sh"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
DEVCONTAINER_SSH_HOST_ALIAS="${DEVCONTAINER_SSH_HOST_ALIAS:-${REPO_NAME}-devcontainer}" \
|
|
181
|
+
bash "${SCRIPT_DIR}/ssh-config-install.sh" "$@"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
start_devcontainer() {
|
|
185
|
+
local up_output
|
|
186
|
+
local up_args
|
|
187
|
+
|
|
188
|
+
if [ "$MODE" = "ssh-proxy" ] && [ "$RECREATE" = false ]; then
|
|
189
|
+
CONTAINER_ID="$(find_running_container_id)"
|
|
190
|
+
if [ -n "$CONTAINER_ID" ]; then
|
|
191
|
+
log "Using running devcontainer for: $WORKSPACE_FOLDER"
|
|
192
|
+
return 0
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
log "Starting devcontainer for: $WORKSPACE_FOLDER"
|
|
197
|
+
|
|
198
|
+
up_args=(--workspace-folder "$WORKSPACE_FOLDER")
|
|
199
|
+
if [ "$RECREATE" = true ]; then
|
|
200
|
+
up_args+=(--remove-existing-container)
|
|
201
|
+
log "Removing existing dev container so create-time settings (e.g. runArgs) apply."
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
if [ "$MODE" = "ssh-proxy" ]; then
|
|
205
|
+
if ! up_output="$(devcontainer_cli up "${up_args[@]}" </dev/null 2>&1)"; then
|
|
206
|
+
[ -z "$up_output" ] || log "$up_output"
|
|
207
|
+
return 1
|
|
208
|
+
fi
|
|
209
|
+
elif ! up_output="$(devcontainer_cli up "${up_args[@]}" 2>&1)"; then
|
|
210
|
+
[ -z "$up_output" ] || log "$up_output"
|
|
211
|
+
return 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
log "$up_output"
|
|
215
|
+
|
|
216
|
+
CONTAINER_ID="$(printf '%s\n' "$up_output" | parse_container_id_from_up_output)"
|
|
217
|
+
if [ -z "$CONTAINER_ID" ]; then
|
|
218
|
+
CONTAINER_ID="$(find_running_container_id)"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if [ -z "$CONTAINER_ID" ]; then
|
|
222
|
+
die "Could not resolve devcontainer ID for: $WORKSPACE_FOLDER"
|
|
223
|
+
fi
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
host_gh_token_or_empty() {
|
|
227
|
+
local token
|
|
228
|
+
|
|
229
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
230
|
+
return 0
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
if ! token="$(gh auth token 2>/dev/null)"; then
|
|
234
|
+
return 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
if [ -z "$token" ]; then
|
|
238
|
+
return 0
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
printf '%s\n' "$token"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
refresh_container_gh_auth() {
|
|
245
|
+
local token
|
|
246
|
+
|
|
247
|
+
token="$(host_gh_token_or_empty)"
|
|
248
|
+
if [ -z "$token" ]; then
|
|
249
|
+
return 0
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
if ! printf '%s\n' "$token" | devcontainer_cli exec \
|
|
253
|
+
--workspace-folder "$WORKSPACE_FOLDER" \
|
|
254
|
+
-- gh auth login --hostname github.com --git-protocol https --with-token --insecure-storage >/dev/null 2>&1; then
|
|
255
|
+
log "Warning: could not refresh GitHub CLI auth inside the devcontainer."
|
|
256
|
+
return 0
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
if ! configure_container_github_git_auth; then
|
|
260
|
+
log "Warning: GitHub CLI auth refreshed, but GitHub Git auth was not configured for this workspace."
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
if devcontainer_cli exec \
|
|
264
|
+
--workspace-folder "$WORKSPACE_FOLDER" \
|
|
265
|
+
-- gh auth status --hostname github.com >/dev/null 2>&1; then
|
|
266
|
+
log "GitHub CLI auth refreshed inside the devcontainer."
|
|
267
|
+
else
|
|
268
|
+
log "Warning: GitHub CLI auth refresh completed, but verification failed."
|
|
269
|
+
fi
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
configure_container_github_git_auth() {
|
|
273
|
+
devcontainer_cli exec \
|
|
274
|
+
--workspace-folder "$WORKSPACE_FOLDER" \
|
|
275
|
+
-- bash -s >/dev/null 2>&1 <<'EOF'
|
|
276
|
+
set -euo pipefail
|
|
277
|
+
|
|
278
|
+
canonical_github_remote_url() {
|
|
279
|
+
local remote_url="$1"
|
|
280
|
+
local owner repo rest
|
|
281
|
+
|
|
282
|
+
case "$remote_url" in
|
|
283
|
+
git@github.com:*)
|
|
284
|
+
rest="${remote_url#git@github.com:}"
|
|
285
|
+
owner="${rest%%/*}"
|
|
286
|
+
repo="${rest#*/}"
|
|
287
|
+
;;
|
|
288
|
+
ssh://git@github.com/*)
|
|
289
|
+
rest="${remote_url#ssh://git@github.com/}"
|
|
290
|
+
owner="${rest%%/*}"
|
|
291
|
+
repo="${rest#*/}"
|
|
292
|
+
;;
|
|
293
|
+
https://github.com/*)
|
|
294
|
+
rest="${remote_url#https://github.com/}"
|
|
295
|
+
owner="${rest%%/*}"
|
|
296
|
+
repo="${rest#*/}"
|
|
297
|
+
;;
|
|
298
|
+
https://*@github.com/*)
|
|
299
|
+
rest="${remote_url#https://*@github.com/}"
|
|
300
|
+
owner="${rest%%/*}"
|
|
301
|
+
repo="${rest#*/}"
|
|
302
|
+
;;
|
|
303
|
+
*)
|
|
304
|
+
return 1
|
|
305
|
+
;;
|
|
306
|
+
esac
|
|
307
|
+
|
|
308
|
+
repo="${repo%/}"
|
|
309
|
+
repo="${repo%.git}"
|
|
310
|
+
|
|
311
|
+
if [ -z "$owner" ] || [ -z "$repo" ] || [ "$owner" = "$repo" ] || [[ "$repo" == */* ]]; then
|
|
312
|
+
return 1
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
printf 'https://github.com/%s/%s.git\n' "$owner" "$repo"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
319
|
+
exit 0
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
remote_lines="$(git config --local --get-regexp '^remote\..*\.url$' || true)"
|
|
323
|
+
if [ -z "$remote_lines" ]; then
|
|
324
|
+
exit 0
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
configured_urls=()
|
|
328
|
+
while IFS= read -r line; do
|
|
329
|
+
key="${line%% *}"
|
|
330
|
+
raw_url="${line#* }"
|
|
331
|
+
remote_name="${key#remote.}"
|
|
332
|
+
remote_name="${remote_name%.url}"
|
|
333
|
+
|
|
334
|
+
if canonical_url="$(canonical_github_remote_url "$raw_url")"; then
|
|
335
|
+
git remote set-url "$remote_name" "$canonical_url"
|
|
336
|
+
git remote set-url --push "$remote_name" "$canonical_url"
|
|
337
|
+
configured_urls+=("$canonical_url")
|
|
338
|
+
fi
|
|
339
|
+
done <<< "$remote_lines"
|
|
340
|
+
|
|
341
|
+
if [ "${#configured_urls[@]}" -eq 0 ]; then
|
|
342
|
+
exit 0
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
git config --local --unset-all credential.https://github.com.helper >/dev/null 2>&1 || true
|
|
346
|
+
git config --local --add credential.https://github.com.helper ''
|
|
347
|
+
git config --local --add credential.https://github.com.helper '!gh auth git-credential'
|
|
348
|
+
|
|
349
|
+
for canonical_url in "${configured_urls[@]}"; do
|
|
350
|
+
git config --local --replace-all "url.${canonical_url}.insteadOf" "$canonical_url"
|
|
351
|
+
done
|
|
352
|
+
EOF
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
run_refresh_gh_token() {
|
|
356
|
+
start_devcontainer
|
|
357
|
+
refresh_container_gh_auth
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
run_refresh_gh_token_running() {
|
|
361
|
+
CONTAINER_ID="$(find_running_container_id)"
|
|
362
|
+
if [ -z "$CONTAINER_ID" ]; then
|
|
363
|
+
die "No running devcontainer found for: $WORKSPACE_FOLDER"
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
refresh_container_gh_auth
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
print_port_hint() {
|
|
370
|
+
local container_port
|
|
371
|
+
local host_binding
|
|
372
|
+
|
|
373
|
+
container_port="$(publish_container_port_from_devcontainer_json || true)"
|
|
374
|
+
if [ -z "$container_port" ]; then
|
|
375
|
+
echo "Warning: could not find a runArgs publish port (expected a quoted string like \"127.0.0.1::<port>\")." >&2
|
|
376
|
+
echo " Skipping docker port URL hint." >&2
|
|
377
|
+
return 0
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
host_binding="$(docker port "$CONTAINER_ID" "${container_port}/tcp" 2>/dev/null | sed -n '1p')" || true
|
|
381
|
+
if [ -n "$host_binding" ]; then
|
|
382
|
+
printf '\nDev server available at: http://%s\n\n' "$host_binding"
|
|
383
|
+
else
|
|
384
|
+
echo "Warning: container is running but port ${container_port}/tcp is not mapped." >&2
|
|
385
|
+
fi
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
open_shell() {
|
|
389
|
+
echo "Dropping into container shell..."
|
|
390
|
+
# Resolve TERM to something the container's terminfo knows about.
|
|
391
|
+
# Terminals like kitty, ghostty, alacritty set custom TERM values the container won't have.
|
|
392
|
+
# Fall back to xterm-256color (truecolor still works via COLORTERM=truecolor).
|
|
393
|
+
if ! infocmp "${TERM:-xterm-256color}" &>/dev/null 2>&1; then
|
|
394
|
+
TERM=xterm-256color
|
|
395
|
+
fi
|
|
396
|
+
TERM="${TERM:-xterm-256color}" devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- env TERM="${TERM:-xterm-256color}" COLORTERM=truecolor bash
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
ensure_container_sshd_runtime() {
|
|
400
|
+
devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- bash .devcontainer/utils/ssh-bootstrap.sh runtime </dev/null >&2
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
run_ssh_proxy() {
|
|
404
|
+
install_ssh_config_alias --quiet >&2
|
|
405
|
+
ensure_host_ssh_key
|
|
406
|
+
start_devcontainer
|
|
407
|
+
ensure_container_sshd_runtime
|
|
408
|
+
|
|
409
|
+
exec docker exec -i "$CONTAINER_ID" /usr/sbin/sshd -i \
|
|
410
|
+
-o LogLevel=QUIET \
|
|
411
|
+
-o PubkeyAuthentication=yes \
|
|
412
|
+
-o PasswordAuthentication=no \
|
|
413
|
+
-o KbdInteractiveAuthentication=no \
|
|
414
|
+
-o AllowTcpForwarding=yes \
|
|
415
|
+
-o AllowStreamLocalForwarding=yes \
|
|
416
|
+
-o PermitTTY=yes
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
run_shell() {
|
|
420
|
+
start_devcontainer
|
|
421
|
+
print_port_hint
|
|
422
|
+
open_shell
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
while [ $# -gt 0 ]; do
|
|
426
|
+
case "$1" in
|
|
427
|
+
--help | -h)
|
|
428
|
+
usage
|
|
429
|
+
exit 0
|
|
430
|
+
;;
|
|
431
|
+
--shell)
|
|
432
|
+
MODE="shell"
|
|
433
|
+
shift
|
|
434
|
+
;;
|
|
435
|
+
--ssh-proxy)
|
|
436
|
+
MODE="ssh-proxy"
|
|
437
|
+
shift
|
|
438
|
+
;;
|
|
439
|
+
ssh-config)
|
|
440
|
+
MODE="ssh-config-install"
|
|
441
|
+
if [ "${2:-}" = "install" ]; then
|
|
442
|
+
shift 2
|
|
443
|
+
elif [ "$#" -eq 1 ] || [ "${2#-}" != "$2" ]; then
|
|
444
|
+
shift
|
|
445
|
+
else
|
|
446
|
+
die "Unknown command: ssh-config ${2}"
|
|
447
|
+
fi
|
|
448
|
+
;;
|
|
449
|
+
--refresh-gh-token)
|
|
450
|
+
MODE="refresh-gh-token"
|
|
451
|
+
shift
|
|
452
|
+
;;
|
|
453
|
+
--refresh-gh-token-running)
|
|
454
|
+
MODE="refresh-gh-token-running"
|
|
455
|
+
shift
|
|
456
|
+
;;
|
|
457
|
+
--recreate)
|
|
458
|
+
RECREATE=true
|
|
459
|
+
shift
|
|
460
|
+
;;
|
|
461
|
+
*)
|
|
462
|
+
echo "Unknown option: $1" >&2
|
|
463
|
+
echo "Run with --help for usage." >&2
|
|
464
|
+
exit 1
|
|
465
|
+
;;
|
|
466
|
+
esac
|
|
467
|
+
done
|
|
468
|
+
|
|
469
|
+
case "$MODE" in
|
|
470
|
+
shell)
|
|
471
|
+
run_shell
|
|
472
|
+
;;
|
|
473
|
+
ssh-proxy)
|
|
474
|
+
run_ssh_proxy
|
|
475
|
+
;;
|
|
476
|
+
ssh-config-install)
|
|
477
|
+
install_ssh_config_alias
|
|
478
|
+
;;
|
|
479
|
+
refresh-gh-token)
|
|
480
|
+
run_refresh_gh_token
|
|
481
|
+
;;
|
|
482
|
+
refresh-gh-token-running)
|
|
483
|
+
run_refresh_gh_token_running
|
|
484
|
+
;;
|
|
485
|
+
esac
|