Last active
August 12, 2026 11:11
-
-
Save whileloop99/812d6c980c8cc40166273e28316fbcba to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #Requires -Version 5.1 | |
| <# | |
| .SYNOPSIS | |
| Công cụ thiết lập chứng chỉ SSL local (dựa trên mkcert) | |
| .DESCRIPTION | |
| Cài đặt Chocolatey + mkcert, tạo chứng chỉ SSL cho tên miền local, | |
| và sinh hướng dẫn tích hợp cho nhiều loại web server / framework. | |
| #> | |
| # ============================================================ | |
| # THIẾT LẬP ENCODING (để hiển thị tiếng Việt có dấu đúng) | |
| # ============================================================ | |
| $OutputEncoding = [System.Text.Encoding]::UTF8 | |
| [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 | |
| try { chcp 65001 > $null } catch {} | |
| # ============================================================ | |
| # UI HELPERS | |
| # ============================================================ | |
| function Write-Banner { | |
| $lines = @( | |
| "╔══════════════════════════════════════════════════════════╗", | |
| "║ ║", | |
| "║ THIẾT LẬP CHỨNG CHỈ SSL LOCAL (nền tảng mkcert) ║", | |
| "║ ║", | |
| "╚══════════════════════════════════════════════════════════╝" | |
| ) | |
| Write-Host "" | |
| foreach ($l in $lines) { Write-Host $l -ForegroundColor Cyan } | |
| Write-Host "" | |
| } | |
| function Write-Section { | |
| param([string]$Title, [int]$Step, [int]$Total) | |
| Write-Host "" | |
| $tag = if ($Step -gt 0) { "[$Step/$Total] " } else { "" } | |
| $bar = "─" * ([Math]::Max(4, 60 - $tag.Length - $Title.Length)) | |
| Write-Host "$tag" -NoNewline -ForegroundColor DarkGray | |
| Write-Host "$Title " -NoNewline -ForegroundColor White | |
| Write-Host $bar -ForegroundColor DarkGray | |
| } | |
| function Write-Ok { param([string]$Msg) Write-Host " ✔ $Msg" -ForegroundColor Green } | |
| function Write-Warn { param([string]$Msg) Write-Host " ⚠ $Msg" -ForegroundColor Yellow } | |
| function Write-Err { param([string]$Msg) Write-Host " ✖ $Msg" -ForegroundColor Red } | |
| function Write-Info { param([string]$Msg) Write-Host " → $Msg" -ForegroundColor Gray } | |
| function Write-KeyVal { | |
| param([string]$Key, [string]$Val) | |
| Write-Host (" {0,-16}" -f $Key) -NoNewline -ForegroundColor DarkGray | |
| Write-Host $Val -ForegroundColor White | |
| } | |
| function Write-Box { | |
| param([string[]]$Lines, [string]$Color = "Green") | |
| $width = ($Lines | Measure-Object -Property Length -Maximum).Maximum + 4 | |
| Write-Host ("┌" + ("─" * $width) + "┐") -ForegroundColor $Color | |
| foreach ($l in $Lines) { | |
| Write-Host ("│ " + $l.PadRight($width - 2) + "│") -ForegroundColor $Color | |
| } | |
| Write-Host ("└" + ("─" * $width) + "┘") -ForegroundColor $Color | |
| } | |
| # ============================================================ | |
| # 0. KIỂM TRA QUYỀN ADMIN | |
| # ============================================================ | |
| Clear-Host | |
| Write-Banner | |
| $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| if (-not $isAdmin) { | |
| Write-Err "Script này cần quyền Administrator để cài đặt Chocolatey / mkcert và sửa trust store." | |
| Write-Info "Hãy mở lại PowerShell bằng 'Run as administrator' rồi thử lại." | |
| Write-Host "" | |
| exit 1 | |
| } | |
| Write-Ok "Đang chạy với quyền Administrator" | |
| $totalSteps = 5 | |
| # ============================================================ | |
| # 1. CHOCOLATEY | |
| # ============================================================ | |
| Write-Section -Title "Kiểm tra / cài đặt Chocolatey" -Step 1 -Total $totalSteps | |
| Set-ExecutionPolicy Bypass -Scope Process -Force | |
| [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 | |
| if (Get-Command choco -ErrorAction SilentlyContinue) { | |
| Write-Ok "Chocolatey đã có sẵn" | |
| } else { | |
| Write-Info "Đang tải và cài đặt Chocolatey..." | |
| try { | |
| iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) | Out-Null | |
| Write-Ok "Cài đặt Chocolatey thành công" | |
| } catch { | |
| Write-Err "Cài đặt Chocolatey thất bại: $($_.Exception.Message)" | |
| exit 1 | |
| } | |
| } | |
| $chocoBin = "$env:ProgramData\chocolatey\bin" | |
| if ($env:Path -notlike "*$chocoBin*") { $env:Path += ";$chocoBin" } | |
| # ============================================================ | |
| # 2. MKCERT | |
| # ============================================================ | |
| Write-Section -Title "Kiểm tra / cài đặt mkcert" -Step 2 -Total $totalSteps | |
| if (Test-Path "$chocoBin\mkcert.exe") { | |
| Write-Ok "mkcert đã có sẵn" | |
| } else { | |
| Write-Info "Đang cài đặt mkcert qua Chocolatey..." | |
| & "$chocoBin\choco.exe" install mkcert -y | Out-Null | |
| if (Test-Path "$chocoBin\mkcert.exe") { | |
| Write-Ok "Cài đặt mkcert thành công" | |
| } else { | |
| Write-Err "Không tìm thấy mkcert sau khi cài. Kiểm tra lại kết nối mạng." | |
| exit 1 | |
| } | |
| } | |
| Write-Info "Đang đăng ký Local CA vào trust store..." | |
| & "$chocoBin\mkcert.exe" -install | Out-Null | |
| Write-Ok "Local CA đã được tin cậy bởi hệ thống / trình duyệt" | |
| # ============================================================ | |
| # 3. NHẬP TÊN MIỀN + FILE HOSTS | |
| # ============================================================ | |
| Write-Section -Title "Cấu hình tên miền" -Step 3 -Total $totalSteps | |
| Write-Host " Nhập tên miền local bạn muốn dùng" -ForegroundColor White | |
| Write-Host " (ví dụ: abc.local, myapp.test — để trống = dùng localhost)" -ForegroundColor DarkGray | |
| $domain = Read-Host " ›" | |
| if ([string]::IsNullOrWhiteSpace($domain)) { $domain = "localhost" } | |
| if ($domain -ne "localhost") { | |
| $hostsPath = "$env:SystemRoot\System32\drivers\etc\hosts" | |
| $hostsContent = Get-Content $hostsPath -Raw | |
| if ($hostsContent -notmatch [regex]::Escape($domain)) { | |
| Add-Content -Path $hostsPath -Value "`n127.0.0.1`t$domain" | |
| Write-Ok "Đã thêm '$domain' vào file hosts (trỏ về 127.0.0.1)" | |
| } else { | |
| Write-Warn "'$domain' đã có sẵn trong file hosts, bỏ qua" | |
| } | |
| } else { | |
| Write-Info "Dùng 'localhost', không cần sửa file hosts" | |
| } | |
| # ============================================================ | |
| # 4. TẠO CHỨNG CHỈ | |
| # ============================================================ | |
| Write-Section -Title "Tạo chứng chỉ SSL" -Step 4 -Total $totalSteps | |
| $certDir = "$env:USERPROFILE\mkcert-certs" | |
| New-Item -ItemType Directory -Path $certDir -Force | Out-Null | |
| Set-Location $certDir | |
| Write-Info "Đang tạo chứng chỉ cho: $domain, localhost, 127.0.0.1, ::1" | |
| & "$chocoBin\mkcert.exe" $domain localhost 127.0.0.1 "::1" | Out-Null | |
| $certFiles = Get-ChildItem -Path $certDir -Filter "*.pem" | Sort-Object LastWriteTime -Descending | |
| $keyFile = ($certFiles | Where-Object { $_.Name -like "*-key.pem" } | Select-Object -First 1).Name | |
| $crtFile = ($certFiles | Where-Object { $_.Name -notlike "*-key.pem" } | Select-Object -First 1).Name | |
| if (-not $keyFile -or -not $crtFile) { | |
| Write-Err "Không tạo được chứng chỉ. Kiểm tra lại lỗi bên trên." | |
| exit 1 | |
| } | |
| Write-Ok "Chứng chỉ đã được tạo thành công" | |
| Write-Host "" | |
| Write-KeyVal "Thư mục:" $certDir | |
| Write-KeyVal "Cert:" $crtFile | |
| Write-KeyVal "Key:" $keyFile | |
| # ============================================================ | |
| # 5. MENU HƯỚNG DẪN TÍCH HỢP | |
| # ============================================================ | |
| Write-Section -Title "Chọn nơi tích hợp" -Step 5 -Total $totalSteps | |
| $menu = @( | |
| @{ id = "1"; label = "Vite" } | |
| @{ id = "2"; label = "Webpack Dev Server" } | |
| @{ id = "3"; label = "Next.js" } | |
| @{ id = "4"; label = "Node.js / Express" } | |
| @{ id = "5"; label = "Nginx" } | |
| @{ id = "6"; label = "Apache (httpd)" } | |
| @{ id = "7"; label = "IIS (Windows)" } | |
| @{ id = "8"; label = "Caddy" } | |
| @{ id = "9"; label = ".NET / Kestrel" } | |
| @{ id = "10"; label = "PHP built-in server" } | |
| @{ id = "11"; label = "Docker (mount cert vào container)" } | |
| ) | |
| $colWidth = 32 | |
| Write-Host "" | |
| for ($i = 0; $i -lt $menu.Count; $i += 2) { | |
| $left = " [{0,2}] {1}" -f $menu[$i].id, $menu[$i].label | |
| $line = $left.PadRight($colWidth + 6) | |
| if ($i + 1 -lt $menu.Count) { | |
| $right = "[{0,2}] {1}" -f $menu[$i+1].id, $menu[$i+1].label | |
| $line += $right | |
| } | |
| Write-Host $line -ForegroundColor White | |
| } | |
| Write-Host "" | |
| $choice = Read-Host " Nhập số (1-11)" | |
| $fp = ($certDir -replace '\\','/') | |
| $slashKey = "$certDir\$keyFile" -replace '\\','/' | |
| $slashCrt = "$certDir\$crtFile" -replace '\\','/' | |
| function Show-Snippet { | |
| param([string]$Title, [string]$Code, [string[]]$Notes = @()) | |
| Write-Host "" | |
| Write-Host " ── $Title " -ForegroundColor Cyan -NoNewline | |
| Write-Host ("─" * [Math]::Max(4, 50 - $Title.Length)) -ForegroundColor DarkGray | |
| Write-Host "" | |
| $Code -split "`n" | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } | |
| foreach ($n in $Notes) { Write-Host ""; Write-Warn $n } | |
| } | |
| switch ($choice) { | |
| "1" { Show-Snippet "VITE (vite.config.js/ts)" @" | |
| import fs from 'fs' | |
| export default { | |
| server: { | |
| https: { | |
| key: fs.readFileSync('$fp/$keyFile'), | |
| cert: fs.readFileSync('$fp/$crtFile'), | |
| }, | |
| host: '$domain', | |
| port: 5173, | |
| }, | |
| } | |
| "@ } | |
| "2" { Show-Snippet "WEBPACK DEV SERVER (webpack.config.js)" @" | |
| const fs = require('fs') | |
| module.exports = { | |
| devServer: { | |
| server: { | |
| type: 'https', | |
| options: { | |
| key: fs.readFileSync('$fp/$keyFile'), | |
| cert: fs.readFileSync('$fp/$crtFile'), | |
| }, | |
| }, | |
| host: '$domain', | |
| port: 8080, | |
| }, | |
| } | |
| "@ } | |
| "3" { Show-Snippet "NEXT.JS (server.js tùy chỉnh)" @" | |
| const { createServer } = require('https') | |
| const { parse } = require('url') | |
| const next = require('next') | |
| const fs = require('fs') | |
| const httpsOptions = { | |
| key: fs.readFileSync('$fp/$keyFile'), | |
| cert: fs.readFileSync('$fp/$crtFile'), | |
| } | |
| const app = next({ dev: true }) | |
| const handle = app.getRequestHandler() | |
| app.prepare().then(() => { | |
| createServer(httpsOptions, (req, res) => { | |
| handle(req, res, parse(req.url, true)) | |
| }).listen(3000, () => console.log('https://$domain:3000')) | |
| }) | |
| "@ -Notes @("next dev không hỗ trợ https trực tiếp, phải chạy: node server.js") | |
| } | |
| "4" { Show-Snippet "NODE.JS / EXPRESS" @" | |
| const https = require('https') | |
| const fs = require('fs') | |
| const express = require('express') | |
| const app = express() | |
| const options = { | |
| key: fs.readFileSync('$fp/$keyFile'), | |
| cert: fs.readFileSync('$fp/$crtFile'), | |
| } | |
| https.createServer(options, app).listen(443, () => { | |
| console.log('https://$domain') | |
| }) | |
| "@ } | |
| "5" { Show-Snippet "NGINX (nginx.conf)" @" | |
| server { | |
| listen 443 ssl; | |
| server_name $domain; | |
| ssl_certificate $slashCrt; | |
| ssl_certificate_key $slashKey; | |
| location / { | |
| proxy_pass http://127.0.0.1:3000; | |
| proxy_set_header Host `$host; | |
| } | |
| } | |
| "@ -Notes @("Sau khi sửa config: nginx -s reload") | |
| } | |
| "6" { Show-Snippet "APACHE (httpd-ssl.conf)" @" | |
| <VirtualHost *:443> | |
| ServerName $domain | |
| SSLEngine on | |
| SSLCertificateFile "$slashCrt" | |
| SSLCertificateKeyFile "$slashKey" | |
| DocumentRoot "C:/path/to/your/project" | |
| </VirtualHost> | |
| "@ -Notes @("Nhớ bật module: LoadModule ssl_module modules/mod_ssl.so") | |
| } | |
| "7" { | |
| Write-Host "" | |
| Write-Host " ── IIS (Windows) " -ForegroundColor Cyan -NoNewline | |
| Write-Host ("─" * 34) -ForegroundColor DarkGray | |
| Write-Host "" | |
| Write-Host " 1. Mở mmc.exe -> Add Snap-in -> Certificates -> Local Computer -> Personal" -ForegroundColor Gray | |
| Write-Host " 2. Import file $crtFile và $keyFile (convert sang .pfx trước):" -ForegroundColor Gray | |
| Write-Host " openssl pkcs12 -export -out $domain.pfx -inkey $keyFile -in $crtFile" -ForegroundColor Yellow | |
| Write-Host " 3. IIS Manager -> chọn site -> Bindings -> Add -> type: https -> chọn cert vừa import" -ForegroundColor Gray | |
| Write-Host " 4. Set Host name = $domain" -ForegroundColor Gray | |
| } | |
| "8" { Show-Snippet "CADDY (Caddyfile)" @" | |
| $domain { | |
| tls $slashCrt $slashKey | |
| reverse_proxy 127.0.0.1:3000 | |
| } | |
| "@ -Notes @("Chạy: caddy run") | |
| } | |
| "9" { Show-Snippet ".NET / KESTREL (Program.cs)" @" | |
| builder.WebHost.ConfigureKestrel(options => | |
| { | |
| options.ListenAnyIP(5001, listenOptions => | |
| { | |
| listenOptions.UseHttps("$fp/$crtFile", null, | |
| httpsOptions => { }); | |
| }); | |
| }); | |
| "@ -Notes @(".NET cần file .pfx: openssl pkcs12 -export -out $domain.pfx -inkey $keyFile -in $crtFile") | |
| } | |
| "10" { | |
| Write-Host "" | |
| Write-Host " ── PHP BUILT-IN SERVER " -ForegroundColor Cyan -NoNewline | |
| Write-Host ("─" * 28) -ForegroundColor DarkGray | |
| Write-Warn "PHP built-in server không hỗ trợ https trực tiếp." | |
| Write-Info "Dùng Nginx/Caddy làm reverse proxy https -> http://127.0.0.1:8000" | |
| } | |
| "11" { Show-Snippet "DOCKER (docker-compose.yml)" @" | |
| services: | |
| web: | |
| volumes: | |
| - "$($fp):/certs:ro" | |
| environment: | |
| - SSL_CERT=/certs/$crtFile | |
| - SSL_KEY=/certs/$keyFile | |
| ports: | |
| - "443:443" | |
| "@ -Notes @("Bên trong container, trỏ app tới /certs/$crtFile và /certs/$keyFile") | |
| } | |
| default { Write-Err "Lựa chọn không hợp lệ. Chứng chỉ vẫn sẵn sàng tại: $certDir" } | |
| } | |
| # ============================================================ | |
| # HOÀN TẤT | |
| # ============================================================ | |
| Write-Host "" | |
| Write-Box -Lines @( | |
| "HOÀN TẤT!", | |
| "", | |
| "URL: https://$domain", | |
| "Cert: $certDir\$crtFile", | |
| "Key: $certDir\$keyFile" | |
| ) -Color Green | |
| Write-Host "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment