feat(ops): add imgproxy native runtime tooling and record 103 deployment
Memind CI / Test, build, and release guards (pull_request) Failing after 18s

Bundle vendored imgproxy with Mach-O dylibs for 103 launchd, fix install to exec the binary directly with launchctl enable, and document the live native release in the runtime topology.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-31 05:38:14 +08:00
parent 08ea5e05a9
commit 78e5a6c026
8 changed files with 734 additions and 0 deletions
+77
View File
@@ -166,3 +166,80 @@ ssh john@58.38.22.103 'curl -fsS http://127.0.0.1:8082/health'
ssh john@58.38.22.103 'for p in $(seq 18006 18014); do curl -kfsS https://127.0.0.1:$p/status; echo; done'
ssh john@58.38.22.103 'grep ^TKMIND_API_TARGETS= /Users/john/Project/Memind/.env'
```
## imgproxy
103 imgproxy runs as a **native vendor runtime** (no brew on 103; Docker `memind-imgproxy` retired).
| Item | Value |
|------|-------|
| Runtime base | `/Users/john/Project/imgproxy-runtime` |
| Current release (2026-07-31) | `20260731-052342-imgproxy-native` (**native launchd**, imgproxy 4.0.7) |
| Legacy Docker release | `20260715-3819ccd5` (retired) |
| Vendor binary | `releases/<id>/vendor/imgproxy-runtime/bin/imgproxy` (+ bundled dylibs) |
| LaunchAgent | `cn.tkmind.imgproxy` → direct binary exec → `127.0.0.1:20082` |
| Compat proxy | `cn.tkmind.imgproxy-compat``10.10.0.2:20081` |
| Public entry | `https://img.tkmind.cn` |
| Signing config | Portal `.env``IMGPROXY_SIGNING_KEY` / `IMGPROXY_SIGNING_SALT` |
| Storage root | `MINDSPACE_STORAGE_ROOT=/Users/john/MindSpace/data/mindspace` |
Build locally, release to 103:
```bash
npm run build:imgproxy-runtime-native
bash scripts/release-imgproxy-native-prod.sh --dry-run # from release/* on synced main
bash scripts/release-imgproxy-native-prod.sh --yes
```
Install on 103 reads Portal `.env` for signing keys and storage root; launchd plist **must exec the vendored binary directly** (not `/bin/bash` wrapper). If bootstrap fails with `error 5`, run `launchctl enable gui/$(id -u)/cn.tkmind.imgproxy` before re-bootstrap.
Verify:
```bash
ssh john@58.38.22.103 'curl -fsS http://127.0.0.1:20082/health'
ssh john@58.38.22.103 'curl -fsS http://10.10.0.2:20081/health'
curl -fsS https://img.tkmind.cn/health
```
Legacy Docker release remains in `scripts/release-imgproxy-runtime-prod.sh` for rollback only.
Do **not** point launchd at `/Users/john/Project/Memind/vendor/...` without a Portal runtime that actually ships that path; imgproxy is an independent runtime under `imgproxy-runtime/`.
## image_make
`image_make` is an independent service. Portal runtime does **not** bundle it.
| Item | Value |
|------|-------|
| Local source | `/Users/john/Project/image_make` |
| 103 runtime base | `/Users/john/Project/image_make` |
| Current release (2026-07-30) | `20260730-220317-native-ceaff399a073` (**native launchd**) |
| Legacy Docker release | `20260720-standalone-rc1` (retired) |
| Listen | `127.0.0.1:18083` |
| LaunchAgent | `cn.tkmind.image-make` |
| Persistent env | `shared/.env` |
| Persistent data | `shared/data/artifacts` |
| Build (local) | `bash scripts/build-image-make-runtime-native.sh` |
| Release (103) | `bash scripts/release-image-make-runtime-prod.sh --yes` |
| Source fingerprint | `release-metadata.env``IMAGE_MAKE_SOURCE_TREE_SHA256` |
103 runs **darwin/arm64 native** (uv venv in release tar). Docker/Colima is no longer required for image_make.
**ComfyUI is a separate runtime** (not bundled into image_make tar):
| Item | Value |
|------|-------|
| Local build tree | `/Users/john/Project/image_make/.runtime/ComfyUI` + `comfyui-venv` |
| 103 runtime base | `/Users/john/Project/comfyui-runtime` |
| Current release (2026-07-30) | `20260730-222051-native-e87e7a81bb76` (**native launchd**) |
| Publish entry | `bash scripts/release-comfyui-runtime-prod.sh --yes` (from local Mac) |
| LaunchAgent | `cn.tkmind.comfyui``127.0.0.1:8188` |
| Fingerprint | `config/comfyui/runtime.lock` + SD1.5 SHA256 |
103 must **not** run `install_comfyui_runtime.sh` or download models directly; the local machine stages
`/Users/john/Project/comfyui-runtime/releases/<id>/` with production paths, tars it, and scp installs it.
Enable `comfyui` in Portal `h5_image_make_admin_config` only after ComfyUI is running.
Current production: default `aliyun_bailian`, `comfyui` enabled as failover backup.
Memind integration: HTTP client only (`image-make-client.mjs`); config via `IMAGE_MAKE_BASE_URL` + token. Docs: [image-make-integration.md](./image-make-integration.md).
+1
View File
@@ -48,6 +48,7 @@
"test:release-gate:all": "node scripts/run-release-gate.mjs --mode all",
"verify:release-gate-report": "node scripts/verify-release-gate-report.mjs",
"build:imgproxy-runtime": "bash scripts/build-imgproxy-runtime-image.sh",
"build:imgproxy-runtime-native": "bash scripts/build-imgproxy-runtime-native.sh",
"build:mindspace-service-runtime": "node scripts/build-mindspace-service-runtime.mjs",
"check:mindspace-public-links": "node scripts/check-mindspace-public-links.mjs --downloads-only",
"check:mindspace-public-links:all": "node scripts/check-mindspace-public-links.mjs --all-links",
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# Materialize a darwin/arm64 imgproxy vendor binary into .runtime/imgproxy-native/.
# Prefers the local Homebrew Cellar binary; bundles non-system dylibs for 103.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT_DIR="${IMGPROXY_NATIVE_OUT_DIR:-${ROOT}/.runtime/imgproxy-native}"
VENDOR_DIR="${OUT_DIR}/vendor/imgproxy-runtime"
BIN_DIR="${VENDOR_DIR}/bin"
LIB_DIR="${VENDOR_DIR}/lib"
IMGPROXY_VERSION="${IMGPROXY_VERSION:-4.0.7}"
BREW_PREFIX="${HOMEBREW_PREFIX:-/opt/homebrew}"
DRY_RUN=0
# shellcheck source=scripts/lib/bundle-mach-dylibs.sh
source "${ROOT}/scripts/lib/bundle-mach-dylibs.sh"
case "${1:-}" in
--dry-run) DRY_RUN=1 ;;
--help|-h)
cat <<'EOF'
Usage: bash scripts/build-imgproxy-runtime-native.sh [--dry-run]
Builds .runtime/imgproxy-native with:
vendor/imgproxy-runtime/bin/imgproxy
vendor/imgproxy-runtime/lib/*.dylib
vendor/imgproxy-runtime/VERSION.txt
vendor/imgproxy-runtime/SHA256SUMS
scripts/install-imgproxy-native-prod.sh
scripts/run-imgproxy-prod.sh
imgproxy-compat-proxy.mjs
RUNBOOK.txt
Set IMGPROXY_SOURCE_BIN to copy a specific binary, or IMGPROXY_VERSION for Cellar lookup.
EOF
exit 0
;;
esac
resolve_imgproxy_binary() {
if [[ -n "${IMGPROXY_SOURCE_BIN:-}" ]]; then
[[ -x "${IMGPROXY_SOURCE_BIN}" ]] || {
echo "IMGPROXY_SOURCE_BIN is not executable: ${IMGPROXY_SOURCE_BIN}" >&2
exit 1
}
printf '%s\n' "${IMGPROXY_SOURCE_BIN}"
return
fi
local cellar_bin="${BREW_PREFIX}/Cellar/imgproxy/${IMGPROXY_VERSION}/bin/imgproxy"
if [[ -x "${cellar_bin}" ]]; then
printf '%s\n' "${cellar_bin}"
return
fi
if command -v brew >/dev/null 2>&1; then
local tmp cache_glob candidate
tmp="$(mktemp -d "${TMPDIR:-/tmp}/imgproxy-bottle.XXXXXX")"
brew fetch --force-bottle imgproxy >/dev/null
cache_glob="${HOME}/Library/Caches/Homebrew/downloads/*imgproxy*${IMGPROXY_VERSION}*.bottle.tar.gz"
candidate="$(ls -1 ${cache_glob} 2>/dev/null | head -1 || true)"
[[ -n "${candidate}" ]] || {
echo "imgproxy ${IMGPROXY_VERSION} bottle not found after brew fetch" >&2
exit 1
}
tar -xzf "${candidate}" -C "${tmp}"
find "${tmp}" -path "*/bin/imgproxy" -type f | head -1
return
fi
echo "imgproxy binary not found; install via brew or set IMGPROXY_SOURCE_BIN" >&2
exit 1
}
if [[ "${DRY_RUN}" == 1 ]]; then
candidate="$(resolve_imgproxy_binary || true)"
printf 'version=%s\nsource=%s\nout_dir=%s\n' "${IMGPROXY_VERSION}" "${candidate:-missing}" "${OUT_DIR}"
exit 0
fi
mkdir -p "${BIN_DIR}" "${LIB_DIR}" "${OUT_DIR}/scripts"
source_bin="$(resolve_imgproxy_binary)"
cp "${source_bin}" "${BIN_DIR}/imgproxy"
chmod 755 "${BIN_DIR}/imgproxy"
bundle_mach_dylibs "${BIN_DIR}/imgproxy" "${LIB_DIR}" "${source_bin}"
"${BIN_DIR}/imgproxy" version > "${VENDOR_DIR}/VERSION.txt"
(
cd "${VENDOR_DIR}"
shasum -a 256 bin/imgproxy VERSION.txt lib/* > SHA256SUMS
)
cp "${ROOT}/scripts/install-imgproxy-native-prod.sh" "${OUT_DIR}/scripts/"
cp "${ROOT}/scripts/run-imgproxy-prod.sh" "${OUT_DIR}/scripts/"
cp "${ROOT}/scripts/imgproxy-compat-proxy.mjs" "${OUT_DIR}/"
chmod 755 "${OUT_DIR}/scripts/install-imgproxy-native-prod.sh" "${OUT_DIR}/scripts/run-imgproxy-prod.sh"
cat > "${OUT_DIR}/RUNBOOK.txt" <<EOF
TKMind imgproxy native runtime (vendor binary)
Production base: /Users/john/Project/imgproxy-runtime
Persistent env: Portal /Users/john/Project/Memind/.env (IMGPROXY_SIGNING_KEY/SALT)
Active release: current -> releases/<release-id>/
LaunchAgent: cn.tkmind.imgproxy (127.0.0.1:20082)
Compat proxy: cn.tkmind.imgproxy-compat (10.10.0.2:20081)
Health: http://127.0.0.1:20082/health
Public: https://img.tkmind.cn/health
Vendor binary: vendor/imgproxy-runtime/bin/imgproxy
Bundled libs: vendor/imgproxy-runtime/lib/
Built from: ${source_bin}
This runtime replaces the Docker imgproxy container on 103.
Portal runtime releases stay separate.
EOF
lib_count="$(find "${LIB_DIR}" -name '*.dylib' | wc -l | tr -d ' ')"
printf 'imgproxy native runtime built at %s (%s, %s dylibs)\n' \
"${OUT_DIR}" "$(cat "${VENDOR_DIR}/VERSION.txt")" "${lib_count}"
+31
View File
@@ -38,3 +38,34 @@ test('release verifies checksums before loading the image and backs up launch st
assert.match(release, /release\/\*/);
assert.match(release, /rev-parse origin\/main/);
});
test('native builder vendors a pinned darwin arm64 imgproxy binary', () => {
const builder = read('scripts/build-imgproxy-runtime-native.sh');
assert.match(builder, /vendor\/imgproxy-runtime\/bin\/imgproxy/);
assert.match(builder, /vendor\/imgproxy-runtime\/lib/);
assert.match(builder, /bundle_mach_dylibs/);
assert.match(builder, /shasum -a 256 bin\/imgproxy VERSION\.txt lib\/\*/);
assert.match(builder, /IMGPROXY_SOURCE_BIN/);
assert.match(builder, /run-imgproxy-prod\.sh/);
});
test('native installer uses the vendored binary and retires docker imgproxy', () => {
const installer = read('scripts/install-imgproxy-native-prod.sh');
assert.match(installer, /vendor\/imgproxy-runtime\/bin\/imgproxy/);
assert.match(installer, /ProgramArguments/);
assert.match(installer, /IMGPROXY_KEY/);
assert.match(installer, /127\.0\.0\.1:20082/);
assert.match(installer, /10\.10\.0\.2/);
assert.match(installer, /"\$\{DOCKER_BIN\}" stop/);
assert.match(installer, /"\$\{DOCKER_BIN\}" rm -f/);
assert.match(installer, /cn\.tkmind\.imgproxy/);
});
test('native release bundles vendor artifacts and verifies health on 103', () => {
const release = read('scripts/release-imgproxy-native-prod.sh');
assert.match(release, /build-imgproxy-runtime-native\.sh/);
assert.match(release, /vendor\/imgproxy-runtime\/bin\/imgproxy/);
assert.match(release, /install-imgproxy-native-prod\.sh/);
assert.match(release, /imgproxy-native-runtime-\$RELEASE_ID\.tar\.gz/);
assert.match(release, /img\.tkmind\.cn\/health/);
});
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Runs only on 103 from a verified imgproxy native release directory.
set -euo pipefail
ROOT="${ROOT:-/Users/john/Project/Memind}"
RUNTIME_BASE="${IMGPROXY_RUNTIME_BASE:-/Users/john/Project/imgproxy-runtime}"
RUNTIME_DIR="${IMGPROXY_RUNTIME_DIR:?IMGPROXY_RUNTIME_DIR is required}"
IMGPROXY_BIN="${RUNTIME_DIR}/vendor/imgproxy-runtime/bin/imgproxy"
RUN_SCRIPT="${RUNTIME_DIR}/scripts/run-imgproxy-prod.sh"
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
DOCKER_BIN="${DOCKER_BIN:-/opt/homebrew/bin/docker}"
CONTAINER="${IMGPROXY_CONTAINER:-memind-imgproxy}"
NATIVE_LABEL="cn.tkmind.imgproxy"
COMPAT_LABEL="cn.tkmind.imgproxy-compat"
NATIVE_PLIST="${HOME}/Library/LaunchAgents/${NATIVE_LABEL}.plist"
COMPAT_PLIST="${HOME}/Library/LaunchAgents/${COMPAT_LABEL}.plist"
LOG_DIR="${HOME}/Library/Logs"
GUI="gui/$(id -u)"
[[ -x "${IMGPROXY_BIN}" ]] || {
echo "missing vendor imgproxy binary: ${IMGPROXY_BIN}" >&2
exit 1
}
[[ -x "${RUN_SCRIPT}" ]] || {
echo "missing imgproxy run script: ${RUN_SCRIPT}" >&2
exit 1
}
set -a
source "${ROOT}/.env"
set +a
: "${IMGPROXY_SIGNING_KEY:?missing IMGPROXY_SIGNING_KEY}"
: "${IMGPROXY_SIGNING_SALT:?missing IMGPROXY_SIGNING_SALT}"
: "${MINDSPACE_STORAGE_ROOT:?missing MINDSPACE_STORAGE_ROOT}"
cd "${RUNTIME_DIR}/vendor/imgproxy-runtime"
shasum -a 256 -c SHA256SUMS >/dev/null
mkdir -p "${HOME}/Library/LaunchAgents" "${LOG_DIR}"
cat > "${NATIVE_PLIST}" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${NATIVE_LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>${IMGPROXY_BIN}</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>IMGPROXY_BIND</key>
<string>127.0.0.1:20082</string>
<key>IMGPROXY_LOCAL_FILESYSTEM_ROOT</key>
<string>${MINDSPACE_STORAGE_ROOT}</string>
<key>IMGPROXY_KEY</key>
<string>${IMGPROXY_SIGNING_KEY}</string>
<key>IMGPROXY_SALT</key>
<string>${IMGPROXY_SIGNING_SALT}</string>
<key>IMGPROXY_USE_ETAG</key>
<string>true</string>
<key>IMGPROXY_ENABLE_WEBP_DETECTION</key>
<string>true</string>
</dict>
<key>WorkingDirectory</key>
<string>${ROOT}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>10</integer>
<key>StandardOutPath</key>
<string>${LOG_DIR}/imgproxy.log</string>
<key>StandardErrorPath</key>
<string>${LOG_DIR}/imgproxy.log</string>
</dict>
</plist>
EOF
xattr -cr "${RUNTIME_DIR}" 2>/dev/null || true
if [[ -x "${DOCKER_BIN}" ]]; then
"${DOCKER_BIN}" stop "${CONTAINER}" >/dev/null 2>&1 || true
"${DOCKER_BIN}" rm -f "${CONTAINER}" >/dev/null 2>&1 || true
"${DOCKER_BIN}" update --restart=no "${CONTAINER}" >/dev/null 2>&1 || true
fi
launchctl bootout "${GUI}/${NATIVE_LABEL}" 2>/dev/null || true
plutil -lint "${NATIVE_PLIST}" >/dev/null
launchctl enable "${GUI}/${NATIVE_LABEL}" 2>/dev/null || true
launchctl bootstrap "${GUI}" "${NATIVE_PLIST}"
launchctl kickstart -k "${GUI}/${NATIVE_LABEL}"
for _ in $(seq 1 30); do
curl -fsS --max-time 2 http://127.0.0.1:20082/health >/dev/null && break
sleep 1
done
curl -fsS --max-time 3 http://127.0.0.1:20082/health >/dev/null
cat > "${COMPAT_PLIST}" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>${COMPAT_LABEL}</string>
<key>ProgramArguments</key><array><string>${NODE_BIN}</string><string>${RUNTIME_DIR}/imgproxy-compat-proxy.mjs</string></array>
<key>WorkingDirectory</key><string>${RUNTIME_DIR}</string>
<key>EnvironmentVariables</key><dict><key>IMGPROXY_COMPAT_HOST</key><string>10.10.0.2</string><key>IMGPROXY_COMPAT_PORT</key><string>20081</string><key>IMGPROXY_UPSTREAM</key><string>http://127.0.0.1:20082</string></dict>
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>ThrottleInterval</key><integer>10</integer>
<key>StandardOutPath</key><string>${LOG_DIR}/imgproxy-compat.log</string><key>StandardErrorPath</key><string>${LOG_DIR}/imgproxy-compat.log</string>
</dict></plist>
EOF
launchctl bootout "${GUI}/${COMPAT_LABEL}" 2>/dev/null || true
plutil -lint "${COMPAT_PLIST}" >/dev/null
launchctl enable "${GUI}/${COMPAT_LABEL}" 2>/dev/null || true
launchctl bootstrap "${GUI}" "${COMPAT_PLIST}"
launchctl kickstart -k "${GUI}/${COMPAT_LABEL}"
for _ in $(seq 1 30); do
curl -fsS --max-time 2 http://10.10.0.2:20081/health >/dev/null && break
sleep 1
done
curl -fsS --max-time 3 http://10.10.0.2:20081/health >/dev/null
printf 'imgproxy native installed from %s (%s)\n' "${RUNTIME_DIR}" "$("${IMGPROXY_BIN}" version)"
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/env bash
# Recursively copy non-system dylibs next to a Mach-O binary and rewrite load paths.
set -euo pipefail
find_homebrew_lib() {
local base="$1"
find "${HOMEBREW_PREFIX:-/opt/homebrew}/Cellar" "${HOMEBREW_PREFIX:-/opt/homebrew}/opt" \
-name "${base}" -path '*/lib/*' 2>/dev/null | head -1
}
resolve_dependency_path() {
local owner="$1"
local dep="$2"
local reference="${3:-${owner}}"
if [[ "${dep}" != @* ]]; then
[[ -f "${dep}" ]] || return 1
printf '%s\n' "${dep}"
return 0
fi
local owner_dir reference_dir
owner_dir="$(cd "$(dirname "${owner}")" && pwd)"
reference_dir="$(cd "$(dirname "${reference}")" && pwd)"
case "${dep}" in
@loader_path/*)
local candidate="${owner_dir}/${dep#@loader_path/}"
[[ -f "${candidate}" ]] || return 1
printf '%s\n' "${candidate}"
;;
@executable_path/*)
local rel="${dep#@executable_path/}"
local candidate="${owner_dir}/${rel}"
if [[ ! -f "${candidate}" ]]; then
candidate="${reference_dir}/${rel}"
fi
if [[ ! -f "${candidate}" ]]; then
candidate="$(find_homebrew_lib "$(basename "${rel}")")"
fi
[[ -f "${candidate}" ]] || return 1
printf '%s\n' "${candidate}"
;;
@rpath/*)
local rel="${dep#@rpath/}"
local rpath candidate
while IFS= read -r rpath; do
case "${rpath}" in
@loader_path/*) candidate="${owner_dir}/${rpath#@loader_path/}/${rel}" ;;
@executable_path/*) candidate="${owner_dir}/${rpath#@executable_path/}/${rel}" ;;
*) candidate="${rpath}/${rel}" ;;
esac
if [[ -f "${candidate}" ]]; then
printf '%s\n' "${candidate}"
return 0
fi
done < <(
otool -l "${owner}" | awk '
/cmd LC_RPATH/ { want=1; next }
want && /path / {
sub(/^.*path /, "", $0)
sub(/ \(offset.*$/, "", $0)
print
want=0
}
'
)
candidate="${owner_dir}/${rel}"
[[ -f "${candidate}" ]] || return 1
printf '%s\n' "${candidate}"
;;
*)
return 1
;;
esac
}
list_load_dylibs() {
local target="$1"
otool -l "${target}" | awk '
/cmd LC_LOAD_DYLIB/ { want=1; next }
want && /name / {
sub(/^.*name /, "", $0)
sub(/ \(offset.*$/, "", $0)
print
want=0
}
'
}
bundle_mach_dylibs() {
local binary="${1:?binary path required}"
local lib_dir="${2:?lib directory required}"
local reference_binary="${3:-${binary}}"
[[ -f "${binary}" ]] || {
echo "bundle_mach_dylibs: missing binary: ${binary}" >&2
return 1
}
mkdir -p "${lib_dir}"
local -a queue=("${binary}")
local seen_list=" "
local -a sources=()
is_system_lib() {
case "$1" in
/usr/lib/*|/System/*|/Library/*) return 0 ;;
esac
return 1
}
already_seen() {
[[ "${seen_list}" == *" $1 "* ]]
}
remember() {
seen_list+=" $1 "
}
while ((${#queue[@]} > 0)); do
local target="${queue[0]}"
queue=("${queue[@]:1}")
[[ -f "${target}" ]] || continue
local dep resolved
while IFS= read -r dep; do
[[ -n "${dep}" ]] || continue
is_system_lib "${dep}" && continue
already_seen "${dep}" && continue
remember "${dep}"
resolved="$(resolve_dependency_path "${target}" "${dep}" "${reference_binary}")" || {
echo "bundle_mach_dylibs: missing dependency ${dep} for ${target}" >&2
return 1
}
already_seen "${resolved}" || remember "${resolved}"
if [[ "${resolved}" != "${binary}" ]]; then
sources+=("${resolved}")
queue+=("${resolved}")
fi
done < <(list_load_dylibs "${target}")
done
local src base
for src in "${sources[@]}"; do
base="$(basename "${src}")"
cp -f "${src}" "${lib_dir}/${base}"
chmod 755 "${lib_dir}/${base}"
done
rewrite_dep() {
local target="$1"
local dep="$2"
local new_path="$3"
install_name_tool -change "${dep}" "${new_path}" "${target}" 2>/dev/null || true
}
rewrite_targets() {
local target="$1"
local prefix="$2"
local dep base
while IFS= read -r dep; do
[[ -n "${dep}" ]] || continue
is_system_lib "${dep}" && continue
[[ "${dep}" == @* ]] && continue
base="$(basename "${dep}")"
[[ -f "${lib_dir}/${base}" ]] || continue
rewrite_dep "${target}" "${dep}" "${prefix}${base}"
done < <(list_load_dylibs "${target}")
}
rewrite_rpath_targets() {
local target="$1"
local prefix="$2"
local dep base
while IFS= read -r dep; do
[[ -n "${dep}" ]] || continue
[[ "${dep}" == @rpath/* ]] || continue
base="$(basename "${dep}")"
[[ -f "${lib_dir}/${base}" ]] || continue
rewrite_dep "${target}" "${dep}" "${prefix}${base}"
done < <(list_load_dylibs "${target}")
}
local changed=1
while ((changed)); do
changed=0
rewrite_targets "${binary}" "@executable_path/../lib/"
rewrite_rpath_targets "${binary}" "@executable_path/../lib/"
for staged in "${lib_dir}"/*.dylib; do
[[ -f "${staged}" ]] || continue
before="$(list_load_dylibs "${staged}" | { grep -E 'homebrew|^@rpath/' || true; } | wc -l | tr -d ' ')"
rewrite_targets "${staged}" "@loader_path/"
rewrite_rpath_targets "${staged}" "@loader_path/"
after="$(list_load_dylibs "${staged}" | { grep -E 'homebrew|^@rpath/' || true; } | wc -l | tr -d ' ')"
[[ "${before}" != "${after}" ]] && changed=1
done
done
local remaining
remaining="$(list_load_dylibs "${binary}" | grep homebrew || true)"
if [[ -n "${remaining}" ]]; then
echo "bundle_mach_dylibs: unresolved homebrew dependencies remain:" >&2
printf '%s\n' "${remaining}" >&2
return 1
fi
install_name_tool -add_rpath "@executable_path/../lib" "${binary}" 2>/dev/null || true
local staged
for staged in "${lib_dir}"/*.dylib; do
[[ -f "${staged}" ]] || continue
codesign --force --sign - "${staged}" >/dev/null
done
codesign --force --sign - "${binary}" >/dev/null
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
bundle_mach_dylibs "$@"
fi
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Release the independently versioned native imgproxy runtime (vendor binary) to 103.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
HOST="${STUDIO_HOST:-john@58.38.22.103}"
REMOTE_ROOT="${STUDIO_REMOTE_ROOT:-/Users/john/Project}"
RUNTIME_BASE="${REMOTE_ROOT}/imgproxy-runtime"
INCOMING="${REMOTE_ROOT}/incoming/imgproxy-runtime"
RELEASE_ID="$(date +%Y%m%d-%H%M%S)-$(git -C "$ROOT" rev-parse --short HEAD)"
TMP="$(mktemp -d "${TMPDIR:-/tmp}/imgproxy-native-release.XXXXXX")"
DRY_RUN=0
cleanup() { rm -rf "$TMP"; }
trap cleanup EXIT
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
-h|--help)
cat <<'EOF'
Usage: bash scripts/release-imgproxy-native-prod.sh [--dry-run]
Builds and installs the vendor imgproxy runtime on 103. Portal runtime is unchanged.
EOF
exit 0
;;
*)
echo "unknown argument: $1" >&2
exit 1
;;
esac
shift
done
git -C "$ROOT" fetch origin main --quiet
branch="$(git -C "$ROOT" branch --show-current)"
[[ "$branch" == release/* ]] || { echo 'imgproxy native production release must run from a release/* branch' >&2; exit 1; }
[[ -z "$(git -C "$ROOT" status --porcelain)" ]] || { echo 'worktree must be clean' >&2; exit 1; }
[[ "$(git -C "$ROOT" rev-parse HEAD)" == "$(git -C "$ROOT" rev-parse origin/main)" ]] || {
echo 'main must match origin/main' >&2
exit 1
}
bash "$ROOT/scripts/check-release-ready.sh"
bash "$ROOT/scripts/build-imgproxy-runtime-native.sh"
RUNTIME_DIR="$ROOT/.runtime/imgproxy-native"
for file in \
vendor/imgproxy-runtime/bin/imgproxy \
vendor/imgproxy-runtime/SHA256SUMS \
vendor/imgproxy-runtime/VERSION.txt \
scripts/install-imgproxy-native-prod.sh \
imgproxy-compat-proxy.mjs \
RUNBOOK.txt; do
[[ -f "$RUNTIME_DIR/$file" ]] || { echo "missing runtime artifact: $file" >&2; exit 1; }
done
MANIFEST="$TMP/release-manifest.txt"
{
echo "release_id=${RELEASE_ID}"
echo "git_head=$(git -C "$ROOT" rev-parse HEAD)"
echo "imgproxy_version=$(cat "$RUNTIME_DIR/vendor/imgproxy-runtime/VERSION.txt")"
echo "runtime_mode=native-vendor"
} > "$MANIFEST"
mkdir -p "$TMP/runtime"
cp -R "$RUNTIME_DIR/." "$TMP/runtime/"
cp "$MANIFEST" "$TMP/runtime/"
tar -C "$TMP" -czf "$TMP/imgproxy-native-runtime-$RELEASE_ID.tar.gz" runtime
(cd "$TMP" && shasum -a 256 "imgproxy-native-runtime-$RELEASE_ID.tar.gz" > "imgproxy-native-runtime-$RELEASE_ID.tar.gz.sha256")
if [[ "$DRY_RUN" == 1 ]]; then
shasum -a 256 -c "$TMP/imgproxy-native-runtime-$RELEASE_ID.tar.gz.sha256"
echo "imgproxy native dry-run artifact verified: $TMP/imgproxy-native-runtime-$RELEASE_ID.tar.gz"
exit 0
fi
ssh -o BatchMode=yes "$HOST" "mkdir -p '$INCOMING' '$RUNTIME_BASE/releases' '$RUNTIME_BASE/backups'"
scp -q "$TMP/imgproxy-native-runtime-$RELEASE_ID.tar.gz" "$TMP/imgproxy-native-runtime-$RELEASE_ID.tar.gz.sha256" "$HOST:$INCOMING/"
ssh -o BatchMode=yes "$HOST" "RELEASE_ID='$RELEASE_ID' RUNTIME_BASE='$RUNTIME_BASE' INCOMING='$INCOMING' bash -s" <<'REMOTE'
set -euo pipefail
archive="$INCOMING/imgproxy-native-runtime-$RELEASE_ID.tar.gz"
sha="$archive.sha256"
release_dir="$RUNTIME_BASE/releases/$RELEASE_ID"
backup_dir="$RUNTIME_BASE/backups/$RELEASE_ID"
current_link="$RUNTIME_BASE/current"
previous_target="$(readlink "$current_link" 2>/dev/null || true)"
mkdir -p "$release_dir" "$backup_dir"
cp "$HOME/Library/LaunchAgents/cn.tkmind.imgproxy.plist" "$backup_dir/imgproxy.plist" 2>/dev/null || true
cp "$HOME/Library/LaunchAgents/cn.tkmind.imgproxy-compat.plist" "$backup_dir/imgproxy-compat.plist" 2>/dev/null || true
cd "$INCOMING"
shasum -a 256 -c "$sha"
tar -xzf "$archive" -C "$release_dir" --strip-components=1
rollback() {
launchctl bootout "gui/$(id -u)/cn.tkmind.imgproxy" >/dev/null 2>&1 || true
launchctl bootout "gui/$(id -u)/cn.tkmind.imgproxy-compat" >/dev/null 2>&1 || true
if [[ -n "$previous_target" && -d "$previous_target" ]]; then
ln -sfn "$previous_target" "$current_link"
else
rm -f "$current_link"
fi
if [[ -f "$backup_dir/imgproxy.plist" ]]; then
cp "$backup_dir/imgproxy.plist" "$HOME/Library/LaunchAgents/cn.tkmind.imgproxy.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/cn.tkmind.imgproxy.plist" >/dev/null 2>&1 || true
fi
if [[ -f "$backup_dir/imgproxy-compat.plist" ]]; then
cp "$backup_dir/imgproxy-compat.plist" "$HOME/Library/LaunchAgents/cn.tkmind.imgproxy-compat.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/cn.tkmind.imgproxy-compat.plist" >/dev/null 2>&1 || true
fi
}
trap rollback ERR
IMGPROXY_RUNTIME_DIR="$release_dir" bash "$release_dir/scripts/install-imgproxy-native-prod.sh"
ln -sfn "$release_dir" "$current_link"
curl -fsS --max-time 5 http://127.0.0.1:20082/health >/dev/null
curl -fsS --max-time 5 http://10.10.0.2:20081/health >/dev/null
trap - ERR
printf '%s\n' "$RELEASE_ID" > "$RUNTIME_BASE/current-release.txt"
REMOTE
curl -kfsS --max-time 15 https://img.tkmind.cn/health >/dev/null
printf 'imgproxy native release verified: %s\n' "$RELEASE_ID"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
RUNTIME_BASE="${IMGPROXY_RUNTIME_BASE:-/Users/john/Project/imgproxy-runtime}"
RUNTIME_DIR="${IMGPROXY_RUNTIME_DIR:-${RUNTIME_BASE}/current}"
ROOT="${ROOT:-/Users/john/Project/Memind}"
IMGPROXY_BIN="${RUNTIME_DIR}/vendor/imgproxy-runtime/bin/imgproxy"
[[ -x "${IMGPROXY_BIN}" ]] || {
echo "[imgproxy-start] missing binary: ${IMGPROXY_BIN}" >&2
exit 1
}
if [[ -f "${ROOT}/.env" ]]; then
set -a
# shellcheck disable=SC1090
source "${ROOT}/.env"
set +a
fi
: "${IMGPROXY_SIGNING_KEY:?missing IMGPROXY_SIGNING_KEY}"
: "${IMGPROXY_SIGNING_SALT:?missing IMGPROXY_SIGNING_SALT}"
: "${MINDSPACE_STORAGE_ROOT:?missing MINDSPACE_STORAGE_ROOT}"
export IMGPROXY_BIND="${IMGPROXY_BIND:-127.0.0.1:20082}"
export IMGPROXY_LOCAL_FILESYSTEM_ROOT="${MINDSPACE_STORAGE_ROOT}"
export IMGPROXY_KEY="${IMGPROXY_SIGNING_KEY}"
export IMGPROXY_SALT="${IMGPROXY_SIGNING_SALT}"
export IMGPROXY_USE_ETAG="${IMGPROXY_USE_ETAG:-true}"
export IMGPROXY_ENABLE_WEBP_DETECTION="${IMGPROXY_ENABLE_WEBP_DETECTION:-true}"
exec "${IMGPROXY_BIN}"