learning_ai_common_plat/docs/devops/AZURE_PORTAL_SETUP.md
saravanakumardb1 8af997ba0f docs(devops): update stale mywisprai/MyWisprAI branding across 5 AKV docs
- BytelystAI → ByteLyst in titles
- MyWisprAI → LysnrAI (legacy) for wispr-* secret references
- Added 'legacy resource name' annotations to Azure resource names
  (kv-mywisprai, cosmos-mywisprai, etc. cannot be renamed)
- Updated dashboard paths (admin-web moved to dashboards/)
- Fixed telemetry role name: mywisprai-admin → lysnrai-admin
- Updated last-updated dates to 2026-03-21

Files: AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md,
AZURE_PORTAL_SETUP.md, AZURE_RESOURCE_INVENTORY.md,
ENVIRONMENT_VARIABLES_AND_KEYVAULT_AUDIT.md,
END_TO_END_ENCRYPTION_ROADMAP.md
2026-03-21 09:15:30 -07:00

46 KiB
Raw Blame History

ByteLyst — Azure Portal Setup Guide (Shared Infrastructure)

Purpose: Step-by-step instructions to provision and manage Azure infrastructure shared between LysnrAI and MindLyst under a single resource group. Architecture: Single RG, shared stateless services, separate databases. Time estimate: ~20 minutes (most resources already exist from LysnrAI) Prerequisites: An Azure account with an active subscription Last updated: 2026-03-21

Security note (staging): This document does not include secret values. Store secrets in Azure Key Vault (kv-mywisprai — legacy resource name) and reference them by name (see MindLyst Environment Variables and Key Vault sections). Rotation is deferred; see docs/devops/AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md.

Note on Azure resource names: Many resources below still carry mywisprai in their names (e.g., cosmos-mywisprai, kv-mywisprai). Azure resources cannot be renamed after creation. The old names are purely cosmetic and work correctly.


Table of Contents


Architecture Overview

Both LysnrAI and MindLyst share a single Azure resource group with shared stateless services and separate data stores.

rg-mywisprai/                             (1 resource group)
│
├── cosmos-mywisprai                       (1 Cosmos DB account — already exists)
│   ├── mywisprai                          (database: LysnrAI — legacy name, 10 containers)
│   └── mindlyst                           (database: MindLyst, 12 containers)
│
├── bytelystblobs                          (1 Storage account — already exists)
│   ├── mindlyst-voice                     ← NEW
│   ├── mindlyst-images                    ← NEW
│   └── mindlyst-exports                   ← NEW
│
├── mywisprai-openai-sweden                (1 Azure OpenAI — shared, swedencentral)
│   └── gpt-4o-mini                        (1 deployment — shared by both apps)
│
├── mywisprai-speech                        (1 Speech Service — shared, eastus)
│
├── kv-mywisprai                           (1 Key Vault — shared)
│   ├── lysnr-*                            (LysnrAI secrets)
│   ├── wispr-*                            (LysnrAI legacy secrets, pre-rebrand)
│   └── mindlyst-*                         (MindLyst secrets)
│
├── lysnnai                                (1 Notification Hub namespace)
│   ├── notificationhub                    (existing hub)
│   └── mindlyst-hub                       (MindLyst APNs + FCM) ← NEW
│
└── bytelyst-appinsights                   (1 Application Insights) ← NEW

Total: 8 Azure resources instead of 14+ with separate resource groups.

Why Share?

Concern Answer
Cost Cosmos DB Serverless charges per-RU, not per-account. One account = one endpoint, one key pair, simpler rotation
Management 1 RG to monitor, 1 cost dashboard, 1 health check
Data isolation Separate databases within Cosmos = full container-level isolation. Queries can't cross databases
Teardown safety Delete the mindlyst database → LysnrAI data untouched
Stateless services OpenAI & Speech are stateless — same key serves both apps, no data leakage

What Must Stay Separate

Resource Why
Notification Hub hubs Each app has its own APNs certificate and FCM server key
Stripe accounts Different products, pricing tiers, and webhook endpoints (external, not Azure)
Cosmos databases Each app gets its own database with its own set of containers

Existing Resources (Staging / Shared)

These resources are already provisioned and will be reused by MindLyst:

Resource Azure Name Region SKU Status
Resource Group rg-mywisprai eastus Exists
Cosmos DB cosmos-mywisprai westus2 Serverless, NoSQL Exists — DBs: mywisprai (LysnrAI, 10 containers), mindlyst (12 containers)
Blob Storage bytelystblobs westus2 StorageV2, RAGRS Exists
Azure OpenAI mywisprai-openai-sweden swedencentral S0 Exists — deployment: gpt-4o-mini
Speech Service mywisprai-speech eastus F0 Exists
Key Vault kv-mywisprai eastus Standard Exists
Notification Hub (namespace) lysnnai eastus Free Exists (hubs: notificationhub, mindlyst-hub)
Application Insights bytelyst-appinsights eastus Classic Exists

