| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | # Atomically install the exact binaries built by this checkout and leave a |
| 5 | # durable identity receipt. Replacing the directory entry (rather than copying |
| 6 | # over a running vnode) keeps live sessions on their old image while new shells |
| 7 | # get the new build safely. |
| 8 | |
| 9 | script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 10 | repo_root="$(cd "${script_dir}/../.." && pwd)" |
| 11 | src_dir="${1:-${repo_root}/target/release}" |
| 12 | |
| 13 | if [[ ! -x "${src_dir}/codewhale" || ! -x "${src_dir}/codew" || ! -x "${src_dir}/codewhale-tui" ]]; then |
| 14 | echo "ERROR: expected executable codewhale, codew, and codewhale-tui in ${src_dir}" >&2 |
| 15 | # Since #5245 local builds are unstamped ("(dev)"); a dogfood build must be |
| 16 | # stamped explicitly or the identity check below will (correctly) refuse it. |
| 17 | echo "Build first: DEEPSEEK_BUILD_SHA=\$(git rev-parse HEAD) cargo build --release -p codewhale-cli -p codewhale-tui --locked" >&2 |
| 18 | exit 1 |
| 19 | fi |
| 20 | |
| 21 | source_sha="$(git -C "${repo_root}" rev-parse HEAD)" |
| 22 | source_dirty="$(git -C "${repo_root}" status --porcelain --untracked-files=no)" |
| 23 | if [[ -n "${source_dirty}" ]]; then |
| 24 | if [[ "${CODEWHALE_ALLOW_DIRTY_DOGFOOD:-0}" != "1" ]]; then |
| 25 | echo "ERROR: refusing to install from a dirty source tree" >&2 |
| 26 | echo "Commit/stash the source, or set CODEWHALE_ALLOW_DIRTY_DOGFOOD=1 explicitly." >&2 |
| 27 | exit 1 |
| 28 | fi |
| 29 | source_identity="${source_sha}-dirty" |
| 30 | else |
| 31 | source_identity="${source_sha}" |
| 32 | fi |
| 33 | |
| 34 | cli_version="$("${src_dir}/codewhale" --version)" |
| 35 | shim_version="$("${src_dir}/codew" --version)" |
| 36 | tui_version="$("${src_dir}/codewhale-tui" --version)" |
| 37 | short_sha="${source_sha:0:12}" |
| 38 | if [[ "${cli_version}" != *"${short_sha}"* || "${shim_version}" != *"${short_sha}"* || "${tui_version}" != *"${short_sha}"* ]]; then |
| 39 | echo "ERROR: release binaries do not embed current HEAD ${short_sha}" >&2 |
| 40 | echo " codewhale: ${cli_version}" >&2 |
| 41 | echo " codew: ${shim_version}" >&2 |
| 42 | echo " codewhale-tui: ${tui_version}" >&2 |
| 43 | echo "Rebuild this checkout before installing:" >&2 |
| 44 | echo " DEEPSEEK_BUILD_SHA=\$(git rev-parse HEAD) cargo build --release -p codewhale-cli -p codewhale-tui --locked" >&2 |
| 45 | exit 1 |
| 46 | fi |
| 47 | cli_sha="$(shasum -a 256 "${src_dir}/codewhale" | awk '{print $1}')" |
| 48 | shim_sha="$(shasum -a 256 "${src_dir}/codew" | awk '{print $1}')" |
| 49 | tui_sha="$(shasum -a 256 "${src_dir}/codewhale-tui" | awk '{print $1}')" |
| 50 | |
| 51 | default_install_dirs="${HOME}/.cargo/bin:${HOME}/.local/bin" |
| 52 | for command_name in codewhale codewhale-tui codew; do |
| 53 | if command_path="$(command -v "${command_name}" 2>/dev/null)" \ |
| 54 | && [[ "${command_path}" == "${HOME}/"* ]]; then |
| 55 | command_dir="$(dirname "${command_path}")" |
| 56 | if [[ ":${default_install_dirs}:" != *":${command_dir}:"* ]]; then |
| 57 | default_install_dirs="${default_install_dirs}:${command_dir}" |
| 58 | fi |
| 59 | fi |
| 60 | done |
| 61 | IFS=':' read -r -a dest_dirs <<< "${CODEWHALE_INSTALL_DIRS:-${default_install_dirs}}" |
| 62 | |
| 63 | install_binary() { |
| 64 | local src="$1" |
| 65 | local dst="$2" |
| 66 | local tmp="${dst}.tmp.$$" |
| 67 | trap 'rm -f -- "${tmp}"' RETURN |
| 68 | cp "${src}" "${tmp}" |
| 69 | chmod 0755 "${tmp}" |
| 70 | # macOS AMFI kills ad-hoc linker-signed binaries after `cp` into a new |
| 71 | # path (SIGKILL on exec, no output). Re-sign in place with a proper |
| 72 | # ad-hoc signature so self-built dogfood installs run after install. |
| 73 | if [[ "$(uname -s)" == "Darwin" ]] && command -v codesign >/dev/null 2>&1; then |
| 74 | codesign --force --sign - "${tmp}" >/dev/null 2>&1 || { |
| 75 | echo "WARN: codesign failed for ${tmp}; binary may be killed by AMFI after install" >&2 |
| 76 | } |
| 77 | fi |
| 78 | mv -f "${tmp}" "${dst}" |
| 79 | # Re-sign the final path as well — some macOS versions re-evaluate on rename. |
| 80 | if [[ "$(uname -s)" == "Darwin" ]] && command -v codesign >/dev/null 2>&1; then |
| 81 | codesign --force --sign - "${dst}" >/dev/null 2>&1 || true |
| 82 | fi |
| 83 | cmp -s "${src}" "${dst}" || { |
| 84 | # cmp can fail after codesign rewrote the code signature; verify exec instead. |
| 85 | if [[ ! -x "${dst}" ]]; then |
| 86 | echo "ERROR: installed binary not executable: ${dst}" >&2 |
| 87 | return 1 |
| 88 | fi |
| 89 | } |
| 90 | trap - RETURN |
| 91 | } |
| 92 | |
| 93 | installed=() |
| 94 | for dest in "${dest_dirs[@]}"; do |
| 95 | mkdir -p "${dest}" |
| 96 | install_binary "${src_dir}/codewhale" "${dest}/codewhale" |
| 97 | install_binary "${src_dir}/codew" "${dest}/codew" |
| 98 | install_binary "${src_dir}/codewhale-tui" "${dest}/codewhale-tui" |
| 99 | installed+=("${dest}/codewhale" "${dest}/codewhale-tui" "${dest}/codew") |
| 100 | done |
| 101 | |
| 102 | verify_fresh_shell_binary() { |
| 103 | local command_name="$1" |
| 104 | local command_path |
| 105 | local command_version |
| 106 | local dest |
| 107 | local is_installed=0 |
| 108 | |
| 109 | command_path="$(zsh -lc "command -v ${command_name}" 2>/dev/null || true)" |
| 110 | if [[ -z "${command_path}" || ! -x "${command_path}" ]]; then |
| 111 | echo "ERROR: fresh login shell cannot resolve ${command_name}" >&2 |
| 112 | return 1 |
| 113 | fi |
| 114 | for dest in "${dest_dirs[@]}"; do |
| 115 | if [[ "${command_path}" == "${dest}/${command_name}" ]]; then |
| 116 | is_installed=1 |
| 117 | break |
| 118 | fi |
| 119 | done |
| 120 | if [[ "${is_installed}" != "1" ]]; then |
| 121 | echo "ERROR: fresh-shell ${command_name} resolves outside the installed destinations: ${command_path}" >&2 |
| 122 | return 1 |
| 123 | fi |
| 124 | command_version="$(zsh -lc "${command_name} --version" 2>/dev/null || true)" |
| 125 | if [[ "${command_version}" != *"${short_sha}"* ]]; then |
| 126 | echo "ERROR: fresh-shell ${command_name} does not report current HEAD ${short_sha}" >&2 |
| 127 | return 1 |
| 128 | fi |
| 129 | printf '%s\n' "${command_path}" |
| 130 | } |
| 131 | |
| 132 | path_cli="$(verify_fresh_shell_binary codewhale)" |
| 133 | path_shim="$(verify_fresh_shell_binary codew)" |
| 134 | path_tui="$(verify_fresh_shell_binary codewhale-tui)" |
| 135 | installed_cli_sha="$(shasum -a 256 "${path_cli}" | awk '{print $1}')" |
| 136 | installed_shim_sha="$(shasum -a 256 "${path_shim}" | awk '{print $1}')" |
| 137 | installed_tui_sha="$(shasum -a 256 "${path_tui}" | awk '{print $1}')" |
| 138 | |
| 139 | default_receipt_root="${HOME}/.codewhale/dogfood-receipts" |
| 140 | if [[ -d "/Volumes/VIXinSSD/CW/backups" ]]; then |
| 141 | default_receipt_root="/Volumes/VIXinSSD/CW/backups/dogfood-installs" |
| 142 | fi |
| 143 | receipt_root="${CODEWHALE_DOGFOOD_RECEIPT_DIR:-${default_receipt_root}}" |
| 144 | mkdir -p "${receipt_root}" |
| 145 | timestamp="$(date -u +%Y%m%dT%H%M%SZ)" |
| 146 | receipt="${receipt_root}/${timestamp}-${source_sha:0:12}.txt" |
| 147 | { |
| 148 | echo "installed_at_utc=${timestamp}" |
| 149 | echo "source_repo=${repo_root}" |
| 150 | echo "source_commit=${source_identity}" |
| 151 | echo "source_dir=${src_dir}" |
| 152 | echo "codewhale_version=${cli_version}" |
| 153 | echo "codewhale_sha256=${cli_sha}" |
| 154 | echo "installed_codewhale_sha256=${installed_cli_sha}" |
| 155 | echo "codew_version=${shim_version}" |
| 156 | echo "codew_sha256=${shim_sha}" |
| 157 | echo "installed_codew_sha256=${installed_shim_sha}" |
| 158 | echo "codewhale_tui_version=${tui_version}" |
| 159 | echo "codewhale_tui_sha256=${tui_sha}" |
| 160 | echo "installed_codewhale_tui_sha256=${installed_tui_sha}" |
| 161 | echo "fresh_shell_codewhale=${path_cli}" |
| 162 | echo "fresh_shell_codew=${path_shim}" |
| 163 | echo "fresh_shell_codewhale_tui=${path_tui}" |
| 164 | printf 'installed_path=%s\n' "${installed[@]}" |
| 165 | } >"${receipt}" |
| 166 | |
| 167 | echo "Installed ${source_identity}:" |
| 168 | printf ' %s\n' "${installed[@]}" |
| 169 | echo "Receipt: ${receipt}" |
| 170 | echo "Fresh-shell check: zsh -lc 'type -a codew codewhale codewhale-tui; codew --version; codewhale-tui --version'" |
| 171 |