Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drizzle-pg/0004_add_build_on_deploy.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "git_stacks" ADD COLUMN "build_on_deploy" boolean DEFAULT true;
7 changes: 7 additions & 0 deletions drizzle-pg/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
"when": 1767687362730,
"tag": "0003_add_stack_paths",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1767900000000,
"tag": "0004_add_build_on_deploy",
"breakpoints": true
}
]
}
1 change: 1 addition & 0 deletions drizzle/0004_add_build_on_deploy.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE `git_stacks` ADD `build_on_deploy` integer DEFAULT 1;
7 changes: 7 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
"when": 1767689000000,
"tag": "0003_add_stack_paths",
"breakpoints": true
},
{
"idx": 4,
"version": "6",
"when": 1767900000000,
"tag": "0004_add_build_on_deploy",
"breakpoints": true
}
]
}
6 changes: 5 additions & 1 deletion src/lib/server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2049,6 +2049,7 @@ export interface GitStackData {
autoUpdateCron: string;
webhookEnabled: boolean;
webhookSecret: string | null;
buildOnDeploy: boolean;
lastSync: string | null;
lastCommit: string | null;
syncStatus: GitSyncStatus;
Expand Down Expand Up @@ -2402,6 +2403,7 @@ export async function createGitStack(data: {
autoUpdateCron?: string;
webhookEnabled?: boolean;
webhookSecret?: string | null;
buildOnDeploy?: boolean;
}): Promise<GitStackWithRepo> {
const result = await db.insert(gitStacks).values({
stackName: data.stackName,
Expand All @@ -2413,7 +2415,8 @@ export async function createGitStack(data: {
autoUpdateSchedule: data.autoUpdateSchedule || 'daily',
autoUpdateCron: data.autoUpdateCron || '0 3 * * *',
webhookEnabled: data.webhookEnabled || false,
webhookSecret: data.webhookSecret || null
webhookSecret: data.webhookSecret || null,
buildOnDeploy: data.buildOnDeploy ?? true
}).returning();
return getGitStack(result[0].id) as Promise<GitStackWithRepo>;
}
Expand All @@ -2430,6 +2433,7 @@ export async function updateGitStack(id: number, data: Partial<GitStackData>): P
if (data.autoUpdateCron !== undefined) updateData.autoUpdateCron = data.autoUpdateCron;
if (data.webhookEnabled !== undefined) updateData.webhookEnabled = data.webhookEnabled;
if (data.webhookSecret !== undefined) updateData.webhookSecret = data.webhookSecret;
if (data.buildOnDeploy !== undefined) updateData.buildOnDeploy = data.buildOnDeploy;
if (data.lastSync !== undefined) updateData.lastSync = data.lastSync;
if (data.lastCommit !== undefined) updateData.lastCommit = data.lastCommit;
if (data.syncStatus !== undefined) updateData.syncStatus = data.syncStatus;
Expand Down
1 change: 1 addition & 0 deletions src/lib/server/db/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ export const gitStacks = sqliteTable('git_stacks', {
autoUpdateCron: text('auto_update_cron').default('0 3 * * *'),
webhookEnabled: integer('webhook_enabled', { mode: 'boolean' }).default(false),
webhookSecret: text('webhook_secret'),
buildOnDeploy: integer('build_on_deploy', { mode: 'boolean' }).default(true),
lastSync: text('last_sync'),
lastCommit: text('last_commit'),
syncStatus: text('sync_status').default('pending'),
Expand Down
1 change: 1 addition & 0 deletions src/lib/server/db/schema/pg-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export const gitStacks = pgTable('git_stacks', {
autoUpdateCron: text('auto_update_cron').default('0 3 * * *'),
webhookEnabled: boolean('webhook_enabled').default(false),
webhookSecret: text('webhook_secret'),
buildOnDeploy: boolean('build_on_deploy').default(true),
lastSync: timestamp('last_sync', { mode: 'string' }),
lastCommit: text('last_commit'),
syncStatus: text('sync_status').default('pending'),
Expand Down
6 changes: 4 additions & 2 deletions src/lib/server/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,8 @@ export async function deployGitStack(stackId: number, options?: { force?: boolea
sourceDir: syncResult.composeDir, // Copy entire directory from git repo
composeFileName: syncResult.composeFileName, // Use original compose filename from repo
envFileName: syncResult.envFileName, // Env file relative to compose dir (for --env-file flag, optional)
forceRecreate
forceRecreate,
build: gitStack.buildOnDeploy !== false // default true
});

console.log(`${logPrefix} ----------------------------------------`);
Expand Down Expand Up @@ -1093,7 +1094,8 @@ export async function deployGitStackWithProgress(
name: gitStack.stackName,
compose: composeContent,
envId: gitStack.environmentId,
sourceDir: composeDir // Copy entire directory from git repo
sourceDir: composeDir, // Copy entire directory from git repo
build: gitStack.buildOnDeploy !== false // default true
});

if (result.success) {
Expand Down
28 changes: 20 additions & 8 deletions src/lib/server/stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface DeployStackOptions {
envId?: number | null;
sourceDir?: string; // Directory to copy all files from (for git stacks)
forceRecreate?: boolean;
build?: boolean; // Pass --build to docker compose up (for git stacks with build: directives)
composePath?: string; // Custom compose file path (for adopted/imported stacks)
envPath?: string; // Custom env file path (for adopted/imported stacks)
composeFileName?: string; // Compose filename to use (e.g., "docker-compose.yaml") for git stacks
Expand Down Expand Up @@ -742,6 +743,8 @@ interface ComposeCommandOptions {
envPath?: string;
/** When true, write non-secret envVars to .env.dockhand override file (git stacks only) */
useOverrideFile?: boolean;
/** When true, pass --build to docker compose up (for git stacks with build: directives) */
build?: boolean;
}

/**
Expand All @@ -767,7 +770,8 @@ async function executeLocalCompose(
workingDir?: string,
customComposePath?: string,
customEnvPath?: string,
useOverrideFile?: boolean
useOverrideFile?: boolean,
build?: boolean
): Promise<StackOperationResult> {
const logPrefix = `[Stack:${stackName}]`;

Expand Down Expand Up @@ -909,6 +913,7 @@ async function executeLocalCompose(
case 'up':
args.push('up', '-d', '--remove-orphans');
if (forceRecreate) args.push('--force-recreate');
if (build) args.push('--build');
break;
case 'down':
args.push('down');
Expand Down Expand Up @@ -1062,7 +1067,8 @@ async function executeComposeViaHawser(
secretVars?: Record<string, string>,
forceRecreate?: boolean,
removeVolumes?: boolean,
stackFiles?: Record<string, string>
stackFiles?: Record<string, string>,
build?: boolean
): Promise<StackOperationResult> {
const logPrefix = `[Stack:${stackName}]`;
// Import dockerFetch dynamically to avoid circular dependency
Expand Down Expand Up @@ -1132,6 +1138,7 @@ async function executeComposeViaHawser(
files, // Files including .env (secrets NOT in .env file)
forceRecreate: forceRecreate || false,
removeVolumes: removeVolumes || false,
build: build || false,
registries // Registry credentials for docker login
});

Expand Down Expand Up @@ -1198,7 +1205,7 @@ async function executeComposeCommand(
envVars?: Record<string, string>,
secretVars?: Record<string, string>
): Promise<StackOperationResult> {
const { stackName, envId, forceRecreate, removeVolumes, stackFiles, workingDir, composePath, envPath, useOverrideFile } = options;
const { stackName, envId, forceRecreate, removeVolumes, stackFiles, workingDir, composePath, envPath, useOverrideFile, build } = options;

// Get environment configuration
const env = envId ? await getEnvironment(envId) : null;
Expand All @@ -1219,7 +1226,8 @@ async function executeComposeCommand(
workingDir,
composePath,
envPath,
useOverrideFile
useOverrideFile,
build
);
}

Expand Down Expand Up @@ -1251,7 +1259,8 @@ async function executeComposeCommand(
secretVars,
forceRecreate,
removeVolumes,
stackFiles
stackFiles,
build
);
}

Expand Down Expand Up @@ -1281,7 +1290,8 @@ async function executeComposeCommand(
workingDir,
composePath,
envPath,
useOverrideFile
useOverrideFile,
build
);
}

Expand All @@ -1301,7 +1311,8 @@ async function executeComposeCommand(
workingDir,
composePath,
envPath,
useOverrideFile
useOverrideFile,
build
);
}
}
Expand Down Expand Up @@ -1886,7 +1897,7 @@ export async function removeStack(
* Uses stack locking to prevent concurrent deployments.
*/
export async function deployStack(options: DeployStackOptions): Promise<StackOperationResult> {
const { name, compose, envId, sourceDir, forceRecreate, composePath, envPath, composeFileName, envFileName } = options;
const { name, compose, envId, sourceDir, forceRecreate, build, composePath, envPath, composeFileName, envFileName } = options;
const logPrefix = `[Stack:${name}]`;

console.log(`${logPrefix} ========================================`);
Expand Down Expand Up @@ -2007,6 +2018,7 @@ export async function deployStack(options: DeployStackOptions): Promise<StackOpe
stackName: name,
envId,
forceRecreate,
build,
stackFiles,
workingDir,
composePath: actualComposePath,
Expand Down
3 changes: 2 additions & 1 deletion src/routes/api/git/stacks/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ export const POST: RequestHandler = async (event) => {
autoUpdateSchedule: data.autoUpdateSchedule || 'daily',
autoUpdateCron: data.autoUpdateCron || '0 3 * * *',
webhookEnabled: data.webhookEnabled || false,
webhookSecret: webhookSecret
webhookSecret: webhookSecret,
buildOnDeploy: data.buildOnDeploy
});

// Create stack_sources entry so the stack appears in the list immediately
Expand Down
3 changes: 2 additions & 1 deletion src/routes/api/git/stacks/[id]/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export const PUT: RequestHandler = async (event) => {
autoUpdateSchedule: data.autoUpdateSchedule,
autoUpdateCron: data.autoUpdateCron,
webhookEnabled: data.webhookEnabled,
webhookSecret: data.webhookSecret
webhookSecret: data.webhookSecret,
buildOnDeploy: data.buildOnDeploy
});

// If stack name changed, update related records
Expand Down
20 changes: 19 additions & 1 deletion src/routes/stacks/GitStackModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { Label } from '$lib/components/ui/label';
import { Input } from '$lib/components/ui/input';
import { TogglePill } from '$lib/components/ui/toggle-pill';
import { Loader2, GitBranch, RefreshCw, Webhook, Rocket, RefreshCcw, Copy, Check, FolderGit2, Github, Key, KeyRound, Lock, FileText, HelpCircle, GripVertical, X, Download } from 'lucide-svelte';
import { Loader2, GitBranch, RefreshCw, Webhook, Rocket, RefreshCcw, Copy, Check, FolderGit2, Github, Key, KeyRound, Lock, FileText, HelpCircle, GripVertical, X, Download, Hammer } from 'lucide-svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import CronEditor from '$lib/components/cron-editor.svelte';
import StackEnvVarsPanel from '$lib/components/StackEnvVarsPanel.svelte';
Expand Down Expand Up @@ -84,6 +84,7 @@
let formAutoUpdateCron = $state('0 3 * * *');
let formWebhookEnabled = $state(false);
let formWebhookSecret = $state('');
let formBuildOnDeploy = $state(true);
let formDeployNow = $state(false);
let formError = $state('');
let formSaving = $state(false);
Expand Down Expand Up @@ -345,6 +346,7 @@
formAutoUpdateCron = gitStack.autoUpdateCron || '0 3 * * *';
formWebhookEnabled = gitStack.webhookEnabled;
formWebhookSecret = gitStack.webhookSecret || '';
formBuildOnDeploy = gitStack.buildOnDeploy !== false;
formDeployNow = false;
// Load env files and overrides for editing (async - will populate envFiles, envVars, fileEnvVars)
loadEnvFiles();
Expand All @@ -367,6 +369,7 @@
formAutoUpdateCron = '0 3 * * *';
formWebhookEnabled = false;
formWebhookSecret = '';
formBuildOnDeploy = true;
formDeployNow = false;
}
}
Expand Down Expand Up @@ -423,6 +426,7 @@
autoUpdateCron: formAutoUpdateCron,
webhookEnabled: formWebhookEnabled,
webhookSecret: formWebhookEnabled ? formWebhookSecret : null,
buildOnDeploy: formBuildOnDeploy,
deployNow: deployAfterSave,
envVars: overrideVars.map(v => ({
key: v.key.trim(),
Expand Down Expand Up @@ -852,6 +856,20 @@
{/if}
</div>

<!-- Build on deploy option -->
<div class="space-y-3 p-3 bg-muted/50 rounded-md">
<div class="flex items-center gap-3">
<div class="flex items-center gap-2 flex-1">
<Hammer class="w-4 h-4 text-muted-foreground" />
<Label class="text-sm font-normal">Build images on deploy</Label>
</div>
<TogglePill bind:checked={formBuildOnDeploy} />
</div>
<p class="text-xs text-muted-foreground">
Rebuild Docker images when deploying. Required for stacks that use <code>build:</code> in their compose file.
</p>
</div>

<!-- Deploy now option (only for new stacks) -->
{#if !gitStack}
<div class="space-y-3 p-3 bg-muted/50 rounded-md">
Expand Down
Loading