diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4eb46f1..36a8ef9 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -28,7 +28,6 @@ "vivaxy.vscode-conventional-commits", "christian-kohler.path-intellisense", "christian-kohler.npm-intellisense", - "chakrounanas.turbo-console-log", "pranaygp.vscode-css-peek", "alefragnani.bookmarks", "pkief.material-icon-theme", diff --git a/LCBP3_20260221.bin b/LCBP3_20260221.bin new file mode 100644 index 0000000..4bd115f Binary files /dev/null and b/LCBP3_20260221.bin differ diff --git a/lcbp3.code-workspace b/lcbp3.code-workspace index cbb85eb..d55af77 100644 --- a/lcbp3.code-workspace +++ b/lcbp3.code-workspace @@ -4,7 +4,7 @@ { "name": "🔧 Backend", "path": "./backend" }, { "name": "🎨 Frontend", "path": "./frontend" }, { "name": "🗓️ docs", "path": "./docs" }, - { "name": "🔗 specs", "path": "./specs" } + { "name": "🔗 specs", "path": "./specs" }, ], "settings": { // ======================================== @@ -25,7 +25,7 @@ "editor.smoothScrolling": true, "editor.cursorBlinking": "smooth", "editor.cursorSmoothCaretAnimation": "on", - "editor.wordWrap": "on", + "editor.wordWrap": "off", "editor.linkedEditing": true, "editor.formatOnSave": true, "editor.formatOnPaste": true, @@ -39,41 +39,41 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[javascriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[scss]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[markdown]": { "editor.defaultFormatter": "yzhang.markdown-all-in-one", - "editor.wordWrap": "on" + "editor.wordWrap": "on", }, "[yaml]": { - "editor.defaultFormatter": "redhat.vscode-yaml" + "editor.defaultFormatter": "redhat.vscode-yaml", }, "[dockerfile]": { - "editor.defaultFormatter": "ms-azuretools.vscode-containers" + "editor.defaultFormatter": "ms-azuretools.vscode-containers", }, "[sql]": { "editor.defaultFormatter": "mtxr.sqltools", @@ -81,7 +81,7 @@ "editor.insertSpaces": true, "editor.detectIndentation": false, "editor.wordWrap": "off", - "editor.formatOnSave": true + "editor.formatOnSave": true, }, "sqltools.format": { "indent": " ", // 2 spaces @@ -92,7 +92,7 @@ "functionCase": "lower", // count(), sum(), date_format() // Spacing and Lines "linesBetweenQueries": 2, // เว้นบรรทัดระหว่าง query - "denseOperators": true, + //"denseOperators": true, "spaceAroundOperators": false, // Comma Style "commaPosition": "after", // ใส่ comma หลังคอลัมน์ @@ -109,7 +109,7 @@ // Other Styles "compact": true, // ไม่ย่อโค้ดให้แน่นเกินไป "uppercaseKeywords": true, - "newlineBeforeSemicolon": false + "newlineBeforeSemicolon": false, }, // ป้องกัน extension อื่นมายุ่ง @@ -120,7 +120,7 @@ "editor.codeActionsOnSave": { "source.fixAll": "explicit", "source.fixAll.prettier": "explicit", - "source.fixAll.eslint": "explicit" + "source.fixAll.eslint": "explicit", //"source.organizeImports": "explicit", //"source.addMissingImports": "explicit" }, @@ -178,7 +178,7 @@ "@public": "${workspaceFolder:🎨 Frontend}/public", "@styles": "${workspaceFolder:🎨 Frontend}/styles", "@types": "${workspaceFolder:🎨 Frontend}/types", - "@api": "${workspaceFolder:🎨 Frontend}/app/api" + "@api": "${workspaceFolder:🎨 Frontend}/app/api", }, "path-intellisense.autoSlashAfterDirectory": true, "path-intellisense.extensionOnImport": false, @@ -203,18 +203,18 @@ "tailwindCSS.suggestions": true, "tailwindCSS.includeLanguages": { "typescript": "javascript", - "typescriptreact": "javascript" + "typescriptreact": "javascript", }, "tailwindCSS.experimental.classRegex": [ ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], - "class[Nn]ame\\s*=\\s*['\"`]([^'\"`]*)['\"`]" + "class[Nn]ame\\s*=\\s*['\"`]([^'\"`]*)['\"`]", ], // ระบุที่ตั้งของ tailwind.config (ถ้ามี) "tailwindCSS.experimental.configFile": { //"backend/tailwind.config.js": "backend/**", - "frontend/tailwind.config.ts": "frontend/**" + "frontend/tailwind.config.ts": "frontend/**", }, // ======================================== @@ -233,7 +233,7 @@ "javascript", "javascriptreact", "typescript", - "typescriptreact" + "typescriptreact", ], "auto-rename-tag.activationOnLanguage": [ @@ -242,7 +242,7 @@ "javascript", "javascriptreact", "typescript", - "typescriptreact" + "typescriptreact", ], // ======================================== @@ -255,32 +255,32 @@ "color": "#FF2D00", "strikethrough": false, "backgroundColor": "transparent", - "bold": true + "bold": true, }, { "tag": "?", "color": "#3498DB", "strikethrough": false, - "backgroundColor": "transparent" + "backgroundColor": "transparent", }, { "tag": "//", "color": "#474747", "strikethrough": true, - "backgroundColor": "transparent" + "backgroundColor": "transparent", }, { "tag": "todo", "color": "#FF8C00", "strikethrough": false, - "backgroundColor": "transparent" + "backgroundColor": "transparent", }, { "tag": "*", "color": "#98C379", "strikethrough": false, - "backgroundColor": "transparent" - } + "backgroundColor": "transparent", + }, ], // ======================================== @@ -295,28 +295,28 @@ "TODO": { "icon": "check", "iconColour": "#FF8C00", - "foreground": "#FF8C00" + "foreground": "#FF8C00", }, "FIXME": { "icon": "alert", "iconColour": "#FF2D00", - "foreground": "#FF2D00" + "foreground": "#FF2D00", }, "BUG": { "icon": "bug", "iconColour": "#FF2D00", - "foreground": "#FF2D00" + "foreground": "#FF2D00", }, "NOTE": { "icon": "note", "iconColour": "#3498DB", - "foreground": "#3498DB" + "foreground": "#3498DB", }, "HACK": { "icon": "tools", "iconColour": "#FFA500", - "foreground": "#FFA500" - } + "foreground": "#FFA500", + }, }, // ======================================== @@ -335,6 +335,7 @@ "gitlens.views.repositories.location": "scm", "gitlens.views.fileHistory.location": "explorer", "gitlens.views.lineHistory.location": "explorer", + "gitlens.terminal.enabled": false, // ======================================== // GIT @@ -369,7 +370,7 @@ "javascript.updateImportsOnFileMove.enabled": "always", "javascript.inlayHints.parameterNames.enabled": "all", "javascript.inlayHints.functionLikeReturnTypes.enabled": true, - "javascript.inlayHints.variableTypes.enabled": true, + "javascript.inlayHints.variableTypes.enabled": false, "javascript.preferences.importModuleSpecifier": "relative", "typescript.suggest.autoImports": true, @@ -386,7 +387,7 @@ "emmet.includeLanguages": { "javascript": "javascriptreact", - "typescript": "typescriptreact" + "typescript": "typescriptreact", }, "emmet.triggerExpansionOnTab": true, "emmet.showSuggestionsAsSnippets": true, @@ -404,7 +405,7 @@ "files.associations": { "*.css": "tailwindcss", ".env*": "dotenv", - "*.md": "markdown" + "*.md": "markdown", }, "files.exclude": { @@ -417,7 +418,7 @@ "**/.turbo": true, "**/coverage": true, "**/.nyc_output": true, - "**/*.log": true + "**/*.log": true, }, "files.watcherExclude": { @@ -427,7 +428,7 @@ "**/.next/**": true, "**/dist/**": true, "**/build/**": true, - "**/.turbo/**": true + "**/.turbo/**": true, }, // ======================================== @@ -445,7 +446,7 @@ "**/yarn.lock": true, "**/package-lock.json": true, "**/pnpm-lock.yaml": true, - "**/*.log": true + "**/*.log": true, }, "search.followSymlinks": false, "search.useIgnoreFiles": true, @@ -466,8 +467,22 @@ "terminal.integrated.profiles.windows": { "PowerShell-7": { "path": "C:\\Program Files\\PowerShell\\7\\pwsh.exe", - "icon": "terminal-powershell" - } + "icon": "terminal-powershell", + }, + "SSH QNAP": { + "path": "C:\\Windows\\System32\\OpenSSH\\ssh.exe", + "args": ["qnap"], + "icon": "terminal-linux", + "color": "terminal.ansiGreen", + "overrideName": true, + }, + "SSH ASUSTOR": { + "path": "C:\\Windows\\System32\\OpenSSH\\ssh.exe", + "args": ["asustor"], + "icon": "terminal-linux", + "color": "terminal.ansiBlue", + "overrideName": true, + }, }, // ======================================== @@ -485,7 +500,7 @@ "workbench.editor.limit.value": 10, "workbench.startupEditor": "welcomePage", "workbench.view.showQuietly": { - "workbench.panel.output": false + "workbench.panel.output": false, }, // ======================================== // EXPLORER @@ -505,7 +520,7 @@ "*.ts": "${capture}.test.ts, ${capture}.spec.ts", "*.tsx": "${capture}.test.tsx, ${capture}.spec.tsx, ${capture}.module.css", "*.js": "${capture}.test.js, ${capture}.spec.js", - "*.jsx": "${capture}.test.jsx, ${capture}.spec.jsx" + "*.jsx": "${capture}.test.jsx, ${capture}.spec.jsx", }, // ======================================== @@ -555,10 +570,8 @@ // ======================================== "yaml.schemas": { - "https://json.schemastore.org/github-workflow.json": ".github/workflows/*.{yml,yaml}", - "https://json.schemastore.org/github-action.json": "action.{yml,yaml}", "https://json.schemastore.org/prettierrc.json": ".prettierrc.{yml,yaml}", - "https://json.schemastore.org/docker-compose.json": "docker-compose*.{yml,yaml}" + "https://json.schemastore.org/docker-compose.json": "docker-compose*.{yml,yaml}", }, "yaml.format.enable": true, "yaml.format.singleQuote": false, @@ -575,14 +588,14 @@ "rest-client.showResponseInDifferentTab": true, "rest-client.environmentVariables": { "$shared": { - "apiUrl": "http://localhost:3000" + "apiUrl": "http://localhost:3000", }, "development": { - "apiUrl": "http://localhost:3000" + "apiUrl": "http://localhost:3000", }, "production": { - "apiUrl": "https://lcbp3.nap-dms.work" - } + "apiUrl": "https://lcbp3.nap-dms.work", + }, }, // ======================================== @@ -595,7 +608,7 @@ "*.env.local": "Tune", "*.env.development": "Tune", "*.env.production": "Tune", - "docker-compose.*.yml": "Docker" + "docker-compose.*.yml": "Docker", }, "material-icon-theme.folders.associations": { "hooks": "Custom", @@ -607,7 +620,7 @@ "entities": "Database", "modules": "Folder-Controllers", "common": "Shared", - "config": "Config" + "config": "Config", }, // ======================================== @@ -623,7 +636,7 @@ // PERFORMANCE // ======================================== - "files.maxMemoryForLargeFilesMB": 4096, + "files.maxMemoryForLargeFilesMB": 1024, "telemetry.telemetryLevel": "off", "security.workspace.trust.untrustedFiles": "open", "extensions.ignoreRecommendations": false, @@ -643,7 +656,7 @@ { "mysqlOptions": { "authProtocol": "default", - "enableSsl": "Disabled" + "enableSsl": "Disabled", }, "ssh": "Disabled", "previewLimit": 50, @@ -654,8 +667,8 @@ "database": "lcbp3_dev", "username": "root", "password": "", - "askForPassword": true // ✅ ปลอดภัยกว่า - } + "askForPassword": true, // ✅ ปลอดภัยกว่า + }, ], "database-client.variableIndicator": [":", "$"], "geminicodeassist.rules": "ใช้ภาษาไทยในการโต้ตอบ\n\n\n\n", @@ -664,7 +677,7 @@ "vitest.commandLine": "npm run test --", "vitest.enable": true, "yaml.maxItemsComputed": 6000, - "powershell.cwd": "🎯 Root" + "powershell.cwd": "🎯 Root", }, // ======================================== // LAUNCH CONFIGURATIONS @@ -683,7 +696,7 @@ // "internalConsoleOptions": "neverOpen", "skipFiles": ["/**"], "sourceMaps": true, - "restart": true + "restart": true, }, { "name": "🎨 Debug Frontend (Next.js)", @@ -695,8 +708,8 @@ "serverReadyAction": { "pattern": "- Local:.+(https?://\\S+)", "uriFormat": "%s", - "action": "debugWithChrome" - } + "action": "debugWithChrome", + }, }, { "name": "🧪 Debug Backend Tests (Jest)", @@ -705,7 +718,7 @@ "runtimeExecutable": "npm", "runtimeArgs": ["run", "test:debug"], "cwd": "${workspaceFolder:🔧 Backend}", - "console": "integratedTerminal" + "console": "integratedTerminal", }, { "name": "🧪 Debug Frontend Tests (Vitest)", @@ -715,17 +728,17 @@ "runtimeArgs": ["run", "test:debug"], "cwd": "${workspaceFolder:🎨 Frontend}", "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - } + "internalConsoleOptions": "neverOpen", + }, ], "compounds": [ { "name": "🚀 Debug Full Stack", "configurations": ["🔧 Debug Backend (NestJS)", "🎨 Debug Frontend (Next.js)"], - "stopAll": true - } - ] + "stopAll": true, + }, + ], }, // ======================================== // TASKS @@ -738,130 +751,166 @@ "type": "shell", "command": "npm run start:dev", "options": { - "cwd": "${workspaceFolder:🔧 Backend}" + "cwd": "${workspaceFolder:🔧 Backend}", }, "problemMatcher": [], "isBackground": true, "presentation": { "reveal": "always", "panel": "dedicated", - "group": "dev" - } + "group": "dev", + }, }, { "label": "🎨 Start Frontend Dev", "type": "shell", "command": "npm run dev", "options": { - "cwd": "${workspaceFolder:🎨 Frontend}" + "cwd": "${workspaceFolder:🎨 Frontend}", }, "problemMatcher": [], "isBackground": true, "presentation": { "reveal": "always", "panel": "dedicated", - "group": "dev" - } + "group": "dev", + }, }, { "label": "🚀 Start Full Stack", "dependsOn": ["🔧 Start Backend Dev", "🎨 Start Frontend Dev"], - "problemMatcher": [] + "problemMatcher": [], }, { "label": "🧪 Run Backend Tests", "type": "shell", "command": "npm run test", "options": { - "cwd": "${workspaceFolder:🔧 Backend}" + "cwd": "${workspaceFolder:🔧 Backend}", }, - "problemMatcher": [] + "problemMatcher": [], }, { "label": "🧪 Run Frontend Tests", "type": "shell", "command": "npm run test", "options": { - "cwd": "${workspaceFolder:🎨 Frontend}" + "cwd": "${workspaceFolder:🎨 Frontend}", }, - "problemMatcher": [] + "problemMatcher": [], }, { "label": "🧪 Watch Backend Tests", "type": "shell", "command": "npm run test:watch", "options": { - "cwd": "${workspaceFolder:🔧 Backend}" + "cwd": "${workspaceFolder:🔧 Backend}", }, "problemMatcher": [], - "isBackground": true + "isBackground": true, }, { "label": "🧪 Watch Frontend Tests", "type": "shell", "command": "npm run test:watch", "options": { - "cwd": "${workspaceFolder:🎨 Frontend}" + "cwd": "${workspaceFolder:🎨 Frontend}", }, "problemMatcher": [], - "isBackground": true + "isBackground": true, }, { "label": "🏗️ Build Backend", "type": "shell", "command": "npm run build", "options": { - "cwd": "${workspaceFolder:🔧 Backend}" + "cwd": "${workspaceFolder:🔧 Backend}", }, "problemMatcher": ["$tsc"], "group": { "kind": "build", - "isDefault": false - } + "isDefault": false, + }, }, { "label": "🏗️ Build Frontend", "type": "shell", "command": "npm run build", "options": { - "cwd": "${workspaceFolder:🎨 Frontend}" + "cwd": "${workspaceFolder:🎨 Frontend}", }, "problemMatcher": ["$tsc"], "group": { "kind": "build", - "isDefault": false - } + "isDefault": false, + }, }, { "label": "🔍 Lint Backend", "type": "shell", "command": "npm run lint", "options": { - "cwd": "${workspaceFolder:🔧 Backend}" + "cwd": "${workspaceFolder:🔧 Backend}", }, - "problemMatcher": ["$eslint-stylish"] + "problemMatcher": ["$eslint-stylish"], }, { "label": "🔍 Lint Frontend", "type": "shell", "command": "npm run lint", "options": { - "cwd": "${workspaceFolder:🎨 Frontend}" + "cwd": "${workspaceFolder:🎨 Frontend}", }, - "problemMatcher": ["$eslint-stylish"] + "problemMatcher": ["$eslint-stylish"], }, { "label": "🐳 Docker Compose Up", "type": "shell", "command": "docker-compose up -d", - "problemMatcher": [] + "options": { + "cwd": "${workspaceFolder:🎯 Root}", + }, + "problemMatcher": [], }, { "label": "🐳 Docker Compose Down", "type": "shell", "command": "docker-compose down", - "problemMatcher": [] - } - ] - } + "options": { + "cwd": "${workspaceFolder:🎯 Root}", + }, + "problemMatcher": [], + }, + { + "label": "🖥️ SSH QNAP", + "type": "shell", + "command": "ssh qnap", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "dedicated", + "group": "ssh", + }, + "runOptions": { + "runOn": "folderOpen", + }, + }, + { + "label": "🖥️ SSH ASUSTOR", + "type": "shell", + "command": "ssh asustor", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "dedicated", + "group": "ssh", + }, + "runOptions": { + "runOn": "folderOpen", + }, + }, + ], + }, } diff --git a/specs/08-infrastructure/Rev01/README.md b/specs/08-infrastructure/Rev01/README.md deleted file mode 100644 index d109a3e..0000000 --- a/specs/08-infrastructure/Rev01/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# 08-Infrastructure - -คู่มือการตั้งค่า Infrastructure สำหรับ **NAP-DMS LCBP3** (Laem Chabang Port Phase 3 - Document Management System) - -> 📍 **Platform:** QNAP (Container Station) + ASUSTOR (Portainer) -> 🌐 **Domain:** `*.np-dms.work` (IP: 159.192.126.103) -> 🔒 **Network:** `lcbp3` (Docker External Network) -> 📄 **Version:** v2.0.0 (Refactored for Stability) - ---- - -## 🏢 Hardware Infrastructure - -### Server Role Separation - -#### QNAP TS-473A -| (Application & Database Server)||| -| :--------------------- | :---------------- | :-------------------- | -| ✔ Application Runtime |✔ API / Web | ✔ Database (Primary) | -| ✔ High CPU / RAM usage | ✔ Worker / Queue | ✖ No long-term backup | -| Container Station (UI) | 32GB RAM (Capped) | AMD Ryzen V1500B | - -#### ASUSTOR AS5403T -| (Infrastructure & Backup Server) ||| -| :--------------------- | :---------------- | :------------------- | -| ✔ File Storage | ✔ Backup Target | ✔ Docker Infra | -|✔ Monitoring / Registry | ✔ Log Aggregation | ✖ No heavy App logic | -| Portainer (Manage All) | 16GB RAM | Intel Celeron @2GHz | - -### Servers Specification & Resource Allocation - -| Device | Model | CPU | RAM | Resource Policy | Role | -| :---------- | :------ | :---------------------- | :--- | :------------------ | :--------------------- | -| **QNAP** | TS-473A | AMD Ryzen V1500B | 32GB | **Strict Limits** | Application, DB, Cache | -| **ASUSTOR** | AS5403T | Intel Celeron @ 2.00GHz | 16GB | **Moderate Limits** | Infra, Backup, Monitor | - -### Service Distribution by Server - -#### QNAP TS-473A (Application Stack) - -| Category | Service | Strategy | Resource Limit (Est.) | -| :-------------- | :------------------------ | :------------------------------ | :-------------------- | -| **Web App** | Next.js (Frontend) | Single Instance | 2.0 CPU / 2GB RAM | -| **Backend API** | NestJS | **2 Replicas** (Load Balanced) | 2.0 CPU / 1.5GB RAM | -| **Database** | MariaDB (Primary) | Performance Tuned (Buffer Pool) | 4.0 CPU / 5GB RAM | -| **Worker** | Redis + BullMQ Worker | **Standalone + AOF** | 2.0 CPU / 1.5GB RAM | -| **Search** | Elasticsearch | **Heap Locked (2GB)** | 2.0 CPU / 4GB RAM | -| **API Gateway** | NPM (Nginx Proxy Manager) | SSL Termination | 1.0 CPU / 512MB RAM | -| **Workflow** | n8n | Automation | 1.0 CPU / 1GB RAM | -| **Code** | Gitea | Git Repository | 1.0 CPU / 1GB RAM | - -#### ASUSTOR AS5403T (Infrastructure Stack) - -| Category | Service | Notes | -| :--------------- | :------------------ | :------------------------------ | -| **File Storage** | NFS / SMB | Shared volumes for backup | -| **Backup** | Restic / Borg | Pull-based Backup (More Safe) | -| **Docker Infra** | Registry, Portainer | Container image registry, mgmt | -| **Monitoring** | Uptime Kuma | Service availability monitoring | -| **Metrics** | Prometheus, Grafana | Cross-Server Scraping | -| **Log** | Loki / Syslog | Centralized logging | - ---- - -## 🔄 Data Flow Architecture -┌──────────────┐ -│ User │ -└──────┬───────┘ - │ HTTPS (443) - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ QNAP TS-473A │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ Nginx Proxy Manager (NPM) │ │ -│ │ SSL Termination + Round Robin LB │ │ -│ └───────────────────────┬─────────────────────────────────┘ │ -│ │ │ -│ ┌───────────────────────▼─────────────────────────────────┐ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ | │ -│ │ │ Next.js │─▶│ NestJS │ │ NestJS │ | │ -│ │ │ (Frontend) │ │ (Replica 1) │ │ (Replica 2) │ │ │ -│ │ └──────────────┘ └──────┬───────┘ └──────┬───────┘ │ │ -│ │ │ │ │ │ -│ │ ┌─────────────────────────┼────────────────┼────┐ │ │ -│ │ ▼ ▼ ▼ ▼ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ │ -│ │ │ MariaDB │ │ Redis │ │Elasticsearch│ │ │ -│ │ │ (Primary)│ │(Persist.)│ │ (Search) │ │ │ -│ │ └────┬─────┘ └──────────┘ └─────────────┘ │ │ -│ └──────┼──────────────────────────────────────────────────┘ │ -│ └──────┼────────────────────────────────────────────────────┘ - | Local Dump -> Restic Pull (Cross-Server) - ▼ -┌──────────────────────────────────────────────────────────────┐ -│ ASUSTOR AS5403T │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ Backup │ │ Registry │ │ Uptime │ │ │ -│ │ │ (Restic) │ │ (Docker) │ │ Kuma │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │Prometheus│───▶│ Grafana │ │ Loki │ │ │ -│ │ │(Scraper) │ │(Dashboard)│ │ (Logs) │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ ││ -│ └──────────────────────────────────────────────────────────┘│ └──────────────────────────────────────────────────────────────┘ diff --git a/specs/08-infrastructure/SSH_setting.md b/specs/08-infrastructure/SSH_setting.md new file mode 100644 index 0000000..28e651e --- /dev/null +++ b/specs/08-infrastructure/SSH_setting.md @@ -0,0 +1,219 @@ +# SSH Setting — QNAP & ASUSTOR + +> คู่มือการตั้งค่าและใช้งาน SSH สำหรับ NAS ทั้ง 2 เครื่องในโปรเจกต์ NAP-DMS + +--- + +## 📋 ข้อมูลการเชื่อมต่อ + +| รายการ | QNAP (TS-464) | ASUSTOR (AS5402T) | +| ------------- | ------------------ | ------------------- | +| **Role** | Application Server | Monitoring / Backup | +| **IP** | `192.168.10.8` | `192.168.10.9` | +| **SSH Port** | `22` | `22` | +| **Username** | `nattanin` | `nattanin` | +| **SSH Alias** | `qnap` | `asustor` | + +--- + +## 1. เปิดใช้งาน SSH บน NAS + +### 1.1 QNAP + +1. เข้า **QTS Web UI** → `http://192.168.10.8:8080` +2. ไปที่ **Control Panel → Network & File Services → Telnet / SSH** +3. เปิด ✅ **Allow SSH connection** +4. ตั้ง Port เป็น `22` +5. คลิก **Apply** + +### 1.2 ASUSTOR + +1. เข้า **ADM Web UI** → `http://192.168.10.9:8000` +2. ไปที่ **Settings → Terminal & SNMP** +3. เปิด ✅ **Enable SSH service** +4. ตั้ง Port เป็น `22` +5. คลิก **Apply** + +--- + +## 2. ตั้งค่า SSH Key (Client → NAS) + +### 2.1 สร้าง SSH Key (ทำครั้งเดียวบนเครื่อง Client) + +```powershell +# ตรวจสอบว่ามี key อยู่แล้วหรือไม่ +ls ~/.ssh/id_ed25519* + +# ถ้ายังไม่มี → สร้างใหม่ +ssh-keygen -t ed25519 -C "nattanin@np-dms" +``` + +> **หมายเหตุ:** กด Enter ผ่าน passphrase ได้ หรือตั้ง passphrase เพื่อความปลอดภัยเพิ่มเติม + +### 2.2 คัดลอก Public Key ไปยัง NAS + +```powershell +# QNAP +ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.8 + +# ASUSTOR +ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.9 +``` + +> **ถ้า `ssh-copy-id` ไม่มีบน Windows** ให้ทำ manual: + +```powershell +# อ่าน public key +cat ~/.ssh/id_ed25519.pub + +# SSH เข้า NAS ด้วย password ก่อน แล้วเพิ่ม key +ssh nattanin@192.168.10.8 +mkdir -p ~/.ssh && chmod 700 ~/.ssh +echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... your-email@example.com" >> ~/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys +exit +``` + +### 2.3 ทดสอบการเชื่อมต่อ + +```powershell +# ต้องเข้าได้โดยไม่ต้องใส่ password +ssh nattanin@192.168.10.8 +ssh nattanin@192.168.10.9 +``` + +--- + +## 3. ตั้งค่า SSH Config (Client) + +ไฟล์: `~/.ssh/config` (Windows: `C:\Users\\.ssh\config`) + +```ssh-config +Host gitea + HostName git.np-dms.work + User git + Port 2222 + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + +Host qnap + HostName 192.168.10.8 + User nattanin + Port 22 + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + +Host asustor + HostName 192.168.10.9 + User nattanin + Port 22 + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes +``` + +### การใช้งาน Alias + +```powershell +# แทนที่จะพิมพ์ ssh nattanin@192.168.10.8 +ssh qnap + +# แทนที่จะพิมพ์ ssh nattanin@192.168.10.9 +ssh asustor + +# Git push ไป Gitea (ใช้ alias gitea) +git push gitea main +``` + +--- + +## 4. คำสั่ง SSH ที่ใช้บ่อย + +### 4.1 การเชื่อมต่อ + +```powershell +# เชื่อมต่อปกติ +ssh qnap +ssh asustor + +# เชื่อมต่อพร้อมระบุ port (กรณี port ไม่ใช่ 22) +ssh -p 2222 nattanin@192.168.10.8 +``` + +### 4.2 คัดลอกไฟล์ (SCP) + +```powershell +# คัดลอกไฟล์จาก Local → NAS +scp ./myfile.txt qnap:/share/np-dms/data/ + +# คัดลอกไฟล์จาก NAS → Local +scp qnap:/share/np-dms/data/myfile.txt ./ + +# คัดลอก Folder (recursive) +scp -r ./myfolder qnap:/share/np-dms/data/ +``` + +### 4.3 รันคำสั่งบน NAS โดยไม่ต้อง Login + +```powershell +# ดู Docker containers ที่กำลังรัน +ssh qnap "docker ps" + +# ดู Disk usage +ssh qnap "df -h" + +# ดู logs ของ container +ssh qnap "docker logs --tail 50 lcbp3-backend" + +# Restart container +ssh qnap "docker restart lcbp3-backend" +``` + +### 4.4 Port Forwarding (Tunnel) + +```powershell +# Forward port 3306 ของ QNAP มาที่ localhost:3306 (MariaDB) +ssh -L 3306:localhost:3306 qnap + +# Forward port 9200 (Elasticsearch) +ssh -L 9200:localhost:9200 qnap + +# Forward port 3000 (Grafana จาก ASUSTOR) +ssh -L 3000:localhost:3000 asustor +``` + +--- + +## 5. Hardening (เพิ่มความปลอดภัย) + +> กำหนดค่าบน NAS แต่ละเครื่อง — ไฟล์: `/etc/ssh/sshd_config` + +```bash +# ปิด login ด้วย password (ใช้ key เท่านั้น) +PasswordAuthentication no + +# ปิด root login +PermitRootLogin no + +# อนุญาตเฉพาะ user ที่ต้องการ +AllowUsers nattanin + +# Restart SSH service (QNAP) +/etc/init.d/login_server.sh restart + +# Restart SSH service (ASUSTOR) +/etc/init.d/sshd restart +``` + +> ⚠️ **คำเตือน:** ก่อนปิด `PasswordAuthentication` ให้แน่ใจว่า SSH Key ใช้งานได้แล้ว มิฉะนั้นจะเข้าไม่ได้ — ต้อง login ผ่าน Web UI เพื่อแก้ไข + +--- + +## 6. Troubleshooting + +| ปัญหา | สาเหตุ | วิธีแก้ | +| ------------------------------- | -------------------------- | --------------------------------------------------------------------- | +| `Connection refused` | SSH ไม่ได้เปิดบน NAS | เปิด SSH ผ่าน Web UI (ตาม Section 1) | +| `Permission denied (publickey)` | Key ไม่ตรงหรือ permission ผิด | ตรวจ `chmod 700 ~/.ssh` และ `chmod 600 ~/.ssh/authorized_keys` บน NAS | +| `Host key verification failed` | IP เปลี่ยนแต่ key เก่ายังอยู่ | `ssh-keygen -R 192.168.10.8` แล้วเชื่อมต่อใหม่ | +| `Connection timed out` | Firewall block หรือ IP ผิด | ตรวจ ACL ใน `03_Securities.md` และ ping ทดสอบ | +| `Network is unreachable` | อยู่คนละ VLAN / subnet | ตรวจ routing ใน `02_Network_daigram.md` |