feat(ops): add imgproxy native runtime tooling and record 103 deployment
Memind CI / Test, build, and release guards (pull_request) Failing after 18s
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:
@@ -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).
|
||||
|
||||
@@ -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",
|
||||
|
||||
Executable
+118
@@ -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}"
|
||||
@@ -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/);
|
||||
});
|
||||
|
||||
Executable
+128
@@ -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)"
|
||||
Executable
+223
@@ -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
|
||||
Executable
+124
@@ -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"
|
||||
Executable
+32
@@ -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}"
|
||||
Reference in New Issue
Block a user