Merge pull request 'fix: remount goosed after portal runtime swap' (#11) from codex/fix-goosed-remount into main
Memind CI / Test, build, and release guards (push) Failing after 1m23s
Memind CI / Test, build, and release guards (push) Failing after 1m23s
Ensure goosed containers remount the new Portal/MindSpace live directory after each atomic runtime swap.
This commit was merged in pull request #11.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const releaseScript = fs.readFileSync(
|
||||
new URL('./scripts/release-portal-runtime-prod.sh', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('103 release remounts goosed after swapping the live directory', () => {
|
||||
assert.match(releaseScript, /remount_goosed_after_live_swap\(\)/);
|
||||
assert.match(releaseScript, /docker-compose\.prod\.yml/);
|
||||
assert.match(releaseScript, /up -d --force-recreate/);
|
||||
assert.match(releaseScript, /MindSpace\/\.goosed-remount-/);
|
||||
assert.match(releaseScript, /containers\[@\].*!= 9/);
|
||||
assert.match(releaseScript, /seq 18006 18014/);
|
||||
assert.match(releaseScript, /\/opt\/portal\/mindspace-sandbox-mcp\.mjs/);
|
||||
|
||||
const swapIndex = releaseScript.indexOf('mv "${RUNTIME_DIR}" "${APP_DIR}"');
|
||||
const remountIndex = releaseScript.indexOf('remount_goosed_after_live_swap', swapIndex);
|
||||
const portalStartIndex = releaseScript.indexOf('say "启动新的 Portal runtime"');
|
||||
assert.ok(swapIndex >= 0, 'live directory swap must exist');
|
||||
assert.ok(remountIndex > swapIndex, 'goosed remount must happen after the live swap');
|
||||
assert.ok(portalStartIndex > remountIndex, 'Portal must start only after goosed remount succeeds');
|
||||
});
|
||||
@@ -378,6 +378,84 @@ OLD_LIVE_DIR="${ARCHIVE_DIR}/Memind-source-before-${RELEASE_ID}"
|
||||
BUNDLE="${INCOMING_DIR}/memind-portal-runtime-${RELEASE_ID}.tar.gz"
|
||||
MANIFEST="${INCOMING_DIR}/memind-portal-runtime-${RELEASE_ID}.manifest.txt"
|
||||
SHA_FILE="${INCOMING_DIR}/memind-portal-runtime-${RELEASE_ID}.sha256"
|
||||
|
||||
remount_goosed_after_live_swap() {
|
||||
local docker_bin="/opt/homebrew/bin/docker"
|
||||
local compose_dir="/Users/john/Project/goosed-prod"
|
||||
local compose_file="${compose_dir}/docker-compose.prod.yml"
|
||||
local marker_rel="MindSpace/.goosed-remount-${RELEASE_ID}"
|
||||
local marker_host="${APP_DIR}/${marker_rel}"
|
||||
local marker_value="${RELEASE_ID}-$(date +%s)"
|
||||
local ready=0
|
||||
|
||||
if [[ ! -x "${docker_bin}" || ! -f "${compose_file}" ]]; then
|
||||
echo "goosed remount failed: docker or ${compose_file} is unavailable" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "${APP_DIR}/MindSpace"
|
||||
printf '%s\n' "${marker_value}" > "${marker_host}"
|
||||
|
||||
say "重建 goosed 容器以刷新 Portal/MindSpace bind mount"
|
||||
if ! (
|
||||
cd "${compose_dir}"
|
||||
"${docker_bin}" compose -f "${compose_file}" up -d --force-recreate
|
||||
); then
|
||||
rm -f "${marker_host}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
local healthy=1
|
||||
local containers=()
|
||||
while IFS= read -r container; do
|
||||
[[ -n "${container}" ]] && containers+=("${container}")
|
||||
done < <("${docker_bin}" ps \
|
||||
--filter 'label=com.docker.compose.project=goosed-prod' \
|
||||
--format '{{.Names}}')
|
||||
|
||||
if ((${#containers[@]} != 9)); then
|
||||
healthy=0
|
||||
fi
|
||||
|
||||
local container
|
||||
for container in "${containers[@]}"; do
|
||||
local state
|
||||
state="$("${docker_bin}" inspect "${container}" --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' 2>/dev/null || true)"
|
||||
if [[ "${state}" != "healthy" ]]; then
|
||||
healthy=0
|
||||
continue
|
||||
fi
|
||||
local observed
|
||||
observed="$("${docker_bin}" exec "${container}" sh -lc "cat '${APP_DIR}/${marker_rel}'" 2>/dev/null || true)"
|
||||
if [[ "${observed}" != "${marker_value}" ]]; then
|
||||
healthy=0
|
||||
fi
|
||||
if ! "${docker_bin}" exec "${container}" sh -lc 'test -f /opt/portal/mindspace-sandbox-mcp.mjs' >/dev/null 2>&1; then
|
||||
healthy=0
|
||||
fi
|
||||
done
|
||||
|
||||
local port
|
||||
for port in $(seq 18006 18014); do
|
||||
if [[ "$(curl -skS -m 5 "https://127.0.0.1:${port}/status" 2>/dev/null || true)" != "ok" ]]; then
|
||||
healthy=0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${healthy}" -eq 1 ]]; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
rm -f "${marker_host}"
|
||||
if [[ "${ready}" -ne 1 ]]; then
|
||||
echo "goosed remount failed: containers did not become healthy on the new live directory" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
FULL_BACKUP_TAR="${BACKUP_DIR}/memind-full-${RELEASE_ID}-before.tar.gz"
|
||||
PERSIST_BACKUP_TAR="${BACKUP_DIR}/memind-persisted-${RELEASE_ID}-before.tar.gz"
|
||||
PERSISTED_ITEMS=(
|
||||
@@ -570,6 +648,8 @@ rm -rf "${OLD_LIVE_DIR}"
|
||||
mv "${APP_DIR}" "${OLD_LIVE_DIR}"
|
||||
mv "${RUNTIME_DIR}" "${APP_DIR}"
|
||||
|
||||
remount_goosed_after_live_swap
|
||||
|
||||
say "更新 LaunchAgent 指向 runtime 启动脚本"
|
||||
cat > "${HOME}/Library/LaunchAgents/${PORTAL_LABEL}.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
Reference in New Issue
Block a user