#!/bin/bash set -euo pipefail REQUESTED_LOCATION="${LOCATION:-}" REQUESTED_RESOURCE_GROUP="${RESOURCE_GROUP:-}" APP_PREFIX="${APP_PREFIX:-skuska}" LOCATION="${LOCATION:-norwayeast}" RESOURCE_GROUP="${RESOURCE_GROUP:-${APP_PREFIX}-rg}" CONTAINER_ENV_RESOURCE_GROUP="${CONTAINER_ENV_RESOURCE_GROUP:-$RESOURCE_GROUP}" ALLOWED_LOCATIONS="${ALLOWED_LOCATIONS:-norwayeast}" ENV_FILE=".skuska.env" if ! command -v az >/dev/null 2>&1; then echo "Azure CLI is required. Install it and run: az login" exit 1 fi if ! command -v docker >/dev/null 2>&1; then echo "Docker is required to build and push container images." exit 1 fi if ! command -v openssl >/dev/null 2>&1; then echo "openssl is required to generate DB_PASSWORD." exit 1 fi az account show >/dev/null if [ -f "$ENV_FILE" ]; then # shellcheck disable=SC1090 source "$ENV_FILE" fi if [ -n "$REQUESTED_LOCATION" ]; then LOCATION="$REQUESTED_LOCATION" fi if [ -n "$REQUESTED_RESOURCE_GROUP" ]; then RESOURCE_GROUP="$REQUESTED_RESOURCE_GROUP" fi CONTAINER_ENV_RESOURCE_GROUP="${CONTAINER_ENV_RESOURCE_GROUP:-$RESOURCE_GROUP}" SUFFIX="${SUFFIX:-$(date +%s | tail -c 7)}" ACR_NAME="${ACR_NAME:-${APP_PREFIX}acr${SUFFIX}}" CONTAINER_ENV="${CONTAINER_ENV:-${APP_PREFIX}-env}" APP_NAME="${APP_NAME:-${APP_PREFIX}-app}" STORAGE_ACCOUNT="${STORAGE_ACCOUNT:-${APP_PREFIX}st${SUFFIX}}" STORAGE_ACCOUNT="$(echo "$STORAGE_ACCOUNT" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9' | cut -c1-24)" STORAGE_SHARE="${STORAGE_SHARE:-pgbackup}" STORAGE_MOUNT_NAME="${STORAGE_MOUNT_NAME:-postgres-backup-storage}" DB_NAME="${DB_NAME:-appdb}" DB_USER="${DB_USER:-appuser}" DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 24)}" cat > "$ENV_FILE" </dev/null for _ in $(seq 1 60); do state="$(az provider show --namespace "$namespace" --query registrationState -o tsv 2>/dev/null || true)" echo " $namespace state: ${state:-unknown}" if [ "$state" = "Registered" ]; then return 0 fi sleep 10 done echo "Provider $namespace was not registered after 10 minutes." exit 1 } wait_for_acr() { local state="" echo "Waiting for Azure Container Registry: $ACR_NAME" for _ in $(seq 1 60); do state="$(az acr show \ --resource-group "$RESOURCE_GROUP" \ --name "$ACR_NAME" \ --query provisioningState \ -o tsv 2>/dev/null || true)" echo " $ACR_NAME state: ${state:-not found yet}" if [ "$state" = "Succeeded" ]; then return 0 fi if [ "$state" = "Failed" ]; then echo "Azure Container Registry provisioning failed." exit 1 fi sleep 10 done echo "Azure Container Registry was not ready after 10 minutes." exit 1 } get_acr_credentials() { for _ in $(seq 1 30); do ACR_LOGIN_SERVER="$(az acr show --resource-group "$RESOURCE_GROUP" --name "$ACR_NAME" --query loginServer -o tsv 2>/dev/null || true)" ACR_USERNAME="$(az acr credential show --name "$ACR_NAME" --query username -o tsv 2>/dev/null || true)" ACR_PASSWORD="$(az acr credential show --name "$ACR_NAME" --query passwords[0].value -o tsv 2>/dev/null || true)" if [ -n "$ACR_LOGIN_SERVER" ] && [ -n "$ACR_USERNAME" ] && [ -n "$ACR_PASSWORD" ]; then return 0 fi echo " ACR credentials are not ready yet..." sleep 10 done echo "Could not read Azure Container Registry credentials." exit 1 } yaml_quote() { printf "'%s'" "$(printf "%s" "$1" | sed "s/'/''/g")" } ensure_container_env() { local existing_env="" local candidate="" local candidates="${CONTAINER_ENV_LOCATIONS:-$ALLOWED_LOCATIONS}" if az containerapp env show \ --resource-group "$CONTAINER_ENV_RESOURCE_GROUP" \ --name "$CONTAINER_ENV" >/dev/null 2>&1; then echo "Using existing Container Apps environment: $CONTAINER_ENV in $CONTAINER_ENV_RESOURCE_GROUP" return 0 fi existing_env="$(az containerapp env list \ --query "[?name=='$CONTAINER_ENV'] | [0].resourceGroup" \ -o tsv 2>/dev/null || true)" if [ -n "$existing_env" ] && [ "$existing_env" != "None" ]; then CONTAINER_ENV_RESOURCE_GROUP="$existing_env" echo "Found existing Container Apps environment: $CONTAINER_ENV in $CONTAINER_ENV_RESOURCE_GROUP" return 0 fi echo "Creating one Container Apps environment: $CONTAINER_ENV" for candidate in $candidates; do echo " Trying Container Apps environment region: $candidate" if az containerapp env create \ --resource-group "$CONTAINER_ENV_RESOURCE_GROUP" \ --name "$CONTAINER_ENV" \ --location "$candidate" >/dev/null 2>&1; then LOCATION="$candidate" echo " Created Container Apps environment in $LOCATION" return 0 fi done echo echo "Could not create Container Apps environment in tested regions." echo "Your subscription likely blocks Container Apps environments or reached the regional quota." echo "List existing environments with:" echo " az containerapp env list -o table" echo echo "If Azure Portal shows an existing environment, rerun with:" echo " CONTAINER_ENV= CONTAINER_ENV_RESOURCE_GROUP= ./prepare-app.sh" echo echo "You can also provide your own region list:" echo " CONTAINER_ENV_LOCATIONS=\"region1 region2 region3\" ./prepare-app.sh" exit 1 } echo "Installing/updating Azure Container Apps extension..." az extension add --name containerapp --upgrade >/dev/null wait_for_provider "Microsoft.App" wait_for_provider "Microsoft.OperationalInsights" wait_for_provider "Microsoft.ContainerRegistry" wait_for_provider "Microsoft.Storage" EXISTING_RESOURCE_GROUP_LOCATION="$(az group show --name "$RESOURCE_GROUP" --query location -o tsv 2>/dev/null || true)" if [ -n "$EXISTING_RESOURCE_GROUP_LOCATION" ]; then LOCATION="$EXISTING_RESOURCE_GROUP_LOCATION" echo "Using existing resource group $RESOURCE_GROUP in $LOCATION..." else echo "Creating resource group in $LOCATION..." az group create --name "$RESOURCE_GROUP" --location "$LOCATION" >/dev/null fi if [ "$CONTAINER_ENV_RESOURCE_GROUP" != "$RESOURCE_GROUP" ]; then az group show --name "$CONTAINER_ENV_RESOURCE_GROUP" >/dev/null fi echo "Creating Azure Container Registry..." if ! az acr show --resource-group "$RESOURCE_GROUP" --name "$ACR_NAME" >/dev/null 2>&1; then az acr create \ --resource-group "$RESOURCE_GROUP" \ --name "$ACR_NAME" \ --sku Basic \ --admin-enabled true >/dev/null fi wait_for_acr az acr update --resource-group "$RESOURCE_GROUP" --name "$ACR_NAME" --admin-enabled true >/dev/null get_acr_credentials echo "Logging in to Azure Container Registry..." az acr login --name "$ACR_NAME" >/dev/null echo "Building and pushing backend image..." docker build -t "$ACR_LOGIN_SERVER/${APP_PREFIX}-backend:latest" ./backend docker push "$ACR_LOGIN_SERVER/${APP_PREFIX}-backend:latest" echo "Building and pushing frontend image..." docker build --build-arg API_BASE_URL=/api -t "$ACR_LOGIN_SERVER/${APP_PREFIX}-frontend:latest" ./frontend docker push "$ACR_LOGIN_SERVER/${APP_PREFIX}-frontend:latest" echo "Building and pushing postgres image..." docker build -t "$ACR_LOGIN_SERVER/${APP_PREFIX}-postgres:latest" ./postgres docker push "$ACR_LOGIN_SERVER/${APP_PREFIX}-postgres:latest" ensure_container_env CONTAINER_ENV_ID="$(az containerapp env show \ --resource-group "$CONTAINER_ENV_RESOURCE_GROUP" \ --name "$CONTAINER_ENV" \ --query id -o tsv)" echo "Creating Azure Storage Account for persistent PostgreSQL backups..." if ! az storage account show --resource-group "$RESOURCE_GROUP" --name "$STORAGE_ACCOUNT" >/dev/null 2>&1; then az storage account create \ --resource-group "$RESOURCE_GROUP" \ --name "$STORAGE_ACCOUNT" \ --location "$LOCATION" \ --sku Standard_LRS \ --kind StorageV2 \ --min-tls-version TLS1_2 >/dev/null fi STORAGE_KEY="$(az storage account keys list \ --resource-group "$RESOURCE_GROUP" \ --account-name "$STORAGE_ACCOUNT" \ --query "[0].value" \ -o tsv)" echo "Creating Azure Files share for persistent PostgreSQL backups..." az storage share create \ --account-name "$STORAGE_ACCOUNT" \ --account-key "$STORAGE_KEY" \ --name "$STORAGE_SHARE" \ --quota 5 >/dev/null echo "Connecting Azure Files share to Container Apps environment..." az containerapp env storage set \ --resource-group "$CONTAINER_ENV_RESOURCE_GROUP" \ --name "$CONTAINER_ENV" \ --storage-name "$STORAGE_MOUNT_NAME" \ --storage-type AzureFile \ --azure-file-account-name "$STORAGE_ACCOUNT" \ --azure-file-account-key "$STORAGE_KEY" \ --azure-file-share-name "$STORAGE_SHARE" \ --access-mode ReadWrite >/dev/null APP_YAML=".skuska-containerapp.yaml" REGISTRY_PASSWORD_YAML="$(yaml_quote "$ACR_PASSWORD")" DB_PASSWORD_YAML="$(yaml_quote "$DB_PASSWORD")" cat > "$APP_YAML" </dev/null 2>&1 || true az containerapp create \ --resource-group "$RESOURCE_GROUP" \ --name "$APP_NAME" \ --environment "$CONTAINER_ENV_ID" \ --yaml "$APP_YAML" >/dev/null APP_FQDN="$(az containerapp show --resource-group "$RESOURCE_GROUP" --name "$APP_NAME" --query properties.configuration.ingress.fqdn -o tsv)" rm -f "$APP_YAML" grep -v -E '^(LOCATION|ACR_LOGIN_SERVER|APP_URL|CONTAINER_ENV_RESOURCE_GROUP)=' "$ENV_FILE" > "${ENV_FILE}.tmp" mv "${ENV_FILE}.tmp" "$ENV_FILE" cat >> "$ENV_FILE" <