boxdown 1.2.0 → 1.3.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 (53) hide show
  1. package/README.md +10 -0
  2. package/assets/devcontainer/README.md +29 -6
  3. package/assets/devcontainer/devcontainer.json +4 -9
  4. package/assets/devcontainer/hooks/initialize.sh +49 -27
  5. package/assets/devcontainer/hooks/post-create.sh +23 -1
  6. package/assets/devcontainer/hooks/post-start.sh +0 -10
  7. package/assets/devcontainer/utils/git-signing-bootstrap.sh +58 -5
  8. package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
  9. package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
  10. package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
  11. package/dist/bin/cli.cjs +1 -1
  12. package/dist/bin/cli.mjs +1 -1
  13. package/dist/{main-Df4E8ARj.cjs → main-CT2n9qcb.cjs} +907 -266
  14. package/dist/{main-XMBsKjIK.mjs → main-O__JaiXe.mjs} +891 -268
  15. package/dist/main-O__JaiXe.mjs.map +1 -0
  16. package/dist/main.cjs +4 -1
  17. package/dist/main.d.cts +98 -26
  18. package/dist/main.d.cts.map +1 -1
  19. package/dist/main.d.mts +98 -26
  20. package/dist/main.d.mts.map +1 -1
  21. package/dist/main.mjs +2 -2
  22. package/docs/features/commit-signing.md +71 -0
  23. package/docs/features/generated-config-and-state.md +19 -4
  24. package/docs/features/github-auth-refresh.md +3 -2
  25. package/docs/features/lifecycle.md +19 -0
  26. package/docs/features/setup.md +5 -0
  27. package/docs/features/start-and-shell.md +10 -0
  28. package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
  29. package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
  30. package/docs/superpowers/plans/2026-07-18-architecture-aware-1password-installer.md +163 -0
  31. package/docs/superpowers/plans/2026-07-18-devcontainer-node-image-digest.md +341 -0
  32. package/docs/superpowers/plans/2026-07-19-container-runtime-readiness.md +1536 -0
  33. package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +20 -0
  34. package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
  35. package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
  36. package/docs/superpowers/specs/2026-07-18-architecture-aware-1password-installer-design.md +63 -0
  37. package/docs/superpowers/specs/2026-07-18-devcontainer-node-image-digest-design.md +108 -0
  38. package/docs/superpowers/specs/2026-07-19-container-runtime-readiness-design.md +353 -0
  39. package/package.json +1 -1
  40. package/src/config.ts +14 -2
  41. package/src/constants.ts +7 -0
  42. package/src/container-runtime.ts +295 -0
  43. package/src/devcontainer.ts +57 -30
  44. package/src/doctor.ts +169 -46
  45. package/src/git-signing.ts +205 -25
  46. package/src/logging.ts +11 -1
  47. package/src/main.ts +97 -12
  48. package/src/paths.ts +21 -1
  49. package/src/process.ts +50 -10
  50. package/src/progress.ts +63 -8
  51. package/src/purge.ts +8 -0
  52. package/src/shell.ts +6 -0
  53. package/dist/main-XMBsKjIK.mjs.map +0 -1
package/README.md CHANGED
@@ -169,6 +169,16 @@ no-start bind-mount probe for the workspace and Boxdown-managed mount paths.
169
169
  Run `boxdown doctor` directly for the complete diagnostic report; an unavailable
170
170
  best-effort mount probe is reported as a warning and does not block setup.
171
171
 
172
+ `boxdown start` is standalone: it can create or reuse the devcontainer even if
173
+ `boxdown setup` was skipped or its preflight failed. Setup-only SSH aliases and
174
+ Codex/Claude application integrations are still installed only by `setup`.
175
+
176
+ Before a command creates or starts a container, Boxdown waits up to 60 seconds
177
+ for the Docker daemon and the selected Docker Buildx builder. If Buildx is not
178
+ installed, the bundled Dev Containers CLI uses its supported classic-build
179
+ fallback and Boxdown continues with a warning. Boxdown does not retry an actual
180
+ Dev Containers build failure.
181
+
172
182
  Container bring-up installs Codex and Claude Code by default. The OpenCode and
