251118:1300 ปรับปรุง Project requirement

This commit is contained in:
admin
2025-11-18 13:01:23 +07:00
parent 693dd7f074
commit 7c1c5e3c99
20 changed files with 15426 additions and 13926 deletions

View File

@@ -1856,7 +1856,6 @@ CREATE INDEX idx_notifications_created_at ON notifications(created_at);
-- Indexes for search_indices
CREATE INDEX idx_search_indices_entity ON search_indices(entity_type, entity_id);
CREATE INDEX idx_search_indices_indexed_at ON search_indices(indexed_at);
CREATE FULLTEXT INDEX idx_search_indices_content ON search_indices(content);
-- Indexes for backup_logs
CREATE INDEX idx_backup_logs_type ON backup_logs(backup_type);
CREATE INDEX idx_backup_logs_status ON backup_logs(status);

483
Documnets/0.html Normal file
View File

@@ -0,0 +1,483 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LCBP3-DMS V1.4.1 Project Infographic</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f4f7f6;
}
.chart-container {
position: relative;
width: 100%;
max-width: 600px;
margin-left: auto;
margin-right: auto;
height: 350px;
max-height: 400px;
}
@media (max-width: 768px) {
.chart-container {
height: 300px;
max-height: 350px;
}
}
.stat-card {
background-color: #004AAD;
color: white;
border-left: 5px solid #FFC107;
}
.kpi-value {
font-size: 2.5rem;
line-height: 1.2;
}
.section-card {
background-color: white;
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
margin-bottom: 2rem;
overflow: hidden;
}
.section-header {
background-color: #002F6C;
color: white;
padding: 1.5rem;
}
.section-content {
padding: 1.5rem;
}
.flow-box {
border: 2px solid #007BFF;
background-color: #B3D7FF;
color: #002F6C;
padding: 0.75rem;
border-radius: 0.5rem;
text-align: center;
font-weight: 600;
box-shadow: 0 4px 6px rgba(0, 75, 173, 0.1);
}
.flow-arrow {
font-size: 2rem;
color: #004AAD;
align-self: center;
margin: 0 0.5rem;
}
.timeline-item {
position: relative;
padding-left: 2.5rem;
padding-bottom: 2rem;
border-left: 4px solid #007BFF;
}
.timeline-dot {
position: absolute;
left: -11px;
top: 0;
height: 20px;
width: 20px;
background-color: #FFC107;
border: 3px solid #004AAD;
border-radius: 50%;
}
.rbac-level {
border: 2px dashed;
padding: 1rem;
border-radius: 0.5rem;
margin-top: 1rem;
text-align: center;
}
</style>
</head>
<body class="antialiased">
<header class="bg-white shadow-md sticky top-0 z-50">
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 flex items-center">
<span class="text-2xl font-bold text-[#002F6C]">LCBP3-DMS</span>
<span class="ml-2 text-xl font-semibold text-[#007BFF]">V1.4.1 Project Overview</span>
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<a href="#kpi" class="text-gray-600 hover:text-[#007BFF] px-3 py-2 rounded-md text-sm font-medium">KPIs</a>
<a href="#architecture" class="text-gray-600 hover:text-[#007BFF] px-3 py-2 rounded-md text-sm font-medium">Architecture</a>
<a href="#roadmap" class="text-gray-600 hover:text-[#007BFF] px-3 py-2 rounded-md text-sm font-medium">Roadmap</a>
<a href="#security" class="text-gray-600 hover:text-[#007BFF] px-3 py-2 rounded-md text-sm font-medium">Security</a>
<a href="#stats" class="text-gray-600 hover:text-[#007BFF] px-3 py-2 rounded-md text-sm font-medium">Stats</a>
</div>
</div>
</div>
</nav>
</header>
<main class="max-w-7xl mx-auto p-4 md:p-8 mt-8">
<section id="intro" class="section-card">
<div class="section-header">
<h1 class="text-3xl font-bold">Project Overview: LCBP3 Document Management System (V1.4.1)</h1>
</div>
<div class="section-content">
<p class="text-lg text-gray-700">
นี่คือภาพรวมของระบบบริหารจัดการเอกสารโครงการ (DMS) V1.4.1
ที่กำลังพัฒนาสำหรับโครงการแหลมฉบังเฟส 3 (LCBP3)
เป้าหมายหลักคือการสร้างเว็บแอปพลิเคชั่นที่ทันสมัย ปลอดภัย
และมีประสิทธิภาพสูงเพื่อจัดการและควบคุมการสื่อสารด้วยเอกสารที่ซับซ้อน
ลดการใช้กระดาษ และเพิ่มความสะดวกในการทำงานร่วมกันระหว่างองค์กร
</p>
</div>
</section>
<section id="kpi" class="mb-8">
<h2 class="text-3xl font-bold text-gray-800 mb-6">Key Performance Indicators (KPIs)</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="stat-card p-6 rounded-lg shadow-lg">
<h3 class="text-lg font-semibold uppercase tracking-wider">API Response Time</h3>
<p class="kpi-value font-bold mt-2">&lt; 200<span class="text-xl">ms</span></p>
<p class="text-sm opacity-90">(90th Percentile)</p>
</div>
<div class="stat-card p-6 rounded-lg shadow-lg">
<h3 class="text-lg font-semibold uppercase tracking-wider">Search Performance</h3>
<p class="kpi-value font-bold mt-2">&lt; 500<span class="text-xl">ms</span></p>
<p class="text-sm opacity-90">(Elasticsearch)</p>
</div>
<div class="stat-card p-6 rounded-lg shadow-lg">
<h3 class="text-lg font-semibold uppercase tracking-wider">File Upload (50MB)</h3>
<p class="kpi-value font-bold mt-2">&lt; 30<span class="text-xl">s</span></p>
<p class="text-sm opacity-90">(Inc. Virus Scan)</p>
</div>
<div class="stat-card p-6 rounded-lg shadow-lg">
<h3 class="text-lg font-semibold uppercase tracking-wider">Cache Hit Ratio</h3>
<p class="kpi-value font-bold mt-2">&gt; 80<span class="text-xl">%</span></p>
<p class="text-sm opacity-90">(Redis)</p>
</div>
</div>
</section>
<section id="architecture" class="section-card">
<div class="section-header">
<h2 class="text-3xl font-bold">System Architecture & Technology Stack</h2>
</div>
<div class="section-content">
<p class="text-lg text-gray-700 mb-6">
สถาปัตยกรรมระบบเป็นแบบ API-First ที่ทำงานบน QNAP Container Station (Docker)
โดยมีการแบ่งส่วนบริการ (Services) อย่างชัดเจน
เพื่อความสะดวกในการจัดการและบำรุงรักษา
</p>
<div class="border-2 border-gray-200 rounded-lg p-4">
<div class="flex flex-col md:flex-row md:items-center justify-center space-y-4 md:space-y-0 md:space-x-4">
<div class="flow-box bg-[#FFC107] border-[#004AAD]">Internet User</div>
<div class="flow-arrow hidden md:block"></div>
<div class="flow-arrow md:hidden text-center"></div>
<div class="flow-box">QNAP (WAN)</div>
<div class="flow-arrow hidden md:block"></div>
<div class="flow-arrow md:hidden text-center"></div>
<div class="flow-box bg-green-100 border-green-500 text-green-800">Nginx Proxy Manager</div>
</div>
<div class="border-t-2 border-dashed border-gray-400 my-6"></div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-xl font-semibold text-center text-[#002F6C] mb-4">Public Facing Services (ผ่าน NPM)</h3>
<div class="space-y-3">
<div class="flow-box">Frontend (Next.js)</div>
<div class="flow-box">Backend (NestJS)</div>
<div class="flow-box">Gitea (Git)</div>
<div class="flow-box">n8n (Automation)</div>
</div>
</div>
<div>
<h3 class="text-xl font-semibold text-center text-[#002F6C] mb-4">Internal Services (Backend เรียกใช้)</h3>
<div class="space-y-3">
<div class="flow-box bg-gray-200 border-gray-400 text-gray-800">MariaDB (Database)</div>
<div class="flow-box bg-gray-200 border-gray-400 text-gray-800">Redis (Cache)</div>
<div class="flow-box bg-gray-200 border-gray-400 text-gray-800">Elasticsearch (Search)</div>
<div class="flow-box bg-gray-200 border-gray-400 text-gray-800">ClamAV (Virus Scan)</div>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<section id="roadmap" class="section-card">
<div class="section-header">
<h2 class="text-3xl font-bold">Development Roadmap</h2>
</div>
<div class="section-content">
<p class="text-lg text-gray-700 mb-6">
แผนการพัฒนาถูกแบ่งออกเป็น Phase (Backend) และ Sprints (Frontend)
เพื่อให้สามารถส่งมอบงานได้อย่างต่อเนื่องและเป็นระบบ
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-xl font-semibold text-[#002F6C] mb-4">Backend (NestJS)</h3>
<div class="relative">
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Phase 0-1: Setup & Core</h4>
<p class="text-sm text-gray-600">Infrastructure, DB Schema, ORM</p>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Phase 2-3: Auth & RBAC</h4>
<p class="text-sm text-gray-600">JWT, Passport, CASL 4-Level</p>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Phase 4-5: Core Features</h4>
<p class="text-sm text-gray-600">Document Upload, RFA Workflow</p>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Phase 6-8: Integration & Deploy</h4>
<p class="text-sm text-gray-600">Search, Cache, Notification, Deploy</p>
</div>
</div>
</div>
<div>
<h3 class="text-xl font-semibold text-[#002F6C] mb-4">Frontend (Next.js)</h3>
<div class="relative">
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Sprint 1-2: Setup & Auth</h4>
<p class="text-sm text-gray-600">shadcn/ui, NextAuth, Layout</p>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Sprint 3: Dashboard</h4>
<p class="text-sm text-gray-600">Charts (Recharts), KPIs</p>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Sprint 4-5: Document Module</h4>
<p class="text-sm text-gray-600">TanStack Table, Upload, Search</p>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<h4 class="font-bold">Sprint 6-7: Workflow & Deploy</h4>
<p class="text-sm text-gray-600">RFA Forms, Testing, Deploy</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="security" class="section-card">
<div class="section-header">
<h2 class="text-3xl font-bold">Feature Focus: 4-Level RBAC</h2>
</div>
<div class="section-content">
<p class="text-lg text-gray-700 mb-6">
ระบบควบคุมสิทธิ์ (RBAC) เป็นหัวใจสำคัญของความปลอดภัย
โดยใช้สถาปัตยกรรม 4 ระดับ (4-Level) เพื่อการควบคุมที่ละเอียดสูงสุด
</p>
<div class="rbac-level border-[#002F6C]">
<span class="font-bold text-lg text-[#002F6C]">🌍 Level 1: Global</span>
<p class="text-sm text-gray-600">(Super Admin, System Settings)</p>
<div class="rbac-level border-[#004AAD]">
<span class="font-bold text-lg text-[#004AAD]">🏢 Level 2: Organization</span>
<p class="text-sm text-gray-600">(Org Admin, Manage Users & Projects)</p>
<div class="rbac-level border-[#007BFF]">
<span class="font-bold text-lg text-[#007BFF]">🏗️ Level 3: Project</span>
<p class="text-sm text-gray-600">(Project Manager, View All Project Docs)</p>
<div class="rbac-level border-[#B3D7FF]">
<span class="font-bold text-lg text-[#002F6C]">📄 Level 4: Contract</span>
<p class="text-sm text-gray-600">(Contractor, Access Own Contract Docs Only)</p>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<section id="stats" class="section-card">
<div class="section-header">
<h2 class="text-3xl font-bold">Document Statistics (Mockup Data)</h2>
</div>
<div class="section-content">
<p class="text-lg text-gray-700 mb-6">
Dashboard จะแสดงสถิติเอกสารแบบ Real-time
(อ้างอิงจาก View: `v_document_statistics` ในฐานข้อมูล)
เพื่อช่วยในการติดตามและบริหารจัดการโครงการ
</p>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-semibold text-center text-[#002F6C] mb-4">เอกสารตามประเภท (By Type)</h3>
<p class="text-sm text-center text-gray-600 mb-4">
แสดงจำนวนเอกสารทั้งหมดโดยแบ่งตามประเภทหลัก
ช่วยให้เห็นภาพรวมของเอกสารในระบบ
</p>
<div class="chart-container">
<canvas id="docTypeChart"></canvas>
</div>
</div>
<div>
<h3 class="text-xl font-semibold text-center text-[#002F6C] mb-4">สถานะเอกสาร (By Status)</h3>
<p class="text-sm text-center text-gray-600 mb-4">
แสดงสัดส่วนของสถานะเอกสารในปัจจุบัน
(เช่น ร่าง, รออนุมัติ, อนุมัติแล้ว) เพื่อติดตาม Workflow
</p>
<div class="chart-container">
<canvas id="docStatusChart"></canvas>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="text-center p-6 text-gray-600 text-sm">
LCBP3-DMS V1.4.1 Infographic | Generated on: <span id="generation-date"></span>
</footer>
<script>
document.getElementById('generation-date').textContent = new Date().toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
const wrapLabel = (label) => {
const maxChars = 16;
if (typeof label === 'string' && label.length > maxChars) {
const words = label.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
if ((currentLine + word).length > maxChars) {
lines.push(currentLine.trim());
currentLine = '';
}
currentLine += word + ' ';
}
lines.push(currentLine.trim());
return lines;
}
return label;
};
const tooltipTitleCallback = (tooltipItems) => {
const item = tooltipItems[0];
let label = item.chart.data.labels[item.dataIndex];
if (Array.isArray(label)) {
return label.join(' ');
} else {
return label;
}
};
const sharedChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
title: tooltipTitleCallback
}
},
legend: {
labels: {
font: {
family: "'Inter', sans-serif"
}
}
}
},
scales: {
x: {
ticks: {
font: {
family: "'Inter', sans-serif"
}
}
},
y: {
ticks: {
font: {
family: "'Inter', sans-serif"
}
}
}
}
};
const docTypeCtx = document.getElementById('docTypeChart').getContext('2d');
const docTypeChart = new Chart(docTypeCtx, {
type: 'bar',
data: {
labels: [
'Correspondence (RFA)',
'Shop Drawing',
'Contract Drawing',
wrapLabel('Technical Specification'),
'Method Statement'
],
datasets: [{
label: 'จำนวนเอกสาร',
data: [120, 190, 75, 45, 88],
backgroundColor: [
'#004AAD',
'#007BFF',
'#B3D7FF',
'#002F6C',
'#FFC107'
],
borderColor: '#ffffff',
borderWidth: 1
}]
},
options: { ...sharedChartOptions }
});
const docStatusCtx = document.getElementById('docStatusChart').getContext('2d');
const docStatusChart = new Chart(docStatusCtx, {
type: 'doughnut',
data: {
labels: ['Draft', 'Pending Approval', 'Approved', 'Rejected'],
datasets: [{
label: 'สถานะเอกสาร',
data: [30, 45, 150, 15],
backgroundColor: [
'#B3D7FF',
'#FFC107',
'#007BFF',
'#D9363E'
],
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
title: tooltipTitleCallback
}
},
legend: {
position: 'bottom',
labels: {
font: {
family: "'Inter', sans-serif"
}
}
}
}
}
});
</script>
</body>
</html>

220
Documnets/Git_command.md Normal file
View File

@@ -0,0 +1,220 @@
# **คำสั่งตั้งค่า Gitea ใหม่ทั้งหมด + คำสั่งใช้งานประจำวัน / แก้ปัญหา / branch”**
---
📘 Git + Gitea (QNAP / Container Station) Cheat Sheet
คู่มือนี้รวบรวม:
- คำสั่งตั้งค่า Gitea ใหม่ทั้งหมด
- คำสั่งใช้งาน Git ประจำวัน
- การแก้ไขปัญหา repository
- การทำงานกับ branch
- การ reset / clone / merge / rebase
---
## 🧩 SECTION 1 การตั้งค่า Gitea ใหม่ทั้งหมด
🔹 1) เคลียร์ host key เดิม ใช้เมื่อ Gitea ถูก reset ใหม่ หรือ IP / key เปลี่ยน
```bash
ssh-keygen -R "[git.np-dms.work]:2222"
```
🔹 2) เชื่อมต่อครั้งแรก (จะมีคำถาม fingerprint)
```bash
ssh -T git@git.np-dms.work -p 2222
```
🔹 3) แสดง SSH public key เพื่อเพิ่มใน Gitea
```bash
cat /root/.ssh/id_ed25519.pub
cat /root/.ssh/id_rsa.pub
```
🔹 4) เพิ่ม remote ใหม่ (หากยังไม่ได้เพิ่ม)
```bash
git remote add origin ssh://git@git.np-dms.work:2222/np-dms/lcbp3.git
```
🔹 5) ลบ remote เดิมหากผิด
```bash
git remote remove origin
```
🔹 6) Push ครั้งแรกหลังตั้งค่า
```bash
git push -u origin main
```
🔹 7) Clone repo ใหม่ทั้งหมด
```bash
git clone ssh://git@git.np-dms.work:2222/np-dms/lcbp3.git
```
---
## 🧩 SECTION 2 คำสั่ง Git ใช้งานประจำวัน
🟦 ตรวจสอบสถานะงาน
```bash
git status
```
🟦 ดูว่าแก้ไฟล์อะไรไป
```bash
git diff
```
🟦 เพิ่มไฟล์ทั้งหมด
```bash
git add .
```
🟦 Commit การแก้ไข
```bash
git commit -m "message"
```
🟦 Push
```bash
git push
```
🟦 Pull (ดึงงานล่าสุด)
```bash
git pull
```
---
## 🧩 SECTION 3 ทำงานกับ Branch
### ดู branch ทั้งหมด
```bash
git branch
```
### สร้าง branch ใหม่
```bash
git checkout -b feature/login-page
```
### สลับ branch
```bash
git checkout main
```
### ส่ง branch ขึ้น Gitea
```bash
git push -u origin feature/login-page
```
### ลบ branch ในเครื่อง
```bash
git branch -d feature/login-page
```
### ลบ branch บน Gitea
```bash
git push origin --delete feature/login-page
```
### Merge branch → main
```bash
git checkout main
git pull
git merge feature/login-page
git push
```
### Rebase เพื่อให้ history สวย
```bash
git checkout feature/login-page
git rebase main
git checkout main
git merge feature/login-page
git push
```
---
## 🧩 SECTION 4 แก้ไขปัญหา Repo
🔴 (1) Reset repo ทั้งหมดให้เหมือน remote
⚠ ใช้เมื่อไฟล์ในเครื่องพัง หรือแก้จนเละ
```bash
git fetch --all
git reset --hard origin/main
```
🔴 (2) แก้ปัญหา conflict ตอน pull
```bash
git pull --rebase
```
🔴 (3) ดู remote ว่าชี้ไปทางไหน
```bash
git remote -v
```
🔴 (4) เปลี่ยน remote ใหม่
```bash
git remote remove origin
git remote add origin ssh://git@git.np-dms.work:2222/np-dms/lcbp3.git
```
🔴 (5) Commit message ผิด แก้ใหม่
```bash
git commit --amend
```
🔴 (6) ย้อน commit ล่าสุด (ไม่ลบไฟล์)
```bash
git reset --soft HEAD~1
```
🔴 (7) ดู log แบบสรุป
```bash
git log --oneline --graph
```
🔴 (8) Clone repo ใหม่ทั้งหมด (เมื่อพังหนัก)
```bash
rm -rf lcbp3
git clone ssh://git@git.np-dms.work:2222/np-dms/lcbp3.git
```
---
## 📌 END
```

View File

@@ -0,0 +1,91 @@
# การติดตั้ง Gitea ใน Docker
* user id ของ gites:
* uid=1000(git) gid=1000(git) groups=1000(git)
## กำหนดสิทธิ
```bash
chown -R 1000:1000 /share/Container/gitea/
[/share/Container/git] # ls -l /share/Container/gitea/etc/app.ini
[/share/Container/git] # setfacl -R -m u:1000:rwx /share/Container/gitea/
[/share/Container/git] # setfacl -R -m u:70:rwx /share/Container/git/postgres/
getfacl /share/Container/git/etc/app.ini
chown -R 1000:1000 /share/Container/gitea/
ล้าง
setfacl -R -b /share/Container/gitea/
chgrp -R administrators /share/Container/gitea/
chown -R 1000:1000 /share/Container/gitea/etc /share/Container/gitea/lib /share/Container/gitea/backup
setfacl -m u:1000:rwx -m g:1000:rwx /share/Container/gitea/etc /share/Container/gitea/lib /share/Container/gitea/backup
```
## Docker file
```yml
# File: share/Container/git/docker-compose.yml
# DMS Container v1_4_1 : แยก service และ folder, Application name: git, Servive:gitea
networks:
lcbp3:
external: true
giteanet:
external: true
name: gitnet
services:
gitea:
image: gitea/gitea:latest-rootless
container_name: gitea
restart: always
stdin_open: true
tty: true
environment:
# ---- File ownership in QNAP ----
USER_UID: "1000"
USER_GID: "1000"
TZ: Asia/Bangkok
# ---- Server / Reverse proxy (NPM) ----
GITEA__server__ROOT_URL: https://git.np-dms.work/
GITEA__server__DOMAIN: git.np-dms.work
GITEA__server__SSH_DOMAIN: git.np-dms.work
GITEA__server__START_SSH_SERVER: "true"
GITEA__server__SSH_PORT: "22"
GITEA__server__SSH_LISTEN_PORT: "22"
GITEA__server__LFS_START_SERVER: "true"
GITEA__server__HTTP_ADDR: "0.0.0.0"
GITEA__server__HTTP_PORT: "3000"
GITEA__server__TRUSTED_PROXIES: "127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
# --- การตั้งค่าฐานข้อมูล
GITEA__database__DB_TYPE: mysql
GITEA__database__HOST: mariadb:3306
GITEA__database__NAME: "gitea"
GITEA__database__USER: "gitea"
GITEA__database__PASSWD: "Center#2025"
# --- repos
GITEA__repository__ROOT: /var/lib/gitea/git/repositories
DISABLE_HTTP_GIT: "false"
ENABLE_BASIC_AUTHENTICATION: "true"
# --- Enable Package Registry ---
GITEA__packages__ENABLED: "true"
GITEA__packages__REGISTRY__ENABLED: "true"
GITEA__packages__REGISTRY__STORAGE_TYPE: local
GITEA__packages__REGISTRY__STORAGE_PATH: /data/registry
# Optional: lock install after setup (เปลี่ยนเป็น true เมื่อจบ onboarding)
GITEA__security__INSTALL_LOCK: "true"
volumes:
- /share/Container/gitea/backup:/backup
- /share/Container/gitea/etc:/etc/gitea
- /share/Container/gitea/lib:/var/lib/gitea
# ให้ repo root ใช้จาก /share/dms-data/gitea_repos
- /share/dms-data/gitea_repos:/var/lib/gitea/git/repositories
- /share/dms-data/gitea_registry:/data/registry
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3003:3000" # HTTP (ไปหลัง NPM)
- "2222:22" # SSH สำหรับ git clone/push
networks:
- lcbp3
- giteanet
```

View File

@@ -0,0 +1,138 @@
# การติดตั้ง MAriaDB และ PHPMyAdmin ใน Docker
* user id ของ mariadb:
* uid=0(root) gid=0(root) groups=0(root)
## กำหนดสิทธิ
```bash
chown -R 999:999 /share/Container/mariadb/init
chmod 755 /share/Container/mariadb/init
setfacl -R -m u:999:r-x /share/Container/mariadb/init
setfacl -R -d -m u:999:r-x /share/Container/mariadb/init
chown -R 33:33 /share/Container/pma/tmp
chmod 755 /share/Container/pma/tmp
setfacl -R -m u:33:rwx /share/Container/pma/tmp
setfacl -R -d -m u:33:rwx /share/Container/pma/tmp
chown -R 33:33 /share/dms-data/logs/pma
chmod 755 /share/dms-data/logs/pma
setfacl -R -m u:33:rwx /share/dms-data/logs/pma
setfacl -R -d -m u:33:rwx /share/dms-data/logs/pma
setfacl -R -m u:1000:rwx /share/Container/gitea
setfacl -R -m u:1000:rwx /share/dms-data/gitea_repos
setfacl -R -m u:1000:rwx /share/dms-data/gitea_registry
```
## เพิ่ม database & user สำหรับ Nginx Proxy Manager (NPM)
```bash
docker exec -it mariadb mysql -u root -p
CREATE DATABASE npm;
CREATE USER 'npm'@'%' IDENTIFIED BY 'npm';
GRANT ALL PRIVILEGES ON npm.* TO 'npm'@'%';
FLUSH PRIVILEGES;
```
## เพิ่ม database & user สำหรับ Gitea
```bash
docker exec -it mariadb mysql -u root -p
CREATE DATABASE gitea CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
CREATE USER 'gitea'@'%' IDENTIFIED BY 'Center#2025';
GRANT ALL PRIVILEGES ON gitea.* TO 'gitea'@'%';
FLUSH PRIVILEGES;
```
## Docker file
```yml
# File: share/Container/mariadb/docker-compose.yml
# DMS Container v1_4_1 : แยก service และ folder,Application name: lcbp3-db, Servive: mariadb, pma
x-restart: &restart_policy
restart: unless-stopped
x-logging: &default_logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
services:
mariadb:
<<: [*restart_policy, *default_logging]
image: mariadb:11.8
container_name: mariadb
stdin_open: true
tty: true
deploy:
resources:
limits:
cpus: "2.0"
memory: 4G
reservations:
cpus: "0.5"
memory: 1G
environment:
MYSQL_ROOT_PASSWORD: "Center#2025"
MYSQL_DATABASE: "lcbp3"
MYSQL_USER: "center"
MYSQL_PASSWORD: "Center#2025"
TZ: "Asia/Bangkok"
ports:
- "3306:3306"
volumes:
- "/share/Container/mariadb/data:/var/lib/mysql"
- "/share/Container/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf:ro"
- "/share/Container/mariadb/init:/docker-entrypoint-initdb.d:ro"
- "/share/dms-data/mariadb/backup:/backup"
healthcheck:
test:
["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -pCenter#2025 || exit 1"]
interval: 10s
timeout: 5s
retries: 15
networks:
lcbp3: {}
pma:
<<: [*restart_policy, *default_logging]
image: phpmyadmin:5-apache
container_name: pma
stdin_open: true
tty: true
deploy:
resources:
limits:
cpus: "0.25"
memory: 256M
environment:
TZ: "Asia/Bangkok"
PMA_HOST: "mariadb"
PMA_PORT: "3306"
PMA_ABSOLUTE_URI: "https://pma.np-dms.work/"
UPLOAD_LIMIT: "1G"
MEMORY_LIMIT: "512M"
ports:
- "89:80"
# expose:
# - "80"
volumes:
- "/share/Container/pma/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php:ro"
- "/share/Container/pma/zzz-custom.ini:/usr/local/etc/php/conf.d/zzz-custom.ini:ro"
- "/share/Container/pma/tmp:/var/lib/phpmyadmin/tmp:rw"
- "/share/dms-data/logs/pma:/var/log/apache2"
depends_on:
mariadb:
condition: service_healthy
networks:
lcbp3: {}
networks:
lcbp3:
external: true
```

99
Documnets/NPM_setting.md Normal file
View File

@@ -0,0 +1,99 @@
# การติดตั้ง Nginx Proxy Manager (NPM) ใน Docker
* ค่าเริ่มต้นคือ:Email: [admin@example.com] Password: changeme
* user id ของ NPM:
* uid=0(root) gid=0(root) groups=0(root)
---
## กำหนดสิทธิ
```bash
# ตรวจสอบ user id ของ NPM
docker exec -it npm id
chown -R 0:0 /share/Container/npm
setfacl -R -m u:0:rwx /share/Container/npm
```
## Note: Configurations
| Domain Names | Forward Hostname | IP Forward Port | Cache Assets | Block Common Exploits | Websockets | Force SSL | HTTP/2 | SupportHSTS Enabled |
| :----------------------------- | :--------------- | :-------------- | :----------- | :-------------------- | :--------- | :-------- | :----- | :------------------ |
| backend.np-dms.work | backend | 3000 | [ ] | [x] | [ ] | [x] | [x] | [ ] |
| lcbp3.np-dms.work | frontend | 3000 | [x] | [x] | [x] | [x] | [x] | [ ] |
| db.np-dms.work | mariadb | 3306 | [x] | [x] | [x] | [x] | [x] | [ ] |
| git.np-dms.work | gitea | 3000 | [x] | [x] | [x] | [x] | [x] | [ ] |
| n8n.np-dms.work | n8n | 5678 | [x] | [x] | [x] | [x] | [x] | [ ] |
| npm.np-dms.work | npm | 81 | [ ] | [x] | [x] | [x] | [x] | [ ] |
| pma.np-dms.work | pma | 80 | [x] | [x] | [ ] | [x] | [x] | [ ] |
| np-dms.work, [www.np-dms.work] | localhost | 80 | [x] | [x] | [ ] | [x] | [x] | [ ] |
## Docker file
```yml
# File: share/Container/npm/docker-compose-npm.yml
# DMS Container v1_4_1 แยก service และ folder, Application name: lcbp3-npm, Servive:npm
x-restart: &restart_policy
restart: unless-stopped
x-logging: &default_logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
services:
npm:
<<: [*restart_policy, *default_logging]
image: jc21/nginx-proxy-manager:latest
container_name: npm
stdin_open: true
tty: true
deploy:
resources:
limits:
cpus: "1.0" # 50% CPU
memory: 512M
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "81:81" # NPM Admin UI
environment:
TZ: "Asia/Bangkok"
DB_MYSQL_HOST: "mariadb"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
# Uncomment this if IPv6 is not enabled on your host
DISABLE_IPV6: "true"
networks:
- lcbp3
- giteanet
volumes:
- "/share/Container/npm/data:/data"
- "/share/dms-data/logs/npm:/data/logs" # <-- เพิ่ม logging volume
- "/share/Container/npm/letsencrypt:/etc/letsencrypt"
- "/share/Container/npm/custom:/data/nginx/custom" # <-- สำคัญสำหรับ http_top.conf
# - "/share/Container/lcbp3/npm/landing:/data/landing:ro"
landing:
image: nginx:1.27-alpine
container_name: landing
restart: unless-stopped
volumes:
- "/share/Container/npm/landing:/usr/share/nginx/html:ro"
networks:
- lcbp3
networks:
lcbp3:
external: true
giteanet:
external: true
name: gitnet
```

View File

@@ -235,20 +235,21 @@ src/
- **[✅] T0.1 Setup QNAP Container Station**
- [✅]สร้าง Docker Network: `lcbp3`
- [✅]Setup docker-compose.yml สำหรับ:
- [✅]MariaDB (db.np-dms.work)
- [✅]PHPMyAdmin (pma.np-dms.work)
- [ ]Redis (cache.np-dms.work)
- [ ]Elasticsearch (search.np-dms.work)
- [✅]MariaDB (db.np-dms.work) -> MariaDB_setting.md
- [✅]PHPMyAdmin (pma.np-dms.work) -> MariaDB_setting.md
- [✅]Gitea (git.np-dms.work) -> Gitea_setting.md
- [✅]Nginx Proxy Manager (npm.np-dms.work) -> NPM_setting.md
- [✅]Backend (backend.np-dms.work)
- [✅]Nginx Proxy Manager (npm.np-dms.work)
- [✅]Redis
- [✅]Elasticsearch
- กำหนด Environment Variables ใน docker-compose.yml (ไม่ใช้ .env)
- **Security:** Setup network segmentation และ firewall rules
- [ ] **Deliverable:** Services ทั้งหมดรันได้และเชื่อมต่อกันผ่าน Network
- [] **Deliverable:** Services ทั้งหมดรันได้และเชื่อมต่อกันผ่าน Network
- **Dependencies:** None (Task เริ่มต้น)
- **[✅] T0.2 Initialize NestJS Project**
- สร้างโปรเจกต์ใหม่ด้วย Nest CLI
- ติดตั้ง Dependencies:
- [ ] สร้างโปรเจกต์ใหม่ด้วย Nest CLI
- [ ] ติดตั้ง Dependencies:
```bash
# Core
@@ -1258,7 +1259,7 @@ quality_gates:
## 📊 **สรุป Timeline**
| Phase | ระยะเวลา | จำนวนงาน | Output หลัก |
| ------- | -------------- | ------------ | ------------------------------------ |
| ------- | ------------ | ------------ | ------------------------------------ |
| Phase 0 | 1 สัปดาห์ | 4 | Infrastructure Ready + Security Base |
| Phase 1 | 2 สัปดาห์ | 5 | Auth & User Management + RBAC |
| Phase 2 | 1 สัปดาห์ | 4 | Security & File Management |

