Remove Azure deployment stack; Proxmox 7810 is canonical production.
Delete Static Web Apps workflow, Bicep infra, Azure Functions api/, SWA configs, and Azure-only scripts; document Proxmox deploy path and refresh QuickStart and prerequisites. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,202 +0,0 @@
|
||||
# Production Deployment Script for Miracles in Motion
|
||||
# This script deploys the application to Azure with production SKUs and custom domain support
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$ResourceGroupName = "rg-miraclesinmotion-prod",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Location = "East US",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$CustomDomain = "mim4u.org",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$StripePublicKey = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$SkipBuild = $false
|
||||
)
|
||||
|
||||
Write-Host "🚀 Starting Production Deployment for Miracles in Motion" -ForegroundColor Green
|
||||
Write-Host "=================================================" -ForegroundColor Green
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if (!(Get-Command "az" -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "Azure CLI is not installed. Please install it first: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if Static Web Apps CLI is installed
|
||||
if (!(Get-Command "swa" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "📦 Installing Azure Static Web Apps CLI..." -ForegroundColor Yellow
|
||||
npm install -g @azure/static-web-apps-cli
|
||||
}
|
||||
|
||||
# Login to Azure if not already logged in
|
||||
$currentAccount = az account show --query "user.name" -o tsv 2>$null
|
||||
if (!$currentAccount) {
|
||||
Write-Host "🔐 Please log in to Azure..." -ForegroundColor Yellow
|
||||
az login
|
||||
}
|
||||
|
||||
Write-Host "✅ Logged in as: $currentAccount" -ForegroundColor Green
|
||||
|
||||
# Create resource group if it doesn't exist
|
||||
Write-Host "📁 Creating resource group: $ResourceGroupName" -ForegroundColor Yellow
|
||||
az group create --name $ResourceGroupName --location $Location
|
||||
|
||||
# Validate Stripe key
|
||||
if ([string]::IsNullOrEmpty($StripePublicKey)) {
|
||||
$StripePublicKey = Read-Host "Enter your Stripe Public Key (pk_live_...)"
|
||||
}
|
||||
|
||||
if (!$StripePublicKey.StartsWith("pk_live_")) {
|
||||
Write-Warning "Warning: Using non-production Stripe key. For production, use pk_live_..."
|
||||
}
|
||||
|
||||
# Build and test the application
|
||||
if (!$SkipBuild) {
|
||||
Write-Host "🔨 Building the application..." -ForegroundColor Yellow
|
||||
|
||||
# Install dependencies
|
||||
Write-Host "📦 Installing main project dependencies..." -ForegroundColor Cyan
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
# Install API dependencies
|
||||
Write-Host "📦 Installing API dependencies..." -ForegroundColor Cyan
|
||||
Set-Location api
|
||||
npm install
|
||||
Set-Location ..
|
||||
|
||||
# Run tests
|
||||
Write-Host "🧪 Running tests..." -ForegroundColor Cyan
|
||||
npx vitest run --reporter=verbose
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "Some tests failed, but continuing with deployment..."
|
||||
}
|
||||
|
||||
# Build the application
|
||||
Write-Host "🏗️ Building production bundle..." -ForegroundColor Cyan
|
||||
npm run build
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Build failed! Please fix the errors and try again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Build completed successfully" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Deploy infrastructure
|
||||
Write-Host "🏗️ Deploying Azure infrastructure..." -ForegroundColor Yellow
|
||||
|
||||
$deploymentName = "mim-prod-deployment-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||
|
||||
$deploymentResult = az deployment group create `
|
||||
--resource-group $ResourceGroupName `
|
||||
--template-file "infrastructure/main-production.bicep" `
|
||||
--parameters "infrastructure/main-production.parameters.json" `
|
||||
--parameters stripePublicKey=$StripePublicKey `
|
||||
--parameters customDomainName=$CustomDomain `
|
||||
--parameters enableCustomDomain=$true `
|
||||
--name $deploymentName `
|
||||
--output json | ConvertFrom-Json
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Infrastructure deployment failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Infrastructure deployed successfully" -ForegroundColor Green
|
||||
|
||||
# Get deployment outputs
|
||||
$staticWebAppName = $deploymentResult.properties.outputs.staticWebAppName.value
|
||||
$functionAppName = $deploymentResult.properties.outputs.functionAppName.value
|
||||
$staticWebAppUrl = $deploymentResult.properties.outputs.staticWebAppUrl.value
|
||||
|
||||
Write-Host "📋 Deployment Details:" -ForegroundColor Cyan
|
||||
Write-Host " Static Web App: $staticWebAppName" -ForegroundColor White
|
||||
Write-Host " Function App: $functionAppName" -ForegroundColor White
|
||||
Write-Host " Primary URL: $staticWebAppUrl" -ForegroundColor White
|
||||
if ($CustomDomain) {
|
||||
Write-Host " Custom Domain: https://$CustomDomain" -ForegroundColor White
|
||||
}
|
||||
|
||||
# Get deployment token for Static Web App
|
||||
Write-Host "🔑 Getting deployment token..." -ForegroundColor Yellow
|
||||
$deploymentToken = az staticwebapp secrets list --name $staticWebAppName --resource-group $ResourceGroupName --query "properties.apiKey" -o tsv
|
||||
|
||||
if ([string]::IsNullOrEmpty($deploymentToken)) {
|
||||
Write-Error "Failed to get deployment token!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Deploy to Static Web App
|
||||
Write-Host "🚀 Deploying to Static Web App..." -ForegroundColor Yellow
|
||||
|
||||
$env:SWA_CLI_DEPLOYMENT_TOKEN = $deploymentToken
|
||||
|
||||
# Deploy using SWA CLI
|
||||
swa deploy ./dist --api-location ./api --env production --deployment-token $deploymentToken
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Static Web App deployment failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Application deployed successfully!" -ForegroundColor Green
|
||||
|
||||
# Deploy Function App
|
||||
Write-Host "🔧 Deploying Azure Functions..." -ForegroundColor Yellow
|
||||
|
||||
# Build API project
|
||||
Set-Location api
|
||||
npm run build 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Building API project..." -ForegroundColor Cyan
|
||||
npm run tsc 2>$null
|
||||
}
|
||||
Set-Location ..
|
||||
|
||||
# Deploy functions
|
||||
az functionapp deployment source config-zip --resource-group $ResourceGroupName --name $functionAppName --src "./api.zip" 2>$null
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✅ Azure Functions deployed successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning "Function deployment may have issues, but Static Web App is deployed"
|
||||
}
|
||||
|
||||
# Custom Domain Setup Instructions
|
||||
if ($CustomDomain) {
|
||||
Write-Host "🌐 Custom Domain Setup:" -ForegroundColor Magenta
|
||||
Write-Host "================================" -ForegroundColor Magenta
|
||||
Write-Host "1. Add a CNAME record in your DNS:" -ForegroundColor Yellow
|
||||
Write-Host " Name: www (or @)" -ForegroundColor White
|
||||
Write-Host " Value: $($staticWebAppUrl -replace 'https://', '')" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "2. Wait for DNS propagation (up to 48 hours)" -ForegroundColor Yellow
|
||||
Write-Host "3. The SSL certificate will be automatically provisioned" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Final Summary
|
||||
Write-Host "🎉 DEPLOYMENT COMPLETE!" -ForegroundColor Green
|
||||
Write-Host "========================" -ForegroundColor Green
|
||||
Write-Host "🌐 Primary URL: $staticWebAppUrl" -ForegroundColor Cyan
|
||||
if ($CustomDomain) {
|
||||
Write-Host "🌐 Custom Domain: https://$CustomDomain (after DNS setup)" -ForegroundColor Cyan
|
||||
}
|
||||
Write-Host "🔗 Portal Access: $staticWebAppUrl#/portals" -ForegroundColor Cyan
|
||||
Write-Host "📊 Analytics: $staticWebAppUrl#/analytics" -ForegroundColor Cyan
|
||||
Write-Host "🤖 AI Portal: $staticWebAppUrl#/ai-portal" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "📚 Next Steps:" -ForegroundColor Yellow
|
||||
Write-Host "1. Set up DNS records for custom domain" -ForegroundColor White
|
||||
Write-Host "2. Configure authentication providers if needed" -ForegroundColor White
|
||||
Write-Host "3. Set up monitoring and alerts" -ForegroundColor White
|
||||
Write-Host "4. Update Stripe webhook endpoints" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "✨ Your Miracles in Motion application is now live in production!" -ForegroundColor Green
|
||||
@@ -1,62 +0,0 @@
|
||||
# Miracles in Motion - Production Deployment Script
|
||||
param(
|
||||
[string]$ResourceGroupName = "rg-miraclesinmotion-prod",
|
||||
[string]$Location = "East US 2",
|
||||
[string]$SubscriptionId = "6187c4d0-3c1a-4135-a8b5-c9782fcf0743"
|
||||
)
|
||||
|
||||
Write-Host "🚀 Starting Miracles in Motion Production Deployment" -ForegroundColor Green
|
||||
|
||||
# Set subscription
|
||||
Write-Host "Setting Azure subscription..." -ForegroundColor Yellow
|
||||
az account set --subscription $SubscriptionId
|
||||
|
||||
# Create resource group
|
||||
Write-Host "Creating resource group: $ResourceGroupName" -ForegroundColor Yellow
|
||||
az group create --name $ResourceGroupName --location $Location
|
||||
|
||||
# Deploy infrastructure using Bicep
|
||||
Write-Host "Deploying Azure infrastructure..." -ForegroundColor Yellow
|
||||
$deploymentResult = az deployment group create `
|
||||
--resource-group $ResourceGroupName `
|
||||
--template-file infrastructure/main.bicep `
|
||||
--parameters @infrastructure/main.parameters.json `
|
||||
--query 'properties.outputs' `
|
||||
--output json
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Infrastructure deployment failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Infrastructure deployed successfully!" -ForegroundColor Green
|
||||
|
||||
# Parse deployment outputs
|
||||
$outputs = $deploymentResult | ConvertFrom-Json
|
||||
|
||||
# Build and deploy Functions
|
||||
Write-Host "Building Azure Functions..." -ForegroundColor Yellow
|
||||
Set-Location api
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Deploy Functions
|
||||
Write-Host "Deploying Azure Functions..." -ForegroundColor Yellow
|
||||
func azure functionapp publish $outputs.functionAppName.value
|
||||
|
||||
# Build frontend
|
||||
Write-Host "Building frontend application..." -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Deploy to Static Web Apps
|
||||
Write-Host "Deploying to Azure Static Web Apps..." -ForegroundColor Yellow
|
||||
az staticwebapp deploy `
|
||||
--name $outputs.staticWebAppName.value `
|
||||
--resource-group $ResourceGroupName `
|
||||
--source dist/
|
||||
|
||||
Write-Host "🎉 Deployment completed successfully!" -ForegroundColor Green
|
||||
Write-Host "🌐 Frontend URL: https://$($outputs.staticWebAppName.value).azurestaticapps.net" -ForegroundColor Cyan
|
||||
Write-Host "⚡ Functions URL: https://$($outputs.functionAppName.value).azurewebsites.net" -ForegroundColor Cyan
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Simple Azure deployment script
|
||||
|
||||
Write-Host "🚀 Deploying Miracles in Motion to Azure..." -ForegroundColor Green
|
||||
|
||||
# Deploy infrastructure
|
||||
Write-Host "Deploying infrastructure..." -ForegroundColor Yellow
|
||||
$deployment = az deployment group create `
|
||||
--resource-group rg-miraclesinmotion-prod `
|
||||
--template-file infrastructure/main.bicep `
|
||||
--parameters @infrastructure/main.parameters.json `
|
||||
--name "infra-deploy-$(Get-Date -Format 'yyyyMMdd-HHmmss')" `
|
||||
--output json | ConvertFrom-Json
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "❌ Infrastructure deployment failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Infrastructure deployed successfully!" -ForegroundColor Green
|
||||
|
||||
# Get deployment outputs
|
||||
$functionAppName = $deployment.properties.outputs.functionAppName.value
|
||||
$staticWebAppName = $deployment.properties.outputs.staticWebAppName.value
|
||||
|
||||
Write-Host "Function App: $functionAppName" -ForegroundColor Cyan
|
||||
Write-Host "Static Web App: $staticWebAppName" -ForegroundColor Cyan
|
||||
|
||||
# Install Azure Functions Core Tools if needed
|
||||
Write-Host "Checking Azure Functions Core Tools..." -ForegroundColor Yellow
|
||||
try {
|
||||
func --version
|
||||
} catch {
|
||||
Write-Host "Installing Azure Functions Core Tools..." -ForegroundColor Yellow
|
||||
npm install -g azure-functions-core-tools@4 --unsafe-perm true
|
||||
}
|
||||
|
||||
# Deploy Functions
|
||||
Write-Host "Deploying Azure Functions..." -ForegroundColor Yellow
|
||||
Set-Location api
|
||||
func azure functionapp publish $functionAppName --typescript
|
||||
Set-Location ..
|
||||
|
||||
# Deploy Static Web App
|
||||
Write-Host "Deploying Static Web App..." -ForegroundColor Yellow
|
||||
az staticwebapp deploy `
|
||||
--name $staticWebAppName `
|
||||
--resource-group rg-miraclesinmotion-prod `
|
||||
--source dist/
|
||||
|
||||
Write-Host "🎉 Deployment completed successfully!" -ForegroundColor Green
|
||||
Write-Host "🌐 Frontend URL: https://$staticWebAppName.azurestaticapps.net" -ForegroundColor Cyan
|
||||
Write-Host "⚡ Functions URL: https://$functionAppName.azurewebsites.net" -ForegroundColor Cyan
|
||||
@@ -1,350 +0,0 @@
|
||||
# Deployment Checklist Script for Miracles In Motion
|
||||
# This script verifies all prerequisites are met before deployment
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$ResourceGroupName = "rg-miraclesinmotion-prod",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$StaticWebAppName = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$FunctionAppName = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$SkipCloudflare = $false,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$SkipStripe = $false
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Colors for output
|
||||
function Write-ColorOutput($ForegroundColor) {
|
||||
$fc = $host.UI.RawUI.ForegroundColor
|
||||
$host.UI.RawUI.ForegroundColor = $ForegroundColor
|
||||
if ($args) {
|
||||
Write-Output $args
|
||||
}
|
||||
$host.UI.RawUI.ForegroundColor = $fc
|
||||
}
|
||||
|
||||
Write-ColorOutput Green "🚀 Deployment Prerequisites Checklist"
|
||||
Write-Output "=========================================="
|
||||
Write-Output ""
|
||||
|
||||
$allChecksPassed = $true
|
||||
$checks = @()
|
||||
|
||||
# Function to add check result
|
||||
function Add-Check {
|
||||
param(
|
||||
[string]$Name,
|
||||
[bool]$Passed,
|
||||
[string]$Message = ""
|
||||
)
|
||||
|
||||
$checks += @{
|
||||
Name = $Name
|
||||
Passed = $Passed
|
||||
Message = $Message
|
||||
}
|
||||
|
||||
if (-not $Passed) {
|
||||
$script:allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
|
||||
# 1. Azure CLI Check
|
||||
Write-ColorOutput Cyan "1. Checking Azure CLI..."
|
||||
try {
|
||||
$azVersion = az version --output json | ConvertFrom-Json
|
||||
Add-Check "Azure CLI" $true "Version: $($azVersion.'azure-cli')"
|
||||
Write-ColorOutput Green " ✅ Azure CLI installed"
|
||||
} catch {
|
||||
Add-Check "Azure CLI" $false "Azure CLI not found. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli"
|
||||
Write-ColorOutput Red " ❌ Azure CLI not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 2. Azure Login Check
|
||||
Write-ColorOutput Cyan "2. Checking Azure login status..."
|
||||
try {
|
||||
$account = az account show --output json 2>$null | ConvertFrom-Json
|
||||
if ($account) {
|
||||
Add-Check "Azure Login" $true "Logged in as: $($account.user.name)"
|
||||
Write-ColorOutput Green " ✅ Logged in to Azure"
|
||||
Write-Output " Subscription: $($account.name)"
|
||||
Write-Output " Tenant ID: $($account.tenantId)"
|
||||
} else {
|
||||
throw "Not logged in"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Azure Login" $false "Not logged in to Azure. Run: az login"
|
||||
Write-ColorOutput Red " ❌ Not logged in to Azure"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 3. Resource Group Check
|
||||
Write-ColorOutput Cyan "3. Checking resource group..."
|
||||
try {
|
||||
$rg = az group show --name $ResourceGroupName --output json 2>$null | ConvertFrom-Json
|
||||
if ($rg) {
|
||||
Add-Check "Resource Group" $true "Resource group exists: $($rg.name)"
|
||||
Write-ColorOutput Green " ✅ Resource group exists"
|
||||
Write-Output " Location: $($rg.location)"
|
||||
} else {
|
||||
throw "Resource group not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Resource Group" $false "Resource group not found: $ResourceGroupName"
|
||||
Write-ColorOutput Red " ❌ Resource group not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 4. Static Web App Check
|
||||
Write-ColorOutput Cyan "4. Checking Static Web App..."
|
||||
if ([string]::IsNullOrEmpty($StaticWebAppName)) {
|
||||
# Try to find Static Web App
|
||||
$swa = az staticwebapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($swa) {
|
||||
$StaticWebAppName = $swa.name
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrEmpty($StaticWebAppName)) {
|
||||
try {
|
||||
$swa = az staticwebapp show --name $StaticWebAppName --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json
|
||||
if ($swa) {
|
||||
Add-Check "Static Web App" $true "Static Web App exists: $($swa.name)"
|
||||
Write-ColorOutput Green " ✅ Static Web App exists"
|
||||
Write-Output " URL: https://$($swa.defaultHostname)"
|
||||
} else {
|
||||
throw "Static Web App not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Static Web App" $false "Static Web App not found: $StaticWebAppName"
|
||||
Write-ColorOutput Red " ❌ Static Web App not found"
|
||||
}
|
||||
} else {
|
||||
Add-Check "Static Web App" $false "Static Web App name not specified"
|
||||
Write-ColorOutput Red " ❌ Static Web App name not specified"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 5. Function App Check
|
||||
Write-ColorOutput Cyan "5. Checking Function App..."
|
||||
if ([string]::IsNullOrEmpty($FunctionAppName)) {
|
||||
# Try to find Function App
|
||||
$fa = az functionapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($fa) {
|
||||
$FunctionAppName = $fa.name
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrEmpty($FunctionAppName)) {
|
||||
try {
|
||||
$fa = az functionapp show --name $FunctionAppName --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json
|
||||
if ($fa) {
|
||||
Add-Check "Function App" $true "Function App exists: $($fa.name)"
|
||||
Write-ColorOutput Green " ✅ Function App exists"
|
||||
Write-Output " URL: https://$($fa.defaultHostName)"
|
||||
} else {
|
||||
throw "Function App not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Function App" $false "Function App not found: $FunctionAppName"
|
||||
Write-ColorOutput Red " ❌ Function App not found"
|
||||
}
|
||||
} else {
|
||||
Add-Check "Function App" $false "Function App name not specified"
|
||||
Write-ColorOutput Red " ❌ Function App name not specified"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 6. Key Vault Check
|
||||
Write-ColorOutput Cyan "6. Checking Key Vault..."
|
||||
try {
|
||||
$kv = az keyvault list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($kv) {
|
||||
Add-Check "Key Vault" $true "Key Vault exists: $($kv.name)"
|
||||
Write-ColorOutput Green " ✅ Key Vault exists"
|
||||
|
||||
# Check for required secrets
|
||||
$requiredSecrets = @("stripe-secret-key", "azure-client-id", "azure-tenant-id")
|
||||
$missingSecrets = @()
|
||||
|
||||
foreach ($secret in $requiredSecrets) {
|
||||
try {
|
||||
$secretValue = az keyvault secret show --vault-name $kv.name --name $secret --output json 2>$null | ConvertFrom-Json
|
||||
if (-not $secretValue) {
|
||||
$missingSecrets += $secret
|
||||
}
|
||||
} catch {
|
||||
$missingSecrets += $secret
|
||||
}
|
||||
}
|
||||
|
||||
if ($missingSecrets.Count -eq 0) {
|
||||
Write-ColorOutput Green " ✅ Required secrets present"
|
||||
} else {
|
||||
Write-ColorOutput Yellow " ⚠️ Missing secrets: $($missingSecrets -join ', ')"
|
||||
}
|
||||
} else {
|
||||
throw "Key Vault not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Key Vault" $false "Key Vault not found"
|
||||
Write-ColorOutput Red " ❌ Key Vault not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 7. Cosmos DB Check
|
||||
Write-ColorOutput Cyan "7. Checking Cosmos DB..."
|
||||
try {
|
||||
$cosmos = az cosmosdb list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($cosmos) {
|
||||
Add-Check "Cosmos DB" $true "Cosmos DB exists: $($cosmos.name)"
|
||||
Write-ColorOutput Green " ✅ Cosmos DB exists"
|
||||
} else {
|
||||
throw "Cosmos DB not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Cosmos DB" $false "Cosmos DB not found"
|
||||
Write-ColorOutput Red " ❌ Cosmos DB not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 8. Application Insights Check
|
||||
Write-ColorOutput Cyan "8. Checking Application Insights..."
|
||||
try {
|
||||
$ai = az monitor app-insights component show --app $ResourceGroupName --output json 2>$null | ConvertFrom-Json
|
||||
if (-not $ai) {
|
||||
# Try alternative method
|
||||
$ai = az resource list --resource-group $ResourceGroupName --resource-type "Microsoft.Insights/components" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
}
|
||||
if ($ai) {
|
||||
Add-Check "Application Insights" $true "Application Insights exists"
|
||||
Write-ColorOutput Green " ✅ Application Insights exists"
|
||||
} else {
|
||||
throw "Application Insights not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Application Insights" $false "Application Insights not found"
|
||||
Write-ColorOutput Red " ❌ Application Insights not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 9. Azure AD App Registration Check
|
||||
Write-ColorOutput Cyan "9. Checking Azure AD App Registration..."
|
||||
try {
|
||||
$appReg = az ad app list --display-name "Miracles In Motion Web App" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($appReg) {
|
||||
Add-Check "Azure AD App Registration" $true "App Registration exists: $($appReg.appId)"
|
||||
Write-ColorOutput Green " ✅ Azure AD App Registration exists"
|
||||
Write-Output " App ID: $($appReg.appId)"
|
||||
|
||||
# Check redirect URIs
|
||||
if ($appReg.web.redirectUris) {
|
||||
Write-Output " Redirect URIs: $($appReg.web.redirectUris.Count)"
|
||||
}
|
||||
} else {
|
||||
throw "App Registration not found"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Azure AD App Registration" $false "Azure AD App Registration not found"
|
||||
Write-ColorOutput Red " ❌ Azure AD App Registration not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# 10. Cloudflare Check
|
||||
if (-not $SkipCloudflare) {
|
||||
Write-ColorOutput Cyan "10. Checking Cloudflare configuration..."
|
||||
try {
|
||||
# Check DNS resolution
|
||||
$dnsResult = Resolve-DnsName -Name "miraclesinmotion.org" -ErrorAction SilentlyContinue
|
||||
if ($dnsResult) {
|
||||
Add-Check "Cloudflare DNS" $true "DNS resolution working"
|
||||
Write-ColorOutput Green " ✅ DNS resolution working"
|
||||
} else {
|
||||
Add-Check "Cloudflare DNS" $false "DNS resolution failed"
|
||||
Write-ColorOutput Red " ❌ DNS resolution failed"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Cloudflare DNS" $false "Could not verify DNS"
|
||||
Write-ColorOutput Yellow " ⚠️ Could not verify DNS"
|
||||
}
|
||||
Write-Output ""
|
||||
}
|
||||
|
||||
# 11. Stripe Check
|
||||
if (-not $SkipStripe) {
|
||||
Write-ColorOutput Cyan "11. Checking Stripe configuration..."
|
||||
try {
|
||||
if ($kv) {
|
||||
$stripeKey = az keyvault secret show --vault-name $kv.name --name "stripe-secret-key" --output json 2>$null | ConvertFrom-Json
|
||||
if ($stripeKey -and $stripeKey.value -like "sk_live_*") {
|
||||
Add-Check "Stripe Configuration" $true "Stripe keys configured"
|
||||
Write-ColorOutput Green " ✅ Stripe keys configured"
|
||||
} else {
|
||||
Add-Check "Stripe Configuration" $false "Stripe keys not configured or not production keys"
|
||||
Write-ColorOutput Yellow " ⚠️ Stripe keys not configured or not production keys"
|
||||
}
|
||||
} else {
|
||||
Add-Check "Stripe Configuration" $false "Key Vault not available"
|
||||
Write-ColorOutput Yellow " ⚠️ Key Vault not available"
|
||||
}
|
||||
} catch {
|
||||
Add-Check "Stripe Configuration" $false "Could not verify Stripe configuration"
|
||||
Write-ColorOutput Yellow " ⚠️ Could not verify Stripe configuration"
|
||||
}
|
||||
Write-Output ""
|
||||
}
|
||||
|
||||
# 12. Environment Variables Check
|
||||
Write-ColorOutput Cyan "12. Checking environment variables..."
|
||||
$envFile = ".env.production"
|
||||
if (Test-Path $envFile) {
|
||||
Add-Check "Environment File" $true "Environment file exists"
|
||||
Write-ColorOutput Green " ✅ Environment file exists"
|
||||
} else {
|
||||
Add-Check "Environment File" $false "Environment file not found: $envFile"
|
||||
Write-ColorOutput Yellow " ⚠️ Environment file not found"
|
||||
}
|
||||
Write-Output ""
|
||||
|
||||
# Summary
|
||||
Write-Output ""
|
||||
Write-ColorOutput Cyan "=========================================="
|
||||
Write-ColorOutput Cyan "Summary"
|
||||
Write-ColorOutput Cyan "=========================================="
|
||||
Write-Output ""
|
||||
|
||||
$passedChecks = ($checks | Where-Object { $_.Passed -eq $true }).Count
|
||||
$totalChecks = $checks.Count
|
||||
|
||||
Write-Output "Passed: $passedChecks / $totalChecks"
|
||||
Write-Output ""
|
||||
|
||||
foreach ($check in $checks) {
|
||||
if ($check.Passed) {
|
||||
Write-ColorOutput Green "✅ $($check.Name)"
|
||||
} else {
|
||||
Write-ColorOutput Red "❌ $($check.Name)"
|
||||
if ($check.Message) {
|
||||
Write-Output " $($check.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output ""
|
||||
|
||||
if ($allChecksPassed) {
|
||||
Write-ColorOutput Green "✅ All checks passed! Ready for deployment."
|
||||
exit 0
|
||||
} else {
|
||||
Write-ColorOutput Red "❌ Some checks failed. Please fix the issues before deploying."
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
# Script to populate .env file with Azure configuration
|
||||
# This script gathers Azure information and creates/updates the .env file
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$ResourceGroupName = "rg-miraclesinmotion-prod",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Location = "eastus2",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Domain = "mim4u.org",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$CreateResourceGroup = $false
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "🔧 Populating .env file with Azure configuration" -ForegroundColor Green
|
||||
Write-Host "=============================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check if logged in to Azure
|
||||
$account = az account show --output json 2>$null | ConvertFrom-Json
|
||||
if (-not $account) {
|
||||
Write-Host "❌ Not logged in to Azure. Please run: az login" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Logged in to Azure" -ForegroundColor Green
|
||||
Write-Host " Subscription: $($account.name)" -ForegroundColor Gray
|
||||
Write-Host " Tenant ID: $($account.tenantId)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Get subscription ID
|
||||
$subscriptionId = $account.id
|
||||
$tenantId = $account.tenantId
|
||||
|
||||
# Check if resource group exists
|
||||
$rgExists = az group exists --name $ResourceGroupName --output tsv
|
||||
if ($rgExists -eq "false") {
|
||||
if ($CreateResourceGroup) {
|
||||
Write-Host "📁 Creating resource group: $ResourceGroupName" -ForegroundColor Cyan
|
||||
az group create --name $ResourceGroupName --location $Location | Out-Null
|
||||
Write-Host "✅ Resource group created" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Resource group '$ResourceGroupName' does not exist." -ForegroundColor Yellow
|
||||
Write-Host " Run with -CreateResourceGroup to create it, or deploy infrastructure first." -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "✅ Resource group exists: $ResourceGroupName" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Check for existing resources
|
||||
Write-Host "🔍 Checking for existing resources..." -ForegroundColor Cyan
|
||||
|
||||
# Check for Static Web App
|
||||
$staticWebApp = az staticwebapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
$staticWebAppName = ""
|
||||
$staticWebAppUrl = ""
|
||||
|
||||
if ($staticWebApp) {
|
||||
$staticWebAppName = $staticWebApp.name
|
||||
$staticWebAppUrl = "https://$($staticWebApp.defaultHostname)"
|
||||
Write-Host "✅ Found Static Web App: $staticWebAppName" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Static Web App not found (will use placeholder)" -ForegroundColor Yellow
|
||||
$staticWebAppUrl = "https://mim4u.org"
|
||||
}
|
||||
|
||||
# Check for Function App
|
||||
$functionApp = az functionapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
$functionAppName = ""
|
||||
$functionAppUrl = ""
|
||||
|
||||
if ($functionApp) {
|
||||
$functionAppName = $functionApp.name
|
||||
$functionAppUrl = "https://$($functionApp.defaultHostName)"
|
||||
Write-Host "✅ Found Function App: $functionAppName" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Function App not found (will use placeholder)" -ForegroundColor Yellow
|
||||
$functionAppUrl = "https://YOUR_FUNCTION_APP.azurewebsites.net"
|
||||
}
|
||||
|
||||
# Check for Key Vault
|
||||
$keyVault = az keyvault list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
$keyVaultName = ""
|
||||
$keyVaultUrl = ""
|
||||
|
||||
if ($keyVault) {
|
||||
$keyVaultName = $keyVault.name
|
||||
$keyVaultUrl = "https://$keyVaultName.vault.azure.net/"
|
||||
Write-Host "✅ Found Key Vault: $keyVaultName" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Key Vault not found (will use placeholder)" -ForegroundColor Yellow
|
||||
$keyVaultUrl = "https://YOUR_KEY_VAULT_NAME.vault.azure.net/"
|
||||
}
|
||||
|
||||
# Check for Cosmos DB
|
||||
$cosmosAccount = az cosmosdb list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
$cosmosEndpoint = ""
|
||||
|
||||
if ($cosmosAccount) {
|
||||
$cosmosEndpoint = "https://$($cosmosAccount.name).documents.azure.com:443/"
|
||||
Write-Host "✅ Found Cosmos DB: $($cosmosAccount.name)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Cosmos DB not found (will use placeholder)" -ForegroundColor Yellow
|
||||
$cosmosEndpoint = "https://YOUR_COSMOS_ACCOUNT.documents.azure.com:443/"
|
||||
}
|
||||
|
||||
# Check for Application Insights
|
||||
$appInsights = az monitor app-insights component show --app $ResourceGroupName --output json 2>$null | ConvertFrom-Json
|
||||
if (-not $appInsights) {
|
||||
$appInsights = az resource list --resource-group $ResourceGroupName --resource-type "Microsoft.Insights/components" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
}
|
||||
$appInsightsConnectionString = ""
|
||||
|
||||
if ($appInsights) {
|
||||
$appInsightsConnectionString = $appInsights.connectionString
|
||||
Write-Host "✅ Found Application Insights: $($appInsights.name)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Application Insights not found (will use placeholder)" -ForegroundColor Yellow
|
||||
$appInsightsConnectionString = "InstrumentationKey=YOUR_KEY;IngestionEndpoint=https://YOUR_REGION.in.applicationinsights.azure.com/"
|
||||
}
|
||||
|
||||
# Check for SignalR
|
||||
$signalR = az signalr list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
$signalRConnectionString = ""
|
||||
|
||||
if ($signalR) {
|
||||
$signalRKeys = az signalr key list --name $signalR.name --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json
|
||||
if ($signalRKeys) {
|
||||
$signalREndpoint = $signalR.hostName
|
||||
$signalRKey = $signalRKeys.primaryKey
|
||||
$signalRConnectionString = "Endpoint=https://$signalREndpoint;AccessKey=$signalRKey;Version=1.0;"
|
||||
Write-Host "✅ Found SignalR: $($signalR.name)" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "⚠️ SignalR not found (will use placeholder)" -ForegroundColor Yellow
|
||||
$signalRConnectionString = "Endpoint=https://YOUR_SIGNALR.service.signalr.net;AccessKey=YOUR_KEY;Version=1.0;"
|
||||
}
|
||||
|
||||
# Check for Azure AD App Registration
|
||||
$appReg = az ad app list --display-name "Miracles In Motion Web App" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1
|
||||
$azureClientId = ""
|
||||
|
||||
if ($appReg) {
|
||||
$azureClientId = $appReg.appId
|
||||
Write-Host "✅ Found Azure AD App Registration: $azureClientId" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Azure AD App Registration not found (will use placeholder)" -ForegroundColor Yellow
|
||||
Write-Host " Run: .\scripts\setup-azure-entra.ps1 to create it" -ForegroundColor Yellow
|
||||
$azureClientId = "your-azure-client-id"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Prompt for Stripe keys
|
||||
Write-Host "💳 Stripe Configuration" -ForegroundColor Cyan
|
||||
$stripePublishableKey = Read-Host "Enter Stripe Publishable Key (pk_live_...) [or press Enter to skip]"
|
||||
if ([string]::IsNullOrWhiteSpace($stripePublishableKey)) {
|
||||
$stripePublishableKey = "pk_live_YOUR_KEY"
|
||||
}
|
||||
|
||||
$stripeSecretKey = Read-Host "Enter Stripe Secret Key (sk_live_...) [or press Enter to skip]"
|
||||
if ([string]::IsNullOrWhiteSpace($stripeSecretKey)) {
|
||||
$stripeSecretKey = "sk_live_YOUR_KEY"
|
||||
}
|
||||
|
||||
$stripeWebhookSecret = Read-Host "Enter Stripe Webhook Secret (whsec_...) [or press Enter to skip]"
|
||||
if ([string]::IsNullOrWhiteSpace($stripeWebhookSecret)) {
|
||||
$stripeWebhookSecret = "whsec_YOUR_SECRET"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Create .env file content
|
||||
$envContent = @"
|
||||
# Azure Configuration
|
||||
AZURE_SUBSCRIPTION_ID=$subscriptionId
|
||||
AZURE_TENANT_ID=$tenantId
|
||||
AZURE_RESOURCE_GROUP=$ResourceGroupName
|
||||
AZURE_LOCATION=$Location
|
||||
AZURE_STATIC_WEB_APP_URL=$staticWebAppUrl
|
||||
AZURE_STATIC_WEB_APP_NAME=$staticWebAppName
|
||||
AZURE_FUNCTION_APP_URL=$functionAppUrl
|
||||
AZURE_FUNCTION_APP_NAME=$functionAppName
|
||||
AZURE_CLIENT_ID=$azureClientId
|
||||
AZURE_TENANT_ID=$tenantId
|
||||
AZURE_CLIENT_SECRET=your-azure-client-secret
|
||||
|
||||
# Stripe Configuration
|
||||
VITE_STRIPE_PUBLISHABLE_KEY=$stripePublishableKey
|
||||
STRIPE_SECRET_KEY=$stripeSecretKey
|
||||
STRIPE_WEBHOOK_SECRET=$stripeWebhookSecret
|
||||
|
||||
# Cosmos DB Configuration
|
||||
COSMOS_DATABASE_NAME=MiraclesInMotion
|
||||
COSMOS_ENDPOINT=$cosmosEndpoint
|
||||
COSMOS_KEY=your-cosmos-key
|
||||
|
||||
# Application Insights
|
||||
APPLICATIONINSIGHTS_CONNECTION_STRING=$appInsightsConnectionString
|
||||
|
||||
# Key Vault
|
||||
KEY_VAULT_URL=$keyVaultUrl
|
||||
KEY_VAULT_NAME=$keyVaultName
|
||||
|
||||
# SignalR
|
||||
SIGNALR_CONNECTION_STRING=$signalRConnectionString
|
||||
|
||||
# Custom Domain
|
||||
CUSTOM_DOMAIN=$Domain
|
||||
|
||||
# Environment
|
||||
NODE_ENV=production
|
||||
VITE_API_BASE_URL=$staticWebAppUrl/api
|
||||
|
||||
# Feature Flags
|
||||
VITE_ENABLE_ANALYTICS=true
|
||||
VITE_ENABLE_PWA=true
|
||||
VITE_ENABLE_AI=true
|
||||
|
||||
# Cloudflare (Optional)
|
||||
CLOUDFLARE_ZONE_ID=your-cloudflare-zone-id
|
||||
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
|
||||
|
||||
# Salesforce (Optional)
|
||||
SALESFORCE_CLIENT_ID=your-salesforce-client-id
|
||||
SALESFORCE_CLIENT_SECRET=your-salesforce-client-secret
|
||||
SALESFORCE_USERNAME=your-salesforce-username
|
||||
SALESFORCE_PASSWORD=your-salesforce-password
|
||||
SALESFORCE_SECURITY_TOKEN=your-salesforce-security-token
|
||||
|
||||
# Email Configuration (Optional)
|
||||
SMTP_HOST=smtp.office365.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@domain.com
|
||||
SMTP_PASSWORD=your-email-password
|
||||
SMTP_FROM=noreply@mim4u.org
|
||||
|
||||
# Monitoring (Optional)
|
||||
SENTRY_DSN=your-sentry-dsn
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Security
|
||||
SESSION_SECRET=your-session-secret
|
||||
JWT_SECRET=your-jwt-secret
|
||||
ENCRYPTION_KEY=your-encryption-key
|
||||
"@
|
||||
|
||||
# Write .env file
|
||||
$envFile = ".env.production"
|
||||
$envContent | Out-File -FilePath $envFile -Encoding UTF8 -NoNewline
|
||||
|
||||
Write-Host "✅ Created .env file: $envFile" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "📋 Summary:" -ForegroundColor Cyan
|
||||
Write-Host " Subscription: $($account.name)" -ForegroundColor Gray
|
||||
Write-Host " Tenant ID: $tenantId" -ForegroundColor Gray
|
||||
Write-Host " Resource Group: $ResourceGroupName" -ForegroundColor Gray
|
||||
Write-Host " Domain: $Domain" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "⚠️ Next Steps:" -ForegroundColor Yellow
|
||||
Write-Host "1. Review and update placeholder values in $envFile" -ForegroundColor White
|
||||
Write-Host "2. Run: .\scripts\setup-azure-entra.ps1 to create Azure AD app registration" -ForegroundColor White
|
||||
Write-Host "3. Deploy infrastructure: az deployment group create ..." -ForegroundColor White
|
||||
Write-Host "4. Store secrets in Key Vault using: .\scripts\store-secrets-in-keyvault.ps1" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
# MS Entra (Azure AD) Setup Script for Miracles In Motion (PowerShell)
|
||||
# This script helps configure Azure AD authentication for the application
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$AppName = "Miracles In Motion Web App",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Domain = "mim4u.org",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$StaticWebAppName = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$AzureResourceGroup = "rg-miraclesinmotion-prod",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$KeyVaultName = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "🔐 MS Entra (Azure AD) Setup Script" -ForegroundColor Green
|
||||
Write-Host "==========================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if (-not (Get-Command "az" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "❌ Azure CLI not found. Please install it first." -ForegroundColor Red
|
||||
Write-Host "Install from: https://docs.microsoft.com/cli/azure/install-azure-cli" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if logged in to Azure
|
||||
Write-Host "📋 Checking Azure login status..." -ForegroundColor Cyan
|
||||
$account = az account show --output json 2>$null | ConvertFrom-Json
|
||||
if (-not $account) {
|
||||
Write-Host "⚠️ Not logged in to Azure. Please log in..." -ForegroundColor Yellow
|
||||
az login
|
||||
$account = az account show --output json | ConvertFrom-Json
|
||||
}
|
||||
Write-Host "✅ Logged in as: $($account.user.name)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Get Azure Static Web App URL
|
||||
Write-Host "📋 Getting Azure Static Web App information..." -ForegroundColor Cyan
|
||||
|
||||
if ([string]::IsNullOrEmpty($StaticWebAppName)) {
|
||||
# Try to find Static Web App
|
||||
$swa = az staticwebapp list --resource-group $AzureResourceGroup --output json | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($swa) {
|
||||
$StaticWebAppName = $swa.name
|
||||
}
|
||||
}
|
||||
|
||||
$azureStaticWebAppUrl = ""
|
||||
if (-not [string]::IsNullOrEmpty($StaticWebAppName)) {
|
||||
$azureStaticWebAppUrl = az staticwebapp show `
|
||||
--name $StaticWebAppName `
|
||||
--resource-group $AzureResourceGroup `
|
||||
--query "defaultHostname" -o tsv 2>$null
|
||||
|
||||
if ($azureStaticWebAppUrl) {
|
||||
$azureStaticWebAppUrl = "https://$azureStaticWebAppUrl"
|
||||
Write-Host "✅ Static Web App URL: $azureStaticWebAppUrl" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "⚠️ Static Web App not found. Using default URL format." -ForegroundColor Yellow
|
||||
$azureStaticWebAppUrl = "https://${StaticWebAppName}.azurestaticapps.net"
|
||||
}
|
||||
|
||||
$productionUrl = "https://$Domain"
|
||||
$wwwUrl = "https://www.$Domain"
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Get Tenant ID
|
||||
$tenantId = $account.tenantId
|
||||
Write-Host "✅ Tenant ID: $tenantId" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check if app registration already exists
|
||||
Write-Host "🔍 Checking for existing app registration..." -ForegroundColor Cyan
|
||||
$existingApp = az ad app list --display-name $AppName --output json | ConvertFrom-Json | Select-Object -First 1
|
||||
|
||||
if ($existingApp) {
|
||||
Write-Host "⚠️ App registration already exists: $($existingApp.appId)" -ForegroundColor Yellow
|
||||
$updateApp = Read-Host "Do you want to update it? (y/n)"
|
||||
if ($updateApp -ne "y") {
|
||||
$appId = $existingApp.appId
|
||||
Write-Host "✅ Using existing app registration" -ForegroundColor Green
|
||||
} else {
|
||||
$appId = $existingApp.appId
|
||||
Write-Host "📝 Updating app registration..." -ForegroundColor Cyan
|
||||
}
|
||||
} else {
|
||||
# Create app registration
|
||||
Write-Host "📝 Creating app registration..." -ForegroundColor Cyan
|
||||
$appId = az ad app create `
|
||||
--display-name $AppName `
|
||||
--sign-in-audience "AzureADMultipleOrgs" `
|
||||
--web-redirect-uris $productionUrl $wwwUrl $azureStaticWebAppUrl `
|
||||
--query "appId" -o tsv
|
||||
|
||||
Write-Host "✅ App registration created: $appId" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Update redirect URIs
|
||||
Write-Host "📝 Updating redirect URIs..." -ForegroundColor Cyan
|
||||
az ad app update --id $appId `
|
||||
--web-redirect-uris $productionUrl $wwwUrl $azureStaticWebAppUrl `
|
||||
--enable-id-token-issuance true `
|
||||
--enable-access-token-issuance false | Out-Null
|
||||
|
||||
Write-Host "✅ Redirect URIs updated" -ForegroundColor Green
|
||||
Write-Host " - $productionUrl"
|
||||
Write-Host " - $wwwUrl"
|
||||
Write-Host " - $azureStaticWebAppUrl"
|
||||
Write-Host ""
|
||||
|
||||
# Configure API permissions
|
||||
Write-Host "📝 Configuring API permissions..." -ForegroundColor Cyan
|
||||
|
||||
$graphPermissions = @(
|
||||
"User.Read",
|
||||
"User.ReadBasic.All",
|
||||
"email",
|
||||
"openid",
|
||||
"profile"
|
||||
)
|
||||
|
||||
foreach ($permission in $graphPermissions) {
|
||||
Write-Host " Adding permission: $permission" -ForegroundColor Gray
|
||||
$permissionId = az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?value=='$permission'].id" -o tsv
|
||||
if ($permissionId) {
|
||||
az ad app permission add `
|
||||
--id $appId `
|
||||
--api "00000003-0000-0000-c000-000000000000" `
|
||||
--api-permissions "${permissionId}=Scope" 2>$null | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "✅ API permissions configured" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Create service principal
|
||||
Write-Host "📝 Creating service principal..." -ForegroundColor Cyan
|
||||
$spId = az ad sp create --id $appId --query "id" -o tsv 2>$null
|
||||
if (-not $spId) {
|
||||
$spId = az ad sp show --id $appId --query "id" -o tsv
|
||||
}
|
||||
|
||||
Write-Host "✅ Service principal created: $spId" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Grant admin consent
|
||||
Write-Host "📝 Granting admin consent for API permissions..." -ForegroundColor Cyan
|
||||
$hasAdmin = Read-Host "Do you have admin privileges to grant consent? (y/n)"
|
||||
|
||||
if ($hasAdmin -eq "y") {
|
||||
az ad app permission admin-consent --id $appId 2>$null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✅ Admin consent granted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Could not grant admin consent. You may need to do this manually." -ForegroundColor Yellow
|
||||
Write-Host " Go to: Azure Portal → Microsoft Entra ID → App registrations → $AppName → API permissions → Grant admin consent" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "⚠️ Skipping admin consent. Please grant consent manually in Azure Portal." -ForegroundColor Yellow
|
||||
Write-Host " Go to: Azure Portal → Microsoft Entra ID → App registrations → $AppName → API permissions → Grant admin consent" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Create client secret
|
||||
Write-Host "📝 Client Secret Configuration..." -ForegroundColor Cyan
|
||||
$createSecret = Read-Host "Do you want to create a client secret? (y/n)"
|
||||
|
||||
$clientSecret = ""
|
||||
if ($createSecret -eq "y") {
|
||||
$secretName = "Miracles In Motion Secret $(Get-Date -Format 'yyyyMMdd')"
|
||||
$clientSecret = az ad app credential reset --id $appId --display-name $secretName --years 2 --query "password" -o tsv
|
||||
Write-Host "✅ Client secret created" -ForegroundColor Green
|
||||
Write-Host "⚠️ IMPORTANT: Save this secret now - it won't be shown again!" -ForegroundColor Red
|
||||
Write-Host "Secret: $clientSecret" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Read-Host "Press Enter to continue after saving the secret..."
|
||||
} else {
|
||||
Write-Host "⚠️ Skipping client secret creation" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Store configuration in Key Vault
|
||||
Write-Host "📝 Storing configuration in Key Vault..." -ForegroundColor Cyan
|
||||
|
||||
if ([string]::IsNullOrEmpty($KeyVaultName)) {
|
||||
$KeyVaultName = az keyvault list --resource-group $AzureResourceGroup --query "[0].name" -o tsv 2>$null
|
||||
}
|
||||
|
||||
if ($KeyVaultName) {
|
||||
Write-Host "Storing in Key Vault: $KeyVaultName" -ForegroundColor Gray
|
||||
|
||||
az keyvault secret set --vault-name $KeyVaultName --name "azure-client-id" --value $appId 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✅ Client ID stored" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Could not store Client ID" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
az keyvault secret set --vault-name $KeyVaultName --name "azure-tenant-id" --value $tenantId 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✅ Tenant ID stored" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Could not store Tenant ID" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if ($clientSecret) {
|
||||
az keyvault secret set --vault-name $KeyVaultName --name "azure-client-secret" --value $clientSecret 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✅ Client Secret stored" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Could not store Client Secret" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "⚠️ Key Vault not found. Skipping secret storage." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Summary
|
||||
Write-Host "✅ MS Entra Setup Complete!" -ForegroundColor Green
|
||||
Write-Host "==================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Configuration Summary:"
|
||||
Write-Host " App Registration ID: $appId"
|
||||
Write-Host " Tenant ID: $tenantId"
|
||||
Write-Host " Service Principal ID: $spId"
|
||||
Write-Host ""
|
||||
Write-Host "Redirect URIs:"
|
||||
Write-Host " - $productionUrl"
|
||||
Write-Host " - $wwwUrl"
|
||||
Write-Host " - $azureStaticWebAppUrl"
|
||||
Write-Host ""
|
||||
Write-Host "Next Steps:"
|
||||
Write-Host "1. Assign users to app roles in Azure Portal"
|
||||
Write-Host "2. Update staticwebapp.config.json with authentication configuration"
|
||||
Write-Host "3. Update application code to use Azure AD authentication"
|
||||
Write-Host "4. Test authentication flow"
|
||||
Write-Host ""
|
||||
Write-Host "Azure Portal Links:"
|
||||
Write-Host " App Registration: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/$appId"
|
||||
Write-Host " API Permissions: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/$appId"
|
||||
Write-Host ""
|
||||
|
||||
# Export variables
|
||||
$configContent = @"
|
||||
# Azure AD Configuration
|
||||
AZURE_CLIENT_ID=$appId
|
||||
AZURE_TENANT_ID=$tenantId
|
||||
AZURE_CLIENT_SECRET=$clientSecret
|
||||
AZURE_STATIC_WEB_APP_URL=$azureStaticWebAppUrl
|
||||
AZURE_PRODUCTION_URL=$productionUrl
|
||||
"@
|
||||
|
||||
$configContent | Out-File -FilePath ".azure-entra-config.env" -Encoding UTF8
|
||||
Write-Host "✅ Configuration saved to .azure-entra-config.env" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# MS Entra (Azure AD) Setup Script for Miracles In Motion
|
||||
# This script helps configure Azure AD authentication for the application
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
APP_NAME="Miracles In Motion Web App"
|
||||
DOMAIN="miraclesinmotion.org"
|
||||
STATIC_WEB_APP_NAME="${STATIC_WEB_APP_NAME:-mim-prod-web}"
|
||||
AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-rg-miraclesinmotion-prod}"
|
||||
|
||||
echo -e "${GREEN}🔐 MS Entra (Azure AD) Setup Script${NC}"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if ! command -v az &> /dev/null; then
|
||||
echo -e "${RED}❌ Azure CLI not found. Please install it first.${NC}"
|
||||
echo "Install from: https://docs.microsoft.com/cli/azure/install-azure-cli"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in to Azure
|
||||
echo -e "${BLUE}📋 Checking Azure login status...${NC}"
|
||||
CURRENT_USER=$(az account show --query "user.name" -o tsv 2>/dev/null || echo "")
|
||||
if [ -z "$CURRENT_USER" ]; then
|
||||
echo -e "${YELLOW}⚠️ Not logged in to Azure. Please log in...${NC}"
|
||||
az login
|
||||
CURRENT_USER=$(az account show --query "user.name" -o tsv)
|
||||
fi
|
||||
echo -e "${GREEN}✅ Logged in as: $CURRENT_USER${NC}"
|
||||
echo ""
|
||||
|
||||
# Get Azure Static Web App URL
|
||||
echo -e "${BLUE}📋 Getting Azure Static Web App information...${NC}"
|
||||
AZURE_STATIC_WEB_APP_URL=$(az staticwebapp show \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--query "defaultHostname" -o tsv 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$AZURE_STATIC_WEB_APP_URL" ]; then
|
||||
echo -e "${YELLOW}⚠️ Static Web App not found. Using default URL format.${NC}"
|
||||
AZURE_STATIC_WEB_APP_URL="${STATIC_WEB_APP_NAME}.azurestaticapps.net"
|
||||
fi
|
||||
|
||||
FULL_STATIC_WEB_APP_URL="https://${AZURE_STATIC_WEB_APP_URL}"
|
||||
PRODUCTION_URL="https://${DOMAIN}"
|
||||
WWW_URL="https://www.${DOMAIN}"
|
||||
|
||||
echo -e "${GREEN}✅ Static Web App URL: $FULL_STATIC_WEB_APP_URL${NC}"
|
||||
echo ""
|
||||
|
||||
# Get Tenant ID
|
||||
TENANT_ID=$(az account show --query "tenantId" -o tsv)
|
||||
echo -e "${GREEN}✅ Tenant ID: $TENANT_ID${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if app registration already exists
|
||||
echo -e "${BLUE}🔍 Checking for existing app registration...${NC}"
|
||||
EXISTING_APP_ID=$(az ad app list --display-name "$APP_NAME" --query "[0].appId" -o tsv 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$EXISTING_APP_ID" ] && [ "$EXISTING_APP_ID" != "null" ]; then
|
||||
echo -e "${YELLOW}⚠️ App registration already exists: $EXISTING_APP_ID${NC}"
|
||||
read -p "Do you want to update it? (y/n): " UPDATE_APP
|
||||
if [ "$UPDATE_APP" != "y" ]; then
|
||||
APP_ID=$EXISTING_APP_ID
|
||||
echo -e "${GREEN}✅ Using existing app registration${NC}"
|
||||
else
|
||||
echo -e "${BLUE}📝 Updating app registration...${NC}"
|
||||
APP_ID=$EXISTING_APP_ID
|
||||
fi
|
||||
else
|
||||
# Create app registration
|
||||
echo -e "${BLUE}📝 Creating app registration...${NC}"
|
||||
APP_ID=$(az ad app create \
|
||||
--display-name "$APP_NAME" \
|
||||
--sign-in-audience "AzureADMultipleOrgs" \
|
||||
--web-redirect-uris "$PRODUCTION_URL" "$WWW_URL" "$FULL_STATIC_WEB_APP_URL" \
|
||||
--query "appId" -o tsv)
|
||||
|
||||
echo -e "${GREEN}✅ App registration created: $APP_ID${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Update redirect URIs
|
||||
echo -e "${BLUE}📝 Updating redirect URIs...${NC}"
|
||||
az ad app update --id "$APP_ID" \
|
||||
--web-redirect-uris "$PRODUCTION_URL" "$WWW_URL" "$FULL_STATIC_WEB_APP_URL" \
|
||||
--enable-id-token-issuance true \
|
||||
--enable-access-token-issuance false \
|
||||
--query "appId" -o tsv > /dev/null
|
||||
|
||||
echo -e "${GREEN}✅ Redirect URIs updated${NC}"
|
||||
echo " - $PRODUCTION_URL"
|
||||
echo " - $WWW_URL"
|
||||
echo " - $FULL_STATIC_WEB_APP_URL"
|
||||
echo ""
|
||||
|
||||
# Configure API permissions
|
||||
echo -e "${BLUE}📝 Configuring API permissions...${NC}"
|
||||
|
||||
# Microsoft Graph permissions
|
||||
GRAPH_PERMISSIONS=(
|
||||
"User.Read"
|
||||
"User.ReadBasic.All"
|
||||
"email"
|
||||
"openid"
|
||||
"profile"
|
||||
)
|
||||
|
||||
GRAPH_RESOURCE_ID=$(az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "id" -o tsv)
|
||||
|
||||
for PERMISSION in "${GRAPH_PERMISSIONS[@]}"; do
|
||||
PERMISSION_ID=$(az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?value=='$PERMISSION'].id" -o tsv)
|
||||
if [ -n "$PERMISSION_ID" ]; then
|
||||
echo " Adding permission: $PERMISSION"
|
||||
az ad app permission add \
|
||||
--id "$APP_ID" \
|
||||
--api "00000003-0000-0000-c000-000000000000" \
|
||||
--api-permissions "$PERMISSION_ID=Scope" \
|
||||
--query "appId" -o tsv > /dev/null 2>&1 || echo " (may already exist)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "${GREEN}✅ API permissions configured${NC}"
|
||||
echo ""
|
||||
|
||||
# Create app roles
|
||||
echo -e "${BLUE}📝 Creating app roles...${NC}"
|
||||
|
||||
# Function to create app role
|
||||
create_app_role() {
|
||||
local role_name=$1
|
||||
local role_value=$2
|
||||
local role_description=$3
|
||||
|
||||
echo " Creating role: $role_name"
|
||||
|
||||
local role_json=$(cat <<EOF
|
||||
{
|
||||
"allowedMemberTypes": ["User"],
|
||||
"description": "$role_description",
|
||||
"displayName": "$role_name",
|
||||
"id": "$(uuidgen | tr '[:upper:]' '[:lower:]')",
|
||||
"isEnabled": true,
|
||||
"value": "$role_value"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Get existing roles
|
||||
local existing_roles=$(az ad app show --id "$APP_ID" --query "appRoles" -o json)
|
||||
|
||||
# Check if role already exists
|
||||
local role_exists=$(echo "$existing_roles" | jq -r ".[] | select(.value == \"$role_value\") | .value" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$role_exists" ]; then
|
||||
# Add new role to existing roles
|
||||
local updated_roles=$(echo "$existing_roles" | jq ". + [$role_json]")
|
||||
az ad app update --id "$APP_ID" --app-roles "$updated_roles" > /dev/null 2>&1
|
||||
echo " ✅ Role created"
|
||||
else
|
||||
echo " ⚠️ Role already exists"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create app roles
|
||||
create_app_role "Admin" "Admin" "Administrator access to all features"
|
||||
create_app_role "Volunteer" "Volunteer" "Volunteer access to assigned tasks"
|
||||
create_app_role "Resource" "Resource" "Resource provider access"
|
||||
|
||||
echo -e "${GREEN}✅ App roles created${NC}"
|
||||
echo ""
|
||||
|
||||
# Create service principal
|
||||
echo -e "${BLUE}📝 Creating service principal...${NC}"
|
||||
SP_ID=$(az ad sp create --id "$APP_ID" --query "id" -o tsv 2>/dev/null || \
|
||||
az ad sp show --id "$APP_ID" --query "id" -o tsv)
|
||||
|
||||
echo -e "${GREEN}✅ Service principal created: $SP_ID${NC}"
|
||||
echo ""
|
||||
|
||||
# Grant admin consent (requires admin privileges)
|
||||
echo -e "${BLUE}📝 Granting admin consent for API permissions...${NC}"
|
||||
read -p "Do you have admin privileges to grant consent? (y/n): " HAS_ADMIN
|
||||
|
||||
if [ "$HAS_ADMIN" == "y" ]; then
|
||||
az ad app permission admin-consent --id "$APP_ID" && \
|
||||
echo -e "${GREEN}✅ Admin consent granted${NC}" || \
|
||||
echo -e "${YELLOW}⚠️ Could not grant admin consent. You may need to do this manually.${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Skipping admin consent. Please grant consent manually in Azure Portal.${NC}"
|
||||
echo " Go to: Azure Portal → Microsoft Entra ID → App registrations → $APP_NAME → API permissions → Grant admin consent"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Create client secret (optional)
|
||||
echo -e "${BLUE}📝 Client Secret Configuration...${NC}"
|
||||
read -p "Do you want to create a client secret? (y/n): " CREATE_SECRET
|
||||
|
||||
if [ "$CREATE_SECRET" == "y" ]; then
|
||||
SECRET_NAME="Miracles In Motion Secret $(date +%Y%m%d)"
|
||||
SECRET=$(az ad app credential reset --id "$APP_ID" --display-name "$SECRET_NAME" --years 2 --query "password" -o tsv)
|
||||
echo -e "${GREEN}✅ Client secret created${NC}"
|
||||
echo -e "${RED}⚠️ IMPORTANT: Save this secret now - it won't be shown again!${NC}"
|
||||
echo "Secret: $SECRET"
|
||||
echo ""
|
||||
read -p "Press Enter to continue after saving the secret..."
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Skipping client secret creation${NC}"
|
||||
SECRET=""
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Store configuration in Key Vault (if available)
|
||||
echo -e "${BLUE}📝 Storing configuration in Key Vault...${NC}"
|
||||
KEY_VAULT_NAME=$(az keyvault list --resource-group "$AZURE_RESOURCE_GROUP" --query "[0].name" -o tsv 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$KEY_VAULT_NAME" ]; then
|
||||
echo "Storing in Key Vault: $KEY_VAULT_NAME"
|
||||
|
||||
az keyvault secret set \
|
||||
--vault-name "$KEY_VAULT_NAME" \
|
||||
--name "azure-client-id" \
|
||||
--value "$APP_ID" > /dev/null 2>&1 && echo -e "${GREEN}✅ Client ID stored${NC}" || echo -e "${YELLOW}⚠️ Could not store Client ID${NC}"
|
||||
|
||||
az keyvault secret set \
|
||||
--vault-name "$KEY_VAULT_NAME" \
|
||||
--name "azure-tenant-id" \
|
||||
--value "$TENANT_ID" > /dev/null 2>&1 && echo -e "${GREEN}✅ Tenant ID stored${NC}" || echo -e "${YELLOW}⚠️ Could not store Tenant ID${NC}"
|
||||
|
||||
if [ -n "$SECRET" ]; then
|
||||
az keyvault secret set \
|
||||
--vault-name "$KEY_VAULT_NAME" \
|
||||
--name "azure-client-secret" \
|
||||
--value "$SECRET" > /dev/null 2>&1 && echo -e "${GREEN}✅ Client Secret stored${NC}" || echo -e "${YELLOW}⚠️ Could not store Client Secret${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Key Vault not found. Skipping secret storage.${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo -e "${GREEN}✅ MS Entra Setup Complete!${NC}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "Configuration Summary:"
|
||||
echo " App Registration ID: $APP_ID"
|
||||
echo " Tenant ID: $TENANT_ID"
|
||||
echo " Service Principal ID: $SP_ID"
|
||||
echo ""
|
||||
echo "Redirect URIs:"
|
||||
echo " - $PRODUCTION_URL"
|
||||
echo " - $WWW_URL"
|
||||
echo " - $FULL_STATIC_WEB_APP_URL"
|
||||
echo ""
|
||||
echo "App Roles:"
|
||||
echo " - Admin"
|
||||
echo " - Volunteer"
|
||||
echo " - Resource"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo "1. Assign users to app roles in Azure Portal"
|
||||
echo "2. Update staticwebapp.config.json with authentication configuration"
|
||||
echo "3. Update application code to use Azure AD authentication"
|
||||
echo "4. Test authentication flow"
|
||||
echo ""
|
||||
echo "Azure Portal Links:"
|
||||
echo " App Registration: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/$APP_ID"
|
||||
echo " API Permissions: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/$APP_ID"
|
||||
echo " App Roles: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/AppRoles/appId/$APP_ID"
|
||||
echo ""
|
||||
|
||||
# Export variables for use in other scripts
|
||||
cat > .azure-entra-config.env <<EOF
|
||||
# Azure AD Configuration
|
||||
AZURE_CLIENT_ID=$APP_ID
|
||||
AZURE_TENANT_ID=$TENANT_ID
|
||||
AZURE_CLIENT_SECRET=$SECRET
|
||||
AZURE_STATIC_WEB_APP_URL=$FULL_STATIC_WEB_APP_URL
|
||||
AZURE_PRODUCTION_URL=$PRODUCTION_URL
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✅ Configuration saved to .azure-entra-config.env${NC}"
|
||||
echo ""
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Automated Cloudflare Setup Script
|
||||
# Reads credentials from .env.production and configures Cloudflare automatically
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
DOMAIN="mim4u.org"
|
||||
STATIC_WEB_APP_NAME="mim-prod-igiay4-web"
|
||||
AZURE_RESOURCE_GROUP="rg-miraclesinmotion-prod"
|
||||
|
||||
echo -e "${GREEN}🌐 Automated Cloudflare Setup${NC}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
# Load environment variables from .env files
|
||||
ENV_FILES=(".env.production" ".env" "../.env.production" "../.env")
|
||||
CREDENTIALS_LOADED=false
|
||||
|
||||
for env_file in "${ENV_FILES[@]}"; do
|
||||
if [ -f "$env_file" ]; then
|
||||
echo -e "${GREEN}📋 Loading credentials from $env_file...${NC}"
|
||||
# Try different formats
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^CLOUDFLARE_API_TOKEN= ]] || [[ "$line" =~ ^CLOUDFLARE_ZONE_ID= ]]; then
|
||||
export "$line"
|
||||
CREDENTIALS_LOADED=true
|
||||
fi
|
||||
done < "$env_file"
|
||||
|
||||
# Also try with export command
|
||||
set -a
|
||||
source "$env_file" 2>/dev/null || true
|
||||
set +a
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if credentials are already set in environment
|
||||
if [ -n "$CLOUDFLARE_API_TOKEN" ] && [ -n "$CLOUDFLARE_ZONE_ID" ]; then
|
||||
CREDENTIALS_LOADED=true
|
||||
fi
|
||||
|
||||
# Check if credentials are set
|
||||
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ZONE_ID" ]; then
|
||||
echo -e "${YELLOW}⚠️ Cloudflare credentials not found in env files${NC}"
|
||||
echo "Checking environment variables..."
|
||||
|
||||
# Final check - maybe they're already exported
|
||||
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ZONE_ID" ]; then
|
||||
echo -e "${RED}❌ Cloudflare credentials not found${NC}"
|
||||
echo "Please set: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID"
|
||||
echo "Or add them to .env.production file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Credentials loaded${NC}"
|
||||
echo "Zone ID: ${CLOUDFLARE_ZONE_ID:0:15}..."
|
||||
echo ""
|
||||
|
||||
# Get Azure Static Web App default hostname
|
||||
echo -e "${GREEN}📋 Getting Azure Static Web App information...${NC}"
|
||||
AZURE_STATIC_WEB_APP_URL=$(az staticwebapp show \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--query "defaultHostname" -o tsv 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$AZURE_STATIC_WEB_APP_URL" ]; then
|
||||
echo -e "${RED}❌ Could not find Static Web App${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Found Static Web App: ${AZURE_STATIC_WEB_APP_URL}${NC}"
|
||||
echo ""
|
||||
|
||||
# Verify Cloudflare API access
|
||||
echo -e "${GREEN}🔐 Verifying Cloudflare API access...${NC}"
|
||||
ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
ZONE_SUCCESS=$(echo "$ZONE_RESPONSE" | grep -o '"success":true' || echo "")
|
||||
|
||||
if [ -z "$ZONE_SUCCESS" ]; then
|
||||
echo -e "${RED}❌ Failed to authenticate with Cloudflare API${NC}"
|
||||
echo "Response: $ZONE_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ZONE_NAME=$(echo "$ZONE_RESPONSE" | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
|
||||
echo -e "${GREEN}✅ Authenticated with Cloudflare${NC}"
|
||||
echo "Zone: $ZONE_NAME"
|
||||
echo ""
|
||||
|
||||
# Function to create or update DNS record
|
||||
create_dns_record() {
|
||||
local record_type=$1
|
||||
local record_name=$2
|
||||
local record_content=$3
|
||||
local proxy=$4
|
||||
|
||||
echo -n "Configuring DNS: $record_name.$DOMAIN -> $record_content... "
|
||||
|
||||
# Check if record exists
|
||||
EXISTING_RECORD=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records?type=$record_type&name=$record_name.$DOMAIN" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
RECORD_ID=$(echo "$EXISTING_RECORD" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
|
||||
if [ -n "$RECORD_ID" ] && [ "$RECORD_ID" != "null" ]; then
|
||||
# Update existing record
|
||||
DATA=$(cat <<EOF
|
||||
{
|
||||
"type": "$record_type",
|
||||
"name": "$record_name",
|
||||
"content": "$record_content",
|
||||
"proxied": $proxy,
|
||||
"ttl": 1
|
||||
}
|
||||
EOF
|
||||
)
|
||||
RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records/$RECORD_ID" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$DATA")
|
||||
else
|
||||
# Create new record
|
||||
DATA=$(cat <<EOF
|
||||
{
|
||||
"type": "$record_type",
|
||||
"name": "$record_name",
|
||||
"content": "$record_content",
|
||||
"proxied": $proxy,
|
||||
"ttl": 1
|
||||
}
|
||||
EOF
|
||||
)
|
||||
RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$DATA")
|
||||
fi
|
||||
|
||||
SUCCESS=$(echo "$RESPONSE" | grep -o '"success":true' || echo "")
|
||||
if [ -n "$SUCCESS" ]; then
|
||||
echo -e "${GREEN}✅${NC}"
|
||||
return 0
|
||||
else
|
||||
ERRORS=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | cut -d'"' -f4 | head -1)
|
||||
echo -e "${YELLOW}⚠️ $ERRORS${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create DNS records
|
||||
echo -e "${GREEN}📝 Configuring DNS Records...${NC}"
|
||||
create_dns_record "CNAME" "www" "$AZURE_STATIC_WEB_APP_URL" "true"
|
||||
create_dns_record "CNAME" "@" "$AZURE_STATIC_WEB_APP_URL" "true"
|
||||
echo ""
|
||||
|
||||
# Configure SSL/TLS settings
|
||||
echo -e "${GREEN}🔒 Configuring SSL/TLS...${NC}"
|
||||
|
||||
# Set SSL mode to Full
|
||||
SSL_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/ssl" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"full"}')
|
||||
|
||||
if echo "$SSL_RESPONSE" | grep -q '"success":true'; then
|
||||
echo -e "${GREEN}✅ SSL mode set to Full${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not update SSL settings${NC}"
|
||||
fi
|
||||
|
||||
# Enable Always Use HTTPS
|
||||
HTTPS_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/always_use_https" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"on"}')
|
||||
|
||||
if echo "$HTTPS_RESPONSE" | grep -q '"success":true'; then
|
||||
echo -e "${GREEN}✅ Always Use HTTPS enabled${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not enable Always Use HTTPS${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Configure Security Settings
|
||||
echo -e "${GREEN}🛡️ Configuring Security Settings...${NC}"
|
||||
|
||||
# Set security level to Medium
|
||||
SECURITY_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/security_level" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"medium"}')
|
||||
|
||||
if echo "$SECURITY_RESPONSE" | grep -q '"success":true'; then
|
||||
echo -e "${GREEN}✅ Security level set to Medium${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not update security level${NC}"
|
||||
fi
|
||||
|
||||
# Enable Browser Integrity Check
|
||||
BROWSER_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/browser_check" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"on"}')
|
||||
|
||||
if echo "$BROWSER_RESPONSE" | grep -q '"success":true'; then
|
||||
echo -e "${GREEN}✅ Browser Integrity Check enabled${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not enable browser check${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Configure Speed Settings
|
||||
echo -e "${GREEN}⚡ Configuring Speed Settings...${NC}"
|
||||
|
||||
# Enable Minification
|
||||
MINIFY_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/minify" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":{"css":"on","html":"on","js":"on"}}')
|
||||
|
||||
if echo "$MINIFY_RESPONSE" | grep -q '"success":true'; then
|
||||
echo -e "${GREEN}✅ Minification enabled${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not enable minification${NC}"
|
||||
fi
|
||||
|
||||
# Enable Brotli compression
|
||||
BROTLI_RESPONSE=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/brotli" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"on"}')
|
||||
|
||||
if echo "$BROTLI_RESPONSE" | grep -q '"success":true'; then
|
||||
echo -e "${GREEN}✅ Brotli compression enabled${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not enable Brotli${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Add custom domain to Azure Static Web App
|
||||
echo -e "${GREEN}🔗 Adding Custom Domain to Azure Static Web App...${NC}"
|
||||
|
||||
# For apex domain (may require TXT validation)
|
||||
az staticwebapp hostname set \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--hostname "$DOMAIN" 2>/dev/null && \
|
||||
echo -e "${GREEN}✅ Custom domain $DOMAIN added${NC}" || \
|
||||
echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}"
|
||||
|
||||
# For www subdomain
|
||||
az staticwebapp hostname set \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--hostname "www.$DOMAIN" 2>/dev/null && \
|
||||
echo -e "${GREEN}✅ Custom domain www.$DOMAIN added${NC}" || \
|
||||
echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}"
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo -e "${GREEN}✅ Cloudflare Setup Complete!${NC}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "Configuration Summary:"
|
||||
echo " Domain: $DOMAIN"
|
||||
echo " Static Web App: $AZURE_STATIC_WEB_APP_URL"
|
||||
echo ""
|
||||
echo "DNS Records:"
|
||||
echo " ✅ www.$DOMAIN -> $AZURE_STATIC_WEB_APP_URL (Proxied)"
|
||||
echo " ✅ $DOMAIN -> $AZURE_STATIC_WEB_APP_URL (Proxied)"
|
||||
echo ""
|
||||
echo "Cloudflare Settings:"
|
||||
echo " ✅ SSL Mode: Full"
|
||||
echo " ✅ Always Use HTTPS: Enabled"
|
||||
echo " ✅ Security Level: Medium"
|
||||
echo " ✅ Browser Integrity Check: Enabled"
|
||||
echo " ✅ Minification: Enabled (JS, CSS, HTML)"
|
||||
echo " ✅ Brotli Compression: Enabled"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo " 1. Wait for DNS propagation (usually 5-30 minutes)"
|
||||
echo " 2. Verify SSL certificates are provisioned (1-24 hours)"
|
||||
echo " 3. Test the website at https://$DOMAIN"
|
||||
echo " 4. Monitor Cloudflare analytics"
|
||||
echo ""
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
# Cloudflare Setup Script for Miracles In Motion (PowerShell)
|
||||
# This script helps configure Cloudflare for the production deployment
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Domain = "mim4u.org",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$StaticWebAppName = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$AzureResourceGroup = "rg-miraclesinmotion-prod",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$CloudflareApiToken = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$CloudflareZoneId = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "🌐 Cloudflare Setup Script" -ForegroundColor Green
|
||||
Write-Host "==================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if (-not (Get-Command "az" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "❌ Azure CLI not found. Please install it first." -ForegroundColor Red
|
||||
Write-Host "Install from: https://docs.microsoft.com/cli/azure/install-azure-cli" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get Azure Static Web App default hostname
|
||||
Write-Host "📋 Getting Azure Static Web App information..." -ForegroundColor Cyan
|
||||
|
||||
if ([string]::IsNullOrEmpty($StaticWebAppName)) {
|
||||
# Try to find Static Web App
|
||||
$swa = az staticwebapp list --resource-group $AzureResourceGroup --output json | ConvertFrom-Json | Select-Object -First 1
|
||||
if ($swa) {
|
||||
$StaticWebAppName = $swa.name
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($StaticWebAppName)) {
|
||||
Write-Host "❌ Static Web App name not specified and could not be found." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$azureStaticWebAppUrl = az staticwebapp show `
|
||||
--name $StaticWebAppName `
|
||||
--resource-group $AzureResourceGroup `
|
||||
--query "defaultHostname" -o tsv
|
||||
|
||||
if ([string]::IsNullOrEmpty($azureStaticWebAppUrl)) {
|
||||
Write-Host "❌ Could not find Static Web App." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Found Static Web App: $azureStaticWebAppUrl" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Get Cloudflare API Token
|
||||
if ([string]::IsNullOrEmpty($CloudflareApiToken)) {
|
||||
$CloudflareApiToken = Read-Host "Enter your Cloudflare API Token"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($CloudflareApiToken)) {
|
||||
Write-Host "❌ Cloudflare API Token is required." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get Cloudflare Zone ID
|
||||
if ([string]::IsNullOrEmpty($CloudflareZoneId)) {
|
||||
Write-Host "Looking up Zone ID for $Domain..." -ForegroundColor Cyan
|
||||
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $CloudflareApiToken"
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
|
||||
$zoneResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones?name=$Domain" -Method Get -Headers $headers
|
||||
|
||||
if ($zoneResponse.success -and $zoneResponse.result.Count -gt 0) {
|
||||
$CloudflareZoneId = $zoneResponse.result[0].id
|
||||
Write-Host "✅ Zone ID: $CloudflareZoneId" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "❌ Could not find Zone ID for $Domain" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Function to create DNS record
|
||||
function New-CloudflareDnsRecord {
|
||||
param(
|
||||
[string]$RecordType,
|
||||
[string]$RecordName,
|
||||
[string]$RecordContent,
|
||||
[bool]$Proxied = $true
|
||||
)
|
||||
|
||||
Write-Host "Creating DNS record: $RecordName.$Domain -> $RecordContent" -ForegroundColor Cyan
|
||||
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $CloudflareApiToken"
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
|
||||
$body = @{
|
||||
type = $RecordType
|
||||
name = $RecordName
|
||||
content = $RecordContent
|
||||
proxied = $Proxied
|
||||
ttl = 1
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/dns_records" -Method Post -Headers $headers -Body $body
|
||||
|
||||
if ($response.success) {
|
||||
Write-Host "✅ DNS record created successfully" -ForegroundColor Green
|
||||
return $true
|
||||
} else {
|
||||
$errors = $response.errors | ForEach-Object { $_.message } -Join ", "
|
||||
Write-Host "⚠️ DNS record may already exist or error: $errors" -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Error creating DNS record: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Create CNAME records
|
||||
Write-Host "📝 Creating DNS Records..." -ForegroundColor Green
|
||||
New-CloudflareDnsRecord -RecordType "CNAME" -RecordName "www" -RecordContent $azureStaticWebAppUrl -Proxied $true
|
||||
New-CloudflareDnsRecord -RecordType "CNAME" -RecordName "@" -RecordContent $azureStaticWebAppUrl -Proxied $true
|
||||
Write-Host ""
|
||||
|
||||
# Configure SSL/TLS settings
|
||||
Write-Host "🔒 Configuring SSL/TLS..." -ForegroundColor Green
|
||||
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $CloudflareApiToken"
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
|
||||
$sslBody = @{
|
||||
value = "full"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$sslResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/ssl" -Method Patch -Headers $headers -Body $sslBody
|
||||
if ($sslResponse.success) {
|
||||
Write-Host "✅ SSL mode set to Full" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not update SSL settings: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$httpsBody = @{
|
||||
value = "on"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$httpsResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/always_use_https" -Method Patch -Headers $headers -Body $httpsBody
|
||||
if ($httpsResponse.success) {
|
||||
Write-Host "✅ Always Use HTTPS enabled" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not enable Always Use HTTPS: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Configure Security Settings
|
||||
Write-Host "🛡️ Configuring Security Settings..." -ForegroundColor Green
|
||||
|
||||
$securityBody = @{
|
||||
value = "medium"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$securityResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/security_level" -Method Patch -Headers $headers -Body $securityBody
|
||||
if ($securityResponse.success) {
|
||||
Write-Host "✅ Security level set to Medium" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not update security level: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$browserCheckBody = @{
|
||||
value = "on"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$browserCheckResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/browser_check" -Method Patch -Headers $headers -Body $browserCheckBody
|
||||
if ($browserCheckResponse.success) {
|
||||
Write-Host "✅ Browser Integrity Check enabled" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not enable browser check: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Configure Speed Settings
|
||||
Write-Host "⚡ Configuring Speed Settings..." -ForegroundColor Green
|
||||
|
||||
$minifyBody = @{
|
||||
value = @{
|
||||
css = "on"
|
||||
html = "on"
|
||||
js = "on"
|
||||
}
|
||||
} | ConvertTo-Json -Depth 3
|
||||
|
||||
try {
|
||||
$minifyResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/minify" -Method Patch -Headers $headers -Body $minifyBody
|
||||
if ($minifyResponse.success) {
|
||||
Write-Host "✅ Minification enabled" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not enable minification: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$brotliBody = @{
|
||||
value = "on"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$brotliResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/brotli" -Method Patch -Headers $headers -Body $brotliBody
|
||||
if ($brotliResponse.success) {
|
||||
Write-Host "✅ Brotli compression enabled" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not enable Brotli: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Add custom domain to Azure Static Web App
|
||||
Write-Host "🔗 Adding Custom Domain to Azure Static Web App..." -ForegroundColor Green
|
||||
|
||||
try {
|
||||
az staticwebapp hostname set `
|
||||
--name $StaticWebAppName `
|
||||
--resource-group $AzureResourceGroup `
|
||||
--hostname $Domain 2>$null | Out-Null
|
||||
Write-Host "✅ Custom domain $Domain added" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "⚠️ Domain may already be added or DNS not ready" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
try {
|
||||
az staticwebapp hostname set `
|
||||
--name $StaticWebAppName `
|
||||
--resource-group $AzureResourceGroup `
|
||||
--hostname "www.$Domain" 2>$null | Out-Null
|
||||
Write-Host "✅ Custom domain www.$Domain added" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "⚠️ Domain may already be added or DNS not ready" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Summary
|
||||
Write-Host "✅ Cloudflare Setup Complete!" -ForegroundColor Green
|
||||
Write-Host "==================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next Steps:"
|
||||
Write-Host "1. Verify DNS propagation (may take 24-48 hours)"
|
||||
Write-Host "2. Verify SSL certificates are provisioned"
|
||||
Write-Host "3. Test the website at https://$Domain"
|
||||
Write-Host "4. Monitor Cloudflare analytics"
|
||||
Write-Host ""
|
||||
Write-Host "DNS Records Created:"
|
||||
Write-Host " - www.$Domain -> $azureStaticWebAppUrl"
|
||||
Write-Host " - $Domain -> $azureStaticWebAppUrl"
|
||||
Write-Host ""
|
||||
Write-Host "Cloudflare Settings:"
|
||||
Write-Host " - SSL Mode: Full (strict)"
|
||||
Write-Host " - Always Use HTTPS: Enabled"
|
||||
Write-Host " - Security Level: Medium"
|
||||
Write-Host " - Minification: Enabled"
|
||||
Write-Host " - Brotli Compression: Enabled"
|
||||
Write-Host ""
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Cloudflare Setup Script for Miracles In Motion
|
||||
# This script helps configure Cloudflare for the production deployment
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
DOMAIN="miraclesinmotion.org"
|
||||
STATIC_WEB_APP_NAME="${STATIC_WEB_APP_NAME:-mim-prod-web}"
|
||||
AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-rg-miraclesinmotion-prod}"
|
||||
|
||||
echo -e "${GREEN}🌐 Cloudflare Setup Script${NC}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
# Check if Cloudflare CLI is installed
|
||||
if ! command -v cloudflared &> /dev/null; then
|
||||
echo -e "${YELLOW}⚠️ Cloudflare CLI (cloudflared) not found.${NC}"
|
||||
echo "Install it from: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check if jq is installed
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo -e "${YELLOW}⚠️ jq not found. Installing...${NC}"
|
||||
# Try to install jq (this may vary by OS)
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
sudo apt-get update && sudo apt-get install -y jq
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
brew install jq
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get Azure Static Web App default hostname
|
||||
echo -e "${GREEN}📋 Getting Azure Static Web App information...${NC}"
|
||||
AZURE_STATIC_WEB_APP_URL=$(az staticwebapp show \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--query "defaultHostname" -o tsv 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$AZURE_STATIC_WEB_APP_URL" ]; then
|
||||
echo -e "${RED}❌ Could not find Static Web App. Please check the name and resource group.${NC}"
|
||||
echo "Usage: STATIC_WEB_APP_NAME=your-app-name AZURE_RESOURCE_GROUP=your-rg ./setup-cloudflare.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Found Static Web App: ${AZURE_STATIC_WEB_APP_URL}${NC}"
|
||||
echo ""
|
||||
|
||||
# Prompt for Cloudflare API credentials
|
||||
echo -e "${YELLOW}📝 Cloudflare API Configuration${NC}"
|
||||
echo "You need a Cloudflare API token with the following permissions:"
|
||||
echo " - Zone:Read, DNS:Edit, SSL:Edit, Page Rules:Edit"
|
||||
echo ""
|
||||
read -p "Enter your Cloudflare API Token: " CF_API_TOKEN
|
||||
read -p "Enter your Cloudflare Zone ID (or we'll look it up): " CF_ZONE_ID
|
||||
|
||||
if [ -z "$CF_ZONE_ID" ]; then
|
||||
echo "Looking up Zone ID for $DOMAIN..."
|
||||
CF_ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" | jq -r '.result[0].id')
|
||||
|
||||
if [ -z "$CF_ZONE_ID" ] || [ "$CF_ZONE_ID" == "null" ]; then
|
||||
echo -e "${RED}❌ Could not find Zone ID for $DOMAIN${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Zone ID: $CF_ZONE_ID${NC}"
|
||||
echo ""
|
||||
|
||||
# Function to create DNS record
|
||||
create_dns_record() {
|
||||
local record_type=$1
|
||||
local record_name=$2
|
||||
local record_content=$3
|
||||
local proxy=$4
|
||||
|
||||
echo "Creating DNS record: $record_name.$DOMAIN -> $record_content"
|
||||
|
||||
local data=$(jq -n \
|
||||
--arg type "$record_type" \
|
||||
--arg name "$record_name" \
|
||||
--arg content "$record_content" \
|
||||
--argjson proxied "$proxy" \
|
||||
'{
|
||||
type: $type,
|
||||
name: $name,
|
||||
content: $content,
|
||||
proxied: $proxied,
|
||||
ttl: 1
|
||||
}')
|
||||
|
||||
local response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$data")
|
||||
|
||||
local success=$(echo "$response" | jq -r '.success')
|
||||
if [ "$success" == "true" ]; then
|
||||
echo -e "${GREEN}✅ DNS record created successfully${NC}"
|
||||
else
|
||||
local errors=$(echo "$response" | jq -r '.errors[]?.message' | tr '\n' ' ')
|
||||
echo -e "${YELLOW}⚠️ DNS record may already exist or error: $errors${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create CNAME records
|
||||
echo -e "${GREEN}📝 Creating DNS Records...${NC}"
|
||||
create_dns_record "CNAME" "www" "$AZURE_STATIC_WEB_APP_URL" "true"
|
||||
create_dns_record "CNAME" "@" "$AZURE_STATIC_WEB_APP_URL" "true"
|
||||
echo ""
|
||||
|
||||
# Configure SSL/TLS settings
|
||||
echo -e "${GREEN}🔒 Configuring SSL/TLS...${NC}"
|
||||
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/ssl" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"full"}' | jq -r '.success' && echo -e "${GREEN}✅ SSL mode set to Full${NC}" || echo -e "${YELLOW}⚠️ Could not update SSL settings${NC}"
|
||||
|
||||
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/always_use_https" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"on"}' | jq -r '.success' && echo -e "${GREEN}✅ Always Use HTTPS enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable Always Use HTTPS${NC}"
|
||||
echo ""
|
||||
|
||||
# Create Page Rules
|
||||
echo -e "${GREEN}📋 Creating Page Rules...${NC}"
|
||||
|
||||
# Rule 1: Force HTTPS
|
||||
create_page_rule() {
|
||||
local url=$1
|
||||
local settings=$2
|
||||
local rule_name=$3
|
||||
|
||||
local data=$(jq -n \
|
||||
--arg url "$url" \
|
||||
--argjson settings "$settings" \
|
||||
'{
|
||||
targets: [
|
||||
{
|
||||
target: "url",
|
||||
constraint: {
|
||||
operator: "matches",
|
||||
value: $url
|
||||
}
|
||||
}
|
||||
],
|
||||
actions: $settings,
|
||||
priority: 1,
|
||||
status: "active"
|
||||
}')
|
||||
|
||||
local response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/pagerules" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$data")
|
||||
|
||||
local success=$(echo "$response" | jq -r '.success')
|
||||
if [ "$success" == "true" ]; then
|
||||
echo -e "${GREEN}✅ Page rule '$rule_name' created${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not create page rule '$rule_name'${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create page rules
|
||||
HTTPS_SETTINGS='[{"id": "always_use_https"}]'
|
||||
create_page_rule "*${DOMAIN}/*" "$HTTPS_SETTINGS" "Force HTTPS"
|
||||
echo ""
|
||||
|
||||
# Configure Security Settings
|
||||
echo -e "${GREEN}🛡️ Configuring Security Settings...${NC}"
|
||||
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/security_level" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"medium"}' | jq -r '.success' && echo -e "${GREEN}✅ Security level set to Medium${NC}" || echo -e "${YELLOW}⚠️ Could not update security level${NC}"
|
||||
|
||||
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/browser_check" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"on"}' | jq -r '.success' && echo -e "${GREEN}✅ Browser Integrity Check enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable browser check${NC}"
|
||||
echo ""
|
||||
|
||||
# Configure Speed Settings
|
||||
echo -e "${GREEN}⚡ Configuring Speed Settings...${NC}"
|
||||
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/minify" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":{"css":"on","html":"on","js":"on"}}' | jq -r '.success' && echo -e "${GREEN}✅ Minification enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable minification${NC}"
|
||||
|
||||
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/brotli" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"value":"on"}' | jq -r '.success' && echo -e "${GREEN}✅ Brotli compression enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable Brotli${NC}"
|
||||
echo ""
|
||||
|
||||
# Add custom domain to Azure Static Web App
|
||||
echo -e "${GREEN}🔗 Adding Custom Domain to Azure Static Web App...${NC}"
|
||||
az staticwebapp hostname set \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--hostname "$DOMAIN" 2>/dev/null && echo -e "${GREEN}✅ Custom domain $DOMAIN added${NC}" || echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}"
|
||||
|
||||
az staticwebapp hostname set \
|
||||
--name "$STATIC_WEB_APP_NAME" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--hostname "www.$DOMAIN" 2>/dev/null && echo -e "${GREEN}✅ Custom domain www.$DOMAIN added${NC}" || echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}"
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo -e "${GREEN}✅ Cloudflare Setup Complete!${NC}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo "1. Verify DNS propagation (may take 24-48 hours)"
|
||||
echo "2. Verify SSL certificates are provisioned"
|
||||
echo "3. Test the website at https://$DOMAIN"
|
||||
echo "4. Monitor Cloudflare analytics"
|
||||
echo ""
|
||||
echo "DNS Records Created:"
|
||||
echo " - www.$DOMAIN -> $AZURE_STATIC_WEB_APP_URL"
|
||||
echo " - $DOMAIN -> $AZURE_STATIC_WEB_APP_URL"
|
||||
echo ""
|
||||
echo "Cloudflare Settings:"
|
||||
echo " - SSL Mode: Full (strict)"
|
||||
echo " - Always Use HTTPS: Enabled"
|
||||
echo " - Security Level: Medium"
|
||||
echo " - Minification: Enabled"
|
||||
echo " - Brotli Compression: Enabled"
|
||||
echo ""
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
# Script to store secrets in Azure Key Vault
|
||||
# This script reads from .env.production and stores secrets in Key Vault
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$ResourceGroupName = "rg-miraclesinmotion-prod",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$KeyVaultName = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$CreateKeyVault = $false
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "🔐 Storing Secrets in Azure Key Vault" -ForegroundColor Green
|
||||
Write-Host "====================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check if logged in to Azure
|
||||
$account = az account show --output json 2>$null | ConvertFrom-Json
|
||||
if (-not $account) {
|
||||
Write-Host "❌ Not logged in to Azure. Please run: az login" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ Logged in to Azure" -ForegroundColor Green
|
||||
Write-Host " Subscription: $($account.name)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Get Key Vault name
|
||||
if ([string]::IsNullOrEmpty($KeyVaultName)) {
|
||||
$KeyVaultName = az keyvault list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -ExpandProperty name
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($KeyVaultName)) {
|
||||
if ($CreateKeyVault) {
|
||||
$KeyVaultName = "mim-prod-$(Get-Random -Minimum 1000 -Maximum 9999)-kv"
|
||||
Write-Host "📦 Creating Key Vault: $KeyVaultName" -ForegroundColor Cyan
|
||||
|
||||
az keyvault create `
|
||||
--name $KeyVaultName `
|
||||
--resource-group $ResourceGroupName `
|
||||
--location $(az group show --name $ResourceGroupName --query location -o tsv) `
|
||||
--sku standard `
|
||||
--enable-rbac-authorization true `
|
||||
--enable-soft-delete true `
|
||||
--retention-days 90 | Out-Null
|
||||
|
||||
Write-Host "✅ Key Vault created" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "❌ Key Vault not found. Run with -CreateKeyVault to create one." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "✅ Found Key Vault: $KeyVaultName" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Check if .env.production exists
|
||||
$envFile = ".env.production"
|
||||
if (-not (Test-Path $envFile)) {
|
||||
Write-Host "❌ .env.production file not found. Please create it first." -ForegroundColor Red
|
||||
Write-Host " Run: .\scripts\populate-env.ps1" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "📄 Reading secrets from $envFile..." -ForegroundColor Cyan
|
||||
|
||||
# Read .env file and parse key-value pairs
|
||||
$envContent = Get-Content $envFile -Raw
|
||||
$secrets = @{}
|
||||
|
||||
# Parse environment variables (simple parser - handles KEY=VALUE format)
|
||||
$lines = $envContent -split "`n"
|
||||
foreach ($line in $lines) {
|
||||
$line = $line.Trim()
|
||||
if ($line -and -not $line.StartsWith("#") -and $line -match "^([^=]+)=(.*)$") {
|
||||
$key = $matches[1].Trim()
|
||||
$value = $matches[2].Trim()
|
||||
|
||||
# Skip empty values and placeholders
|
||||
if ($value -and $value -notmatch "^(your-|YOUR_|placeholder)" -and $value -ne "") {
|
||||
$secrets[$key] = $value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "✅ Found $($secrets.Count) secrets to store" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Define which secrets to store in Key Vault
|
||||
$secretsToStore = @(
|
||||
@{Name="azure-tenant-id"; EnvKey="AZURE_TENANT_ID"; Required=$true},
|
||||
@{Name="azure-client-id"; EnvKey="AZURE_CLIENT_ID"; Required=$false},
|
||||
@{Name="azure-client-secret"; EnvKey="AZURE_CLIENT_SECRET"; Required=$false},
|
||||
@{Name="stripe-publishable-key"; EnvKey="VITE_STRIPE_PUBLISHABLE_KEY"; Required=$false},
|
||||
@{Name="stripe-secret-key"; EnvKey="STRIPE_SECRET_KEY"; Required=$false},
|
||||
@{Name="stripe-webhook-secret"; EnvKey="STRIPE_WEBHOOK_SECRET"; Required=$false},
|
||||
@{Name="cosmos-endpoint"; EnvKey="COSMOS_ENDPOINT"; Required=$false},
|
||||
@{Name="cosmos-key"; EnvKey="COSMOS_KEY"; Required=$false},
|
||||
@{Name="cosmos-database-name"; EnvKey="COSMOS_DATABASE_NAME"; Required=$false},
|
||||
@{Name="app-insights-connection-string"; EnvKey="APPLICATIONINSIGHTS_CONNECTION_STRING"; Required=$false},
|
||||
@{Name="signalr-connection-string"; EnvKey="SIGNALR_CONNECTION_STRING"; Required=$false},
|
||||
@{Name="cloudflare-zone-id"; EnvKey="CLOUDFLARE_ZONE_ID"; Required=$false},
|
||||
@{Name="cloudflare-api-token"; EnvKey="CLOUDFLARE_API_TOKEN"; Required=$false},
|
||||
@{Name="salesforce-client-id"; EnvKey="SALESFORCE_CLIENT_ID"; Required=$false},
|
||||
@{Name="salesforce-client-secret"; EnvKey="SALESFORCE_CLIENT_SECRET"; Required=$false},
|
||||
@{Name="smtp-password"; EnvKey="SMTP_PASSWORD"; Required=$false},
|
||||
@{Name="session-secret"; EnvKey="SESSION_SECRET"; Required=$false},
|
||||
@{Name="jwt-secret"; EnvKey="JWT_SECRET"; Required=$false},
|
||||
@{Name="encryption-key"; EnvKey="ENCRYPTION_KEY"; Required=$false}
|
||||
)
|
||||
|
||||
$storedCount = 0
|
||||
$skippedCount = 0
|
||||
$errorCount = 0
|
||||
|
||||
Write-Host "📦 Storing secrets in Key Vault..." -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
foreach ($secretDef in $secretsToStore) {
|
||||
$secretName = $secretDef.Name
|
||||
$envKey = $secretDef.EnvKey
|
||||
$required = $secretDef.Required
|
||||
|
||||
if ($secrets.ContainsKey($envKey)) {
|
||||
$secretValue = $secrets[$envKey]
|
||||
|
||||
try {
|
||||
Write-Host " Storing: $secretName" -ForegroundColor Gray
|
||||
az keyvault secret set `
|
||||
--vault-name $KeyVaultName `
|
||||
--name $secretName `
|
||||
--value $secretValue 2>$null | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " ✅ Stored successfully" -ForegroundColor Green
|
||||
$storedCount++
|
||||
} else {
|
||||
Write-Host " ⚠️ Failed to store (may need RBAC permissions)" -ForegroundColor Yellow
|
||||
$errorCount++
|
||||
}
|
||||
} catch {
|
||||
Write-Host " ❌ Error: $($_.Exception.Message)" -ForegroundColor Red
|
||||
$errorCount++
|
||||
}
|
||||
} else {
|
||||
if ($required) {
|
||||
Write-Host " ⚠️ Missing required secret: $secretName ($envKey)" -ForegroundColor Yellow
|
||||
$skippedCount++
|
||||
} else {
|
||||
Write-Host " ⏭️ Skipping: $secretName (not in .env file)" -ForegroundColor Gray
|
||||
$skippedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "📊 Summary:" -ForegroundColor Cyan
|
||||
Write-Host " ✅ Stored: $storedCount" -ForegroundColor Green
|
||||
Write-Host " ⏭️ Skipped: $skippedCount" -ForegroundColor Yellow
|
||||
Write-Host " ❌ Errors: $errorCount" -ForegroundColor $(if ($errorCount -gt 0) { "Red" } else { "Green" })
|
||||
Write-Host ""
|
||||
|
||||
# Prompt for additional secrets
|
||||
Write-Host "💡 Additional Secrets" -ForegroundColor Cyan
|
||||
Write-Host "You can manually add more secrets using:" -ForegroundColor Gray
|
||||
Write-Host " az keyvault secret set --vault-name $KeyVaultName --name <secret-name> --value <secret-value>" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
# Show how to retrieve secrets
|
||||
Write-Host "📖 Retrieving Secrets" -ForegroundColor Cyan
|
||||
Write-Host "To retrieve a secret:" -ForegroundColor Gray
|
||||
Write-Host " az keyvault secret show --vault-name $KeyVaultName --name <secret-name> --query value -o tsv" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
# Show Key Vault URL
|
||||
$keyVaultUrl = "https://$KeyVaultName.vault.azure.net/"
|
||||
Write-Host "🔗 Key Vault URL: $keyVaultUrl" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
if ($errorCount -gt 0) {
|
||||
Write-Host "⚠️ Some secrets failed to store. You may need to:" -ForegroundColor Yellow
|
||||
Write-Host "1. Grant yourself 'Key Vault Secrets Officer' role on the Key Vault" -ForegroundColor White
|
||||
Write-Host "2. Or use Azure Portal to manually add secrets" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Grant role command:" -ForegroundColor Cyan
|
||||
$currentUser = az ad signed-in-user show --query id -o tsv
|
||||
Write-Host " az role assignment create --role 'Key Vault Secrets Officer' --assignee $currentUser --scope /subscriptions/$($account.id)/resourceGroups/$ResourceGroupName/providers/Microsoft.KeyVault/vaults/$KeyVaultName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "✅ Done!" -ForegroundColor Green
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Deployment Testing Script for Miracles in Motion
|
||||
# Tests all endpoints and verifies deployment status
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
STATIC_WEB_APP_URL="https://lemon-water-015cb3010.3.azurestaticapps.net"
|
||||
FUNCTION_APP_URL="https://mim-prod-igiay4-func.azurewebsites.net"
|
||||
RESOURCE_GROUP="rg-miraclesinmotion-prod"
|
||||
|
||||
echo -e "${GREEN}🧪 Starting Deployment Tests${NC}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
# Test counter
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
# Function to test endpoint
|
||||
test_endpoint() {
|
||||
local url=$1
|
||||
local name=$2
|
||||
local expected_code=${3:-200}
|
||||
|
||||
echo -n "Testing $name... "
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url" || echo "000")
|
||||
|
||||
if [ "$HTTP_CODE" = "$expected_code" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC} (HTTP $HTTP_CODE)"
|
||||
((TESTS_PASSED++))
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC} (HTTP $HTTP_CODE, expected $expected_code)"
|
||||
((TESTS_FAILED++))
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test Static Web App
|
||||
echo -e "${YELLOW}📱 Testing Static Web App${NC}"
|
||||
test_endpoint "$STATIC_WEB_APP_URL" "Static Web App Homepage"
|
||||
test_endpoint "$STATIC_WEB_APP_URL/index.html" "Static Web App Index"
|
||||
test_endpoint "$STATIC_WEB_APP_URL/manifest.webmanifest" "PWA Manifest" 200
|
||||
|
||||
# Test Function App
|
||||
echo ""
|
||||
echo -e "${YELLOW}⚡ Testing Function App${NC}"
|
||||
test_endpoint "$FUNCTION_APP_URL" "Function App Homepage"
|
||||
test_endpoint "$FUNCTION_APP_URL/api/health" "Function App Health Check" 200
|
||||
|
||||
# Test Azure Resources
|
||||
echo ""
|
||||
echo -e "${YELLOW}☁️ Testing Azure Resources${NC}"
|
||||
|
||||
# Check Static Web App status
|
||||
echo -n "Checking Static Web App status... "
|
||||
SWA_STATUS=$(az staticwebapp show --name mim-prod-igiay4-web --resource-group $RESOURCE_GROUP --query "properties.provisioningState" -o tsv 2>/dev/null || echo "Unknown")
|
||||
if [ "$SWA_STATUS" = "Succeeded" ] || [ "$SWA_STATUS" = "Ready" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC} (Status: $SWA_STATUS)"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ WARNING${NC} (Status: $SWA_STATUS)"
|
||||
fi
|
||||
|
||||
# Check Function App status
|
||||
echo -n "Checking Function App status... "
|
||||
FA_STATUS=$(az functionapp show --name mim-prod-igiay4-func --resource-group $RESOURCE_GROUP --query "state" -o tsv 2>/dev/null || echo "Unknown")
|
||||
if [ "$FA_STATUS" = "Running" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC} (Status: $FA_STATUS)"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC} (Status: $FA_STATUS)"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
|
||||
# Check Key Vault
|
||||
echo -n "Checking Key Vault... "
|
||||
KV_EXISTS=$(az keyvault show --name mim-prod-igiay4-kv --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "")
|
||||
if [ -n "$KV_EXISTS" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
|
||||
# Check Application Insights
|
||||
echo -n "Checking Application Insights... "
|
||||
AI_EXISTS=$(az monitor app-insights component show --app mim-prod-igiay4-appinsights --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "")
|
||||
if [ -n "$AI_EXISTS" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
|
||||
# Test SSL/TLS
|
||||
echo ""
|
||||
echo -e "${YELLOW}🔒 Testing SSL/TLS${NC}"
|
||||
echo -n "Testing HTTPS on Static Web App... "
|
||||
if echo | openssl s_client -connect lemon-water-015cb3010.3.azurestaticapps.net:443 -servername lemon-water-015cb3010.3.azurestaticapps.net 2>/dev/null | grep -q "Verify return code: 0"; then
|
||||
echo -e "${GREEN}✅ PASS${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ WARNING${NC} (Could not verify SSL)"
|
||||
fi
|
||||
|
||||
# Test Performance
|
||||
echo ""
|
||||
echo -e "${YELLOW}⚡ Testing Performance${NC}"
|
||||
echo -n "Testing Static Web App response time... "
|
||||
RESPONSE_TIME=$(curl -s -o /dev/null -w "%{time_total}" --max-time 10 "$STATIC_WEB_APP_URL" || echo "999")
|
||||
if (( $(echo "$RESPONSE_TIME < 3.0" | bc -l) )); then
|
||||
echo -e "${GREEN}✅ PASS${NC} (${RESPONSE_TIME}s)"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ WARNING${NC} (${RESPONSE_TIME}s - may be slow)"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo -e "${GREEN}Tests Passed: $TESTS_PASSED${NC}"
|
||||
if [ $TESTS_FAILED -gt 0 ]; then
|
||||
echo -e "${RED}Tests Failed: $TESTS_FAILED${NC}"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}Tests Failed: $TESTS_FAILED${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ All tests passed!${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user