Fix idempotent FTPS directory setup (#217)

The production FTPS deploy now authenticates, but lftp exits when cPanel
returns 550 File exists for pre-created deployment directories. Use lftp
mkdir -p -f for the archive and activation directories so retries remain
idempotent while subsequent upload operations still surface real access
failures.\n\nVerification:\n- vitest tests/unit/cpanel-deploy.spec.js
(26 passed)\n- eslint scripts/release/cpanel-deploy-lib.mjs
tests/unit/cpanel-deploy.spec.js\n- git diff --check

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
This commit is contained in:
Jeppe B
2026-07-23 00:00:48 +02:00
committed by GitHub
co-authored by Jeppe Bundgaard
parent 9b3c06fc6f
commit fc67e7cf0b
2 changed files with 10 additions and 3 deletions
+7 -3
View File
@@ -247,6 +247,10 @@ function lftpQuote(value) {
return `'${String(value).replaceAll("'", `'\\''`)}'`;
}
function ensureLftpDirectory(value) {
return `mkdir -p -f ${lftpQuote(value)}`;
}
export function buildLftpScript(config, commands) {
const { host, user, password, root } = config.ftp;
const lines = [
@@ -498,8 +502,8 @@ export function createLftpTransport(config, dependencies = {}) {
let queueError;
try {
await run([
`mkdir -p ${lftpQuote("activation-requests")}`,
`mkdir -p ${lftpQuote("activation-results")}`,
ensureLftpDirectory("activation-requests"),
ensureLftpDirectory("activation-results"),
`rm -f ${lftpQuote(`${remoteRequest}.part`)}`,
`put ${lftpQuote(requestPath)} -o ${lftpQuote(`${remoteRequest}.part`)}`,
`mv ${lftpQuote(`${remoteRequest}.part`)} ${lftpQuote(remoteRequest)}`,
@@ -557,7 +561,7 @@ export function createLftpTransport(config, dependencies = {}) {
const downloadedChecksum = path.join(temporaryDirectory, checksumName);
try {
await run([
`mkdir -p ${lftpQuote("archives")}`,
ensureLftpDirectory("archives"),
`rm -f ${lftpQuote(`archives/${archiveName}.part`)}`,
`rm -f ${lftpQuote(`archives/${checksumName}.part`)}`,
`put ${lftpQuote(config.archivePath)} -o ${lftpQuote(`archives/${archiveName}.part`)}`,
+3
View File
@@ -248,6 +248,7 @@ describe("secure FTPS archive upload", () => {
});
expect(calls).toHaveLength(2);
expect(calls[0].args).toEqual([]);
expect(calls[0].input).toContain("mkdir -p -f 'archives'");
expect(calls[0].input).toContain(`${path.basename(archivePath)}.part`);
expect(calls[0].input).toContain("get 'archives/");
expect(calls[1].input).toContain(
@@ -303,6 +304,8 @@ describe("secure FTPS archive upload", () => {
target
);
expect(scripts[0]).not.toContain("release-activate.sh");
expect(scripts[0]).toContain("mkdir -p -f 'activation-requests'");
expect(scripts[0]).toContain("mkdir -p -f 'activation-results'");
expect(uploadedRequest).toContain(`release_id=${deploymentConfig.releaseId}`);
expect(uploadedRequest).toContain(`commit_sha=${deploymentConfig.expectedCommit}`);
expect(uploadedRequest).toContain("archive_sha256=" + "a".repeat(64));