fix: improve SSH command execution in deploy service
- Prefix all nvm-dependent commands with NVM source via withNvm() helper - Fix .env file writing using printf instead of heredoc (which does not work over SSH exec) - Fix git clone URL encoding using URL class for proper credential encoding - Add DEBIAN_FRONTEND=noninteractive to apt-get for non-interactive installs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -252,12 +252,15 @@ async function firstDeploy(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (repoConfig.aptPackages.length > 0) {
|
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 giteaPat = user.giteaPAT ? decrypt(user.giteaPAT) : "";
|
||||||
const authCloneUrl = cloneUrl.replace("https://", `https://${user.giteaUsername}:${giteaPat}@`);
|
const parsedUrl = new URL(cloneUrl.startsWith("http") ? cloneUrl : `https://${cloneUrl}`);
|
||||||
await runSshStep(previewId, sshSession, `git clone ${authCloneUrl} /opt/app`);
|
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 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);
|
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(
|
async function setupAndBuild(
|
||||||
previewId: number,
|
previewId: number,
|
||||||
sshSession: SshSession,
|
sshSession: SshSession,
|
||||||
@@ -342,15 +351,22 @@ async function setupAndBuild(
|
|||||||
commitSha: string,
|
commitSha: string,
|
||||||
isFirstProvision: boolean,
|
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<string, string>;
|
const envVars = repoConfig.envVars as Record<string, string>;
|
||||||
const envContent = Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join("\n");
|
const envLines = Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join("\\n");
|
||||||
await runSshStep(previewId, sshSession, `cat > /opt/app/.env << 'PPEOF'\n${envContent}\nPPEOF`);
|
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) {
|
if (isFirstProvision) {
|
||||||
for (const cmd of repoConfig.setupCommands) {
|
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`);
|
await runSshStep(previewId, sshSession, `cd /opt/app && docker compose -f ${composePath} up -d --build --force-recreate 2>&1`);
|
||||||
} else {
|
} else {
|
||||||
for (const cmd of repoConfig.buildCommands) {
|
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) {
|
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) {
|
if (repoConfig.runCommand) {
|
||||||
const res = await runSshStep(previewId, sshSession,
|
const startCmd = withNvm(`cd /opt/app && nohup ${repoConfig.runCommand} > /opt/app/pp.log 2>&1 & echo $!`);
|
||||||
`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);
|
const pid = parseInt(res.stdout.trim(), 10);
|
||||||
if (!isNaN(pid)) {
|
if (!isNaN(pid)) {
|
||||||
await prisma.preview.update({ where: { id: previewId }, data: { 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) {
|
async function runSshStep(previewId: number, sshSession: SshSession, command: string, throwOnFail = true) {
|
||||||
checkAbortSession(sshSession);
|
checkAbortSession(sshSession);
|
||||||
await appendLog(previewId, `$ ${command}\n`);
|
await appendLog(previewId, `$ ${command}\n`);
|
||||||
|
|||||||
Reference in New Issue
Block a user