View File

@@ -618,7 +618,7 @@
**Purpose**: เก็บข้อมูลแม่แบบ (Template) ของสายงานการส่งต่อเอกสารเพื่อขออนุมัติ ทำให้ไม่ต้องกำหนดขั้นตอนซ้ำทุกครั้ง สามารถสร้างเป็นแม่แบบทั่วไป หรือเฉพาะสำหรับโครงการใดโครงการหนึ่งได้
| Column Name | Data Type | Constraints | Description |
| ------------- | --------- | --------------------------- | -------------------------------------------- |
| ------------- | ------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| id | INT | PRIMARY KEY, AUTO_INCREMENT | ID หลัก (Primary Key) ของแม่แบบ รันค่าอัตโนมัติ |
| template_name | VARCHAR(255) | NOT NULL | ชื่อของแม่แบบ เช่น "เสนอโครงการ", "ขออนุมัติจัดซื้อ" |
| description | TEXT | NULL | คำอธิบายรายละเอียดเกี่ยวกับแม่แบบนี้ |
@@ -643,7 +643,7 @@
**Purpose**: เก็บรายละเอียดของแต่ละขั้นตอน (Steps) ภายในแม่แบบสายงาน (correspondence_routing_templates) กำหนดว่าจะส่งไปที่องค์กรไหน ลำดับเป็นเท่าไร และเพื่อวัตถุประสงค์อะไร
| Column Name | Data Type | Constraints | Description |
| :----- |----- | ----- | ---- |
| :----------------- | --------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| id | INT | PRIMARY KEY, AUTO_INCREMENT | ID หลักของขั้นตอน |
| template_id | INT | NOT NULL | ID ของแม่แบบที่ขั้นตอนนี้สังกัดอยู่ |
| sequence | INT | NOT NULL | ลำดับของขั้นตอน (1, 2, 3, ...) |
@@ -669,7 +669,7 @@
**Purpose**: เป็นตารางที่เก็บข้อมูลการส่งต่อเอกสารจริง (Instance/Run-time) ติดตามประวัติการเคลื่อนย้ายของแต่ละเอกสาร ว่าผ่านใครมาบ้าง อยู่ที่ใคร และสถานะปัจจุบันคืออะไร
| Column Name | Data Type | Constraints | Description |
| --- | --- | --- | --- |
| -------------------- | --------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | INT | PRIMARY KEY, AUTO_INCREMENT | ID หลักของรายการส่งต่อ |
| correspondence_id | INT | NOT NUL | ID ของเอกสาร (FK ไปยัง correspondence_revisions) |
| template_id | INT | NULL | ID ของแม่แบบที่ใช้สร้างสายงานนี้ (เก็บไว้เป็นข้อมูลอ้างอิง) |
@@ -708,7 +708,7 @@
**Purpose**: ตารางนี้ใช้กำหนดกฎ (State Machine) ว่าสถานะใดสามารถเปลี่ยนไปเป็นสถานะใดได้บ้าง โดยขึ้นอยู่กับประเภทของหนังสือ เพื่อควบคุมการไหลของสถานะให้ถูกต้องตามข้อบังคับ
| Column Name | Data Type | Constraints | Description |
| -------------- | --------- | ----------- | ------------------------------------------------- |
| -------------- | --------- | ----------- | ----------------------------------------------- |
| type_id | INT | PRIMARY KEY | ID ของประเภทหนังสือ (เช่น หนังสือภายใน, หนังสือภายนอก) |
| from_status_id | INT | PRIMARY KEY | ID ของสถานะต้นทาง (เช่น ร่าง) |
| to_status_id | INT | PRIMARY KEY | ID ของสถานะปลายทาง (เช่น รออนุมัติ) |
@@ -1998,7 +1998,6 @@
- PRIMARY KEY (id)
- INDEX (entity_type, entity_id)
- INDEX (indexed_at)
- FULLTEXT INDEX (content) - Enables full-text searching
**Business Rules**:
@@ -2346,9 +2345,6 @@ CALL sp_get_next_document_number(
- `idx_audit_logs_reporting` on (created_at, entity_type, action)
- `idx_notifications_user_unread` on (user_id, is_read, created_at)
6. **Search**:
- `FULLTEXT idx_search_indices_content` on (content)
---
## Data Integrity Constraints

119
Documnets/Securities.md Normal file
View File

@@ -0,0 +1,119 @@
สวัสดีครับ! การตั้งค่า Network Segmentation และ Firewall Rules เป็นขั้นตอนที่ฉลาดมากครับ โดยเฉพาะเมื่อคุณมี Services ที่ต้องเปิดสู่ Public (เช่น `lcbp3.np-dms.work`) และ Services ภายใน (เช่น `db.np-dms.work`)
สำหรับอุปกรณ์ Omada (ER7206 + OC200) กลยุทธ์หลักคือการใช้ **VLANs (Virtual LANs)** เพื่อแบ่งกลุ่มอุปกรณ์ และใช้ **Firewall ACLs (Access Control Lists)** เพื่อควบคุมการจราจรระหว่างกลุ่มเหล่านั้น
นี่คือคำแนะนำตามแนวทาง "Zero Trust" ที่ปรับให้เข้ากับสถาปัตยกรรมของคุณครับ
---
## 1. 🌐 การแบ่งส่วนเครือข่าย (VLAN Segmentation)
ใน Omada Controller (OC200) ให้คุณไปที่ `Settings > Wired Networks > LAN` และสร้างเครือข่ายย่อย (VLANs) ดังนี้:
* **VLAN 1 (Default): Management**
* **IP Range:** 192.168.1.x
* **วัตถุประสงค์:** ใช้สำหรับอุปกรณ์ Network (ER7206, OC200, Switches) และ PC ของผู้ดูแลระบบ (Admin) เท่านั้น
* **VLAN 10: Servers (DMZ)**
* **IP Range:** 192.168.10.x
* **วัตถุประสงค์:** นี่คือ VLAN ที่คุณจะเสียบสาย LAN ของ **QNAP NAS** ครับ QNAP จะได้รับ IP ในกลุ่มนี้ (เช่น `192.168.10.100`)
* **VLAN 20: Office / Trusted**
* **IP Range:** 192.168.20.x
* **วัตถุประสงค์:** สำหรับ PC, Notebook, และ Wi-Fi ของพนักงานทั่วไปที่ต้องเข้าใช้งานระบบ (เช่น `lcbp3.np-dms.work`)
* **VLAN 30: Guests / Untrusted**
* **IP Range:** 192.168.30.x
* **วัตถุประสงค์:** สำหรับ Wi-Fi แขก (Guest) ห้ามเข้าถึงเครือข่ายภายในโดยเด็ดขาด
**การตั้งค่า Port Switch:**
หลังจากสร้าง VLANs แล้ว ให้ไปที่ `Devices` > เลือก Switch ของคุณ > `Ports` > กำหนด Port Profile:
* Port ที่เสียบ QNAP NAS: ตั้งค่า Profile เป็น **VLAN 10**
* Port ที่เสียบ PC พนักงาน: ตั้งค่า Profile เป็น **VLAN 20**
---
## 2. 🔥 Firewall Rules (ACLs)
นี่คือหัวใจสำคัญครับ ไปที่ `Settings > Network Security > ACL (Access Control)`
กฎของ Firewall จะทำงานจากบนลงล่าง (ข้อ 1 ทำก่อนข้อ 2)
### A. กฎการห้าม (Deny Rules) - สำคัญที่สุด
**กฎข้อ 1: บล็อก Guest (VLAN 30) ไม่ให้ยุ่งกับใคร**
* **Name:** Isolate-Guests
* **Policy:** Deny
* **Source:** `Network` -> `VLAN 30`
* **Destination:** `Network` -> `VLAN 1`, `VLAN 10`, `VLAN 20`
* *(กฎนี้จะทำให้ Guest ออกอินเทอร์เน็ตได้อย่างเดียว แต่คุยข้าม VLAN ไม่ได้)*
**กฎข้อ 2: บล็อก Server (VLAN 10) ไม่ให้โจมตีคนอื่น**
* **Name:** Isolate-Servers
* **Policy:** Deny
* **Source:** `Network` -> `VLAN 10`
* **Destination:** `Network` -> `VLAN 20`
* *(กฎนี้ป้องกันไม่ให้ Server (QNAP) ที่อาจถูกแฮก เริ่มเชื่อมต่อไปยัง PC ของพนักงาน (VLAN 20) เพื่อแพร่กระจาย Malware)*
**กฎข้อ 3: บล็อก Office ไม่ให้เข้าหน้า Admin**
* **Name:** Block-Office-to-Management
* **Policy:** Deny
* **Source:** `Network` -> `VLAN 20`
* **Destination:** `Network` -> `VLAN 1`
* *(ป้องกันไม่ให้พนักงานทั่วไปเข้าหน้าตั้งค่า Router หรือ Controller)*
### B. กฎการอนุญาต (Allow Rules)
**กฎข้อ 4: อนุญาตให้ Office (VLAN 20) ใช้งาน Services ที่จำเป็น**
* **Name:** Allow-Office-to-Services
* **Policy:** Allow
* **Source:** `Network` -> `VLAN 20`
* **Destination:** `IP Group` -> (สร้าง Group ชื่อ `QNAP_Services` ชี้ไปที่ `192.168.10.100` (IP ของ QNAP))
* **Port:** `Service` -> (สร้าง Port Group ชื่อ `Web_Services`):
* TCP 443 (HTTPS - สำหรับทุก Service เช่น lcbp3, git, pma)
* TCP 80 (HTTP - สำหรับ NPM redirect)
* TCP 81 (NPM Admin UI)
* TCP 2222 (Gitea SSH)
* (ไม่จำเป็นต้องเปิด Port 3000, 3003, 5678, 89 เพราะ NPM จัดการให้แล้ว)
### C. กฎสุดท้าย (Default)
Omada มักจะมีกฎ "Allow All" อยู่ล่างสุด ให้ปล่อยไว้ หรือถ้าคุณต้องการความปลอดภัยสูงสุด (Zero Trust) ให้เปลี่ยนกฎสุดท้ายเป็น "Deny All" (แต่ต้องมั่นใจว่ากฎ Allow ของคุณครอบคลุมทั้งหมดแล้ว)
---
## 3. 🚪 Port Forwarding (การเปิด Service สู่สาธารณะ)
ส่วนนี้ไม่ใช่ Firewall ACL แต่จำเป็นเพื่อให้คนนอกเข้าใช้งานได้ครับ
ไปที่ `Settings > Transmission > Port Forwarding`
สร้างกฎเพื่อส่งต่อการจราจรจาก WAN (อินเทอร์เน็ต) ไปยัง Nginx Proxy Manager (NPM) ที่อยู่บน QNAP (VLAN 10)
* **Name:** Allow-NPM-HTTPS
* **External Port:** 443
* **Internal Port:** 443
* **Internal IP:** `192.168.10.100` (IP ของ QNAP)
* **Protocol:** TCP
* **Name:** Allow-NPM-HTTP (สำหรับ Let's Encrypt)
* **External Port:** 80
* **Internal Port:** 80
* **Internal IP:** `192.168.10.100` (IP ของ QNAP)
* **Protocol:** TCP
### สรุปผังการเชื่อมต่อ
1. **ผู้ใช้ภายนอก** -> `https://lcbp3.np-dms.work`
2. **ER7206** รับที่ Port 443
3. **Port Forwarding** ส่งต่อไปยัง `192.168.10.100:443` (QNAP NPM)
4. **NPM** (บน QNAP) ส่งต่อไปยัง `backend:3000` หรือ `frontend:3000` ภายใน Docker
5. **ผู้ใช้ภายใน (Office)** -> `https://lcbp3.np-dms.work`
6. **Firewall ACL** (กฎข้อ 4) อนุญาตให้ VLAN 20 คุยกับ `192.168.10.100:443`
7. (ขั้นตอนที่ 3-4 ทำงานเหมือนเดิม)
การตั้งค่าตามนี้จะช่วยแยกส่วน Server ของคุณออกจากเครือข่ายพนักงานอย่างชัดเจน ซึ่งปลอดภัยกว่าการวางทุกอย่างไว้ในวง LAN เดียวกันมากครับ

View File

@@ -0,0 +1,143 @@
# การติดตั้ง Nginx Proxy Manager (NPM) ใน Docker
---
## **📝 คำอธิบายและข้อควรพิจารณา**
* 1 Redis (Service: cache)
* Image: redis:7-alpine มีขนาดเล็กและทันสมัย
* Port: ไม่ได้ expose port 6379 ออกมาที่ Host QNAP เพราะตามสถาปัตยกรรม Service backend (NestJS) จะคุยกับ cache (Redis) ผ่าน lcbp3 network ภายในโดยตรง ซึ่งปลอดภัยกว่าครับ
* Volume: map data ไปที่ /share/Container/cache/data เผื่อใช้ Redis ในการทำ Persistent Cache (ถ้าต้องการแค่ Locking อาจจะไม่จำเป็นต้อง map volume ก็ได้ครับ)
* User ID: Image redis:7-alpine รันด้วย user redis (UID 999)
* 2 Elasticsearch (Service: search)
* Image: elasticsearch:8.11.1 ผมเลือกเวอร์ชัน 8 ที่ใหม่และระบุชัดเจน (ไม่ใช้ latest) เพื่อความเสถียรครับ
* Port: ไม่ได้ expose port 9200 ออกมาที่ Host เช่นกัน เพราะ NPM_setting.md ระบุว่า npm (Nginx Proxy Manager) จะ forward search.np-dms.work ไปยัง service search ที่ port 9200 ผ่าน lcbp3 network ครับ
* Environment (สำคัญมาก):
* discovery.type: "single-node": ต้องมี ไม่อย่างนั้น Elasticsearch V.8 จะไม่ยอม start ถ้าไม่พบ node อื่นใน cluster
* xpack.security.enabled: "false": เพื่อความสะดวกในการพัฒนาระยะแรก NestJS จะได้เชื่อมต่อ API port 9200 ได้เลย (หากเปิดใช้งานจะต้องตั้งค่า SSL และ Token ซึ่งซับซ้อนกว่ามาก)
* ES_JAVA_OPTS: "-Xms1g -Xmx1g": เป็น Best Practice ที่ต้องกำหนด Heap Size ให้ Elasticsearch (ในที่นี้คือ 1GB)
* User ID: Image elasticsearch รันด้วย user elasticsearch (UID 1000)
---
## กำหนดสิทธิ
```bash
# สร้าง Directory
mkdir -p /share/Container/services/cache/data
mkdir -p /share/Container/services/search/data
# กำหนดสิทธิ์ให้ตรงกับ User ID ใน Container
# Redis (UID 999)
chown -R 999:999 /share/Container/services/cache/data
chmod -R 750 /share/Container/services/cache/data
# Elasticsearch (UID 1000)
chown -R 1000:1000 /share/Container/services/search/data
chmod -R 750 /share/Container/services/search/data
```
## Docker file
```yml
# File: /share/Container/services/docker-compose.yml (หรือไฟล์ที่คุณใช้รวม)
# DMS Container v1_4_1: เพิ่ม Application name: services, Services 'cache' (Redis) และ 'search' (Elasticsearch)
x-restart: &restart_policy
restart: unless-stopped
x-logging: &default_logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
networks:
lcbp3:
external: true
services:
# ----------------------------------------------------------------
# 1. Redis (สำหรับ Caching และ Distributed Lock)
# Service Name: cache (ตามที่ NPM และ Backend Plan อ้างอิง)
# ----------------------------------------------------------------
cache:
<<: [*restart_policy, *default_logging]
image: redis:7-alpine # ใช้ Alpine image เพื่อให้มีขนาดเล็ก
container_name: cache
stdin_open: true
tty: true
deploy:
resources:
limits:
cpus: "1.0"
memory: 2G # Redis เป็น in-memory, ให้ memory เพียงพอต่อการใช้งาน
reservations:
cpus: "0.25"
memory: 512M
environment:
TZ: "Asia/Bangkok"
networks:
- lcbp3 # เชื่อมต่อ network ภายในเท่านั้น
volumes:
- "/share/Container/cache/data:/data" # Map volume สำหรับเก็บข้อมูล (ถ้าต้องการ persistence)
healthcheck:
test: ["CMD", "redis-cli", "ping"] # ตรวจสอบว่า service พร้อมใช้งาน
interval: 10s
timeout: 5s
retries: 5
# ----------------------------------------------------------------
# 2. Elasticsearch (สำหรับ Advanced Search)
# Service Name: search (ตามที่ NPM และ Backend Plan อ้างอิง)
# ----------------------------------------------------------------
search:
<<: [*restart_policy, *default_logging]
image: elasticsearch:8.11.1 # แนะนำให้ระบุเวอร์ชันชัดเจน (V.8)
container_name: search
stdin_open: true
tty: true
deploy:
resources:
limits:
cpus: "2.0" # Elasticsearch ใช้ CPU และ Memory ค่อนข้างหนัก
memory: 4G
reservations:
cpus: "0.5"
memory: 2G
environment:
TZ: "Asia/Bangkok"
# --- Critical Settings for Single-Node ---
discovery.type: "single-node" # สำคัญมาก: กำหนดให้รันแบบ 1 node
# --- Security (Disable for Development) ---
# ปิด xpack security เพื่อให้ NestJS เชื่อมต่อง่าย (backend -> search:9200)
# หากเป็น Production จริง ควรเปิดใช้งานและตั้งค่า token/cert ครับ
xpack.security.enabled: "false"
# --- Performance Tuning ---
# กำหนด Heap size (1GB) ให้เหมาะสมกับ memory limit (4GB)
ES_JAVA_OPTS: "-Xms1g -Xmx1g"
networks:
- lcbp3 # เชื่อมต่อ network ภายใน (NPM จะ proxy port 9200 จากภายนอก)
volumes:
- "/share/Container/search/data:/usr/share/elasticsearch/data" # Map volume สำหรับเก็บ data/indices
healthcheck:
# รอจนกว่า cluster health จะเป็น yellow หรือ green
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"\\|\\\"status\":\"yellow\"'"]
interval: 30s
timeout: 10s
retries: 5
```

91
Documnets/n8n_setting.md Normal file
View File

@@ -0,0 +1,91 @@
# การติดตั้ง n8n ใน Docker
* user id ของ gites:
* uid=1000(node) gid=1000(node) groups=1000(node)
## กำหนดสิทธิ
```bash
# สำหรับ n8n volumes
chown -R 1000:1000 /share/Container/n8n
chmod -R 755 /share/Container/n8n
```
## Docker file
```yml
# File: share/Container/n8n/docker-compose.yml
# DMS Container v1_4_1 แยก service และ folder, Application name:n8n service n8n
x-restart: &restart_policy
restart: unless-stopped
x-logging: &default_logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
services:
n8n:
<<: [*restart_policy, *default_logging]
image: n8nio/n8n:latest
container_name: n8n
stdin_open: true
tty: true
deploy:
resources:
limits:
cpus: "1.5"
memory: 2G
reservations:
cpus: "0.25"
memory: 512M
environment:
TZ: "Asia/Bangkok"
NODE_ENV: "production"
# N8N_PATH: "/n8n/"
N8N_PUBLIC_URL: "https://n8n.np-dms.work/"
WEBHOOK_URL: "https://n8n.np-dms.work/"
N8N_EDITOR_BASE_URL: "https://n8n.np-dms.work/"
N8N_PROTOCOL: "https"
N8N_HOST: "n8n.np-dms.work"
N8N_PORT: 5678
N8N_PROXY_HOPS: "1"
N8N_DIAGNOSTICS_ENABLED: 'false'
N8N_SECURE_COOKIE: 'true'
N8N_ENCRYPTION_KEY: "9AAIB7Da9DW1qAhJE5/Bz4SnbQjeAngI"
N8N_BASIC_AUTH_ACTIVE: 'true'
N8N_BASIC_AUTH_USER: admin
N8N_BASIC_AUTH_PASSWORD: Center#2025
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: 'true'
GENERIC_TIMEZONE: "Asia/Bangkok"
DB_TYPE: mysqldb
DB_MYSQLDB_DATABASE: "n8n"
DB_MYSQLDB_USER: "center"
DB_MYSQLDB_PASSWORD: "Center#2025"
DB_MYSQLDB_HOST: "mariadb"
DB_MYSQLDB_PORT: 3306
ports:
- "5678:5678"
networks:
lcbp3: {}
volumes:
- "/share/Container/n8n:/home/node/.n8n"
- "/share/Container/n8n/cache:/home/node/.cache"
- "/share/Container/n8n/scripts:/scripts"
- "/share/Container/n8n/data:/data"
- "/var/run/docker.sock:/var/run/docker.sock"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5678/"]
# test: ["CMD", "curl", "-f", "http://127.0.0.1:5678/ || exit 1"]
interval: 15s
timeout: 5s
retries: 30
networks:
lcbp3:
external: true
```

View File

@@ -0,0 +1,120 @@
# **🗺️ แผนผัง Network Architecture & Firewall (LCBP3-DMS)**
แผนผังนี้แสดงการแบ่งส่วนเครือข่าย (VLANs) และกฎ Firewall (ACLs) สำหรับ TP-Link Omada (ER7206/OC200) เพื่อรักษาความปลอดภัยของ QNAP NAS และ Docker Services
## **1\. แผนผังการเชื่อมต่อ (Connection Flow Diagram)**
graph TD
direction TB
subgraph Flow1 \[\<b\>การเชื่อมต่อจากภายนอก (Public WAN)\</b\>\]
User\[ผู้ใช้งานภายนอก (Internet)\]
end
subgraph Router \[\<b\>Router (ER7206)\</b\> \- Gateway\]
User \-- "Port 80/443 (HTTPS/HTTP)" \--\> ER7206
ER7206(\<b\>Port Forwarding\</b\>\<br/\>TCP 80 \-\> 192.168.10.100:80\<br/\>TCP 443 \-\> 192.168.10.100:443)
end
subgraph VLANs \[\<b\>เครือข่ายภายใน (VLANs & Firewall Rules)\</b\>\]
direction LR
subgraph VLAN10 \[\<b\>VLAN 10: Servers (DMZ)\</b\>\<br/\>192.168.10.x\]
QNAP\[\<b\>QNAP NAS (192.168.10.100)\</b\>\]
end
subgraph VLAN20 \[\<b\>VLAN 20: Office\</b\>\<br/\>192.168.20.x\]
OfficePC\[PC พนักงาน/Wi-Fi\]
end
subgraph VLAN30 \[\<b\>VLAN 30: Guests\</b\>\<br/\>192.168.30.x\]
GuestPC\[Guest Wi-Fi\]
end
subgraph Firewall \[\<b\>Firewall ACLs (ควบคุมโดย OC200)\</b\>\]
direction TB
rule1(\<b\>Rule 1: DENY\</b\>\<br/\>Guest (VLAN 30\) \-\> All VLANs)
rule2(\<b\>Rule 2: DENY\</b\>\<br/\>Server (VLAN 10\) \-\> Office (VLAN 20))
rule3(\<b\>Rule 3: ALLOW\</b\>\<br/\>Office (VLAN 20\) \-\> QNAP (192.168.10.100)\<br/\>Ports: 443, 80, 81, 2222\)
end
%% \--- แสดงผล Firewall Rules \---
GuestPC \-.x|rule1| QNAP
QNAP \-.x|rule2| OfficePC
OfficePC \-- "\[https://lcbp3.np-dms.work\](https://lcbp3.np-dms.work)" \--\>|rule3| QNAP
end
%% \--- เชื่อมต่อ Router กับ QNAP \---
ER7206 \--\> QNAP
subgraph Docker \[\<b\>Docker Network 'lcbp3' (ภายใน QNAP)\</b\>\]
direction TB
subgraph PublicServices \[Services ที่ NPM เปิดสู่ภายนอก\]
direction LR
NPM\[\<b\>NPM (Nginx Proxy Manager)\</b\>\<br/\>รับการจราจรจาก QNAP\]
Frontend(frontend:3000)
Backend(backend:3000)
Gitea(gitea:3000)
PMA(pma:80)
N8N(n8n:5678)
end
subgraph InternalServices \[Internal Services (Backend เรียกใช้เท่านั้น)\]
direction LR
DB(mariadb:3306)
Cache(cache:6379)
Search(search:9200)
end
%% \--- การเชื่อมต่อภายใน Docker \---
NPM \-- "lcbp3.np-dms.work" \--\> Frontend
NPM \-- "backend.np-dms.work" \--\> Backend
NPM \-- "git.np-dms.work" \--\> Gitea
NPM \-- "pma.np-dms.work" \--\> PMA
NPM \-- "n8n.np-dms.work" \--\> N8N
Backend \-- "lcbp3 Network" \--\> DB
Backend \-- "lcbp3 Network" \--\> Cache
Backend \-- "lcbp3 Network" \--\> Search
end
%% \--- เชื่อมต่อ QNAP กับ Docker \---
QNAP \--\> NPM
%% \--- Styling \---
classDef default fill:\#f9f9f9,stroke:\#333,stroke-width:2px;
classDef router fill:\#e6f7ff,stroke:\#0056b3,stroke-width:2px;
classDef vlan fill:\#fffbe6,stroke:\#d46b08,stroke-width:2px;
classDef docker fill:\#e6ffed,stroke:\#096dd9,stroke-width:2px;
classDef internal fill:\#f0f0f0,stroke:\#595959,stroke-width:2px,stroke-dasharray: 5 5;
classDef fw fill:\#fff0f0,stroke:\#d9363e,stroke-width:2px,stroke-dasharray: 3 3;
class Router,ER7206 router;
class VLANs,VLAN10,VLAN20,VLAN30 vlan;
class Docker,PublicServices,InternalServices docker;
class DB,Cache,Search internal;
class Firewall,rule1,rule2,rule3 fw;
## **2\. สรุปการตั้งค่า Firewall ACLs (สำหรับ Omada OC200)**
นี่คือรายการกฎ (Rules) ที่คุณต้องสร้างใน Settings \> Network Security \> ACL (เรียงลำดับจากบนลงล่าง):
| ลำดับ | Name | Policy | Source | Destination | Ports |
| :---- | :---- | :---- | :---- | :---- | :---- |
| **1** | Isolate-Guests | **Deny** | Network \-\> VLAN 30 (Guests) | Network \-\> VLAN 1, 10, 20 | All |
| **2** | Isolate-Servers | **Deny** | Network \-\> VLAN 10 (Servers) | Network \-\> VLAN 20 (Office) | All |
| **3** | Block-Office-to-Mgmt | **Deny** | Network \-\> VLAN 20 (Office) | Network \-\> VLAN 1 (Mgmt) | All |
| **4** | Allow-Office-to-Services | **Allow** | Network \-\> VLAN 20 (Office) | IP Group \-\> QNAP\_Services (192.168.10.100) | Port Group \-\> Web\_Services (443, 80, 81, 2222\) |
| **5** | (Default) | Allow | Any | Any | All |
## **3\. สรุปการตั้งค่า Port Forwarding (สำหรับ Omada ER7206)**
นี่คือรายการกฎที่คุณต้องสร้างใน Settings \> Transmission \> Port Forwarding:
| Name | External Port | Internal IP | Internal Port | Protocol |
| :---- | :---- | :---- | :---- | :---- |
| Allow-NPM-HTTPS | 443 | 192.168.10.100 | 443 | TCP |
| Allow-NPM-HTTP | 80 | 192.168.10.100 | 80 | TCP |