Add remote build fallback to deploy script
This commit is contained in:
@@ -8,6 +8,7 @@ REMOTE_HOST="${BOSS_REMOTE_HOST:-${REMOTE_USER}@${REMOTE_HOSTNAME}}"
|
||||
REMOTE_DIR="${BOSS_REMOTE_DIR:-/opt/boss}"
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no"
|
||||
KEYCHAIN_SERVICE="${BOSS_KEYCHAIN_SERVICE:-boss-server-debug-ssh}"
|
||||
BUILD_MODE="${BOSS_DEPLOY_BUILD_MODE:-auto}"
|
||||
|
||||
resolve_password() {
|
||||
if [[ -n "${BOSS_SERVER_PASS:-}" ]]; then
|
||||
@@ -36,23 +37,70 @@ else
|
||||
SSH_PREFIX=(ssh ${=SSH_OPTS})
|
||||
fi
|
||||
|
||||
BOSS_RUNTIME_ROOT="$ROOT_DIR" BOSS_STATE_FILE="$ROOT_DIR/data/boss-state.json" npm run build
|
||||
run_local_build() {
|
||||
BOSS_RUNTIME_ROOT="$ROOT_DIR" BOSS_STATE_FILE="$ROOT_DIR/data/boss-state.json" npm run build
|
||||
}
|
||||
|
||||
use_remote_build=false
|
||||
build_log="$(mktemp)"
|
||||
cleanup() {
|
||||
rm -f "$build_log"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
case "$BUILD_MODE" in
|
||||
local)
|
||||
run_local_build
|
||||
;;
|
||||
remote)
|
||||
use_remote_build=true
|
||||
;;
|
||||
auto|"")
|
||||
if ! run_local_build 2>&1 | tee "$build_log"; then
|
||||
if grep -q "ENOSPC" "$build_log"; then
|
||||
echo "Local build hit ENOSPC, falling back to remote build." >&2
|
||||
use_remote_build=true
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported BOSS_DEPLOY_BUILD_MODE: $BUILD_MODE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
"${SSH_PREFIX[@]}" "$REMOTE_HOST" "sudo mkdir -p $REMOTE_DIR && sudo chown -R ${REMOTE_USER}:${REMOTE_USER} $REMOTE_DIR && sudo rm -rf $REMOTE_DIR/.next"
|
||||
|
||||
RSYNC_EXCLUDES=(
|
||||
--exclude ".git"
|
||||
--exclude "node_modules"
|
||||
--exclude "data/"
|
||||
--exclude "android/app/build"
|
||||
--exclude ".DS_Store"
|
||||
)
|
||||
|
||||
if [[ "$use_remote_build" == true ]]; then
|
||||
RSYNC_EXCLUDES+=(--exclude ".next")
|
||||
fi
|
||||
|
||||
rsync -az --delete \
|
||||
--exclude ".git" \
|
||||
--exclude "node_modules" \
|
||||
--exclude "data/" \
|
||||
--exclude ".DS_Store" \
|
||||
"${RSYNC_EXCLUDES[@]}" \
|
||||
-e "$RSYNC_RSH" \
|
||||
"$ROOT_DIR/" "$REMOTE_HOST:$REMOTE_DIR/"
|
||||
|
||||
if [[ "$use_remote_build" == true ]]; then
|
||||
REMOTE_INSTALL_AND_BUILD_CMD="npm install && BOSS_RUNTIME_ROOT=$REMOTE_DIR BOSS_STATE_FILE=$REMOTE_DIR/data/boss-state.json npm run build && npm prune --omit=dev"
|
||||
else
|
||||
REMOTE_INSTALL_AND_BUILD_CMD="npm install --omit=dev"
|
||||
fi
|
||||
|
||||
POST_SYNC_REMOTE_CMD="
|
||||
sudo bash $REMOTE_DIR/scripts/bootstrap-server.sh &&
|
||||
sudo chown -R ${REMOTE_USER}:${REMOTE_USER} $REMOTE_DIR &&
|
||||
cd $REMOTE_DIR &&
|
||||
npm install --omit=dev &&
|
||||
$REMOTE_INSTALL_AND_BUILD_CMD &&
|
||||
sudo systemctl restart boss-web &&
|
||||
sudo systemctl restart caddy &&
|
||||
sleep 2 &&
|
||||
|
||||
@@ -84,3 +84,93 @@ exec "$@"
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("deploy-server falls back to remote build when local build hits ENOSPC", async () => {
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "boss-deploy-script-remote-build-"));
|
||||
const binDir = path.join(tempRoot, "bin");
|
||||
const logDir = path.join(tempRoot, "logs");
|
||||
const repoRoot = "/Users/kris/code/boss";
|
||||
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await mkdir(logDir, { recursive: true });
|
||||
|
||||
await writeExecutable(
|
||||
path.join(binDir, "security"),
|
||||
`#!/bin/sh
|
||||
printf 'Asd123456.'
|
||||
`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "npm"),
|
||||
`#!/bin/sh
|
||||
COUNT_FILE="${path.join(logDir, "npm-count")}"
|
||||
count=0
|
||||
if [ -f "$COUNT_FILE" ]; then
|
||||
count="$(cat "$COUNT_FILE")"
|
||||
fi
|
||||
count=$((count + 1))
|
||||
printf '%s' "$count" > "$COUNT_FILE"
|
||||
printf '%s\\0' "$*" >> "${path.join(logDir, "npm.log")}"
|
||||
if [ "$count" -eq 1 ]; then
|
||||
printf 'Error: ENOSPC: no space left on device\\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "rsync"),
|
||||
`#!/bin/sh
|
||||
printf '%s\\0' "$*" >> "${path.join(logDir, "rsync.log")}"
|
||||
exit 0
|
||||
`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "ssh"),
|
||||
`#!/bin/sh
|
||||
printf '%s\\0' "$*" >> "${path.join(logDir, "ssh.log")}"
|
||||
exit 0
|
||||
`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "sshpass"),
|
||||
`#!/bin/sh
|
||||
if [ "$1" = "-e" ]; then
|
||||
shift
|
||||
fi
|
||||
exec "$@"
|
||||
`,
|
||||
);
|
||||
|
||||
try {
|
||||
await execFileAsync("zsh", [path.join(repoRoot, "scripts/deploy-server.sh")], {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${binDir}:${process.env.PATH ?? ""}`,
|
||||
},
|
||||
});
|
||||
|
||||
const rsyncLog = await readFile(path.join(logDir, "rsync.log"), "utf8");
|
||||
const rsyncArgs = rsyncLog
|
||||
.split("\0")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
assert.match(rsyncArgs, /--exclude \.next/);
|
||||
|
||||
const sshLog = await readFile(path.join(logDir, "ssh.log"), "utf8");
|
||||
const sshCalls = sshLog
|
||||
.split("\0")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
assert.equal(sshCalls.length, 2);
|
||||
assert.match(sshCalls[1] ?? "", /npm install && BOSS_RUNTIME_ROOT=\/opt\/boss BOSS_STATE_FILE=\/opt\/boss\/data\/boss-state\.json npm run build/);
|
||||
assert.match(sshCalls[1] ?? "", /npm prune --omit=dev/);
|
||||
assert.doesNotMatch(sshCalls[1] ?? "", /npm install --omit=dev/);
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user