modules/devops/Write-Docker.ps1
|
param( [string]$ProjectName, [string]$OrmMode, [string]$DbProvider, [string]$OutputPath, [string]$FrontendMode, [string[]]$Entities, [switch]$SkipFrontend, [bool]$Observability = $false, [string]$Platform = "Web" ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") $dockerRoot = Join-Path $OutputPath "$ProjectName-docker" if (-not $Global:DryRun -and -not (Test-Path $dockerRoot)) { New-Item -ItemType Directory -Path $dockerRoot -Force | Out-Null } function Write-File($f, $c) { Write-CoreFile (Join-Path $dockerRoot $f) $c } function T($t) { $t.Replace("{P}", $ProjectName) } $p = $ProjectName.ToLower() # --- Connection String Helper --- $dockerConnStr = switch ($DbProvider) { "Postgres" { "Host=db;Database=${ProjectName}Db;Username=postgres;Password=postgres" } default { "Server=db,1433;Database=${ProjectName}Db;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" } } # --- Database Service Definition --- $dbService = "" if ($DbProvider -eq "MSSQL") { $dbBuildOrImage = if ($OrmMode -eq "Dapper") { " build:`n context: ./sqlserver" } else { " image: mcr.microsoft.com/mssql/server:2022-latest" } $dbService = @" db: container_name: ${p}-db $dbBuildOrImage environment: SA_PASSWORD: "YourStrong@Passw0rd" ACCEPT_EULA: "Y" ports: - "1433:1433" healthcheck: test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'YourStrong@Passw0rd' -C -Q 'SELECT 1' || exit 1"] interval: 5s timeout: 5s retries: 5 volumes: - ${p}_data:/var/opt/mssql "@ } elseif ($DbProvider -eq "Postgres") { $dbService = @" db: container_name: ${p}-db image: postgres:16-alpine environment: POSTGRES_DB: ${ProjectName}Db POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d ${ProjectName}Db"] interval: 5s timeout: 5s retries: 5 volumes: - ${p}_data:/var/lib/postgresql/data - ./postgresql/init.sql:/docker-entrypoint-initdb.d/init.sql "@ } # --- docker-compose.yml --- $dockerComposeContent = @" services: $dbService api: container_name: ${p}-api build: context: ../$ProjectName-backend dockerfile: src/$ProjectName.WebApi/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Production - ConnectionStrings__DefaultConnection=$dockerConnStr - Jwt__Key=your-super-secret-key-min-32-characters!! - Jwt__Issuer=${ProjectName}Api - Jwt__Audience=${ProjectName}Client ports: - "8080:8080" healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"] interval: 15s timeout: 10s retries: 5 start_period: 30s depends_on: db: condition: service_healthy "@ if (-not $SkipFrontend -and $Platform -match "Web|Both") { $uiExternalPort = 3000 $uiInternalPort = 80 $uiContext = "../$ProjectName-web" $dockerComposeContent += @" ui: container_name: ${p}-ui build: context: $uiContext dockerfile: Dockerfile env_file: - $uiContext/.env.docker ports: - "${uiExternalPort}:${uiInternalPort}" depends_on: api: condition: service_healthy "@ } $dockerComposeContent += "`n`nvolumes:`n ${p}_data:" Write-File "docker-compose.yml" $dockerComposeContent # --- docker-compose.override.yml (Seq for Observability) --- if ($Observability) { $override = @" services: seq: image: datalust/seq:latest environment: ACCEPT_EULA: Y ports: - "5341:80" - "5342:5341" api: environment: - Serilog__WriteTo__0__Args__serverUrl=http://seq:5341 "@ Write-File "docker-compose.override.yml" $override } # --- SQL Scripts --- # EFCore/Hybrid: EF Core Migrations & SeedData handle table creation and seed data. init.sql only creates the DB. # Dapper: No EF migrations, so init.sql creates all tables and seeds data. $bcryptHash = '$2a$12$4V.u4DaYV9N7BPMkbHBtuO18JGY.AQWcGGb4c39EDBxZz9eQXlZRK' if ($OrmMode -eq "Dapper") { $tables = "" foreach ($e in $Entities) { $pE = Get-Plural $e $extraSql = if ($e -eq "Product") { ", Price DECIMAL(18,2) DEFAULT 0" } else { "" } if ($DbProvider -eq "Postgres") { if ($e -eq "User") { $tables += "CREATE TABLE IF NOT EXISTS `"Users`" (`"Id`" SERIAL PRIMARY KEY, `"Username`" VARCHAR(100) NOT NULL UNIQUE, `"PasswordHash`" VARCHAR(256) NOT NULL, `"Email`" VARCHAR(256), `"Role`" VARCHAR(50) DEFAULT 'User' NOT NULL, `"IsActive`" BOOLEAN DEFAULT TRUE, `"IsDeleted`" BOOLEAN DEFAULT FALSE, `"DeletedAt`" TIMESTAMP, `"CreatedAt`" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `"UpdatedAt`" TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`n" } else { $tables += "CREATE TABLE IF NOT EXISTS `"$pE`" (`"Id`" SERIAL PRIMARY KEY, `"Name`" VARCHAR(200) NOT NULL$extraSql, `"IsDeleted`" BOOLEAN DEFAULT FALSE, `"DeletedAt`" TIMESTAMP, `"CreatedAt`" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `"UpdatedAt`" TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`n" } } else { if ($e -eq "User") { $tables += "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='Users' AND xtype='U') CREATE TABLE [{P}Db].[dbo].[Users] (Id INT IDENTITY(1,1) PRIMARY KEY, Username NVARCHAR(100) NOT NULL UNIQUE, PasswordHash NVARCHAR(256) NOT NULL, Email NVARCHAR(256), Role NVARCHAR(50) DEFAULT 'User' NOT NULL, IsActive BIT DEFAULT 1, IsDeleted BIT DEFAULT 0, DeletedAt DATETIME2, CreatedAt DATETIME2 DEFAULT GETUTCDATE(), UpdatedAt DATETIME2 DEFAULT GETUTCDATE());`n" } else { $tables += "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='$pE' AND xtype='U') CREATE TABLE [{P}Db].[dbo].$pE (Id INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(200) NOT NULL$extraSql, IsDeleted BIT DEFAULT 0, DeletedAt DATETIME2, CreatedAt DATETIME2 DEFAULT GETUTCDATE(), UpdatedAt DATETIME2 DEFAULT GETUTCDATE());`n" } } } if ($DbProvider -eq "MSSQL") { $seedSql = (T $tables) + (T @' -- Default Password: Admin123! IF NOT EXISTS (SELECT 1 FROM [{P}Db].[dbo].[Users] WHERE Username='admin') INSERT INTO [{P}Db].[dbo].[Users] (Username, PasswordHash, Email, Role) VALUES ('admin', '$2a$12$4V.u4DaYV9N7BPMkbHBtuO18JGY.AQWcGGb4c39EDBxZz9eQXlZRK', 'admin@example.com', 'Admin'); '@) $initSql = (T @' IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'{P}Db') CREATE DATABASE [{P}Db]; '@) + "`n" + $seedSql } else { $initSql = $tables + "`n-- Default Password: Admin123!`nINSERT INTO `"Users`" (`"Username`", `"PasswordHash`", `"Email`", `"Role`") VALUES ('admin', '$bcryptHash', 'admin@example.com', 'Admin') ON CONFLICT (`"Username`") DO NOTHING;" } } else { # EFCore & Hybrid modes: database creation only (tables created by EF Core Migrations) if ($DbProvider -eq "MSSQL") { $initSql = (T @' IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'{P}Db') CREATE DATABASE [{P}Db]; '@) } else { $initSql = (T "-- Database {P}Db is automatically created by POSTGRES_DB environment variable.`n-- EF Core Migrations and SeedData manage database schema and initial data.`n") } } # --- DB-Specific Files --- if ($DbProvider -eq "MSSQL") { if (-not $Global:DryRun) { New-Item -ItemType Directory -Path (Join-Path $dockerRoot "sqlserver") -Force | Out-Null } Write-CoreFile (Join-Path $dockerRoot "sqlserver\Dockerfile") "FROM mcr.microsoft.com/mssql/server:2022-latest`nENV ACCEPT_EULA=Y`nENV SA_PASSWORD=`"YourStrong@Passw0rd`"`nCOPY init.sql /init.sql`nCOPY --chmod=755 entrypoint.sh /entrypoint.sh`nENTRYPOINT [`"/entrypoint.sh`"]" Write-CoreFile (Join-Path $dockerRoot "sqlserver\entrypoint.sh") (T "#!/bin/bash`n/opt/mssql/bin/sqlservr &`nsleep 30`n/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P `"YourStrong@Passw0rd`" -i /init.sql -No`nwait") Write-CoreFile (Join-Path $dockerRoot "sqlserver\init.sql") $initSql } elseif ($DbProvider -eq "Postgres") { if (-not $Global:DryRun) { New-Item -ItemType Directory -Path (Join-Path $dockerRoot "postgresql") -Force | Out-Null } Write-CoreFile (Join-Path $dockerRoot "postgresql\init.sql") $initSql } # --- Write .gitignore at the main OutputPath --- Write-CoreFile (Join-Path $OutputPath ".gitignore") "bin/`nobj/`n*.user`n.vs/`n*.log`n.data/`n*.db`n.env`nnode_modules/`ndist/`n.expo/" |