feat: Enable Git operations within a specified deployment directory by adding cwd options and configuring Git to trust the deploy directory.
This commit is contained in:
@@ -39,7 +39,8 @@ RUN apt-get update && apt-get install -y \
|
|||||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bookworm stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bookworm stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y docker-ce-cli \
|
&& apt-get install -y docker-ce-cli \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& git config --system --add safe.directory /app/deploy
|
||||||
|
|
||||||
# Copy only what's needed for production
|
# Copy only what's needed for production
|
||||||
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
|
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ const BUILD_TIMEOUT_MS = 180_000; // 3 minutes for web build
|
|||||||
*/
|
*/
|
||||||
async function execWithTimeout(
|
async function execWithTimeout(
|
||||||
cmd: string,
|
cmd: string,
|
||||||
timeoutMs: number = DEFAULT_TIMEOUT_MS
|
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
||||||
|
options: { cwd?: string } = {}
|
||||||
): Promise<{ stdout: string; stderr: string }> {
|
): Promise<{ stdout: string; stderr: string }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const process = exec(cmd, (error: ExecException | null, stdout: string, stderr: string) => {
|
const process = exec(cmd, { cwd: options.cwd }, (error: ExecException | null, stdout: string, stderr: string) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
@@ -51,22 +52,24 @@ export class UpdateService {
|
|||||||
* Optimized: Parallel git commands and combined requirements check
|
* Optimized: Parallel git commands and combined requirements check
|
||||||
*/
|
*/
|
||||||
static async checkForUpdates(): Promise<UpdateInfo & { requirements: UpdateCheckResult }> {
|
static async checkForUpdates(): Promise<UpdateInfo & { requirements: UpdateCheckResult }> {
|
||||||
|
const cwd = this.getDeployDir();
|
||||||
|
|
||||||
// Get branch first (needed for subsequent commands)
|
// Get branch first (needed for subsequent commands)
|
||||||
const { stdout: branchName } = await execWithTimeout("git rev-parse --abbrev-ref HEAD");
|
const { stdout: branchName } = await execWithTimeout("git rev-parse --abbrev-ref HEAD", DEFAULT_TIMEOUT_MS, { cwd });
|
||||||
const branch = branchName.trim();
|
const branch = branchName.trim();
|
||||||
|
|
||||||
// Parallel execution: get current commit while fetching
|
// Parallel execution: get current commit while fetching
|
||||||
const [currentResult] = await Promise.all([
|
const [currentResult] = await Promise.all([
|
||||||
execWithTimeout("git rev-parse --short HEAD"),
|
execWithTimeout("git rev-parse --short HEAD", DEFAULT_TIMEOUT_MS, { cwd }),
|
||||||
execWithTimeout(`git fetch origin ${branch} --prune`) // Only fetch current branch
|
execWithTimeout(`git fetch origin ${branch} --prune`, DEFAULT_TIMEOUT_MS, { cwd }) // Only fetch current branch
|
||||||
]);
|
]);
|
||||||
const currentCommit = currentResult.stdout.trim();
|
const currentCommit = currentResult.stdout.trim();
|
||||||
|
|
||||||
// After fetch completes, get remote info in parallel
|
// After fetch completes, get remote info in parallel
|
||||||
const [latestResult, logResult, diffResult] = await Promise.all([
|
const [latestResult, logResult, diffResult] = await Promise.all([
|
||||||
execWithTimeout(`git rev-parse --short origin/${branch}`),
|
execWithTimeout(`git rev-parse --short origin/${branch}`, DEFAULT_TIMEOUT_MS, { cwd }),
|
||||||
execWithTimeout(`git log HEAD..origin/${branch} --format="%h|%s|%an" --no-merges`),
|
execWithTimeout(`git log HEAD..origin/${branch} --format="%h|%s|%an" --no-merges`, DEFAULT_TIMEOUT_MS, { cwd }),
|
||||||
execWithTimeout(`git diff HEAD..origin/${branch} --name-only`)
|
execWithTimeout(`git diff HEAD..origin/${branch} --name-only`, DEFAULT_TIMEOUT_MS, { cwd })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const latestCommit = latestResult.stdout.trim();
|
const latestCommit = latestResult.stdout.trim();
|
||||||
@@ -135,8 +138,9 @@ export class UpdateService {
|
|||||||
* Kept for backwards compatibility
|
* Kept for backwards compatibility
|
||||||
*/
|
*/
|
||||||
static async checkUpdateRequirements(branch: string): Promise<UpdateCheckResult> {
|
static async checkUpdateRequirements(branch: string): Promise<UpdateCheckResult> {
|
||||||
|
const cwd = this.getDeployDir();
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execWithTimeout(`git diff HEAD..origin/${branch} --name-only`);
|
const { stdout } = await execWithTimeout(`git diff HEAD..origin/${branch} --name-only`, DEFAULT_TIMEOUT_MS, { cwd });
|
||||||
const changedFiles = stdout.trim().split("\n").filter(f => f.length > 0);
|
const changedFiles = stdout.trim().split("\n").filter(f => f.length > 0);
|
||||||
return this.analyzeChangedFiles(changedFiles);
|
return this.analyzeChangedFiles(changedFiles);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -179,7 +183,8 @@ export class UpdateService {
|
|||||||
* Save the current commit for potential rollback
|
* Save the current commit for potential rollback
|
||||||
*/
|
*/
|
||||||
static async saveRollbackPoint(): Promise<string> {
|
static async saveRollbackPoint(): Promise<string> {
|
||||||
const { stdout } = await execWithTimeout("git rev-parse HEAD");
|
const cwd = this.getDeployDir();
|
||||||
|
const { stdout } = await execWithTimeout("git rev-parse HEAD", DEFAULT_TIMEOUT_MS, { cwd });
|
||||||
const commit = stdout.trim();
|
const commit = stdout.trim();
|
||||||
await writeFile(this.ROLLBACK_FILE, commit);
|
await writeFile(this.ROLLBACK_FILE, commit);
|
||||||
this.rollbackPointExists = true; // Cache the state
|
this.rollbackPointExists = true; // Cache the state
|
||||||
@@ -190,9 +195,10 @@ export class UpdateService {
|
|||||||
* Rollback to the previous commit
|
* Rollback to the previous commit
|
||||||
*/
|
*/
|
||||||
static async rollback(): Promise<{ success: boolean; message: string }> {
|
static async rollback(): Promise<{ success: boolean; message: string }> {
|
||||||
|
const cwd = this.getDeployDir();
|
||||||
try {
|
try {
|
||||||
const rollbackCommit = await readFile(this.ROLLBACK_FILE, "utf-8");
|
const rollbackCommit = await readFile(this.ROLLBACK_FILE, "utf-8");
|
||||||
await execWithTimeout(`git reset --hard ${rollbackCommit.trim()}`);
|
await execWithTimeout(`git reset --hard ${rollbackCommit.trim()}`, DEFAULT_TIMEOUT_MS, { cwd });
|
||||||
await unlink(this.ROLLBACK_FILE);
|
await unlink(this.ROLLBACK_FILE);
|
||||||
this.rollbackPointExists = false;
|
this.rollbackPointExists = false;
|
||||||
return { success: true, message: `Rolled back to ${rollbackCommit.trim().substring(0, 7)}` };
|
return { success: true, message: `Rolled back to ${rollbackCommit.trim().substring(0, 7)}` };
|
||||||
@@ -226,7 +232,8 @@ export class UpdateService {
|
|||||||
* Perform the git update
|
* Perform the git update
|
||||||
*/
|
*/
|
||||||
static async performUpdate(branch: string): Promise<void> {
|
static async performUpdate(branch: string): Promise<void> {
|
||||||
await execWithTimeout(`git reset --hard origin/${branch}`);
|
const cwd = this.getDeployDir();
|
||||||
|
await execWithTimeout(`git reset --hard origin/${branch}`, DEFAULT_TIMEOUT_MS, { cwd });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,6 +243,11 @@ export class UpdateService {
|
|||||||
static async installDependencies(options: { root: boolean; web: boolean }): Promise<string> {
|
static async installDependencies(options: { root: boolean; web: boolean }): Promise<string> {
|
||||||
const tasks: Promise<{ label: string; output: string }>[] = [];
|
const tasks: Promise<{ label: string; output: string }>[] = [];
|
||||||
|
|
||||||
|
// Install dependencies in the App directory (not deploy dir) because we are updating the RUNNING app
|
||||||
|
// NOTE: If we hot-reload, we want to install in the current directory.
|
||||||
|
// If we restart, dependencies should be in the image.
|
||||||
|
// Assuming this method is used for hot-patching/minor updates without container rebuild.
|
||||||
|
|
||||||
if (options.root) {
|
if (options.root) {
|
||||||
tasks.push(
|
tasks.push(
|
||||||
execWithTimeout("bun install", INSTALL_TIMEOUT_MS)
|
execWithTimeout("bun install", INSTALL_TIMEOUT_MS)
|
||||||
|
|||||||
Reference in New Issue
Block a user