← Files RivetARCHIVED FILE
node_modules/rivet-design/dist/cli/serverHandshake.js
6.78 KB · Nov 7, 2026 · 13:16 UTC
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.acquireStartLock = exports.releaseReadyStartLock = exports.isServerStartInProgress = exports.isPidAlive = exports.removeHandshake = exports.readHandshake = exports.readHandshakeResult = exports.writeHandshake = exports.handshakeFilePath = exports.serverRuntimeDirectory = exports.CLI_HANDSHAKE_VERSION = void 0;
const crypto_1 = require("crypto");
const zod_1 = require("zod");
const ProjectRuntimeRecords_1 = require("../storage/library/ProjectRuntimeRecords");
const installationChannel_1 = require("../install/installationChannel");
const fs_1 = require("fs");
const path_1 = require("path");
const logger_1 = require("../utils/logger");
const pid_1 = require("../utils/pid");
Object.defineProperty(exports, "isPidAlive", { enumerable: true, get: function () { return pid_1.isPidAlive; } });
const errnoCode_1 = require("../utils/errnoCode");
const writeFileAtomic_1 = require("../utils/writeFileAtomic");
const log = (0, logger_1.createLogger)('CliServerHandshake');
exports.CLI_HANDSHAKE_VERSION = 1;
const serverRuntimeDirectory = (projectPath, channel = (0, installationChannel_1.installationChannel)()) => channel === 'production'
? (0, path_1.join)(projectPath, '.rivet', 'cli')
: (0, path_1.join)(projectPath, '.rivet', 'cli', channel);
exports.serverRuntimeDirectory = serverRuntimeDirectory;
const handshakeFilePath = (projectPath, channel = (0, installationChannel_1.installationChannel)()) => (0, path_1.join)((0, exports.serverRuntimeDirectory)(projectPath, channel), 'server.json');
exports.handshakeFilePath = handshakeFilePath;
const lockFilePath = (projectPath, channel = (0, installationChannel_1.installationChannel)()) => (0, path_1.join)((0, exports.serverRuntimeDirectory)(projectPath, channel), 'server.lock');
const OWNER_ONLY_FILE_MODE = 0o600;
const writeHandshake = (handshake, channel = (0, installationChannel_1.installationChannel)()) => {
const filePath = (0, exports.handshakeFilePath)(handshake.projectPath, channel);
(0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
(0, writeFileAtomic_1.writeFileAtomicSync)(filePath, JSON.stringify(handshake), {
mode: OWNER_ONLY_FILE_MODE,
});
};
exports.writeHandshake = writeHandshake;
const readHandshakeResult = (projectPath, channel = (0, installationChannel_1.installationChannel)()) => {
const filePath = (0, exports.handshakeFilePath)(projectPath, channel);
try {
const parsed = JSON.parse((0, fs_1.readFileSync)(filePath, 'utf8'));
if (parsed.version !== exports.CLI_HANDSHAKE_VERSION) {
return { status: 'invalid' };
}
if (typeof parsed.pid !== 'number' ||
!Number.isInteger(parsed.pid) ||
parsed.pid <= 0 ||
typeof parsed.rivetPort !== 'number' ||
!Number.isInteger(parsed.rivetPort) ||
parsed.rivetPort <= 0) {
return { status: 'invalid' };
}
return { status: 'found', handshake: parsed };
}
catch (error) {
if ((0, errnoCode_1.errnoCode)(error) === 'ENOENT') {
return { status: 'missing' };
}
log.debug(`Unreadable CLI handshake at ${filePath}`, error);
return { status: 'invalid' };
}
};
exports.readHandshakeResult = readHandshakeResult;
const readHandshake = (projectPath, channel = (0, installationChannel_1.installationChannel)()) => {
const result = (0, exports.readHandshakeResult)(projectPath, channel);
return result.status === 'found' ? result.handshake : null;
};
exports.readHandshake = readHandshake;
const removeHandshake = (projectPath, pid) => {
const existing = (0, exports.readHandshake)(projectPath);
if (!existing || existing.pid !== pid) {
return;
}
try {
(0, fs_1.unlinkSync)((0, exports.handshakeFilePath)(projectPath));
}
catch { }
};
exports.removeHandshake = removeHandshake;
const RuntimeOwnerSchema = zod_1.z.object({
pid: zod_1.z.number().int().positive(),
token: zod_1.z.string().uuid(),
phase: zod_1.z.enum(['active', 'starting', 'retiring']),
});
const fileOwnerCanWrite = (projectPath, channel) => {
try {
const [owner] = (0, fs_1.readFileSync)(lockFilePath(projectPath, channel), 'utf8').split('\n');
const pid = Number(owner);
return !Number.isInteger(pid) || pid <= 0 || (0, pid_1.isPidAlive)(pid);
}
catch (error) {
return (0, errnoCode_1.errnoCode)(error) !== 'ENOENT';
}
};
const withRuntimeOwnership = async (directory, operation) => {
const records = await ProjectRuntimeRecords_1.ProjectRuntimeRecords.open(directory);
try {
return records.transaction(() => operation(records));
}
finally {
records.close();
}
};
const isServerStartInProgress = async (projectPath, channel = (0, installationChannel_1.installationChannel)()) => {
if (fileOwnerCanWrite(projectPath, channel))
return true;
const directory = (0, exports.serverRuntimeDirectory)(projectPath, channel);
if (!(0, fs_1.existsSync)((0, path_1.join)(directory, 'lifecycle.sqlite')))
return false;
return withRuntimeOwnership(directory, (records) => {
const row = records.read();
return row !== null && (0, pid_1.isPidAlive)(RuntimeOwnerSchema.parse(row).pid);
});
};
exports.isServerStartInProgress = isServerStartInProgress;
const releaseReadyStartLock = async (projectPath, pid) => {
await withRuntimeOwnership((0, exports.serverRuntimeDirectory)(projectPath), (records) => {
const row = records.read();
if (row)
RuntimeOwnerSchema.parse(row);
records.releaseReady(pid);
});
};
exports.releaseReadyStartLock = releaseReadyStartLock;
const acquireStartLock = async (projectPath, channel = (0, installationChannel_1.installationChannel)()) => {
const directory = (0, exports.serverRuntimeDirectory)(projectPath, channel);
const token = await withRuntimeOwnership(directory, (records) => {
if (fileOwnerCanWrite(projectPath, channel))
return null;
const row = records.read();
if (row && (0, pid_1.isPidAlive)(RuntimeOwnerSchema.parse(row).pid))
return null;
const token = (0, crypto_1.randomUUID)();
records.assign({ pid: process.pid, token, phase: 'active' });
return token;
});
if (token === null)
return null;
return {
holdForProcess: async (pid, phase = 'starting') => {
const owner = RuntimeOwnerSchema.parse({ pid, phase, token });
await withRuntimeOwnership(directory, (records) => records.transfer(owner));
},
release: async () => {
await withRuntimeOwnership(directory, (records) => records.release(token));
},
};
};
exports.acquireStartLock = acquireStartLock;
//# sourceMappingURL=serverHandshake.js.mapSHA-256: 1465a968edca598d33bfd8c18a45485e7545bc1b646b2faa974c904fd2bd72e6