Cosmos DB: Existing LysnrAI database containers (staging)

Database: mywisprai (legacy name — this is the LysnrAI database)

  • api_tokens
  • audit_log
  • devices
  • licenses
  • payments
  • settings
  • subscriptions
  • transcripts
  • usage_daily
  • users

Partition keys for these existing containers are not documented here. Check Cosmos DB → Data Explorer → Container → Settings if you need to replicate LysnrAI data layout.

Key Vault secrets (staging)

Source of truth: Key Vault kv-mywisprai in rg-mywisprai.

MindLyst secrets (canonical):

  • mindlyst-cosmos-endpoint
  • mindlyst-cosmos-key
  • mindlyst-cosmos-database
  • mindlyst-openai-endpoint
  • mindlyst-openai-key
  • mindlyst-openai-deployment
  • mindlyst-openai-api-version
  • mindlyst-speech-key
  • mindlyst-speech-region
  • mindlyst-blob-connection-string
  • mindlyst-notification-hub-connection-string
  • mindlyst-appinsights-connection-string

LysnrAI secrets (legacy wispr-* prefix, pre-rebrand — superseded by lysnr-*):

  • wispr-azure-openai-deployment
  • wispr-azure-openai-endpoint
  • wispr-azure-openai-key
  • wispr-azure-speech-key
  • wispr-azure-speech-region

List whats currently in the vault:

az keyvault secret list --vault-name kv-mywisprai --query "[].name" -o tsv

What to Add for MindLyst

Only these additions are needed — no new accounts to create:

Action Where What
Rename RG (optional) Resource Group Keep rg-mywisprai for now. Only move resources if you want a cleaner name.
Add database Cosmos DB (cosmos-mywisprai) New database mindlyst with 12 containers
Add blob containers Blob Storage (bytelystblobs) 3 new containers: mindlyst-voice, mindlyst-images, mindlyst-exports
Add secrets Key Vault (kv-mywisprai) 12 secrets with mindlyst- prefix (keys + config)
Add hub Notification Hub namespace (lysnnai) New hub mindlyst-hub
Create App Insights New resource bytelyst-appinsights (shared telemetry)

Current Staging State (Completed)

As of 2026-02-14, the MindLyst staging infra is provisioned in rg-mywisprai:

  • Cosmos DB: cosmos-mywisprai → database mindlyst with 12 containers
  • Blob Storage: bytelystblobs → containers mindlyst-voice, mindlyst-images, mindlyst-exports
  • Key Vault: kv-mywisprai → secrets mindlyst-*
  • Notification Hubs: namespace lysnnai → hub mindlyst-hub
  • App Insights: bytelyst-appinsights

Step 1: Resource Group

Option A: Create New RG + Move Resources (Optional)

Azure doesn't support direct rename. You must move resources to a new RG.

Caution: Moving resources is safe when done carefully, but it can have RBAC/policy implications in larger org setups. For staging, consider skipping this until you have a clear reason.

  1. Go to portal.azure.comResource groups → search rg-mywisprai
  2. Click on the resource group → Overview
  3. Select all resources (checkbox at top of list)
  4. Click MoveMove to another resource group
  5. Click Create new → name it rg-bytelyst → same subscription
  6. Click Next → wait for validation → click Move
  7. Wait for move to complete (~510 minutes)
  8. Delete the empty rg-mywisprai resource group

Option B: Keep Existing Name

If you prefer not to move, keep rg-mywisprai and add MindLyst resources there. All instructions below assume the RG name — substitute as needed.

Note: Renaming is cosmetic. All connection strings and keys remain the same regardless of RG name.


Step 2: Cosmos DB — Add MindLyst Database

Reusing the existing cosmos-mywisprai account. Just add a new database and containers.

2a. Create the MindLyst database

  1. Go to Azure Cosmos DB → click cosmos-mywisprai
  2. Click Data Explorer (left sidebar)
  3. Click New Database (top toolbar)
    • Database id: mindlyst
  4. Click OK

You should now see both mywisprai and mindlyst databases in the tree.

2b. Create all 12 containers

For each container below, right-click the mindlyst database (or click the menu) → New Container:

