From fd9be92b9dc3b49616f6ec7df18083412cc60fb0 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 21 Feb 2026 17:03:09 +0700 Subject: [PATCH] 260221:1703 20260221 --- .vscode/extensions.json | 1 - LCBP3_20260221.bin | Bin 0 -> 17650 bytes lcbp3.code-workspace | 247 ++++++++++++++---------- specs/08-infrastructure/Rev01/README.md | 106 ---------- specs/08-infrastructure/SSH_setting.md | 219 +++++++++++++++++++++ 5 files changed, 367 insertions(+), 206 deletions(-) create mode 100644 LCBP3_20260221.bin delete mode 100644 specs/08-infrastructure/Rev01/README.md create mode 100644 specs/08-infrastructure/SSH_setting.md 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 0000000000000000000000000000000000000000..4bd115f8603271dae14e0e3cb892c6e6bcdceeab GIT binary patch literal 17650 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 bhG}!)oe@Xvl)jEGr{fssVwqwB;D7fYD*Qy= literal 0 HcmV?d00001 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` |