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
|
*.code-workspace
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
!.env.staging.example
|
||||||
.next/
|
.next/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
|
|||||||
+13
-4
@@ -5,13 +5,22 @@
|
|||||||
./deploy/down.ps1
|
./deploy/down.ps1
|
||||||
./deploy/down.ps1 -Volumes
|
./deploy/down.ps1 -Volumes
|
||||||
#>
|
#>
|
||||||
param([switch]$Volumes)
|
param([switch]$Volumes, [switch]$Staging)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$root = Split-Path -Parent $PSScriptRoot
|
$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' }
|
if ($Volumes) { $composeArgs += '--volumes' }
|
||||||
|
|
||||||
Push-Location $root
|
Push-Location $root
|
||||||
|
|||||||
+27
-10
@@ -8,15 +8,28 @@
|
|||||||
#>
|
#>
|
||||||
param(
|
param(
|
||||||
[switch]$Proxy,
|
[switch]$Proxy,
|
||||||
[switch]$Foreground
|
[switch]$Foreground,
|
||||||
|
[switch]$Staging # production-shaped staging stack: separate env, ports, volumes
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$root = Split-Path -Parent $PSScriptRoot # repo root (deploy/ is one level down)
|
$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.
|
||||||
throw "Missing $envFile. Create it from .env.example with your real secrets."
|
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.
|
# 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[$_]) }
|
$missing = $required | Where-Object { [string]::IsNullOrWhiteSpace($envMap[$_]) }
|
||||||
if ($missing) { throw "deploy/.env is missing values for: $($missing -join ', ')" }
|
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') }
|
if ($Proxy) { $composeArgs += @('--profile', 'proxy') }
|
||||||
$composeArgs += @('up', '--build')
|
$composeArgs += @('up', '--build')
|
||||||
if (-not $Foreground) { $composeArgs += '-d' }
|
if (-not $Foreground) { $composeArgs += '-d' }
|
||||||
|
|
||||||
Push-Location $root
|
Push-Location $root
|
||||||
try {
|
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
|
& docker @composeArgs
|
||||||
if (-not $Foreground) {
|
if (-not $Foreground) {
|
||||||
& docker compose --env-file $envFile ps
|
& docker compose @project --env-file $envFile @composeFiles ps
|
||||||
Write-Host "`nFrontend: http://localhost:8081 API/Swagger: http://localhost:8080/swagger" -ForegroundColor Green
|
if ($Staging) {
|
||||||
Write-Host "Logs: ./deploy/logs.ps1 Stop: ./deploy/down.ps1" -ForegroundColor DarkGray
|
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$(if($Staging){' -Staging'})" -ForegroundColor DarkGray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally { Pop-Location }
|
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