# Container Name Partition Key Purpose
1 users /id User accounts and profiles
2 brains /userId User brain configurations (War Room, Home Base, Money Guard, etc.)
3 brain_templates /id Brain Pack gallery templates
4 memory_items /userId All captured memories (text, voice, image, link, email)
5 actions /memoryItemId AI-suggested actions from triage
6 entities /memoryItemId Extracted entities (people, dates, places, amounts)
7 reflections /userId Weekly reflection reports and insights
8 share_cards /userId Generated share cards for social sharing
9 daily_briefs /userId Morning and evening brief content
10 streaks /userId Capture streak tracking and gamification
11 notification_log /userId Push notification delivery log and scheduling
12 brain_insights /userId Cross-brain insight discoveries

For each container:

  1. Click New Container
  2. Existing Database: select mindlyst (do NOT create a new database)
  3. Enter the Container id from the table
  4. Enter the Partition key from the table (include the / prefix)
  5. Throughput: leave as Serverless (auto — no provisioned RUs)
  6. Click OK

Repeat for all 12 containers. Takes ~3 minutes.

2c. Verify

In Data Explorer, expand the mindlyst database — you should see all 12 containers listed.

Connection details (from Key Vault)

Cosmos connection details are stored in Key Vault:

  • mindlyst-cosmos-endpointCOSMOS_ENDPOINT
  • mindlyst-cosmos-keyCOSMOS_KEY
  • mindlyst-cosmos-databaseCOSMOS_DATABASE (should be mindlyst)

Step 3: Blob Storage — Add MindLyst Containers

Reusing the existing bytelystblobs storage account. Just add 3 new containers.

  1. Go to Storage accounts → click bytelystblobs
  2. Go to Data storageContainers (left sidebar)
  3. Click + Container for each:
Container Name Access Level Purpose
mindlyst-voice Private Voice capture recordings (WAV/PCM from STT pipeline)
mindlyst-images Private Captured photos, screenshots, and OCR source images
mindlyst-exports Private User data exports (JSON), weekly reflection PDFs

For each:

  1. Click + Container
  2. Enter the Name (must be lowercase, no underscores — use hyphens)
  3. Anonymous access level: leave as Private (no anonymous access)
  4. Click Create

Verify

You should now see these MindLyst containers in bytelystblobs (plus any other app containers you already had):

Container App
mindlyst-voice MindLyst
mindlyst-images MindLyst
mindlyst-exports MindLyst

Connection details (from Key Vault)

Blob connection details are stored in Key Vault:

  • mindlyst-blob-connection-stringAZURE_BLOB_CONNECTION_STRING

Non-secret config:

  • AZURE_BLOB_ACCOUNT_NAME=bytelystblobs
  • Containers: mindlyst-voice, mindlyst-images, mindlyst-exports

Step 4: Azure OpenAI — Reuse Existing

No action needed. The existing mywisprai-openai-sweden resource with gpt-4o-mini deployment serves both apps.

Connection details (from Key Vault)

Azure OpenAI connection details are stored in Key Vault:

  • mindlyst-openai-endpointAZURE_OPENAI_ENDPOINT
  • mindlyst-openai-keyAZURE_OPENAI_KEY
  • mindlyst-openai-deploymentAZURE_OPENAI_DEPLOYMENT (staging: gpt-4o-mini)
  • mindlyst-openai-api-versionAZURE_OPENAI_API_VERSION (default: 2024-06-01)

MindLyst web should set:

  • OPENAI_PROVIDER=azure

Step 5: Speech Service — Reuse Existing

No action needed. The existing mywisprai-speech (F0 free tier, eastus) serves both apps.

Connection details (from Key Vault)

Speech connection details are stored in Key Vault:

  • mindlyst-speech-keyAZURE_SPEECH_KEY
  • mindlyst-speech-regionAZURE_SPEECH_REGION

Note: F0 free tier has a 5-hour/month STT limit. If both apps start consuming real audio, upgrade to S0 ($1/audio hour).


Step 6: Key Vault — MindLyst Secrets

Staging source of truth: kv-mywisprai in rg-mywisprai.

For local dev, pull values from Key Vault into mindlyst-native/web/.env.local (see MindLyst Environment Variables).

MindLyst secrets (canonical):

Key Vault secret name Env var Notes
mindlyst-cosmos-endpoint COSMOS_ENDPOINT Config (ok to store)
mindlyst-cosmos-key COSMOS_KEY Secret
mindlyst-cosmos-database COSMOS_DATABASE Config (mindlyst)
mindlyst-openai-endpoint AZURE_OPENAI_ENDPOINT Config
mindlyst-openai-key AZURE_OPENAI_KEY Secret
mindlyst-openai-deployment AZURE_OPENAI_DEPLOYMENT Config
mindlyst-openai-api-version AZURE_OPENAI_API_VERSION Config
mindlyst-speech-key AZURE_SPEECH_KEY Secret
mindlyst-speech-region AZURE_SPEECH_REGION Config
mindlyst-blob-connection-string AZURE_BLOB_CONNECTION_STRING Secret (prefer SAS later)
mindlyst-notification-hub-connection-string ANH_CONNECTION_STRING Secret
mindlyst-appinsights-connection-string APPLICATIONINSIGHTS_CONNECTION_STRING Treat as sensitive

