docs: archive entra materials and simplify deployment docs
This commit is contained in:
+85
-177
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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}"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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' });"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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
@@ -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 "=========================================="
|
||||
@@ -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 "=========================================="
|
||||
|
||||
Executable
+48
@@ -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 "=========================================="
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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 "=========================================="
|
||||
|
||||
|
||||
@@ -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!"
|
||||
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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
@@ -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}"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user