refactor(specs): merge 08-infrastructure into canonical 04-06 dirs
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s

- Append live QNAP configs to 04-01-docker-compose.md (Appendix A)
  - MariaDB+PMA, Redis+Elasticsearch, NPM, Gitea, n8n, App stack
- Append SSH setup + Secrets management to 04-06-security-operations.md
  - Appendix A: SSH key setup, config, hardening, port forwarding
  - Appendix B: .env structure, secret generation, rotation, GPG backup
- Append QNAP/Gitea CI/CD docs to 04-04-deployment-guide.md
  - Appendix A: Container Station deployment steps
  - Appendix B: Gitea Actions CI/CD pipeline setup
  - Appendix C: act_runner (ASUSTOR) installation
- Move Git_command.md -> 05-Engineering-Guidelines/05-05-git-cheatsheet.md
- Move docker-compose-app.yml, lcbp3-monitoring.yml, lcbp3-registry.yml,
  grafana/ -> 04-Infrastructure-OPS/
- Archive lcbp3-db.md -> 99-archives/
- Remove all legacy 08-infrastructure/* files from git
- Remove Google OAuth client_secret JSON from git index (security)
- Add .gitignore rules: *client_secret*.json, *service_account*.json,
  specs/08-infrastructure/
- Update 04-Infrastructure-OPS/README.md with new file index
This commit is contained in:
admin
2026-02-23 15:03:35 +07:00
parent b7676777e8
commit 5eff8861e1
23 changed files with 808 additions and 1971 deletions

View File

@@ -1387,3 +1387,456 @@ networks:
-- Export audit logs for compliance
SELECT *
FROM document_numbering
---
# Appendix A — Live QNAP Production Configs
> 🖥️ **Server:** QNAP TS-473A · Path: `/share/np-dms/`
> These are the **actual running Docker Compose configurations** on QNAP Container Station (v1.8.0).
> They differ from the generic Blue-Green templates above in that they use the real `lcbp3` Docker external network.
---
## A.1 Database Stack (`lcbp3-db`)
> **Path:** `/share/np-dms/mariadb/docker-compose.yml`
> **Application name:** `lcbp3-db` — Services: `mariadb`, `pma`
### Pre-requisite: File Permissions
```bash
chown -R 999:999 /share/np-dms/mariadb
chmod -R 755 /share/np-dms/mariadb
setfacl -R -m u:999:rwx /share/np-dms/mariadb
# phpMyAdmin (UID 33)
chown -R 33:33 /share/np-dms/pma/tmp
chmod 755 /share/np-dms/pma/tmp
# Add Prometheus exporter user
# docker exec -it mariadb mysql -u root -p
# CREATE USER 'exporter'@'%' IDENTIFIED BY '<PASSWORD>' WITH MAX_USER_CONNECTIONS 3;
# GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
# FLUSH PRIVILEGES;
```
### Add Databases for NPM & Gitea
```sql
-- Nginx Proxy Manager
CREATE DATABASE npm;
CREATE USER 'npm'@'%' IDENTIFIED BY 'npm';
GRANT ALL PRIVILEGES ON npm.* TO 'npm'@'%';
FLUSH PRIVILEGES;
-- Gitea
CREATE DATABASE gitea CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
CREATE USER 'gitea'@'%' IDENTIFIED BY '<PASSWORD>';
GRANT ALL PRIVILEGES ON gitea.* TO 'gitea'@'%';
FLUSH PRIVILEGES;
```
```yaml
# /share/np-dms/mariadb/docker-compose.yml
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:
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: "<ROOT_PASSWORD>"
MYSQL_DATABASE: "lcbp3"
MYSQL_USER: "center"
MYSQL_PASSWORD: "<PASSWORD>"
TZ: "Asia/Bangkok"
ports:
- "3306:3306"
networks:
- lcbp3
volumes:
- "/share/np-dms/mariadb/data:/var/lib/mysql"
- "/share/np-dms/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf:ro"
- "/share/np-dms/mariadb/init:/docker-entrypoint-initdb.d:ro"
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
pma:
<<: [*restart_policy, *default_logging]
image: phpmyadmin:5-apache
container_name: pma
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"
networks:
- lcbp3
volumes:
- "/share/np-dms/pma/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php:ro"
- "/share/np-dms/pma/tmp:/var/lib/phpmyadmin/tmp:rw"
depends_on:
mariadb:
condition: service_healthy
```
---
## A.2 Cache + Search Stack (`services`)
> **Path:** `/share/np-dms/services/docker-compose.yml`
> **Application name:** `services` — Services: `cache` (Redis 7.2), `search` (Elasticsearch 8.11)
### Pre-requisite: File Permissions
```bash
mkdir -p /share/np-dms/services/cache/data
mkdir -p /share/np-dms/services/search/data
# Redis (UID 999)
chown -R 999:999 /share/np-dms/services/cache/data
chmod -R 750 /share/np-dms/services/cache/data
# Elasticsearch (UID 1000)
chown -R 1000:1000 /share/np-dms/services/search/data
chmod -R 750 /share/np-dms/services/search/data
```
```yaml
# /share/np-dms/services/docker-compose.yml
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:
cache:
<<: [*restart_policy, *default_logging]
image: redis:7-alpine
container_name: cache
deploy:
resources:
limits:
cpus: "1.0"
memory: 2G
reservations:
cpus: "0.25"
memory: 512M
environment:
TZ: "Asia/Bangkok"
ports:
- "6379:6379"
networks:
- lcbp3
volumes:
- "/share/np-dms/services/cache/data:/data"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
search:
<<: [*restart_policy, *default_logging]
image: elasticsearch:8.11.1
container_name: search
deploy:
resources:
limits:
cpus: "2.0"
memory: 4G
reservations:
cpus: "0.5"
memory: 2G
environment:
TZ: "Asia/Bangkok"
discovery.type: "single-node"
xpack.security.enabled: "false"
# Heap locked at 1GB per ADR-005
ES_JAVA_OPTS: "-Xms1g -Xmx1g"
ports:
- "9200:9200"
networks:
- lcbp3
volumes:
- "/share/np-dms/services/search/data:/usr/share/elasticsearch/data"
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"\\|\"status\":\"yellow\"'"]
interval: 30s
timeout: 10s
retries: 5
```
---
## A.3 Nginx Proxy Manager (`lcbp3-npm`)
> **Path:** `/share/np-dms/npm/docker-compose.yml`
> **Application name:** `lcbp3-npm` — Services: `npm`, `landing`
### NPM Proxy Host Config Reference
| Domain | Forward Host | Port | Cache | Block Exploits | WebSocket | SSL |
| :-------------------- | :----------- | :--- | :---- | :------------- | :-------- | :--- |
| `backend.np-dms.work` | `backend` | 3000 | ❌ | ✅ | ❌ | ✅ |
| `lcbp3.np-dms.work` | `frontend` | 3000 | ✅ | ✅ | ✅ | ✅ |
| `git.np-dms.work` | `gitea` | 3000 | ✅ | ✅ | ✅ | ✅ |
| `n8n.np-dms.work` | `n8n` | 5678 | ✅ | ✅ | ✅ | ✅ |
| `chat.np-dms.work` | `rocketchat` | 3000 | ✅ | ✅ | ✅ | ✅ |
| `npm.np-dms.work` | `npm` | 81 | ❌ | ✅ | ✅ | ✅ |
| `pma.np-dms.work` | `pma` | 80 | ✅ | ✅ | ❌ | ✅ |
```yaml
# /share/np-dms/npm/docker-compose.yml
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
giteanet:
external: true
name: gitnet
services:
npm:
<<: [*restart_policy, *default_logging]
image: jc21/nginx-proxy-manager:latest
container_name: npm
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
ports:
- "80:80"
- "443:443"
- "81:81"
environment:
TZ: "Asia/Bangkok"
DB_MYSQL_HOST: "mariadb"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
DISABLE_IPV6: "true"
networks:
- lcbp3
- giteanet
volumes:
- "/share/np-dms/npm/data:/data"
- "/share/np-dms/npm/letsencrypt:/etc/letsencrypt"
- "/share/np-dms/npm/custom:/data/nginx/custom"
landing:
image: nginx:1.27-alpine
container_name: landing
restart: unless-stopped
volumes:
- "/share/np-dms/npm/landing:/usr/share/nginx/html:ro"
networks:
- lcbp3
```
---
## A.4 Gitea (`git`)
> **Path:** `/share/np-dms/git/docker-compose.yml`
> **Application name:** `git` — Service: `gitea` (rootless)
### Pre-requisite: File Permissions
```bash
chown -R 1000:1000 /share/np-dms/gitea/
setfacl -m u:1000:rwx /share/np-dms/gitea/etc \
/share/np-dms/gitea/lib \
/share/np-dms/gitea/backup
```
```yaml
# /share/np-dms/git/docker-compose.yml
networks:
lcbp3:
external: true
giteanet:
external: true
name: gitnet
services:
gitea:
image: gitea/gitea:latest-rootless
container_name: gitea
restart: always
environment:
USER_UID: "1000"
USER_GID: "1000"
TZ: Asia/Bangkok
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_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: "<PASSWORD>"
GITEA__packages__ENABLED: "true"
GITEA__security__INSTALL_LOCK: "true"
volumes:
- /share/np-dms/gitea/backup:/backup
- /share/np-dms/gitea/etc:/etc/gitea
- /share/np-dms/gitea/lib:/var/lib/gitea
- /share/np-dms/gitea/gitea_repos:/var/lib/gitea/git/repositories
- /share/np-dms/gitea/gitea_registry:/data/registry
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3003:3000"
- "2222:22"
networks:
- lcbp3
- giteanet
```
---
## A.5 n8n Automation
> **Path:** `/share/np-dms/n8n/docker-compose.yml`
```bash
chown -R 1000:1000 /share/np-dms/n8n
chmod -R 755 /share/np-dms/n8n
```
```yaml
# /share/np-dms/n8n/docker-compose.yml
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:
n8n:
<<: [*restart_policy, *default_logging]
image: n8nio/n8n:latest
container_name: n8n
deploy:
resources:
limits:
cpus: "1.5"
memory: 2G
reservations:
cpus: "0.25"
memory: 512M
environment:
TZ: "Asia/Bangkok"
NODE_ENV: "production"
N8N_PUBLIC_URL: "https://n8n.np-dms.work/"
WEBHOOK_URL: "https://n8n.np-dms.work/"
N8N_HOST: "n8n.np-dms.work"
N8N_PORT: 5678
N8N_PROTOCOL: "https"
N8N_PROXY_HOPS: "1"
N8N_DIAGNOSTICS_ENABLED: 'false'
N8N_SECURE_COOKIE: 'true'
# N8N_ENCRYPTION_KEY should be kept in .env (gitignored)
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: 'true'
DB_TYPE: mysqldb
DB_MYSQLDB_DATABASE: "n8n"
DB_MYSQLDB_USER: "center"
DB_MYSQLDB_HOST: "mariadb"
DB_MYSQLDB_PORT: 3306
ports:
- "5678:5678"
networks:
lcbp3: {}
volumes:
- "/share/np-dms/n8n:/home/node/.n8n"
- "/share/np-dms/n8n/cache:/home/node/.cache"
- "/share/np-dms/n8n/scripts:/scripts"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5678/"]
interval: 15s
timeout: 5s
retries: 30
```
---
## A.6 Application Stack (`lcbp3-app`)
> **Path:** `/share/np-dms/app/docker-compose.yml`
> See the annotated version in [`04-Infrastructure-OPS/docker-compose-app.yml`](./docker-compose-app.yml)
This stack contains `backend` (NestJS) and `frontend` (Next.js).
Refer to [04-04-deployment-guide.md](./04-04-deployment-guide.md) for full deployment steps and CI/CD pipeline details.

View File

@@ -935,3 +935,159 @@ docker exec lcbp3-mariadb mysql -u root -p -e "
**Version:** 1.8.0
**Last Updated:** 2025-12-02
**Next Review:** 2026-06-01
---
# Appendix A — QNAP Container Station Deployment
> 🖥️ **Platform:** QNAP TS-473A · Container Station · Docker Compose App path: `/share/np-dms/app/`
## A.1 Prerequisites Checklist
Before deploying `lcbp3-app`, ensure these services are **healthy**:
| Service | Container Name | Stack |
| -------------- | -------------- | ----------------------------- |
| MariaDB | `mariadb` | `lcbp3-db` |
| Redis | `cache` | `services` |
| Elasticsearch | `search` | `services` |
| NPM | `npm` | `lcbp3-npm` |
| Docker Network | `lcbp3` | `docker network create lcbp3` |
## A.2 Directory Setup (QNAP SSH)
```bash
mkdir -p /share/np-dms/data/uploads/temp
mkdir -p /share/np-dms/data/uploads/permanent
mkdir -p /share/np-dms/data/logs/backend
mkdir -p /share/np-dms/app
# UID 1001 = non-root nestjs user in container
chown -R 1001:1001 /share/np-dms/data/uploads
chown -R 1001:1001 /share/np-dms/data/logs/backend
chmod -R 750 /share/np-dms/data/uploads
```
## A.3 Deploy via Container Station UI
1. เปิด **Container Station****Applications****Create**
2. ตั้งชื่อ Application: `lcbp3-app`
3. วาง content จาก `specs/04-Infrastructure-OPS/docker-compose-app.yml`
4. แก้ไข Environment Variables ตามต้องการ (secrets ต้องไม่อยู่ใน git)
5. กด **Create**
ตรวจสอบ Container Status: Applications → `lcbp3-app`
-`backend` → Running (healthy)
-`frontend` → Running (healthy)
## A.4 Verify Deployment
```bash
# Backend health (inside Docker network)
docker exec frontend wget -qO- http://backend:3000/health
# Via NPM
curl -I https://lcbp3.np-dms.work
curl -I https://backend.np-dms.work/api
```
---
# Appendix B — Gitea Actions CI/CD Pipeline
> 🔄 Automated Build + Deploy on every push to `main`
## B.1 Setup Gitea Secrets
Gitea → Repository → Settings → Actions → Secrets → **Add New Secret**:
| Secret Name | Value | Description |
| ----------- | -------------- | ----------------------------- |
| `HOST` | `192.168.10.8` | QNAP IP (VLAN 10) |
| `PORT` | `22` | SSH Port |
| `USERNAME` | `admin` | SSH user with Docker access |
| `PASSWORD` | `***` | SSH password (or use SSH Key) |
## B.2 Pipeline Flow
```mermaid
graph TD
A[Push to main] --> B[Gitea Runner picks up job]
B --> C[SSH to QNAP]
C --> D[git pull latest code]
D --> E[Build Backend Image]
E --> F[Build Frontend Image]
F --> G[docker compose up -d]
G --> H[Cleanup old images]
H --> I[Deploy complete ✅]
```
## B.3 Manual Trigger (Re-deploy without code change)
1. Go to repository → **Actions** tab (top menu)
2. Select workflow **"Build and Deploy"**
3. Click **"Run workflow"** → Select branch `main`**Run**
## B.4 Troubleshooting
| Error | Cause | Fix |
| ---------------------------------------------- | ------------------------------- | --------------------------------------------- |
| `No matching runner with label: ubuntu-latest` | Runner not registered / offline | Register act_runner per Appendix C |
| `SSH Timeout` | QNAP firewall / ACL | Check VLAN 10 ACL allows runner IP on port 22 |
| `Disk Full` | Old images accumulate | `docker image prune -a` on QNAP |
| `Build failed: ENOENT .bin/ts-script` | pnpm deploy symlink error | Use `--shamefully-hoist` flag in Dockerfile |
---
# Appendix C — Gitea Runner (act_runner) on ASUSTOR
> **Platform:** ASUSTOR AS5403T · Path: `/volume1/np-dms/gitea-runner/`
> **Note:** Gitea is on QNAP, Runner is on ASUSTOR (per Server Role Separation)
## C.1 Get Registration Token
Gitea Web UI → **Site Administration****Actions****Runners****Create new Runner** → Copy token
## C.2 Setup Directory
```bash
ssh asustor
mkdir -p /volume1/np-dms/gitea-runner/data
```
## C.3 Docker Compose
```yaml
# /volume1/np-dms/gitea-runner/docker-compose.yml
services:
runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: always
environment:
GITEA_INSTANCE_URL: https://git.np-dms.work
GITEA_RUNNER_REGISTRATION_TOKEN: <paste-token-here>
GITEA_RUNNER_NAME: asustor-runner
# Label must match runs-on in deploy.yaml
GITEA_RUNNER_LABELS: ubuntu-latest:docker://node:18-bullseye,self-hosted:docker://node:18-bullseye
volumes:
- /volume1/np-dms/gitea-runner/data:/data
- /var/run/docker.sock:/var/run/docker.sock
```
```bash
cd /volume1/np-dms/gitea-runner
docker compose up -d
```
## C.4 Verify
Gitea → **Settings****Actions****Runners** — should show **Total: 1** with green indicator next to `asustor-runner`.
## C.5 Maintenance
```bash
# Cleanup old build images periodically
docker image prune -a # on ASUSTOR (runner images)
ssh qnap "docker image prune -a" # on QNAP (app images)
```

View File

@@ -442,3 +442,180 @@ echo "Account compromise response completed for User ID: $USER_ID"
**Version:** 1.8.0
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
---
# Appendix A — SSH Setup (QNAP & ASUSTOR)
> คู่มือการตั้งค่าและใช้งาน SSH สำหรับ NAS ทั้ง 2 เครื่อง
## A.1 Connection Info
| Item | QNAP (TS-473A) | ASUSTOR (AS5403T) |
| ------------- | ------------------ | ----------------------- |
| **Role** | Application Server | Infrastructure / Backup |
| **IP** | `192.168.10.8` | `192.168.10.9` |
| **SSH Port** | `22` | `22` |
| **SSH Alias** | `qnap` | `asustor` |
## A.2 Enable SSH on NAS
**QNAP:** QTS Web UI → Control Panel → Network & File Services → Telnet/SSH → Enable SSH (port 22)
**ASUSTOR:** ADM Web UI → Settings → Terminal & SNMP → Enable SSH service (port 22)
## A.3 SSH Key Setup (Client → NAS)
```powershell
# Create key (once on dev machine)
ssh-keygen -t ed25519 -C "nattanin@np-dms"
# Copy public key to NAS
ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.8 # QNAP
ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.9 # ASUSTOR
```
## A.4 SSH Config (`~/.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
```
## A.5 SSH Hardening (`/etc/ssh/sshd_config` on NAS)
```bash
PasswordAuthentication no # Key-only login
PermitRootLogin no
AllowUsers nattanin
# Restart SSH
/etc/init.d/login_server.sh restart # QNAP
/etc/init.d/sshd restart # ASUSTOR
```
> ⚠️ **ตรวจสอบว่า SSH Key ใช้งานได้ก่อนปิด PasswordAuthentication**
## A.6 SSH Port Forwarding (Useful Tunnels)
```powershell
# MariaDB local tunnel
ssh -L 3306:localhost:3306 qnap
# Elasticsearch tunnel
ssh -L 9200:localhost:9200 qnap
# Grafana tunnel (from ASUSTOR)
ssh -L 3000:localhost:3000 asustor
```
## A.7 SSH Troubleshooting
| Problem | Cause | Fix |
| ------------------------------- | ------------------------ | ------------------------------------------------------ |
| `Connection refused` | SSH not enabled on NAS | Enable SSH via Web UI |
| `Permission denied (publickey)` | Wrong key or permissions | `chmod 700 ~/.ssh`, `chmod 600 ~/.ssh/authorized_keys` |
| `Host key verification failed` | IP changed, stale key | `ssh-keygen -R 192.168.10.8` |
| `Connection timed out` | Firewall / wrong IP | Check ACL and ping |
---
# Appendix B — Secrets Management (QNAP Deployment)
> ⚠️ **Security Level: CONFIDENTIAL** — ห้าม commit secrets ลง Git
## B.1 Secret Categories
| Category | Examples | Storage |
| -------------------- | ------------------------------ | ---------------------- |
| Database Credentials | `MYSQL_ROOT_PASSWORD` | `.env` (gitignored) |
| API Keys | `JWT_SECRET`, `REDIS_PASSWORD` | `.env` (gitignored) |
| SSL Certificates | Let's Encrypt | NPM volume |
| SSH Keys | Backup access keys | ASUSTOR secure storage |
## B.2 Environment File (QNAP)
```bash
# File: /share/np-dms/.env
# ⚠️ MUST be in .gitignore
# === Database ===
MYSQL_ROOT_PASSWORD=<strong-password>
MYSQL_DATABASE=lcbp3
MYSQL_USER=center
MYSQL_PASSWORD=<strong-password>
# === Redis ===
REDIS_PASSWORD=<strong-password>
# === Application ===
JWT_SECRET=<random-64-hex> # openssl rand -hex 64
JWT_REFRESH_SECRET=<random-64-hex>
# === Monitoring ===
GRAFANA_PASSWORD=<admin-password>
# === External Services ===
LINE_CHANNEL_SECRET=<line-secret>
LINE_CHANNEL_ACCESS_TOKEN=<line-token>
```
## B.3 Generate Strong Secrets
```bash
openssl rand -base64 32 # Strong password (24+ chars)
openssl rand -hex 64 # JWT Secret (64 hex chars)
```
## B.4 Rotation Schedule
| Secret | Period | Impact |
| ----------------- | -------------------- | ----------------------- |
| JWT Secret | 90 days | Users re-login required |
| Database Password | 180 days | Service restart needed |
| Redis Password | 180 days | Service restart needed |
| SSL Certificates | Auto (Let's Encrypt) | None |
## B.5 Encrypted Secret Backup
```bash
# Encrypt and back up to ASUSTOR
gpg --symmetric --cipher-algo AES256 /share/np-dms/.env -o /tmp/env.gpg
scp /tmp/env.gpg admin@192.168.10.9:/volume1/backup/secrets/
rm /tmp/env.gpg
# Restore
scp admin@192.168.10.9:/volume1/backup/secrets/env.gpg /tmp/
gpg --decrypt /tmp/env.gpg > /share/np-dms/.env
rm /tmp/env.gpg
```
## B.6 Gitea Actions Secrets (CI/CD)
Configure at: Gitea → Repository → Settings → Actions → Secrets
| Secret Name | Description |
| ----------- | ----------------------------- |
| `HOST` | QNAP IP (`192.168.10.8`) |
| `PORT` | SSH Port (`22`) |
| `USERNAME` | SSH user with Docker access |
| `PASSWORD` | SSH password (prefer SSH Key) |

View File

@@ -16,15 +16,24 @@ It consolidates what was previously split across multiple operations and specifi
## 📂 Document Index
| File | Purpose | Key Contents |
| ------------------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------- |
| **[04-01-docker-compose.md](./04-01-docker-compose.md)** | Core Environment Setup | `.env` configs, Blue/Green Docker Compose, MariaDB & Redis optimization |
| **[04-02-backup-recovery.md](./04-02-backup-recovery.md)** | Disaster Recovery | RTO/RPO strategies, QNAP to ASUSTOR backup scripts, Restic/Mysqldump config |
| **[04-03-monitoring.md](./04-03-monitoring.md)** | Observability | Prometheus metrics, AlertManager rules (inclusive of Document Numbering DB), Grafana alerts |
| **[04-04-deployment-guide.md](./04-04-deployment-guide.md)** | Production Rollout | Step-by-step Blue-Green deployment scripts, rollback playbooks, Nginx Reverse Proxy |
| **[04-05-maintenance-procedures.md](./04-05-maintenance-procedures.md)** | Routine Care | Log rotation, dependency zero-downtime updates, scheduled DB optimizations |
| **[04-06-security-operations.md](./04-06-security-operations.md)** | Hardening & Audit | User access review scripts, SSL renewals, vulnerability scanning procedures |
| **[04-07-incident-response.md](./04-07-incident-response.md)** | Escalation | P0-P3 classifications, incident commander roles, Post-Incident Review (PIR) |
| File | Purpose | Key Contents |
| ------------------------------------------------------------------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **[04-01-docker-compose.md](./04-01-docker-compose.md)** | Core Environment Setup | `.env` configs, Blue/Green Docker Compose, MariaDB & Redis optimization, **Appendix A: Live QNAP configs** (MariaDB, Redis/ES, NPM, Gitea, n8n) |
| **[04-02-backup-recovery.md](./04-02-backup-recovery.md)** | Disaster Recovery | RTO/RPO strategies, QNAP to ASUSTOR backup scripts, Restic/Mysqldump config |
| **[04-03-monitoring.md](./04-03-monitoring.md)** | Observability | Prometheus metrics, AlertManager rules, Grafana alerts |
| **[04-04-deployment-guide.md](./04-04-deployment-guide.md)** | Production Rollout | Blue-Green deployment scripts, **Appendix A: QNAP Container Station**, **Appendix B: Gitea Actions CI/CD**, **Appendix C: act_runner setup** |
| **[04-05-maintenance-procedures.md](./04-05-maintenance-procedures.md)** | Routine Care | Log rotation, dependency updates, scheduled DB optimizations |
| **[04-06-security-operations.md](./04-06-security-operations.md)** | Hardening & Audit | User access review, SSL renewals, vulnerability scanning, **Appendix A: SSH Setup**, **Appendix B: Secrets Management** |
| **[04-07-incident-response.md](./04-07-incident-response.md)** | Escalation | P0-P3 classifications, incident commander roles, Post-Incident Review |
### 🐳 Live Docker Compose Files (QNAP)
| File | Application | Path on QNAP |
| ------------------------------------------------------ | ---------------------------------------------- | ----------------------------- |
| **[docker-compose-app.yml](./docker-compose-app.yml)** | `lcbp3-app` (backend + frontend) | `/share/np-dms/app/` |
| **[lcbp3-monitoring.yml](./lcbp3-monitoring.yml)** | `lcbp3-monitoring` (Prometheus, Grafana, etc.) | `/volume1/np-dms/monitoring/` |
| **[lcbp3-registry.yml](./lcbp3-registry.yml)** | `lcbp3-registry` (Docker Registry) | `/volume1/np-dms/registry/` |
| **[grafana/](./grafana/)** | Grafana dashboard JSON configs | Imported via Grafana UI |
---

View File

@@ -1,148 +0,0 @@
# การตั้งค่า Redis และ Elasticsearch
---
## **📝 คำอธิบายและข้อควรพิจารณา**
* 1 Redis (Service: cache)
* Image: redis:7-alpine มีขนาดเล็กและทันสมัย
* Port: expose port 6379 ออกมาที่ Host QNAP เพื่อให้ Uptime Kuma (ASUSTOR) สามารถ monitor ได้ — Backend (NestJS) ยังคงคุยผ่าน 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 QNAP เพื่อให้ Uptime Kuma (ASUSTOR) และ Prometheus สามารถ monitor ได้
* 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/np-dms/services/cache/data
mkdir -p /share/np-dms/services/search/data
# กำหนดสิทธิ์ให้ตรงกับ User ID ใน Container
# Redis (UID 999)
chown -R 999:999 /share/np-dms/services/cache/data
chmod -R 750 /share/np-dms/services/cache/data
# Elasticsearch (UID 1000)
chown -R 1000:1000 /share/np-dms/services/search/data
chmod -R 750 /share/np-dms/services/search/data
```
## Docker file
```yml
# File: /share/np-dms/services/docker-compose.yml (หรือไฟล์ที่คุณใช้รวม)
# DMS Container v1_7_0: เพิ่ม 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"
ports:
- "6379:6379"
networks:
- lcbp3
volumes:
- "/share/np-dms/services/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"
ports:
- "9200:9200"
networks:
- lcbp3
volumes:
- "/share/np-dms/services/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
```

View File

@@ -1,201 +0,0 @@
# Secrets Management สำหรับ LCBP3-DMS
> 📍 **Version:** v1.8.0
> ⚠️ **Security Level:** CONFIDENTIAL
---
## Overview
เอกสารนี้อธิบายวิธีการจัดการ Secrets และ Sensitive Data สำหรับ LCBP3-DMS
---
## 1. Secret Categories
| Category | Examples | Storage Location |
| :------------------- | :----------------------------- | :----------------------- |
| Database Credentials | `MYSQL_ROOT_PASSWORD` | `.env` file (gitignored) |
| API Keys | `JWT_SECRET`, `REDIS_PASSWORD` | `.env` file (gitignored) |
| SSL Certificates | Let's Encrypt certs | NPM volume |
| SSH Keys | Backup access keys | ASUSTOR secure storage |
---
## 2. Environment File Structure
### 2.1 Main Environment File
```bash
# File: /share/np-dms/.env (QNAP)
# ⚠️ This file MUST be in .gitignore
# === Database ===
MYSQL_ROOT_PASSWORD=<strong-password>
MYSQL_DATABASE=lcbp3_db
MYSQL_USER=lcbp3_user
MYSQL_PASSWORD=<strong-password>
# === Redis ===
REDIS_PASSWORD=<strong-password>
# === Application ===
JWT_SECRET=<random-256-bit-string>
SESSION_SECRET=<random-256-bit-string>
# === Monitoring ===
GRAFANA_PASSWORD=<admin-password>
# === External Services ===
LINE_CHANNEL_SECRET=<line-secret>
LINE_CHANNEL_ACCESS_TOKEN=<line-token>
SMTP_PASSWORD=<email-password>
```
### 2.2 Docker Compose Override (Optional)
```yaml
# File: /share/np-dms/docker-compose.override.yml
# For additional local development secrets
services:
backend:
environment:
- DEBUG_MODE=true
- LOG_LEVEL=debug
```
---
## 3. Secret Generation
### 3.1 Generate Strong Passwords
```bash
# Generate random 32-character password
openssl rand -base64 32
# Generate random hex string (for JWT)
openssl rand -hex 64
```
### 3.2 Recommended Password Policy
| Type | Length | Characters | Example Tool |
| :--------- | :----- | :--------------------- | :------------------------ |
| Database | 24+ | Alphanumeric + symbols | `openssl rand -base64 32` |
| JWT Secret | 64+ | Hex | `openssl rand -hex 64` |
| API Keys | 32+ | Alphanumeric | `openssl rand -base64 32` |
---
## 4. Secret Rotation
### 4.1 Rotation Schedule
| Secret Type | Rotation Period | Impact on Services |
| :---------------- | :------------------- | :--------------------- |
| JWT Secret | 90 days | Users need to re-login |
| Database Password | 180 days | Requires restart |
| Redis Password | 180 days | Requires restart |
| SSL Certificates | Auto (Let's Encrypt) | None |
### 4.2 Rotation Procedure
```bash
# 1. Update .env file with new secret
nano /share/np-dms/.env
# 2. Restart affected services
docker-compose up -d --force-recreate backend
# 3. Verify services are running
docker ps
curl https://backend.np-dms.work/health
```
---
## 5. Access Control
### 5.1 Who Has Access
| Role | .env Access | Server SSH | Backup Access |
| :----------- | :---------- | :--------- | :------------ |
| System Admin | ✅ Full | ✅ Full | ✅ Full |
| DevOps | ✅ Read | ✅ Limited | ❌ None |
| Developer | ❌ None | ❌ None | ❌ None |
### 5.2 Audit Logging
```bash
# View SSH login attempts
tail -100 /var/log/auth.log
# Monitor file access
auditctl -w /share/np-dms/.env -p rwa -k secrets_access
```
---
## 6. Emergency Procedures
### 6.1 Secret Compromised
1. **Immediately** rotate the compromised secret
2. **Check** access logs for unauthorized access
3. **Notify** security team
4. **Document** incident
### 6.2 Lost Access to Secrets
1. Contact QNAP Admin for direct access
2. Use backup `.env` from ASUSTOR (encrypted)
3. If both unavailable, regenerate all secrets and reset passwords
---
## 7. Backup of Secrets
```bash
# Encrypted backup of .env (run on QNAP)
gpg --symmetric --cipher-algo AES256 \
/share/np-dms/.env -o /tmp/env.gpg
# Copy to ASUSTOR
scp /tmp/env.gpg admin@192.168.10.9:/volume1/backup/secrets/
# Clean up
rm /tmp/env.gpg
```
### 7.1 Restore from Backup
```bash
# Copy from ASUSTOR
scp admin@192.168.10.9:/volume1/backup/secrets/env.gpg /tmp/
# Decrypt
gpg --decrypt /tmp/env.gpg > /share/np-dms/.env
# Clean up
rm /tmp/env.gpg
```
---
## 8. Checklist
- [ ] `.env` file exists and is configured
- [ ] `.env` is in `.gitignore`
- [ ] All passwords are strong (24+ characters)
- [ ] JWT secret is 64+ hex characters
- [ ] Encrypted backup of secrets exists on ASUSTOR
- [ ] Access control is properly configured
- [ ] Rotation schedule is documented
---
> ⚠️ **Security Warning**: ห้ามเก็บ secrets ใน version control หรือ commit ไปยัง Git repository
>
> 📝 **หมายเหตุ**: เอกสารนี้อ้างอิงจาก Architecture Document **v1.8.0**

View File

@@ -1,256 +0,0 @@
# การ Deploy Application (Backend + Frontend) บน QNAP
> 📍 **Version:** v1.7.0
> 🖥️ **Server:** QNAP TS-473A (Container Station)
> 🔗 **Docker Compose Path:** `/share/np-dms/app/docker-compose.yml`
---
## 📋 Prerequisites
ก่อน deploy ต้องมี services เหล่านี้รันอยู่แล้ว:
| Service | Container Name | Docker Compose | Status |
| :------------- | :------------- | :--------------------------------- | :----- |
| MariaDB | `mariadb` | `lcbp3-db` (MariaDB_setting.md) | ✅ |
| Redis | `cache` | `services` (04_Service_setting.md) | ✅ |
| Elasticsearch | `search` | `services` (04_Service_setting.md) | ✅ |
| NPM | `npm` | `lcbp3-npm` (NPM_setting.md) | ✅ |
| Docker Network | `lcbp3` | `docker network create lcbp3` | ✅ |
---
## 1. Build Docker Images
### Option A: Build บน Dev Machine (Windows) แล้ว Transfer
```powershell
# อยู่ที่ workspace root (nap-dms.lcbp3/)
# Build Backend
docker build -f backend/Dockerfile -t lcbp3-backend:latest .
# Build Frontend (NEXT_PUBLIC_API_URL bake เข้าไปตอน build)
docker build -f frontend/Dockerfile `
--build-arg NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api `
-t lcbp3-frontend:latest .
# Export เป็น .tar เพื่อ Transfer
docker save lcbp3-backend:latest -o lcbp3-backend.tar
docker save lcbp3-frontend:latest -o lcbp3-frontend.tar
# Transfer ไปยัง QNAP (ผ่าน SMB Shared Folder)
# Copy lcbp3-backend.tar และ lcbp3-frontend.tar ไปที่ \\192.168.10.8\np-dms\app\
```
### Option B: Build บน QNAP โดยตรง (SSH)
```bash
# SSH เข้า QNAP
ssh admin@192.168.10.8
# Clone หรือ Pull code จาก Gitea
cd /share/np-dms/app/source
git pull origin main
# Build images
docker build -f backend/Dockerfile -t lcbp3-backend:latest .
docker build -f frontend/Dockerfile \
--build-arg NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api \
-t lcbp3-frontend:latest .
```
---
## 2. Load Images บน QNAP (เฉพาะ Option A)
```bash
# SSH เข้า QNAP
ssh admin@192.168.10.8
# Load images
docker load < /share/np-dms/app/lcbp3-backend.tar
docker load < /share/np-dms/app/lcbp3-frontend.tar
# ตรวจสอบ
docker images | grep lcbp3
```
---
## 3. สร้าง Directories และกำหนดสิทธิ์
```bash
# สร้าง directories สำหรับ volumes
mkdir -p /share/np-dms/data/uploads/temp
mkdir -p /share/np-dms/data/uploads/permanent
mkdir -p /share/np-dms/data/logs/backend
mkdir -p /share/np-dms/app
# กำหนดสิทธิ์ให้ non-root user ใน container (UID 1001)
chown -R 1001:1001 /share/np-dms/data/uploads
chown -R 1001:1001 /share/np-dms/data/logs/backend
chmod -R 750 /share/np-dms/data/uploads
```
---
## 4. Deploy ผ่าน Container Station
### 4.1 Copy docker-compose.yml
คัดลอกไฟล์ `specs/08-infrastructure/docker-compose-app.yml` ไปยัง QNAP:
```bash
# วางไฟล์ที่ path
/share/np-dms/app/docker-compose.yml
```
### 4.2 สร้าง Application ใน Container Station
1. เปิด **Container Station** บน QNAP Web UI
2. ไปที่ **Applications****Create**
3. เลือก **Create Application**
4. ตั้งชื่อ Application: `lcbp3-app`
5. วาง (Paste) เนื้อหาจาก `docker-compose-app.yml`
6. แก้ไข Environment Variables ตามต้องการ (โดยเฉพาะ Secrets)
7. กด **Create** เพื่อ deploy
> ⚠️ **สำคัญ:** ตรวจสอบ environment variables ก่อน deploy:
> - `DB_PASSWORD` — Password ของ MariaDB
> - `REDIS_PASSWORD` — Password ของ Redis
> - `JWT_SECRET` — Secret key สำหรับ JWT Tokens
> - `AUTH_SECRET` — Secret key สำหรับ NextAuth
### 4.3 ตรวจสอบ Container Status
ใน Container Station → Applications → `lcbp3-app`:
-`backend` — Status: **Running** (healthy)
-`frontend` — Status: **Running** (healthy)
---
## 5. Verify Deployment
### ตรวจสอบ Health
```bash
# Backend health (จากภายใน Docker network)
docker exec frontend wget -qO- http://backend:3000/health
# Frontend (ผ่าน NPM)
curl -I https://lcbp3.np-dms.work
# Backend API (ผ่าน NPM)
curl -I https://backend.np-dms.work/api
```
### ตรวจสอบ Logs
```bash
# ดู logs ใน Container Station UI
# หรือผ่าน CLI:
docker logs -f backend
docker logs -f frontend
```
---
## 6. Update / Re-deploy
เมื่อต้องการ deploy version ใหม่:
```powershell
# 1. Build images ใหม่ (บน Dev Machine - PowerShell)
docker build -f backend/Dockerfile -t lcbp3-backend:latest .
docker build -f frontend/Dockerfile `
--build-arg NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api `
-t lcbp3-frontend:latest .
# 2. Export & Transfer
docker save lcbp3-backend:latest -o lcbp3-backend.tar
docker save lcbp3-frontend:latest -o lcbp3-frontend.tar
# Copy ไปที่ \\192.168.10.8\np-dms\app\ ผ่าน SMB Shared Folder
# 3. Load บน QNAP (SSH)
ssh admin@192.168.10.8
docker load < /share/np-dms/app/lcbp3-backend.tar
docker load < /share/np-dms/app/lcbp3-frontend.tar
# 4. Restart ใน Container Station
# Applications → lcbp3-app → Restart
```
---
## 7. Automated Deployment via Gitea (CI/CD)
ระบบใช้ **Gitea Actions** เพื่อทำ CI/CD โดยจะทำงานอัตโนมัติเมื่อมีการ `push` เข้าสู่สาขา `main` หรือเรียกใช้งานแบบ `manual`
### 7.1 การตั้งค่า Gitea Secrets
เพื่อให้ Pipeline สามารถเชื่อมต่อกับ QNAP ผ่าน SSH ได้อย่างปลอดภัย ต้องตั้งค่า Secrets ที่ **Gitea Web UI**:
1. เข้าไปที่ Repository: `np-dms/lcbp3`
2. ไปที่ **Settings****Actions****Secrets**
3. กด **Add New Secret** สำหรับค่าต่อไปนี้:
| Secret Name | Value Example | Description |
| :---------- | :------------- | :--------------------------------------- |
| `HOST` | `192.168.10.8` | IP ภายในของ QNAP (VLAN 10) |
| `PORT` | `22` | SSH Port ของ QNAP |
| `USERNAME` | `admin` | User ที่มีสิทธิ์รัน Docker |
| `PASSWORD` | `********` | รหัสผ่าน SSH (แนะนำให้ใช้ SSH Key แทนในอนาคต) |
### 7.2 โครงสร้าง Pipeline (`deploy.yaml`)
ไฟล์ตั้งค่าอยู่ที่ [`.gitea/workflows/deploy.yaml`](file://../../.gitea/workflows/deploy.yaml) โดยมีขั้นตอนหลักดังนี้:
```mermaid
graph TD
A[Push to main] --> B[Gitea Runner Pick up Task]
B --> C[SSH to QNAP]
C --> D[git pull latest code]
D --> E[Build Backend Image]
E --> F[Build Frontend Image]
F --> G[docker-compose up -d]
G --> H[Cleanup Unused Images]
H --> I[Finish Deploy]
```
### 7.3 วิธีการรันแบบ Manual (Manual Trigger)
หากต้องการ Re-deploy โดยไม่ต้องแก้โค้ด:
1. ไปที่แถบเมนู **Actions** (อยู่ข้างๆ Pull Requests ที่เมนูด้านบนสุด **ไม่ใช่ในเมนู Settings**)
2. ทางซ้ายมือจะเห็นรายการ Workflow ให้เลือก **"Build and Deploy"**
3. หากมีไฟล์ `.yaml` ถูกต้อง จะมีปุ่ม **Run workflow** ปรากฏขึ้นมา (สีฟ้า/น้ำเงิน)
4. เลือก Branch `main` -> กด **Run workflow** เพื่อเริ่มทำงาน
### 7.4 ตัวอย่างข้อความแจ้งเตือนและการแก้ไขปัญหา (Troubleshooting)
* **❌ No matching online runner with label: ubuntu-latest:**
* **สาเหตุ:** แปลว่าในหน้า Settings -> Actions -> Runners **ไม่มี Runner ที่มีสถานะ Online และมีป้ายกำกับ (Label) ว่า `ubuntu-latest`** ครับ
* **วิธีแก้ 1:** ไปที่เมนู Settings -> Actions -> Runners กดแก้ไข Runner แล้วเพิ่ม Label `ubuntu-latest` เข้าไป (ถ้ามี Runner อยู่แล้ว)
* **วิธีแก้ 2:** แก้ไขไฟล์ `.gitea/workflows/deploy.yaml` ในบรรทัดที่ 11 จาก `runs-on: ubuntu-latest` ให้ตรงกับ **Label** ของ Runner ที่คุณมีจริง (เช่น `self-hosted` หรือชื่อที่คุณตั้งไว้)
* **📂 Paths:** ตรวจสอบว่า Code อยู่ในพาธที่ถูกต้อง (ปัจจุบันใช้ `/share/np-dms/app/source/lcbp3`)
* **SSH Timeout:** ตรวจสอบว่า QNAP เปิด SSH Service และ Firewall (ACL) อนุญาตให้เครื่องที่เป็น Runner เชื่อมต่อมายัง Port 22 ได้
* **Disk Full:** หาก Build ไม่ผ่านบ่อยๆ ให้รัน `docker image prune -a` บน QNAP เพื่อล้าง Image เก่าออก
### 7.5 การตรวจสอบ Gitea Runner (สำคัญมาก ⚠️)
จากรูปที่คุณส่งมา **Runners Management (Total: 0)** หมายความว่ายังไม่มีเครื่องที่จะมารันคำสั่ง Deploy ให้ครับ
* **ปัญหา:** แม้จะกด Run workflow ได้ แต่สถานะจะค้างที่ "Waiting" ตลอดไป
* **วิธีแก้:** ต้องทำการติดตั้งและ Register **Gitea Runner** (หรือ `act_runner`) ตามคู่มือนี้ครับ: [10_gitea_runner.md](10_gitea_runner.md)
* **ผลลัพธ์:** เมื่อติดตั้งสำเร็จ สถานะในหน้า Settings จะขึ้น **Total: 1** หรือมากกว่า และเป็นสีเขียวครับ
### 7.6 ขั้นตอนการตรวจสอบผลการ Deploy
เมื่อมีการรันแล้ว สามารถดูสถานะได้ที่:
1. คลิกที่รายการในหน้า **Actions** (หน้าหลักของ Actions)
2. จะเห็น Log รายละเอียดแต่ละ Step (Build Backend, Build Frontend, etc.) หากผ่านจะเป็นสีเขียวทั้งหมด
---
## 📦 Resource Summary
| Service | Image | CPU Limit | Memory Limit | Port |
| :----------- | :---------------------- | :-------- | :----------- | :--- |
| **backend** | `lcbp3-backend:latest` | 2.0 | 1.5 GB | 3000 |
| **frontend** | `lcbp3-frontend:latest` | 2.0 | 2 GB | 3000 |
> 📖 NPM Proxy Hosts ตั้งค่าเรียบร้อยแล้ว:
> - `lcbp3.np-dms.work` → `frontend:3000`
> - `backend.np-dms.work` → `backend:3000`

View File

@@ -1,74 +0,0 @@
# การติดตั้ง Gitea Actions Runner (act_runner) บน ASUSTOR
คู่มือนี้สำหรับติดตั้ง **act_runner** บน ASUSTOR เพื่อเชื่อมต่อกับ Gitea ที่รันอยู่บน QNAP
> ⚠️ **Note:** Gitea อยู่บน **QNAP** แต่ Runner อยู่บน **ASUSTOR** ตามหลัก Server Role Separation
> (QNAP = Application, ASUSTOR = Infrastructure)
## 🏗️ โครงสร้างการติดตั้ง
* **Platform:** ASUSTOR AS5403T (Infrastructure Server)
* **Method:** Portainer Stack หรือ Docker Compose
* **Path:** `/volume1/np-dms/gitea-runner/`
---
## 🚀 ขั้นตอนการติดตั้ง
### 1. รับ Registration Token
1. เข้า Gitea Web UI (`https://git.np-dms.work`) ไปที่ **Site Administration** -> **Actions** -> **Runners**
2. กดปุ่ม **Create new Runner**
3. คัดลอก **Registration Token** มาเก็บไว้
### 2. เตรียม Directory บน ASUSTOR
```bash
# SSH เข้า ASUSTOR
ssh admin@192.168.10.9
# สร้างโฟลเดอร์เก็บข้อมูล
mkdir -p /volume1/np-dms/gitea-runner/data
```
### 3. สร้าง Docker Compose
สร้างไฟล์ `/volume1/np-dms/gitea-runner/docker-compose.yml` หรือใช้ Portainer Stack:
```yaml
# File: /volume1/np-dms/gitea-runner/docker-compose.yml
# Deploy on: ASUSTOR AS5403T
# เชื่อมต่อกับ Gitea บน QNAP ผ่าน Domain URL
version: "3.8"
services:
runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: always
environment:
# ใช้ Domain URL เพื่อเชื่อมต่อ Gitea ข้ามเครื่อง (QNAP)
- GITEA_INSTANCE_URL=https://git.np-dms.work
- GITEA_RUNNER_REGISTRATION_TOKEN=คัดลอก_TOKEN_มาวางที่นี่
- GITEA_RUNNER_NAME=asustor-runner
# Label ต้องตรงกับ runs-on ใน deploy.yaml
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:18-bullseye,self-hosted:docker://node:18-bullseye
volumes:
- /volume1/np-dms/gitea-runner/data:/data
- /var/run/docker.sock:/var/run/docker.sock
```
### 4. สั่งรัน Runner
```bash
cd /volume1/np-dms/gitea-runner
docker compose up -d
```
---
## 🔍 การตรวจสอบภายหลังการติดตั้ง
1. กลับไปที่หน้า **Settings -> Actions -> Runners** ใน Gitea (QNAP)
2. สถานะควรเปลี่ยนเป็น **Total: 1** และมีจุดสีเขียวหน้า `asustor-runner`
3. ลองกด **Run workflow** ในแถบ Actions เพื่อทดสอบ
## ⚠️ ข้อควรระวัง
* **Network:** ASUSTOR ต้องเข้าถึง `https://git.np-dms.work` ได้ (ผ่าน DNS/NPM)
* **Disk Cleanup:** รัน `docker image prune -a` เป็นระยะเพื่อลบ cache images เก่า

View File

@@ -1,224 +0,0 @@
# การติดตั้ง Rocket.Chat บน QNAP
> 📍 **Version:** v1.0.0 (Chat Service)
> 🖥️ **Server:** QNAP TS-473A (Container Station)
> 🔗 **Docker Compose Path:** `/share/np-dms/rocketchat/docker-compose.yml`
> 🌐 **Domain:** `chat.np-dms.work`
---
## 📋 Prerequisites
ก่อนติดตั้ง ต้องมั่นใจว่า:
1. **Docker Network** `lcbp3` ถูกสร้างแล้ว (ตรวจสอบด้วย `docker network ls`)
2. **Nginx Proxy Manager (NPM)** รันอยู่เพื่อจัดการ SSL และ Domain
---
## 1. เตรียม Directories
สร้าง folder สำหรับเก็บข้อมูลเพื่อให้ข้อมูลไม่หายเมื่อลบ container:
```bash
# SSH เข้า QNAP
ssh admin@192.168.10.8
# สร้าง directories
mkdir -p /share/np-dms/rocketchat/uploads
mkdir -p /share/np-dms/rocketchat/data/db
mkdir -p /share/np-dms/rocketchat/data/dump
# Permissions:
# MongoDB ใน Docker ปกติใช้ uid 999 หรือ root, Rocket.Chat ใช้ uid 1000 หรือ root
# การสร้าง folder ผ่าน ssh admin ปกติจะเป็น admin:administrators
```
---
## 2. Docker Compose Configuration
สร้างไฟล์ `docker-compose.yml` ที่ `/share/np-dms/rocketchat/docker-compose.yml`:
```yml
x-restart: &restart_policy
restart: unless-stopped
x-logging: &default_logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
services:
mongodb:
<<: [*restart_policy, *default_logging]
image: docker.io/library/mongo:7.0
container_name: mongodb
command: mongod --oplogSize 128 --replSet rs0 --bind_ip_all
volumes:
- /share/np-dms/rocketchat/data/db:/data/db
- /share/np-dms/rocketchat/data/dump:/dump
deploy:
resources:
limits:
cpus: "1.0"
memory: 1G
networks:
- lcbp3
# Service สำหรับ Init Replica Set อัตโนมัติ (รันแล้วจบ)
mongo-init-replica:
image: docker.io/library/mongo:7.0
command: >
bash -c "for i in `seq 1 30`; do
mongosh --host mongodb --eval 'rs.initiate({ _id: \"rs0\", members: [ { _id: 0, host: \"mongodb:27017\" } ] })' && break;
sleep 1;
done"
depends_on:
- mongodb
networks:
- lcbp3
rocketchat:
<<: [*restart_policy, *default_logging]
image: registry.rocket.chat/rocketchat/rocket.chat:latest
container_name: rocketchat
volumes:
- /share/np-dms/rocketchat/uploads:/app/uploads
environment:
- PORT=3000
- ROOT_URL=https://chat.np-dms.work
- MONGO_URL=mongodb://mongodb:27017/rocketchat?replicaSet=rs0
- MONGO_OPLOG_URL=mongodb://mongodb:27017/local?replicaSet=rs0
- DEPLOY_METHOD=docker
- ACCOUNTS_AVATAR_STORE_PATH=/app/uploads
deploy:
resources:
limits:
cpus: "1.0"
memory: 1G
depends_on:
- mongodb
networks:
- lcbp3
expose:
- "3000"
networks:
lcbp3:
external: true
```
> **📝 Note:**
> - **MongoDB Replica Set (`rs0`):** จำเป็นสำหรับ Rocket.Chat เพื่อใช้ Oplog
> - **Expose:** เราเปิด port 3000 ภายใน network `lcbp3` เท่านั้น ไม่ expose ออก host โดยตรง เพื่อความปลอดภัยและให้ผ่าน NPM
> - **Resources:** กำหนด CPU/Memory Limit เพื่อป้องกันไม่ให้กินทรัพยากรเครื่อง QNAP มากเกินไป
---
## 3. Deployment
1. ไปที่ **Container Station** บน QNAP
2. เลือกเมนู **Applications** -> **Create**
3. ตั้งชื่อ Application: `lcbp3-chat`
4. วาง Code จาก `docker-compose.yml` ด้านบนลงไป
5. Check Env Variable Validations
6. กด **Create**
---
## 4. Nginx Proxy Manager (NPM) Setup
ต้องตั้งค่า Proxy Host ที่ NPM เพื่อให้เข้าใช้งานผ่าน `https://chat.np-dms.work` ได้
1. Login **NPM Admin** (`https://npm.np-dms.work`)
2. ไปที่ **Hosts** -> **Proxy Hosts** -> **Add Proxy Host**
3. **Details Tab:**
* **Domain Names:** `chat.np-dms.work`
* **Scheme:** `http`
* **Forward Hostname:** `rocketchat` (ชื่อ service ใน docker-compose)
* **Forward Port:** `3000`
* **Cache Assets:**
* **Block Common Exploits:**
* **Websockets Support:** ✅ (⚠️ สำคัญมากสำหรับ Real-time chat)
4. **SSL Tab:**
* **SSL Certificate:** Request a new SSL Certificate (Let's Encrypt) หรือใช้ Wildcard เดิมที่มี
* **Force SSL:**
* **HTTP/2 Support:**
5. กด **Save**
---
## 5. Verification
1. เปิด Browser เข้าไปที่ `https://chat.np-dms.work`
2. จะพบหน้า **Setup Wizard**
3. กรอกข้อมูล Admin และ Organization เพื่อเริ่มใช้งาน
---
## 6. Configuration & Initial Setup
### 6.1 Setup Wizard (First Run)
เมื่อเข้าสู่ระบบครั้งแรก จะพบกับ **Setup Wizard** ให้กรอกข้อมูลดังนี้:
1. **Admin Info:**
* **Name:** Administrator (หรือชื่อผู้ดูแลระบบ)
* **Username:** `admin` (แนะนำให้เปลี่ยนเพื่อความปลอดภัย)
* **Email:** `admin@np-dms.work`
* **Password:** (ตั้งรหัสผ่านที่ซับซ้อนและบันทึกใน Secrets Management)
2. **Organization Info:**
* **Organization Type:** Government / Public Sector
* **Organization Name:** Laem Chabang Port Phase 3
* **Industry:** Construction / Infrastructure
* **Size:** 51-100 (หรือตามจริง)
* **Country:** Thailand
3. **Server Info:**
* **Site Name:** LCBP3 DMS Chat
* **Language:** English / Thai
* **Server Type:** Private Team
* **2FA:** แนะนำให้เปิด Two Factor Authentication (Optional)
4. **Register Server:**
* **Standalone:** เลือก "Keep standalone" หากต้องการความเป็นส่วนตัวสูงสุด (Privacy-first / Air-gapped)
* **Registered:** หากต้องการใช้ Mobile App Push Notification Gateway ของ Rocket.Chat (ฟรีจำกัดจำนวน)
> **💡 Tip:** หากเลือก Standalone จะไม่มี Push Notification ไปยังมือถือ (iOS/Android) แต่ยังใช้งานผ่าน Browser และ Desktop App ได้ปกติ
### 6.2 Post-Installation Settings
หลังจาก Setup เสร็จสิ้น แนะนำให้ตรวจสอบค่าเหล่านี้ใน **Administration**:
1. **General > Site URL:** ต้องเป็น `https://chat.np-dms.work`
2. **General > Force SSL:** ต้องเป็น `True`
3. **File Upload > File Upload:** เปิดใช้งาน
4. **File Upload > Max File Size:** ปรับตามนโยบาย (Default 2MB อาจน้อยไป แนะนำ 50MB+)
* *หมายเหตุ: ต้องสัมพันธ์กับ `client_max_body_size` ใน NPM ด้วย*
---
## 7. Maintenance
### Backup Strategy
ข้อมูลสำคัญจะอยู่ที่ Path บน QNAP:
* `/share/np-dms/rocketchat/data/db` (Database)
* `/share/np-dms/rocketchat/uploads` (Files)
ระบบ Backup (Restic บน ASUSTOR) ควรสั่ง backup folder `/share/np-dms/` ทั้งหมดอยู่แล้ว
### Troubleshooting
หากเข้าเว็บไม่ได้ หรือขึ้น 502 Bad Gateway:
1. เช็ค Logs Rocket.Chat: `docker logs -f rocketchat`
2. เช็ค Logs MongoDB: `docker logs -f mongodb` (ดูว่า Replica Set init หรือยัง)
3. เช็ค NPM: มั่นใจว่า Forward Hostname ถูกต้อง (`rocketchat` ต้องอยู่ใน network เดียวกันคือ `lcbp3`)
---
## 📦 Resource Summary
| Service | Image | CPU Limit | Memory Limit | Port |
| :------------- | :-------------------------------------------- | :-------- | :----------- | :---- |
| **mongodb** | `mongo:7.0` | 1.0 | 1 GB | 27017 |
| **rocketchat** | `registry.rocket.chat/rocketchat/rocket.chat` | 1.0 | 1 GB | 3000 |

View File

@@ -1,92 +0,0 @@
# การติดตั้ง Gitea ใน Docker
* user id ของ gites:
* uid=1000(git) gid=1000(git) groups=1000(git)
## กำหนดสิทธิ
```bash
chown -R 1000:1000 /share/np-dms/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/np-dms/git/etc/app.ini
chown -R 1000:1000 /share/np-dms/gitea/
ล้างสิทธิ์
setfacl -R -b /share/np-dms/gitea/
chgrp -R administrators /share/np-dms/gitea/
chown -R 1000:1000 /share/np-dms/gitea/etc /share/np-dms/gitea/lib /share/np-dms/gitea/backup
setfacl -m u:1000:rwx -m g:1000:rwx /share/np-dms/gitea/etc /share/np-dms/gitea/lib /share/np-dms/gitea/backup
```
## Docker file
```yml
# File: share/np-dms/git/docker-compose.yml
# DMS Container v1_7_0 : แยก 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/np-dms/gitea/backup:/backup
- /share/np-dms/gitea/etc:/etc/gitea
- /share/np-dms/gitea/lib:/var/lib/gitea
# ให้ repo root ใช้จาก /share/dms-data/gitea_repos
- /share/np-dms/gitea/gitea_repos:/var/lib/gitea/git/repositories
- /share/np-dms/gitea/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

@@ -1,176 +0,0 @@
# การติดตั้ง MAriaDB และ PHPMyAdmin ใน Docker
* user id ของ mariadb:
* uid=0(root) gid=0(root) groups=0(root)
## กำหนดสิทธิ
```bash
chown -R 999:999 /share/np-dms/mariadb
chmod -R 755 /share/np-dms/mariadb
setfacl -R -m u:999:rwx /share/np-dms/mariadb
setfacl -R -d -m u:999:rwx /share/np-dms/mariadb
chown -R 999:999 /share/np-dms/mariadb/init
chmod 755 /share/np-dms/mariadb/init
setfacl -R -m u:999:r-x /share/np-dms/mariadb/init
setfacl -R -d -m u:999:r-x /share/np-dms/mariadb/init
chown -R 33:33 /share/np-dms/pma/tmp
chmod 755 /share/np-dms/pma/tmp
setfacl -R -m u:33:rwx /share/np-dms/pma/tmp
setfacl -R -d -m u:33:rwx /share/np-dms/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/nap-dms/gitea
setfacl -R -m u:1000:rwx /share/nap-dms/gitea/gitea_repos
setfacl -R -m u:1000:rwx /share/nap-dms/gitea/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 exec -it mariadb mysql -u root -p
CREATE USER 'exporter'@'%' IDENTIFIED BY 'Center#2025' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
FLUSH PRIVILEGES;
```
## Docker file
```yml
# File: share/nap-dms/mariadb/docker-compose.yml
# DMS Container v1_7_0 : ย้าย folder ไปที่ share/nap-dms/
# 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"
networks:
- lcbp3
volumes:
- "/share/np-dms/mariadb/data:/var/lib/mysql"
- "/share/np-dms/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf:ro"
- "/share/np-dms/mariadb/init:/docker-entrypoint-initdb.d:ro"
- "/share/dms-data/mariadb/backup:/backup"
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
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"
networks:
- lcbp3
# expose:
# - "80"
volumes:
- "/share/np-dms/pma/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php:ro"
- "/share/np-dms/pma/zzz-custom.ini:/usr/local/etc/php/conf.d/zzz-custom.ini:ro"
- "/share/np-dms/pma/tmp:/var/lib/phpmyadmin/tmp:rw"
- "/share/dms-data/logs/pma:/var/log/apache2"
depends_on:
mariadb:
condition: service_healthy
networks:
lcbp3:
external: true
# chown -R 999:999 /share/np-dms/mariadb/init
# chmod 755 /share/np-dms/mariadb/init
# setfacl -R -m u:999:r-x /share/np-dms/mariadb/init
# setfacl -R -d -m u:999:r-x /share/np-dms/mariadb/init
# chown -R 33:33 /share/np-dms/pma/tmp
# chmod 755 /share/np-dms/pma/tmp
# setfacl -R -m u:33:rwx /share/np-dms/pma/tmp
# setfacl -R -d -m u:33:rwx /share/np-dms/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
# 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;
```

View File

@@ -1,101 +0,0 @@
# การติดตั้ง 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] | [ ] |
| chat.np-dms.work | rocketchat | 3000 | [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/np-dms/npm/docker-compose-npm.yml
# DMS Container v1_7_0 : ย้าย folder ไปที่ share/np-dms/
# 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/np-dms/npm/data:/data"
- "/share/dms-data/logs/npm:/data/logs" # <-- เพิ่ม logging volume
- "/share/np-dms/npm/letsencrypt:/etc/letsencrypt"
- "/share/np-dms/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/np-dms/npm/landing:/usr/share/nginx/html:ro"
networks:
- lcbp3
networks:
lcbp3:
external: true
giteanet:
external: true
name: gitnet
```

View File

@@ -1,379 +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:** v1.8.0 (aligned with 01-02-architecture.md)
---
## 🏢 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
| 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 |
| **Chat** | Rocket.Chat | **Standalone + MongoDB RS** | 2.0 CPU / 2GB 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 |
| **CI/CD** | Gitea Runner | Automated Deployment (act_runner) |
---
## 🔄 Architecture Diagrams
> 📊 **ดู Diagrams แบบ Interactive (Mermaid) ได้ที่:** [Network_daigram.md](Network_daigram.md)
>
> เอกสารนี้รวม Diagrams หลักไว้ได้แก่:
> - **Data Flow Diagram** - การไหลของข้อมูลระหว่าง Services
> - **Docker Management View** - การจัดการ Containers ผ่าน Portainer
> - **Security Zones Diagram** - การแบ่ง Security Zones (Public, App, Data, Infra)
> - **Network Flow Diagram** - การเชื่อมต่อ VLANs และ Firewall Rules
---
## 🌐 Network Architecture (VLAN)
### VLAN Networks
| VLAN ID | Name | Gateway/Subnet | DHCP Range | Purpose |
| :------ | :----- | :-------------- | :----------------- | :-------------------- |
| 10 | SERVER | 192.168.10.1/24 | Static | Servers (NAS, Docker) |
| 20 | MGMT | 192.168.20.1/24 | Static | Network Management |
| 30 | USER | 192.168.30.1/24 | .10-.254 (7 days) | Staff Devices |
| 40 | CCTV | 192.168.40.1/24 | .100-.150 (7 days) | Surveillance |
| 50 | VOICE | 192.168.50.1/24 | .201-.250 (7 days) | IP Phones |
| 60 | DMZ | 192.168.60.1/24 | Static | Public Services |
| 70 | GUEST | 192.168.70.1/24 | .200-.250 (1 day) | Guest WiFi |
### Static IP Allocation (Key Devices)
| VLAN | Device | IP Address | Role |
| :--------- | :------- | :---------------------------- | :------------------ |
| SERVER(10) | QNAP | 192.168.10.8 | App/DB Server |
| SERVER(10) | ASUSTOR | 192.168.10.9 | Infra/Backup Server |
| MGMT(20) | ER7206 | 192.168.20.1 | Gateway/Router |
| MGMT(20) | SG2428P | 192.168.20.2 | Core Switch |
| MGMT(20) | AMPCOM | 192.168.20.3 | Server Switch |
| MGMT(20) | OC200 | 192.168.20.250 | Omada Controller |
| USER(30) | Printer | 192.168.30.222 | Kyocera CS3554ci |
| CCTV(40) | NVR | 192.168.40.200 | HikVision NVR |
| VOICE(50) | IP Phone | 192.168.50.211-192.168.50.221 | IP Phone |
### Network Equipment
| Device | Model | Ports | IP Address | Role |
| :------------------ | :----------------- | :---------------------- | :------------- | :--------------- |
| **Router** | TP-LINK ER7206 | 1 SFP + WAN + 4×GbE | 192.168.20.1 | Gateway/Firewall |
| **Core Switch** | TP-LINK SG2428P | 24×GbE PoE+ + 4×SFP | 192.168.20.2 | Core/PoE Switch |
| **Server Switch** | AMPCOM | 8×2.5GbE + 1×10G SFP+ | 192.168.20.3 | Server Uplink |
| **Admin Switch** | TP-LINK ES205G | 5×GbE (Unmanaged) | N/A | Admin PC |
| **CCTV Switch** | TP-LINK TL-SL1226P | 24×PoE+ 100Mbps + 2×SFP | 192.168.20.4 | CCTV PoE |
| **IP Phone Switch** | TP-LINK SG1210P | 8×PoE+ + 1×GbE + 1×SFP | 192.168.20.5 | VoIP |
| **Controller** | TP-LINK OC200 | Omada Controller | 192.168.20.250 | AP Management |
> 📖 Detailed port mappings and ACL rules: see [Securities.md](Securities.md) and [แผนผัง Network.md](แผนผัง%20Network.md)
---
## 🔗 Network Topology
```mermaid
graph TB
subgraph Internet
WAN[("🌐 Internet<br/>WAN")]
end
subgraph Router["ER7206 Router"]
R[("🔲 ER7206<br/>192.168.20.1")]
end
subgraph CoreSwitch["SG2428P Core Switch"]
CS[("🔲 SG2428P<br/>192.168.20.2")]
end
subgraph ServerSwitch["AMPCOM 2.5G Switch"]
SS[("🔲 AMPCOM<br/>192.168.20.3")]
end
subgraph Servers["VLAN 10 - Servers"]
QNAP[("💾 QNAP (App/DB)<br/>192.168.10.8")]
ASUSTOR[("💾 ASUSTOR (Infra)<br/>192.168.10.9")]
end
subgraph AccessPoints["EAP610 x16"]
AP[("📶 WiFi APs")]
end
subgraph OtherSwitches["Distribution"]
OC200[("🔲OC200<br/>Omada Controller")]
CCTV_SW[("🔲 TL-SL1226P<br/>CCTV Switch")]
PHONE_SW[("🔲 SG1210P<br/>IP Phone Switch")]
ADMIN_SW[("🔲 ES205G<br/>Admin Switch")]
end
WAN --> R
R -->|Port 3 - Port 1| CS
CS -->|LAG Port 3| SS
SS -->|Port 3-4 LACP| QNAP
SS -->|Port 5-6 LACP| ASUSTOR
CS -->|Port 5-20| AP
CS -->|Port 2| OC200
CS -->|SFP 25| CCTV_SW
CS -->|SFP 26| PHONE_SW
CS -->|Port 21| ADMIN_SW
```
---
## 📁 สารบัญเอกสาร
| ไฟล์ | คำอธิบาย |
| :--------------------------------------------------- | :--------------------------------------------------------------------------- |
| [Infrastructure Setup.md](Infrastructure%20Setup.md) | ภาพรวมการตั้งค่าโครงสร้างพื้นฐาน (Redis, MariaDB, Backend, Monitoring, Backup, DR) |
| [แผนผัง Network.md](แผนผัง%20Network.md) | แผนผัง Network Architecture และ Container Services |
| [Securities.md](Securities.md) | VLAN Segmentation, Firewall Rules, ACL (ER7206, SG2428P, EAP) |
---
## 🐳 Docker Compose Files
### Core Services (QNAP)
| ไฟล์ | Application | Services | Path บน QNAP |
| :----------------------------------------------- | :---------- | :---------------------------------------- | :------------------------ |
| [MariaDB_setting.md](MariaDB_setting.md) | `lcbp3-db` | `mariadb`, `pma` | `/share/np-dms/mariadb/` |
| [NPM_setting.md](NPM_setting.md) | `lcbp3-npm` | `npm`, `landing` | `/share/np-dms/npm/` |
| [Service_setting.md](Service_setting.md) | `services` | `cache` (Redis), `search` (Elasticsearch) | `/share/np-dms/services/` |
| [Gitea_setting.md](Gitea_setting.md) | `git` | `gitea` | `/share/np-dms/gitea/` |
| [n8n_setting.md](n8n_setting.md) | `n8n` | `n8n` | `/share/np-dms/n8n/` |
| [docker-compose-app.yml](docker-compose-app.yml) | `lcbp3-app` | `backend` (NestJS), `frontend` (Next.js) | `/share/np-dms/app/` |
### Infrastructure Services (ASUSTOR)
| ไฟล์ | Application | Services | Path บน ASUSTOR |
| :--------------------------------------------------- | :----------------- | :--------------------------------------------------- | :------------------------------ |
| [05_monitoring.md](05_monitoring.md) | `lcbp3-monitoring` | `prometheus`, `grafana`, `node-exporter`, `cadvisor` | `/volume1/np-dms/monitoring/` |
| [lcbp3-registry.yml](lcbp3-registry.yml) | `lcbp3-registry` | `registry` | `/volume1/np-dms/registry/` |
| [10_gitea_runner.md](10_gitea_runner.md) | `gitea-runner` | `act_runner` | `/volume1/np-dms/gitea-runner/` |
| [06_backup.md](06_backup.md) | `lcbp3-backup` | `restic`, Pull-based strategy | `/volume1/np-dms/backup/` |
| [07_disaster_recovery.md](07_disaster_recovery.md) | - | DR Plan, RTO/RPO Targets | - |
| [08_secrets_management.md](08_secrets_management.md) | - | Secrets & Credentials Management | - |
---
## 🌐 Domain Mapping (NPM Proxy)
### Application Domains (QNAP)
| Domain | Service | Port | Host | Description |
| :-------------------- | :--------- | :--- | :--- | :------------------------ |
| `lcbp3.np-dms.work` | frontend | 3000 | QNAP | Frontend Next.js |
| `backend.np-dms.work` | backend | 3000 | QNAP | Backend NestJS API |
| `pma.np-dms.work` | pma | 80 | QNAP | phpMyAdmin |
| `git.np-dms.work` | gitea | 3000 | QNAP | Gitea Git Server |
| `n8n.np-dms.work` | n8n | 5678 | QNAP | n8n Workflow Automation |
| `chat.np-dms.work` | rocketchat | 3000 | QNAP | Rocket.Chat Service |
| `npm.np-dms.work` | npm | 81 | QNAP | Nginx Proxy Manager Admin |
### Infrastructure Domains (ASUSTOR)
| Domain | Service | Port | Host | Description |
| :----------------------- | :---------- | :--- | :------ | :----------------- |
| `grafana.np-dms.work` | grafana | 3000 | ASUSTOR | Grafana Dashboard |
| `prometheus.np-dms.work` | prometheus | 9090 | ASUSTOR | Prometheus Metrics |
| `uptime.np-dms.work` | uptime-kuma | 3001 | ASUSTOR | Uptime Monitoring |
| `portainer.np-dms.work` | portainer | 9443 | ASUSTOR | Docker Management |
| `registry.np-dms.work` | registry | 5000 | ASUSTOR | Docker Registry |
---
## ⚙️ Core Services Summary
### QNAP Services (Application)
| Service | Technology | Port | Purpose |
| :---------------- | :----------------- | :----- | :------------------------------------------- |
| **Reverse Proxy** | NPM | 80/443 | SSL Termination, Domain Routing |
| **Backend API** | NestJS | 3000 | REST API, Business Logic, Workflow Engine |
| **Frontend** | Next.js | 3000 | Web UI (App Router, React, Tailwind, Shadcn) |
| **Database** | MariaDB 11.8 | 3306 | Primary Relational Database |
| **Cache** | Redis 7.2 | 6379 | Caching, Session, BullMQ |
| **Search** | Elasticsearch 8.11 | 9200 | Full-text Search |
| **Code Hosting** | Gitea | 3000 | Git Repository (Self-hosted) |
| **Workflow** | n8n | 5678 | Automation, Integrations (LINE, Email) |
### ASUSTOR Services (Infrastructure)
| Service | Technology | Port | Purpose |
| :--------------- | :-------------- | :--- | :---------------------------- |
| **Metrics** | Prometheus | 9090 | Metrics Collection |
| **Dashboard** | Grafana | 3000 | Visualization, Alerting |
| **Uptime** | Uptime Kuma | 3001 | Service Availability Monitor |
| **Registry** | Docker Registry | 5000 | Private Container Images |
| **Management** | Portainer | 9443 | Centralized Docker Management |
| **Host Metrics** | node-exporter | 9100 | CPU, Memory, Disk metrics |
| **Container** | cAdvisor | 8080 | Container resource metrics |
| **Backup** | Restic/Borg | N/A | Automated Backups |
---
## 🔧 Quick Reference
### Docker Commands (QNAP - Container Station)
```bash
# ดู containers ทั้งหมด
docker ps -a
# ดู logs
docker logs -f <container_name>
# เข้าไปใน container
docker exec -it <container_name> sh
# Restart service
docker restart <container_name>
```
### Docker Commands (ASUSTOR - Portainer)
```bash
# Remote Docker endpoint connection
# Configure via Portainer UI: Settings > Environments > Add Environment
# Direct SSH to ASUSTOR
ssh admin@192.168.10.9
# Portainer API (Optional)
curl -X GET https://portainer.np-dms.work/api/endpoints \
-H "X-API-Key: <your-api-key>"
```
### Network
```bash
# สร้าง external network (ครั้งแรก) - ต้องทำทั้ง 2 servers
# On QNAP:
docker network create lcbp3
# On ASUSTOR:
docker network create lcbp3
# ดู network
docker network ls
docker network inspect lcbp3
```
### MariaDB
```bash
# เข้า MySQL CLI (QNAP)
docker exec -it mariadb mysql -u root -p
# Backup database (QNAP -> ASUSTOR)
docker exec mariadb mysqldump -u root -p lcbp3 > backup.sql
# Copy to ASUSTOR via NFS/SCP
```
---
## ⚙️ Environment Variables
ตัวแปรสำคัญที่ใช้ร่วมกันทุก Service:
| Variable | Value | Description |
| :----------------------- | :------------- | :--------------------- |
| `TZ` | `Asia/Bangkok` | Timezone |
| `MYSQL_HOST` / `DB_HOST` | `mariadb` | MariaDB hostname |
| `MYSQL_PORT` / `DB_PORT` | `3306` | MariaDB port |
| `REDIS_HOST` | `cache` | Redis hostname |
| `ELASTICSEARCH_HOST` | `search` | Elasticsearch hostname |
> ⚠️ **Security Note:** Sensitive secrets (Password, Keys) ต้องใช้ `docker-compose.override.yml` (gitignored) หรือ Docker secrets - ห้ามระบุใน docker-compose.yml หลัก
---
## 📚 เอกสารเสริม
| ไฟล์ | คำอธิบาย |
| :------------------------------------------- | :-------------------------------------------------------- |
| [Git_command.md](Git_command.md) | คำสั่ง Git + Gitea Cheat Sheet |
| [lcbp3-db.md](lcbp3-db.md) | Docker Compose สำหรับ MariaDB (alternative version) |
| [09_app_deployment.md](09_app_deployment.md) | ขั้นตอน Deploy Backend + Frontend บน QNAP Container Station |
---
## 📋 Checklist สำหรับการติดตั้งใหม่
### Phase 1: Network & Infrastructure
1. [ ] Configure VLANs on ER7206 Router
2. [ ] Configure Switch Profiles on SG2428P
3. [ ] Configure Static IPs (QNAP: .8, ASUSTOR: .9)
### Phase 2: ASUSTOR Setup (Infra)
1. [ ] Create Docker Network: `docker network create lcbp3`
2. [ ] Deploy Portainer & Registry
3. [ ] Deploy Monitoring Stack (`prometheus.yml` with QNAP IP target)
4. [ ] Verify Prometheus can reach QNAP services
### Phase 3: QNAP Setup (App)
1. [ ] Create Docker Network: `docker network create lcbp3`
2. [ ] Create `.env` file with secure passwords
3. [ ] Deploy **MariaDB** (Wait for init)
4. [ ] Deploy **Redis Standalone** (Check AOF is active)
5. [ ] Deploy **Elasticsearch** (Check Heap limit)
6. [ ] Deploy **NPM** & App Services (Backend/Frontend)
7. [ ] Verify Internal Load Balancing (Backend Replicas)
### Phase 4: Backup & Security
1. [ ] Configure Restic on ASUSTOR to pull from QNAP
2. [ ] Set Resource Limits (Check `docker stats`)
3. [ ] Configure Firewall ACL Rules
---
> 📝 **หมายเหตุ:** เอกสารทั้งหมดอ้างอิงจาก Architecture Document **v1.8.0** และ DMS Container Schema **v1.7.0**

View File

@@ -1,219 +0,0 @@
# 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\<username>\.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` |

View File

@@ -1 +0,0 @@
{"web":{"client_id":"1028957954367-93vi8kcnim3m28mnaqjbclasjvfrbbgo.apps.googleusercontent.com","project_id":"gen-lang-client-0716462124","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["https://np-dms.cloudflareaccess.com/cdn-cgi/access/callback"],"javascript_origins":["https://np-dms.cloudflareaccess.com"]}}

View File

@@ -1,91 +0,0 @@
# การติดตั้ง n8n ใน Docker
* user id ของ gites:
* uid=1000(node) gid=1000(node) groups=1000(node)
## กำหนดสิทธิ
```bash
# สำหรับ n8n volumes
chown -R 1000:1000 /share/np-dms/n8n
chmod -R 755 /share/np-dms/n8n
```
## Docker file
```yml
# File: share/np-dms/n8n/docker-compose.yml
# DMS Container v1_7_0 แยก 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/np-dms/n8n:/home/node/.n8n"
- "/share/np-dms/n8n/cache:/home/node/.cache"
- "/share/np-dms/n8n/scripts:/scripts"
- "/share/np-dms/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
```