List whats currently in the vault:

az keyvault secret list --vault-name kv-mywisprai --query "[].name" -o tsv

Existing LysnrAI secrets (for reference)

Secret Name Notes
wispr-azure-openai-deployment Legacy (pre-rebrand) — superseded by lysnr-*
wispr-azure-openai-endpoint Legacy (pre-rebrand)
wispr-azure-openai-key Legacy (pre-rebrand)
wispr-azure-speech-key Legacy (pre-rebrand)
wispr-azure-speech-region Legacy (pre-rebrand)

Step 7: Notification Hub — Add MindLyst Hub

MindLyst needs its own hub (different APNs cert + FCM key) but can share the namespace.

7a. Check existing namespace

  1. Go to Notification Hub Namespaces → check if lysnnai exists
  2. If it exists, open it → skip to 7c
  3. If not, continue to 7b

7b. Create namespace (if needed)

  1. Search "Notification Hubs" → click Create Notification Hub Namespace
  2. Fill in:
    • Resource group: rg-mywisprai
    • Namespace name: lysnnai
    • Location: East US
    • Pricing tier: Free (up to 1M pushes/month)
  3. Click Create

7c. Create MindLyst hub

  1. Inside the namespace → click + Notification Hub
  2. Hub name: mindlyst-hub
  3. Click Create

7d. Configure APNs and FCM (later — when mobile app is ready)

  1. Go to mindlyst-hubSettingsApple (APNS)
    • Upload your MindLyst APNs certificate or token key
  2. Go to SettingsGoogle (FCM v1)
    • Enter MindLyst Firebase project credentials

Connection details

  1. Go to mindlyst-hubSettingsAccess Policies
  2. Copy the connection string for DefaultFullSharedAccessSignature (Send + Manage)

Store it in Key Vault:

  • mindlyst-notification-hub-connection-stringANH_CONNECTION_STRING

Non-secret config:

  • ANH_HUB_NAME=mindlyst-hub

Step 8: Application Insights — Reuse or Create

Optional for MVP. If you want telemetry for both apps, create one shared resource.

Staging status: Created bytelyst-appinsights in rg-mywisprai (East US).

Create (if not exists)

Option A: Workspace-based (recommended)

  1. Search "Application Insights" → click Create
  2. Fill in:
    • Resource group: rg-mywisprai
    • Name: bytelyst-appinsights
    • Region: East US
    • Resource Mode: Workspace-based
    • Log Analytics Workspace: create new → bytelyst-logs
  3. Click Review + CreateCreate

Option B: Classic (matches current staging) Use the Az CLI snippet in the Optional: Application Insights section below.

Get connection details

  1. Go to the resource → Overview
  2. Copy Connection String
  3. Store it in Key Vault:
    • mindlyst-appinsights-connection-stringAPPLICATIONINSIGHTS_CONNECTION_STRING

Separating telemetry per app

In your application code, add a custom property to every trace:

// MindLyst
telemetryClient.context.tags['ai.cloud.role'] = 'mindlyst-web';

// LysnrAI
telemetryClient.context.tags['ai.cloud.role'] = 'lysnrai-admin';

Then filter in the Azure Portal: Application Insights → Logs → where cloud_RoleName == "mindlyst-web"


MindLyst Environment Variables

After completing all steps, create mindlyst-native/web/.env.local:

Note: MindLyst web API routes use x-user-id (or MINDLYST_USER_ID) as the Cosmos DB partition key for containers with /userId.

Hosted envs: Prefer Key Vault references / Managed Identity (see docs/devops/AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md). For local dev, generate .env.local from Key Vault.

KV_NAME="kv-mywisprai"

COSMOS_ENDPOINT="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-cosmos-endpoint --query value -o tsv)"
COSMOS_KEY="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-cosmos-key --query value -o tsv)"
COSMOS_DATABASE="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-cosmos-database --query value -o tsv)"

AZURE_OPENAI_ENDPOINT="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-endpoint --query value -o tsv)"
AZURE_OPENAI_KEY="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-key --query value -o tsv)"
AZURE_OPENAI_DEPLOYMENT="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-deployment --query value -o tsv)"
AZURE_OPENAI_API_VERSION="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-api-version --query value -o tsv)"

AZURE_SPEECH_KEY="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-speech-key --query value -o tsv)"
AZURE_SPEECH_REGION="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-speech-region --query value -o tsv)"

