diff --git a/backend/src/services/deploy.ts b/backend/src/services/deploy.ts index 524e360d..1ed99f22 100644 --- a/backend/src/services/deploy.ts +++ b/backend/src/services/deploy.ts @@ -252,12 +252,15 @@ async function firstDeploy( try { if (repoConfig.aptPackages.length > 0) { - await runSshStep(previewId, sshSession, `sudo apt-get install -y ${repoConfig.aptPackages.join(" ")}`); + await runSshStep(previewId, sshSession, `sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ${repoConfig.aptPackages.join(" ")}`); } const giteaPat = user.giteaPAT ? decrypt(user.giteaPAT) : ""; - const authCloneUrl = cloneUrl.replace("https://", `https://${user.giteaUsername}:${giteaPat}@`); - await runSshStep(previewId, sshSession, `git clone ${authCloneUrl} /opt/app`); + const parsedUrl = new URL(cloneUrl.startsWith("http") ? cloneUrl : `https://${cloneUrl}`); + parsedUrl.username = encodeURIComponent(user.giteaUsername || ""); + parsedUrl.password = encodeURIComponent(giteaPat); + const authCloneUrl = parsedUrl.toString(); + await runSshStep(previewId, sshSession, `git clone '${authCloneUrl}' /opt/app`); await runSshStep(previewId, sshSession, `cd /opt/app && git fetch origin pull/${prNumber}/head:pp-pr && git checkout pp-pr`); await setupAndBuild(previewId, sshSession, repoConfig, preview, commitSha, true); @@ -334,6 +337,12 @@ async function redeploy( } } +const NVM_PREFIX = `export NVM_DIR="/root/.nvm"; source "$NVM_DIR/nvm.sh" 2>/dev/null;`; + +function withNvm(cmd: string): string { + return `bash -c '${NVM_PREFIX} ${cmd.replace(/'/g, `'"'"'`)}'`; +} + async function setupAndBuild( previewId: number, sshSession: SshSession, @@ -342,15 +351,22 @@ async function setupAndBuild( commitSha: string, isFirstProvision: boolean, ) { - await detectAndUseNode(previewId, sshSession); + // Detect and use Node version + const nvmCmd = `${NVM_PREFIX} if [ -f /opt/app/.nvmrc ]; then nvm install && nvm use; else nvm use default; fi`; + await runSshStep(previewId, sshSession, `bash -c '${nvmCmd}'`, false); + // Write .env file const envVars = repoConfig.envVars as Record; - const envContent = Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join("\n"); - await runSshStep(previewId, sshSession, `cat > /opt/app/.env << 'PPEOF'\n${envContent}\nPPEOF`); + const envLines = Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join("\\n"); + if (envLines) { + await runSshStep(previewId, sshSession, `printf '${envLines}\\n' > /opt/app/.env`, false); + } else { + await runSshStep(previewId, sshSession, `touch /opt/app/.env`, false); + } if (isFirstProvision) { for (const cmd of repoConfig.setupCommands) { - await runSshStep(previewId, sshSession, `cd /opt/app && ${cmd}`); + await runSshStep(previewId, sshSession, withNvm(`cd /opt/app && ${cmd}`)); } } @@ -361,16 +377,15 @@ async function setupAndBuild( await runSshStep(previewId, sshSession, `cd /opt/app && docker compose -f ${composePath} up -d --build --force-recreate 2>&1`); } else { for (const cmd of repoConfig.buildCommands) { - await runSshStep(previewId, sshSession, `cd /opt/app && ${cmd}`); + await runSshStep(previewId, sshSession, withNvm(`cd /opt/app && ${cmd}`)); } for (const cmd of repoConfig.postBuildCommands) { - await runSshStep(previewId, sshSession, `cd /opt/app && ${cmd}`); + await runSshStep(previewId, sshSession, withNvm(`cd /opt/app && ${cmd}`)); } if (repoConfig.runCommand) { - const res = await runSshStep(previewId, sshSession, - `cd /opt/app && nohup ${repoConfig.runCommand} > /opt/app/pp.log 2>&1 & echo $!` - ); + const startCmd = withNvm(`cd /opt/app && nohup ${repoConfig.runCommand} > /opt/app/pp.log 2>&1 & echo $!`); + const res = await runSshStep(previewId, sshSession, startCmd); const pid = parseInt(res.stdout.trim(), 10); if (!isNaN(pid)) { await prisma.preview.update({ where: { id: previewId }, data: { pid } }); @@ -379,11 +394,6 @@ async function setupAndBuild( } } -async function detectAndUseNode(previewId: number, sshSession: SshSession) { - const nvmSource = `export NVM_DIR="/root/.nvm" && source "$NVM_DIR/nvm.sh"`; - const res = await runSshStep(previewId, sshSession, `${nvmSource} && [ -f /opt/app/.nvmrc ] && nvm install && nvm use || nvm use default 2>&1`, false); -} - async function runSshStep(previewId: number, sshSession: SshSession, command: string, throwOnFail = true) { checkAbortSession(sshSession); await appendLog(previewId, `$ ${command}\n`);