refactor(specs): merge 08-infrastructure into canonical 04-06 dirs
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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) |
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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**
|
||||
@@ -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`
|
||||
@@ -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 เก่า
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
```
|
||||
@@ -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;
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
@@ -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**
|
||||
@@ -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` |
|
||||
@@ -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"]}}
|
||||
@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user