AZURE_BLOB_CONNECTION_STRING="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-blob-connection-string --query value -o tsv)"

ANH_CONNECTION_STRING="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-notification-hub-connection-string --query value -o tsv)"
APPLICATIONINSIGHTS_CONNECTION_STRING="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-appinsights-connection-string --query value -o tsv)"

cat > mindlyst-native/web/.env.local <<EOF
# ── Azure Cosmos DB ─────────────────────────────────────────
COSMOS_ENDPOINT=${COSMOS_ENDPOINT}
COSMOS_KEY=${COSMOS_KEY}
COSMOS_DATABASE=${COSMOS_DATABASE}

# ── Azure OpenAI (AI Foundry) ───────────────────────────────
OPENAI_PROVIDER=azure
AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
AZURE_OPENAI_KEY=${AZURE_OPENAI_KEY}
AZURE_OPENAI_DEPLOYMENT=${AZURE_OPENAI_DEPLOYMENT}
AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}

# ── Azure Speech ────────────────────────────────────────────
AZURE_SPEECH_KEY=${AZURE_SPEECH_KEY}
AZURE_SPEECH_REGION=${AZURE_SPEECH_REGION}

# ── Azure Blob Storage ──────────────────────────────────────
AZURE_BLOB_CONNECTION_STRING=${AZURE_BLOB_CONNECTION_STRING}

# ── Push Notifications ──────────────────────────────────────
ANH_CONNECTION_STRING=${ANH_CONNECTION_STRING}
ANH_HUB_NAME=mindlyst-hub

# ── App Insights ────────────────────────────────────────────
APPLICATIONINSIGHTS_CONNECTION_STRING=${APPLICATIONINSIGHTS_CONNECTION_STRING}

# ── App Config ──────────────────────────────────────────────
NEXT_PUBLIC_APP_NAME=MindLyst
NEXT_PUBLIC_APP_VERSION=0.2.0
MINDLYST_USER_ID=staging_user

# ── Abuse Controls (Web API) ────────────────────────────────
# Basic, per-instance rate limiting + payload guards for Azure OpenAI routes.
RATE_LIMIT_ENABLED=true
LLM_RATE_LIMIT_WINDOW_MS=60000
TRIAGE_RATE_LIMIT_LIMIT=30
BRAIN_CHAT_RATE_LIMIT_LIMIT=20
TRIAGE_MAX_CONTENT_CHARS=8000
BRAIN_CHAT_MAX_MESSAGE_CHARS=2000
BRAIN_CHAT_MAX_HISTORY_MESSAGES=20
BRAIN_CHAT_MAX_HISTORY_TOTAL_CHARS=10000
EOF

Verification Checklist

After completing all steps, verify:

# Check How
1 Cosmos DB: mindlyst database Data Explorer → expand cosmos-mywispraimindlyst → 12 containers visible
2 Cosmos DB: mywisprai (LysnrAI) untouched Data Explorer → mywisprai database still has its existing containers
3 Blob: 3 new containers Storage → bytelystblobs → Containers → mindlyst-voice, mindlyst-images, mindlyst-exports
4 Blob: other containers intact (if any) Same page → any pre-existing containers should still be present
5 OpenAI deployment Azure OpenAI Studio → Deployments → gpt-4o-mini listed
6 Speech Service keys Speech resource → Keys and Endpoint → keys visible
7 Key Vault: MindLyst secrets Key Vault → Secrets → mindlyst-* entries visible
8 MindLyst web connects Run cd mindlyst-native/web && npm run dev → triage/brain-chat should use Azure OpenAI (not mock)

Cost Estimates

Combined Cost (LysnrAI + MindLyst, MVP / Low Usage)

Service Pricing LysnrAI MindLyst Combined
Cosmos DB (Serverless) $0.25/M RU + $0.25/GB ~$2 ~$2 ~$4
Blob Storage (LRS) $0.018/GB ~$0.10 ~$0.10 ~$0.20
Azure OpenAI (GPT-4o-mini) $0.15/M input + $0.60/M output ~$5 ~$5 ~$10
Speech (F0 free) 5h STT free $0 $0 $0
Key Vault $0.03/10K ops ~$0.03 ~$0.03 ~$0.06
App Insights 5GB/month free $0 $0 $0
Notification Hub (Free) 1M pushes/month free $0 $0 $0
Total ~$515/month

Cost Savings from Sharing

  • Cosmos DB: 1 account instead of 2 = no extra overhead (Serverless has no idle cost)
  • OpenAI: 1 resource instead of 2 = no extra base cost (pay per token only)
  • Speech: 1 resource instead of 2 = shared 5h free tier (may need S0 upgrade sooner)
  • Blob Storage: 1 account instead of 2 = negligible savings but simpler key management
  • Estimated savings: ~$38/month at MVP scale

