Skip to content

Instantly share code, notes, and snippets.

@whileloop99
Last active August 12, 2026 11:11
Show Gist options
  • Select an option

  • Save whileloop99/812d6c980c8cc40166273e28316fbcba to your computer and use it in GitHub Desktop.

Select an option

Save whileloop99/812d6c980c8cc40166273e28316fbcba to your computer and use it in GitHub Desktop.
#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