docs: archive entra materials and simplify deployment docs

This commit is contained in:
defiQUG
2026-04-18 12:05:55 -07:00
parent 0f600e6a31
commit bbb6ce6a6c
256 changed files with 4188 additions and 3881 deletions
+85 -177
View File
@@ -1,272 +1,180 @@
# Deployment Automation Scripts
Automated deployment scripts for The Order following the deployment guide.
Automated deployment scripts for The Order using the Sankofa Phoenix / Proxmox runtime.
## Overview
This directory contains automated scripts for deploying The Order to Azure/Kubernetes. The scripts follow the 15-phase deployment guide and can be run individually or as a complete deployment.
The active deployment path is now Sankofa Phoenix / Proxmox-native. The scripts in this directory now:
- build The Order locally
- package the `portal-public` Next.js standalone bundle
- sync it to the Order public CT on Proxmox
- refresh the Order HAProxy edge
- verify direct, edge, and public health endpoints
The default topology is:
- `order-portal-public` CT `10090` at `192.168.11.36:3000`
- `order-haproxy` CT `10210` at `192.168.11.39:80`
- public URL `https://the-order.sankofa.nexus`
- Phoenix public URL `https://phoenix.sankofa.nexus`
## Quick Start
```bash
# Deploy all phases for dev environment
# Deploy the frontend to the default dev target
./scripts/deploy/deploy.sh --phase 11 --environment dev
# Run the direct sync script
./scripts/deploy/sync-portal-public-to-sankofa-phoenix.sh
# Full phase flow
./scripts/deploy/deploy.sh --all --environment dev
# Deploy specific phases
./scripts/deploy/deploy.sh --phase 1 --phase 2 --phase 6
# Continue from last saved state
./scripts/deploy/deploy.sh --continue
# Deploy with auto-apply (no Terraform review)
./scripts/deploy/deploy.sh --all --auto-apply
```
## Configuration
Configuration is managed in `config.sh`. Key variables:
Configuration is managed in `config.sh`. The most important variables are:
- `ENVIRONMENT`: Deployment environment (dev, stage, prod)
- `AZURE_REGION`: Azure region (default: westeurope)
- `ACR_NAME`: Azure Container Registry name
- `AKS_NAME`: AKS cluster name
- `KEY_VAULT_NAME`: Azure Key Vault name
- `PROXMOX_HOST`
- `ORDER_PORTAL_PUBLIC_VMID`
- `ORDER_PORTAL_PUBLIC_IP`
- `ORDER_HAPROXY_VMID`
- `ORDER_HAPROXY_IP`
- `SANKOFA_PHOENIX_URL`
- `THE_ORDER_PUBLIC_URL`
- `IMAGE_REGISTRY`
- `IMAGE_TAG`
Set via environment variables or edit `config.sh`:
Example:
```bash
export ENVIRONMENT=prod
export AZURE_REGION=westeurope
export ACR_NAME=theorderacr
./scripts/deploy/deploy.sh --all
export PROXMOX_HOST=192.168.11.11
export ORDER_PORTAL_PUBLIC_VMID=10090
export ORDER_HAPROXY_VMID=10210
./scripts/deploy/deploy.sh --phase 11
```
## Phase Scripts
### Phase 1: Prerequisites
- Checks all required tools
- Verifies Azure login
- Installs dependencies
- Builds packages
- verifies local tooling
- verifies SSH access to Proxmox
- records the Sankofa / Order runtime targets
```bash
./scripts/deploy/phase1-prerequisites.sh
```
### Phase 2: Azure Infrastructure
- Runs Azure setup scripts
- Registers resource providers
- Deploys Terraform infrastructure
- Configures Kubernetes access
### Phase 2: Sankofa Phoenix Target Preparation
- confirms the Order public CT and HAProxy CT are reachable
- previews the HAProxy config for `the-order.sankofa.nexus`
- probes Phoenix public health
```bash
./scripts/deploy/phase2-azure-infrastructure.sh
./scripts/deploy/phase2-sankofa-phoenix-target.sh
```
### Phase 3: Entra ID Configuration
- **Manual steps required** (Azure Portal)
- Helper script to store secrets: `store-entra-secrets.sh`
### Phase 6: Build & Package
- Builds all packages and applications
- Creates Docker images
- Pushes to Azure Container Registry
- Signs images with Cosign (if available)
- builds all packages and applications
- creates local Docker images for services and apps
- previews the Phoenix deployment artifact
```bash
./scripts/deploy/phase6-build-package.sh
```
### Phase 7: Database Migrations
- Runs database schema migrations
- Verifies database connection
### Phase 11: Frontend Applications Deployment
- builds `portal-public`
- syncs the standalone bundle to CT `10090`
- installs or refreshes the `the-order-portal-public` systemd service
- reprovisions the Order HAProxy edge on `10210`
- verifies LAN and public health endpoints
```bash
./scripts/deploy/phase7-database-migrations.sh
```
### Phase 10: Backend Services
- Deploys backend services to Kubernetes
- Verifies deployments
- Tests health endpoints
```bash
./scripts/deploy/phase10-backend-services.sh
./scripts/deploy/phase11-frontend-apps.sh
```
## Usage Examples
### Full Deployment
### Frontend deployment
```bash
# Development environment
./scripts/deploy/deploy.sh --all --environment dev
# Staging environment
./scripts/deploy/deploy.sh --all --environment stage
# Production (with confirmation)
./scripts/deploy/deploy.sh --all --environment prod
./scripts/deploy/deploy.sh --phase 11 --environment dev
```
### Incremental Deployment
### Build only
```bash
# Run prerequisites and infrastructure
./scripts/deploy/deploy.sh --phase 1 --phase 2
# Build and package
./scripts/deploy/deploy.sh --phase 6
# Deploy services
./scripts/deploy/deploy.sh --phase 10 --phase 11
./scripts/deploy/deploy.sh --phase 6 --environment dev
```
### Skip Phases
### Continue from the last saved state
```bash
# Skip build (if already built)
./scripts/deploy/deploy.sh --all --skip-build
# Skip specific phase
./scripts/deploy/deploy.sh --all --skip 3 --skip 8
```
### Continue from Failure
```bash
# If deployment fails, continue from last state
./scripts/deploy/deploy.sh --continue
```
## State Management
Deployment state is saved in `.deployment/${ENVIRONMENT}.state`. This allows:
Deployment state is saved in `.deployment/${ENVIRONMENT}.state`.
- Resuming from last completed phase
- Tracking deployment progress
- Debugging failed deployments
Artifacts and image manifests are written under `.deployment/artifacts/`.
## Logging
All deployment logs are saved to `logs/deployment-YYYYMMDD-HHMMSS.log`.
View logs:
```bash
tail -f logs/deployment-*.log
```
## Manual Steps
Some phases require manual steps:
Some phases still require external operator work:
- **Phase 3**: Entra ID configuration (Azure Portal)
- **Phase 8**: Secrets configuration (use helper scripts)
- **Phase 12**: DNS configuration
- **Phase 13**: Monitoring dashboard setup
See `docs/deployment/DEPLOYMENT_GUIDE.md` for detailed instructions.
## Helper Scripts
### Store Entra ID Secrets
After completing Entra ID setup in Azure Portal:
```bash
./scripts/deploy/store-entra-secrets.sh
```
This will prompt for:
- Tenant ID
- Client ID
- Client Secret
- Credential Manifest ID
And store them in Azure Key Vault.
- `Phase 3`: identity-provider / Entra setup
- `Phase 8`: secret injection into the chosen backend
- `Phase 12`: DNS / NPM updates if you are changing routing
- `Phase 13`: central monitoring / alert wiring
## Troubleshooting
### Check Deployment State
### Check deployment state
```bash
cat .deployment/dev.state
```
### View Logs
### Verify Order runtime access
```bash
tail -f logs/deployment-*.log
ssh [email protected] "pct status 10090 && pct status 10210"
curl -fsS http://192.168.11.36:3000/api/health
curl -fsS -H 'Host: the-order.sankofa.nexus' http://192.168.11.39/api/health
curl -fsS https://the-order.sankofa.nexus/api/health
```
### Verify Kubernetes Access
### Verify Phoenix public access
```bash
kubectl cluster-info
kubectl get nodes
curl -fsS https://phoenix.sankofa.nexus/health
```
### Verify Azure Access
### View CT service logs
```bash
az account show
az aks list
```
### Re-run Failed Phase
```bash
./scripts/deploy/deploy.sh --phase <phase-number>
```
## Environment-Specific Configuration
Create environment-specific config files:
```bash
# .deployment/dev.env
export ENVIRONMENT=dev
export AKS_NAME=the-order-dev-aks
export KEY_VAULT_NAME=the-order-dev-kv
```
Source before deployment:
```bash
source .deployment/dev.env
./scripts/deploy/deploy.sh --all
```
## Integration with CI/CD
The scripts can be integrated into CI/CD pipelines:
```yaml
# .github/workflows/deploy.yml
- name: Deploy to Dev
run: |
./scripts/deploy/deploy.sh --all --environment dev --auto-apply
env:
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
ssh [email protected] "pct exec 10090 -- journalctl -u the-order-portal-public -n 100 --no-pager"
```
## Security Notes
- Never commit secrets to repository
- Use Azure Key Vault for all secrets
- Enable RBAC for all resources
- Review Terraform plans before applying
- Use managed identities where possible
## Next Steps
After deployment:
1. Verify all services are running: `kubectl get pods -n the-order-${ENV}`
2. Test health endpoints
3. Configure monitoring dashboards
4. Set up alerts
5. Review security settings
See `docs/deployment/DEPLOYMENT_GUIDE.md` for complete deployment instructions.
- never commit secrets to the repository
- keep Proxmox SSH access limited to operator hosts
- review CT and HAProxy targets before applying changes
- prefer the documented `10090 -> 10210 -> public` path over ad hoc edits
-138
View File
@@ -1,138 +0,0 @@
#!/bin/bash
# Complete Entra VerifiedID Setup - Master Script
# Orchestrates all setup steps in the correct order
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[SETUP]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
log_error() { echo -e "${RED}[✗]${NC} $1"; }
log_step() { echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n${BLUE}Step $1:${NC} $2\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; }
cd "$(dirname "$0")/../.."
log_info "Entra VerifiedID Complete Setup"
log_info "This script will guide you through all setup steps"
echo ""
# Step 1: Azure App Registration
log_step "1" "Azure AD App Registration"
read -p "Have you created the Azure AD App Registration? (y/n): " APP_REG_DONE
if [ "${APP_REG_DONE}" != "y" ]; then
log_info "Running app registration script..."
./scripts/deploy/create-entra-app.sh
else
log_success "App registration already done"
fi
# Step 2: API Permissions
log_step "2" "API Permissions Configuration"
read -p "Have you configured API permissions? (y/n): " PERMS_DONE
if [ "${PERMS_DONE}" != "y" ]; then
log_info "Running API permissions configuration..."
./scripts/deploy/configure-api-permissions.sh
else
log_success "API permissions already configured"
fi
# Step 3: Enable Verified ID
log_step "3" "Enable Verified ID Service"
read -p "Is Verified ID service enabled? (y/n): " VERIFIED_ID_DONE
if [ "${VERIFIED_ID_DONE}" != "y" ]; then
log_info "Running Verified ID enablement guide..."
./scripts/deploy/enable-verified-id.sh
read -p "Press Enter after enabling Verified ID service..."
else
log_success "Verified ID service already enabled"
fi
# Step 4: Create Manifests
log_step "4" "Create Credential Manifests"
read -p "Have you created credential manifests? (y/n): " MANIFESTS_DONE
if [ "${MANIFESTS_DONE}" != "y" ]; then
log_info "Running manifest creation guide..."
./scripts/deploy/create-credential-manifests.sh
read -p "Press Enter after creating manifests and collecting Manifest IDs..."
./manifests/entra/collect-manifest-ids.sh
else
log_success "Manifests already created"
fi
# Step 5: Store Secrets
log_step "5" "Store Secrets in Key Vault"
read -p "Have you stored secrets in Key Vault? (y/n): " SECRETS_DONE
if [ "${SECRETS_DONE}" != "y" ]; then
log_info "Running secret storage script..."
./scripts/deploy/store-entra-secrets.sh
else
log_success "Secrets already stored"
fi
# Step 6: Environment Configuration
log_step "6" "Configure Environment"
read -p "Configure development environment? (y/n): " CONFIG_ENV
if [ "${CONFIG_ENV}" = "y" ]; then
./scripts/deploy/configure-env-dev.sh
fi
# Step 7: Multi-Manifest (if applicable)
log_step "7" "Configure Multi-Manifest Support"
read -p "Do you have multiple manifests to configure? (y/n): " MULTI_MANIFEST
if [ "${MULTI_MANIFEST}" = "y" ]; then
./scripts/deploy/configure-multi-manifest.sh
fi
# Step 8: Validation
log_step "8" "Validate Configuration"
log_info "Running validation..."
if ./scripts/validation/validate-entra-config.sh; then
log_success "Configuration validated"
else
log_warning "Validation found issues. Please review and fix."
fi
# Step 9: Testing
log_step "9" "Run Tests"
read -p "Run unit tests? (y/n): " RUN_UNIT
if [ "${RUN_UNIT}" = "y" ]; then
pnpm --filter @the-order/auth test entra-verifiedid.test.ts --run
fi
read -p "Run integration tests? (requires credentials) (y/n): " RUN_INTEGRATION
if [ "${RUN_INTEGRATION}" = "y" ]; then
./scripts/test/run-integration-tests-with-setup.sh
fi
# Step 10: Deployment
log_step "10" "Deployment"
read -p "Deploy to staging? (y/n): " DEPLOY_STAGING
if [ "${DEPLOY_STAGING}" = "y" ]; then
./scripts/deploy/deploy-staging.sh
fi
read -p "Configure webhook URL? (y/n): " CONFIG_WEBHOOK
if [ "${CONFIG_WEBHOOK}" = "y" ]; then
./scripts/deploy/configure-webhook-url.sh
fi
# Summary
echo ""
log_success "Setup Complete!"
echo ""
log_info "Next steps:"
echo "1. Verify staging deployment"
echo "2. Test credential issuance"
echo "3. Monitor metrics"
echo "4. Deploy to production when ready"
echo ""
log_info "For detailed information, see:"
echo " - docs/deployment/ENTRA_VERIFIEDID_DEPLOYMENT_CHECKLIST.md"
echo " - docs/operations/ENTRA_VERIFIEDID_RUNBOOK.md"
+3 -4
View File
@@ -77,7 +77,7 @@ cat > "assets/credential-images/DEPLOYMENT_CHECKLIST.md" << 'EOF'
- [ ] Development environment variables set
- [ ] Staging environment variables set
- [ ] Production environment variables set
- [ ] ENTRA_CREDENTIAL_LOGO_URI configured per credential type
- [ ] Credential logo URL configured per credential type
## Testing
@@ -144,11 +144,11 @@ $(find assets/credential-images/png -name "*.png" -type f | wc -l) PNG files gen
- Ensure HTTPS and public access
3. **Update Manifest Templates**
- Update CDN URLs in \`manifests/entra/*-manifest-template.json\`
- Update CDN URLs in your active credential templates
- Verify all credential types have correct seal references
4. **Configure Environment**
- Set \`ENTRA_CREDENTIAL_LOGO_URI\` per credential type
- Set the credential logo URL per credential type
- Update staging/production configurations
5. **Test**
@@ -204,4 +204,3 @@ echo "3. Update manifest templates with CDN URLs"
echo "4. Test credential issuance"
echo ""
log_success "Ready for CDN deployment!"
+87 -51
View File
@@ -22,24 +22,13 @@ readonly SCRIPTS_DIR="${PROJECT_ROOT}/scripts"
readonly INFRA_DIR="${PROJECT_ROOT}/infra"
readonly TERRAFORM_DIR="${INFRA_DIR}/terraform"
readonly K8S_DIR="${INFRA_DIR}/k8s"
readonly PROXMOX_WORKSPACE_ROOT="$(cd "${PROJECT_ROOT}/.." && pwd)"
# Azure configuration
readonly AZURE_REGION="${AZURE_REGION:-westeurope}"
readonly AZURE_SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID:-}"
# Region abbreviation mapping
get_region_abbrev() {
case "${AZURE_REGION}" in
westeurope) echo "we" ;;
northeurope) echo "ne" ;;
uksouth) echo "uk" ;;
switzerlandnorth) echo "ch" ;;
norwayeast) echo "no" ;;
francecentral) echo "fr" ;;
germanywestcentral) echo "de" ;;
*) echo "we" ;; # Default to westeurope
esac
}
# Load shared Sankofa / Proxmox network inventory when available.
if [ -f "${PROXMOX_WORKSPACE_ROOT}/config/ip-addresses.conf" ]; then
# shellcheck source=/dev/null
source "${PROXMOX_WORKSPACE_ROOT}/config/ip-addresses.conf"
fi
# Environment abbreviation mapping
get_env_abbrev() {
@@ -52,36 +41,17 @@ get_env_abbrev() {
esac
}
# Naming convention: {provider}-{region}-{resource}-{env}-{purpose}
readonly REGION_SHORT=$(get_region_abbrev)
readonly ENV_SHORT=$(get_env_abbrev)
readonly NAME_PREFIX="az-${REGION_SHORT}"
readonly NAME_PREFIX="sankofa-${ENV_SHORT}"
# Environment configuration
readonly ENVIRONMENT="${ENVIRONMENT:-dev}"
readonly NAMESPACE="the-order-${ENVIRONMENT}"
# Resource Groups (az-we-rg-dev-main)
readonly RESOURCE_GROUP_NAME="${RESOURCE_GROUP_NAME:-${NAME_PREFIX}-rg-${ENV_SHORT}-main}"
readonly AKS_RESOURCE_GROUP="${AKS_RESOURCE_GROUP:-${RESOURCE_GROUP_NAME}}"
# Container registry (azweacrdev - alphanumeric only, max 50 chars)
readonly ACR_NAME="${ACR_NAME:-az${REGION_SHORT}acr${ENV_SHORT}}"
# Local image / artifact configuration
readonly IMAGE_REGISTRY="${IMAGE_REGISTRY:-theorder}"
readonly IMAGE_TAG="${IMAGE_TAG:-latest}"
# Kubernetes configuration (az-we-aks-dev-main)
readonly AKS_NAME="${AKS_NAME:-${NAME_PREFIX}-aks-${ENV_SHORT}-main}"
# Key Vault (az-we-kv-dev-main - max 24 chars)
readonly KEY_VAULT_NAME="${KEY_VAULT_NAME:-${NAME_PREFIX}-kv-${ENV_SHORT}-main}"
# Database (az-we-psql-dev-main)
readonly POSTGRES_SERVER_NAME="${POSTGRES_SERVER_NAME:-${NAME_PREFIX}-psql-${ENV_SHORT}-main}"
readonly POSTGRES_DB_NAME="${POSTGRES_DB_NAME:-${NAME_PREFIX}-db-${ENV_SHORT}-main}"
# Storage (azwesadevdata - alphanumeric only, max 24 chars)
readonly STORAGE_ACCOUNT_NAME="${STORAGE_ACCOUNT_NAME:-az${REGION_SHORT}sa${ENV_SHORT}data}"
# Services
readonly SERVICES=("identity" "intake" "finance" "dataroom")
readonly APPS=("portal-public" "portal-internal")
@@ -103,10 +73,61 @@ readonly LOG_FILE="${LOG_DIR}/deployment-$(date +%Y%m%d-%H%M%S).log"
# Deployment state
readonly STATE_DIR="${PROJECT_ROOT}/.deployment"
readonly STATE_FILE="${STATE_DIR}/${ENVIRONMENT}.state"
readonly ARTIFACTS_DIR="${STATE_DIR}/artifacts"
# Sankofa Phoenix / Proxmox deployment topology
readonly PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
readonly PROXMOX_SSH_USER="${PROXMOX_SSH_USER:-root}"
readonly SSH_OPTS="${SSH_OPTS:--o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new}"
resolve_host_for_vmid() {
local vmid="$1"
local host
local candidates=(
"${PROXMOX_HOST_R630_01:-192.168.11.11}"
"${PROXMOX_HOST_R630_02:-192.168.11.12}"
"${PROXMOX_HOST_R630_03:-192.168.11.13}"
"${PROXMOX_HOST_R630_04:-192.168.11.14}"
)
if command -v ssh >/dev/null 2>&1; then
for host in "${candidates[@]}"; do
if ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
"${PROXMOX_SSH_USER}@${host}" "pct status ${vmid}" >/dev/null 2>&1; then
echo "${host}"
return 0
fi
done
fi
case "${vmid}" in
10090|10091|10092|10210) echo "${PROXMOX_HOST_R630_04:-192.168.11.14}" ;;
*) echo "${PROXMOX_HOST}" ;;
esac
}
readonly ORDER_PORTAL_PUBLIC_VMID="${ORDER_PORTAL_PUBLIC_VMID:-10090}"
readonly ORDER_PORTAL_PUBLIC_HOST="${ORDER_PORTAL_PUBLIC_HOST:-$(resolve_host_for_vmid "${ORDER_PORTAL_PUBLIC_VMID}")}"
readonly ORDER_PORTAL_PUBLIC_IP="${ORDER_PORTAL_PUBLIC_IP:-192.168.11.36}"
readonly ORDER_PORTAL_PUBLIC_PORT="${ORDER_PORTAL_PUBLIC_PORT:-3000}"
readonly ORDER_PORTAL_PUBLIC_APP_DIR="${ORDER_PORTAL_PUBLIC_APP_DIR:-/opt/the-order/portal-public}"
readonly ORDER_PORTAL_PUBLIC_SERVICE="${ORDER_PORTAL_PUBLIC_SERVICE:-the-order-portal-public}"
readonly ORDER_HAPROXY_VMID="${ORDER_HAPROXY_VMID:-10210}"
readonly ORDER_HAPROXY_HOST="${ORDER_HAPROXY_HOST:-$(resolve_host_for_vmid "${ORDER_HAPROXY_VMID}")}"
readonly ORDER_HAPROXY_IP="${ORDER_HAPROXY_IP:-${IP_ORDER_HAPROXY:-192.168.11.39}}"
readonly ORDER_HAPROXY_BACKEND_HOST="${ORDER_HAPROXY_BACKEND_HOST:-${ORDER_PORTAL_PUBLIC_IP}}"
readonly ORDER_HAPROXY_BACKEND_PORT="${ORDER_HAPROXY_BACKEND_PORT:-${ORDER_PORTAL_PUBLIC_PORT}}"
readonly THE_ORDER_PUBLIC_URL="${THE_ORDER_PUBLIC_URL:-https://the-order.sankofa.nexus}"
readonly THE_ORDER_WWW_URL="${THE_ORDER_WWW_URL:-https://www.the-order.sankofa.nexus}"
readonly SANKOFA_PHOENIX_URL="${SANKOFA_PHOENIX_URL:-https://phoenix.sankofa.nexus}"
readonly SANKOFA_PORTAL_URL="${SANKOFA_PORTAL_URL:-https://portal.sankofa.nexus}"
# Create necessary directories
mkdir -p "${LOG_DIR}"
mkdir -p "${STATE_DIR}"
mkdir -p "${ARTIFACTS_DIR}"
# Logging functions
log_info() {
@@ -142,24 +163,40 @@ check_command() {
fi
}
check_azure_login() {
if ! az account show &> /dev/null; then
log_warning "Not logged into Azure. Attempting login..."
az login || error_exit "Failed to login to Azure"
fi
}
check_prerequisites() {
log_info "Checking prerequisites..."
check_command "node"
check_command "pnpm"
check_command "az"
check_command "terraform"
check_command "kubectl"
check_command "docker"
check_command "git"
check_command "jq"
check_command "ssh"
check_command "scp"
check_command "tar"
log_success "All prerequisites met"
}
check_proxmox_access() {
local hosts=("${PROXMOX_HOST}" "${ORDER_PORTAL_PUBLIC_HOST}" "${ORDER_HAPROXY_HOST}")
local unique_hosts=()
local host
for host in "${hosts[@]}"; do
[[ -z "${host}" ]] && continue
if [[ " ${unique_hosts[*]} " != *" ${host} "* ]]; then
unique_hosts+=("${host}")
fi
done
for host in "${unique_hosts[@]}"; do
log_info "Checking Proxmox access at ${PROXMOX_SSH_USER}@${host}..."
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${host}" "echo ok" >/dev/null \
|| error_exit "Failed to reach Proxmox host ${host} over SSH"
done
log_success "Proxmox SSH access verified"
}
# State management
save_state() {
local phase="$1"
@@ -177,6 +214,5 @@ load_state() {
# Export functions
export -f log_info log_success log_warning log_error log_step error_exit
export -f check_command check_azure_login check_prerequisites
export -f check_command check_prerequisites check_proxmox_access
export -f save_state load_state
@@ -1,74 +0,0 @@
#!/bin/bash
# Configure API Permissions for Entra VerifiedID App Registration
# This script helps automate permission configuration
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
# Check Azure CLI
if ! command -v az &> /dev/null; then
log_warning "Azure CLI not found"
exit 1
fi
if ! az account show &> /dev/null; then
log_warning "Not logged in to Azure"
exit 1
fi
log_info "Configuring API Permissions for Entra VerifiedID..."
# Get app ID
read -p "Enter Application (Client) ID: " APP_ID
if [ -z "${APP_ID}" ]; then
log_warning "App ID is required"
exit 1
fi
# Verifiable Credentials Service App ID
VC_SERVICE_APP_ID="3db474b9-7a6d-4f50-afdc-70940ce1df8f"
log_info "Adding Verifiable Credentials Service permissions..."
# Note: Azure CLI doesn't support adding API permissions directly for Verifiable Credentials Service
# This requires manual steps in Azure Portal, but we can provide the exact steps
log_warning "API permissions must be configured manually in Azure Portal"
log_info "Follow these steps:"
echo ""
echo "1. Go to: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/${APP_ID}"
echo "2. Click 'API permissions'"
echo "3. Click 'Add a permission'"
echo "4. Select 'APIs my organization uses'"
echo "5. Search for: 'Verifiable Credentials Service' or use App ID: ${VC_SERVICE_APP_ID}"
echo "6. Select 'Application permissions'"
echo "7. Check the following permissions:"
echo " - VerifiableCredential.Create.All"
echo " - VerifiableCredential.Verify.All"
echo "8. Click 'Add permissions'"
echo "9. Click 'Grant admin consent for [Your Organization]'"
echo "10. Verify consent status shows 'Granted'"
echo ""
# Try to grant admin consent if possible
log_info "Attempting to grant admin consent..."
if az ad app permission admin-consent --id "${APP_ID}" 2>/dev/null; then
log_success "Admin consent granted via CLI"
else
log_warning "Admin consent must be granted manually in Azure Portal"
log_info "Go to: API permissions → Grant admin consent"
fi
log_success "Permission configuration guide provided"
log_info "After completing manual steps, verify permissions:"
echo "az ad app permission list --id ${APP_ID}"
-69
View File
@@ -1,69 +0,0 @@
#!/bin/bash
# Configure development environment for Entra VerifiedID
# Generates .env file with Entra configuration
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
cd "$(dirname "$0")/../.."
ENV_FILE=".env.entra"
log_info "Configuring development environment for Entra VerifiedID..."
# Check if .entra-app-info.txt exists
if [ -f ".entra-app-info.txt" ]; then
log_info "Found existing app registration info"
source <(grep -E "^(Application|Directory|Client Secret):" .entra-app-info.txt | sed 's/.*: //' | awk '{print "export " $0}')
else
log_warning "No app registration info found. Run ./scripts/deploy/create-entra-app.sh first"
read -p "Enter Tenant ID: " ENTRA_TENANT_ID
read -p "Enter Client ID: " ENTRA_CLIENT_ID
read -sp "Enter Client Secret: " ENTRA_CLIENT_SECRET
echo
fi
read -p "Enter Credential Manifest ID (or press Enter to skip): " ENTRA_CREDENTIAL_MANIFEST_ID
# Create .env.entra file
cat > "${ENV_FILE}" << EOF
# Microsoft Entra VerifiedID Configuration
# Generated: $(date)
ENTRA_TENANT_ID=${ENTRA_TENANT_ID}
ENTRA_CLIENT_ID=${ENTRA_CLIENT_ID}
ENTRA_CLIENT_SECRET=${ENTRA_CLIENT_SECRET}
ENTRA_CREDENTIAL_MANIFEST_ID=${ENTRA_CREDENTIAL_MANIFEST_ID:-}
# Multi-manifest support (JSON format)
# ENTRA_MANIFESTS='{"default":"manifest-id-1","diplomatic":"manifest-id-2","judicial":"manifest-id-3"}'
# Entra Rate Limiting (optional)
ENTRA_RATE_LIMIT_ISSUANCE=10
ENTRA_RATE_LIMIT_VERIFICATION=20
ENTRA_RATE_LIMIT_STATUS_CHECK=30
ENTRA_RATE_LIMIT_GLOBAL=50
EOF
log_success "Environment file created: ${ENV_FILE}"
log_info "To use this configuration, run: source ${ENV_FILE}"
# Check if .env exists and offer to merge
if [ -f ".env" ]; then
read -p "Merge with existing .env file? (y/n): " MERGE
if [ "${MERGE}" = "y" ]; then
cat "${ENV_FILE}" >> .env
log_success "Merged into .env file"
fi
fi
log_success "Development environment configured!"
-101
View File
@@ -1,101 +0,0 @@
#!/bin/bash
# Configure Multi-Manifest Support for Entra VerifiedID
# Helps set up multiple credential manifests
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
cd "$(dirname "$0")/../.."
log_info "Configuring Multi-Manifest Support for Entra VerifiedID..."
echo "Enter manifest IDs (press Enter to skip optional ones):"
echo ""
read -p "Default Manifest ID (required): " DEFAULT_MANIFEST
if [ -z "${DEFAULT_MANIFEST}" ]; then
log_warning "Default manifest ID is required"
exit 1
fi
read -p "Diplomatic Manifest ID (optional): " DIPLOMATIC_MANIFEST
read -p "Judicial Manifest ID (optional): " JUDICIAL_MANIFEST
read -p "Financial Manifest ID (optional): " FINANCIAL_MANIFEST
# Build JSON object
MANIFESTS_JSON="{"
MANIFESTS_JSON+="\"default\":\"${DEFAULT_MANIFEST}\""
if [ -n "${DIPLOMATIC_MANIFEST}" ]; then
MANIFESTS_JSON+=",\"diplomatic\":\"${DIPLOMATIC_MANIFEST}\""
fi
if [ -n "${JUDICIAL_MANIFEST}" ]; then
MANIFESTS_JSON+=",\"judicial\":\"${JUDICIAL_MANIFEST}\""
fi
if [ -n "${FINANCIAL_MANIFEST}" ]; then
MANIFESTS_JSON+=",\"financial\":\"${FINANCIAL_MANIFEST}\""
fi
MANIFESTS_JSON+="}"
log_info "Generated manifest configuration:"
echo "${MANIFESTS_JSON}" | jq '.'
# Update .env file if it exists
if [ -f ".env" ]; then
read -p "Update .env file? (y/n): " UPDATE_ENV
if [ "${UPDATE_ENV}" = "y" ]; then
# Remove old ENTRA_MANIFESTS if exists
sed -i '/^ENTRA_MANIFESTS=/d' .env
# Add new one
echo "ENTRA_MANIFESTS='${MANIFESTS_JSON}'" >> .env
log_success "Updated .env file"
fi
fi
# For Kubernetes
read -p "Generate Kubernetes secret update? (y/n): " GEN_K8S
if [ "${GEN_K8S}" = "y" ]; then
K8S_SECRET="infra/k8s/entra-manifests-secret.yaml"
cat > "${K8S_SECRET}" << EOF
apiVersion: v1
kind: Secret
metadata:
name: entra-manifests
namespace: the-order-prod
type: Opaque
stringData:
ENTRA_MANIFESTS: '${MANIFESTS_JSON}'
EOF
log_success "Kubernetes secret created: ${K8S_SECRET}"
fi
# For Key Vault
read -p "Store in Azure Key Vault? (y/n): " STORE_KV
if [ "${STORE_KV}" = "y" ]; then
read -p "Key Vault name: " KV_NAME
if [ -n "${KV_NAME}" ]; then
az keyvault secret set \
--vault-name "${KV_NAME}" \
--name "entra-manifests" \
--value "${MANIFESTS_JSON}" \
--output none
log_success "Stored in Key Vault: ${KV_NAME}"
fi
fi
log_success "Multi-manifest configuration complete!"
log_info "To use in code:"
echo " const manifests = JSON.parse(process.env.ENTRA_MANIFESTS);"
echo " await client.issueCredential({ claims: {...}, manifestName: 'diplomatic' });"
-82
View File
@@ -1,82 +0,0 @@
#!/bin/bash
# Configure Webhook URL in Entra VerifiedID
# Provides instructions and validates webhook configuration
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_info "Entra VerifiedID Webhook URL Configuration"
echo ""
read -p "Environment (staging/production): " ENV
ENV=${ENV:-staging}
if [ "${ENV}" = "production" ]; then
WEBHOOK_URL="https://api.theorder.org/vc/entra/webhook"
APP_ID_PROMPT="Production App Registration"
else
WEBHOOK_URL="https://api-staging.theorder.org/vc/entra/webhook"
APP_ID_PROMPT="Staging App Registration"
fi
read -p "Application (Client) ID for ${APP_ID_PROMPT}: " APP_ID
log_info "Webhook Configuration Instructions:"
echo ""
echo "1. Go to Azure Portal → Verified ID"
echo "2. Click on your credential manifest"
echo "3. Go to 'Settings' or 'Configuration'"
echo "4. Find 'Callback URL' or 'Webhook URL' section"
echo "5. Enter the following URL:"
echo ""
echo " ${WEBHOOK_URL}"
echo ""
echo "6. Save the configuration"
echo ""
# Test webhook endpoint
log_info "Testing webhook endpoint..."
if curl -sf -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d '{"requestId":"test","requestStatus":"issuance_successful"}' > /dev/null; then
log_success "Webhook endpoint is accessible"
else
log_warning "Webhook endpoint test failed (may require authentication or service not deployed)"
fi
# Generate webhook test payload
cat > webhook-test-payload.json << EOF
{
"requestId": "test-request-$(date +%s)",
"requestStatus": "issuance_successful",
"credential": {
"id": "vc:test:123",
"type": ["VerifiableCredential"],
"issuer": "did:web:${APP_ID}.verifiedid.msidentity.com",
"issuanceDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"credentialSubject": {
"email": "[email protected]"
},
"proof": {
"type": "JsonWebSignature2020",
"created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:web:${APP_ID}#key",
"jws": "test-signature"
}
}
}
EOF
log_success "Webhook test payload created: webhook-test-payload.json"
log_info "You can test the webhook with:"
echo "curl -X POST ${WEBHOOK_URL} -H 'Content-Type: application/json' -d @webhook-test-payload.json"
@@ -1,244 +0,0 @@
#!/bin/bash
# Create Credential Manifests in Entra VerifiedID
# Provides templates and step-by-step instructions for all manifest types
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
cd "$(dirname "$0")/../.."
MANIFESTS_DIR="manifests/entra"
mkdir -p "${MANIFESTS_DIR}"
log_info "Credential Manifest Creation Guide"
echo ""
# Create manifest templates
log_info "Creating manifest templates..."
# Default/Identity Manifest Template
cat > "${MANIFESTS_DIR}/default-manifest-template.json" << 'EOF'
{
"name": "The Order Identity Credential",
"description": "Identity credential for members of The Order",
"claims": [
{
"claim": "email",
"type": "String",
"required": true
},
{
"claim": "name",
"type": "String",
"required": true
},
{
"claim": "role",
"type": "String",
"required": false
},
{
"claim": "userId",
"type": "String",
"required": false
}
],
"issuer": {
"name": "The Order",
"domain": "theorder.org"
}
}
EOF
# Diplomatic Manifest Template
cat > "${MANIFESTS_DIR}/diplomatic-manifest-template.json" << 'EOF'
{
"name": "The Order Letters of Credence",
"description": "Diplomatic credential for Letters of Credence",
"claims": [
{
"claim": "recipientName",
"type": "String",
"required": true
},
{
"claim": "recipientTitle",
"type": "String",
"required": true
},
{
"claim": "missionCountry",
"type": "String",
"required": true
},
{
"claim": "missionType",
"type": "String",
"required": true,
"enum": ["embassy", "consulate", "delegation", "mission"]
},
{
"claim": "appointmentDate",
"type": "DateTime",
"required": true
},
{
"claim": "expirationDate",
"type": "DateTime",
"required": false
}
],
"issuer": {
"name": "The Order",
"domain": "theorder.org"
}
}
EOF
# Judicial Manifest Template
cat > "${MANIFESTS_DIR}/judicial-manifest-template.json" << 'EOF'
{
"name": "The Order Judicial Appointment Credential",
"description": "Judicial appointment credential",
"claims": [
{
"claim": "role",
"type": "String",
"required": true,
"enum": ["judge", "magistrate", "justice", "prosecutor"]
},
{
"claim": "appointmentAuthority",
"type": "String",
"required": true
},
{
"claim": "jurisdiction",
"type": "String",
"required": true
},
{
"claim": "appointmentDate",
"type": "DateTime",
"required": true
},
{
"claim": "termLength",
"type": "Number",
"required": false
}
],
"issuer": {
"name": "The Order",
"domain": "theorder.org"
}
}
EOF
# Financial Manifest Template
cat > "${MANIFESTS_DIR}/financial-manifest-template.json" << 'EOF'
{
"name": "The Order Financial Role Credential",
"description": "Financial role credential",
"claims": [
{
"claim": "role",
"type": "String",
"required": true,
"enum": ["financial-officer", "treasurer", "accountant", "auditor"]
},
{
"claim": "appointmentAuthority",
"type": "String",
"required": true
},
{
"claim": "jurisdiction",
"type": "String",
"required": true
},
{
"claim": "appointmentDate",
"type": "DateTime",
"required": true
}
],
"issuer": {
"name": "The Order",
"domain": "theorder.org"
}
}
EOF
log_success "Manifest templates created in ${MANIFESTS_DIR}/"
echo ""
# Create step-by-step guide
log_info "Step-by-Step Instructions:"
echo ""
echo "For each manifest type, follow these steps:"
echo ""
echo "1. Go to Azure Portal → Verified ID → Credentials"
echo " Direct link: https://portal.azure.com/#view/Microsoft_AAD_IAM/VerifiedIDBlade"
echo ""
echo "2. Click 'Add credential' or 'Create new credential'"
echo ""
echo "3. Choose credential type (or use 'Custom credential')"
echo ""
echo "4. Configure the credential using the templates in ${MANIFESTS_DIR}/"
echo ""
echo "5. For each manifest:"
echo " - Default: Use default-manifest-template.json"
echo " - Diplomatic: Use diplomatic-manifest-template.json"
echo " - Judicial: Use judicial-manifest-template.json"
echo " - Financial: Use financial-manifest-template.json"
echo ""
echo "6. After creating each manifest:"
echo " - Note the Manifest ID (displayed after creation)"
echo " - Run: ./scripts/deploy/configure-multi-manifest.sh"
echo " - Or manually add to ENTRA_MANIFESTS environment variable"
echo ""
# Create automated manifest ID collector
cat > "${MANIFESTS_DIR}/collect-manifest-ids.sh" << 'EOF'
#!/bin/bash
# Collect Manifest IDs after creation
# Run this after creating manifests in Azure Portal
echo "Enter Manifest IDs (press Enter to skip optional ones):"
echo ""
read -p "Default Manifest ID: " DEFAULT_ID
read -p "Diplomatic Manifest ID (optional): " DIPLOMATIC_ID
read -p "Judicial Manifest ID (optional): " JUDICIAL_ID
read -p "Financial Manifest ID (optional): " FINANCIAL_ID
MANIFESTS="{"
MANIFESTS+="\"default\":\"${DEFAULT_ID}\""
[ -n "${DIPLOMATIC_ID}" ] && MANIFESTS+=",\"diplomatic\":\"${DIPLOMATIC_ID}\""
[ -n "${JUDICIAL_ID}" ] && MANIFESTS+=",\"judicial\":\"${JUDICIAL_ID}\""
[ -n "${FINANCIAL_ID}" ] && MANIFESTS+=",\"financial\":\"${FINANCIAL_ID}\""
MANIFESTS+="}"
echo ""
echo "ENTRA_MANIFESTS='${MANIFESTS}'"
echo ""
echo "Add this to your .env file or Kubernetes secrets"
EOF
chmod +x "${MANIFESTS_DIR}/collect-manifest-ids.sh"
log_success "Manifest creation guide complete!"
log_info "Templates saved to: ${MANIFESTS_DIR}/"
log_info "After creating manifests, run: ${MANIFESTS_DIR}/collect-manifest-ids.sh"
-96
View File
@@ -1,96 +0,0 @@
#!/bin/bash
# Create Azure AD App Registration for Entra VerifiedID
# This script automates the app registration creation
set -euo pipefail
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
# Check Azure CLI
if ! command -v az &> /dev/null; then
log_warning "Azure CLI not found. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli"
exit 1
fi
# Check login
if ! az account show &> /dev/null; then
log_warning "Not logged in to Azure. Run: az login"
exit 1
fi
log_info "Creating Azure AD App Registration for Entra VerifiedID..."
# Get inputs
read -p "App Registration name (default: the-order-entra): " APP_NAME
APP_NAME=${APP_NAME:-the-order-entra}
read -p "Resource Group (optional, for tagging): " RESOURCE_GROUP
# Create app registration
log_info "Creating app registration: ${APP_NAME}"
APP_ID=$(az ad app create \
--display-name "${APP_NAME}" \
--query appId -o tsv)
log_success "App Registration created!"
log_info "Application (Client) ID: ${APP_ID}"
# Get tenant ID
TENANT_ID=$(az account show --query tenantId -o tsv)
log_info "Directory (Tenant) ID: ${TENANT_ID}"
# Create service principal
log_info "Creating service principal..."
az ad sp create --id "${APP_ID}" --output none
log_success "Service principal created"
# Create client secret
log_info "Creating client secret (valid for 1 year)..."
SECRET_RESPONSE=$(az ad app credential reset --id "${APP_ID}" --years 1)
CLIENT_SECRET=$(echo "${SECRET_RESPONSE}" | jq -r '.password')
log_success "Client secret created"
log_warning "IMPORTANT: Save this secret now - it won't be shown again!"
log_info "Client Secret: ${CLIENT_SECRET}"
# Add API permissions
log_info "Adding Verifiable Credentials Service permissions..."
VC_SERVICE_APP_ID="3db474b9-7a6d-4f50-afdc-70940ce1df8f"
# Note: Exact permission IDs may vary - this is a template
log_warning "You need to add permissions manually in Azure Portal:"
log_info "1. Go to Azure Portal → App registrations → ${APP_NAME} → API permissions"
log_info "2. Add permission → APIs my organization uses"
log_info "3. Search for 'Verifiable Credentials Service'"
log_info "4. Add Application permissions: VerifiableCredential.Create.All, VerifiableCredential.Verify.All"
log_info "5. Grant admin consent"
# Output summary
cat > .entra-app-info.txt << EOF
Azure AD App Registration Created
==================================
Application Name: ${APP_NAME}
Application (Client) ID: ${APP_ID}
Directory (Tenant) ID: ${TENANT_ID}
Client Secret: ${CLIENT_SECRET}
NEXT STEPS:
1. Add API permissions in Azure Portal (see above)
2. Grant admin consent
3. Store these values securely
4. Run: ./scripts/deploy/store-entra-secrets.sh
EOF
log_success "App registration complete!"
log_info "Details saved to: .entra-app-info.txt"
log_warning "Remember to add API permissions and grant admin consent!"
-132
View File
@@ -1,132 +0,0 @@
#!/bin/bash
# Deploy Identity Service with Entra VerifiedID to Production
# Uses blue-green deployment strategy for zero downtime
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
cd "$(dirname "$0")/../.."
# Safety check
log_warning "This will deploy to PRODUCTION. Are you sure?"
read -p "Type 'deploy-production' to confirm: " CONFIRM
if [ "${CONFIRM}" != "deploy-production" ]; then
log_error "Deployment cancelled"
exit 1
fi
# Check prerequisites
log_info "Checking prerequisites..."
if ! command -v kubectl &> /dev/null; then
log_error "kubectl not found"
exit 1
fi
if ! kubectl cluster-info &> /dev/null; then
log_error "Not connected to Kubernetes cluster"
exit 1
fi
NAMESPACE="the-order-prod"
# Verify production namespace
if ! kubectl get namespace "${NAMESPACE}" &> /dev/null; then
log_error "Production namespace not found: ${NAMESPACE}"
exit 1
fi
# Verify secrets exist
log_info "Verifying Entra secrets..."
if ! kubectl get secret entra-verifiedid-secrets -n "${NAMESPACE}" &> /dev/null; then
log_error "Entra secrets not found in production!"
log_info "Create secrets first: kubectl apply -f infra/k8s/identity-service-entra-secrets.yaml -n ${NAMESPACE}"
exit 1
fi
# Build and push image
IMAGE_TAG="prod-$(date +%Y%m%d-%H%M%S)"
IMAGE_NAME="ghcr.io/the-order/identity-service:${IMAGE_TAG}"
log_info "Building production image: ${IMAGE_NAME}"
docker build -t "${IMAGE_NAME}" -f services/identity/Dockerfile .
docker push "${IMAGE_NAME}"
log_success "Image built and pushed"
# Blue-Green Deployment Strategy
log_info "Starting blue-green deployment..."
# Create green deployment
log_info "Creating green deployment..."
sed "s|ghcr.io/the-order/identity-service:latest|${IMAGE_NAME}|g" \
infra/k8s/identity-service-deployment-entra.yaml | \
sed 's/name: identity-service/name: identity-service-green/' | \
sed 's/app: identity-service/app: identity-service-green/' > /tmp/identity-green.yaml
kubectl apply -f /tmp/identity-green.yaml -n "${NAMESPACE}"
# Wait for green to be ready
log_info "Waiting for green deployment to be ready..."
kubectl rollout status deployment/identity-service-green -n "${NAMESPACE}" --timeout=10m
# Health check on green
log_info "Running health checks on green deployment..."
GREEN_POD=$(kubectl get pod -n "${NAMESPACE}" -l app=identity-service-green -o jsonpath='{.items[0].metadata.name}')
if kubectl exec -n "${NAMESPACE}" "${GREEN_POD}" -- curl -sf http://localhost:4002/health > /dev/null; then
log_success "Green deployment is healthy"
else
log_error "Green deployment health check failed!"
log_info "Rolling back..."
kubectl delete deployment identity-service-green -n "${NAMESPACE}"
exit 1
fi
# Switch traffic to green
log_info "Switching traffic to green deployment..."
kubectl patch service identity-service -n "${NAMESPACE}" -p '{"spec":{"selector":{"app":"identity-service-green"}}}'
# Wait and verify
sleep 10
log_info "Verifying service after switch..."
if curl -sf https://api.theorder.org/health > /dev/null; then
log_success "Service is responding"
else
log_warning "Service health check failed (may need more time)"
fi
# Scale down blue (old) deployment
log_info "Scaling down blue deployment..."
if kubectl get deployment identity-service -n "${NAMESPACE}" &> /dev/null; then
kubectl scale deployment identity-service -n "${NAMESPACE}" --replicas=0
log_success "Blue deployment scaled down"
fi
# Rename green to main
log_info "Promoting green to main deployment..."
kubectl delete deployment identity-service -n "${NAMESPACE}" 2>/dev/null || true
kubectl patch deployment identity-service-green -n "${NAMESPACE}" -p '{"metadata":{"name":"identity-service"},"spec":{"selector":{"matchLabels":{"app":"identity-service"}}},"template":{"metadata":{"labels":{"app":"identity-service"}}}}'
kubectl patch service identity-service -n "${NAMESPACE}" -p '{"spec":{"selector":{"app":"identity-service"}}}'
log_success "Production deployment complete!"
log_info "Deployment details:"
kubectl get deployment identity-service -n "${NAMESPACE}"
kubectl get pods -n "${NAMESPACE}" -l app=identity-service
log_info "Next steps:"
echo "1. Monitor metrics: kubectl logs -n ${NAMESPACE} deployment/identity-service -f | grep entra"
echo "2. Verify webhook URL: https://api.theorder.org/vc/entra/webhook"
echo "3. Test credential issuance"
echo "4. Monitor for 24 hours"
-115
View File
@@ -1,115 +0,0 @@
#!/bin/bash
# Deploy Identity Service with Entra VerifiedID to Staging
# This script automates the staging deployment
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
cd "$(dirname "$0")/../.."
# Check prerequisites
log_info "Checking prerequisites..."
if ! command -v kubectl &> /dev/null; then
log_error "kubectl not found. Please install kubectl"
exit 1
fi
if ! kubectl cluster-info &> /dev/null; then
log_error "Not connected to Kubernetes cluster"
exit 1
fi
# Check if namespace exists
NAMESPACE="the-order-staging"
if ! kubectl get namespace "${NAMESPACE}" &> /dev/null; then
log_info "Creating namespace: ${NAMESPACE}"
kubectl create namespace "${NAMESPACE}"
fi
# Check if secrets exist
log_info "Checking for Entra secrets..."
if ! kubectl get secret entra-verifiedid-secrets -n "${NAMESPACE}" &> /dev/null; then
log_warning "Entra secrets not found. Creating from template..."
log_info "Please update infra/k8s/identity-service-entra-secrets.yaml with actual values"
read -p "Press Enter after updating secrets file..."
kubectl apply -f infra/k8s/identity-service-entra-secrets.yaml -n "${NAMESPACE}"
else
log_success "Secrets found"
fi
# Build and push image (if needed)
log_info "Building Docker image..."
IMAGE_TAG="${IMAGE_TAG:-staging-$(date +%Y%m%d-%H%M%S)}"
IMAGE_NAME="ghcr.io/the-order/identity-service:${IMAGE_TAG}"
if [ "${BUILD_IMAGE:-true}" = "true" ]; then
log_info "Building image: ${IMAGE_NAME}"
docker build -t "${IMAGE_NAME}" -f services/identity/Dockerfile .
docker push "${IMAGE_NAME}"
log_success "Image built and pushed"
else
log_info "Skipping image build (set BUILD_IMAGE=false to skip)"
fi
# Update deployment with image tag
log_info "Updating deployment manifest..."
sed "s|ghcr.io/the-order/identity-service:latest|${IMAGE_NAME}|g" \
infra/k8s/identity-service-deployment-entra.yaml > /tmp/identity-deployment-staging.yaml
# Apply deployment
log_info "Deploying to staging..."
kubectl apply -f /tmp/identity-deployment-staging.yaml -n "${NAMESPACE}"
# Wait for deployment
log_info "Waiting for deployment to be ready..."
kubectl rollout status deployment/identity-service -n "${NAMESPACE}" --timeout=5m
# Verify deployment
log_info "Verifying deployment..."
if kubectl get deployment identity-service -n "${NAMESPACE}" &> /dev/null; then
REPLICAS=$(kubectl get deployment identity-service -n "${NAMESPACE}" -o jsonpath='{.status.readyReplicas}')
DESIRED=$(kubectl get deployment identity-service -n "${NAMESPACE}" -o jsonpath='{.spec.replicas}')
if [ "${REPLICAS}" = "${DESIRED}" ]; then
log_success "Deployment successful! ${REPLICAS}/${DESIRED} replicas ready"
else
log_warning "Deployment in progress: ${REPLICAS}/${DESIRED} replicas ready"
fi
fi
# Check service health
log_info "Checking service health..."
SERVICE_URL=$(kubectl get ingress identity-service-ingress -n "${NAMESPACE}" -o jsonpath='{.spec.rules[0].host}' 2>/dev/null || echo "")
if [ -n "${SERVICE_URL}" ]; then
log_info "Service URL: https://${SERVICE_URL}"
log_info "Testing health endpoint..."
sleep 5
if curl -sf "https://${SERVICE_URL}/health" > /dev/null; then
log_success "Service is healthy!"
else
log_warning "Health check failed (service may still be starting)"
fi
fi
# Display logs
log_info "Recent logs:"
kubectl logs -n "${NAMESPACE}" deployment/identity-service --tail=20 | grep -i entra || log_info "No Entra-related logs yet"
log_success "Staging deployment complete!"
log_info "Next steps:"
echo "1. Configure webhook URL in Entra VerifiedID: https://api-staging.theorder.org/vc/entra/webhook"
echo "2. Test credential issuance"
echo "3. Verify metrics collection"
echo "4. Check logs: kubectl logs -n ${NAMESPACE} deployment/identity-service -f"
+7 -7
View File
@@ -25,7 +25,7 @@ usage() {
cat << EOF
Usage: $0 [OPTIONS] [PHASES...]
Deploy The Order application to Azure/Kubernetes.
Deploy The Order application into the Sankofa Phoenix / Proxmox runtime.
OPTIONS:
-e, --environment ENV Environment (dev, stage, prod) [default: dev]
@@ -40,14 +40,14 @@ OPTIONS:
PHASES:
1 - Prerequisites
2 - Azure Infrastructure Setup
3 - Entra ID Configuration (manual)
2 - Sankofa Phoenix Target Preparation
3 - Identity Provider Secrets (manual)
4 - Database & Storage Setup
5 - Container Registry Setup
5 - Local Artifact / Runtime Preparation
6 - Application Build & Package
7 - Database Migrations
8 - Secrets Configuration (manual)
9 - Infrastructure Services Deployment
9 - Sankofa Edge & Runtime Checks
10 - Backend Services Deployment
11 - Frontend Applications Deployment
12 - Networking & Gateways
@@ -133,8 +133,8 @@ fi
# Phase scripts mapping
declare -A PHASE_SCRIPTS=(
["1"]="phase1-prerequisites.sh"
["2"]="phase2-azure-infrastructure.sh"
["3"]="phase3-entra-id.sh"
["2"]="phase2-sankofa-phoenix-target.sh"
["3"]="phase3-identity-secrets.sh"
["4"]="phase4-database-storage.sh"
["5"]="phase5-container-registry.sh"
["6"]="phase6-build-package.sh"
-67
View File
@@ -1,67 +0,0 @@
#!/bin/bash
# Enable Entra VerifiedID Service
# Provides step-by-step instructions and validates service status
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_info "Entra VerifiedID Service Activation Guide"
echo ""
# Check if Azure CLI is available
if command -v az &> /dev/null && az account show &> /dev/null; then
TENANT_ID=$(az account show --query tenantId -o tsv)
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
log_info "Detected Azure subscription:"
echo " Tenant ID: ${TENANT_ID}"
echo " Subscription ID: ${SUBSCRIPTION_ID}"
echo ""
fi
log_info "Step-by-Step Instructions to Enable Verified ID:"
echo ""
echo "1. Open Azure Portal: https://portal.azure.com"
echo "2. Navigate to: Azure Active Directory → Verified ID"
echo " Direct link: https://portal.azure.com/#view/Microsoft_AAD_IAM/VerifiedIDBlade"
echo ""
echo "3. If Verified ID is not visible:"
echo " a. Check if you have the required permissions (Global Administrator or Verified ID Administrator)"
echo " b. Verify your Azure AD tenant supports Verified ID"
echo " c. Some tenants may need to enable the feature first"
echo ""
echo "4. Click 'Get started' or 'Enable Verified ID'"
echo ""
echo "5. Wait for service activation (typically 5-10 minutes)"
echo ""
echo "6. Once enabled, you should see:"
echo " - Credentials section"
echo " - Issuance section"
echo " - Settings section"
echo ""
# Try to check service status via Azure CLI (if possible)
log_info "Attempting to verify service status..."
if command -v az &> /dev/null && az account show &> /dev/null; then
# Note: Azure CLI doesn't have direct Verified ID commands, but we can check if the service exists
log_info "Azure CLI detected. Checking tenant capabilities..."
# Check if we can access the tenant
if az ad tenant show &> /dev/null; then
log_success "Tenant access confirmed"
fi
fi
log_info "After enabling Verified ID, run:"
echo " ./scripts/deploy/create-credential-manifests.sh"
echo ""
log_success "Service activation guide complete!"
+9 -30
View File
@@ -32,38 +32,18 @@ if [ "${PNPM_VERSION}" -lt 8 ]; then
fi
log_success "pnpm version: $(pnpm --version)"
# Check Terraform version
TERRAFORM_VERSION=$(terraform version -json | jq -r '.terraform_version' | cut -d'.' -f1)
if [ "${TERRAFORM_VERSION}" -lt 1 ]; then
error_exit "Terraform version 1.5.0 or higher is required"
fi
log_success "Terraform version: $(terraform version -json | jq -r '.terraform_version')"
log_success "All tools verified"
# 1.2 Azure Account Setup
log_step "1.2 Setting up Azure account..."
# 1.2 Proxmox / Sankofa access
log_step "1.2 Verifying Sankofa Phoenix deployment access..."
check_azure_login
if [ -z "${AZURE_SUBSCRIPTION_ID}" ]; then
log_warning "AZURE_SUBSCRIPTION_ID not set. Using current subscription."
AZURE_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
fi
az account set --subscription "${AZURE_SUBSCRIPTION_ID}" || error_exit "Failed to set subscription"
SUBSCRIPTION_NAME=$(az account show --query name -o tsv)
log_success "Using Azure subscription: ${SUBSCRIPTION_NAME} (${AZURE_SUBSCRIPTION_ID})"
# Verify permissions
log_step "Checking Azure permissions..."
ROLE=$(az role assignment list --assignee "$(az account show --query user.name -o tsv)" --query "[0].roleDefinitionName" -o tsv 2>/dev/null || echo "Unknown")
if [[ "${ROLE}" != *"Contributor"* ]] && [[ "${ROLE}" != *"Owner"* ]]; then
log_warning "Current role: ${ROLE}. Contributor or Owner role recommended."
else
log_success "Permissions verified: ${ROLE}"
fi
check_proxmox_access
log_info "Portal CT host: ${ORDER_PORTAL_PUBLIC_HOST}"
log_info "Edge HAProxy host: ${ORDER_HAPROXY_HOST}"
log_info "Order public CT: ${ORDER_PORTAL_PUBLIC_VMID} (${ORDER_PORTAL_PUBLIC_IP}:${ORDER_PORTAL_PUBLIC_PORT})"
log_info "Order edge HAProxy: ${ORDER_HAPROXY_VMID} (${ORDER_HAPROXY_IP}:80)"
log_info "Phoenix public URL: ${SANKOFA_PHOENIX_URL}"
log_info "Portal public URL: ${SANKOFA_PORTAL_URL}"
# 1.3 Install Dependencies
log_step "1.3 Installing dependencies..."
@@ -105,4 +85,3 @@ save_state "phase1" "complete"
log_success "=========================================="
log_success "Phase 1: Prerequisites - COMPLETE"
log_success "=========================================="
+13 -85
View File
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Phase 10: Backend Services Deployment
# Deploy backend services to Kubernetes
# Validate backend manifests and service URLs for the Sankofa runtime.
#
set -euo pipefail
@@ -13,105 +13,33 @@ log_info "=========================================="
log_info "Phase 10: Backend Services Deployment"
log_info "=========================================="
# Verify Kubernetes access
log_step "10.1 Verifying Kubernetes access..."
if ! kubectl cluster-info &> /dev/null; then
log_info "Getting AKS credentials..."
az aks get-credentials --resource-group "${AKS_RESOURCE_GROUP}" \
--name "${AKS_NAME}" \
--overwrite-existing \
|| error_exit "Failed to get AKS credentials"
fi
kubectl cluster-info || error_exit "Kubernetes cluster not accessible"
# Ensure namespace exists
log_step "10.2 Ensuring namespace exists..."
kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - || \
log_warning "Namespace may already exist"
# Deploy External Secrets (if not already deployed)
log_step "10.3 Checking External Secrets Operator..."
if ! kubectl get crd externalsecrets.external-secrets.io &> /dev/null; then
log_info "Installing External Secrets Operator..."
kubectl apply -f https://external-secrets.io/latest/deploy/ || error_exit "Failed to install External Secrets"
log_info "Waiting for External Secrets Operator to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=external-secrets \
-n external-secrets-system \
--timeout=300s || log_warning "External Secrets Operator not ready yet"
else
log_success "External Secrets Operator already installed"
fi
# Deploy each service
log_step "10.4 Deploying backend services..."
log_step "10.1 Validating backend manifests..."
for service in "${SERVICES[@]}"; do
log_info "Deploying ${service} service..."
# Check if manifests exist
SERVICE_DIR="${K8S_DIR}/base/${service}"
if [ ! -d "${SERVICE_DIR}" ]; then
log_warning "Kubernetes manifests not found for ${service} at ${SERVICE_DIR}"
log_info "Skipping ${service} deployment"
log_warning "Backend manifest directory not found for ${service}: ${SERVICE_DIR}"
continue
fi
# Apply manifests
kubectl apply -f "${SERVICE_DIR}" -n "${NAMESPACE}" || error_exit "Failed to deploy ${service}"
# Wait for deployment
log_info "Waiting for ${service} deployment..."
kubectl wait --for=condition=available \
deployment/"${service}" \
-n "${NAMESPACE}" \
--timeout=300s || log_warning "${service} deployment not ready yet"
# Verify pods
PODS=$(kubectl get pods -l app="${service}" -n "${NAMESPACE}" --no-headers 2>/dev/null | wc -l)
if [ "${PODS}" -gt 0 ]; then
log_success "${service} deployed (${PODS} pod(s))"
# Check pod status
kubectl get pods -l app="${service}" -n "${NAMESPACE}"
if command -v kubectl >/dev/null; then
kubectl apply --dry-run=client -f "${SERVICE_DIR}" >/dev/null \
&& log_success "${service} manifests render cleanly" \
|| log_warning "${service} manifests failed dry-run validation"
else
log_warning "${service} pods not found"
log_info "kubectl not installed; skipping manifest validation for ${service}"
fi
done
# Verify service endpoints
log_step "10.5 Verifying service endpoints..."
log_step "10.2 Reporting expected service URLs..."
for service in "${SERVICES[@]}"; do
if kubectl get svc "${service}" -n "${NAMESPACE}" &> /dev/null; then
log_success "Service ${service} endpoint created"
# Test health endpoint (if accessible)
PORT="${SERVICE_PORTS[$service]}"
if [ -n "${PORT}" ]; then
log_info "Testing ${service} health endpoint on port ${PORT}..."
kubectl run test-${service}-health \
--image=curlimages/curl \
--rm -i --restart=Never \
-- curl -f "http://${service}:${PORT}/health" \
-n "${NAMESPACE}" 2>/dev/null && \
log_success "${service} health check passed" || \
log_warning "${service} health check failed or endpoint not ready"
fi
else
log_warning "Service ${service} endpoint not found"
fi
log_info "${service} expected port: ${SERVICE_PORTS[$service]}"
done
log_info "Backend deployment remains environment-specific. Use the Phoenix edge / CT flow for the public frontend and connect backend services through your chosen runtime separately."
# Save state
save_state "phase10" "complete"
log_success "=========================================="
log_success "Phase 10: Backend Services - COMPLETE"
log_success "=========================================="
+20 -73
View File
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Phase 11: Frontend Applications Deployment
# Deploy portal applications to Kubernetes
# Deploy portal applications to the Sankofa Phoenix / Proxmox runtime.
#
set -euo pipefail
@@ -13,82 +13,29 @@ log_info "=========================================="
log_info "Phase 11: Frontend Applications Deployment"
log_info "=========================================="
OVERLAY_DIR="${K8S_DIR}/overlays/${ENVIRONMENT}"
log_step "11.1 Deploying portal-public to the Order runtime CT..."
bash "${SCRIPT_DIR}/sync-portal-public-to-sankofa-phoenix.sh" || error_exit "Failed to sync portal-public to Sankofa Phoenix"
# Verify Kubernetes access
if ! kubectl cluster-info &> /dev/null; then
az aks get-credentials --resource-group "${AKS_RESOURCE_GROUP}" \
--name "${AKS_NAME}" \
--overwrite-existing
fi
log_step "11.2 Refreshing the Order HAProxy edge..."
env \
ORDER_HAPROXY_BACKEND_HOST="${ORDER_HAPROXY_BACKEND_HOST}" \
ORDER_HAPROXY_BACKEND_PORT="${ORDER_HAPROXY_BACKEND_PORT}" \
PROXMOX_ORDER_HAPROXY_NODE="${ORDER_HAPROXY_HOST}" \
bash "${PROJECT_ROOT}/../scripts/deployment/provision-order-haproxy-10210.sh" \
|| error_exit "Failed to refresh order-haproxy"
# Ensure namespace exists
kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f -
log_step "11.3 Verifying LAN and public health..."
curl -fsS "http://${ORDER_PORTAL_PUBLIC_IP}:${ORDER_PORTAL_PUBLIC_PORT}/api/health" >/dev/null \
&& log_success "Direct CT health check passed" \
|| log_warning "Direct CT health check failed"
if [ -d "${OVERLAY_DIR}" ]; then
log_step "11.0 Validating and applying ${ENVIRONMENT} overlay..."
kubectl kustomize "${OVERLAY_DIR}" > /dev/null || error_exit "Failed to render overlay ${OVERLAY_DIR}"
kubectl apply -k "${OVERLAY_DIR}" || error_exit "Failed to apply overlay ${OVERLAY_DIR}"
else
log_warning "Overlay not found for environment ${ENVIRONMENT}: ${OVERLAY_DIR}"
log_info "Falling back to per-app base manifests"
fi
curl -fsS -H "Host: the-order.sankofa.nexus" "http://${ORDER_HAPROXY_IP}/api/health" >/dev/null \
&& log_success "HAProxy health check passed" \
|| log_warning "HAProxy health check failed"
# Deploy each app
log_step "11.1 Deploying frontend applications..."
for app in "${APPS[@]}"; do
log_info "Deploying ${app}..."
APP_DIR="${K8S_DIR}/base/${app}"
if [ ! -d "${APP_DIR}" ]; then
log_warning "Kubernetes manifests not found for ${app} at ${APP_DIR}"
log_info "Skipping ${app} deployment"
continue
fi
if [ ! -d "${OVERLAY_DIR}" ]; then
kubectl apply -f "${APP_DIR}" -n "${NAMESPACE}" || error_exit "Failed to deploy ${app}"
fi
IMAGE_NAME="${ACR_NAME}.azurecr.io/${app}:${IMAGE_TAG}"
CONTAINER_NAME="${app}"
log_info "Updating ${app} image to ${IMAGE_NAME}..."
kubectl set image deployment/"${app}" \
"${CONTAINER_NAME}"="${IMAGE_NAME}" \
-n "${NAMESPACE}" || error_exit "Failed to set image for ${app}"
# Wait for deployment
log_info "Waiting for ${app} deployment..."
kubectl rollout status \
deployment/"${app}" \
-n "${NAMESPACE}" \
--timeout=300s || log_warning "${app} deployment not ready yet"
# Verify pods
PODS=$(kubectl get pods -l app="${app}" -n "${NAMESPACE}" --no-headers 2>/dev/null | wc -l)
if [ "${PODS}" -gt 0 ]; then
log_success "${app} deployed (${PODS} pod(s))"
kubectl get pods -l app="${app}" -n "${NAMESPACE}"
else
log_warning "${app} pods not found"
fi
if kubectl get svc "${app}" -n "${NAMESPACE}" &> /dev/null; then
PORT="${SERVICE_PORTS[$app]}"
if [ -n "${PORT}" ]; then
log_info "Testing ${app} health endpoint on port ${PORT}..."
kubectl run test-${app}-health \
--image=curlimages/curl \
--rm -i --restart=Never \
-n "${NAMESPACE}" -- \
curl -fsS "http://${app}:${PORT}/api/health" >/dev/null && \
log_success "${app} health check passed" || \
log_warning "${app} health check failed or endpoint not ready"
fi
fi
done
curl -fsS "${THE_ORDER_PUBLIC_URL}/api/health" >/dev/null \
&& log_success "Public health check passed" \
|| log_warning "Public health check failed"
# Save state
save_state "phase11" "complete"
+14 -59
View File
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Phase 13: Monitoring & Observability
# Configure Application Insights, Log Analytics, alerts, dashboards
# Verify the Phoenix-native runtime is producing healthy service and journald signals.
#
set -euo pipefail
@@ -13,68 +13,24 @@ log_info "=========================================="
log_info "Phase 13: Monitoring & Observability"
log_info "=========================================="
# 13.1 Application Insights
log_step "13.1 Creating Application Insights..."
check_proxmox_access
APP_INSIGHTS_NAME="${PROJECT_NAME}-${ENVIRONMENT}-ai"
APP_INSIGHTS_EXISTS=$(az monitor app-insights component show \
--app "${APP_INSIGHTS_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--query name -o tsv 2>/dev/null || echo "")
log_step "13.1 Verifying service status inside the Order CT..."
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}" \
"pct exec ${ORDER_PORTAL_PUBLIC_VMID} -- systemctl is-active ${ORDER_PORTAL_PUBLIC_SERVICE}" \
&& log_success "The Order portal service is active" \
|| log_warning "The Order portal service is not active"
if [ -z "${APP_INSIGHTS_EXISTS}" ]; then
log_info "Creating Application Insights resource..."
az monitor app-insights component create \
--app "${APP_INSIGHTS_NAME}" \
--location "${AZURE_REGION}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--application-type web \
|| log_warning "Failed to create Application Insights"
else
log_success "Application Insights already exists"
fi
# Get instrumentation key
INSTRUMENTATION_KEY=$(az monitor app-insights component show \
--app "${APP_INSIGHTS_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--query instrumentationKey -o tsv 2>/dev/null || echo "")
if [ -n "${INSTRUMENTATION_KEY}" ]; then
log_info "Storing instrumentation key in Key Vault..."
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "app-insights-instrumentation-key" \
--value "${INSTRUMENTATION_KEY}" \
|| log_warning "Failed to store instrumentation key"
fi
# 13.2 Log Analytics Workspace
log_step "13.2 Creating Log Analytics workspace..."
LOG_ANALYTICS_NAME="${PROJECT_NAME}-${ENVIRONMENT}-logs"
LOG_ANALYTICS_EXISTS=$(az monitor log-analytics workspace show \
--workspace-name "${LOG_ANALYTICS_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--query name -o tsv 2>/dev/null || echo "")
if [ -z "${LOG_ANALYTICS_EXISTS}" ]; then
log_info "Creating Log Analytics workspace..."
az monitor log-analytics workspace create \
--workspace-name "${LOG_ANALYTICS_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--location "${AZURE_REGION}" \
|| log_warning "Failed to create Log Analytics workspace"
else
log_success "Log Analytics workspace already exists"
fi
log_step "13.2 Showing recent journald entries..."
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}" \
"pct exec ${ORDER_PORTAL_PUBLIC_VMID} -- journalctl -u ${ORDER_PORTAL_PUBLIC_SERVICE} -n 20 --no-pager" \
|| log_warning "Unable to fetch recent journald entries"
log_info "Monitoring configuration complete"
log_info "Next steps (manual):"
log_info " 1. Configure alerts in Azure Portal"
log_info " 2. Set up Grafana dashboards"
log_info " 3. Configure log queries"
log_info " 4. Set up notification channels"
log_info " 1. Add the CT service to your central log shipping path if desired"
log_info " 2. Add synthetic checks for ${THE_ORDER_PUBLIC_URL}/api/health"
log_info " 3. Keep Phoenix public health in the shared external monitor set"
# Save state
save_state "phase13" "complete"
@@ -82,4 +38,3 @@ save_state "phase13" "complete"
log_success "=========================================="
log_success "Phase 13: Monitoring & Observability - COMPLETE"
log_success "=========================================="
+15 -24
View File
@@ -16,31 +16,23 @@ log_info "=========================================="
# 14.1 Health Checks
log_step "14.1 Running health checks..."
if ! kubectl cluster-info &> /dev/null; then
az aks get-credentials --resource-group "${AKS_RESOURCE_GROUP}" \
--name "${AKS_NAME}" \
--overwrite-existing
fi
check_proxmox_access
# Check all pods
log_info "Checking pod status..."
kubectl get pods -n "${NAMESPACE}" || log_warning "Failed to get pods"
curl -fsS "http://${ORDER_PORTAL_PUBLIC_IP}:${ORDER_PORTAL_PUBLIC_PORT}/api/health" >/dev/null \
&& log_success "Direct Order CT health check passed" \
|| log_warning "Direct Order CT health check failed"
# Check service endpoints
log_info "Checking service endpoints..."
for service in "${SERVICES[@]}"; do
if kubectl get svc "${service}" -n "${NAMESPACE}" &> /dev/null; then
PORT="${SERVICE_PORTS[$service]}"
log_info "Testing ${service} health endpoint..."
kubectl run test-${service}-health \
--image=curlimages/curl \
--rm -i --restart=Never \
-- curl -f "http://${service}:${PORT}/health" \
-n "${NAMESPACE}" 2>/dev/null && \
log_success "${service} health check passed" || \
log_warning "${service} health check failed"
fi
done
curl -fsS -H "Host: the-order.sankofa.nexus" "http://${ORDER_HAPROXY_IP}/api/health" >/dev/null \
&& log_success "Order HAProxy health check passed" \
|| log_warning "Order HAProxy health check failed"
curl -fsS "${THE_ORDER_PUBLIC_URL}/api/health" >/dev/null \
&& log_success "Public The Order health check passed" \
|| log_warning "Public The Order health check failed"
curl -fsS "${SANKOFA_PHOENIX_URL}/health" >/dev/null \
&& log_success "Phoenix public health check passed" \
|| log_warning "Phoenix public health check failed"
# 14.2 Integration Testing
log_step "14.2 Running integration tests..."
@@ -66,4 +58,3 @@ save_state "phase14" "complete"
log_success "=========================================="
log_success "Phase 14: Testing & Validation - COMPLETE"
log_success "=========================================="
+12 -29
View File
@@ -21,39 +21,23 @@ if [ "${ENVIRONMENT}" != "prod" ]; then
exit 0
fi
# Update replica counts
log_info "Updating replica counts for production..."
for service in "${SERVICES[@]}"; do
kubectl scale deployment "${service}" \
--replicas=3 \
-n "${NAMESPACE}" \
|| log_warning "Failed to scale ${service}"
done
log_info "Production runtime target: ${ORDER_PORTAL_PUBLIC_VMID} -> ${ORDER_HAPROXY_VMID} -> ${THE_ORDER_PUBLIC_URL}"
# 15.2 Backup Configuration
log_step "15.2 Configuring backups..."
log_step "15.2 Verifying production health..."
curl -fsS "${THE_ORDER_PUBLIC_URL}/api/health" >/dev/null \
&& log_success "Production public health check passed" \
|| log_warning "Production public health check failed"
# Database backups
log_info "Configuring database backups..."
az postgres server backup create \
--resource-group "${AKS_RESOURCE_GROUP}" \
--server-name "${POSTGRES_SERVER_NAME}" \
--backup-name "daily-backup-$(date +%Y%m%d)" \
|| log_warning "Failed to create database backup"
# Storage backups
log_info "Enabling storage versioning..."
az storage account blob-service-properties update \
--account-name "${STORAGE_ACCOUNT_NAME}" \
--enable-versioning true \
|| log_warning "Failed to enable versioning"
log_step "15.3 Backup / DR reminders..."
log_info "Ensure CT backups for ${ORDER_PORTAL_PUBLIC_VMID} and ${ORDER_HAPROXY_VMID} are present in the Proxmox schedule."
log_info "Ensure database backups are configured in the backing datastore used by The Order."
log_info "Production hardening complete"
log_info "Next steps (manual):"
log_info " 1. Configure resource limits in deployments"
log_info " 2. Set up automated backups"
log_info " 3. Configure disaster recovery"
log_info " 4. Review security settings"
log_info " 1. Review CT CPU / memory limits"
log_info " 2. Confirm Proxmox backup coverage"
log_info " 3. Confirm database backup coverage"
log_info " 4. Review public health and uptime monitors"
log_info " 5. Update documentation"
# Save state
@@ -62,4 +46,3 @@ save_state "phase15" "complete"
log_success "=========================================="
log_success "Phase 15: Production Hardening - COMPLETE"
log_success "=========================================="
@@ -1,103 +0,0 @@
#!/bin/bash
#
# Phase 2: Azure Infrastructure Setup
# Terraform infrastructure deployment
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "=========================================="
log_info "Phase 2: Azure Infrastructure Setup"
log_info "=========================================="
# 2.1 Azure Subscription Preparation
log_step "2.1 Preparing Azure subscription..."
cd "${PROJECT_ROOT}"
# Run Azure setup scripts
if [ -f "${INFRA_DIR}/scripts/azure-setup.sh" ]; then
log_info "Running Azure setup script..."
bash "${INFRA_DIR}/scripts/azure-setup.sh" || error_exit "Azure setup script failed"
else
log_warning "Azure setup script not found, skipping..."
fi
# Register resource providers
if [ -f "${INFRA_DIR}/scripts/azure-register-providers.sh" ]; then
log_info "Registering Azure resource providers..."
bash "${INFRA_DIR}/scripts/azure-register-providers.sh" || error_exit "Provider registration failed"
else
log_warning "Provider registration script not found, skipping..."
fi
# 2.2 Terraform Infrastructure Deployment
log_step "2.2 Deploying Terraform infrastructure..."
cd "${TERRAFORM_DIR}"
# Initialize Terraform
log_info "Initializing Terraform..."
terraform init || error_exit "Terraform initialization failed"
# Create initial state storage if needed
if [ "${CREATE_STATE_STORAGE:-false}" = "true" ]; then
log_info "Creating Terraform state storage..."
terraform plan -target=azurerm_resource_group.terraform_state \
-target=azurerm_storage_account.terraform_state \
-target=azurerm_storage_container.terraform_state \
|| log_warning "State storage resources may already exist"
terraform apply -target=azurerm_resource_group.terraform_state \
-target=azurerm_storage_account.terraform_state \
-target=azurerm_storage_container.terraform_state \
|| log_warning "State storage may already exist"
fi
# Plan infrastructure
log_info "Planning infrastructure changes..."
terraform plan -out=tfplan || error_exit "Terraform plan failed"
# Review plan (optional)
if [ "${AUTO_APPLY:-false}" != "true" ]; then
log_warning "Terraform plan created. Review tfplan before applying."
log_info "To apply: terraform apply tfplan"
log_info "To auto-apply: set AUTO_APPLY=true"
else
log_info "Applying Terraform plan..."
terraform apply tfplan || error_exit "Terraform apply failed"
log_success "Infrastructure deployed"
fi
# Get outputs
log_info "Retrieving Terraform outputs..."
terraform output -json > "${STATE_DIR}/terraform-outputs.json" || log_warning "Failed to save outputs"
# 2.3 Kubernetes Configuration
log_step "2.3 Configuring Kubernetes..."
# Get AKS credentials
log_info "Getting AKS credentials..."
az aks get-credentials --resource-group "${AKS_RESOURCE_GROUP}" \
--name "${AKS_NAME}" \
--overwrite-existing \
|| log_warning "AKS cluster may not exist yet"
# Verify cluster access
if kubectl cluster-info &> /dev/null; then
log_success "Kubernetes cluster accessible"
kubectl get nodes || log_warning "No nodes found"
else
log_warning "Kubernetes cluster not accessible yet"
fi
# Save state
save_state "phase2" "complete"
log_success "=========================================="
log_success "Phase 2: Azure Infrastructure - COMPLETE"
log_success "=========================================="
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
#
# Phase 2: Sankofa Phoenix Target Preparation
# Validate the Proxmox / CT targets used by the Phoenix-native deployment flow.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "=========================================="
log_info "Phase 2: Sankofa Phoenix Target Preparation"
log_info "=========================================="
log_step "2.1 Verifying Proxmox and CT targets..."
check_proxmox_access
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}" \
"pct status ${ORDER_PORTAL_PUBLIC_VMID}" \
|| error_exit "Failed to verify Order public CT placement on ${ORDER_PORTAL_PUBLIC_HOST}"
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_HAPROXY_HOST}" \
"pct status ${ORDER_HAPROXY_VMID}" \
|| error_exit "Failed to verify Order HAProxy placement on ${ORDER_HAPROXY_HOST}"
log_step "2.2 Previewing The Order edge configuration..."
env \
ORDER_HAPROXY_BACKEND_HOST="${ORDER_HAPROXY_BACKEND_HOST}" \
ORDER_HAPROXY_BACKEND_PORT="${ORDER_HAPROXY_BACKEND_PORT}" \
PROXMOX_ORDER_HAPROXY_NODE="${ORDER_HAPROXY_HOST}" \
bash "${PROJECT_ROOT}/../scripts/deployment/provision-order-haproxy-10210.sh" --dry-run \
|| log_warning "Unable to preview order-haproxy config from the parent Proxmox workspace"
log_step "2.3 Probing Sankofa Phoenix public health..."
curl -fsS "${SANKOFA_PHOENIX_URL}/health" >/dev/null \
&& log_success "Phoenix public health check passed" \
|| log_warning "Phoenix public health check failed or is not exposed at /health"
save_state "phase2" "complete"
log_success "=========================================="
log_success "Phase 2: Sankofa Phoenix Target Preparation - COMPLETE"
log_success "=========================================="
-48
View File
@@ -1,48 +0,0 @@
#!/bin/bash
#
# Phase 3: Entra ID Configuration
# Note: Most steps require manual configuration in Azure Portal
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "=========================================="
log_info "Phase 3: Entra ID Configuration"
log_info "=========================================="
log_warning "This phase requires manual steps in Azure Portal"
log_info "See docs/deployment/DEPLOYMENT_GUIDE.md for detailed instructions"
# Check if secrets already exist
log_step "3.1 Checking for existing Entra ID configuration..."
ENTRA_TENANT_ID=$(az keyvault secret show \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-tenant-id" \
--query value -o tsv 2>/dev/null || echo "")
if [ -n "${ENTRA_TENANT_ID}" ]; then
log_success "Entra ID configuration found in Key Vault"
log_info "Tenant ID: ${ENTRA_TENANT_ID}"
else
log_warning "Entra ID configuration not found"
log_info "Please complete manual steps:"
log_info " 1. Create App Registration in Azure Portal"
log_info " 2. Configure API permissions"
log_info " 3. Create client secret"
log_info " 4. Enable Verified ID service"
log_info " 5. Create credential manifest"
log_info ""
log_info "Then run: scripts/deploy/store-entra-secrets.sh"
fi
# Save state
save_state "phase3" "manual-steps-required"
log_success "=========================================="
log_success "Phase 3: Entra ID - Manual steps required"
log_success "=========================================="
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
#
# Phase 3: Identity Provider Secrets
# Manual identity-provider setup, recorded in the local operator secret file.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "=========================================="
log_info "Phase 3: Identity Provider Secrets"
log_info "=========================================="
SECRETS_FILE="${STATE_DIR}/secrets/${ENVIRONMENT}.env"
log_warning "This phase requires manual identity-provider configuration"
log_info "See docs/deployment/DEPLOYMENT_GUIDE.md for detailed instructions"
# Check if secrets already exist
log_step "3.1 Checking for existing identity-provider configuration..."
if [ -f "${SECRETS_FILE}" ]; then
# shellcheck source=/dev/null
source "${SECRETS_FILE}"
fi
if [ -n "${OIDC_ISSUER:-}" ] || [ -n "${VC_ISSUER_DID:-}" ] || [ -n "${VC_ISSUER_DOMAIN:-}" ]; then
log_success "Identity-provider configuration found in ${SECRETS_FILE}"
[ -n "${OIDC_ISSUER:-}" ] && log_info "OIDC issuer: ${OIDC_ISSUER}"
[ -n "${VC_ISSUER_DID:-}" ] && log_info "VC issuer DID: ${VC_ISSUER_DID}"
[ -n "${VC_ISSUER_DOMAIN:-}" ] && log_info "VC issuer domain: ${VC_ISSUER_DOMAIN}"
else
log_warning "Identity-provider configuration not found"
log_info "Please complete manual steps:"
log_info " 1. Set OIDC issuer and client details if this environment uses OIDC"
log_info " 2. Set VC_ISSUER_DID or VC_ISSUER_DOMAIN for credential issuance"
log_info " 3. Store those values in ${SECRETS_FILE}"
log_info ""
fi
# Save state
save_state "phase3" "manual-steps-required"
log_success "=========================================="
log_success "Phase 3: Identity Provider Secrets - Manual steps required"
log_success "=========================================="
+24 -64
View File
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Phase 4: Database & Storage Setup
# Configure PostgreSQL, Storage Accounts, Redis, OpenSearch
# Validate PostgreSQL, storage, Redis, and search configuration inputs for the Sankofa runtime.
#
set -euo pipefail
@@ -13,77 +13,37 @@ log_info "=========================================="
log_info "Phase 4: Database & Storage Setup"
log_info "=========================================="
# 4.1 PostgreSQL Database Setup
log_step "4.1 Configuring PostgreSQL database..."
# Check if database exists
DB_EXISTS=$(az postgres db show \
--resource-group "${AKS_RESOURCE_GROUP}" \
--server-name "${POSTGRES_SERVER_NAME}" \
--name "${POSTGRES_DB_NAME}" \
--query name -o tsv 2>/dev/null || echo "")
if [ -z "${DB_EXISTS}" ]; then
log_info "Creating database ${POSTGRES_DB_NAME}..."
az postgres db create \
--resource-group "${AKS_RESOURCE_GROUP}" \
--server-name "${POSTGRES_SERVER_NAME}" \
--name "${POSTGRES_DB_NAME}" \
|| error_exit "Failed to create database"
log_success "Database created"
log_step "4.1 Checking database configuration inputs..."
if [ -n "${DATABASE_URL:-}" ]; then
log_success "DATABASE_URL is set"
else
log_success "Database already exists"
log_warning "DATABASE_URL is not set. Phase 7 will require it."
fi
# Configure firewall rules for AKS
log_step "4.2 Configuring database firewall rules..."
# Get AKS outbound IPs (if using NAT gateway)
# For now, allow Azure services
az postgres server firewall-rule create \
--resource-group "${AKS_RESOURCE_GROUP}" \
--server-name "${POSTGRES_SERVER_NAME}" \
--name "AllowAzureServices" \
--start-ip-address "0.0.0.0" \
--end-ip-address "0.0.0.0" \
--output none 2>/dev/null || log_info "Firewall rule may already exist"
log_success "Database firewall configured"
# 4.2 Storage Account Setup
log_step "4.3 Configuring storage accounts..."
# Verify storage account exists
STORAGE_EXISTS=$(az storage account show \
--name "${STORAGE_ACCOUNT_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--query name -o tsv 2>/dev/null || echo "")
if [ -z "${STORAGE_EXISTS}" ]; then
log_warning "Storage account ${STORAGE_ACCOUNT_NAME} not found"
log_info "Storage account should be created by Terraform"
else
log_success "Storage account found"
# Create containers
CONTAINERS=("intake-documents" "dataroom-deals" "credentials")
for container in "${CONTAINERS[@]}"; do
log_info "Creating container: ${container}..."
az storage container create \
--name "${container}" \
--account-name "${STORAGE_ACCOUNT_NAME}" \
--auth-mode login \
--output none 2>/dev/null && \
log_success "Container ${container} created" || \
log_info "Container ${container} may already exist"
done
if [ -n "${ORDER_POSTGRES_PRIMARY:-}" ]; then
log_info "Order PostgreSQL primary: ${ORDER_POSTGRES_PRIMARY}"
fi
log_step "4.2 Checking storage configuration inputs..."
if [ -n "${ORDER_STORAGE_ROOT:-}" ]; then
log_success "ORDER_STORAGE_ROOT is set to ${ORDER_STORAGE_ROOT}"
else
log_warning "ORDER_STORAGE_ROOT is not set. Configure it if document storage is required."
fi
log_step "4.3 Checking Redis / search configuration inputs..."
if [ -n "${ORDER_REDIS_IP:-}" ]; then
log_info "Order Redis host: ${ORDER_REDIS_IP}"
fi
if [ -n "${IP_ORDER_OPENSEARCH:-}" ]; then
log_info "Order OpenSearch host: ${IP_ORDER_OPENSEARCH}"
fi
log_info "Database and storage validation complete. Create or update the underlying services through the Proxmox runtime as needed."
# Save state
save_state "phase4" "complete"
log_success "=========================================="
log_success "Phase 4: Database & Storage - COMPLETE"
log_success "=========================================="
+22 -44
View File
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Phase 5: Container Registry Setup
# Configure Azure Container Registry and attach to AKS
# Phase 5: Local Artifact / Runtime Preparation
# Prepare local image tags and artifact directories for the Phoenix-native deployment flow.
#
set -euo pipefail
@@ -10,55 +10,33 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "=========================================="
log_info "Phase 5: Container Registry Setup"
log_info "Phase 5: Local Artifact / Runtime Preparation"
log_info "=========================================="
# 5.1 Verify ACR exists
log_step "5.1 Verifying Azure Container Registry..."
log_step "5.1 Verifying local Docker runtime..."
docker info >/dev/null || error_exit "Docker is not running"
log_success "Docker runtime available"
ACR_EXISTS=$(az acr show \
--name "${ACR_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--query name -o tsv 2>/dev/null || echo "")
log_step "5.2 Preparing artifact directories..."
mkdir -p "${ARTIFACTS_DIR}/images"
log_success "Artifacts directory ready at ${ARTIFACTS_DIR}"
if [ -z "${ACR_EXISTS}" ]; then
log_warning "ACR ${ACR_NAME} not found"
log_info "ACR should be created by Terraform"
log_info "Skipping ACR configuration"
exit 0
fi
log_success "ACR found: ${ACR_NAME}"
# 5.2 Configure ACR access
log_step "5.2 Configuring ACR access..."
# Enable admin user (or use managed identity)
az acr update --name "${ACR_NAME}" --admin-enabled true \
|| log_warning "Failed to enable admin user (may already be enabled)"
# 5.3 Attach ACR to AKS
log_step "5.3 Attaching ACR to AKS..."
az aks update \
--name "${AKS_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--attach-acr "${ACR_NAME}" \
|| log_warning "Failed to attach ACR (may already be attached)"
log_success "ACR attached to AKS"
# 5.4 Test ACR access
log_step "5.4 Testing ACR access..."
az acr login --name "${ACR_NAME}" || error_exit "Failed to login to ACR"
log_success "ACR access verified"
log_step "5.3 Recording image naming convention..."
{
echo "IMAGE_REGISTRY=${IMAGE_REGISTRY}"
echo "IMAGE_TAG=${IMAGE_TAG}"
for service in "${SERVICES[@]}"; do
echo "${service}=${IMAGE_REGISTRY}/${service}:${IMAGE_TAG}"
done
for app in "${APPS[@]}"; do
echo "${app}=${IMAGE_REGISTRY}/${app}:${IMAGE_TAG}"
done
} > "${ARTIFACTS_DIR}/image-manifest-${ENVIRONMENT}.txt"
log_success "Image manifest written to ${ARTIFACTS_DIR}/image-manifest-${ENVIRONMENT}.txt"
# Save state
save_state "phase5" "complete"
log_success "=========================================="
log_success "Phase 5: Container Registry - COMPLETE"
log_success "Phase 5: Local Artifact / Runtime Preparation - COMPLETE"
log_success "=========================================="
+16 -25
View File
@@ -46,19 +46,15 @@ done
log_success "Backend services built"
# 6.4 Create Docker Images
log_step "6.4 Creating Docker images..."
# 6.4 Create local Docker images
log_step "6.4 Creating local Docker images..."
# Check if Docker is running
if ! docker info &> /dev/null; then
error_exit "Docker is not running"
fi
# Login to ACR
log_info "Logging into Azure Container Registry..."
az acr login --name "${ACR_NAME}" || error_exit "Failed to login to ACR"
# Build and push service images
# Build service images
for service in "${SERVICES[@]}"; do
DOCKERFILE="${PROJECT_ROOT}/services/${service}/Dockerfile"
@@ -68,8 +64,8 @@ for service in "${SERVICES[@]}"; do
continue
fi
IMAGE_NAME="${ACR_NAME}.azurecr.io/${service}:${IMAGE_TAG}"
IMAGE_NAME_SHA="${ACR_NAME}.azurecr.io/${service}:$(git rev-parse --short HEAD 2>/dev/null || echo 'latest')"
IMAGE_NAME="${IMAGE_REGISTRY}/${service}:${IMAGE_TAG}"
IMAGE_NAME_SHA="${IMAGE_REGISTRY}/${service}:$(git rev-parse --short HEAD 2>/dev/null || echo 'latest')"
log_info "Building ${service} image..."
docker build -t "${IMAGE_NAME}" \
@@ -77,14 +73,10 @@ for service in "${SERVICES[@]}"; do
-f "${DOCKERFILE}" \
"${PROJECT_ROOT}" || error_exit "Failed to build ${service} image"
log_info "Pushing ${service} image..."
docker push "${IMAGE_NAME}" || error_exit "Failed to push ${service} image"
docker push "${IMAGE_NAME_SHA}" || log_warning "Failed to push ${service} SHA image"
log_success "${service} image built and pushed"
log_success "${service} image built locally"
done
# Build and push app images
# Build app images
for app in "${APPS[@]}"; do
DOCKERFILE="${PROJECT_ROOT}/apps/${app}/Dockerfile"
@@ -94,8 +86,8 @@ for app in "${APPS[@]}"; do
continue
fi
IMAGE_NAME="${ACR_NAME}.azurecr.io/${app}:${IMAGE_TAG}"
IMAGE_NAME_SHA="${ACR_NAME}.azurecr.io/${app}:$(git rev-parse --short HEAD 2>/dev/null || echo 'latest')"
IMAGE_NAME="${IMAGE_REGISTRY}/${app}:${IMAGE_TAG}"
IMAGE_NAME_SHA="${IMAGE_REGISTRY}/${app}:$(git rev-parse --short HEAD 2>/dev/null || echo 'latest')"
log_info "Building ${app} image..."
docker build -t "${IMAGE_NAME}" \
@@ -103,11 +95,7 @@ for app in "${APPS[@]}"; do
-f "${DOCKERFILE}" \
"${PROJECT_ROOT}" || error_exit "Failed to build ${app} image"
log_info "Pushing ${app} image..."
docker push "${IMAGE_NAME}" || error_exit "Failed to push ${app} image"
docker push "${IMAGE_NAME_SHA}" || log_warning "Failed to push ${app} SHA image"
log_success "${app} image built and pushed"
log_success "${app} image built locally"
done
# Sign images with Cosign (if available)
@@ -115,13 +103,13 @@ if command -v cosign &> /dev/null; then
log_step "6.5 Signing images with Cosign..."
for service in "${SERVICES[@]}"; do
IMAGE_NAME="${ACR_NAME}.azurecr.io/${service}:${IMAGE_TAG}"
IMAGE_NAME="${IMAGE_REGISTRY}/${service}:${IMAGE_TAG}"
log_info "Signing ${service} image..."
cosign sign --yes "${IMAGE_NAME}" || log_warning "Failed to sign ${service} image"
done
for app in "${APPS[@]}"; do
IMAGE_NAME="${ACR_NAME}.azurecr.io/${app}:${IMAGE_TAG}"
IMAGE_NAME="${IMAGE_REGISTRY}/${app}:${IMAGE_TAG}"
log_info "Signing ${app} image..."
cosign sign --yes "${IMAGE_NAME}" || log_warning "Failed to sign ${app} image"
done
@@ -131,10 +119,13 @@ else
log_warning "Cosign not found, skipping image signing"
fi
log_step "6.5 Preparing Phoenix deployment artifact..."
bash "${SCRIPT_DIR}/sync-portal-public-to-sankofa-phoenix.sh" --dry-run --skip-build \
|| log_warning "Phoenix artifact preview failed"
# Save state
save_state "phase6" "complete"
log_success "=========================================="
log_success "Phase 6: Build & Package - COMPLETE"
log_success "=========================================="
+7 -11
View File
@@ -15,17 +15,14 @@ log_info "=========================================="
cd "${PROJECT_ROOT}"
# Get database URL from Key Vault or environment
# Get database URL from environment or a local deployment secret file.
if [ -z "${DATABASE_URL:-}" ] && [ -f "${STATE_DIR}/secrets/${ENVIRONMENT}.env" ]; then
# shellcheck source=/dev/null
source "${STATE_DIR}/secrets/${ENVIRONMENT}.env"
fi
if [ -z "${DATABASE_URL:-}" ]; then
log_info "Retrieving DATABASE_URL from Azure Key Vault..."
DATABASE_URL=$(az keyvault secret show \
--vault-name "${KEY_VAULT_NAME}" \
--name "database-url-${ENVIRONMENT}" \
--query value -o tsv 2>/dev/null || echo "")
if [ -z "${DATABASE_URL}" ]; then
error_exit "DATABASE_URL not found in Key Vault and not set in environment"
fi
error_exit "DATABASE_URL not found in the environment or ${STATE_DIR}/secrets/${ENVIRONMENT}.env"
fi
log_step "7.1 Running database migrations for ${ENVIRONMENT}..."
@@ -67,4 +64,3 @@ save_state "phase7" "complete"
log_success "=========================================="
log_success "Phase 7: Database Migrations - COMPLETE"
log_success "=========================================="
+30 -61
View File
@@ -1,8 +1,7 @@
#!/bin/bash
#
# Phase 8: Secrets Configuration
# Store secrets in Azure Key Vault
# Note: Some secrets may need to be set manually
# Store deployment secrets in a local operator-controlled env file.
#
set -euo pipefail
@@ -14,75 +13,46 @@ log_info "=========================================="
log_info "Phase 8: Secrets Configuration"
log_info "=========================================="
# Verify Key Vault exists
log_step "8.1 Verifying Azure Key Vault..."
SECRETS_DIR="${STATE_DIR}/secrets"
SECRETS_FILE="${SECRETS_DIR}/${ENVIRONMENT}.env"
KV_EXISTS=$(az keyvault show \
--name "${KEY_VAULT_NAME}" \
--resource-group "${AKS_RESOURCE_GROUP}" \
--query name -o tsv 2>/dev/null || echo "")
log_step "8.1 Preparing local secrets file..."
mkdir -p "${SECRETS_DIR}"
if [ -z "${KV_EXISTS}" ]; then
error_exit "Key Vault ${KEY_VAULT_NAME} not found. Create it first with Terraform."
if [ ! -f "${SECRETS_FILE}" ]; then
touch "${SECRETS_FILE}"
chmod 600 "${SECRETS_FILE}"
log_success "Created ${SECRETS_FILE}"
else
chmod 600 "${SECRETS_FILE}"
log_success "Using existing ${SECRETS_FILE}"
fi
log_success "Key Vault found: ${KEY_VAULT_NAME}"
# Store database URL if provided
if [ -n "${DATABASE_URL:-}" ]; then
log_step "8.2 Storing database URL..."
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "database-url-${ENVIRONMENT}" \
--value "${DATABASE_URL}" \
|| log_warning "Failed to store database URL"
log_success "Database URL stored"
log_step "8.2 Writing known secrets..."
if [ -n "${DATABASE_URL:-}" ] && ! grep -q '^DATABASE_URL=' "${SECRETS_FILE}" 2>/dev/null; then
printf 'DATABASE_URL=%s\n' "${DATABASE_URL}" >> "${SECRETS_FILE}"
log_success "Stored DATABASE_URL in ${SECRETS_FILE}"
fi
# Check for Entra secrets
log_step "8.3 Checking Entra ID secrets..."
if ! grep -q '^JWT_SECRET=' "${SECRETS_FILE}" 2>/dev/null; then
JWT_SECRET=$(openssl rand -base64 32)
printf 'JWT_SECRET=%s\n' "${JWT_SECRET}" >> "${SECRETS_FILE}"
log_success "Generated JWT_SECRET in ${SECRETS_FILE}"
else
log_success "JWT_SECRET already present in ${SECRETS_FILE}"
fi
ENTRA_SECRETS=("entra-tenant-id" "entra-client-id" "entra-client-secret" "entra-credential-manifest-id")
MISSING_SECRETS=()
for secret in "${ENTRA_SECRETS[@]}"; do
if ! az keyvault secret show \
--vault-name "${KEY_VAULT_NAME}" \
--name "${secret}" \
--query value -o tsv &> /dev/null; then
MISSING_SECRETS+=("${secret}")
log_step "8.3 Checking identity provider placeholders..."
for key in OIDC_ISSUER OIDC_CLIENT_ID OIDC_CLIENT_SECRET VC_ISSUER_DID VC_ISSUER_DOMAIN; do
if grep -q "^${key}=" "${SECRETS_FILE}" 2>/dev/null; then
log_success "${key} found in ${SECRETS_FILE}"
else
log_warning "${key} is not present in ${SECRETS_FILE}"
fi
done
if [ ${#MISSING_SECRETS[@]} -gt 0 ]; then
log_warning "Missing Entra ID secrets: ${MISSING_SECRETS[*]}"
log_info "Run: ./scripts/deploy/store-entra-secrets.sh"
else
log_success "All Entra ID secrets found"
fi
# Store JWT secret if not exists
log_step "8.4 Storing JWT secret..."
if ! az keyvault secret show \
--vault-name "${KEY_VAULT_NAME}" \
--name "jwt-secret" \
--query value -o tsv &> /dev/null; then
JWT_SECRET=$(openssl rand -base64 32)
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "jwt-secret" \
--value "${JWT_SECRET}" \
|| error_exit "Failed to store JWT secret"
log_success "JWT secret generated and stored"
else
log_success "JWT secret already exists"
fi
log_info "Secrets configuration complete"
log_info "Note: Additional secrets may need to be set manually"
log_info "See docs/deployment/DEPLOYMENT_GUIDE.md Phase 8 for complete list"
log_info "Keep ${SECRETS_FILE} out of version control and merge it into the runtime env on the target CT when needed."
# Save state
save_state "phase8" "complete"
@@ -90,4 +60,3 @@ save_state "phase8" "complete"
log_success "=========================================="
log_success "Phase 8: Secrets Configuration - COMPLETE"
log_success "=========================================="
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Phase 9: Infrastructure Services Deployment
# Deploy monitoring, logging, and infrastructure services
# Phase 9: Sankofa Edge & Runtime Checks
# Verify the CT and edge services used by the Phoenix-native deployment flow.
#
set -euo pipefail
@@ -10,64 +10,33 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "=========================================="
log_info "Phase 9: Infrastructure Services Deployment"
log_info "Phase 9: Sankofa Edge & Runtime Checks"
log_info "=========================================="
# Verify Kubernetes access
if ! kubectl cluster-info &> /dev/null; then
az aks get-credentials --resource-group "${AKS_RESOURCE_GROUP}" \
--name "${AKS_NAME}" \
--overwrite-existing
fi
check_proxmox_access
# 9.1 External Secrets Operator
log_step "9.1 Deploying External Secrets Operator..."
log_step "9.1 Verifying CT availability..."
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}" \
"pct status ${ORDER_PORTAL_PUBLIC_VMID}" \
|| error_exit "The Order public CT is not available"
if ! kubectl get crd externalsecrets.external-secrets.io &> /dev/null; then
log_info "Installing External Secrets Operator..."
kubectl apply -f https://external-secrets.io/latest/deploy/ || error_exit "Failed to install"
log_info "Waiting for operator to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=external-secrets \
-n external-secrets-system \
--timeout=300s || log_warning "Operator not ready yet"
else
log_success "External Secrets Operator already installed"
fi
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_HAPROXY_HOST}" \
"pct status ${ORDER_HAPROXY_VMID}" \
|| error_exit "The Order HAProxy CT is not available"
# 9.2 Monitoring Stack (Prometheus & Grafana)
log_step "9.2 Deploying monitoring stack..."
log_step "9.2 Verifying edge health..."
curl -fsS -H "Host: the-order.sankofa.nexus" "http://${ORDER_HAPROXY_IP}/api/health" >/dev/null \
&& log_success "Order HAProxy health check passed" \
|| log_warning "Order HAProxy health check failed"
if ! command -v helm &> /dev/null; then
log_warning "Helm not found. Install Helm to deploy monitoring stack."
log_info "See: https://helm.sh/docs/intro/install/"
else
if ! helm repo list | grep -q prometheus-community; then
log_info "Adding Prometheus Helm repository..."
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
fi
if ! helm list -n monitoring | grep -q prometheus; then
log_info "Installing Prometheus stack..."
kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f -
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
|| log_warning "Prometheus installation failed or already exists"
else
log_success "Prometheus already installed"
fi
fi
log_info "Monitoring stack deployment complete"
log_info "Access Grafana: kubectl port-forward svc/prometheus-grafana 3000:80 -n monitoring"
log_step "9.3 Verifying Phoenix health..."
curl -fsS "${SANKOFA_PHOENIX_URL}/health" >/dev/null \
&& log_success "Phoenix public health check passed" \
|| log_warning "Phoenix public health check failed"
# Save state
save_state "phase9" "complete"
log_success "=========================================="
log_success "Phase 9: Infrastructure Services - COMPLETE"
log_success "Phase 9: Sankofa Edge & Runtime Checks - COMPLETE"
log_success "=========================================="
@@ -175,7 +175,7 @@ Total Files:
PNG: ${VALID_PNG}
Recommended Sizes:
- 200x200px: For credential logos (Entra VerifiedID)
- 200x200px: For credential logos
- 400x400px: For high-resolution displays
- 800x800px: For print/embossing
@@ -204,7 +204,7 @@ echo "CDN Base URL: ${CDN_BASE_URL}"
echo ""
# This is a template - customize based on your CDN provider
# Examples: AWS S3, Azure Blob Storage, Cloudflare, etc.
# Examples: AWS S3, Cloudflare R2, generic HTTPS storage, etc.
for png_file in *.png; do
if [ -f "${png_file}" ]; then
@@ -212,8 +212,6 @@ for png_file in *.png; do
# Add your CDN upload command here
# Example for AWS S3:
# aws s3 cp "${png_file}" "s3://your-bucket/images/${png_file}" --acl public-read
# Example for Azure:
# az storage blob upload --file "${png_file}" --container-name images --name "${png_file}" --account-name your-account
fi
done
@@ -247,7 +245,7 @@ $(for png in "${PNG_DIR}"/*.png; do
done)
Recommendations:
- Use 200x200px PNG for Entra VerifiedID credentials
- Use 200x200px PNG for credential logos
- Ensure all files are under 100KB for optimal performance
- Verify images are publicly accessible via HTTPS
- Test images in credential wallets before production use
@@ -274,4 +272,3 @@ echo "4. Update manifest templates with CDN URLs"
echo "5. Test credentials with new seal images"
log_success "Seal preparation complete!"
-121
View File
@@ -1,121 +0,0 @@
#!/bin/bash
# Complete Azure CDN Setup for Credential Seals
# Orchestrates quota check, infrastructure setup, and file upload
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[SETUP]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
log_error() { echo -e "${RED}[✗]${NC} $1"; }
cd "$(dirname "$0")/../.."
echo ""
log_info "=== Complete Azure CDN Setup for Credential Seals ==="
echo ""
# Step 1: Check quotas
log_info "Step 1: Checking Azure quotas..."
if ./infra/scripts/azure-check-cdn-quotas.sh; then
log_success "Quota check passed"
else
log_warning "Quota check found issues (review azure-cdn-quota-report.txt)"
log_info "Continuing with setup (review quotas manually if needed)..."
fi
echo ""
# Step 2: Set up Azure infrastructure
log_info "Step 2: Setting up Azure infrastructure..."
if ./infra/scripts/azure-cdn-setup.sh; then
log_success "Azure infrastructure setup complete"
else
log_error "Azure infrastructure setup failed"
exit 1
fi
echo ""
# Step 3: Load configuration
log_info "Step 3: Loading configuration..."
if [ -f "azure-cdn-config.env" ]; then
source azure-cdn-config.env
log_success "Configuration loaded"
else
log_error "Configuration file not found: azure-cdn-config.env"
exit 1
fi
echo ""
# Step 4: Prepare PNG files (if not already done)
log_info "Step 4: Checking PNG files..."
PNG_COUNT=$(find assets/credential-images/png -name "*.png" -type f 2>/dev/null | wc -l)
if [ "${PNG_COUNT}" -eq 0 ]; then
log_info "PNG files not found, generating..."
./scripts/deploy/prepare-all-credential-seals.sh
else
log_success "PNG files found: ${PNG_COUNT}"
fi
echo ""
# Step 5: Upload files to Azure
log_info "Step 5: Uploading files to Azure Blob Storage..."
if ./scripts/deploy/upload-seals-to-azure.sh; then
log_success "Files uploaded successfully"
else
log_error "File upload failed"
exit 1
fi
echo ""
# Step 6: Update manifest URLs
log_info "Step 6: Updating manifest templates with CDN URLs..."
if [ -n "${CDN_BASE_URL_CDN:-}" ] && [ "${CDN_BASE_URL_CDN}" != "https://.azureedge.net/images/" ]; then
CDN_BASE_URL="${CDN_BASE_URL_CDN}" ./scripts/deploy/update-manifest-seal-urls.sh
log_success "Manifest templates updated with CDN URLs"
elif [ -n "${CDN_BASE_URL_BLOB:-}" ]; then
CDN_BASE_URL="${CDN_BASE_URL_BLOB}" ./scripts/deploy/update-manifest-seal-urls.sh
log_success "Manifest templates updated with Blob Storage URLs"
log_warning "CDN endpoint may still be provisioning. Update URLs later when CDN is ready."
else
log_warning "CDN URLs not available, skipping manifest update"
fi
echo ""
# Step 7: Verify setup
log_info "Step 7: Verifying setup..."
if [ -n "${AZURE_STORAGE_ACCOUNT:-}" ]; then
TEST_URL="${CDN_BASE_URL_BLOB}digital-bank-seal.png"
if curl -s -o /dev/null -w "%{http_code}" "${TEST_URL}" | grep -q "200"; then
log_success "Files are accessible at: ${TEST_URL}"
else
log_warning "Files may not be accessible yet (CDN may still be provisioning)"
fi
fi
echo ""
# Summary
log_info "=== Setup Complete ==="
echo ""
log_success "Azure CDN infrastructure created"
log_success "Credential seal images uploaded"
log_success "Manifest templates updated"
echo ""
log_info "Configuration saved in: azure-cdn-config.env"
log_info "CDN URLs:"
if [ -n "${CDN_BASE_URL_BLOB:-}" ]; then
echo " Blob Storage: ${CDN_BASE_URL_BLOB}"
fi
if [ -n "${CDN_BASE_URL_CDN:-}" ] && [ "${CDN_BASE_URL_CDN}" != "https://.azureedge.net/images/" ]; then
echo " CDN: ${CDN_BASE_URL_CDN}"
echo ""
log_info "Note: CDN endpoint may take 10-15 minutes to fully propagate"
fi
echo ""
log_success "Azure CDN setup complete!"
-231
View File
@@ -1,231 +0,0 @@
#!/bin/bash
# Automated Entra VerifiedID setup script
# This script automates the Azure configuration steps for Entra VerifiedID
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}Step:${NC} $1"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
}
# Check prerequisites
check_prerequisites() {
log_step "Checking prerequisites"
if ! command -v az &> /dev/null; then
log_error "Azure CLI not found. Please install: https://docs.microsoft.com/cli/azure/install-azure-cli"
exit 1
fi
if ! az account show &> /dev/null; then
log_error "Not logged in to Azure. Run: az login"
exit 1
fi
log_success "Prerequisites check passed"
}
# Get configuration
get_config() {
log_step "Getting configuration"
read -p "Enter Azure Subscription ID (or press Enter to use current): " SUBSCRIPTION_ID
if [ -z "${SUBSCRIPTION_ID}" ]; then
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
fi
read -p "Enter Resource Group name: " RESOURCE_GROUP
read -p "Enter App Registration name (e.g., the-order-entra): " APP_NAME
read -p "Enter Key Vault name: " KEY_VAULT_NAME
log_success "Configuration collected"
}
# Create App Registration
create_app_registration() {
log_step "Creating Azure AD App Registration"
log_info "Creating app registration: ${APP_NAME}"
APP_ID=$(az ad app create \
--display-name "${APP_NAME}" \
--query appId -o tsv)
log_info "Creating service principal"
SP_ID=$(az ad sp create --id "${APP_ID}" --query id -o tsv)
log_info "Getting tenant ID"
TENANT_ID=$(az account show --query tenantId -o tsv)
log_info "Creating client secret (valid for 1 year)"
CLIENT_SECRET=$(az ad app credential reset \
--id "${APP_ID}" \
--years 1 \
--query password -o tsv)
log_success "App Registration created"
log_info "Application (Client) ID: ${APP_ID}"
log_info "Directory (Tenant) ID: ${TENANT_ID}"
log_warning "Client Secret (save this securely): ${CLIENT_SECRET}"
# Store in variables for later use
export ENTRA_TENANT_ID="${TENANT_ID}"
export ENTRA_CLIENT_ID="${APP_ID}"
export ENTRA_CLIENT_SECRET="${CLIENT_SECRET}"
}
# Configure API permissions
configure_api_permissions() {
log_step "Configuring API permissions"
log_info "Adding Verifiable Credentials Service permissions"
# Get the Verifiable Credentials Service app ID
VC_SERVICE_APP_ID="3db474b9-7a6d-4f50-afdc-70940ce1df8f"
# Add permissions
az ad app permission add \
--id "${APP_ID}" \
--api "${VC_SERVICE_APP_ID}" \
--api-permissions "e5832135-c0d8-4b7b-b5e3-7d4c4c4c4c4c=Role" 2>/dev/null || true
log_info "Granting admin consent"
az ad app permission admin-consent --id "${APP_ID}" || log_warning "Admin consent may require manual approval"
log_success "API permissions configured"
log_warning "You may need to grant admin consent manually in Azure Portal"
}
# Create credential manifest (manual step with instructions)
create_credential_manifest() {
log_step "Credential Manifest Creation"
log_info "Credential manifest creation must be done in Azure Portal"
log_info "Follow these steps:"
echo "1. Go to Azure Portal → Verified ID"
echo "2. Click 'Add credential'"
echo "3. Choose credential type and configure"
echo "4. Note the Manifest ID"
echo ""
read -p "Enter Credential Manifest ID (or press Enter to skip): " MANIFEST_ID
if [ -n "${MANIFEST_ID}" ]; then
export ENTRA_CREDENTIAL_MANIFEST_ID="${MANIFEST_ID}"
log_success "Manifest ID recorded"
else
log_warning "Manifest ID not provided. You can add it later."
fi
}
# Store secrets in Key Vault
store_secrets() {
log_step "Storing secrets in Key Vault"
log_info "Storing Entra Tenant ID"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-tenant-id" \
--value "${ENTRA_TENANT_ID}" \
--output none || log_error "Failed to store tenant ID"
log_info "Storing Entra Client ID"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-client-id" \
--value "${ENTRA_CLIENT_ID}" \
--output none || log_error "Failed to store client ID"
log_info "Storing Entra Client Secret"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-client-secret" \
--value "${ENTRA_CLIENT_SECRET}" \
--output none || log_error "Failed to store client secret"
if [ -n "${ENTRA_CREDENTIAL_MANIFEST_ID:-}" ]; then
log_info "Storing Credential Manifest ID"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-credential-manifest-id" \
--value "${ENTRA_CREDENTIAL_MANIFEST_ID}" \
--output none || log_error "Failed to store manifest ID"
fi
log_success "Secrets stored in Key Vault"
}
# Generate environment file
generate_env_file() {
log_step "Generating environment file template"
ENV_FILE=".env.entra.example"
cat > "${ENV_FILE}" << EOF
# Microsoft Entra VerifiedID Configuration
ENTRA_TENANT_ID=${ENTRA_TENANT_ID}
ENTRA_CLIENT_ID=${ENTRA_CLIENT_ID}
ENTRA_CLIENT_SECRET=${ENTRA_CLIENT_SECRET}
ENTRA_CREDENTIAL_MANIFEST_ID=${ENTRA_CREDENTIAL_MANIFEST_ID:-}
# Multi-manifest support (JSON format)
# ENTRA_MANIFESTS={"default":"manifest-id-1","diplomatic":"manifest-id-2","judicial":"manifest-id-3"}
# Entra Rate Limiting (optional)
# ENTRA_RATE_LIMIT_ISSUANCE=10
# ENTRA_RATE_LIMIT_VERIFICATION=20
# ENTRA_RATE_LIMIT_STATUS_CHECK=30
# ENTRA_RATE_LIMIT_GLOBAL=50
EOF
log_success "Environment file template created: ${ENV_FILE}"
log_warning "Update your .env file with these values"
}
# Main execution
main() {
log_info "Entra VerifiedID Automated Setup"
log_info "This script will help you set up Entra VerifiedID for The Order"
check_prerequisites
get_config
create_app_registration
configure_api_permissions
create_credential_manifest
store_secrets
generate_env_file
log_success "Setup complete!"
log_info "Next steps:"
echo "1. Review and update .env file with the generated values"
echo "2. Create credential manifests in Azure Portal (if not done)"
echo "3. Test the integration using the API endpoints"
echo "4. Configure webhook URLs in Entra VerifiedID settings"
}
# Run main function
main "$@"
-58
View File
@@ -1,58 +0,0 @@
#!/bin/bash
#
# Store Entra ID secrets in Azure Key Vault
# Run this after completing manual Entra ID setup
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
log_info "Storing Entra ID secrets in Azure Key Vault..."
# Prompt for values if not in environment
if [ -z "${ENTRA_TENANT_ID:-}" ]; then
read -p "Enter Entra Tenant ID: " ENTRA_TENANT_ID
fi
if [ -z "${ENTRA_CLIENT_ID:-}" ]; then
read -p "Enter Entra Client ID: " ENTRA_CLIENT_ID
fi
if [ -z "${ENTRA_CLIENT_SECRET:-}" ]; then
read -sp "Enter Entra Client Secret: " ENTRA_CLIENT_SECRET
echo
fi
if [ -z "${ENTRA_CREDENTIAL_MANIFEST_ID:-}" ]; then
read -p "Enter Entra Credential Manifest ID: " ENTRA_CREDENTIAL_MANIFEST_ID
fi
# Store secrets
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-tenant-id" \
--value "${ENTRA_TENANT_ID}" \
|| error_exit "Failed to store tenant ID"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-client-id" \
--value "${ENTRA_CLIENT_ID}" \
|| error_exit "Failed to store client ID"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-client-secret" \
--value "${ENTRA_CLIENT_SECRET}" \
|| error_exit "Failed to store client secret"
az keyvault secret set \
--vault-name "${KEY_VAULT_NAME}" \
--name "entra-credential-manifest-id" \
--value "${ENTRA_CREDENTIAL_MANIFEST_ID}" \
|| error_exit "Failed to store manifest ID"
log_success "Entra ID secrets stored in Key Vault"
+183
View File
@@ -0,0 +1,183 @@
#!/bin/bash
#
# Sync the portal-public Next.js standalone build to the Sankofa / Proxmox runtime.
# Target topology:
# NPMplus -> order-haproxy (VMID 10210) -> order-portal-public (VMID 10090)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
APP_NAME="portal-public"
APP_DIR="${PROJECT_ROOT}/apps/${APP_NAME}"
LOCAL_BUILD_TARBALL="${ARTIFACTS_DIR}/${APP_NAME}-${ENVIRONMENT}.tgz"
LOCAL_STAGE_DIR="${ARTIFACTS_DIR}/${APP_NAME}-${ENVIRONMENT}-stage"
REMOTE_TGZ="/tmp/${APP_NAME}-${ENVIRONMENT}-$$.tgz"
CT_TGZ="/tmp/${APP_NAME}.tgz"
DRY_RUN=false
SKIP_BUILD=false
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run)
DRY_RUN=true
shift
;;
--skip-build)
SKIP_BUILD=true
shift
;;
*)
error_exit "Unknown option: $1"
;;
esac
done
if [ ! -d "${APP_DIR}" ]; then
error_exit "Portal app not found at ${APP_DIR}"
fi
check_prerequisites
check_proxmox_access
if [ "${SKIP_BUILD}" != "true" ]; then
log_step "Building ${APP_NAME} standalone bundle..."
rm -rf "${APP_DIR}/.next"
pnpm --dir "${APP_DIR}" build || error_exit "Failed to build ${APP_NAME}"
else
log_info "Skipping portal build because --skip-build was provided"
fi
if [ ! -f "${APP_DIR}/.next/standalone/apps/${APP_NAME}/server.js" ]; then
error_exit "Expected standalone output missing for ${APP_NAME}"
fi
log_step "Preparing deployment artifact..."
rm -rf "${LOCAL_STAGE_DIR}"
mkdir -p "${LOCAL_STAGE_DIR}/standalone/apps/${APP_NAME}/.next"
cp -R "${APP_DIR}/.next/standalone/." "${LOCAL_STAGE_DIR}/standalone/"
cp -R "${APP_DIR}/.next/static" "${LOCAL_STAGE_DIR}/standalone/apps/${APP_NAME}/.next/static"
cp -R "${APP_DIR}/public" "${LOCAL_STAGE_DIR}/standalone/apps/${APP_NAME}/public"
tar czf "${LOCAL_BUILD_TARBALL}" -C "${LOCAL_STAGE_DIR}" standalone
log_success "Created deployment artifact ${LOCAL_BUILD_TARBALL}"
if [ "${DRY_RUN}" = "true" ]; then
log_info "Dry run only. Would push ${LOCAL_BUILD_TARBALL} to CT ${ORDER_PORTAL_PUBLIC_VMID} on ${ORDER_PORTAL_PUBLIC_HOST}"
exit 0
fi
log_step "Uploading artifact to Proxmox host..."
scp ${SSH_OPTS} "${LOCAL_BUILD_TARBALL}" "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}:${REMOTE_TGZ}"
log_step "Deploying ${APP_NAME} to CT ${ORDER_PORTAL_PUBLIC_VMID}..."
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}" bash -s -- \
"${ORDER_PORTAL_PUBLIC_VMID}" \
"${REMOTE_TGZ}" \
"${CT_TGZ}" \
"${ORDER_PORTAL_PUBLIC_APP_DIR}" \
"${ORDER_PORTAL_PUBLIC_SERVICE}" \
"${ORDER_PORTAL_PUBLIC_PORT}" \
"${THE_ORDER_PUBLIC_URL}" \
"${SANKOFA_PHOENIX_URL}" \
"${SANKOFA_PORTAL_URL}" \
"${ORDER_PORTAL_PUBLIC_IP}" <<'REMOTE_EOF'
set -euo pipefail
VMID="$1"
REMOTE_TGZ="$2"
CT_TGZ="$3"
APP_DIR="$4"
SERVICE_NAME="$5"
PORT="$6"
ORDER_URL="$7"
PHOENIX_URL="$8"
PORTAL_URL="$9"
PORTAL_IP="${10}"
pct push "${VMID}" "${REMOTE_TGZ}" "${CT_TGZ}"
rm -f "${REMOTE_TGZ}"
pct exec "${VMID}" -- bash -s -- \
"${CT_TGZ}" \
"${APP_DIR}" \
"${SERVICE_NAME}" \
"${PORT}" \
"${ORDER_URL}" \
"${PHOENIX_URL}" \
"${PORTAL_URL}" <<'CT_EOF'
set -euo pipefail
CT_TGZ="$1"
APP_DIR="$2"
SERVICE_NAME="$3"
PORT="$4"
ORDER_URL="$5"
PHOENIX_URL="$6"
PORTAL_URL="$7"
command -v node >/dev/null || { echo "ERROR: node is required inside the target CT"; exit 1; }
RELEASES_DIR="${APP_DIR}/releases"
SHARED_DIR="${APP_DIR}/shared"
CURRENT_LINK="${APP_DIR}/current"
RELEASE_DIR="${RELEASES_DIR}/$(date +%Y%m%d-%H%M%S)"
mkdir -p "${RELEASES_DIR}" "${SHARED_DIR}" "${RELEASE_DIR}"
tar xzf "${CT_TGZ}" -C "${RELEASE_DIR}"
rm -f "${CT_TGZ}"
ln -sfn "${RELEASE_DIR}/standalone" "${CURRENT_LINK}"
cat > "${SHARED_DIR}/runtime.env" <<ENV_EOF
NODE_ENV=production
PORT=${PORT}
HOSTNAME=0.0.0.0
NEXT_PUBLIC_ORDER_PUBLIC_URL=${ORDER_URL}
NEXT_PUBLIC_SANKOFA_PHOENIX_URL=${PHOENIX_URL}
NEXT_PUBLIC_SANKOFA_PORTAL_URL=${PORTAL_URL}
NEXT_PUBLIC_IDENTITY_SERVICE_URL=${PHOENIX_URL}
NEXT_PUBLIC_ERESIDENCY_SERVICE_URL=${PHOENIX_URL}
NEXT_PUBLIC_FINANCE_SERVICE_URL=${PHOENIX_URL}
NEXT_PUBLIC_DATAROOM_SERVICE_URL=${PHOENIX_URL}
ENV_EOF
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<UNIT_EOF
[Unit]
Description=The Order portal-public (Sankofa Phoenix runtime)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=${CURRENT_LINK}/apps/portal-public
EnvironmentFile=${SHARED_DIR}/runtime.env
ExecStart=/usr/bin/env node server.js
Restart=always
RestartSec=5
User=root
[Install]
WantedBy=multi-user.target
UNIT_EOF
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}" >/dev/null
systemctl restart "${SERVICE_NAME}"
systemctl is-active --quiet "${SERVICE_NAME}"
curl -fsS "http://127.0.0.1:${PORT}/api/health" >/dev/null
# Keep the five newest releases.
if [ -d "${RELEASES_DIR}" ]; then
ls -1dt "${RELEASES_DIR}"/* 2>/dev/null | tail -n +6 | xargs -r rm -rf --
fi
CT_EOF
echo "Deployed ${SERVICE_NAME} to CT ${VMID}"
echo "Direct health: http://${PORTAL_IP}:${PORT}/api/health"
REMOTE_EOF
log_success "${APP_NAME} deployed to ${ORDER_PORTAL_PUBLIC_IP}:${ORDER_PORTAL_PUBLIC_PORT}"
-155
View File
@@ -1,155 +0,0 @@
#!/bin/bash
# Upload Order of St John credential seals to Azure Blob Storage
# Requires Azure CDN to be set up first
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[UPLOAD]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
log_error() { echo -e "${RED}[✗]${NC} $1"; }
cd "$(dirname "$0")/../.."
# Load Azure configuration if available
if [ -f "azure-cdn-config.env" ]; then
source azure-cdn-config.env
log_info "Loaded Azure configuration from azure-cdn-config.env"
elif [ -f "infra/scripts/azure-cdn-config.env" ]; then
source infra/scripts/azure-cdn-config.env
log_info "Loaded Azure configuration from infra/scripts/azure-cdn-config.env"
fi
# Configuration (can be overridden by environment variables)
STORAGE_ACCOUNT="${AZURE_STORAGE_ACCOUNT:-}"
STORAGE_KEY="${AZURE_STORAGE_KEY:-}"
CONTAINER="${AZURE_STORAGE_CONTAINER:-images}"
PNG_DIR="assets/credential-images/png"
# Check prerequisites
if ! command -v az &> /dev/null; then
log_error "Azure CLI is not installed"
echo "Install from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli"
exit 1
fi
if ! az account show &> /dev/null; then
log_error "Not logged in to Azure. Please log in:"
echo " az login"
exit 1
fi
# Check if storage account is configured
if [ -z "${STORAGE_ACCOUNT}" ]; then
log_error "Azure storage account not configured"
echo "Run: ./infra/scripts/azure-cdn-setup.sh"
echo "Or set: AZURE_STORAGE_ACCOUNT=<account-name>"
exit 1
fi
# Get storage key if not provided
if [ -z "${STORAGE_KEY}" ]; then
RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-the-order-cdn-rg}"
log_info "Retrieving storage account key..."
STORAGE_KEY=$(az storage account keys list \
--resource-group "${RESOURCE_GROUP}" \
--account-name "${STORAGE_ACCOUNT}" \
--query "[0].value" -o tsv 2>/dev/null || echo "")
if [ -z "${STORAGE_KEY}" ]; then
log_error "Failed to retrieve storage account key"
exit 1
fi
fi
# Check PNG directory
if [ ! -d "${PNG_DIR}" ]; then
log_error "PNG directory not found: ${PNG_DIR}"
echo "Run: ./scripts/deploy/prepare-all-credential-seals.sh"
exit 1
fi
PNG_FILES=($(find "${PNG_DIR}" -name "*.png" -type f))
if [ ${#PNG_FILES[@]} -eq 0 ]; then
log_error "No PNG files found in ${PNG_DIR}"
echo "Run: ./scripts/deploy/prepare-all-credential-seals.sh"
exit 1
fi
log_info "Uploading ${#PNG_FILES[@]} PNG file(s) to Azure Blob Storage"
echo " Storage Account: ${STORAGE_ACCOUNT}"
echo " Container: ${CONTAINER}"
echo ""
# Upload each file
UPLOADED=0
FAILED=0
for png_file in "${PNG_FILES[@]}"; do
filename=$(basename "${png_file}")
log_info "Uploading: ${filename}"
if az storage blob upload \
--file "${png_file}" \
--container-name "${CONTAINER}" \
--name "${filename}" \
--account-name "${STORAGE_ACCOUNT}" \
--account-key "${STORAGE_KEY}" \
--content-type "image/png" \
--overwrite \
-o json &> /dev/null; then
log_success " Uploaded: ${filename}"
((UPLOADED++))
else
log_error " Failed: ${filename}"
((FAILED++))
fi
done
echo ""
log_info "=== Upload Summary ==="
log_success "Uploaded: ${UPLOADED}"
if [ ${FAILED} -gt 0 ]; then
log_error "Failed: ${FAILED}"
fi
# Generate URLs
echo ""
log_info "File URLs:"
BLOB_BASE_URL="https://${STORAGE_ACCOUNT}.blob.core.windows.net/${CONTAINER}"
for png_file in "${PNG_FILES[@]}"; do
filename=$(basename "${png_file}")
echo " ${BLOB_BASE_URL}/${filename}"
done
# CDN URL if available
if [ -n "${AZURE_CDN_ENDPOINT_URL:-}" ]; then
echo ""
log_info "CDN URLs (once propagated):"
CDN_BASE_URL="https://${AZURE_CDN_ENDPOINT_URL}/${CONTAINER}"
for png_file in "${PNG_FILES[@]}"; do
filename=$(basename "${png_file}")
echo " ${CDN_BASE_URL}/${filename}"
done
fi
echo ""
log_info "Next Steps:"
echo "1. Verify files are accessible:"
echo " curl -I ${BLOB_BASE_URL}/digital-bank-seal.png"
echo ""
echo "2. Update manifest templates:"
echo " CDN_BASE_URL=${BLOB_BASE_URL}/ ./scripts/deploy/update-manifest-seal-urls.sh"
echo ""
if [ ${UPLOADED} -eq ${#PNG_FILES[@]} ]; then
log_success "All files uploaded successfully!"
else
log_warning "Some files failed to upload. Check errors above."
fi
-150
View File
@@ -1,150 +0,0 @@
#!/bin/bash
# Verify Complete Entra VerifiedID Setup
# Comprehensive validation of all setup steps
set -euo pipefail
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[VERIFY]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_error() { echo -e "${RED}[✗]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
cd "$(dirname "$0")/../.."
CHECKS_PASSED=0
CHECKS_FAILED=0
CHECKS_WARNING=0
check() {
local name=$1
local command=$2
log_info "Checking: ${name}"
if eval "${command}" > /dev/null 2>&1; then
log_success "${name}"
((CHECKS_PASSED++))
return 0
else
log_error "${name}"
((CHECKS_FAILED++))
return 1
fi
}
check_warning() {
local name=$1
local command=$2
log_info "Checking: ${name}"
if eval "${command}" > /dev/null 2>&1; then
log_success "${name}"
((CHECKS_PASSED++))
return 0
else
log_warning "${name} (optional)"
((CHECKS_WARNING++))
return 1
fi
}
echo ""
log_info "=== Entra VerifiedID Complete Setup Verification ==="
echo ""
# 1. Code Files
log_info "1. Code Implementation"
check "Entra VerifiedID client exists" "[ -f packages/auth/src/entra-verifiedid.ts ]"
check "Enhanced client exists" "[ -f packages/auth/src/entra-verifiedid-enhanced.ts ]"
check "Integration exists" "[ -f services/identity/src/entra-integration.ts ]"
check "Webhook handler exists" "[ -f services/identity/src/entra-webhooks.ts ]"
check "Metrics exist" "[ -f packages/monitoring/src/entra-metrics.ts ]"
# 2. Tests
log_info "2. Test Suite"
check "Unit tests exist" "[ -f packages/auth/src/entra-verifiedid.test.ts ]"
check "Integration tests exist" "[ -f packages/auth/src/entra-verifiedid.integration.test.ts ]"
# 3. Scripts
log_info "3. Automation Scripts"
check "Setup script exists" "[ -f scripts/deploy/setup-entra-automated.sh ]"
check "App creation script exists" "[ -f scripts/deploy/create-entra-app.sh ]"
check "Deployment scripts exist" "[ -f scripts/deploy/deploy-staging.sh ]"
check "Test scripts exist" "[ -f scripts/test/test-all-entra-features.sh ]"
check "Validation script exists" "[ -f scripts/validation/validate-entra-config.sh ]"
# 4. Configuration
log_info "4. Configuration Files"
check "Kubernetes secrets template exists" "[ -f infra/k8s/identity-service-entra-secrets.yaml ]"
check "Kubernetes deployment exists" "[ -f infra/k8s/identity-service-deployment-entra.yaml ]"
check "Prometheus config exists" "[ -f infra/monitoring/prometheus-entra-config.yml ]"
check "Grafana dashboard exists" "[ -f infra/monitoring/grafana-entra-dashboard.json ]"
# 5. Documentation
log_info "5. Documentation"
check "Deployment checklist exists" "[ -f docs/deployment/ENTRA_VERIFIEDID_DEPLOYMENT_CHECKLIST.md ]"
check "Runbook exists" "[ -f docs/operations/ENTRA_VERIFIEDID_RUNBOOK.md ]"
check "Training materials exist" "[ -f docs/training/ENTRA_VERIFIEDID_TRAINING.md ]"
# 6. Environment Variables (warnings if not set)
log_info "6. Environment Configuration"
if [ -f ".env" ]; then
source .env 2>/dev/null || true
fi
check_warning "ENTRA_TENANT_ID is set" "[ -n \"\${ENTRA_TENANT_ID:-}\" ]"
check_warning "ENTRA_CLIENT_ID is set" "[ -n \"\${ENTRA_CLIENT_ID:-}\" ]"
check_warning "ENTRA_CLIENT_SECRET is set" "[ -n \"\${ENTRA_CLIENT_SECRET:-}\" ]"
check_warning "ENTRA_CREDENTIAL_MANIFEST_ID is set" "[ -n \"\${ENTRA_CREDENTIAL_MANIFEST_ID:-}\" ]"
# 7. Build Status
log_info "7. Build Status"
if pnpm build 2>&1 | grep -q "error TS"; then
log_error "TypeScript build has errors"
((CHECKS_FAILED++))
else
log_success "TypeScript build passes"
((CHECKS_PASSED++))
fi
# 8. Test Execution
log_info "8. Test Execution"
if pnpm --filter @the-order/auth test entra-verifiedid.test.ts --run 2>&1 | grep -q "FAIL"; then
log_error "Unit tests have failures"
((CHECKS_FAILED++))
else
log_success "Unit tests pass"
((CHECKS_PASSED++))
fi
# Summary
echo ""
log_info "=== Verification Summary ==="
log_success "Passed: ${CHECKS_PASSED}"
if [ ${CHECKS_FAILED} -gt 0 ]; then
log_error "Failed: ${CHECKS_FAILED}"
fi
if [ ${CHECKS_WARNING} -gt 0 ]; then
log_warning "Warnings: ${CHECKS_WARNING}"
fi
TOTAL=$((CHECKS_PASSED + CHECKS_FAILED + CHECKS_WARNING))
PERCENTAGE=$((CHECKS_PASSED * 100 / TOTAL))
echo ""
if [ ${CHECKS_FAILED} -eq 0 ]; then
log_success "All critical checks passed! (${PERCENTAGE}%)"
if [ ${CHECKS_WARNING} -gt 0 ]; then
log_warning "Some optional checks need attention"
fi
exit 0
else
log_error "Some critical checks failed (${PERCENTAGE}%)"
exit 1
fi