Git workflow, environments & CI/CD pipeline #1
@@ -0,0 +1,17 @@
|
||||
# Copy to deploy/.env.staging and fill in. Used by:
|
||||
# docker compose -p inboxintel-staging --env-file deploy/.env.staging \
|
||||
# -f docker-compose.yml -f docker-compose.staging.yml up
|
||||
# (or ./deploy/up.ps1 -Staging). Kept separate from deploy/.env (production) so
|
||||
# staging can never touch prod credentials, DB, or Google project.
|
||||
POSTGRES_PASSWORD=change-me-staging
|
||||
|
||||
# Use a SEPARATE Google OAuth client for staging with redirect URI:
|
||||
# http://localhost:18081/signin-google
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
|
||||
AI_MODE=Disabled
|
||||
FRONTEND_ORIGIN=http://localhost:18081
|
||||
|
||||
# Staging caps the initial mailbox sync so rehearsals are fast.
|
||||
MAX_MESSAGES=2000
|
||||
@@ -54,6 +54,7 @@ lpt[1-9].*
|
||||
*.code-workspace
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.staging.example
|
||||
.next/
|
||||
dist/
|
||||
build/
|
||||
|
||||
+12
-3
@@ -5,13 +5,22 @@
|
||||
./deploy/down.ps1
|
||||
./deploy/down.ps1 -Volumes
|
||||
#>
|
||||
param([switch]$Volumes)
|
||||
param([switch]$Volumes, [switch]$Staging)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
|
||||
$composeArgs = @('compose', '--env-file', $envFile, 'down')
|
||||
if ($Staging) {
|
||||
$envFile = Join-Path $PSScriptRoot '.env.staging'
|
||||
$composeFiles = @('-f', 'docker-compose.yml', '-f', 'docker-compose.staging.yml')
|
||||
$project = @('-p', 'inboxintel-staging')
|
||||
} else {
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
$composeFiles = @()
|
||||
$project = @()
|
||||
}
|
||||
|
||||
$composeArgs = @('compose') + $project + @('--env-file', $envFile) + $composeFiles + @('down')
|
||||
if ($Volumes) { $composeArgs += '--volumes' }
|
||||
|
||||
Push-Location $root
|
||||
|
||||
+24
-7
@@ -8,15 +8,28 @@
|
||||
#>
|
||||
param(
|
||||
[switch]$Proxy,
|
||||
[switch]$Foreground
|
||||
[switch]$Foreground,
|
||||
[switch]$Staging # production-shaped staging stack: separate env, ports, volumes
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot # repo root (deploy/ is one level down)
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
|
||||
if (-not (Test-Path $envFile)) {
|
||||
# Staging vs production: pick the env file + compose overlay + isolated project name.
|
||||
if ($Staging) {
|
||||
$envFile = Join-Path $PSScriptRoot '.env.staging'
|
||||
$composeFiles = @('-f', 'docker-compose.yml', '-f', 'docker-compose.staging.yml')
|
||||
$project = @('-p', 'inboxintel-staging')
|
||||
if (-not (Test-Path $envFile)) {
|
||||
throw "Missing $envFile. Create it from .env.staging.example."
|
||||
}
|
||||
} else {
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
$composeFiles = @()
|
||||
$project = @()
|
||||
if (-not (Test-Path $envFile)) {
|
||||
throw "Missing $envFile. Create it from .env.example with your real secrets."
|
||||
}
|
||||
}
|
||||
|
||||
# Fail fast if a required key is absent or blank.
|
||||
@@ -28,19 +41,23 @@ Get-Content $envFile | ForEach-Object {
|
||||
$missing = $required | Where-Object { [string]::IsNullOrWhiteSpace($envMap[$_]) }
|
||||
if ($missing) { throw "deploy/.env is missing values for: $($missing -join ', ')" }
|
||||
|
||||
$composeArgs = @('compose', '--env-file', $envFile)
|
||||
$composeArgs = @('compose') + $project + @('--env-file', $envFile) + $composeFiles
|
||||
if ($Proxy) { $composeArgs += @('--profile', 'proxy') }
|
||||
$composeArgs += @('up', '--build')
|
||||
if (-not $Foreground) { $composeArgs += '-d' }
|
||||
|
||||
Push-Location $root
|
||||
try {
|
||||
Write-Host "Starting InboxIntel via docker compose (env: deploy/.env)..." -ForegroundColor Cyan
|
||||
Write-Host "Starting InboxIntel via docker compose (env: $envFile)..." -ForegroundColor Cyan
|
||||
& docker @composeArgs
|
||||
if (-not $Foreground) {
|
||||
& docker compose --env-file $envFile ps
|
||||
& docker compose @project --env-file $envFile @composeFiles ps
|
||||
if ($Staging) {
|
||||
Write-Host "`n[STAGING] Frontend: http://localhost:18081 API/Swagger: http://localhost:18080/swagger" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`nFrontend: http://localhost:8081 API/Swagger: http://localhost:8080/swagger" -ForegroundColor Green
|
||||
Write-Host "Logs: ./deploy/logs.ps1 Stop: ./deploy/down.ps1" -ForegroundColor DarkGray
|
||||
}
|
||||
Write-Host "Logs: ./deploy/logs.ps1 Stop: ./deploy/down.ps1$(if($Staging){' -Staging'})" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
finally { Pop-Location }
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Staging overlay for InboxIntel.
|
||||
#
|
||||
# The base docker-compose.yml IS the production definition (Linux containers,
|
||||
# ASPNETCORE_ENVIRONMENT=Production). This overlay layers a *staging* variant on
|
||||
# top of it so you can run a production-shaped stack locally on Windows WITHOUT
|
||||
# clobbering a real production deployment's data, ports, or volumes.
|
||||
#
|
||||
# It differs from prod only in the ways staging is meant to differ:
|
||||
# - the dev/test banner is on (App__DevMode=true)
|
||||
# - the initial Gmail sync is capped so a big mailbox doesn't take forever
|
||||
# - ports are shifted into the 18xxx range so staging can run alongside prod
|
||||
# - a distinct project name gives it its own isolated pgdata + keys volumes
|
||||
#
|
||||
# Run it with the -p (project name) flag so volumes/networks are namespaced:
|
||||
#
|
||||
# docker compose -p inboxintel-staging \
|
||||
# --env-file deploy/.env.staging \
|
||||
# -f docker-compose.yml -f docker-compose.staging.yml up --build -d
|
||||
#
|
||||
# (deploy/up.ps1 -Staging / up.sh --staging wrap this for you.)
|
||||
|
||||
services:
|
||||
postgres:
|
||||
ports:
|
||||
- "127.0.0.1:15432:5432"
|
||||
|
||||
api:
|
||||
environment:
|
||||
# Same Production runtime as prod (real config binding, real build), but
|
||||
# flagged as a non-production instance so the UI shows the staging banner
|
||||
# and the first sync is bounded.
|
||||
App__DevMode: "true"
|
||||
GmailSync__MaxMessages: ${MAX_MESSAGES:-2000}
|
||||
Cors__Origins__0: ${FRONTEND_ORIGIN:-http://localhost:18081}
|
||||
ports:
|
||||
- "127.0.0.1:18080:8080"
|
||||
|
||||
frontend:
|
||||
ports:
|
||||
- "18081:80"
|
||||
|
||||
nginx:
|
||||
ports:
|
||||
- "18000:80"
|
||||
Reference in New Issue
Block a user