39 KiB
BytelystAI — 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-02-14
Security note (staging): This document does not include secret values. Store secrets in Azure Key Vault (
kv-mywisprai) and reference them by name (see MindLyst Environment Variables and Key Vault sections). Rotation is deferred; seedocs/devops/AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md.
Table of Contents
- Architecture Overview
- Existing Resources (Staging / Shared)
- What to Add for MindLyst
- Step 1: Resource Group
- Step 2: Cosmos DB — Add MindLyst Database
- Step 3: Blob Storage — Add MindLyst Containers
- Step 4: Azure OpenAI — Reuse Existing
- Step 5: Speech Service — Reuse Existing
- Step 6: Key Vault — Add MindLyst Secrets
- Step 7: Notification Hub — Add MindLyst Hub
- Step 8: Application Insights — Reuse or Create
- MindLyst Environment Variables
- Verification Checklist
- Cost Estimates
- Resource Naming Convention
- Az CLI Script (Copy-Paste-Ready)
- Bicep IaC (Replicate To Another Account)
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: 10 containers — existing)
│ └── mindlyst (database: 12 containers — MindLyst)
│
├── 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)
│ ├── wispr-* (existing secrets)
│ └── mindlyst-* (MindLyst secrets) ← NEW
│
├── 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 (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 (existing)
api_tokensaudit_logdeviceslicensespaymentssettingssubscriptionstranscriptsusage_dailyusers
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-endpointmindlyst-cosmos-keymindlyst-cosmos-databasemindlyst-openai-endpointmindlyst-openai-keymindlyst-openai-deploymentmindlyst-openai-api-versionmindlyst-speech-keymindlyst-speech-regionmindlyst-blob-connection-stringmindlyst-notification-hub-connection-stringmindlyst-appinsights-connection-string
LysnrAI secrets (existing):
wispr-azure-openai-deploymentwispr-azure-openai-endpointwispr-azure-openai-keywispr-azure-speech-keywispr-azure-speech-region
List what’s 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→ databasemindlystwith 12 containers - Blob Storage:
bytelystblobs→ containersmindlyst-voice,mindlyst-images,mindlyst-exports - Key Vault:
kv-mywisprai→ secretsmindlyst-* - Notification Hubs: namespace
lysnnai→ hubmindlyst-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.
- Go to portal.azure.com → Resource groups → search
rg-mywisprai - Click on the resource group → Overview
- Select all resources (checkbox at top of list)
- Click Move → Move to another resource group
- Click Create new → name it
rg-bytelyst→ same subscription - Click Next → wait for validation → click Move
- Wait for move to complete (~5–10 minutes)
- Delete the empty
rg-mywisprairesource 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-mywispraiaccount. Just add a new database and containers.
2a. Create the MindLyst database
- Go to Azure Cosmos DB → click
cosmos-mywisprai - Click Data Explorer (left sidebar)
- Click New Database (top toolbar)
- Database id:
mindlyst
- Database id:
- 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:
- Click New Container
- Existing Database: select
mindlyst(do NOT create a new database) - Enter the Container id from the table
- Enter the Partition key from the table (include the
/prefix) - Throughput: leave as Serverless (auto — no provisioned RUs)
- 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-endpoint→COSMOS_ENDPOINTmindlyst-cosmos-key→COSMOS_KEYmindlyst-cosmos-database→COSMOS_DATABASE(should bemindlyst)
Step 3: Blob Storage — Add MindLyst Containers
Reusing the existing
bytelystblobsstorage account. Just add 3 new containers.
- Go to Storage accounts → click
bytelystblobs - Go to Data storage → Containers (left sidebar)
- 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:
- Click + Container
- Enter the Name (must be lowercase, no underscores — use hyphens)
- Anonymous access level: leave as Private (no anonymous access)
- 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-string→AZURE_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-swedenresource withgpt-4o-minideployment serves both apps.
Connection details (from Key Vault)
Azure OpenAI connection details are stored in Key Vault:
mindlyst-openai-endpoint→AZURE_OPENAI_ENDPOINTmindlyst-openai-key→AZURE_OPENAI_KEYmindlyst-openai-deployment→AZURE_OPENAI_DEPLOYMENT(staging:gpt-4o-mini)mindlyst-openai-api-version→AZURE_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-key→AZURE_SPEECH_KEYmindlyst-speech-region→AZURE_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-mywispraiinrg-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 what’s 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 |
Existing OpenAI deployment name |
wispr-azure-openai-endpoint |
Existing OpenAI endpoint |
wispr-azure-openai-key |
Existing OpenAI key |
wispr-azure-speech-key |
Existing Speech key |
wispr-azure-speech-region |
Existing Speech region |
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
- Go to Notification Hub Namespaces → check if
lysnnaiexists - If it exists, open it → skip to 7c
- If not, continue to 7b
7b. Create namespace (if needed)
- Search "Notification Hubs" → click Create Notification Hub Namespace
- Fill in:
- Resource group:
rg-mywisprai - Namespace name:
lysnnai - Location:
East US - Pricing tier: Free (up to 1M pushes/month)
- Resource group:
- Click Create
7c. Create MindLyst hub
- Inside the namespace → click + Notification Hub
- Hub name:
mindlyst-hub - Click Create
7d. Configure APNs and FCM (later — when mobile app is ready)
- Go to
mindlyst-hub→ Settings → Apple (APNS)- Upload your MindLyst APNs certificate or token key
- Go to Settings → Google (FCM v1)
- Enter MindLyst Firebase project credentials
Connection details
- Go to
mindlyst-hub→ Settings → Access Policies - Copy the connection string for
DefaultFullSharedAccessSignature(Send + Manage)
Store it in Key Vault:
mindlyst-notification-hub-connection-string→ANH_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-appinsightsinrg-mywisprai(East US).
Create (if not exists)
Option A: Workspace-based (recommended)
- Search "Application Insights" → click Create
- Fill in:
- Resource group:
rg-mywisprai - Name:
bytelyst-appinsights - Region:
East US - Resource Mode: Workspace-based
- Log Analytics Workspace: create new →
bytelyst-logs
- Resource group:
- Click Review + Create → Create
Option B: Classic (matches current staging) Use the Az CLI snippet in the Optional: Application Insights section below.
Get connection details
- Go to the resource → Overview
- Copy Connection String
- Store it in Key Vault:
mindlyst-appinsights-connection-string→APPLICATIONINSIGHTS_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"] = "mywisprai-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(orMINDLYST_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.localfrom 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-mywisprai → mindlyst → 12 containers visible |
| 2 | Cosmos DB: mywisprai 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 | ~$5–15/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: ~$3–8/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
azCLI installed and logged in (az login). This script is idempotent — safe to re-run if interrupted.
#!/usr/bin/env bash
set -euo pipefail
# ─────────────────────────────────────────────────────────────
# BytelystAI — 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
mindlystdatabase + 12 containers - (Optional) Cosmos DB empty database
mywisprai(containers are app-specific; create separately) - Storage account +
mindlyst-voice,mindlyst-images,mindlyst-exportscontainers (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 ifdeployerObjectIdis provided)
If Key Vault secret creation is enabled, the template populates:
mindlyst-cosmos-endpointmindlyst-cosmos-keymindlyst-cosmos-databasemindlyst-openai-endpointmindlyst-openai-keymindlyst-openai-deploymentmindlyst-speech-keymindlyst-speech-regionmindlyst-blob-connection-stringmindlyst-notification-hub-connection-stringmindlyst-appinsights-connection-string
Deploy (subscription scope)
az loginand select the right subscription:
az login
az account set --subscription "<SUBSCRIPTION_ID>"
- 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
- Get your Azure AD user objectId (used to grant Key Vault secret permissions):
az ad signed-in-user show --query id -o tsv
- Edit the param file and pick globally unique names:
infra/azure/bytelyst-shared/params/staging.example.json
- 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:
openAiModelVersionis region-dependent. If it’s empty, the template will create the OpenAI account but skip creating thegpt-4o-minideployment (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 (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:
- (Done) MindLyst web: Enable Azure OpenAI triage + brain-chat (
OPENAI_PROVIDER=azure+AZURE_OPENAI_*) - (Done) MindLyst web: Cosmos persistence for
/api/memoryand/api/brains(COSMOS_*) - Wire additional MindLyst API routes to Cosmos as needed (reflections, briefs, notifications, etc.)
- Implement voice capture STT pipeline with Azure Speech SDK
- Implement blob upload for voice/image captures to
mindlyst-voice/mindlyst-images - 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.