diff --git a/.github/README.md b/.github/README.md
index 2d58e45e39..320018a73c 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -33,7 +33,6 @@ Typical adblockers run as an extension in popular web browsers. As we browse the
| ------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Website | A React & TypeScript UI built with Ant Design. | [![Website](https://img.shields.io/website-up-down-green-red/http/shields.io.svg?label=Website)](https://filterlists.com/) [![Website Azure DevOps builds](https://dev.azure.com/collinbarrett/FilterLists/_apis/build/status/Web?branchName=main)](https://dev.azure.com/collinbarrett/FilterLists/_build/latest?definitionId=18) [![Website Azure DevOps releases](https://vsrm.dev.azure.com/collinbarrett/_apis/public/Release/badge/b06a3d5c-459e-4789-9735-0f5969006fe8/4/5)](https://dev.azure.com/collinbarrett/FilterLists/_release?definitionId=4) [![Website Docker Image](https://img.shields.io/badge/docker%20image-web-blue?label=Docker%20Image)](https://github.com/users/collinbarrett/packages/container/package/filterlists-web) [![Website Security Headers](https://img.shields.io/security-headers?url=https%3A%2F%2Ffilterlists.com)](https://securityheaders.com/?q=https%3A%2F%2Ffilterlists.com) |
| Directory API | An ASP.NET Core API serving the core FilterList information. | [![Directory API Swagger UI](https://img.shields.io/website-up-down-green-red/http/shields.io.svg?label=API%20Docs)](https://filterlists.com/api/?urls.primaryName=Directory) [![Directory API OpenAPI Specification](https://img.shields.io/swagger/valid/3.0?specUrl=https%3A%2F%2Ffilterlists.com%2Fapi%2Fdirectory%2Fv1%2Fswagger.json)](https://filterlists.com/api/directory/v1/swagger.json) [![Directory API Azure DevOps builds](https://dev.azure.com/collinbarrett/FilterLists/_apis/build/status/Directory%20API?branchName=main)](https://dev.azure.com/collinbarrett/FilterLists/_build/latest?definitionId=27) [![Directory API Azure DevOps releases](https://vsrm.dev.azure.com/collinbarrett/_apis/public/Release/badge/b06a3d5c-459e-4789-9735-0f5969006fe8/3/4)](https://dev.azure.com/collinbarrett/FilterLists/_release?definitionId=3) [![Directory API Docker Image](https://img.shields.io/badge/docker%20image-directory--api-blue?label=Docker%20Image)](https://github.com/users/collinbarrett/packages/container/package/filterlists-directory-api) [![Directory API Security Headers](https://img.shields.io/security-headers?url=https%3A%2F%2Ffilterlists.com%2Fapi%2Fdirectory%2Fv1%2Fswagger.json)](https://securityheaders.com/?q=https%3A%2F%2Ffilterlists.com%2Fapi%2Fdirectory%2Fv1%2Fswagger.json) |
-| Reverse Proxy | An NGINX instance forwarding requests to the respective services above. | [![Reverse Proxy Azure DevOps builds](https://dev.azure.com/collinbarrett/FilterLists/_apis/build/status/Reverse%20Proxy?branchName=main)](https://dev.azure.com/collinbarrett/FilterLists/_build/latest?definitionId=21) [![Reverse Proxy Azure DevOps releases](https://vsrm.dev.azure.com/collinbarrett/_apis/public/Release/badge/b06a3d5c-459e-4789-9735-0f5969006fe8/5/6)](https://dev.azure.com/collinbarrett/FilterLists/_release?definitionId=5) [![Reverse Proxy Mozilla HTTP Observatory Grade](https://img.shields.io/mozilla-observatory/grade/filterlists.com?publish)](https://observatory.mozilla.org/analyze/filterlists.com) [![Reverse Proxy Chromium HSTS preload](https://img.shields.io/hsts/preload/filterlists.com)](https://hstspreload.org/?domain=filterlists.com) |
# Contributing
@@ -53,18 +52,79 @@ FilterLists does not maintain any of these lists. It serves only as a discovery
## Building and Running Locally
-We have containerized FilterLists to make it as easy as possible for contributers to get the project up and running locally.
-
-1. Install Docker CE. [Docs](https://docs.docker.com/install/)
-2. Install the current version of Node.js. [Docs](https://nodejs.org/en/download/current/)
-3. Clone the FilterLists git repository to your computer. [Docs](https://help.github.com/en/articles/cloning-a-repository)
-4. Navigate to the root directory of your locally cloned FilterLists git repository in a command-line interface.
-5. Start the APIs:
- `docker-compose -f docker-compose/docker-compose.yml -f docker-compose/docker-compose.override.yml up -d`
- You can then view the API docs and execute API calls here: http://localhost:8080/api/
-6. Start the Web app:
- `npm i --cwd web && npm start --prefix web`
- You can then view the Web app calling your local instance of the Directory API here: http://localhost:3000
+FilterLists is build on the .NET Aspire stack. Install the [.NET Aspire prerequisites](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/setup-tooling) before proceeding.
+
+### Directory API
+
+#### Configure Azure Resources
+
+Local debugging depends on an Azure Application Insights resource. Either [configure a connection to your Azure subscription](https://learn.microsoft.com/en-us/dotnet/aspire/deployment/azure/local-provisioning#configuration) or comment out the `appInsights` resource in `services/FilterLists.AppHost/Program.cs`.
+
+#### Prepare Database Volume
+
+So that the database does not need to be re-seeded on every startup, the SQL Server container is configured with a volume mount. Create a persistent password in order for this to be accessed by executing the command below in `services/FilterLists.AppHost` replacing `` with a custom password:
+
+```bash
+dotnet user-secrets set Parameters:directorysqlserver-password
+```
+
+Alternatively, remove the `.WithDataVolume()` configuration on the `directoryDb` resource in `services/FilterLists.AppHost/Program.cs` to re-seed the database on every startup.
+
+[MS Learn](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/persist-data-volumes)
+
+#### Adding EF Core Migrations
+
+Modify the database schema or seed data by adding an EF Core migration.
+
+1. Install the [EF Core tools](https://learn.microsoft.com/en-us/ef/core/cli/dotnet#installing-the-tools).
+2. Modify the `QueryDbContext` EF Core model or the seed data.
+3. Execute the command below in the `services/Directory` directory replacing `` with a meaningful name.
+
+```bash
+dotnet ef migrations add --project FilterLists.Directory.Infrastructure.Migrations/FilterLists.Directory.Infrastructure.Migrations.csproj --startup-project FilterLists.Directory.Infrastructure.MigrationService/FilterLists.Directory.Infrastructure.MigrationService.csproj
+```
+
+## Deploying to Production
+
+### Directory API
+
+#### Create and Prepare SQL Server Database
+
+Create an instance of SQL Server containing the users below.
+
+##### DirectoryMigrations user for applying EF Core migrations
+
+```sql
+USE [master];
+GO
+
+CREATE LOGIN [DirectoryMigrations] WITH PASSWORD = 'my_password';
+GO
+
+USE [directorydb];
+GO
+
+CREATE USER [DirectoryMigrations] FOR LOGIN [DirectoryMigrations];
+ALTER ROLE [db_ddladmin] ADD MEMBER [DirectoryMigrations]; -- to apply migrations
+ALTER ROLE [db_datareader] ADD MEMBER [DirectoryMigrations]; -- to read from __EFMigrationsHistory
+ALTER ROLE [db_datawriter] ADD MEMBER [DirectoryMigrations]; -- to insert to __EFMigrationsHistory
+```
+
+##### DirectoryApiReadonly for API runtime reads
+
+```sql
+USE [master];
+GO
+
+CREATE LOGIN [DirectoryApiReadonly] WITH PASSWORD = 'my_password';
+GO
+
+USE [directorydb];
+GO
+
+CREATE USER [DirectoryApiReadonly] FOR LOGIN [DirectoryApiReadonly];
+ALTER ROLE [db_datareader] ADD MEMBER [DirectoryApiReadonly];
+```
# Acknowledgements
diff --git a/services/Directory/AddMigration.md b/services/Directory/AddMigration.md
deleted file mode 100644
index aa3f7604f3..0000000000
--- a/services/Directory/AddMigration.md
+++ /dev/null
@@ -1 +0,0 @@
-dotnet ef migrations add InitialCreate --project FilterLists.Directory.Infrastructure.Migrations/FilterLists.Directory.Infrastructure.Migrations.csproj --startup-project FilterLists.Directory.Infrastructure.MigrationService/FilterLists.Directory.Infrastructure.MigrationService.csproj
\ No newline at end of file
diff --git a/services/Directory/CreateSqlUsers.md b/services/Directory/CreateSqlUsers.md
deleted file mode 100644
index 508a3112a4..0000000000
--- a/services/Directory/CreateSqlUsers.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# DirectoryMigrations user for applying EF Core migrations
-
-```sql
-USE [master];
-GO
-
-CREATE LOGIN DirectoryMigrations WITH PASSWORD = 'my_password';
-GO
-
-USE [directorydb];
-GO
-
-CREATE USER DirectoryMigrations FOR LOGIN DirectoryMigrations;
-ALTER ROLE db_ddladmin ADD MEMBER DirectoryMigrations; -- to apply migrations
-ALTER ROLE db_datareader ADD MEMBER DirectoryMigrations; -- to read from __EFMigrationsHistory
-ALTER ROLE db_datawriter ADD MEMBER DirectoryMigrations; -- to insert to __EFMigrationsHistory
-```
-
-# DirectoryApiReadonly for API runtime reads
-
-```sql
-USE [master];
-GO
-
-CREATE LOGIN DirectoryApiReadonly WITH PASSWORD = 'my_password';
-GO
-
-USE [directorydb];
-GO
-
-CREATE USER DirectoryApiReadonly FOR LOGIN DirectoryApiReadonly;
-ALTER ROLE db_datareader ADD MEMBER DirectoryApiReadonly; -- to read from __EFMigrationsHistory
-```
\ No newline at end of file
diff --git a/services/FilterLists.AppHost/Program.cs b/services/FilterLists.AppHost/Program.cs
index 5d0fc5c55a..aba16b0d66 100644
--- a/services/FilterLists.AppHost/Program.cs
+++ b/services/FilterLists.AppHost/Program.cs
@@ -6,7 +6,8 @@
// TODO: use separate users (db_ddladmin only for migrations) https://stackoverflow.com/q/78564037/2343739
var directoryDb = builder.AddSqlServer("directorysqlserver")
- .PublishAsConnectionString() // customized for free tier, don't trust Aspire to provision db
+ .PublishAsConnectionString() // customized Azure resource for free tier, don't yet trust Aspire to provision db
+ .WithDataVolume() // don't re-seed db on every startup locally, requires https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/persist-data-volumes#create-a-persistent-password
.AddDatabase("directorydb");
// TODO: migrate published db from run-once instance (Container Apps 'App" type restarts)