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.
Files changed (58) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +115 -0
  3. package/assets/devcontainer/README.md +177 -0
  4. package/assets/devcontainer/devcontainer.json +83 -0
  5. package/assets/devcontainer/hooks/initialize.sh +55 -0
  6. package/assets/devcontainer/hooks/post-create.sh +67 -0
  7. package/assets/devcontainer/hooks/post-start.sh +34 -0
  8. package/assets/devcontainer/ssh-config-install.sh +172 -0
  9. package/assets/devcontainer/start.sh +485 -0
  10. package/assets/devcontainer/utils/codex-cli-update.sh +9 -0
  11. package/assets/devcontainer/utils/coding-agent-cli-update.sh +360 -0
  12. package/assets/devcontainer/utils/deps-install.sh +87 -0
  13. package/assets/devcontainer/utils/ssh-bootstrap.sh +200 -0
  14. package/dist/bin/cli.cjs +12 -0
  15. package/dist/bin/cli.d.cts +1 -0
  16. package/dist/bin/cli.d.mts +1 -0
  17. package/dist/bin/cli.mjs +14 -0
  18. package/dist/bin/cli.mjs.map +1 -0
  19. package/dist/main-BuEptwlL.cjs +1707 -0
  20. package/dist/main-ZFTrSVgt.mjs +1685 -0
  21. package/dist/main-ZFTrSVgt.mjs.map +1 -0
  22. package/dist/main.cjs +7 -0
  23. package/dist/main.d.cts +24 -0
  24. package/dist/main.d.cts.map +1 -0
  25. package/dist/main.d.mts +24 -0
  26. package/dist/main.d.mts.map +1 -0
  27. package/dist/main.mjs +3 -0
  28. package/docs/README.md +34 -0
  29. package/docs/architecture.md +75 -0
  30. package/docs/conventions.md +29 -0
  31. package/docs/development.md +59 -0
  32. package/docs/features/README.md +14 -0
  33. package/docs/features/generated-config-and-state.md +64 -0
  34. package/docs/features/github-auth-refresh.md +64 -0
  35. package/docs/features/lifecycle.md +64 -0
  36. package/docs/features/ssh-config-and-proxy.md +60 -0
  37. package/docs/features/start-and-shell.md +83 -0
  38. package/docs/testing.md +63 -0
  39. package/docs/todo.md +170 -0
  40. package/package.json +128 -0
  41. package/src/bin/cli.ts +9 -0
  42. package/src/coding-agents.ts +22 -0
  43. package/src/config.ts +81 -0
  44. package/src/constants.ts +9 -0
  45. package/src/devcontainer-cli.ts +57 -0
  46. package/src/devcontainer.ts +394 -0
  47. package/src/doctor.ts +139 -0
  48. package/src/github-git-auth.ts +143 -0
  49. package/src/jsonc.ts +123 -0
  50. package/src/list.ts +102 -0
  51. package/src/main.ts +362 -0
  52. package/src/metadata.ts +84 -0
  53. package/src/paths.ts +117 -0
  54. package/src/process.ts +94 -0
  55. package/src/shell.ts +60 -0
  56. package/src/ssh-config.ts +113 -0
  57. package/src/ssh-key.ts +52 -0
  58. package/src/status.ts +327 -0
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Install or refresh coding-agent CLIs inside the devcontainer.
4
+
5
+ set -euo pipefail
6
+
7
+ DEFAULT_AGENTS=(codex opencode claude antigravity)
8
+ UPDATE_INTERVAL_SECONDS="${BOXDOWN_CODING_AGENT_UPDATE_INTERVAL_SECONDS:-3600}"
9
+ UPDATE_LOCK_WAIT_SECONDS="${BOXDOWN_CODING_AGENT_UPDATE_LOCK_WAIT_SECONDS:-120}"
10
+ STATE_DIR="${BOXDOWN_CODING_AGENT_UPDATE_STATE_DIR:-${HOME}/.cache/boxdown/coding-agent-clis}"
11
+
12
+ log() {
13
+ echo "coding-agent-cli-update: $*" >&2
14
+ }
15
+
16
+ usage() {
17
+ cat >&2 <<'EOF'
18
+ Usage: coding-agent-cli-update.sh <install|update-now|maybe-update> [codex|opencode|claude|antigravity...]
19
+ EOF
20
+ }
21
+
22
+ numeric_or_default() {
23
+ local value="$1"
24
+ local default_value="$2"
25
+
26
+ case "${value}" in
27
+ '' | *[!0-9]*) printf '%s\n' "${default_value}" ;;
28
+ *) printf '%s\n' "${value}" ;;
29
+ esac
30
+ }
31
+
32
+ env_value() {
33
+ local name="$1"
34
+ local default_value="$2"
35
+ local value="${!name-}"
36
+
37
+ printf '%s\n' "${value:-${default_value}}"
38
+ }
39
+
40
+ agent_env_prefix() {
41
+ case "$1" in
42
+ codex) printf '%s\n' CODEX ;;
43
+ opencode) printf '%s\n' OPENCODE ;;
44
+ claude) printf '%s\n' CLAUDE ;;
45
+ antigravity) printf '%s\n' ANTIGRAVITY ;;
46
+ *)
47
+ log "unknown coding-agent CLI: $1"
48
+ return 1
49
+ ;;
50
+ esac
51
+ }
52
+
53
+ agent_stamp_file() {
54
+ local agent="$1"
55
+ local prefix
56
+ local legacy_value
57
+
58
+ prefix="$(agent_env_prefix "${agent}")" || return 1
59
+ if [ "${agent}" = "codex" ]; then
60
+ legacy_value="${BOXDOWN_CODEX_UPDATE_STAMP_FILE:-}"
61
+ if [ -n "${legacy_value}" ]; then
62
+ printf '%s\n' "${legacy_value}"
63
+ return 0
64
+ fi
65
+ fi
66
+ env_value "BOXDOWN_${prefix}_UPDATE_STAMP_FILE" "${STATE_DIR}/${agent}.stamp"
67
+ }
68
+
69
+ agent_lock_dir() {
70
+ local agent="$1"
71
+ local prefix
72
+ local legacy_value
73
+
74
+ prefix="$(agent_env_prefix "${agent}")" || return 1
75
+ if [ "${agent}" = "codex" ]; then
76
+ legacy_value="${BOXDOWN_CODEX_UPDATE_LOCK_DIR:-}"
77
+ if [ -n "${legacy_value}" ]; then
78
+ printf '%s\n' "${legacy_value}"
79
+ return 0
80
+ fi
81
+ fi
82
+ env_value "BOXDOWN_${prefix}_UPDATE_LOCK_DIR" "${STATE_DIR}/${agent}.lock"
83
+ }
84
+
85
+ agent_interval_seconds() {
86
+ local agent="$1"
87
+ local prefix
88
+
89
+ prefix="$(agent_env_prefix "${agent}")" || return 1
90
+ env_value "BOXDOWN_${prefix}_UPDATE_INTERVAL_SECONDS" "${UPDATE_INTERVAL_SECONDS}"
91
+ }
92
+
93
+ agent_lock_wait_seconds() {
94
+ local agent="$1"
95
+ local prefix
96
+
97
+ prefix="$(agent_env_prefix "${agent}")" || return 1
98
+ env_value "BOXDOWN_${prefix}_UPDATE_LOCK_WAIT_SECONDS" "${UPDATE_LOCK_WAIT_SECONDS}"
99
+ }
100
+
101
+ ensure_state_dir() {
102
+ local stamp_file="$1"
103
+ local lock_dir="$2"
104
+
105
+ mkdir -p "$(dirname "${stamp_file}")" "$(dirname "${lock_dir}")"
106
+ }
107
+
108
+ stamp_fresh() {
109
+ local agent="$1"
110
+ local stamp_file="$2"
111
+ local interval_seconds
112
+ local stamp_time
113
+ local now
114
+
115
+ interval_seconds="$(numeric_or_default "$(agent_interval_seconds "${agent}")" 3600)"
116
+ if [ "${interval_seconds}" = "0" ]; then
117
+ return 1
118
+ fi
119
+
120
+ if [ ! -f "${stamp_file}" ]; then
121
+ return 1
122
+ fi
123
+
124
+ stamp_time="$(stat -c %Y "${stamp_file}" 2>/dev/null || stat -f %m "${stamp_file}" 2>/dev/null || printf '0')"
125
+ now="$(date +%s)"
126
+ [ "$((now - stamp_time))" -lt "${interval_seconds}" ]
127
+ }
128
+
129
+ wait_for_existing_lock() {
130
+ local agent="$1"
131
+ local lock_dir="$2"
132
+ local wait_seconds
133
+ local deadline
134
+
135
+ wait_seconds="$(numeric_or_default "$(agent_lock_wait_seconds "${agent}")" 120)"
136
+ deadline=$((SECONDS + wait_seconds))
137
+
138
+ while [ -d "${lock_dir}" ]; do
139
+ if [ "${SECONDS}" -ge "${deadline}" ]; then
140
+ return 1
141
+ fi
142
+
143
+ sleep 1
144
+ done
145
+ }
146
+
147
+ acquire_lock() {
148
+ local agent="$1"
149
+ local lock_dir="$2"
150
+
151
+ if mkdir "${lock_dir}" 2>/dev/null; then
152
+ ACTIVE_LOCK_DIR="${lock_dir}"
153
+ return 0
154
+ fi
155
+
156
+ log "${agent}: another update is running; waiting for it to finish."
157
+ wait_for_existing_lock "${agent}" "${lock_dir}" || {
158
+ log "${agent}: another update is still running; skipping this preflight."
159
+ return 1
160
+ }
161
+
162
+ mkdir "${lock_dir}" 2>/dev/null || return 1
163
+ ACTIVE_LOCK_DIR="${lock_dir}"
164
+ }
165
+
166
+ release_lock() {
167
+ if [ -n "${ACTIVE_LOCK_DIR:-}" ]; then
168
+ rmdir "${ACTIVE_LOCK_DIR}" 2>/dev/null || true
169
+ ACTIVE_LOCK_DIR=""
170
+ fi
171
+ }
172
+
173
+ run_installer_url() {
174
+ local url="$1"
175
+ shift
176
+
177
+ curl -fsSL "${url}" | "$@"
178
+ }
179
+
180
+ install_codex() {
181
+ local url
182
+
183
+ url="${BOXDOWN_CODEX_INSTALL_URL:-https://chatgpt.com/codex/install.sh}"
184
+ run_installer_url "${url}" env CODEX_NON_INTERACTIVE=1 sh
185
+ }
186
+
187
+ update_codex() {
188
+ if command -v codex >/dev/null 2>&1; then
189
+ if codex update; then
190
+ return 0
191
+ fi
192
+
193
+ log "codex: codex update failed; falling back to installer."
194
+ fi
195
+
196
+ install_codex
197
+ }
198
+
199
+ install_opencode() {
200
+ local url
201
+
202
+ url="${BOXDOWN_OPENCODE_INSTALL_URL:-https://opencode.ai/install}"
203
+ run_installer_url "${url}" bash -s -- --no-modify-path
204
+ }
205
+
206
+ update_opencode() {
207
+ if command -v opencode >/dev/null 2>&1; then
208
+ if opencode upgrade --method curl; then
209
+ return 0
210
+ fi
211
+
212
+ log "opencode: opencode upgrade failed; falling back to installer."
213
+ fi
214
+
215
+ install_opencode
216
+ }
217
+
218
+ install_claude() {
219
+ local url
220
+
221
+ url="${BOXDOWN_CLAUDE_INSTALL_URL:-https://claude.ai/install.sh}"
222
+ run_installer_url "${url}" bash
223
+ }
224
+
225
+ update_claude() {
226
+ if command -v claude >/dev/null 2>&1; then
227
+ if claude update; then
228
+ return 0
229
+ fi
230
+
231
+ log "claude: claude update failed; falling back to installer."
232
+ fi
233
+
234
+ install_claude
235
+ }
236
+
237
+ install_antigravity() {
238
+ local url
239
+
240
+ url="${BOXDOWN_ANTIGRAVITY_INSTALL_URL:-https://antigravity.google/cli/install.sh}"
241
+ run_installer_url "${url}" bash -s -- --skip-path
242
+ }
243
+
244
+ update_antigravity() {
245
+ install_antigravity
246
+ }
247
+
248
+ run_agent_update() {
249
+ case "$1" in
250
+ codex) update_codex ;;
251
+ opencode) update_opencode ;;
252
+ claude) update_claude ;;
253
+ antigravity) update_antigravity ;;
254
+ *)
255
+ log "unknown coding-agent CLI: $1"
256
+ return 1
257
+ ;;
258
+ esac
259
+ }
260
+
261
+ touch_stamp() {
262
+ local stamp_file="$1"
263
+
264
+ touch "${stamp_file}"
265
+ }
266
+
267
+ update_now_agent() {
268
+ local agent="$1"
269
+ local stamp_file
270
+ local lock_dir
271
+ local result=0
272
+
273
+ stamp_file="$(agent_stamp_file "${agent}")" || return 1
274
+ lock_dir="$(agent_lock_dir "${agent}")" || return 1
275
+ ensure_state_dir "${stamp_file}" "${lock_dir}"
276
+ acquire_lock "${agent}" "${lock_dir}" || return 1
277
+ if run_agent_update "${agent}"; then
278
+ touch_stamp "${stamp_file}"
279
+ else
280
+ result=$?
281
+ fi
282
+ release_lock
283
+ return "${result}"
284
+ }
285
+
286
+ maybe_update_agent() {
287
+ local agent="$1"
288
+ local stamp_file
289
+ local lock_dir
290
+ local result=0
291
+
292
+ stamp_file="$(agent_stamp_file "${agent}")" || return 1
293
+ lock_dir="$(agent_lock_dir "${agent}")" || return 1
294
+ ensure_state_dir "${stamp_file}" "${lock_dir}"
295
+ if stamp_fresh "${agent}" "${stamp_file}"; then
296
+ return 0
297
+ fi
298
+
299
+ acquire_lock "${agent}" "${lock_dir}" || return 0
300
+ if stamp_fresh "${agent}" "${stamp_file}"; then
301
+ release_lock
302
+ return 0
303
+ fi
304
+
305
+ if run_agent_update "${agent}"; then
306
+ touch_stamp "${stamp_file}"
307
+ else
308
+ result=$?
309
+ fi
310
+ release_lock
311
+ return "${result}"
312
+ }
313
+
314
+ run_action_for_agent() {
315
+ local action="$1"
316
+ local agent="$2"
317
+
318
+ case "${action}" in
319
+ install | update-now)
320
+ update_now_agent "${agent}"
321
+ ;;
322
+ maybe-update)
323
+ maybe_update_agent "${agent}"
324
+ ;;
325
+ *)
326
+ usage
327
+ return 1
328
+ ;;
329
+ esac
330
+ }
331
+
332
+ main() {
333
+ local action="${1:-}"
334
+ local failures=0
335
+ local agents
336
+
337
+ if [ -z "${action}" ]; then
338
+ usage
339
+ return 1
340
+ fi
341
+ shift
342
+
343
+ if [ "$#" -eq 0 ]; then
344
+ agents=("${DEFAULT_AGENTS[@]}")
345
+ else
346
+ agents=("$@")
347
+ fi
348
+
349
+ for agent in "${agents[@]}"; do
350
+ if ! run_action_for_agent "${action}" "${agent}"; then
351
+ log "${agent}: ${action} failed."
352
+ failures=$((failures + 1))
353
+ fi
354
+ done
355
+
356
+ [ "${failures}" -eq 0 ]
357
+ }
358
+
359
+ trap 'release_lock' EXIT
360
+ main "$@"
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ # Install workspace JS dependencies when package.json exists (non-fatal on failure).
3
+
4
+ main() {
5
+ [[ -f package.json ]] || return 0
6
+ local pm
7
+ pm=$(detect_package_manager)
8
+ install_dependencies "$pm" || true
9
+ }
10
+
11
+ detect_package_manager() {
12
+ local pm
13
+ if pm=$(detect_package_manager_from_lockfiles); then
14
+ echo "$pm"
15
+ return
16
+ fi
17
+ if pm=$(detect_package_manager_from_repo_markers); then
18
+ echo "$pm"
19
+ return
20
+ fi
21
+ if pm=$(detect_package_manager_from_package_json); then
22
+ echo "$pm"
23
+ return
24
+ fi
25
+ echo npm
26
+ }
27
+
28
+ detect_package_manager_from_lockfiles() {
29
+ if [[ -f pnpm-lock.yaml ]]; then
30
+ echo pnpm
31
+ return 0
32
+ fi
33
+ if [[ -f bun.lockb || -f bun.lock ]]; then
34
+ echo bun
35
+ return 0
36
+ fi
37
+ if [[ -f yarn.lock ]]; then
38
+ echo yarn
39
+ return 0
40
+ fi
41
+ if [[ -f package-lock.json ]]; then
42
+ echo npm
43
+ return 0
44
+ fi
45
+ return 1
46
+ }
47
+
48
+ detect_package_manager_from_repo_markers() {
49
+ if [[ -f pnpm-workspace.yaml ]]; then
50
+ echo pnpm
51
+ return 0
52
+ fi
53
+ if [[ -f bunfig.toml ]]; then
54
+ echo bun
55
+ return 0
56
+ fi
57
+ if [[ -f .yarnrc.yml ]]; then
58
+ echo yarn
59
+ return 0
60
+ fi
61
+ return 1
62
+ }
63
+
64
+ detect_package_manager_from_package_json() {
65
+ [[ -f package.json ]] || return 1
66
+ node -e "
67
+ const p = require('./package.json');
68
+ const spec = p.packageManager;
69
+ if (!spec || typeof spec !== 'string') process.exit(1);
70
+ const name = spec.split('@')[0].trim();
71
+ if (!['pnpm', 'npm', 'yarn', 'bun'].includes(name)) process.exit(1);
72
+ console.log(name);
73
+ " 2>/dev/null || return 1
74
+ }
75
+
76
+ install_dependencies() {
77
+ local pm="$1"
78
+ case "$pm" in
79
+ pnpm) pnpm install ;;
80
+ npm) npm install ;;
81
+ yarn) yarn install ;;
82
+ bun) bun install ;;
83
+ *) npm install ;;
84
+ esac
85
+ }
86
+
87
+ main "$@"
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Container-side SSH setup for the repo-local devcontainer workflow.
4
+
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ WORKSPACE_FOLDER="${BOXDOWN_CONTAINER_WORKSPACE_FOLDER:-$(cd "${SCRIPT_DIR}/../.." && pwd -P)}"
9
+ REPO_NAME="${BOXDOWN_WORKSPACE_BASENAME:-$(basename "${WORKSPACE_FOLDER}")}"
10
+ SSH_USER="${DEVCONTAINER_SSH_USER:-node}"
11
+ PUBLIC_KEY_FILE="${DEVCONTAINER_SSH_PUBLIC_KEY_FILE:-${WORKSPACE_FOLDER}/.devcontainer/.ssh/id_ed25519.pub}"
12
+ OPENSSH_CONFIGURE_TIMEOUT_SECONDS="${DEVCONTAINER_OPENSSH_CONFIGURE_TIMEOUT_SECONDS:-180}"
13
+
14
+ as_root() {
15
+ if [ "$(id -u)" -eq 0 ]; then
16
+ "$@"
17
+ else
18
+ sudo "$@"
19
+ fi
20
+ }
21
+
22
+ ssh_user_home() {
23
+ getent passwd "${SSH_USER}" | cut -d: -f6
24
+ }
25
+
26
+ openssh_server_package_configured() {
27
+ local status
28
+
29
+ if ! command -v dpkg-query >/dev/null 2>&1; then
30
+ return 0
31
+ fi
32
+
33
+ status="$(dpkg-query -W -f='${Status}' openssh-server 2>/dev/null || true)"
34
+ [ "${status}" = "install ok installed" ]
35
+ }
36
+
37
+ openssh_server_configured() {
38
+ [ -x /usr/sbin/sshd ] || return 1
39
+ getent passwd sshd >/dev/null 2>&1 || return 1
40
+ openssh_server_package_configured
41
+ }
42
+
43
+ package_manager_running() {
44
+ if command -v pgrep >/dev/null 2>&1; then
45
+ pgrep -x apt >/dev/null 2>&1 ||
46
+ pgrep -x apt-get >/dev/null 2>&1 ||
47
+ pgrep -x dpkg >/dev/null 2>&1
48
+ return $?
49
+ fi
50
+
51
+ ps -eo comm= | grep -Eq '^(apt|apt-get|dpkg)$'
52
+ }
53
+
54
+ configure_pending_dpkg_packages() {
55
+ if ! command -v dpkg >/dev/null 2>&1; then
56
+ return 1
57
+ fi
58
+
59
+ # Feed "no" answers so a recovering openssh-server postinst cannot block if
60
+ # another path already generated host keys before dpkg completed.
61
+ printf 'n\nn\nn\nn\nn\n' |
62
+ as_root env DEBIAN_FRONTEND=noninteractive dpkg --configure -a
63
+ }
64
+
65
+ ensure_openssh_server_configured() {
66
+ local deadline
67
+
68
+ if openssh_server_configured; then
69
+ return 0
70
+ fi
71
+
72
+ echo "ssh-bootstrap: waiting for openssh-server package configuration before generating SSH host keys." >&2
73
+ deadline=$((SECONDS + OPENSSH_CONFIGURE_TIMEOUT_SECONDS))
74
+
75
+ while ! openssh_server_configured; do
76
+ if ! package_manager_running; then
77
+ configure_pending_dpkg_packages || true
78
+ fi
79
+
80
+ if openssh_server_configured; then
81
+ return 0
82
+ fi
83
+
84
+ if [ "${SECONDS}" -ge "${deadline}" ]; then
85
+ echo "ssh-bootstrap: openssh-server did not finish configuring within ${OPENSSH_CONFIGURE_TIMEOUT_SECONDS}s." >&2
86
+ if command -v dpkg-query >/dev/null 2>&1; then
87
+ dpkg-query -W -f='ssh-bootstrap: openssh-server status: ${Status}\n' openssh-server >&2 || true
88
+ fi
89
+ if ! getent passwd sshd >/dev/null 2>&1; then
90
+ echo "ssh-bootstrap: missing sshd privilege separation user." >&2
91
+ fi
92
+ return 1
93
+ fi
94
+
95
+ sleep 2
96
+ done
97
+ }
98
+
99
+ install_openssh_server() {
100
+ if [ ! -x /usr/sbin/sshd ]; then
101
+ as_root apt-get update
102
+ as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server
103
+ fi
104
+
105
+ ensure_openssh_server_configured
106
+ }
107
+
108
+ install_authorized_key_if_present() {
109
+ local user_home
110
+ local authorized_keys
111
+ local public_key
112
+
113
+ if [ ! -r "${PUBLIC_KEY_FILE}" ]; then
114
+ echo "ssh-bootstrap: no public key at ${PUBLIC_KEY_FILE}; skipping authorized_keys setup."
115
+ return 0
116
+ fi
117
+
118
+ user_home="$(ssh_user_home)"
119
+ if [ -z "${user_home}" ]; then
120
+ echo "ssh-bootstrap: could not resolve home directory for ${SSH_USER}." >&2
121
+ return 1
122
+ fi
123
+
124
+ authorized_keys="${user_home}/.ssh/authorized_keys"
125
+ public_key="$(sed -n '1p' "${PUBLIC_KEY_FILE}")"
126
+
127
+ as_root install -d -m 0700 -o "${SSH_USER}" -g "${SSH_USER}" "${user_home}/.ssh"
128
+ as_root touch "${authorized_keys}"
129
+ as_root chown "${SSH_USER}:${SSH_USER}" "${authorized_keys}"
130
+ as_root chmod 0600 "${authorized_keys}"
131
+
132
+ if ! as_root grep -qxF "${public_key}" "${authorized_keys}"; then
133
+ printf '%s\n' "${public_key}" | as_root tee -a "${authorized_keys}" >/dev/null
134
+ fi
135
+
136
+ as_root chown "${SSH_USER}:${SSH_USER}" "${authorized_keys}"
137
+ as_root chmod 0600 "${authorized_keys}"
138
+ }
139
+
140
+ ensure_workspace_home_symlink() {
141
+ local user_home
142
+ local link_path
143
+
144
+ user_home="$(ssh_user_home)"
145
+ if [ -z "${user_home}" ]; then
146
+ echo "ssh-bootstrap: could not resolve home directory for ${SSH_USER}." >&2
147
+ return 1
148
+ fi
149
+
150
+ link_path="${user_home}/${REPO_NAME}"
151
+
152
+ if [ -e "${link_path}" ] && [ ! -L "${link_path}" ]; then
153
+ echo "ssh-bootstrap: ${link_path} already exists and is not a symlink; leaving it unchanged." >&2
154
+ return 0
155
+ fi
156
+
157
+ as_root ln -sfn "${WORKSPACE_FOLDER}" "${link_path}"
158
+ as_root chown -h "${SSH_USER}:${SSH_USER}" "${link_path}"
159
+ }
160
+
161
+ ensure_runtime_ready() {
162
+ if [ ! -x /usr/sbin/sshd ]; then
163
+ echo "ssh-bootstrap: /usr/sbin/sshd is missing. Recreate the devcontainer so post-create installs openssh-server." >&2
164
+ return 1
165
+ fi
166
+
167
+ ensure_openssh_server_configured
168
+ as_root ssh-keygen -A
169
+ as_root install -d -m 0755 /run/sshd
170
+ install_authorized_key_if_present
171
+ ensure_workspace_home_symlink
172
+ as_root /usr/sbin/sshd -t \
173
+ -o PubkeyAuthentication=yes \
174
+ -o PasswordAuthentication=no \
175
+ -o KbdInteractiveAuthentication=no \
176
+ -o AllowTcpForwarding=yes \
177
+ -o AllowStreamLocalForwarding=yes \
178
+ -o PermitTTY=yes
179
+ }
180
+
181
+ main() {
182
+ case "${1:-runtime}" in
183
+ install)
184
+ install_openssh_server
185
+ ;;
186
+ runtime)
187
+ ensure_runtime_ready
188
+ ;;
189
+ all)
190
+ install_openssh_server
191
+ ensure_runtime_ready
192
+ ;;
193
+ *)
194
+ echo "Usage: $(basename "$0") [install|runtime|all]" >&2
195
+ return 1
196
+ ;;
197
+ esac
198
+ }
199
+
200
+ main "$@"
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ const require_main = require('../main-BuEptwlL.cjs');
3
+
4
+ //#region src/bin/cli.ts
5
+ require_main.runCli().then((exitCode) => {
6
+ process.exitCode = exitCode;
7
+ }, (error) => {
8
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
9
+ process.exitCode = 1;
10
+ });
11
+
12
+ //#endregion
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { i as runCli } from "../main-ZFTrSVgt.mjs";
3
+
4
+ //#region src/bin/cli.ts
5
+ runCli().then((exitCode) => {
6
+ process.exitCode = exitCode;
7
+ }, (error) => {
8
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
9
+ process.exitCode = 1;
10
+ });
11
+
12
+ //#endregion
13
+ export { };
14
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../../src/bin/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runCli } from '../main.ts'\n\nrunCli().then((exitCode) => {\n process.exitCode = exitCode\n}, (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n})\n"],"mappings":";;;;AAGA,QAAQ,CAAC,MAAM,aAAa;AAC1B,SAAQ,WAAW;IACjB,UAAmB;AACrB,SAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,IAAI;AACnF,SAAQ,WAAW;EACnB"}