173
183
  Antigravity commands stay available, but install/update those CLIs only when you
174
184
  launch them. Use `--` to pass arguments to the selected agent:
@@ -17,7 +17,7 @@ Run this project in a **consistent Node.js 24 + TypeScript** environment without
17
17
  | `devcontainer.json` | Slim Node base image, pinned feature set/order, Boxdown state mounts, lifecycle commands, env forwarding. |
18
18
  | `start.sh` | Brings the dev container up with the Dev Containers CLI, then opens a shell **inside** the container or acts as an SSH `ProxyCommand`. |
19
19
  | `ssh-config-install.sh` | Installs/updates a host SSH config alias for Cursor, Claude, or plain `ssh`. |
20
- | `hooks/initialize.sh` | Runs on the host before container create/start; prepares the env file and optional secrets. |
20
+ | `hooks/initialize.sh` | Runs on the host before container create/start; refreshes private runtime secret files and host Git state. |
21
21
  | `hooks/post-create.sh` | Runs once after the container is created — e.g. installs OpenSSH server, Debian Python, [APM](https://github.com/microsoft/apm) (Agent Package Manager), and default coding-agent CLIs. |
22
22
  | `hooks/post-start.sh` | Runs on each container start; refreshes runtime state such as SSH host keys and authorized keys. |
23
23
  | `utils/git-config-bootstrap.sh` | Container-side Git config copy/sanitization helper used by lifecycle scripts. |
@@ -28,7 +28,18 @@ Run this project in a **consistent Node.js 24 + TypeScript** environment without
28
28
 
29
29
  ## Base image
30
30
 
31
- Boxdown uses `node:24-trixie-slim` as the base image to keep the shared image smaller than the full Dev Containers TypeScript/Node image. The devcontainer then installs the required operating-system tools through pinned Dev Container features. `common-utils` and `git` run first so later features and lifecycle hooks can rely on shell basics, `sudo`, package metadata, Git, and related utilities.
31
+ Boxdown uses `node:24-trixie-slim` as the base-image update track to keep the
32
+ shared image smaller than the full Dev Containers TypeScript/Node image. The
33
+ template appends the upstream multi-platform OCI index digest, making rebuilds
34
+ immutable while allowing AMD64 and ARM64 hosts to select the matching platform
35
+ manifest from the same pinned release set. Renovate checks the packaged
36
+ template monthly and opens a pull request when that tag's index digest changes,
37
+ so base-image updates remain explicit and auditable.
38
+
39
+ The devcontainer then installs the required operating-system tools through
40
+ pinned Dev Container features. `common-utils` and `git` run first so later
41
+ features and lifecycle hooks can rely on shell basics, `sudo`, package
42
+ metadata, Git, and related utilities.
32
43
 
33
44
  Python is installed during `postCreateCommand` from Debian apt packages (`python3`, `python3-venv`, `python3-pip`, and `pipx`). On Debian trixie this currently provides Python 3.13. Boxdown intentionally avoids the Dev Containers Python feature because it added a large Python runtime/dev-tooling layer, including bundled environments for tools such as mypy, black, pylint, pytest, bandit, pipenv, and flake8.
34
45
 
@@ -175,10 +186,22 @@ export ANTHROPIC_API_KEY=sk-...
175
186
  export SNYK_TOKEN=...
176
187
  ```
177
188
 
178
- They are wired in `devcontainer.json` under `containerEnv` via `localEnv`.
179
-
180
- If `OP_SERVICE_ACCOUNT_TOKEN` is present, it authenticates the 1Password CLI.
181
- It is not a GitHub token and does not authenticate `gh` or GitHub Git remotes.
189
+ Boxdown writes available values to owner-only files in per-workspace runtime
190
+ state outside the repository, then mounts that directory read-only. Bash
191
+ sessions export the values as ordinary environment variables, but Docker
192
+ container configuration and `docker inspect` do not contain their values.
193
+
194
+ When the host `op` CLI can read Boxdown's configured service-account item,
195
+ Boxdown provides `OP_SERVICE_ACCOUNT_TOKEN` through the same runtime mount. A
196
+ missing host value or failed 1Password lookup is non-blocking; the variable is
197
+ simply absent. This token is not a GitHub token and does not authenticate `gh`
198
+ or GitHub Git remotes.
199
+
200
+ Boxdown never creates, modifies, reads, or deletes a project
201
+ `.env.development` file. Existing containers created by an older Boxdown need
202
+ `boxdown start --recreate` to stop receiving legacy Docker environment values.
203
+ If an older version left a service-account token in `.env.development` or
204
+ `boxdown.log`, remove it manually and rotate the token.
182
205
 
183
206
  ## Optional customization
184
207
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Project Devcontainer",
3
- "image": "node:24-trixie-slim",
3
+ "image": "node:24-trixie-slim@sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573",
4
4
  "features": {
5
5
  "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a": {
6
6
  "username": "node"
@@ -75,17 +75,14 @@
75
75
  // Codex auth.json is mounted automatically by Boxdown's generated config when present.
76
76
  ],
77
77
 
78
- // Initialize optional host-side secrets before container create/start.
78
+ // Initialize host runtime state before container create/start.
79
79
  "initializeCommand": "bash .devcontainer/hooks/initialize.sh",
80
80
  // After up, discover the published host binding, e.g.:
81
81
  // docker port <container-name-or-id> 3000/tcp
82
82
  "runArgs": [
83
83
  // publish container port 3000 with dynamic host port (CLI / start.sh)
84
84
  "-p",
85
- "127.0.0.1::3000",
86
- // pass the development environment file to the container
87
- "--env-file",
88
- "${localWorkspaceFolder}/.env.development"
85
+ "127.0.0.1::3000"
89
86
  ],
90
87
  // --
91
88
  "postCreateCommand": "bash .devcontainer/hooks/post-create.sh",
@@ -93,8 +90,6 @@
93
90
  "remoteUser": "node",
94
91
  "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
95
92
  "containerEnv": {
96
- "NODE_ENV": "development",
97
- "ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}",
98
- "SNYK_TOKEN": "${localEnv:SNYK_TOKEN}"
93
+ "NODE_ENV": "development"
99
94
  }
100
95
  }
@@ -1,23 +1,19 @@
1
1
  #!/usr/bin/env bash
2
2
  # initialize: runs on host before container create/start (initializeCommand).
3
- # Prepares .env.development and, when possible, injects OP_SERVICE_ACCOUNT_TOKEN from 1Password.
3
+ # Refreshes private runtime secret files and snapshots host Git config.
4
4
 
5
5
  set -euo pipefail
6
6
 
7
- HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
- WORKSPACE_DIR="${BOXDOWN_WORKSPACE_FOLDER:-$(cd "${HOOKS_DIR}/../.." && pwd)}"
9
- ENV_FILE="${WORKSPACE_DIR}/.env.development"
10
7
  HOST_GITCONFIG_PATH="${BOXDOWN_HOST_GITCONFIG_PATH:-${HOME:-}/.gitconfig}"
11
8
  HOST_GITCONFIG_SNAPSHOT_PATH="${BOXDOWN_HOST_GITCONFIG_SNAPSHOT_PATH:-}"
9
+ SECRET_ENV_DIR="${BOXDOWN_SECRET_ENV_DIR:-}"
12
10
  OP_TOKEN_REFERENCE="op://Private/1Password op CLI Service Account for DevContainers/password"
13
11
 
14
12
  main() {
15
- progress "Preparing workspace environment file"
16
- ensure_env_file_exists
17
13
  progress "Snapshotting host Git config"
18
14
  snapshot_host_gitconfig
19
- progress "Checking 1Password service account token"
20
- maybe_inject_1password_service_account_token
15
+ progress "Refreshing runtime secret environment"
16
+ refresh_runtime_secret_environment
21
17
  }
22
18
 
23
19
  progress() {
@@ -26,12 +22,6 @@ progress() {
26
22
  fi
27
23
  }
28
24
 
29
- ensure_env_file_exists() {
30
- if [[ ! -f "${ENV_FILE}" ]]; then
31
- : > "${ENV_FILE}"
32
- fi
33
- }
34
-
35
25
  snapshot_host_gitconfig() {
36
26
  local snapshot_dir
37
27
 
@@ -52,36 +42,68 @@ snapshot_host_gitconfig() {
52
42
  rm -f "${HOST_GITCONFIG_SNAPSHOT_PATH}"
53
43
  }
54
44
 
55
- maybe_inject_1password_service_account_token() {
45
+ prepare_secret_env_dir() {
46
+ if [[ -z "${SECRET_ENV_DIR}" ]]; then
47
+ echo "initialize.sh: runtime secret directory is not configured; skipping optional secrets." >&2
48
+ return 1
49
+ fi
50
+
51
+ umask 077
52
+ mkdir -p "${SECRET_ENV_DIR}"
53
+ chmod 0700 "${SECRET_ENV_DIR}"
54
+ }
55
+
56
+ write_secret_file() {
57
+ local name="$1"
58
+ local value="$2"
59
+ local temporary_path
60
+
61
+ temporary_path="$(mktemp "${SECRET_ENV_DIR}/.${name}.XXXXXX")"
62
+ printf '%s' "${value}" > "${temporary_path}"
63
+ chmod 0600 "${temporary_path}"
64
+ mv -f "${temporary_path}" "${SECRET_ENV_DIR}/${name}"
65
+ }
66
+
67
+ refresh_host_environment_secret() {
68
+ local name="$1"
69
+ local value="${!name:-}"
70
+
71
+ if [[ -n "${value}" ]]; then
72
+ write_secret_file "${name}" "${value}"
73
+ else
74
+ rm -f "${SECRET_ENV_DIR}/${name}"
75
+ fi
76
+ }
77
+
78
+ refresh_1password_service_account_token() {
56
79
  local token
57
80
 
58
81
  if ! command -v op >/dev/null 2>&1; then
59
- echo "initialize.sh: op CLI not found; leaving ${ENV_FILE} unchanged."
82
+ rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
60
83
  return 0
61
84
  fi
62
85
 
63
86
  if ! token="$(op read "${OP_TOKEN_REFERENCE}" 2>/dev/null)"; then
64
- echo "initialize.sh: could not read OP service token from 1Password; leaving ${ENV_FILE} unchanged."
87
+ rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
65
88
  return 0
66
89
  fi
67
90
 
68
91
  if [[ -z "${token}" ]]; then
69
- echo "initialize.sh: OP service token is empty; leaving ${ENV_FILE} unchanged."
92
+ rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
70
93
  return 0
71
94
  fi
72
95
 
73
- upsert_env_var "OP_SERVICE_ACCOUNT_TOKEN" "${token}"
96
+ write_secret_file "OP_SERVICE_ACCOUNT_TOKEN" "${token}"
74
97
  }
75
98
 
76
- upsert_env_var() {
77
- local key="$1"
78
- local value="$2"
79
- local tmp_file
99
+ refresh_runtime_secret_environment() {
100
+ if ! prepare_secret_env_dir; then
101
+ return 0
102
+ fi
80
103
 
81
- tmp_file="$(mktemp)"
82
- awk -F= -v k="${key}" '$1 != k' "${ENV_FILE}" > "${tmp_file}"
83
- printf '%s=%s\n' "${key}" "${value}" >> "${tmp_file}"
84
- mv "${tmp_file}" "${ENV_FILE}"
104
+ refresh_host_environment_secret "ANTHROPIC_API_KEY"
105
+ refresh_host_environment_secret "SNYK_TOKEN"
106
+ refresh_1password_service_account_token
85
107
  }
86
108
 
87
109
  main "$@"
@@ -8,6 +8,7 @@ main() {
8
8
  run_step "Configuring global Git" configure_global_git
9
9
  run_step "Configuring Git commit signing" configure_git_signing
10
10
  run_step "Configuring workspace Git" configure_local_git
11
+ run_step "Configuring runtime secret environment" configure_runtime_secret_environment
11
12
  run_step "Installing OpenSSH server" install_openssh_server
12
13
  run_step "Installing Python runtime" install_python_runtime
13
14
  run_step "Installing Agent Package Manager" install_apm
@@ -49,6 +50,16 @@ configure_local_git() {
49
50
  fi
50
51
  }
51
52
 
53
+ configure_runtime_secret_environment() {
54
+ local bashrc="${HOME}/.bashrc"
55
+ local source_line='source /opt/boxdown/devcontainer/utils/secret-env-bootstrap.sh'
56
+
57
+ touch "${bashrc}"
58
+ if ! grep -Fqx "${source_line}" "${bashrc}"; then
59
+ printf '%s\n' "${source_line}" >> "${bashrc}"
60
+ fi
61
+ }
62
+
52
63
  install_apm() {
53
64
  # Agent Package Manager: https://github.com/microsoft/apm
54
65
  local installer
@@ -100,7 +111,18 @@ install_snyk_cli() {
100
111
 
101
112
  install_1password_cli() {
102
113
  local op_version="2.32.1"
103
- curl -fsSL "https://cache.agilebits.com/dist/1P/op2/pkg/v${op_version}/op_linux_arm64_v${op_version}.zip" -o /tmp/op.zip
114
+ local op_arch
115
+
116
+ case "$(uname -m)" in
117
+ aarch64 | arm64) op_arch="arm64" ;;
118
+ x86_64 | amd64) op_arch="amd64" ;;
119
+ *)
120
+ echo "post-create: skipping 1Password CLI (unsupported arch: $(uname -m))" >&2
121
+ return 0
122
+ ;;
123
+ esac
124
+
125
+ curl -fsSL "https://cache.agilebits.com/dist/1P/op2/pkg/v${op_version}/op_linux_${op_arch}_v${op_version}.zip" -o /tmp/op.zip
104
126
  python3 -c "import zipfile; zipfile.ZipFile('/tmp/op.zip').extract('op', '/tmp')"
105
127
  sudo mv /tmp/op /usr/local/bin/op && chmod +x /usr/local/bin/op && rm /tmp/op.zip
106
128
  }
@@ -13,7 +13,6 @@ DEVCONTAINER_DIR="$(cd "${HOOKS_DIR}/.." && pwd)"
13
13
  main() {
14
14
  run_step "Preparing SSH runtime" configure_sshd_runtime
15
15
  run_step "Refreshing coding-agent CLIs" refresh_coding_agent_clis
16
- run_step "Cleaning ephemeral environment file" remove_ephemeral_env_file_if_present
17
16
  }
18
17
 
19
18
  progress() {
@@ -39,13 +38,4 @@ refresh_coding_agent_clis() {
39
38
  echo "post-start: warning: one or more coding-agent CLI refreshes failed." >&2
40
39
  }
41
40
 
42
- # When initializeCommand + runArgs inject secrets via .env.development, remove the file
43
- # after start so it is not left on disk and tooling that assumes absence does not break.
44
- remove_ephemeral_env_file_if_present() {
45
- local env_file=".env.development"
46
- if [[ -f "$env_file" ]]; then
47
- rm -f "$env_file"
48
- fi
49
- }
50
-
51
41
  main "$@"
@@ -6,22 +6,75 @@ set -euo pipefail
6
6
  TARGET_PATH="${BOXDOWN_GITCONFIG_TARGET_PATH:-/home/node/.gitconfig}"
7
7
  KEY_PATH="${BOXDOWN_GIT_SIGNING_KEY_PATH:-/opt/boxdown/state/git-signing/signing-key.pub}"
8
8
  ENABLED="${BOXDOWN_GIT_SIGNING_ENABLED:-0}"
9
+ HOST_REASON="${BOXDOWN_GIT_SIGNING_REASON:-host-preflight-unavailable}"
9
10
 
10
11
  git_global() {
11
12
  GIT_CONFIG_GLOBAL="${TARGET_PATH}" git config --global "$@"
12
13
  }
13
14
 
15
+ git_local_value() {
16
+ git config --local --get "$1" 2>/dev/null || true
17
+ }
18
+
19
+ git_global_value() {
20
+ git_global --get "$1" 2>/dev/null || true
21
+ }
22
+
23
+ preserve_user_signing_preference() {
24
+ local local_commit global_commit local_format global_format local_program global_program
25
+ local_commit="$(git_local_value commit.gpgsign)"
26
+ global_commit="$(git_global_value commit.gpgsign)"
27
+ local_format="$(git_local_value gpg.format)"
28
+ global_format="$(git_global_value gpg.format)"
29
+ local_program="$(git_local_value gpg.program)"
30
+ global_program="$(git_global_value gpg.program)"
31
+
32
+ [[ "${local_commit}" == "false" || "${global_commit}" == "false" ]] && return 0
33
+ [[ -n "${local_program}" || -n "${global_program}" ]] && return 0
34
+ [[ -n "${local_format}" && "${local_format}" != "ssh" ]] && return 0
35
+ [[ -n "${global_format}" && "${global_format}" != "ssh" ]] && return 0
36
+ return 1
37
+ }
38
+
14
39
  disable_signing() {
40
+ local reason="${1:-unknown}"
41
+ if preserve_user_signing_preference; then
42
+ printf 'boxdown: commit signing configuration preserved (reason: %s).\n' "${reason}" >&2
43
+ return 0
44
+ fi
15
45
  git_global --unset-all gpg.format >/dev/null 2>&1 || true
16
46
  git_global --unset-all user.signingkey >/dev/null 2>&1 || true
17
47
  git_global --unset-all gpg.program >/dev/null 2>&1 || true
18
48
  git_global --replace-all commit.gpgsign false
19
- printf '%s\n' 'boxdown: commit signing unavailable; commits will remain unsigned.' >&2
49
+ printf 'boxdown: commit signing unavailable (reason: %s); commits will remain unsigned.\n' "${reason}" >&2
20
50
  }
21
51
 
22
52
  enable_signing() {
23
- if [[ ! -r "${KEY_PATH}" ]] || ! ssh-add -L | grep -qF "$(awk 'NR == 1 { print $1 " " $2 }' "${KEY_PATH}")"; then
24
- disable_signing
53
+ if preserve_user_signing_preference; then
54
+ printf '%s\n' 'boxdown: preserving explicit user Git signing configuration.' >&2
55
+ return 0
56
+ fi
57
+
58
+ if [[ ! -r "${KEY_PATH}" ]]; then
59
+ disable_signing 'container-key-unavailable'
60
+ return 0
61
+ fi
62
+
63
+ if ! bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-agent-proxy-bootstrap.sh"; then
64
+ disable_signing 'container-agent-proxy-unavailable'
65
+ return 0
66
+ fi
67
+
68
+ local agent_identities
69
+ if ! agent_identities="$(ssh-add -L 2>/dev/null)"; then
70
+ disable_signing 'container-agent-unavailable'
71
+ return 0
72
+ fi
73
+
74
+ local selected_key
75
+ selected_key="$(awk 'NR == 1 { print $1 " " $2 }' "${KEY_PATH}")"
76
+ if ! grep -qF -- "${selected_key}" <<<"${agent_identities}"; then
77
+ disable_signing 'container-key-not-loaded'
25
78
  return 0
26
79
  fi
27
80
 
@@ -43,7 +96,7 @@ enable_signing() {
43
96
  git commit --allow-empty -m 'boxdown signing probe' >/dev/null
44
97
  ); then
45
98
  rm -rf "${probe_dir}"
46
- disable_signing
99
+ disable_signing 'container-signing-probe-failed'
47
100
  return 0
48
101
  fi
49
102
  rm -rf "${probe_dir}"
@@ -52,5 +105,5 @@ enable_signing() {
52
105
  if [[ "${ENABLED}" == "1" ]]; then
53
106
  enable_signing
54
107
  else
55
- disable_signing
108
+ disable_signing "${HOST_REASON}"
56
109
  fi
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # Shell bootstrap for Boxdown runtime-mounted secret environment files.
3
+
4
+ SECRET_ENV_DIR="${BOXDOWN_SECRET_ENV_DIR:-/run/boxdown/secrets}"
5
+
6
+ export_if_present() {
7
+ local name="$1"
8
+ local path="${SECRET_ENV_DIR}/${name}"
9
+ local value
10
+
11
+ [[ -r "${path}" ]] || return 0
12
+ IFS= read -r value < "${path}" || true
13
+
14
+ if [[ -n "${value}" ]]; then
15
+ export "${name}=${value}"
16
+ fi
17
+ }
18
+
19
+ export_if_present ANTHROPIC_API_KEY
20
+ export_if_present SNYK_TOKEN
21
+ export_if_present OP_SERVICE_ACCOUNT_TOKEN
22
+ unset value
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ # Expose a root-only forwarded SSH-agent socket to the non-root container user.
3
+
4
+ set -euo pipefail
5
+
6
+ SOURCE_SOCKET="${BOXDOWN_GIT_SIGNING_SOURCE_SOCKET:-/run/boxdown/ssh-agent.sock}"
7
+ TARGET_SOCKET="${SSH_AUTH_SOCK:-/run/boxdown/ssh-agent-node.sock}"
8
+ PROXY_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-agent-proxy.mjs"
9
+
10
+ if ssh-add -L >/dev/null 2>&1; then
11
+ exit 0
12
+ fi
13
+
14
+ if ! command -v sudo >/dev/null 2>&1 || ! command -v node >/dev/null 2>&1; then
15
+ exit 1
16
+ fi
17
+
18
+ sudo -n node "${PROXY_SCRIPT}" --source "${SOURCE_SOCKET}" --target "${TARGET_SOCKET}" --uid "$(id -u)" --gid "$(id -g)" >/dev/null 2>&1 &
19
+
20
+ for _ in $(seq 1 20); do
21
+ if [[ -S "${TARGET_SOCKET}" ]] && ssh-add -L >/dev/null 2>&1; then
22
+ exit 0
23
+ fi
24
+ sleep 0.1
25
+ done
26
+
27
+ exit 1
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { chmodSync, chownSync, existsSync, unlinkSync } from 'node:fs'
4
+ import { createConnection, createServer } from 'node:net'
5
+
6
+ function option (name) {
7
+ const index = process.argv.indexOf(name)
8
+ return index === -1 ? undefined : process.argv[index + 1]
9
+ }
10
+
11
+ const source = option('--source')
12
+ const target = option('--target')
13
+ const uid = Number(option('--uid'))
14
+ const gid = Number(option('--gid'))
15
+
16
+ if (source === undefined || target === undefined || !Number.isInteger(uid) || !Number.isInteger(gid)) {
17
+ process.stderr.write('ssh-agent-proxy: expected --source, --target, --uid, and --gid.\n')
18
+ process.exit(2)
19
+ }
20
+
21
+ if (existsSync(target)) unlinkSync(target)
22
+
23
+ const server = createServer((client) => {
24
+ const upstream = createConnection(source)
25
+ client.pipe(upstream)
26
+ upstream.pipe(client)
27
+ client.on('error', () => upstream.destroy())
28
+ upstream.on('error', () => client.destroy())
29
+ })
30
+
31
+ server.on('error', (error) => {
32
+ process.stderr.write(`ssh-agent-proxy: ${error.message}\n`)
33
+ process.exit(1)
34
+ })
35
+
36
+ server.listen(target, () => {
37
+ chmodSync(target, 0o600)
38
+ chownSync(target, uid, gid)
39
+ })
package/dist/bin/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_main = require('../main-Df4E8ARj.cjs');
2
+ const require_main = require('../main-CT2n9qcb.cjs');
3
3
 
4
4
  //#region src/bin/cli.ts
5
5
  require_main.runCli().then((exitCode) => {
package/dist/bin/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { o as runCli } from "../main-XMBsKjIK.mjs";
2
+ import { c as runCli } from "../main-O__JaiXe.mjs";
3
3
 
4
4
  //#region src/bin/cli.ts
5
5
  runCli().then((exitCode) => {