Resource Naming Convention

Azure Resource Current Name Ideal Name Notes
Resource Group rg-mywisprai rg-bytelyst Move resources to new RG (Step 1)
Cosmos DB Account cosmos-mywisprai cosmos-bytelyst Can't rename — keep as-is
Blob Storage bytelystblobs bytelystblobs Already brand-neutral
Azure OpenAI mywisprai-openai-sweden Can't rename — keep as-is
Speech Service mywisprai-speech Can't rename — keep as-is
Key Vault kv-mywisprai kv-bytelyst Can't rename — keep as-is
Notification Hub NS lysnnai bytelyst-hub-ns Consider creating new NS later if desired
App Insights bytelyst-appinsights Can recreate if you want workspace-based mode

Azure resources cannot be renamed after creation. The old names (cosmos-mywisprai, mywisprai-*, etc.) still work perfectly — they're just cosmetic. Only the RG can be "renamed" by moving resources to a new one.


Az CLI Script (Copy-Paste-Ready)

Run this from a machine with az CLI installed and logged in (az login). This script is idempotent — safe to re-run if interrupted.

#!/usr/bin/env bash
set -euo pipefail

# ─────────────────────────────────────────────────────────────
# ByteLyst — Azure Resource Setup Script
# Creates MindLyst deltas inside an existing shared resource group
# ─────────────────────────────────────────────────────────────

RG="rg-mywisprai"
COSMOS_ACCOUNT="cosmos-mywisprai"
COSMOS_DB="mindlyst"
STORAGE_ACCOUNT="bytelystblobs"

echo "=== 1/3  Cosmos DB: Ensure '$COSMOS_DB' database ==="
if az cosmosdb sql database show --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --name "$COSMOS_DB" >/dev/null 2>&1; then
  echo "  [exists] $COSMOS_DB"
else
  az cosmosdb sql database create --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --name "$COSMOS_DB" --query name -o tsv >/dev/null
  echo "  [created] $COSMOS_DB"
fi

echo ""
echo "=== 2/3  Cosmos DB: Ensure 12 MindLyst containers ==="
MINDLYST_CONTAINERS=(
  "users:/id"
  "brains:/userId"
  "brain_templates:/id"
  "memory_items:/userId"
  "actions:/memoryItemId"
  "entities:/memoryItemId"
  "reflections:/userId"
  "share_cards:/userId"
  "daily_briefs:/userId"
  "streaks:/userId"
  "notification_log:/userId"
  "brain_insights:/userId"
)

for spec in "${MINDLYST_CONTAINERS[@]}"; do
  name="${spec%%:*}"
  pk="${spec#*:}"
  if az cosmosdb sql container show --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --database-name "$COSMOS_DB" --name "$name" >/dev/null 2>&1; then
    echo "  [exists] $COSMOS_DB/$name"
  else
    az cosmosdb sql container create --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --database-name "$COSMOS_DB" --name "$name" --partition-key-path "$pk" --query name -o tsv >/dev/null
    echo "  [created] $COSMOS_DB/$name ($pk)"
  fi
done

echo ""
echo "=== 3/3  Blob Storage: Ensure MindLyst containers ==="
SA_KEY=$(az storage account keys list --resource-group "$RG" --account-name "$STORAGE_ACCOUNT" --query '[0].value' -o tsv)
for c in mindlyst-voice mindlyst-images mindlyst-exports; do
  created=$(az storage container create --name "$c" --account-name "$STORAGE_ACCOUNT" --account-key "$SA_KEY" --public-access off --query created -o tsv)
  if [ "$created" = "true" ]; then
    echo "  [created] blob/$c"
  else
    echo "  [exists] blob/$c"
  fi
done

echo ""
echo "=== Done ==="

Optional: Key Vault secrets (run separately if needed)

set -euo pipefail

# Populate/refresh MindLyst secrets in Key Vault by reading current values from Azure resources.
# No rotation: this copies current keys/config into Key Vault.

KV_NAME="kv-mywisprai"
RG="rg-mywisprai"

COSMOS_ENDPOINT="$(az cosmosdb show -g "$RG" -n cosmos-mywisprai --query documentEndpoint -o tsv)"
COSMOS_KEY="$(az cosmosdb keys list -g "$RG" -n cosmos-mywisprai --query primaryMasterKey -o tsv)"
STORAGE_CONNECTION_STRING="$(az storage account show-connection-string -g "$RG" -n bytelystblobs --query connectionString -o tsv)"

