From d46be038a052064a26b288e2920b965e1ff07e15 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 8 Mar 2026 14:34:42 -0400 Subject: [PATCH 1/2] notes in stand-by --- z_temp/keycloak-compose.yaml | 65 ++++++++++++++++++++++++++++++++++++ z_temp/note-compose.yml | 57 +++++++++++++++++++++++++++++++ z_temp/notes.md | 12 +++++++ 3 files changed, 134 insertions(+) create mode 100644 z_temp/keycloak-compose.yaml create mode 100644 z_temp/note-compose.yml create mode 100644 z_temp/notes.md diff --git a/z_temp/keycloak-compose.yaml b/z_temp/keycloak-compose.yaml new file mode 100644 index 0000000..599afe1 --- /dev/null +++ b/z_temp/keycloak-compose.yaml @@ -0,0 +1,65 @@ +name: keycloak-dlvhome + +services: + keycloak: + container_name: keycloak + image: quay.io/keycloak/keycloak:26.5.4 + restart: always + # ports: + # - "8443:8443" + # - "8280:8080" + volumes: + - "./data/certs:/etc/x509/https:ro" + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_USER} + KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_PASSWORD} + KC_HOSTNAME: ${KEYCLOAK_URL} + + KC_PROXY_ADDRESS_FORWARDING: "true" + KC_DB_URL: jdbc:postgresql://keycloak_postgres:5432/keycloak + KC_DB: postgres + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + KC_HTTPS_CERTIFICATE_FILE: /etc/x509/https/dlvhome.crt + KC_HTTPS_CERTIFICATE_KEY_FILE: /etc/x509/https/dlvhome.key + KC_HTTP_ENABLED: "true" + KC_LOG_LEVEL: debug + KC_FEATURES: "token-exchange" + + + labels: + - traefik.enable=true + - traefik.http.routers.keycloak-http.rule=Host(`keycloak.c5m.ca`) + - traefik.http.routers.keycloak-http.entrypoints=web + + depends_on: + - keycloak_postgres + networks: + - proxy + command: + - 'start' + + keycloak_postgres: + container_name: keycloak_postgres + image: postgres:14.18 + restart: always + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PORT: 5432 + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - proxy + + +networks: + proxy: + external: true + # keycloak-network: + # name: keycloak-network + # driver: bridge + +volumes: + postgres_data: diff --git a/z_temp/note-compose.yml b/z_temp/note-compose.yml new file mode 100644 index 0000000..a1524f5 --- /dev/null +++ b/z_temp/note-compose.yml @@ -0,0 +1,57 @@ +name: 2note-bookmark + +services: + api: + image: "fboucher/notebookmark-api:alpha-latest" + container_name: "2notebookmark-api" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "8000" + ConnectionStrings__nb-tables: "${NB_STORAGE_OUTPUTS_TABLEENDPOINT}" + ConnectionStrings__nb-blobs: "${NB_STORAGE_OUTPUTS_BLOBENDPOINT}" + ports: + - "8001:8000" + - "8003:8002" + restart: unless-stopped + networks: + - "proxy" + + + blazor-app: + image: "fboucher/notebookmark-blazor:alpha-latest" + container_name: "2notebookmark-blazor" + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "8004" + services__api__http__0: "http://api:8000" + REKA_API_KEY: "${REKA_API_KEY}" + ConnectionStrings__nb-tables: "${NB_STORAGE_OUTPUTS_TABLEENDPOINT}" + ConnectionStrings__nb-blobs: "${NB_STORAGE_OUTPUTS_BLOBENDPOINT}" + services__keycloak__http__0: "http://keycloak:8080" + Keycloak__Authority: "${KEYCLOAK_AUTHORITY}" + Keycloak__ClientId: "${KEYCLOAK_CLIENT_ID}" + Keycloak__ClientSecret: "${KEYCLOAK_CLIENT_SECRET}" + volumes: + - ./dataprotection-keys:/root/.aspnet/DataProtection-Keys + labels: + - traefik.enable=true + - traefik.http.routers.notebookmark.rule=Host(`nb.c5m.ca`) + - traefik.http.routers.notebookmark.entrypoints=web + - traefik.http.routers.notebookmark.middlewares=nb-https-headers + - traefik.http.middlewares.nb-https-headers.headers.customrequestheaders.X-Forwarded-Proto=https + depends_on: + api: + condition: "service_started" + restart: unless-stopped + networks: + - "proxy" + +networks: + proxy: + external: true diff --git a/z_temp/notes.md b/z_temp/notes.md new file mode 100644 index 0000000..0f4de59 --- /dev/null +++ b/z_temp/notes.md @@ -0,0 +1,12 @@ + + +KEYCLOAK_AUTHORITY=https://keycloak.c5m.ca/realms/notebookmark + + + + +Valid redirect URIs: https://nb.c5m.ca/* + +Valid post logout redirect URIs: https://nb.c5m.ca/* + +Web origins: https://nb.c5m.ca \ No newline at end of file From a02fdbe47bd9e4ec9caa87dc346dc29d5b324c7f Mon Sep 17 00:00:00 2001 From: Frank Boucher Date: Sun, 8 Mar 2026 16:10:29 -0400 Subject: [PATCH 2/2] docs: Refactor Docker Compose files and update Keycloak setup guides Reorganizes the Docker Compose configuration into modular stacks for Keycloak and the application. This change updates the environment variable templates and provides new, detailed documentation for local container setup and realm configuration. --- .env-sample | 35 +++--- README.md | 14 ++- docker-compose/build-and-push.ps1 | 52 -------- docker-compose/docker-compose.yaml | 66 ---------- docker-compose/keycloak-compose.yaml | 51 ++++++++ docker-compose/note-compose.yaml | 53 ++++++++ docs/docker-compose-deployment.md | 174 +++++---------------------- docs/keycloak-container-setup.md | 74 ++++++++++++ docs/keycloak-setup.md | 98 ++++++--------- z_temp/keycloak-compose.yaml | 65 ---------- z_temp/note-compose.yml | 57 --------- z_temp/notes.md | 12 -- 12 files changed, 284 insertions(+), 467 deletions(-) delete mode 100644 docker-compose/build-and-push.ps1 delete mode 100644 docker-compose/docker-compose.yaml create mode 100644 docker-compose/keycloak-compose.yaml create mode 100644 docker-compose/note-compose.yaml create mode 100644 docs/keycloak-container-setup.md delete mode 100644 z_temp/keycloak-compose.yaml delete mode 100644 z_temp/note-compose.yml delete mode 100644 z_temp/notes.md diff --git a/.env-sample b/.env-sample index 3c7da79..2db3554 100644 --- a/.env-sample +++ b/.env-sample @@ -1,22 +1,29 @@ -# NoteBookmark Docker Compose Environment Variables -# Copy this file to .env and replace all placeholder values with your actual configuration +# Copy to docker-compose/.env and set values. -# Keycloak Admin Credentials -KEYCLOAK_ADMIN_PASSWORD=your-secure-admin-password +# Keycloak +KEYCLOAK_USER=admin +KEYCLOAK_PASSWORD=admin -# Keycloak Client Configuration +# Keycloak host (local default). +KEYCLOAK_URL=localhost + +# Postgres for Keycloak. +POSTGRES_USER=keycloak +POSTGRES_PASSWORD=change-me + +# App auth (OIDC) KEYCLOAK_AUTHORITY=http://localhost:8080/realms/notebookmark KEYCLOAK_CLIENT_ID=notebookmark -KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret +KEYCLOAK_CLIENT_SECRET=replace-with-client-secret -# Azure Storage - Table Storage Connection -NB_STORAGE_OUTPUTS_TABLEENDPOINT=https://your-storage-account.table.core.windows.net/ +# Optional +# Keycloak__RequireHttpsMetadata=false -# Azure Storage - Blob Storage Connection +# AI +REKA_API_KEY=replace-with-reka-api-key + +# Storage +NB_STORAGE_OUTPUTS_TABLEENDPOINT=https://your-storage-account.table.core.windows.net/ NB_STORAGE_OUTPUTS_BLOBENDPOINT=https://your-storage-account.blob.core.windows.net/ -# Notes: -# - Never commit the .env file to version control -# - Keep credentials secure and rotate them regularly -# - For local development, you can use "admin" as KEYCLOAK_ADMIN_PASSWORD -# - For production, use strong passwords and proper Azure Storage connection strings +# Do not commit docker-compose/.env. diff --git a/README.md b/README.md index ca99229..b180fe8 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ NoteBookmark is composed of three main sections: ![Slide show of all NoteBookmark Screens](gh/images/NoteBookmark-Tour_hd.gif) +## Run Options + +- Development: running the Aspire project is the easiest path and everything is wired automatically. +- Production-style: run with containers and deploy to Azure. + +Run locally with Aspire: + +```bash +dotnet run --project src/NoteBookmark.AppHost +``` + ## How to deploy Your own NoteBookmark ### Get the code on your machine @@ -52,8 +63,9 @@ Voila! Your app is now secure. ## Documentation For detailed setup guides and configuration information: +- [Keycloak Container Setup](/docs/keycloak-container-setup.md) - Start a local Keycloak instance if you do not already have one - [Keycloak Authentication Setup](/docs/keycloak-setup.md) - Complete guide for setting up Keycloak authentication -- [Docker Compose Deployment](/docs/docker-compose-deployment.md) - Deploy with Docker Compose (generate from Aspire or use provided files) +- [Docker Compose Deployment](/docs/docker-compose-deployment.md) - Deploy NoteBookmark containers (assumes a healthy Keycloak + configured realm) ## Contributing diff --git a/docker-compose/build-and-push.ps1 b/docker-compose/build-and-push.ps1 deleted file mode 100644 index f0cdc83..0000000 --- a/docker-compose/build-and-push.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -# Build and Push Docker Images Script -# Make sure you're logged in to Docker Hub first: docker login - -param( - [Parameter(Mandatory=$true)] - [string]$DockerHubUsername, - - [string]$ApiTag = "latest", - [string]$BlazorTag = "latest" -) - -Write-Host "Building and pushing Docker images for NoteBookmark..." -ForegroundColor Green - -# Build API image -Write-Host "Building API image..." -ForegroundColor Yellow -docker build -f ../src/NoteBookmark.Api/Dockerfile -t "$DockerHubUsername/notebookmark-api:$ApiTag" .. - -if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to build API image" - exit 1 -} - -# Build Blazor App image -Write-Host "Building Blazor App image..." -ForegroundColor Yellow -docker build -f ../src/NoteBookmark.BlazorApp/Dockerfile -t "$DockerHubUsername/notebookmark-blazor:$BlazorTag" .. - -if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to build Blazor App image" - exit 1 -} - -# Push API image -Write-Host "Pushing API image to Docker Hub..." -ForegroundColor Yellow -docker push "$DockerHubUsername/notebookmark-api:$ApiTag" - -if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to push API image" - exit 1 -} - -# Push Blazor App image -Write-Host "Pushing Blazor App image to Docker Hub..." -ForegroundColor Yellow -docker push "$DockerHubUsername/notebookmark-blazor:$BlazorTag" - -if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to push Blazor App image" - exit 1 -} - -Write-Host "Successfully built and pushed both images!" -ForegroundColor Green -Write-Host "API image: $DockerHubUsername/notebookmark-api:$ApiTag" -ForegroundColor Cyan -Write-Host "Blazor image: $DockerHubUsername/notebookmark-blazor:$BlazorTag" -ForegroundColor Cyan diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml deleted file mode 100644 index 3afe790..0000000 --- a/docker-compose/docker-compose.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: note-bookmark - -services: - keycloak: - image: "quay.io/keycloak/keycloak:26.1" - container_name: "notebookmark-keycloak" - command: ["start-dev"] - environment: - KEYCLOAK_ADMIN: "admin" - KEYCLOAK_ADMIN_PASSWORD: "${KEYCLOAK_ADMIN_PASSWORD:-admin}" - KC_HTTP_PORT: "8080" - KC_HOSTNAME_STRICT: "false" - KC_HOSTNAME_STRICT_HTTPS: "false" - KC_HTTP_ENABLED: "true" - ports: - - "8080:8080" - volumes: - - keycloak-data:/opt/keycloak/data - networks: - - "aspire" - api: - image: "fboucher/notebookmark-api:latest" - container_name: "notebookmark-api" - environment: - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" - ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" - HTTP_PORTS: "8000" - ConnectionStrings__nb-tables: "${NB_STORAGE_OUTPUTS_TABLEENDPOINT}" - ConnectionStrings__nb-blobs: "${NB_STORAGE_OUTPUTS_BLOBENDPOINT}" - ports: - - "8001:8000" - - "8003:8002" - networks: - - "aspire" - blazor-app: - image: "fboucher/notebookmark-blazor:latest" - container_name: "notebookmark-blazor" - environment: - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" - ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" - HTTP_PORTS: "8004" - services__api__http__0: "http://api:8000" - services__keycloak__http__0: "http://keycloak:8080" - Keycloak__Authority: "${KEYCLOAK_AUTHORITY:-http://keycloak:8080/realms/notebookmark}" - Keycloak__ClientId: "${KEYCLOAK_CLIENT_ID:-notebookmark}" - Keycloak__ClientSecret: "${KEYCLOAK_CLIENT_SECRET}" - ports: - - "8005:8004" - - "8007:8006" - depends_on: - api: - condition: "service_started" - keycloak: - condition: "service_started" - networks: - - "aspire" -volumes: - keycloak-data: - driver: "local" -networks: - aspire: - driver: "bridge" diff --git a/docker-compose/keycloak-compose.yaml b/docker-compose/keycloak-compose.yaml new file mode 100644 index 0000000..a18921f --- /dev/null +++ b/docker-compose/keycloak-compose.yaml @@ -0,0 +1,51 @@ +name: notebookmark-keycloak + +services: + keycloak_postgres: + container_name: keycloak-postgres + image: postgres:14.18 + restart: unless-stopped + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - notebookmark + + keycloak: + container_name: notebookmark-keycloak + image: quay.io/keycloak/keycloak:26.5.4 + restart: unless-stopped + command: + - start + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_USER} + KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_PASSWORD} + KC_HOSTNAME: ${KEYCLOAK_URL} + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://keycloak_postgres:5432/keycloak + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + KC_PROXY_ADDRESS_FORWARDING: "true" + KC_HTTP_ENABLED: "true" + KC_LOG_LEVEL: info + KC_FEATURES: "token-exchange" + ports: + - "8080:8080" + # Optional production TLS setup: place cert/key under docker-compose/data/certs. + # These values can remain unset for local HTTP usage. + volumes: + - ./data/certs:/etc/x509/https:ro + depends_on: + - keycloak_postgres + networks: + - notebookmark + +networks: + notebookmark: + external: true + +volumes: + postgres-data: \ No newline at end of file diff --git a/docker-compose/note-compose.yaml b/docker-compose/note-compose.yaml new file mode 100644 index 0000000..e732a12 --- /dev/null +++ b/docker-compose/note-compose.yaml @@ -0,0 +1,53 @@ +name: notebookmark-app + +services: + api: + image: fboucher/notebookmark-api:alpha-latest + container_name: notebookmark-api + restart: unless-stopped + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "8000" + ConnectionStrings__nb-tables: ${NB_STORAGE_OUTPUTS_TABLEENDPOINT} + ConnectionStrings__nb-blobs: ${NB_STORAGE_OUTPUTS_BLOBENDPOINT} + ports: + - "8001:8000" + - "8003:8002" + networks: + - notebookmark + + blazor-app: + image: fboucher/notebookmark-blazor:alpha-latest + container_name: notebookmark-blazor + restart: unless-stopped + environment: + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" + OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + HTTP_PORTS: "8004" + services__api__http__0: "http://api:8000" + services__keycloak__http__0: "http://keycloak:8080" + ConnectionStrings__nb-tables: ${NB_STORAGE_OUTPUTS_TABLEENDPOINT} + ConnectionStrings__nb-blobs: ${NB_STORAGE_OUTPUTS_BLOBENDPOINT} + REKA_API_KEY: ${REKA_API_KEY} + Keycloak__Authority: ${KEYCLOAK_AUTHORITY} + Keycloak__ClientId: ${KEYCLOAK_CLIENT_ID} + Keycloak__ClientSecret: ${KEYCLOAK_CLIENT_SECRET} + volumes: + - ./dataprotection-keys:/root/.aspnet/DataProtection-Keys + ports: + - "8005:8004" + - "8007:8006" + depends_on: + api: + condition: service_started + networks: + - notebookmark + +networks: + notebookmark: + external: true \ No newline at end of file diff --git a/docs/docker-compose-deployment.md b/docs/docker-compose-deployment.md index acdf9e2..d48b557 100644 --- a/docs/docker-compose-deployment.md +++ b/docs/docker-compose-deployment.md @@ -1,177 +1,69 @@ # Docker Compose Deployment -This guide explains how to deploy NoteBookmark using Docker Compose, either by generating it fresh from Aspire or using the provided compose file. +This file assumes you already have: -## Two Deployment Options +- A healthy Keycloak instance +- A `notebookmark` realm configured (see [`docs/keycloak-setup.md`](keycloak-setup.md)) -### Option 1: Generate from Aspire (Recommended) +If you do not have Keycloak yet, see [`docs/keycloak-container-setup.md`](keycloak-container-setup.md) first. -Generate an up-to-date docker-compose.yaml from your Aspire AppHost configuration using the official Aspire CLI. -**Prerequisites:** -- .NET Aspire Workload installed: `dotnet workload install aspire` -- Aspire CLI installed: Included with the Aspire workload +## Prerequisites -**Steps:** +- Docker Engine with Compose support (`docker compose`) +- `docker-compose/.env` with valid values +- Azure Storage endpoints (Table + Blob) +- Keycloak client secret for client `notebookmark` -1. **Build container images locally:** - - The generated docker-compose file references image names (e.g., `notebookmark-api`, `notebookmark-blazor`), but these images don't exist until you build them. Build and tag the images with the expected names: - - ```bash - # Build API image - dotnet publish src/NoteBookmark.Api/NoteBookmark.Api.csproj -c Release -t:PublishContainer - - # Build Blazor app image - dotnet publish src/NoteBookmark.BlazorApp/NoteBookmark.BlazorApp.csproj -c Release -t:PublishContainer - ``` - - These commands build the projects and create Docker images tagged as `notebookmark-api:latest` and `notebookmark-blazorapp:latest` (based on your project names). The container names `notebookmark-api` and `notebookmark-blazor` are what the running containers will be called. +## 1. Prepare Environment Values -2. **Publish the application (generates Docker Compose files):** - ```bash - aspire publish --output-path ./aspire-output --project-name notebookmark - ``` - - **Parameters:** - - `--output-path`: Directory where docker-compose files will be generated (default: `aspire-output`) - - `--project-name`: Docker Compose project name (sets `name:` at the top of docker-compose.yaml) - - Without this, the project name defaults to the output directory name - - Affects container names: `notebookmark-api`, `notebookmark-blazor` vs `aspire-output-api`, `aspire-output-blazor` - - This command generates: - - `docker-compose.yaml` from the AppHost configuration - - `.env` file template with expected parameters (unfilled) - - Supporting infrastructure files (Bicep, Azure configs if applicable) +From the repository root: -3. **Fill in environment variables:** - Edit `./aspire-output/.env` and replace placeholder values with your actual configuration: - - Azure Storage connection strings - - Keycloak admin password and client secrets - - Any other environment-specific settings - -4. **Deploy (optional - full workflow):** - ```bash - aspire deploy --output-path ./aspire-output - ``` - This performs the complete workflow: publishes, prepares environment configs, builds images, and runs `docker compose up`. - - Or manually run Docker Compose from the output directory: - ```bash - cd aspire-output - docker compose up -d - ``` - -This ensures your docker-compose file stays in sync with the latest AppHost configuration. - -> **📚 Learn more:** See the [official Aspire Docker integration documentation](https://aspire.dev/integrations/compute/docker/) for advanced scenarios like environment-specific configs and custom image tagging. - -### Option 2: Use the Provided Compose File (Quick Start) - -For a quick start, you can use the checked-in docker-compose.yaml file located in the `docker-compose/` directory. This file was generated from Aspire and committed to the repository for convenience. - -**When to use this option:** -- You want to quickly test the application without regenerating compose files -- You're deploying a stable release version -- You haven't modified the AppHost configuration - -**Important:** If you've modified `src/NoteBookmark.AppHost/AppHost.cs`, use Option 1 to regenerate the compose file to reflect your changes. - -## Environment Configuration - -The docker-compose.yaml file uses environment variables for configuration. You must create a `.env` file in the same directory as your docker-compose.yaml file. - -### What the .env File Is For - -The `.env` file contains sensitive configuration values needed for production deployment: - -- **Database connection strings**: Connection to Azure Table Storage and Blob Storage -- **Keycloak configuration**: Authentication server settings (authority URL, client credentials) -- **Other runtime settings**: Any environment-specific configurations - -### Creating Your .env File - -1. Copy the `.env-sample` file from the repository root: - ```bash - cp .env-sample .env - ``` +```bash +cp .env-sample docker-compose/.env +``` -2. Edit `.env` and replace all placeholder values with your actual configuration: - - Azure Storage connection strings - - Keycloak admin password - - Keycloak client secret - - Keycloak authority URL (if different from default) +Edit `docker-compose/.env` and set all required values. -3. Keep `.env` secure and never commit it to version control (it's in .gitignore) +Important Keycloak values for NoteBookmark: -## Running the Application +- `KEYCLOAK_AUTHORITY` (for example `http://localhost:8080/realms/notebookmark`) +- `KEYCLOAK_CLIENT_ID` (default: `notebookmark`) +- `KEYCLOAK_CLIENT_SECRET` (from Keycloak client settings) -Once your `.env` file is configured: +## 2. Create Shared Network (One Time) -**If using Option 1 (Aspire-generated):** ```bash -cd aspire-output -docker compose up -d +docker network create notebookmark ``` -**If using Option 2 (checked-in file):** +Then move into the compose folder so `.env` is auto-detected: + ```bash cd docker-compose -docker compose up -d ``` -Access the application at: -- **Blazor App**: http://localhost:8005 -- **API**: http://localhost:8001 -- **Keycloak**: http://localhost:8080 - -**First-time setup:** Keycloak needs to be configured with the realm settings. See [Keycloak Setup Guide](./keycloak-setup.md) for detailed instructions. - -## Stopping the Application +## 3. Start NoteBookmark App ```bash -docker compose down +docker compose -f note-compose.yaml up -d ``` -To also remove volumes (WARNING: This deletes Keycloak data): - -```bash -docker compose down -v -``` +## 4. Access Services -## Advanced Deployment Workflows +- Blazor App: `http://localhost:8005` +- API: `http://localhost:8001` -The Aspire CLI supports environment-specific deployments: +## 5. Stop NoteBookmark App -**Prepare for a specific environment:** ```bash -# For staging -aspire do prepare-docker-env --environment staging - -# For production -aspire do prepare-docker-env --environment production +docker compose -f note-compose.yaml down ``` -This generates environment-specific `.env` files and builds container images. +## Quick Validation -**Clean up a deployment:** ```bash -aspire do docker-compose-down-docker-env +docker compose -f note-compose.yaml config ``` -This stops and removes all containers, networks, and volumes. - -## Notes - -- **Development vs Production:** - - In development (`dotnet run`), Aspire manages Keycloak automatically via `AddKeycloak()` - - In production (docker-compose), Keycloak runs as a containerized service - - The AppHost uses `AddDockerComposeEnvironment("docker-env")` to signal Azure Container Apps deployment intent - -- **Service Discovery:** - - Development: Aspire service discovery wires up connections automatically - - Production: Services connect via explicit environment variables in `.env` - -- **Data Persistence:** - - Keycloak data persists in a named volume (`keycloak-data`) - - Use `docker compose down -v` carefully — it deletes all data including Keycloak configuration + diff --git a/docs/keycloak-container-setup.md b/docs/keycloak-container-setup.md new file mode 100644 index 0000000..9f3ed6c --- /dev/null +++ b/docs/keycloak-container-setup.md @@ -0,0 +1,74 @@ +# Keycloak Container Setup (If You Do Not Have Keycloak Yet) + +Use this file only to get a Keycloak container running for NoteBookmark. + +After Keycloak is up, continue with: + +1. [`docs/keycloak-setup.md`](keycloak-setup.md) to configure realm/client +2. [`docs/docker-compose-deployment.md`](docker-compose-deployment.md) to run NoteBookmark + +## Official References + +- Keycloak container guide: +- Keycloak configuration docs: + +## 1. Prepare Environment File + +From repository root: + +```bash +cp .env-sample docker-compose/.env +``` + +Set at least these values in `docker-compose/.env`: + +```env +KEYCLOAK_USER=admin +KEYCLOAK_PASSWORD=change-me +KEYCLOAK_URL=localhost +POSTGRES_USER=keycloak +POSTGRES_PASSWORD=change-me +``` + +## 2. Create Shared Network (One Time) + +```bash +docker network create notebookmark +``` + +Then move into the compose folder so `.env` is auto-detected: + +```bash +cd docker-compose +``` + +## 3. Start Keycloak Stack + +```bash +docker compose -f keycloak-compose.yaml up -d +``` + +This starts: + +- `keycloak_postgres` +- `keycloak` + +Keycloak admin console: `http://localhost:8080` + +## 4. Stop Keycloak Stack + +```bash +docker compose -f keycloak-compose.yaml down +``` + +Remove Keycloak database volume (deletes Keycloak data): + +```bash +docker compose -f keycloak-compose.yaml down -v +``` + +## Quick Validation + +```bash +docker compose -f keycloak-compose.yaml config +``` diff --git a/docs/keycloak-setup.md b/docs/keycloak-setup.md index f62248c..523f251 100644 --- a/docs/keycloak-setup.md +++ b/docs/keycloak-setup.md @@ -1,82 +1,62 @@ -# Keycloak Authentication Setup +# Keycloak Realm Setup For NoteBookmark -## Overview +This file explains only how to configure Keycloak for NoteBookmark. -NoteBookmark now requires authentication via Keycloak (or any OpenID Connect provider). Only the home page is accessible without authentication - all other pages require a logged-in user. +If you do not have a Keycloak server yet, use [`docs/keycloak-container-setup.md`](keycloak-container-setup.md) first. -## Configuration +## Official References -### 1. Keycloak Server Setup +- Keycloak server administration guide: +- Keycloak securing applications (OIDC clients): -You'll need a Keycloak server running. For local development: +## 1. Create Realm -```bash -docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:latest start-dev -``` +In the Keycloak admin console, create a realm named: -### 2. Create a Realm +- `notebookmark` -1. Log into Keycloak admin console (http://localhost:8080) -2. Create a new realm called "notebookmark" +## 2. Create OIDC Client -### 3. Create a Client +In realm `notebookmark`, create a client with: -1. In the realm, create a new client: - - Client ID: `notebookmark` - - Client Protocol: `openid-connect` - - Access Type: `confidential` - - Valid Redirect URIs: `https://localhost:5001/*` (adjust for your environment) - - Web Origins: `https://localhost:5001` (adjust for your environment) +- Client ID: `notebookmark` +- Protocol: OpenID Connect +- Client authentication: Enabled (confidential client) +- Standard flow: Enabled -2. Get the client secret from the Credentials tab +Set redirect and origin values for your app URL. -### 4. Configure the Application +Local example: -Update `appsettings.json` or environment variables: +- Valid redirect URIs: `http://localhost:8005/*` +- Valid post logout redirect URIs: `http://localhost:8005/*` +- Web origins: `http://localhost:8005` -```json -{ - "Keycloak": { - "Authority": "http://localhost:8080/realms/notebookmark", - "ClientId": "notebookmark", - "ClientSecret": "your-client-secret-here" - } -} -``` +Then copy the generated client secret. -**Environment Variables (recommended for production):** +## 3. Map Keycloak Values To NoteBookmark -```bash -export Keycloak__Authority="https://your-keycloak-server/realms/notebookmark" -export Keycloak__ClientId="notebookmark" -export Keycloak__ClientSecret="your-secret" -``` +Use these values in `docker-compose/.env`: -### 5. Add Users - -In Keycloak, create users in the realm who should have access to your private website. +```env +KEYCLOAK_AUTHORITY=http://localhost:8080/realms/notebookmark +KEYCLOAK_CLIENT_ID=notebookmark +KEYCLOAK_CLIENT_SECRET=your-client-secret +``` -## How It Works +These are consumed by `docker-compose/note-compose.yaml`: -- **Home page (/)**: Public - no authentication required -- **All other pages**: Protected with `[Authorize]` attribute -- **Login/Logout**: UI in the header shows login button when not authenticated -- **Session**: Uses cookie-based authentication with OpenID Connect +- `Keycloak__Authority: ${KEYCLOAK_AUTHORITY}` +- `Keycloak__ClientId: ${KEYCLOAK_CLIENT_ID}` +- `Keycloak__ClientSecret: ${KEYCLOAK_CLIENT_SECRET}` -## Technical Details +## 4. Validate Before Running NoteBookmark -- Uses `Microsoft.AspNetCore.Authentication.OpenIdConnect` package -- Cookie-based session management -- Authorization state cascaded throughout the component tree -- `AuthorizeRouteView` in Routes.razor handles route-level protection +Check that: -## Files Modified +- Realm is exactly `notebookmark` +- Client ID is exactly `notebookmark` +- Client secret in `.env` matches Keycloak +- Redirect URI matches your app URL -- `Program.cs`: Added authentication middleware and configuration -- `Routes.razor`: Changed to `AuthorizeRouteView` for authorization support -- `MainLayout.razor`: Added `LoginDisplay` component to header -- `_Imports.razor`: Added authorization namespaces -- All pages except `Home.razor`: Added `@attribute [Authorize]` -- `Components/Shared/LoginDisplay.razor`: New component for login/logout UI -- `Components/Pages/Login.razor`: Login page -- `Components/Pages/Logout.razor`: Logout page +After that, run NoteBookmark using [`docs/docker-compose-deployment.md`](docker-compose-deployment.md). diff --git a/z_temp/keycloak-compose.yaml b/z_temp/keycloak-compose.yaml deleted file mode 100644 index 599afe1..0000000 --- a/z_temp/keycloak-compose.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: keycloak-dlvhome - -services: - keycloak: - container_name: keycloak - image: quay.io/keycloak/keycloak:26.5.4 - restart: always - # ports: - # - "8443:8443" - # - "8280:8080" - volumes: - - "./data/certs:/etc/x509/https:ro" - environment: - KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_USER} - KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_PASSWORD} - KC_HOSTNAME: ${KEYCLOAK_URL} - - KC_PROXY_ADDRESS_FORWARDING: "true" - KC_DB_URL: jdbc:postgresql://keycloak_postgres:5432/keycloak - KC_DB: postgres - KC_DB_USERNAME: ${POSTGRES_USER} - KC_DB_PASSWORD: ${POSTGRES_PASSWORD} - KC_HTTPS_CERTIFICATE_FILE: /etc/x509/https/dlvhome.crt - KC_HTTPS_CERTIFICATE_KEY_FILE: /etc/x509/https/dlvhome.key - KC_HTTP_ENABLED: "true" - KC_LOG_LEVEL: debug - KC_FEATURES: "token-exchange" - - - labels: - - traefik.enable=true - - traefik.http.routers.keycloak-http.rule=Host(`keycloak.c5m.ca`) - - traefik.http.routers.keycloak-http.entrypoints=web - - depends_on: - - keycloak_postgres - networks: - - proxy - command: - - 'start' - - keycloak_postgres: - container_name: keycloak_postgres - image: postgres:14.18 - restart: always - environment: - POSTGRES_DB: keycloak - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_PORT: 5432 - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - proxy - - -networks: - proxy: - external: true - # keycloak-network: - # name: keycloak-network - # driver: bridge - -volumes: - postgres_data: diff --git a/z_temp/note-compose.yml b/z_temp/note-compose.yml deleted file mode 100644 index a1524f5..0000000 --- a/z_temp/note-compose.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: 2note-bookmark - -services: - api: - image: "fboucher/notebookmark-api:alpha-latest" - container_name: "2notebookmark-api" - environment: - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" - ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" - HTTP_PORTS: "8000" - ConnectionStrings__nb-tables: "${NB_STORAGE_OUTPUTS_TABLEENDPOINT}" - ConnectionStrings__nb-blobs: "${NB_STORAGE_OUTPUTS_BLOBENDPOINT}" - ports: - - "8001:8000" - - "8003:8002" - restart: unless-stopped - networks: - - "proxy" - - - blazor-app: - image: "fboucher/notebookmark-blazor:alpha-latest" - container_name: "2notebookmark-blazor" - environment: - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" - OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" - ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" - HTTP_PORTS: "8004" - services__api__http__0: "http://api:8000" - REKA_API_KEY: "${REKA_API_KEY}" - ConnectionStrings__nb-tables: "${NB_STORAGE_OUTPUTS_TABLEENDPOINT}" - ConnectionStrings__nb-blobs: "${NB_STORAGE_OUTPUTS_BLOBENDPOINT}" - services__keycloak__http__0: "http://keycloak:8080" - Keycloak__Authority: "${KEYCLOAK_AUTHORITY}" - Keycloak__ClientId: "${KEYCLOAK_CLIENT_ID}" - Keycloak__ClientSecret: "${KEYCLOAK_CLIENT_SECRET}" - volumes: - - ./dataprotection-keys:/root/.aspnet/DataProtection-Keys - labels: - - traefik.enable=true - - traefik.http.routers.notebookmark.rule=Host(`nb.c5m.ca`) - - traefik.http.routers.notebookmark.entrypoints=web - - traefik.http.routers.notebookmark.middlewares=nb-https-headers - - traefik.http.middlewares.nb-https-headers.headers.customrequestheaders.X-Forwarded-Proto=https - depends_on: - api: - condition: "service_started" - restart: unless-stopped - networks: - - "proxy" - -networks: - proxy: - external: true diff --git a/z_temp/notes.md b/z_temp/notes.md deleted file mode 100644 index 0f4de59..0000000 --- a/z_temp/notes.md +++ /dev/null @@ -1,12 +0,0 @@ - - -KEYCLOAK_AUTHORITY=https://keycloak.c5m.ca/realms/notebookmark - - - - -Valid redirect URIs: https://nb.c5m.ca/* - -Valid post logout redirect URIs: https://nb.c5m.ca/* - -Web origins: https://nb.c5m.ca \ No newline at end of file