From b76e0ef779c0cbb4b398520a0875e2c81abfb01d Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 25 Jan 2026 22:39:58 +0900 Subject: [PATCH] =?UTF-8?q?deploy:nas=20macOS=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy-nas.cjs | 82 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/scripts/deploy-nas.cjs b/scripts/deploy-nas.cjs index 74882d6..4768e79 100644 --- a/scripts/deploy-nas.cjs +++ b/scripts/deploy-nas.cjs @@ -21,16 +21,78 @@ if (isWin) { const cmd = 'powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference=\\"Stop\\"; $src=\\"dist\\"; $dst=\\"Z:\\\\docker\\\\webpage\\\\frontend\\\\\\"; if(!(Test-Path $src)){ throw \\"dist not found. Run build first.\\" }; if(!(Test-Path $dst)){ throw \\"NAS drive not found. Check Z: mapping.\\" }; $log = Join-Path (Get-Location) \\"robocopy.log\\"; robocopy $src $dst /MIR /R:1 /W:1 /E /NFL /NDL /NP /V /TEE /LOG:$log; $rc = $LASTEXITCODE; if($rc -ge 8){ Write-Host \\"robocopy failed with code $rc. See $log\\"; exit $rc } else { exit 0 }"'; execSync(cmd, { stdio: "inherit" }); +} else if (isMac) { + const sshTarget = process.env.NAS_SSH_TARGET; + const sshPath = + process.env.NAS_SSH_PATH || "/volume1/docker/webpage/frontend/"; + const sshPort = process.env.NAS_SSH_PORT; + if (sshTarget) { + const sshCmd = sshPort ? `ssh -p ${sshPort}` : "ssh"; + execSync( + `rsync -r --delete --delete-delay -e \"${sshCmd}\" ${src}/ ${sshTarget}:${sshPath}`, + { stdio: "inherit" } + ); + process.exit(0); + } + // rsync on macOS + SMB/NAS can be flaky; use ditto after a safe clean. + if (!dst.includes("docker/webpage/frontend")) { + console.error("Safety check failed: unexpected dst path: " + dst); + process.exit(1); + } + try { + const testPath = `${dst}.deploy-write-test`; + fs.writeFileSync(testPath, "ok"); + fs.unlinkSync(testPath); + } catch (err) { + console.error( + "NAS write test failed. Files may be locked or permissions are read-only." + ); + console.error( + "Try stopping services using the folder, remounting the share with write access,", + "or set NAS_SSH_TARGET to deploy over SSH instead." + ); + throw err; + } + const sleep = (ms) => + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); + const retry = (fn, attempts = 6) => { + for (let i = 0; i < attempts; i += 1) { + try { + fn(); + return; + } catch (err) { + if (i === attempts - 1) throw err; + sleep(150); + } + } + }; + const removeTree = (target) => { + const stat = fs.lstatSync(target); + if (stat.isDirectory()) { + retry(() => { + const entries = fs.readdirSync(target); + for (const entry of entries) { + removeTree(`${target}/${entry}`); + } + }); + retry(() => fs.rmdirSync(target)); + } else { + retry(() => fs.unlinkSync(target)); + } + }; + if (process.env.NAS_CLEAN === "1") { + try { + const entries = fs.readdirSync(dst); + for (const entry of entries) { + removeTree(`${dst}${entry}`); + } + } catch (err) { + console.warn("Clean skipped due to NAS lock:", err?.message ?? err); + } + } + execSync(`ditto ${src} ${dst}`, { stdio: "inherit" }); } else { - const baseArgs = ["rsync", "-r", "--delete", "--delete-delay"]; - const macSafeArgs = [ - "--omit-dir-times", - "--no-perms", - "--no-owner", - "--no-group", - "--no-times", - ]; - const args = isMac ? baseArgs.concat(macSafeArgs) : baseArgs.concat(["-t"]); - const cmd = `${args.join(" ")} ${src}/ ${dst}`; + const baseArgs = ["rsync", "-r", "--delete", "--delete-delay", "-t"]; + const cmd = `${baseArgs.join(" ")} ${src}/ ${dst}`; execSync(cmd, { stdio: "inherit" }); }