OPENAI_ENDPOINT="$(az cognitiveservices account show -g "$RG" -n mywisprai-openai-sweden --query '{name:name,kind:kind,location:location,endpoint:properties.endpoint}' -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["endpoint"])')"
OPENAI_KEY="$(az cognitiveservices account keys list -g "$RG" -n mywisprai-openai-sweden --query key1 -o tsv)"

SPEECH_REGION="$(az cognitiveservices account show -g "$RG" -n mywisprai-speech --query '{name:name,location:location,kind:kind,sku:sku.name}' -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["location"])')"
SPEECH_KEY="$(az cognitiveservices account keys list -g "$RG" -n mywisprai-speech --query key1 -o tsv)"

NOTIF_PRIMARY_CONNECTION_STRING="$(az notification-hub authorization-rule list-keys -g "$RG" --namespace-name lysnnai --notification-hub-name mindlyst-hub --name DefaultFullSharedAccessSignature -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["primaryConnectionString"])')"
APPINSIGHTS_CONNECTION_STRING="$(az monitor app-insights component show --app bytelyst-appinsights -g "$RG" -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["connectionString"])')"

# Write secrets (suppress output to avoid printing values)
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-cosmos-endpoint --value "$COSMOS_ENDPOINT" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-cosmos-key --value "$COSMOS_KEY" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-cosmos-database --value mindlyst -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-blob-connection-string --value "$STORAGE_CONNECTION_STRING" -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-endpoint --value "$OPENAI_ENDPOINT" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-key --value "$OPENAI_KEY" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-deployment --value gpt-4o-mini -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-api-version --value 2024-06-01 -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-speech-key --value "$SPEECH_KEY" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-speech-region --value "$SPEECH_REGION" -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-notification-hub-connection-string --value "$NOTIF_PRIMARY_CONNECTION_STRING" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-appinsights-connection-string --value "$APPINSIGHTS_CONNECTION_STRING" -o none

echo "✓ MindLyst secrets populated in $KV_NAME"

Optional: Notification Hub (run when mobile app is ready)

# Create namespace (if needed) + MindLyst hub
NS="lysnnai"
RG="rg-mywisprai"

az notification-hub namespace create \
  --resource-group "$RG" \
  --name "$NS" \
  --location eastus \
  --sku Free \
  2>/dev/null && echo "✓ Namespace $NS created" \
  || echo "→ Namespace $NS already exists"

az notification-hub create \
  --resource-group "$RG" \
  --namespace-name "$NS" \
  --name mindlyst-hub \
  2>/dev/null && echo "✓ Hub mindlyst-hub created" \
  || echo "→ Hub mindlyst-hub already exists"

Optional: Application Insights (shared telemetry)

RG="rg-mywisprai"
APP="bytelyst-appinsights"
LOC="eastus"

# Required once per subscription (App Insights may try to auto-register this provider)
az provider register --namespace Microsoft.OperationalInsights --wait

# Create (classic) App Insights component via ARM
az resource create \
  --resource-group "$RG" \
  --name "$APP" \
  --resource-type Microsoft.Insights/components \
  --is-full-object \
  --properties "{\"location\":\"$LOC\",\"kind\":\"web\",\"properties\":{\"Application_Type\":\"web\"}}"

# Get the connection string
APPINSIGHTS_CONNECTION_STRING="$(az resource show \
  --resource-group "$RG" \
  --name "$APP" \
  --resource-type Microsoft.Insights/components \
  --query properties.ConnectionString -o tsv)"

# Store it in Key Vault (treat as sensitive)
az keyvault secret set --vault-name kv-mywisprai --name mindlyst-appinsights-connection-string --value "$APPINSIGHTS_CONNECTION_STRING" -o none
echo "✓ Stored App Insights connection string in Key Vault: mindlyst-appinsights-connection-string"

Bicep IaC (Replicate To Another Account)

If you want to replicate this shared infrastructure into a different Azure subscription/account, use the Bicep templates in:

  • infra/azure/bytelyst-shared/

This folder also includes infra/azure/bytelyst-shared/main.json (compiled ARM template) which is tracked for easy diffing between runs:

az bicep build --file infra/azure/bytelyst-shared/main.bicep

What the Bicep creates

  • 1 Resource group (name is a parameter)
  • Cosmos DB (serverless) + MindLyst mindlyst database + 12 containers
  • (Optional) Cosmos DB empty database mywisprai (containers are app-specific; create separately)
  • Storage account + mindlyst-voice, mindlyst-images, mindlyst-exports containers (private)
  • Azure OpenAI (AI Foundry) account (optional: model deployment)
  • Speech account
  • Notification Hubs namespace + mindlyst-hub + auth rule
  • Application Insights
  • Key Vault (optional: auto-creates mindlyst-* secrets if deployerObjectId is provided)

