feat: Store update restart context in the deployment directory and configure Docker to use the default bun user.
Some checks failed
Deploy to Production / test (push) Failing after 24s
Deploy to Production / build (push) Has been skipped
Deploy to Production / deploy (push) Has been skipped

This commit is contained in:
syntaxbullet
2026-01-30 15:06:32 +01:00
parent 35ecea16f7
commit 422db6479b
3 changed files with 31 additions and 15 deletions

View File

@@ -27,8 +27,8 @@ RUN cd web && bun run build
FROM oven/bun:latest AS production
WORKDIR /app
# Create non-root user for security
RUN groupadd --system appgroup && useradd --system --gid appgroup appuser
# Create non-root user for security (bun user already exists with 1000:1000)
# No need to create user/group
# Install runtime dependencies for update/deploy commands
RUN apt-get update && apt-get install -y \
@@ -43,18 +43,18 @@ RUN apt-get update && apt-get install -y \
&& git config --system --add safe.directory /app/deploy
# 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/web/node_modules ./web/node_modules
COPY --from=builder --chown=appuser:appgroup /app/web/dist ./web/dist
COPY --from=builder --chown=appuser:appgroup /app/web/src ./web/src
COPY --from=builder --chown=appuser:appgroup /app/bot ./bot
COPY --from=builder --chown=appuser:appgroup /app/shared ./shared
COPY --from=builder --chown=appuser:appgroup /app/package.json .
COPY --from=builder --chown=appuser:appgroup /app/drizzle.config.ts .
COPY --from=builder --chown=appuser:appgroup /app/tsconfig.json .
COPY --from=builder --chown=bun:bun /app/node_modules ./node_modules
COPY --from=builder --chown=bun:bun /app/web/node_modules ./web/node_modules
COPY --from=builder --chown=bun:bun /app/web/dist ./web/dist
COPY --from=builder --chown=bun:bun /app/web/src ./web/src
COPY --from=builder --chown=bun:bun /app/bot ./bot
COPY --from=builder --chown=bun:bun /app/shared ./shared
COPY --from=builder --chown=bun:bun /app/package.json .
COPY --from=builder --chown=bun:bun /app/drizzle.config.ts .
COPY --from=builder --chown=bun:bun /app/tsconfig.json .
# Switch to non-root user
USER appuser
USER bun
# Expose web dashboard port
EXPOSE 3000

View File

@@ -213,6 +213,18 @@ async function handleDeploy(interaction: any) {
});
if (result.success) {
// Save restart context so we can notify on startup
await UpdateService.prepareRestartContext({
channelId: interaction.channelId,
userId: interaction.user.id,
timestamp: Date.now(),
runMigrations: true, // Always check migrations on deploy
installDependencies: false, // Handled by Docker build
buildWebAssets: false, // Handled by Docker build
previousCommit: result.previousCommit,
newCommit: result.newCommit
});
await interaction.editReply({
embeds: [getDeploySuccessEmbed(
result.previousCommit,

View File

@@ -1,6 +1,7 @@
import { exec, type ExecException } from "child_process";
import { promisify } from "util";
import { writeFile, readFile, unlink } from "fs/promises";
import * as path from "path";
import { Client, TextChannel } from "discord.js";
import { getPostRestartEmbed, getPostRestartProgressEmbed, type PostRestartProgress } from "@/modules/admin/update.view";
import type { PostRestartResult } from "@/modules/admin/update.view";
@@ -270,7 +271,8 @@ export class UpdateService {
* Prepare restart context with rollback info
*/
static async prepareRestartContext(context: RestartContext): Promise<void> {
await writeFile(this.CONTEXT_FILE, JSON.stringify(context));
const filePath = path.join(this.getDeployDir(), this.CONTEXT_FILE);
await writeFile(filePath, JSON.stringify(context));
}
/**
@@ -325,7 +327,8 @@ export class UpdateService {
private static async loadRestartContext(): Promise<RestartContext | null> {
try {
const contextData = await readFile(this.CONTEXT_FILE, "utf-8");
const filePath = path.join(this.getDeployDir(), this.CONTEXT_FILE);
const contextData = await readFile(filePath, "utf-8");
return JSON.parse(contextData) as RestartContext;
} catch {
return null;
@@ -475,7 +478,8 @@ export class UpdateService {
private static async cleanupContext(): Promise<void> {
try {
await unlink(this.CONTEXT_FILE);
const filePath = path.join(this.getDeployDir(), this.CONTEXT_FILE);
await unlink(filePath);
} catch {
// File may not exist, ignore
}