diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 5a43297..503480b 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -707,9 +707,19 @@ func (d *Deployer) computeVolumeMounts(projectID, stageName, imageTag string) [] return nil } + // Get base volume path from settings. + basePath := "" + if settings, err := d.store.GetSettings(); err == nil { + basePath = settings.BaseVolumePath + } + mounts := make([]mount.Mount, 0, len(vols)) for _, vol := range vols { source := vol.Source + // Prepend base path if source is relative (doesn't start with /). + if basePath != "" && !filepath.IsAbs(source) { + source = filepath.Join(basePath, source) + } if vol.Mode == "isolated" { source = filepath.Join(source, fmt.Sprintf("%s-%s", stageName, imageTag)) } diff --git a/internal/store/models.go b/internal/store/models.go index a136115..2c48e50 100644 --- a/internal/store/models.go +++ b/internal/store/models.go @@ -53,6 +53,7 @@ type Settings struct { NpmPassword string `json:"npm_password"` WebhookSecret string `json:"webhook_secret"` PollingInterval string `json:"polling_interval"` + BaseVolumePath string `json:"base_volume_path"` UpdatedAt string `json:"updated_at"` } diff --git a/internal/store/settings.go b/internal/store/settings.go index 9ce9364..90b5354 100644 --- a/internal/store/settings.go +++ b/internal/store/settings.go @@ -9,10 +9,10 @@ func (s *Store) GetSettings() (Settings, error) { var st Settings err := s.db.QueryRow( `SELECT domain, server_ip, network, subdomain_pattern, notification_url, - npm_url, npm_email, npm_password, webhook_secret, polling_interval, updated_at + npm_url, npm_email, npm_password, webhook_secret, polling_interval, base_volume_path, updated_at FROM settings WHERE id = 1`, ).Scan(&st.Domain, &st.ServerIP, &st.Network, &st.SubdomainPattern, &st.NotificationURL, - &st.NpmURL, &st.NpmEmail, &st.NpmPassword, &st.WebhookSecret, &st.PollingInterval, &st.UpdatedAt) + &st.NpmURL, &st.NpmEmail, &st.NpmPassword, &st.WebhookSecret, &st.PollingInterval, &st.BaseVolumePath, &st.UpdatedAt) if err != nil { return Settings{}, fmt.Errorf("query settings: %w", err) } @@ -25,10 +25,10 @@ func (s *Store) UpdateSettings(st Settings) error { _, err := s.db.Exec( `UPDATE settings SET domain=?, server_ip=?, network=?, subdomain_pattern=?, notification_url=?, - npm_url=?, npm_email=?, npm_password=?, webhook_secret=?, polling_interval=?, updated_at=? + npm_url=?, npm_email=?, npm_password=?, webhook_secret=?, polling_interval=?, base_volume_path=?, updated_at=? WHERE id = 1`, st.Domain, st.ServerIP, st.Network, st.SubdomainPattern, st.NotificationURL, - st.NpmURL, st.NpmEmail, st.NpmPassword, st.WebhookSecret, st.PollingInterval, st.UpdatedAt, + st.NpmURL, st.NpmEmail, st.NpmPassword, st.WebhookSecret, st.PollingInterval, st.BaseVolumePath, st.UpdatedAt, ) if err != nil { return fmt.Errorf("update settings: %w", err) diff --git a/internal/store/store.go b/internal/store/store.go index 0d5fa43..8c5ca3a 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -71,6 +71,8 @@ func (s *Store) runMigrations() error { migrations := []string{ // Add owner column to registries (2026-03-28). `ALTER TABLE registries ADD COLUMN owner TEXT NOT NULL DEFAULT ''`, + // Add base_volume_path to settings (2026-03-28). + `ALTER TABLE settings ADD COLUMN base_volume_path TEXT NOT NULL DEFAULT ''`, } for _, m := range migrations { @@ -131,6 +133,7 @@ CREATE TABLE IF NOT EXISTS settings ( npm_password TEXT NOT NULL DEFAULT '', webhook_secret TEXT NOT NULL DEFAULT '', polling_interval TEXT NOT NULL DEFAULT '5m', + base_volume_path TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 0f156e5..9cc7f2a 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -103,6 +103,7 @@ export interface Settings { npm_password: string; webhook_secret: string; polling_interval: string; + base_volume_path: string; updated_at: string; } diff --git a/web/src/routes/settings/+page.svelte b/web/src/routes/settings/+page.svelte index f7e90e2..7ca56b8 100644 --- a/web/src/routes/settings/+page.svelte +++ b/web/src/routes/settings/+page.svelte @@ -17,6 +17,7 @@ let network = $state(''); let subdomainPattern = $state(''); let pollingInterval = $state(''); + let baseVolumePath = $state(''); let notificationUrl = $state(''); let errors = $state>({}); @@ -68,6 +69,7 @@ network = settings.network ?? ''; subdomainPattern = settings.subdomain_pattern ?? ''; pollingInterval = settings.polling_interval ?? ''; + baseVolumePath = settings.base_volume_path ?? ''; notificationUrl = settings.notification_url ?? ''; } catch (err) { toasts.error(err instanceof Error ? err.message : $t('settingsGeneral.loadFailed')); @@ -90,7 +92,7 @@ await updateSettings({ domain: domain.trim(), server_ip: serverIp.trim(), network: network.trim(), subdomain_pattern: subdomainPattern.trim(), polling_interval: pollingInterval.trim(), - notification_url: notificationUrl.trim() + base_volume_path: baseVolumePath.trim(), notification_url: notificationUrl.trim() }); toasts.success($t('settingsGeneral.saved')); } catch (err) { @@ -139,6 +141,7 @@ +