If Key Vault secret creation is enabled, the template populates:

  • mindlyst-cosmos-endpoint
  • mindlyst-cosmos-key
  • mindlyst-cosmos-database
  • mindlyst-openai-endpoint
  • mindlyst-openai-key
  • mindlyst-openai-deployment
  • mindlyst-speech-key
  • mindlyst-speech-region
  • mindlyst-blob-connection-string
  • mindlyst-notification-hub-connection-string
  • mindlyst-appinsights-connection-string

Deploy (subscription scope)

  1. az login and select the right subscription:
az login
az account set --subscription "<SUBSCRIPTION_ID>"
  1. Register providers (first-time subscriptions):
az provider register --namespace Microsoft.DocumentDB --wait
az provider register --namespace Microsoft.Storage --wait
az provider register --namespace Microsoft.CognitiveServices --wait
az provider register --namespace Microsoft.KeyVault --wait
az provider register --namespace Microsoft.NotificationHubs --wait
az provider register --namespace Microsoft.Insights --wait
  1. Get your Azure AD user objectId (used to grant Key Vault secret permissions):
az ad signed-in-user show --query id -o tsv
  1. Edit the param file and pick globally unique names:
  • infra/azure/bytelyst-shared/params/staging.example.json
  1. Deploy:
az deployment sub create \
  --name bytelyst-shared-staging \
  --location eastus \
  --template-file infra/azure/bytelyst-shared/main.bicep \
  --parameters @infra/azure/bytelyst-shared/params/staging.example.json \
  --parameters deployerObjectId="<YOUR_OBJECT_ID>"

Note: openAiModelVersion is region-dependent. If its empty, the template will create the OpenAI account but skip creating the gpt-4o-mini deployment (create it manually in Azure AI Foundry).

Parameter mapping (current staging)

Use this mapping to understand how the current staging resource names relate to Bicep params. In a different account, you must choose new globally-unique names for most of these.

Bicep parameter Current staging value Notes
resourceGroupName rg-mywisprai In a new subscription, choose a new RG name (example: rg-bytelyst-staging)
cosmosAccountName cosmos-mywisprai Globally unique; must change in another subscription
storageAccountName bytelystblobs Globally unique; must change in another subscription
openAiAccountName mywisprai-openai-sweden Globally unique; must change in another subscription
speechAccountName mywisprai-speech Globally unique; must change in another subscription
keyVaultName kv-mywisprai Globally unique; must change in another subscription
notificationHubNamespaceName lysnnai Globally unique; must change in another subscription
mindlystHubName mindlyst-hub Not globally unique (scoped to the namespace)
appInsightsName bytelyst-appinsights Must be unique within the RG
createMywispraiDatabase N/A Creates an empty mywisprai database placeholder (LysnrAI, no containers)
deployerObjectId N/A If set, Bicep also creates Key Vault secrets (mindlyst-*)

What This Unlocks for MindLyst

Once all steps are complete, these implementation plan tasks are done:

Phase 0.3 (Azure Infrastructure):
[x] Reuse existing Cosmos DB account — add mindlyst database with 12 containers
[x] Reuse existing Blob Storage — add 3 mindlyst-prefixed containers
[x] Reuse existing Azure OpenAI (GPT-4o-mini) for triage + brain invocation
[x] Reuse existing Azure Speech Service for STT/TTS
[x] Add MindLyst secrets to existing Key Vault
[x] Create MindLyst notification hub
[x] Set up Application Insights for telemetry

Next steps after provisioning:

  1. (Done) MindLyst web: Enable Azure OpenAI triage + brain-chat (OPENAI_PROVIDER=azure + AZURE_OPENAI_*)
  2. (Done) MindLyst web: Cosmos persistence for /api/memory and /api/brains (COSMOS_*)
  3. Wire additional MindLyst API routes to Cosmos as needed (reflections, briefs, notifications, etc.)
  4. Implement voice capture STT pipeline with Azure Speech SDK
  5. Implement blob upload for voice/image captures to mindlyst-voice / mindlyst-images
  6. Implement push notifications via mindlyst-hub (once mobile app is ready)

Quick Reference: Portal URLs

Resource Direct Link
Resource Group portal.azure.com → Resource groups → rg-mywisprai
Cosmos DB Data Explorer portal.azure.com → cosmos-mywisprai → Data Explorer
Blob Containers portal.azure.com → bytelystblobs → Containers
OpenAI Studio oai.azure.com → select mywisprai-openai-sweden
Speech Keys portal.azure.com → mywisprai-speech → Keys and Endpoint
Key Vault Secrets portal.azure.com → kv-mywisprai → Secrets

This document lives in learning_ai_common_plat/docs/devops/. It is the single source of truth for Azure infrastructure setup shared by all products.