commit 2f04fa4473953f481258a9209efdf43504f5863a Author: Timmy Date: Wed Apr 1 15:34:02 2026 +0800 Initial commit: k8s cluster manifests and scripts diff --git a/README.md b/README.md new file mode 100644 index 0000000..13389e5 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# K8s 叢集維護記錄 + +## 連線資訊 +- **主機**: 192.168.42.120 +- **帳號**: ubuntu (SSH Key 登入) +- **sudo**: `sudo -i` 可直接成為 root + +## K3s 目錄 +- **遠端**: `/root/k8s` +- **本地**: `/Users/timmy/42_120/k8s` + +## 已部署服務 + +### CodiMD (筆記應用) +- manifests: `apps/codimd/manifests/` +- 備份: `apps/codimd/backup/` +- 腳本: deploy.sh, stop.sh, status.sh, backup.sh, restore.sh + +### Opengist (Git Snippets) +- manifests: `apps/opengist/manifests/` +- 備份: `apps/opengist/backup/` +- 腳本: deploy.sh, stop.sh, status.sh, backup.sh, restore.sh + +### PostgreSQL (資料庫) +- manifests: `apps/postgres/manifests/` +- 備份: `apps/postgres/backups/` +- 腳本: deploy.sh, stop.sh, status.sh, backup.sh + +## 叢集工具 +- `cluster/check_access.sh` - 檢查存取權限 +- `cluster/check_nodes.sh` - 檢查節點狀態 +- `cluster/check_storage.sh` - 檢查儲存空間 + +--- +⚠️ 維護時請同步更新本地與遠端檔案 diff --git a/apps/codimd/backup.sh b/apps/codimd/backup.sh new file mode 100755 index 0000000..304bd62 --- /dev/null +++ b/apps/codimd/backup.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -o pipefail + +APP_NAME="codimd" +BACKUP_DIR="./backup" +TIMESTAMP=$(date +%Y%m%d-%H%M) +DB_BACKUP_FILE="${BACKUP_DIR}/${APP_NAME}-db-${TIMESTAMP}.sql.gz" +UPLOADS_BACKUP_FILE="${BACKUP_DIR}/${APP_NAME}-uploads-${TIMESTAMP}.tar.gz" + +# 1. 取得 Pod 名稱 +DB_POD=$(kubectl get pod -l app=codimd-db -o jsonpath="{.items[0].metadata.name}" 2>/dev/null) +APP_POD=$(kubectl get pod -l app=codimd -o jsonpath="{.items[0].metadata.name}" 2>/dev/null) + +mkdir -p $BACKUP_DIR + +# --- A. 備份資料庫 --- +echo "--- 正在備份資料庫... ---" +if kubectl exec $DB_POD -- pg_dump -U codimd codimd | gzip > $DB_BACKUP_FILE; then + echo "✅ 資料庫備份成功: $(basename $DB_BACKUP_FILE) ($(stat -c%s $DB_BACKUP_FILE) bytes)" +else + echo "❌ 資料庫備份失敗!" + exit 1 +fi + +# --- B. 備份上傳檔案 (Uploads) --- +echo "--- 正在備份上傳圖片 (Uploads)... ---" +# 使用 tar 打包 Pod 內的 uploads 資料夾,並傳回本機 +if kubectl exec $APP_POD -- tar zcf - -C /home/hackmd/app/public uploads > $UPLOADS_BACKUP_FILE; then + echo "✅ 圖片備份成功: $(basename $UPLOADS_BACKUP_FILE) ($(stat -c%s $UPLOADS_BACKUP_FILE) bytes)" +else + echo "❌ 圖片備份失敗!" + exit 1 +fi + +echo "--- ✨ 備份任務完成! ---" diff --git a/apps/codimd/backup/codimd-db-20260108-0543.sql.gz b/apps/codimd/backup/codimd-db-20260108-0543.sql.gz new file mode 100644 index 0000000..5cfee11 Binary files /dev/null and b/apps/codimd/backup/codimd-db-20260108-0543.sql.gz differ diff --git a/apps/codimd/backup/codimd-uploads-20260108-0543.tar.gz b/apps/codimd/backup/codimd-uploads-20260108-0543.tar.gz new file mode 100644 index 0000000..8c1b726 Binary files /dev/null and b/apps/codimd/backup/codimd-uploads-20260108-0543.tar.gz differ diff --git a/apps/codimd/deploy.sh b/apps/codimd/deploy.sh new file mode 100755 index 0000000..b8cd21a --- /dev/null +++ b/apps/codimd/deploy.sh @@ -0,0 +1,2 @@ +#!/bin/bash +kubectl apply -f manifests/ diff --git a/apps/codimd/manifests/00-codimd-middleware.yaml b/apps/codimd/manifests/00-codimd-middleware.yaml new file mode 100644 index 0000000..80a8eb7 --- /dev/null +++ b/apps/codimd/manifests/00-codimd-middleware.yaml @@ -0,0 +1,14 @@ +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: codimd-cors + namespace: default +spec: + headers: + accessControlAllowMethods: ["GET", "OPTIONS", "PUT", "POST"] + accessControlAllowOriginList: ["*"] + accessControlAllowHeaders: ["*"] + accessControlMaxAge: 100 + addVaryHeader: true + customResponseHeaders: + Cache-Control: "no-cache, no-store, must-revalidate" diff --git a/apps/codimd/manifests/01-codimd-secrets.yaml b/apps/codimd/manifests/01-codimd-secrets.yaml new file mode 100644 index 0000000..364708e --- /dev/null +++ b/apps/codimd/manifests/01-codimd-secrets.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: codimd-secrets +type: Opaque +stringData: + POSTGRES_PASSWORD: "8xG94tL0uKlGiBEd" + CMD_DB_URL: "postgres://codimd:8xG94tL0uKlGiBEd@codimd-db/codimd" diff --git a/apps/codimd/manifests/02-codimd-pvc.yaml b/apps/codimd/manifests/02-codimd-pvc.yaml new file mode 100644 index 0000000..c1ed070 --- /dev/null +++ b/apps/codimd/manifests/02-codimd-pvc.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: codimd-db-pvc +spec: + storageClassName: longhorn + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: codimd-upload-pvc +spec: + storageClassName: longhorn + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 5Gi diff --git a/apps/codimd/manifests/03-codimd-db-deployment.yaml b/apps/codimd/manifests/03-codimd-db-deployment.yaml new file mode 100644 index 0000000..2e21f45 --- /dev/null +++ b/apps/codimd/manifests/03-codimd-db-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: codimd-db +spec: + replicas: 1 + strategy: + type: Recreate # 確保 RWO 磁碟順利切換節點 + selector: + matchLabels: + app: codimd-db + template: + metadata: + labels: + app: codimd-db + spec: + containers: + - name: postgres + image: postgres:17-alpine + env: + - name: POSTGRES_USER + value: "codimd" + - name: POSTGRES_DB + value: "codimd" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: codimd-secrets + key: POSTGRES_PASSWORD + - name: PGDATA + value: "/var/lib/postgresql/data/pgdata" + volumeMounts: + - mountPath: "/var/lib/postgresql/data" + name: db-data + volumes: + - name: db-data + persistentVolumeClaim: + claimName: codimd-db-pvc diff --git a/apps/codimd/manifests/04-codimd-db-svc.yaml b/apps/codimd/manifests/04-codimd-db-svc.yaml new file mode 100644 index 0000000..afefe3b --- /dev/null +++ b/apps/codimd/manifests/04-codimd-db-svc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: codimd-db + labels: + app: codimd-db +spec: + selector: + app: codimd-db + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 diff --git a/apps/codimd/manifests/05-codimd-app-deployment.yaml b/apps/codimd/manifests/05-codimd-app-deployment.yaml new file mode 100644 index 0000000..9fb8a4c --- /dev/null +++ b/apps/codimd/manifests/05-codimd-app-deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: codimd-app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: codimd + template: + metadata: + labels: + app: codimd + spec: + securityContext: # <-- 這裡開始 + runAsUser: 0 + fsGroup: 0 # <-- 結束 + containers: + - name: codimd + image: hackmdio/hackmd:latest + env: + - name: CMD_DB_URL + valueFrom: + secretKeyRef: + name: codimd-secrets + key: CMD_DB_URL + - name: CMD_USECDN + value: "false" + - name: CMD_DOMAIN + value: "codimd.lotimmy.com" + - name: CMD_PROTOCOL_USESSL + value: "true" + - name: CMD_CSP_ENABLE + value: "false" + volumeMounts: + - mountPath: "/home/hackmd/app/public/uploads" + name: upload-data + volumes: + - name: upload-data + persistentVolumeClaim: + claimName: codimd-upload-pvc diff --git a/apps/codimd/manifests/06-codimd-app-svc.yaml b/apps/codimd/manifests/06-codimd-app-svc.yaml new file mode 100644 index 0000000..e4db500 --- /dev/null +++ b/apps/codimd/manifests/06-codimd-app-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: codimd-service + labels: + app: codimd +spec: + selector: + app: codimd + ports: + - protocol: TCP + name: http + port: 3000 + targetPort: 3000 diff --git a/apps/codimd/manifests/07-codimd-ingress.yaml b/apps/codimd/manifests/07-codimd-ingress.yaml new file mode 100644 index 0000000..fe0f81e --- /dev/null +++ b/apps/codimd/manifests/07-codimd-ingress.yaml @@ -0,0 +1,28 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: codimd-ingress + labels: + app: codimd + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web, websecure + traefik.ingress.kubernetes.io/router.middlewares: default-codimd-cors@kubernetescrd +spec: + ingressClassName: traefik + tls: + - hosts: + - codimd.192.168.42.120.nip.io + secretName: nip-io-cert + rules: + - host: codimd.lotimmy.com + http: &http_rules + paths: + - path: / + pathType: Prefix + backend: + service: + name: codimd-service + port: + number: 3000 + - host: codimd.192.168.42.120.nip.io + http: *http_rules diff --git a/apps/codimd/restore.sh b/apps/codimd/restore.sh new file mode 100755 index 0000000..ef9e34d --- /dev/null +++ b/apps/codimd/restore.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# ========================================== +# CodiMD 一鍵完整還原腳本 (DB + Uploads) +# ========================================== + +# 1. 自動偵測最新的備份檔案 +LATEST_DB=$(ls -t ./backup/codimd-db-*.sql.gz 2>/dev/null | head -1) +LATEST_IMG=$(ls -t ./backup/codimd-uploads-*.tar.gz 2>/dev/null | head -1) + +if [ -z "$LATEST_DB" ] || [ -z "$LATEST_IMG" ]; then + echo "❌ 錯誤: 在 ./backup 找不到完整的備份檔 (需要 .sql.gz 和 .tar.gz)" + exit 1 +fi + +echo "--- 📋 準備還原以下備份 ---" +echo "📅 資料庫: $LATEST_DB" +echo "🖼️ 圖片檔: $LATEST_IMG" +echo "------------------------------------------" +read -p "⚠️ 警告:這將會覆蓋目前容器內的所有資料!確定要執行嗎?(y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "操作已取消。" + exit 1 +fi + +# 2. 取得 Pod 名稱 +DB_POD=$(kubectl get pod -l app=codimd-db -o jsonpath="{.items[0].metadata.name}" 2>/dev/null) +APP_POD=$(kubectl get pod -l app=codimd -o jsonpath="{.items[0].metadata.name}" 2>/dev/null) + +if [ -z "$DB_POD" ] || [ -z "$APP_POD" ]; then + echo "❌ 錯誤: 找不到運作中的 CodiMD Pod,請確認 K8s 資源狀態。" + exit 1 +fi + +# --- A. 還原資料庫 (含清空邏輯) --- +echo "--- 🗄️ 正在還原資料庫結構與內容... ---" +# 先清空 Schema 以避免重複匯入導致的鍵值衝突 +kubectl exec -i $DB_POD -- psql -U codimd -d codimd -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" > /dev/null + +zcat "$LATEST_DB" | kubectl exec -i $DB_POD -- psql -U codimd -d codimd > /dev/null +if [ $? -eq 0 ]; then + echo "✅ 資料庫還原成功。" +else + echo "❌ 資料庫還原發生錯誤。" + exit 1 +fi + +# --- B. 還原圖片檔案 --- +echo "--- 🖼️ 正在同步圖片檔案 (Uploads)... ---" +# 清空目前目錄避免新舊檔案混雜 +kubectl exec $APP_POD -- sh -c "rm -rf /home/hackmd/app/public/uploads/*" + +# 解壓縮並傳入 Pod +cat "$LATEST_IMG" | kubectl exec -i $APP_POD -- tar zxf - -C /home/hackmd/app/public/ + +# 修正權限確保 CodiMD 服務 (UID 1500) 擁有存取權 +kubectl exec $APP_POD -- chown -R 1500:1500 /home/hackmd/app/public/uploads +echo "✅ 圖片還原與權限修正完成。" + +# 3. 重啟服務以套用變更 +echo "--- 🔄 正在重啟應用程式容器... ---" +kubectl rollout restart deployment codimd-app +kubectl rollout status deployment codimd-app + +echo "------------------------------------------" +echo "✨ 還原程序已執行完畢。" +echo "目前資料已同步至備份檔日期:$(echo $LATEST_DB | grep -oE '[0-9]{8}-[0-9]{4}')" diff --git a/apps/codimd/status.sh b/apps/codimd/status.sh new file mode 100755 index 0000000..90620f3 --- /dev/null +++ b/apps/codimd/status.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +APP_NAME="codimd" + +echo "=== [ $APP_NAME 資源狀態 ] ===" + +# 1. 檢查 Pods +echo "--- Pods ---" +kubectl get pods -l "app in (codimd, codimd-db)" -o wide + +echo "" + +# 2. 檢查 Services +echo "--- Services ---" +kubectl get svc -l "app in (codimd, codimd-db)" + +echo "" + +# 3. 檢查 PVC (儲存) +echo "--- Storage (PVC) ---" +kubectl get pvc | grep codimd + +echo "" + +# 4. 檢查存取入口 (Ingress) +echo "--- Access Points (Ingress) ---" +kubectl get ingress -l "app=codimd" 2>/dev/null || kubectl get ingress | grep codimd + +echo "" + +# 5. 檢查資料庫連通性 (如果 Pod 在 Running) +DB_POD=$(kubectl get pod -l app=codimd-db -o jsonpath="{.items[0].metadata.name}" 2>/dev/null) +POD_STATUS=$(kubectl get pod $DB_POD -o jsonpath="{.status.phase}" 2>/dev/null) + +if [ "$POD_STATUS" == "Running" ]; then + echo "--- Database Connection Check ---" + if kubectl exec $DB_POD -- psql -U codimd -d codimd -c "SELECT 1;" > /dev/null 2>&1; then + echo "✅ Database is Ready and Accessible." + else + echo "❌ Database is Running but Connection Failed." + fi +else + echo "⏳ Waiting for Database Pod to be Ready..." +fi diff --git a/apps/codimd/stop.sh b/apps/codimd/stop.sh new file mode 100755 index 0000000..7e97589 --- /dev/null +++ b/apps/codimd/stop.sh @@ -0,0 +1,12 @@ +#!/bin/bash +echo "--- 安全停止 CodiMD (保留資料) ---" + +# 只刪除 Deployment, Service, Ingress, Middleware +# 故意跳過 02-codimd-pvc.yaml 和 01-codimd-secrets.yaml +kubectl delete -f manifests/03-codimd-db-deployment.yaml --ignore-not-found +kubectl delete -f manifests/05-codimd-app-deployment.yaml --ignore-not-found +kubectl delete -f manifests/04-codimd-db-svc.yaml --ignore-not-found +kubectl delete -f manifests/06-codimd-app-svc.yaml --ignore-not-found +kubectl delete -f manifests/07-codimd-ingress.yaml --ignore-not-found + +echo "✅ 服務已停止,Longhorn 磁碟資料安全保留。" diff --git a/apps/codimd/tmpdir/codimd/Makefile b/apps/codimd/tmpdir/codimd/Makefile new file mode 100644 index 0000000..76a0331 --- /dev/null +++ b/apps/codimd/tmpdir/codimd/Makefile @@ -0,0 +1,55 @@ +# Makefile: 適用於 docker-compose 專案,自動取用當前資料夾為專案名 + +COMPOSE := docker-compose +COMPOSE_FILE := docker-compose.yml +PROJECT_NAME := $(notdir $(CURDIR)) # 依照當前資料夾自動命名 + +# 可選自訂 .env 變數(預設 docker-compose 自動載入) +# ENV_FILE := .env + +.PHONY: help up down build restart logs ps exec clean prune + +help: + @echo "🔧 Docker Compose Makefile 指令:" + @echo " make up - 啟動所有服務 (背景執行)" + @echo " make up-fg - 前景執行(方便除錯)" + @echo " make down - 停止並移除服務容器" + @echo " make build - 重新建構服務映像檔" + @echo " make restart - 重啟所有服務" + @echo " make logs - 查看服務日誌" + @echo " make ps - 顯示服務狀態" + @echo " make exec - 進入主要容器 shell" + @echo " make clean - 清理所有服務資料(含 volume)" + @echo " make prune - 清除未使用的資源" + +up: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up -d + +up-fg: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up + +down: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down + +build: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) build + +restart: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up -d + +logs: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) logs -f --tail=100 + +ps: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) ps + +exec: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) exec app sh + +clean: + $(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down -v --remove-orphans + +prune: + docker system prune -af --volumes + diff --git a/apps/codimd/tmpdir/codimd/backup_uploads/.gitkeep b/apps/codimd/tmpdir/codimd/backup_uploads/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/codimd/tmpdir/codimd/backup_uploads/upload_25c8ae3a38a2d6d29ffec9e433e52737.png b/apps/codimd/tmpdir/codimd/backup_uploads/upload_25c8ae3a38a2d6d29ffec9e433e52737.png new file mode 100644 index 0000000..03cce5d Binary files /dev/null and b/apps/codimd/tmpdir/codimd/backup_uploads/upload_25c8ae3a38a2d6d29ffec9e433e52737.png differ diff --git a/apps/codimd/tmpdir/codimd/backup_uploads/upload_968a528a5737b32e30df900ede1d3242.png b/apps/codimd/tmpdir/codimd/backup_uploads/upload_968a528a5737b32e30df900ede1d3242.png new file mode 100644 index 0000000..6afeb64 Binary files /dev/null and b/apps/codimd/tmpdir/codimd/backup_uploads/upload_968a528a5737b32e30df900ede1d3242.png differ diff --git a/apps/codimd/tmpdir/codimd/backup_uploads/upload_f695c8f30863c4b37d234c80ae421fdd.png b/apps/codimd/tmpdir/codimd/backup_uploads/upload_f695c8f30863c4b37d234c80ae421fdd.png new file mode 100644 index 0000000..8644161 Binary files /dev/null and b/apps/codimd/tmpdir/codimd/backup_uploads/upload_f695c8f30863c4b37d234c80ae421fdd.png differ diff --git a/apps/codimd/tmpdir/codimd/codimd_backup.dump b/apps/codimd/tmpdir/codimd/codimd_backup.dump new file mode 100644 index 0000000..973102f Binary files /dev/null and b/apps/codimd/tmpdir/codimd/codimd_backup.dump differ diff --git a/apps/codimd/tmpdir/codimd/docker-compose.yml b/apps/codimd/tmpdir/codimd/docker-compose.yml new file mode 100644 index 0000000..2cfbfa4 --- /dev/null +++ b/apps/codimd/tmpdir/codimd/docker-compose.yml @@ -0,0 +1,42 @@ +services: + database: + image: postgres:17-alpine + container_name: codimd_database + environment: + - POSTGRES_USER=codimd + - POSTGRES_PASSWORD=C0d1MdP@ss2025 + - POSTGRES_DB=codimd + volumes: + - "database-data:/var/lib/postgresql/data" + restart: always + labels: + - "com.centurylinklabs.watchtower.enable=true" + networks: + - codimd_net + + codimd: + image: hackmdio/hackmd:latest + container_name: codimd_app + environment: + - CMD_DB_URL=postgres://codimd:C0d1MdP@ss2025@database/codimd + - CMD_USECDN=false + depends_on: + - database + ports: + - "3001:3000" + volumes: + - upload-data:/home/hackmd/app/public/uploads + restart: always + labels: + - "com.centurylinklabs.watchtower.enable=true" + networks: + - codimd_net + +volumes: + database-data: {} + upload-data: {} + +networks: + codimd_net: + driver: bridge + diff --git a/apps/codimd/tmpdir/codimd_20251229150418.tar.gz b/apps/codimd/tmpdir/codimd_20251229150418.tar.gz new file mode 100644 index 0000000..4eff0a1 Binary files /dev/null and b/apps/codimd/tmpdir/codimd_20251229150418.tar.gz differ diff --git a/apps/opengist/backup.sh b/apps/opengist/backup.sh new file mode 100755 index 0000000..0ff177d --- /dev/null +++ b/apps/opengist/backup.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -e + +BACKUP_DIR="./backup" +TIMESTAMP=$(date +"%Y%m%d-%H%M") +BACKUP_FILE="${BACKUP_DIR}/opengist-${TIMESTAMP}.tar.gz" + +mkdir -p "${BACKUP_DIR}" + +echo "📦 Creating backup: ${BACKUP_FILE}" + +POD=$(kubectl get pod -l app=opengist -o jsonpath='{.items[0].metadata.name}') + +if [ -z "${POD}" ]; then + echo "❌ OpenGist pod not found" + exit 1 +fi + +kubectl exec "${POD}" -- \ + tar czf - /opengist > "${BACKUP_FILE}" + +echo "✅ Backup completed: ${BACKUP_FILE}" + diff --git a/apps/opengist/backup/opengist-20251231-0709.tar.gz b/apps/opengist/backup/opengist-20251231-0709.tar.gz new file mode 100644 index 0000000..147fd2c Binary files /dev/null and b/apps/opengist/backup/opengist-20251231-0709.tar.gz differ diff --git a/apps/opengist/deploy.sh b/apps/opengist/deploy.sh new file mode 100755 index 0000000..37a0cf5 --- /dev/null +++ b/apps/opengist/deploy.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -e + +MANIFESTS_DIR="./manifests" + +echo "🚀 Deploying OpenGist from ${MANIFESTS_DIR} ..." + +kubectl apply -f "${MANIFESTS_DIR}/opengist-pvc.yaml" +kubectl apply -f "${MANIFESTS_DIR}/opengist-deployment.yaml" +kubectl apply -f "${MANIFESTS_DIR}/opengist-svc-web.yaml" +kubectl apply -f "${MANIFESTS_DIR}/opengist-svc-ssh.yaml" +kubectl apply -f "${MANIFESTS_DIR}/opengist-ingress.yaml" + +echo "⏳ Waiting for deployment to be ready..." +kubectl rollout status deployment/opengist + +echo "✅ OpenGist deployed successfully" + diff --git a/apps/opengist/manifests/opengist-deployment.yaml b/apps/opengist/manifests/opengist-deployment.yaml new file mode 100644 index 0000000..44f36e0 --- /dev/null +++ b/apps/opengist/manifests/opengist-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: opengist + namespace: default + labels: + app: opengist +spec: + replicas: 1 + selector: + matchLabels: + app: opengist + template: + metadata: + labels: + app: opengist + spec: + containers: + - name: opengist + image: ghcr.io/thomiceli/opengist:latest + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 6157 + - name: ssh + containerPort: 2222 + resources: + limits: + memory: "512Mi" + cpu: "500m" + volumeMounts: + - name: data-vol + mountPath: /opengist + volumes: + - name: data-vol + persistentVolumeClaim: + claimName: opengist-pvc + diff --git a/apps/opengist/manifests/opengist-ingress.yaml b/apps/opengist/manifests/opengist-ingress.yaml new file mode 100644 index 0000000..5ba34b3 --- /dev/null +++ b/apps/opengist/manifests/opengist-ingress.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: opengist-ingress + namespace: default + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure +spec: + ingressClassName: traefik + + tls: + - hosts: + - opengist.lotimmy.com + - opengist.192.168.42.120.nip.io + secretName: nip-io-cert + + rules: + - host: opengist.lotimmy.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: opengist-web + port: + number: 6157 + + - host: opengist.192.168.42.120.nip.io + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: opengist-web + port: + number: 6157 + diff --git a/apps/opengist/manifests/opengist-pvc.yaml b/apps/opengist/manifests/opengist-pvc.yaml new file mode 100644 index 0000000..4903716 --- /dev/null +++ b/apps/opengist/manifests/opengist-pvc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: opengist-pvc + namespace: default +spec: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + diff --git a/apps/opengist/manifests/opengist-svc-ssh.yaml b/apps/opengist/manifests/opengist-svc-ssh.yaml new file mode 100644 index 0000000..620ca70 --- /dev/null +++ b/apps/opengist/manifests/opengist-svc-ssh.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: opengist-ssh + namespace: default +spec: + type: NodePort + selector: + app: opengist + ports: + - name: ssh + port: 2222 + targetPort: 2222 + protocol: TCP + nodePort: 32222 + diff --git a/apps/opengist/manifests/opengist-svc-web.yaml b/apps/opengist/manifests/opengist-svc-web.yaml new file mode 100644 index 0000000..ab18909 --- /dev/null +++ b/apps/opengist/manifests/opengist-svc-web.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: opengist-web + namespace: default +spec: + type: ClusterIP + selector: + app: opengist + ports: + - name: http + port: 6157 + targetPort: 6157 + protocol: TCP + diff --git a/apps/opengist/restore.sh b/apps/opengist/restore.sh new file mode 100755 index 0000000..4ed6b60 --- /dev/null +++ b/apps/opengist/restore.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -e + +BACKUP_DIR="./backup" + +if [ $# -ne 1 ]; then + echo "❌ Usage: ./restore.sh " + echo + echo "Example:" + echo " ./restore.sh opengist-20250101-1200.tar.gz" + exit 1 +fi + +BACKUP_FILE="${BACKUP_DIR}/$1" + +if [ ! -f "${BACKUP_FILE}" ]; then + echo "❌ Backup file not found: ${BACKUP_FILE}" + exit 1 +fi + +echo "⚠️ WARNING" +echo "This will OVERWRITE data in /opengist inside the PVC." +echo "Backup file: ${BACKUP_FILE}" +echo +read -p "Type YES to continue: " CONFIRM + +if [ "${CONFIRM}" != "YES" ]; then + echo "❌ Restore aborted" + exit 1 +fi + +echo "🛑 Scaling down OpenGist deployment..." +kubectl scale deployment opengist --replicas=0 + +echo "⏳ Waiting for pod termination..." +kubectl wait --for=delete pod -l app=opengist --timeout=60s || true + +echo "📦 Starting restore process..." + +# 使用暫時 helper pod 掛載同一顆 PVC +cat < "$OUT" + +echo "備份完成:" +echo "$OUT" + diff --git a/apps/postgres/backups/postgres-backup-20251231_055625.sql b/apps/postgres/backups/postgres-backup-20251231_055625.sql new file mode 100644 index 0000000..75d12e6 --- /dev/null +++ b/apps/postgres/backups/postgres-backup-20251231_055625.sql @@ -0,0 +1,157 @@ +-- +-- PostgreSQL database cluster dump +-- + +\restrict pSRBFv7OawZlldF9mhkwy64jnMZdGanavWk5r24Tn672qcvRL3qpsGkiOe7oPNn + +SET default_transaction_read_only = off; + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; + +-- +-- Roles +-- + +CREATE ROLE myuser; +ALTER ROLE myuser WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:lcasyFLTxsODZ2pTQzSfaQ==$Q9F2EsOXVyUo6hdnr4vPuErS4wNLYQ4aiszK/l4uar0=:ZOUai23wJ0E2uPl9ZVldTF4Kf6IduAMK+smPEs8LcjE='; + +-- +-- User Configurations +-- + + + + + + + + +\unrestrict pSRBFv7OawZlldF9mhkwy64jnMZdGanavWk5r24Tn672qcvRL3qpsGkiOe7oPNn + +-- +-- Databases +-- + +-- +-- Database "template1" dump +-- + +\connect template1 + +-- +-- PostgreSQL database dump +-- + +\restrict z5l8f0bphx2JQX8DXx5DcxSoJZ8WO1YgETQRPbrS67n6Z3sZTrRi3rkJm5JGwlm + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict z5l8f0bphx2JQX8DXx5DcxSoJZ8WO1YgETQRPbrS67n6Z3sZTrRi3rkJm5JGwlm + +-- +-- Database "mydb" dump +-- + +-- +-- PostgreSQL database dump +-- + +\restrict gIv4VWnnqhIHCcWxK9Gh9vrZXiMGwbVvmKNa64CtJ60YohCgypC9k0tWmdgClrB + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: mydb; Type: DATABASE; Schema: -; Owner: myuser +-- + +CREATE DATABASE mydb WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8'; + + +ALTER DATABASE mydb OWNER TO myuser; + +\unrestrict gIv4VWnnqhIHCcWxK9Gh9vrZXiMGwbVvmKNa64CtJ60YohCgypC9k0tWmdgClrB +\connect mydb +\restrict gIv4VWnnqhIHCcWxK9Gh9vrZXiMGwbVvmKNa64CtJ60YohCgypC9k0tWmdgClrB + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict gIv4VWnnqhIHCcWxK9Gh9vrZXiMGwbVvmKNa64CtJ60YohCgypC9k0tWmdgClrB + +-- +-- Database "postgres" dump +-- + +\connect postgres + +-- +-- PostgreSQL database dump +-- + +\restrict irY5kyT2aM9aY0ej35sQkGD65pdtbEJK5iVUBxrNe906zZsVHZFLUljOIsxjKic + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict irY5kyT2aM9aY0ej35sQkGD65pdtbEJK5iVUBxrNe906zZsVHZFLUljOIsxjKic + +-- +-- PostgreSQL database cluster dump complete +-- + diff --git a/apps/postgres/backups/postgres-backup-20251231_064007.sql b/apps/postgres/backups/postgres-backup-20251231_064007.sql new file mode 100644 index 0000000..51c4557 --- /dev/null +++ b/apps/postgres/backups/postgres-backup-20251231_064007.sql @@ -0,0 +1,1121 @@ +-- +-- PostgreSQL database cluster dump +-- + +\restrict TxL4kAbPon226DZYUHp47qFqdmHdahSXuQMSjIRM4KgpXPztB7SU5ZbX1Ho2uaN + +SET default_transaction_read_only = off; + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; + +-- +-- Roles +-- + +CREATE ROLE myuser; +ALTER ROLE myuser WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:lcasyFLTxsODZ2pTQzSfaQ==$Q9F2EsOXVyUo6hdnr4vPuErS4wNLYQ4aiszK/l4uar0=:ZOUai23wJ0E2uPl9ZVldTF4Kf6IduAMK+smPEs8LcjE='; + +-- +-- User Configurations +-- + + + + + + + + +\unrestrict TxL4kAbPon226DZYUHp47qFqdmHdahSXuQMSjIRM4KgpXPztB7SU5ZbX1Ho2uaN + +-- +-- Databases +-- + +-- +-- Database "template1" dump +-- + +\connect template1 + +-- +-- PostgreSQL database dump +-- + +\restrict EVqvAcoaKMwbiBprCl9lPK9CA0vqbUHYB8u1EeX81QmawOQHBzt74khoAQIAcXl + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict EVqvAcoaKMwbiBprCl9lPK9CA0vqbUHYB8u1EeX81QmawOQHBzt74khoAQIAcXl + +-- +-- Database "mydb" dump +-- + +-- +-- PostgreSQL database dump +-- + +\restrict PWAkRltnIYXWYaZ8HdFgY7RYpugTOWwfVPdlgVRbvSNZ1QApQ7QMeVZLLtpz0QK + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: mydb; Type: DATABASE; Schema: -; Owner: myuser +-- + +CREATE DATABASE mydb WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8'; + + +ALTER DATABASE mydb OWNER TO myuser; + +\unrestrict PWAkRltnIYXWYaZ8HdFgY7RYpugTOWwfVPdlgVRbvSNZ1QApQ7QMeVZLLtpz0QK +\connect mydb +\restrict PWAkRltnIYXWYaZ8HdFgY7RYpugTOWwfVPdlgVRbvSNZ1QApQ7QMeVZLLtpz0QK + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; + + +-- +-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; + + +-- +-- Name: address; Type: TYPE; Schema: public; Owner: myuser +-- + +CREATE TYPE public.address AS ( + street text, + city text, + zip text +); + + +ALTER TYPE public.address OWNER TO myuser; + +-- +-- Name: trg_orders_after_delete_restore_stock(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.trg_orders_after_delete_restore_stock() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE public.products + SET stock = stock + OLD.quantity, + updated_at = NOW() + WHERE id = OLD.product_id; + + RETURN NULL; +END; +$$; + + +ALTER FUNCTION public.trg_orders_after_delete_restore_stock() OWNER TO myuser; + +-- +-- Name: trg_orders_before_insert_decrement_stock(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.trg_orders_before_insert_decrement_stock() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + current_stock int; +BEGIN + SELECT stock INTO current_stock + FROM public.products + WHERE id = NEW.product_id + FOR UPDATE; + + IF NOT FOUND THEN + RAISE EXCEPTION 'product % not found', NEW.product_id; + END IF; + + IF current_stock < NEW.quantity THEN + RAISE EXCEPTION 'insufficient stock: have %, need %', + current_stock, NEW.quantity; + END IF; + + UPDATE public.products + SET stock = stock - NEW.quantity, + updated_at = NOW() + WHERE id = NEW.product_id; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.trg_orders_before_insert_decrement_stock() OWNER TO myuser; + +-- +-- Name: trg_orders_before_update_adjust_stock(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.trg_orders_before_update_adjust_stock() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + old_prod_stock int; + new_prod_stock int; +BEGIN + -- 沒變更商品也沒變更數量就略過 + IF NEW.product_id = OLD.product_id AND NEW.quantity = OLD.quantity THEN + RETURN NEW; + END IF; + + -- 先把舊商品庫存加回去 + IF OLD.product_id IS NOT NULL THEN + SELECT stock INTO old_prod_stock + FROM public.products + WHERE id = OLD.product_id + FOR UPDATE; + + IF NOT FOUND THEN + RAISE EXCEPTION 'old product % not found', OLD.product_id; + END IF; + + UPDATE public.products + SET stock = stock + OLD.quantity, + updated_at = NOW() + WHERE id = OLD.product_id; + END IF; + + -- 再扣新商品庫存 + SELECT stock INTO new_prod_stock + FROM public.products + WHERE id = NEW.product_id + FOR UPDATE; + + IF NOT FOUND THEN + RAISE EXCEPTION 'new product % not found', NEW.product_id; + END IF; + + IF new_prod_stock < NEW.quantity THEN + RAISE EXCEPTION 'insufficient stock after update: have %, need %', + new_prod_stock, NEW.quantity; + END IF; + + UPDATE public.products + SET stock = stock - NEW.quantity, + updated_at = NOW() + WHERE id = NEW.product_id; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.trg_orders_before_update_adjust_stock() OWNER TO myuser; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: company; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.company ( + id integer NOT NULL, + name text NOT NULL, + location public.address +); + + +ALTER TABLE public.company OWNER TO myuser; + +-- +-- Name: company_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.company_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.company_id_seq OWNER TO myuser; + +-- +-- Name: company_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.company_id_seq OWNED BY public.company.id; + + +-- +-- Name: devices; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.devices ( + id integer NOT NULL, + ip_address inet, + mac_address macaddr +); + + +ALTER TABLE public.devices OWNER TO myuser; + +-- +-- Name: devices_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.devices_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.devices_id_seq OWNER TO myuser; + +-- +-- Name: devices_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.devices_id_seq OWNED BY public.devices.id; + + +-- +-- Name: person; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.person ( + id integer NOT NULL, + name text NOT NULL, + birthdate date +); + + +ALTER TABLE public.person OWNER TO myuser; + +-- +-- Name: employee; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.employee ( + employee_id integer NOT NULL, + department text +) +INHERITS (public.person); + + +ALTER TABLE public.employee OWNER TO myuser; + +-- +-- Name: employee_employee_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.employee_employee_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.employee_employee_id_seq OWNER TO myuser; + +-- +-- Name: employee_employee_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.employee_employee_id_seq OWNED BY public.employee.employee_id; + + +-- +-- Name: events; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.events ( + id integer NOT NULL, + name text, + start_time timestamp without time zone, + start_time_tz timestamp with time zone +); + + +ALTER TABLE public.events OWNER TO myuser; + +-- +-- Name: events_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.events_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.events_id_seq OWNER TO myuser; + +-- +-- Name: events_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.events_id_seq OWNED BY public.events.id; + + +-- +-- Name: members; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.members ( + id integer NOT NULL, + username character varying(50) NOT NULL, + email text, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT members_email_check CHECK ((email ~~ '%@%'::text)) +); + + +ALTER TABLE public.members OWNER TO myuser; + +-- +-- Name: members_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.members_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.members_id_seq OWNER TO myuser; + +-- +-- Name: members_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.members_id_seq OWNED BY public.members.id; + + +-- +-- Name: orders; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.orders ( + id integer NOT NULL, + user_id integer NOT NULL, + product_id integer NOT NULL, + quantity integer DEFAULT 1 NOT NULL, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.orders OWNER TO myuser; + +-- +-- Name: TABLE orders; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.orders IS '訂單資料表,紀錄使用者購買的商品'; + + +-- +-- Name: COLUMN orders.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.id IS '訂單編號 (自動遞增)'; + + +-- +-- Name: COLUMN orders.user_id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.user_id IS '下單使用者,對應 users.id'; + + +-- +-- Name: COLUMN orders.product_id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.product_id IS '購買的商品,對應 products.id'; + + +-- +-- Name: COLUMN orders.quantity; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.quantity IS '購買數量'; + + +-- +-- Name: COLUMN orders.created_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.created_at IS '下單時間'; + + +-- +-- Name: orders_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.orders_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.orders_id_seq OWNER TO myuser; + +-- +-- Name: orders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.orders_id_seq OWNED BY public.orders.id; + + +-- +-- Name: person_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.person_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.person_id_seq OWNER TO myuser; + +-- +-- Name: person_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.person_id_seq OWNED BY public.person.id; + + +-- +-- Name: products; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.products ( + id integer NOT NULL, + name character varying(100) NOT NULL, + description text, + price numeric(10,2) NOT NULL, + stock integer DEFAULT 0, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.products OWNER TO myuser; + +-- +-- Name: TABLE products; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.products IS '商品資料表,存放產品基本資訊'; + + +-- +-- Name: COLUMN products.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.id IS '商品編號 (自動遞增)'; + + +-- +-- Name: COLUMN products.name; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.name IS '商品名稱'; + + +-- +-- Name: COLUMN products.description; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.description IS '商品描述'; + + +-- +-- Name: COLUMN products.price; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.price IS '商品價格,最多兩位小數'; + + +-- +-- Name: COLUMN products.stock; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.stock IS '庫存數量'; + + +-- +-- Name: COLUMN products.created_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.created_at IS '建立時間'; + + +-- +-- Name: products_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.products_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.products_id_seq OWNER TO myuser; + +-- +-- Name: products_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.products_id_seq OWNED BY public.products.id; + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.users ( + id integer NOT NULL, + username character varying(50) NOT NULL, + email character varying(100) NOT NULL, + password character varying(200) NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.users OWNER TO myuser; + +-- +-- Name: TABLE users; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.users IS '使用者資料表,儲存帳號與登入資訊'; + + +-- +-- Name: COLUMN users.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.id IS '使用者編號 (自動遞增)'; + + +-- +-- Name: COLUMN users.username; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.username IS '帳號名稱'; + + +-- +-- Name: COLUMN users.email; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.email IS '使用者 Email,必須唯一'; + + +-- +-- Name: COLUMN users.password; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.password IS '密碼 (通常存雜湊值)'; + + +-- +-- Name: COLUMN users.created_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.created_at IS '建立時間'; + + +-- +-- Name: COLUMN users.updated_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.updated_at IS '更新時間'; + + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.users_id_seq OWNER TO myuser; + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id; + + +-- +-- Name: company id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.company ALTER COLUMN id SET DEFAULT nextval('public.company_id_seq'::regclass); + + +-- +-- Name: devices id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.devices ALTER COLUMN id SET DEFAULT nextval('public.devices_id_seq'::regclass); + + +-- +-- Name: employee id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.employee ALTER COLUMN id SET DEFAULT nextval('public.person_id_seq'::regclass); + + +-- +-- Name: employee employee_id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.employee ALTER COLUMN employee_id SET DEFAULT nextval('public.employee_employee_id_seq'::regclass); + + +-- +-- Name: events id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.events ALTER COLUMN id SET DEFAULT nextval('public.events_id_seq'::regclass); + + +-- +-- Name: members id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.members ALTER COLUMN id SET DEFAULT nextval('public.members_id_seq'::regclass); + + +-- +-- Name: orders id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders ALTER COLUMN id SET DEFAULT nextval('public.orders_id_seq'::regclass); + + +-- +-- Name: person id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.person ALTER COLUMN id SET DEFAULT nextval('public.person_id_seq'::regclass); + + +-- +-- Name: products id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.products ALTER COLUMN id SET DEFAULT nextval('public.products_id_seq'::regclass); + + +-- +-- Name: users id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass); + + +-- +-- Data for Name: company; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.company (id, name, location) FROM stdin; +1 Masterwork Tech (中山路,台北市,100) +\. + + +-- +-- Data for Name: devices; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.devices (id, ip_address, mac_address) FROM stdin; +1 192.168.1.100 08:00:2b:01:02:03 +\. + + +-- +-- Data for Name: employee; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.employee (id, name, birthdate, employee_id, department) FROM stdin; +\. + + +-- +-- Data for Name: events; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.events (id, name, start_time, start_time_tz) FROM stdin; +1 Meeting 2025-08-22 14:00:00 2025-08-22 06:00:00+00 +2 Conference 2025-08-22 09:00:00 2025-08-22 09:00:00+00 +\. + + +-- +-- Data for Name: members; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.members (id, username, email, created_at) FROM stdin; +1 timmy timmy@example.com 2025-08-22 15:05:39.776007 +2 alice alice@newmail.com 2025-08-22 15:05:39.776007 +7 charlie charlie@example.com 2025-08-22 15:08:36.916081 +\. + + +-- +-- Data for Name: orders; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.orders (id, user_id, product_id, quantity, created_at) FROM stdin; +2 1 4 2 2025-08-22 05:40:34.59583+00 +3 2 2 1 2025-08-22 05:40:34.59583+00 +4 3 3 1 2025-08-22 05:40:34.59583+00 +5 3 5 1 2025-08-22 05:40:34.59583+00 +7 1 4 2 2025-08-22 06:00:14.71732+00 +8 1 4 2 2025-08-22 06:19:30.955247+00 +\. + + +-- +-- Data for Name: person; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.person (id, name, birthdate) FROM stdin; +\. + + +-- +-- Data for Name: products; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.products (id, name, description, price, stock, created_at, updated_at) FROM stdin; +2 Galaxy S24 Samsung 旗艦手機 35900.00 30 2025-08-22 13:32:06.952067 2025-08-22 06:00:03.411145+00 +3 MacBook Pro 14" 蘋果筆電 M3 晶片 69900.00 20 2025-08-22 13:32:06.952067 2025-08-22 06:00:03.411145+00 +1 iPhone 15 Apple 最新款智慧型手機 38900.00 51 2025-08-22 13:32:06.952067 2025-08-22 06:09:18.833917+00 +4 AirPods Pro 2 降噪藍牙耳機 7990.00 96 2025-08-22 13:32:06.952067 2025-08-22 06:20:25.194067+00 +5 Nintendo Switch 2 新一代任天堂遊戲機 12990.00 40 2025-08-22 13:32:06.952067 2025-08-22 06:20:37.793073+00 +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.users (id, username, email, password, created_at, updated_at) FROM stdin; +1 alice alice@example.com hashed_pwd_123 2025-08-22 13:31:53.543448 2025-08-22 13:31:53.543448 +2 bob bob@example.com hashed_pwd_456 2025-08-22 13:31:53.543448 2025-08-22 13:31:53.543448 +3 charlie charlie@example.com hashed_pwd_789 2025-08-22 13:31:53.543448 2025-08-22 13:31:53.543448 +\. + + +-- +-- Name: company_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.company_id_seq', 1, true); + + +-- +-- Name: devices_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.devices_id_seq', 1, true); + + +-- +-- Name: employee_employee_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.employee_employee_id_seq', 1, false); + + +-- +-- Name: events_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.events_id_seq', 2, true); + + +-- +-- Name: members_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.members_id_seq', 9, true); + + +-- +-- Name: orders_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.orders_id_seq', 8, true); + + +-- +-- Name: person_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.person_id_seq', 1, false); + + +-- +-- Name: products_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.products_id_seq', 5, true); + + +-- +-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.users_id_seq', 3, true); + + +-- +-- Name: company company_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.company + ADD CONSTRAINT company_pkey PRIMARY KEY (id); + + +-- +-- Name: devices devices_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.devices + ADD CONSTRAINT devices_pkey PRIMARY KEY (id); + + +-- +-- Name: events events_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.events + ADD CONSTRAINT events_pkey PRIMARY KEY (id); + + +-- +-- Name: members members_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.members + ADD CONSTRAINT members_pkey PRIMARY KEY (id); + + +-- +-- Name: members members_username_key; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.members + ADD CONSTRAINT members_username_key UNIQUE (username); + + +-- +-- Name: orders orders_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_pkey PRIMARY KEY (id); + + +-- +-- Name: person person_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.person + ADD CONSTRAINT person_pkey PRIMARY KEY (id); + + +-- +-- Name: products products_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.products + ADD CONSTRAINT products_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_email_key UNIQUE (email); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: idx_members_username; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX idx_members_username ON public.members USING btree (username); + + +-- +-- Name: orders orders_after_delete_restore_stock; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER orders_after_delete_restore_stock AFTER DELETE ON public.orders FOR EACH ROW EXECUTE FUNCTION public.trg_orders_after_delete_restore_stock(); + + +-- +-- Name: orders orders_before_insert_decrement_stock; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER orders_before_insert_decrement_stock BEFORE INSERT ON public.orders FOR EACH ROW EXECUTE FUNCTION public.trg_orders_before_insert_decrement_stock(); + + +-- +-- Name: orders orders_before_update_adjust_stock; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER orders_before_update_adjust_stock BEFORE UPDATE ON public.orders FOR EACH ROW EXECUTE FUNCTION public.trg_orders_before_update_adjust_stock(); + + +-- +-- Name: orders orders_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(id); + + +-- +-- Name: orders orders_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict PWAkRltnIYXWYaZ8HdFgY7RYpugTOWwfVPdlgVRbvSNZ1QApQ7QMeVZLLtpz0QK + +-- +-- Database "postgres" dump +-- + +\connect postgres + +-- +-- PostgreSQL database dump +-- + +\restrict WfbybiJFGsdG2zcyldILapMIAlJzgVhbHJPgqdfWAHerWnwgRKsWY5Xy7dQwQ7X + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict WfbybiJFGsdG2zcyldILapMIAlJzgVhbHJPgqdfWAHerWnwgRKsWY5Xy7dQwQ7X + +-- +-- PostgreSQL database cluster dump complete +-- + diff --git a/apps/postgres/backups/postgres-backup-20260108_072927.sql b/apps/postgres/backups/postgres-backup-20260108_072927.sql new file mode 100644 index 0000000..4ca7a48 --- /dev/null +++ b/apps/postgres/backups/postgres-backup-20260108_072927.sql @@ -0,0 +1,1830 @@ +-- +-- PostgreSQL database cluster dump +-- + +\restrict 6QNvU9pfOnsn22QfObvb8CLZhicbdhX3aX8ICK8XX2QCchD06hxXt0XTAxzMHaV + +SET default_transaction_read_only = off; + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; + +-- +-- Roles +-- + +CREATE ROLE myuser; +ALTER ROLE myuser WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:lcasyFLTxsODZ2pTQzSfaQ==$Q9F2EsOXVyUo6hdnr4vPuErS4wNLYQ4aiszK/l4uar0=:ZOUai23wJ0E2uPl9ZVldTF4Kf6IduAMK+smPEs8LcjE='; + +-- +-- User Configurations +-- + + + + + + + + +\unrestrict 6QNvU9pfOnsn22QfObvb8CLZhicbdhX3aX8ICK8XX2QCchD06hxXt0XTAxzMHaV + +-- +-- Databases +-- + +-- +-- Database "template1" dump +-- + +\connect template1 + +-- +-- PostgreSQL database dump +-- + +\restrict KmGvbtos7oLA27FJvDSnVdvllwTAcuMdbrAlRswZ6fmgiSmUFTu0XicHGewhlSZ + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict KmGvbtos7oLA27FJvDSnVdvllwTAcuMdbrAlRswZ6fmgiSmUFTu0XicHGewhlSZ + +-- +-- Database "mydb" dump +-- + +-- +-- PostgreSQL database dump +-- + +\restrict xo09hRfggjHZJK102rxVJthpERSaJQdW9fk8zxVEReJba5wAFAUfaxU6Uw9nm3g + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: mydb; Type: DATABASE; Schema: -; Owner: myuser +-- + +CREATE DATABASE mydb WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8'; + + +ALTER DATABASE mydb OWNER TO myuser; + +\unrestrict xo09hRfggjHZJK102rxVJthpERSaJQdW9fk8zxVEReJba5wAFAUfaxU6Uw9nm3g +\connect mydb +\restrict xo09hRfggjHZJK102rxVJthpERSaJQdW9fk8zxVEReJba5wAFAUfaxU6Uw9nm3g + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; + + +-- +-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; + + +-- +-- Name: address; Type: TYPE; Schema: public; Owner: myuser +-- + +CREATE TYPE public.address AS ( + street text, + city text, + zip text +); + + +ALTER TYPE public.address OWNER TO myuser; + +-- +-- Name: TYPE address; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TYPE public.address IS '地址複合型別,用於封裝街道/城市/郵遞區號'; + + +-- +-- Name: COLUMN address.street; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.address.street IS '街道地址'; + + +-- +-- Name: COLUMN address.city; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.address.city IS '城市'; + + +-- +-- Name: COLUMN address.zip; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.address.zip IS '郵遞區號'; + + +-- +-- Name: trg_orders_after_delete_restore_stock(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.trg_orders_after_delete_restore_stock() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE public.products + SET stock = stock + OLD.quantity, + updated_at = NOW() + WHERE id = OLD.product_id; + + RETURN NULL; +END; +$$; + + +ALTER FUNCTION public.trg_orders_after_delete_restore_stock() OWNER TO myuser; + +-- +-- Name: trg_orders_before_insert_decrement_stock(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.trg_orders_before_insert_decrement_stock() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + current_stock int; +BEGIN + SELECT stock INTO current_stock + FROM public.products + WHERE id = NEW.product_id + FOR UPDATE; + + IF NOT FOUND THEN + RAISE EXCEPTION 'product % not found', NEW.product_id; + END IF; + + IF current_stock < NEW.quantity THEN + RAISE EXCEPTION 'insufficient stock: have %, need %', + current_stock, NEW.quantity; + END IF; + + UPDATE public.products + SET stock = stock - NEW.quantity, + updated_at = NOW() + WHERE id = NEW.product_id; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.trg_orders_before_insert_decrement_stock() OWNER TO myuser; + +-- +-- Name: trg_orders_before_update_adjust_stock(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.trg_orders_before_update_adjust_stock() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + old_prod_stock int; + new_prod_stock int; +BEGIN + -- 沒變更商品也沒變更數量就略過 + IF NEW.product_id = OLD.product_id AND NEW.quantity = OLD.quantity THEN + RETURN NEW; + END IF; + + -- 先把舊商品庫存加回去 + IF OLD.product_id IS NOT NULL THEN + SELECT stock INTO old_prod_stock + FROM public.products + WHERE id = OLD.product_id + FOR UPDATE; + + IF NOT FOUND THEN + RAISE EXCEPTION 'old product % not found', OLD.product_id; + END IF; + + UPDATE public.products + SET stock = stock + OLD.quantity, + updated_at = NOW() + WHERE id = OLD.product_id; + END IF; + + -- 再扣新商品庫存 + SELECT stock INTO new_prod_stock + FROM public.products + WHERE id = NEW.product_id + FOR UPDATE; + + IF NOT FOUND THEN + RAISE EXCEPTION 'new product % not found', NEW.product_id; + END IF; + + IF new_prod_stock < NEW.quantity THEN + RAISE EXCEPTION 'insufficient stock after update: have %, need %', + new_prod_stock, NEW.quantity; + END IF; + + UPDATE public.products + SET stock = stock - NEW.quantity, + updated_at = NOW() + WHERE id = NEW.product_id; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.trg_orders_before_update_adjust_stock() OWNER TO myuser; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: company; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.company ( + id integer NOT NULL, + name text NOT NULL, + location public.address +); + + +ALTER TABLE public.company OWNER TO myuser; + +-- +-- Name: TABLE company; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.company IS '公司資料表(主檔)'; + + +-- +-- Name: COLUMN company.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.company.id IS '公司唯一識別碼(自動遞增)'; + + +-- +-- Name: COLUMN company.name; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.company.name IS '公司名稱'; + + +-- +-- Name: COLUMN company.location; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.company.location IS '公司地址(address 複合型別)'; + + +-- +-- Name: company_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.company_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.company_id_seq OWNER TO myuser; + +-- +-- Name: company_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.company_id_seq OWNED BY public.company.id; + + +-- +-- Name: devices; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.devices ( + id integer NOT NULL, + ip_address inet, + mac_address macaddr +); + + +ALTER TABLE public.devices OWNER TO myuser; + +-- +-- Name: devices_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.devices_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.devices_id_seq OWNER TO myuser; + +-- +-- Name: devices_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.devices_id_seq OWNED BY public.devices.id; + + +-- +-- Name: person; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.person ( + id integer NOT NULL, + name text NOT NULL, + birthdate date +); + + +ALTER TABLE public.person OWNER TO myuser; + +-- +-- Name: TABLE person; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.person IS '人員基本資料表,存放所有人的共通欄位'; + + +-- +-- Name: COLUMN person.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.person.id IS '人員唯一識別碼(自動遞增)'; + + +-- +-- Name: COLUMN person.name; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.person.name IS '姓名'; + + +-- +-- Name: COLUMN person.birthdate; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.person.birthdate IS '生日(未知可為 NULL)'; + + +-- +-- Name: employee; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.employee ( + employee_id integer NOT NULL, + department text +) +INHERITS (public.person); + + +ALTER TABLE public.employee OWNER TO myuser; + +-- +-- Name: TABLE employee; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.employee IS '員工資料表,繼承 person(包含 person.id/name/birthdate),另存員工專屬欄位'; + + +-- +-- Name: COLUMN employee.employee_id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.employee.employee_id IS '員工編號(自動遞增;注意主鍵仍為 person.id)'; + + +-- +-- Name: COLUMN employee.department; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.employee.department IS '部門名稱'; + + +-- +-- Name: employee_employee_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.employee_employee_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.employee_employee_id_seq OWNER TO myuser; + +-- +-- Name: employee_employee_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.employee_employee_id_seq OWNED BY public.employee.employee_id; + + +-- +-- Name: events; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.events ( + id integer NOT NULL, + name text, + start_time timestamp without time zone, + start_time_tz timestamp with time zone +); + + +ALTER TABLE public.events OWNER TO myuser; + +-- +-- Name: events_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.events_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.events_id_seq OWNER TO myuser; + +-- +-- Name: events_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.events_id_seq OWNED BY public.events.id; + + +-- +-- Name: members; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.members ( + id integer NOT NULL, + username character varying(50) NOT NULL, + email text, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT members_email_check CHECK ((email ~~ '%@%'::text)) +); + + +ALTER TABLE public.members OWNER TO myuser; + +-- +-- Name: members_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.members_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.members_id_seq OWNER TO myuser; + +-- +-- Name: members_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.members_id_seq OWNED BY public.members.id; + + +-- +-- Name: orders; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.orders ( + id integer NOT NULL, + user_id integer NOT NULL, + product_id integer NOT NULL, + quantity integer DEFAULT 1 NOT NULL, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.orders OWNER TO myuser; + +-- +-- Name: TABLE orders; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.orders IS '訂單資料表,紀錄使用者購買的商品'; + + +-- +-- Name: COLUMN orders.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.id IS '訂單編號 (自動遞增)'; + + +-- +-- Name: COLUMN orders.user_id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.user_id IS '下單使用者,對應 users.id'; + + +-- +-- Name: COLUMN orders.product_id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.product_id IS '購買的商品,對應 products.id'; + + +-- +-- Name: COLUMN orders.quantity; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.quantity IS '購買數量'; + + +-- +-- Name: COLUMN orders.created_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.orders.created_at IS '下單時間'; + + +-- +-- Name: orders_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.orders_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.orders_id_seq OWNER TO myuser; + +-- +-- Name: orders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.orders_id_seq OWNED BY public.orders.id; + + +-- +-- Name: person_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.person_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.person_id_seq OWNER TO myuser; + +-- +-- Name: person_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.person_id_seq OWNED BY public.person.id; + + +-- +-- Name: products; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.products ( + id integer NOT NULL, + name character varying(100) NOT NULL, + description text, + price numeric(10,2) NOT NULL, + stock integer DEFAULT 0, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.products OWNER TO myuser; + +-- +-- Name: TABLE products; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.products IS '商品資料表,存放產品基本資訊'; + + +-- +-- Name: COLUMN products.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.id IS '商品編號 (自動遞增)'; + + +-- +-- Name: COLUMN products.name; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.name IS '商品名稱'; + + +-- +-- Name: COLUMN products.description; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.description IS '商品描述'; + + +-- +-- Name: COLUMN products.price; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.price IS '商品價格,最多兩位小數'; + + +-- +-- Name: COLUMN products.stock; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.stock IS '庫存數量'; + + +-- +-- Name: COLUMN products.created_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.products.created_at IS '建立時間'; + + +-- +-- Name: products_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.products_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.products_id_seq OWNER TO myuser; + +-- +-- Name: products_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.products_id_seq OWNED BY public.products.id; + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.users ( + id integer NOT NULL, + username character varying(50) NOT NULL, + email character varying(100) NOT NULL, + password character varying(200) NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.users OWNER TO myuser; + +-- +-- Name: TABLE users; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.users IS '使用者資料表,儲存帳號與登入資訊'; + + +-- +-- Name: COLUMN users.id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.id IS '使用者編號 (自動遞增)'; + + +-- +-- Name: COLUMN users.username; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.username IS '帳號名稱'; + + +-- +-- Name: COLUMN users.email; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.email IS '使用者 Email,必須唯一'; + + +-- +-- Name: COLUMN users.password; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.password IS '密碼 (通常存雜湊值)'; + + +-- +-- Name: COLUMN users.created_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.created_at IS '建立時間'; + + +-- +-- Name: COLUMN users.updated_at; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.users.updated_at IS '更新時間'; + + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.users_id_seq OWNER TO myuser; + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id; + + +-- +-- Name: company id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.company ALTER COLUMN id SET DEFAULT nextval('public.company_id_seq'::regclass); + + +-- +-- Name: devices id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.devices ALTER COLUMN id SET DEFAULT nextval('public.devices_id_seq'::regclass); + + +-- +-- Name: employee id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.employee ALTER COLUMN id SET DEFAULT nextval('public.person_id_seq'::regclass); + + +-- +-- Name: employee employee_id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.employee ALTER COLUMN employee_id SET DEFAULT nextval('public.employee_employee_id_seq'::regclass); + + +-- +-- Name: events id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.events ALTER COLUMN id SET DEFAULT nextval('public.events_id_seq'::regclass); + + +-- +-- Name: members id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.members ALTER COLUMN id SET DEFAULT nextval('public.members_id_seq'::regclass); + + +-- +-- Name: orders id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders ALTER COLUMN id SET DEFAULT nextval('public.orders_id_seq'::regclass); + + +-- +-- Name: person id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.person ALTER COLUMN id SET DEFAULT nextval('public.person_id_seq'::regclass); + + +-- +-- Name: products id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.products ALTER COLUMN id SET DEFAULT nextval('public.products_id_seq'::regclass); + + +-- +-- Name: users id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass); + + +-- +-- Data for Name: company; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.company (id, name, location) FROM stdin; +1 Example Corp ("Main Street 1",Metropolis,12345) +2 Acme Solutions ("Industrial Rd 88",Gotham,54321) +3 Blue Ocean Tech ("Harbor Ave 12",Atlantis,67890) +4 Green Leaf Ltd ("Garden St 25",Springfield,11223) +5 NextGen Systems ("Innovation Way 9",Silicon,94043) +6 Skyline Holdings ("Tower Blvd 101","New City",10001) +7 IronForge Corp ("Foundry Ln 7",Steelton,33445) +8 Sunrise Trading ("Market St 66",Rivertown,55667) +9 Aurora Networks ("Northern Rd 3",Polar,77889) +10 Quantum Labs ("Research Pkwy 42",Nova,99000) +\. + + +-- +-- Data for Name: devices; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.devices (id, ip_address, mac_address) FROM stdin; +1 192.168.1.100 08:00:2b:01:02:03 +\. + + +-- +-- Data for Name: employee; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.employee (id, name, birthdate, employee_id, department) FROM stdin; +\. + + +-- +-- Data for Name: events; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.events (id, name, start_time, start_time_tz) FROM stdin; +1 Meeting 2025-08-22 14:00:00 2025-08-22 06:00:00+00 +2 Conference 2025-08-22 09:00:00 2025-08-22 09:00:00+00 +\. + + +-- +-- Data for Name: members; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.members (id, username, email, created_at) FROM stdin; +1 timmy timmy@example.com 2025-08-22 15:05:39.776007 +2 alice alice@newmail.com 2025-08-22 15:05:39.776007 +7 charlie charlie@example.com 2025-08-22 15:08:36.916081 +\. + + +-- +-- Data for Name: orders; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.orders (id, user_id, product_id, quantity, created_at) FROM stdin; +2 1 4 2 2025-08-22 05:40:34.59583+00 +3 2 2 1 2025-08-22 05:40:34.59583+00 +4 3 3 1 2025-08-22 05:40:34.59583+00 +5 3 5 1 2025-08-22 05:40:34.59583+00 +7 1 4 2 2025-08-22 06:00:14.71732+00 +8 1 4 2 2025-08-22 06:19:30.955247+00 +\. + + +-- +-- Data for Name: person; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.person (id, name, birthdate) FROM stdin; +\. + + +-- +-- Data for Name: products; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.products (id, name, description, price, stock, created_at, updated_at) FROM stdin; +2 Galaxy S24 Samsung 旗艦手機 35900.00 30 2025-08-22 13:32:06.952067 2025-08-22 06:00:03.411145+00 +3 MacBook Pro 14" 蘋果筆電 M3 晶片 69900.00 20 2025-08-22 13:32:06.952067 2025-08-22 06:00:03.411145+00 +1 iPhone 15 Apple 最新款智慧型手機 38900.00 51 2025-08-22 13:32:06.952067 2025-08-22 06:09:18.833917+00 +4 AirPods Pro 2 降噪藍牙耳機 7990.00 96 2025-08-22 13:32:06.952067 2025-08-22 06:20:25.194067+00 +5 Nintendo Switch 2 新一代任天堂遊戲機 12990.00 40 2025-08-22 13:32:06.952067 2025-08-22 06:20:37.793073+00 +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.users (id, username, email, password, created_at, updated_at) FROM stdin; +1 alice alice@example.com hashed_pwd_123 2025-08-22 13:31:53.543448 2025-08-22 13:31:53.543448 +2 bob bob@example.com hashed_pwd_456 2025-08-22 13:31:53.543448 2025-08-22 13:31:53.543448 +3 charlie charlie@example.com hashed_pwd_789 2025-08-22 13:31:53.543448 2025-08-22 13:31:53.543448 +\. + + +-- +-- Name: company_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.company_id_seq', 10, true); + + +-- +-- Name: devices_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.devices_id_seq', 1, true); + + +-- +-- Name: employee_employee_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.employee_employee_id_seq', 1, false); + + +-- +-- Name: events_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.events_id_seq', 2, true); + + +-- +-- Name: members_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.members_id_seq', 9, true); + + +-- +-- Name: orders_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.orders_id_seq', 8, true); + + +-- +-- Name: person_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.person_id_seq', 1, false); + + +-- +-- Name: products_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.products_id_seq', 5, true); + + +-- +-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.users_id_seq', 3, true); + + +-- +-- Name: company company_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.company + ADD CONSTRAINT company_pkey PRIMARY KEY (id); + + +-- +-- Name: devices devices_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.devices + ADD CONSTRAINT devices_pkey PRIMARY KEY (id); + + +-- +-- Name: events events_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.events + ADD CONSTRAINT events_pkey PRIMARY KEY (id); + + +-- +-- Name: members members_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.members + ADD CONSTRAINT members_pkey PRIMARY KEY (id); + + +-- +-- Name: members members_username_key; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.members + ADD CONSTRAINT members_username_key UNIQUE (username); + + +-- +-- Name: orders orders_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_pkey PRIMARY KEY (id); + + +-- +-- Name: person person_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.person + ADD CONSTRAINT person_pkey PRIMARY KEY (id); + + +-- +-- Name: products products_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.products + ADD CONSTRAINT products_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_email_key UNIQUE (email); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: idx_members_username; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX idx_members_username ON public.members USING btree (username); + + +-- +-- Name: orders orders_after_delete_restore_stock; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER orders_after_delete_restore_stock AFTER DELETE ON public.orders FOR EACH ROW EXECUTE FUNCTION public.trg_orders_after_delete_restore_stock(); + + +-- +-- Name: orders orders_before_insert_decrement_stock; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER orders_before_insert_decrement_stock BEFORE INSERT ON public.orders FOR EACH ROW EXECUTE FUNCTION public.trg_orders_before_insert_decrement_stock(); + + +-- +-- Name: orders orders_before_update_adjust_stock; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER orders_before_update_adjust_stock BEFORE UPDATE ON public.orders FOR EACH ROW EXECUTE FUNCTION public.trg_orders_before_update_adjust_stock(); + + +-- +-- Name: orders orders_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(id); + + +-- +-- Name: orders orders_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict xo09hRfggjHZJK102rxVJthpERSaJQdW9fk8zxVEReJba5wAFAUfaxU6Uw9nm3g + +-- +-- Database "netmeta" dump +-- + +-- +-- PostgreSQL database dump +-- + +\restrict Q4WpGms7fzf4wjhFCm4WDvwuXmx9BJApLRK21PfyCiZwM8lcxit6KlqiKHhVF9S + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: netmeta; Type: DATABASE; Schema: -; Owner: myuser +-- + +CREATE DATABASE netmeta WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8'; + + +ALTER DATABASE netmeta OWNER TO myuser; + +\unrestrict Q4WpGms7fzf4wjhFCm4WDvwuXmx9BJApLRK21PfyCiZwM8lcxit6KlqiKHhVF9S +\connect netmeta +\restrict Q4WpGms7fzf4wjhFCm4WDvwuXmx9BJApLRK21PfyCiZwM8lcxit6KlqiKHhVF9S + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: netmeta; Type: DATABASE PROPERTIES; Schema: -; Owner: myuser +-- + +ALTER DATABASE netmeta SET "TimeZone" TO 'Asia/Taipei'; + + +\unrestrict Q4WpGms7fzf4wjhFCm4WDvwuXmx9BJApLRK21PfyCiZwM8lcxit6KlqiKHhVF9S +\connect netmeta +\restrict Q4WpGms7fzf4wjhFCm4WDvwuXmx9BJApLRK21PfyCiZwM8lcxit6KlqiKHhVF9S + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; + + +-- +-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams'; + + +-- +-- Name: node_role; Type: TYPE; Schema: public; Owner: myuser +-- + +CREATE TYPE public.node_role AS ENUM ( + 'access', + 'bras', + 'backbone', + 'international_gateway', + 'peering', + 'pni', + 'enterprise_link', + 'pop', + 'edge', + 'cloud_backbone', + 'service' +); + + +ALTER TYPE public.node_role OWNER TO myuser; + +-- +-- Name: TYPE node_role; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TYPE public.node_role IS '網路節點角色列舉,限制角色集合以確保資料一致性。'; + + +-- +-- Name: set_updated_at(); Type: FUNCTION; Schema: public; Owner: myuser +-- + +CREATE FUNCTION public.set_updated_at() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.set_updated_at() OWNER TO myuser; + +-- +-- Name: FUNCTION set_updated_at(); Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON FUNCTION public.set_updated_at() IS '更新 network_node 時自動刷新 updated_at。'; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: ip_address; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.ip_address ( + ip inet NOT NULL, + node_id bigint NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public.ip_address OWNER TO myuser; + +-- +-- Name: TABLE ip_address; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.ip_address IS 'IP 對應表:使用 INET 型別(IPv4/IPv6),支援子網查詢;一個 IP 對應一個節點角色。'; + + +-- +-- Name: COLUMN ip_address.ip; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.ip_address.ip IS 'IP 位址(INET),可直接進行子網/包含查詢。'; + + +-- +-- Name: COLUMN ip_address.node_id; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.ip_address.node_id IS '外鍵,指向 network_node.id。'; + + +-- +-- Name: network_node; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.network_node ( + id bigint NOT NULL, + org text NOT NULL, + role public.node_role NOT NULL, + description text NOT NULL, + metadata jsonb DEFAULT '{}'::jsonb NOT NULL, + fts tsvector GENERATED ALWAYS AS (to_tsvector('simple'::regconfig, description)) STORED, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public.network_node OWNER TO myuser; + +-- +-- Name: TABLE network_node; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TABLE public.network_node IS '網路節點角色主表:一筆代表一個可重用的節點角色(不直接存 IP),支援全文檢索與 JSONB 擴充屬性。'; + + +-- +-- Name: COLUMN network_node.org; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.network_node.org IS '組織/單位,例如 中華電信(HiNet)、Microsoft、Apple、Cloudflare。'; + + +-- +-- Name: COLUMN network_node.role; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.network_node.role IS '節點角色(ENUM),例如 backbone、pni、edge、service。'; + + +-- +-- Name: COLUMN network_node.description; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.network_node.description IS '角色與說明的完整文字描述,保留人工可讀性。'; + + +-- +-- Name: COLUMN network_node.metadata; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.network_node.metadata IS '半結構化補充資料(JSONB),例如 ASN、region、provider、confidence。'; + + +-- +-- Name: COLUMN network_node.fts; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON COLUMN public.network_node.fts IS '全文檢索向量(由 description 產生)。'; + + +-- +-- Name: network_node_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.network_node_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.network_node_id_seq OWNER TO myuser; + +-- +-- Name: network_node_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.network_node_id_seq OWNED BY public.network_node.id; + + +-- +-- Name: v_ip_node; Type: VIEW; Schema: public; Owner: myuser +-- + +CREATE VIEW public.v_ip_node AS + SELECT a.ip AS stored_ip, + n.org, + n.role, + n.description, + n.metadata + FROM (public.ip_address a + JOIN public.network_node n ON ((n.id = a.node_id))); + + +ALTER VIEW public.v_ip_node OWNER TO myuser; + +-- +-- Name: network_node id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.network_node ALTER COLUMN id SET DEFAULT nextval('public.network_node_id_seq'::regclass); + + +-- +-- Data for Name: ip_address; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.ip_address (ip, node_id, created_at) FROM stdin; +211.23.141.254 1 2025-12-31 16:10:56.195078+08 +168.95.98.254 2 2025-12-31 16:10:56.195078+08 +168.95.228.58 3 2025-12-31 16:10:56.199972+08 +168.95.170.102 4 2025-12-31 16:10:56.199972+08 +168.95.170.118 4 2025-12-31 16:10:56.199972+08 +168.95.82.206 4 2025-12-31 16:10:56.199972+08 +168.95.171.122 5 2025-12-31 16:10:56.199972+08 +220.128.1.237 6 2025-12-31 16:10:56.206363+08 +220.128.1.189 6 2025-12-31 16:10:56.206363+08 +220.128.2.105 7 2025-12-31 16:10:56.290596+08 +220.128.13.85 8 2025-12-31 16:10:56.290596+08 +220.128.13.105 8 2025-12-31 16:10:56.290596+08 +211.22.229.213 9 2025-12-31 16:10:56.33773+08 +203.75.72.37 9 2025-12-31 16:10:56.33773+08 +202.39.91.77 10 2025-12-31 16:10:56.499255+08 +202.39.84.13 11 2025-12-31 16:10:56.499255+08 +202.39.84.120 11 2025-12-31 16:10:56.499255+08 +202.39.82.225 11 2025-12-31 16:10:56.499255+08 +202.39.84.1 12 2025-12-31 16:10:56.499255+08 +203.75.230.129 13 2025-12-31 16:10:56.50478+08 +63.223.43.66 14 2025-12-31 16:10:56.510137+08 +63.218.212.246 14 2025-12-31 16:10:56.510137+08 +63.223.43.114 14 2025-12-31 16:10:56.510137+08 +100.42.215.243 15 2025-12-31 16:10:56.510137+08 +100.42.215.213 15 2025-12-31 16:10:56.510137+08 +45.43.223.33 15 2025-12-31 16:10:56.510137+08 +141.11.93.252 16 2025-12-31 16:10:56.514878+08 +104.234.37.106 16 2025-12-31 16:10:56.514878+08 +203.69.110.161 17 2025-12-31 16:10:56.514878+08 +203.69.110.113 18 2025-12-31 16:10:56.514878+08 +51.10.1.94 19 2025-12-31 16:10:56.514878+08 +17.253.144.10 20 2025-12-31 16:10:56.514878+08 +104.44.23.163 21 2025-12-31 16:10:56.514878+08 +104.44.31.136 21 2025-12-31 16:10:56.514878+08 +104.44.20.44 21 2025-12-31 16:10:56.514878+08 +20.27.177.113 22 2025-12-31 16:10:56.514878+08 +1.1.1.1 23 2025-12-31 16:10:56.514878+08 +125.229.110.50 24 2025-12-31 16:20:26.732449+08 +211.23.141.206 25 2025-12-31 16:24:24.601576+08 +211.23.141.205 25 2025-12-31 16:26:13.418044+08 +211.23.141.207 25 2025-12-31 16:26:13.418044+08 +211.23.141.208 25 2025-12-31 16:26:13.418044+08 +211.23.141.209 25 2025-12-31 16:26:13.418044+08 +211.23.141.210 25 2025-12-31 16:26:13.418044+08 +60.251.128.43 25 2025-12-31 16:29:41.282355+08 +60.251.128.44 25 2025-12-31 16:29:41.282355+08 +60.251.128.45 25 2025-12-31 16:29:41.282355+08 +60.251.128.52 25 2025-12-31 16:29:41.282355+08 +60.251.128.53 25 2025-12-31 16:29:41.282355+08 +60.251.128.54 25 2025-12-31 16:29:41.282355+08 +220.135.87.91 25 2025-12-31 16:46:19.050883+08 +92.126.223.175 29 2025-12-31 17:34:05.405786+08 +94.132.97.76 30 2025-12-31 17:36:54.87063+08 +94.181.191.152 31 2025-12-31 17:38:55.481619+08 +46.151.182.137 32 2025-12-31 17:44:43.134361+08 +220.135.27.129 25 2025-12-31 17:52:54.579141+08 +106.105.181.40 35 2025-12-31 17:54:42.204643+08 +140.136.202.209 36 2025-12-31 17:56:56.895223+08 +210.242.157.193 25 2025-12-31 18:00:20.536188+08 +210.242.157.194 25 2025-12-31 18:00:20.536188+08 +210.242.157.195 25 2025-12-31 18:00:20.536188+08 +210.242.157.196 25 2025-12-31 18:00:20.536188+08 +210.242.157.197 25 2025-12-31 18:00:20.536188+08 +192.168.42.0/24 38 2025-12-31 18:02:23.666729+08 +64.23.128.29 39 2026-01-02 07:31:41.668549+08 +167.71.96.197 39 2026-01-02 07:40:03.590604+08 +59.111.239.33 41 2026-01-02 07:45:34.885834+08 +115.236.112.25 42 2026-01-02 07:49:45.061776+08 +111.124.200.65 43 2026-01-02 07:52:53.476539+08 +199.59.243.228 44 2026-01-02 07:57:38.494313+08 +1.34.0.0/15 45 2026-01-02 07:59:11.226155+08 +1.160.0.0/12 45 2026-01-02 08:00:54.890055+08 +36.224.0.0/12 45 2026-01-02 08:00:54.890055+08 +1.200.0.0/16 47 2026-01-02 08:01:06.701168+08 +27.51.0.0/16 47 2026-01-02 08:01:06.701168+08 +27.52.0.0/15 47 2026-01-02 08:01:06.701168+08 +27.240.0.0/13 47 2026-01-02 08:01:06.701168+08 +27.96.224.0/19 47 2026-01-02 08:01:06.701168+08 +36.50.228.0/23 47 2026-01-02 08:01:06.701168+08 +36.50.248.0/23 47 2026-01-02 08:01:06.701168+08 +27.100.64.0/18 48 2026-01-02 08:01:24.008002+08 +27.105.0.0/16 48 2026-01-02 08:01:24.008002+08 +27.0.152.0/22 49 2026-01-02 08:01:33.48536+08 +27.147.0.0/18 49 2026-01-02 08:01:33.48536+08 +39.1.0.0/16 51 2026-01-02 09:02:03.095643+08 +39.8.0.0/13 51 2026-01-02 09:02:03.095643+08 +58.99.0.0/18 52 2026-01-02 09:06:13.128295+08 +58.114.0.0/15 52 2026-01-02 09:06:13.128295+08 +59.104.0.0/15 52 2026-01-02 09:06:13.128295+08 +59.112.0.0/13 52 2026-01-02 09:06:13.128295+08 +59.120.0.0/14 52 2026-01-02 09:06:13.128295+08 +59.124.0.0/14 52 2026-01-02 09:06:13.128295+08 +49.214.0.0/15 53 2026-01-02 09:06:22.817204+08 +49.216.0.0/14 53 2026-01-02 09:06:22.817204+08 +101.8.0.0/13 53 2026-01-02 09:06:22.817204+08 +101.136.0.0/14 53 2026-01-02 09:06:22.817204+08 +60.198.0.0/16 54 2026-01-02 09:06:36.47257+08 +60.199.0.0/16 54 2026-01-02 09:06:36.47257+08 +61.30.0.0/16 55 2026-01-02 09:06:44.255863+08 +61.31.0.0/16 55 2026-01-02 09:06:44.255863+08 +61.62.0.0/15 56 2026-01-02 09:06:53.283214+08 +61.64.0.0/14 56 2026-01-02 09:06:53.283214+08 +49.158.0.0/15 57 2026-01-02 09:07:06.78768+08 +58.86.0.0/16 58 2026-01-02 09:07:15.786728+08 +125.224.0.0/13 59 2026-01-02 09:12:02.616517+08 +125.232.0.0/15 59 2026-01-02 09:12:02.616517+08 +223.136.0.0/13 60 2026-01-02 09:24:38.748982+08 +223.200.0.0/16 61 2026-01-02 09:24:47.104014+08 +219.68.0.0/17 62 2026-01-02 09:24:54.995013+08 +219.68.128.0/17 62 2026-01-02 09:24:54.995013+08 +219.69.0.0/16 62 2026-01-02 09:24:54.995013+08 +219.70.0.0/15 62 2026-01-02 09:24:54.995013+08 +219.90.0.0/18 62 2026-01-02 09:24:54.995013+08 +219.91.0.0/17 62 2026-01-02 09:24:54.995013+08 +219.84.0.0/15 63 2026-01-02 09:25:03.180654+08 +221.169.0.0/16 63 2026-01-02 09:25:03.180654+08 +114.24.0.0/14 64 2026-01-02 09:30:00.751707+08 +114.32.0.0/12 64 2026-01-02 09:30:00.751707+08 +114.136.0.0/15 65 2026-01-02 09:30:07.915218+08 +114.140.0.0/16 65 2026-01-02 09:30:07.915218+08 +61.216.0.0/16 66 2026-01-02 09:37:03.802976+08 +61.217.0.0/16 66 2026-01-02 09:37:03.802976+08 +61.218.0.0/15 66 2026-01-02 09:37:03.802976+08 +61.220.0.0/14 66 2026-01-02 09:37:03.802976+08 +61.20.0.0/16 66 2026-01-02 09:37:03.802976+08 +61.56.0.0/14 67 2026-01-02 09:37:12.70647+08 +61.224.0.0/14 67 2026-01-02 09:37:12.70647+08 +61.228.0.0/14 67 2026-01-02 09:37:12.70647+08 +61.60.0.0/16 68 2026-01-02 09:37:21.357311+08 +61.61.0.0/16 71 2026-01-02 09:37:46.924657+08 +61.70.0.0/16 71 2026-01-02 09:37:46.924657+08 +61.71.0.0/16 71 2026-01-02 09:37:46.924657+08 +220.130.0.0/15 73 2026-01-02 10:03:11.523115+08 +220.132.0.0/14 74 2026-01-02 10:03:19.20386+08 +220.136.0.0/13 74 2026-01-02 10:03:19.20386+08 +89.216.107.241 75 2026-01-02 10:10:39.707664+08 +\. + + +-- +-- Data for Name: network_node; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.network_node (id, org, role, description, metadata, created_at, updated_at) FROM stdin; +1 中華電信(HiNet) access 區域層級的 ISP 接入節點,通常位於新北市汐止或內湖、南港一帶。功能為寬頻遠端接入伺服器(BRAS),負責接收並建立用戶連線。 {"layer": "access"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +2 中華電信(HiNet) bras 區域寬頻接入節點(BRAS),位於ISP機房端。負責建立用戶的 PPPoE 連線並分配公網 IP。 {"protocol": "PPPoE"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +3 中華電信(HiNet) backbone 核心骨幹節點,設於台北市主要機房。負責彙整各區域流量,並進行國內或國際方向的轉送。 {"scope": "domestic-international"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +4 中華電信(HiNet) backbone 核心骨幹網路節點,設於主要機房。負責匯聚與轉送骨幹層級流量,並導向國際出口或特定互連線路。 {"scope": "core-routing"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +5 中華電信(HiNet) backbone 核心骨幹網路節點,設於主要機房。用於承載與轉送大規模骨幹流量,並進行網路路由交換。 {"scope": "core-switching"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +6 中華電信(HiNet) international_gateway 國際出口閘道節點,位於台北。作為骨幹流量匯集點,負責將資料送往海外網路。 {"direction": "outbound"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +7 中華電信(HiNet) peering 國內互連交換節點(Peering Gateway)。負責將中華電信網路流量導向台灣境內的內容傳遞網路(CDN)或網際網路交換中心。 {"type": "public-peering"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +8 中華電信(HiNet) pni 國內互連閘道節點,用於與大型內容供應商或雲端業者進行私有互連(PNI),承接並轉送高流量服務之專用連線。 {"type": "private-peering"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +9 中華電信(HiNet) enterprise_link 企業數據專線節點,屬於 HiNet 的數據分封網路,用於高優先級的企業互連或專用直連通道。 {"priority": "high"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +10 HiNet(TWGate) pop 海外 PoP 節點,設備實體設於海外,屬於中華電信海外網路架構,用於承接並轉送國際流量。 {"scope": "international"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +11 HiNet(TWGate) backbone 海外路由節點,設於美國洛杉磯。作為中華電信海外網路的一部分,負責當地流量的路由與轉送。 {"region": "US"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +12 HiNet(TWGate) backbone 海外路由節點,屬於中華電信海外網路架構的一部分。作為海外機房內的路由介面,用於承接並轉送進入海外網段的流量。 {"region": "US"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +13 中華電信(HiNet) peering 網際網路交換中心互連節點。作為中華電信與第三方網路服務供應商進行直接互連的介接位置。 {"type": "ixp"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +14 PCCW Global backbone Tier 1 國際電信營運商節點,位於國際骨幹網路中。用於跨電信網路之間的流量交換與轉送。 {"tier": "1"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +15 PacketHub backbone 資料中心上游網路節點,位於美國洛杉磯。作為機房對外的上游供應商,負責資料中心的流量進出與交換。 {"role": "datacenter-upstream"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +16 Virmach service VPS 主機節點,位於資料中心網路中。隸屬 AS25693(Virmach),提供虛擬專用伺服器服務,用於承載應用系統與對外服務流量。 {"asn": "AS25693"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +17 中華電信(HiNet) pni Microsoft 直連介接節點,用於將中華電信網路流量直接交付至 Microsoft 網路架構。 {"partner": "Microsoft"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +18 中華電信(HiNet) pni Apple 直連介接節點,用於將中華電信網路流量直接交付至 Apple 網路架構。 {"partner": "Apple"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +19 Microsoft edge Microsoft 邊緣節點(Edge Node),屬於微軟全球網路架構的一部分,用於承接進入 Microsoft 內部網路的流量。 {"provider": "Microsoft"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +20 Apple edge Apple 邊緣節點(Edge Node),屬於 Apple 全球內容傳遞網路的一部分,用於承接進入 Apple 內部服務網路的流量。 {"provider": "Apple"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +21 Microsoft cloud_backbone Azure 骨幹網路節點,屬於 Microsoft Azure 的內部骨幹網路,用於跨區域資料中心之間的流量傳輸。 {"cloud": "Azure"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +22 Microsoft Azure service Azure 雲端服務節點,位於 Azure 區域資料中心中,用於提供實際雲端服務與應用存取。 {"cloud": "Azure"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +23 Cloudflare service 公共 DNS 解析服務節點,採用 Anycast 架構。用於提供全球分散式的 DNS 查詢服務,實際回應位置依網路拓撲與路由策略而定。 {"service": "DNS", "architecture": "Anycast"} 2025-12-31 16:09:45.387957+08 2025-12-31 16:09:45.387957+08 +24 中華電信(HiNet) service 用戶端公網 IP,隸屬光世代寬頻配發範圍,用於作為連網設備對外連線的網際網路出口。 {"asn": "AS3462", "scope": "subscriber", "asn_org": "Data Communication Business Group", "access_type": "FTTH"} 2025-12-31 16:20:26.732449+08 2025-12-31 16:20:26.732449+08 +35 台灣智慧光網(Taifo) service 用戶端公網 IP,隸屬 AS131584(Taiwan Intelligent Fiber Optic Network),為光纖寬頻業者配發之 IP 位址,作為連網設備的網際網路出口。 {"asn": "AS131584", "scope": "subscriber", "asn_org": "Taiwan Intelligent Fiber Optic Network", "country": "TW"} 2025-12-31 17:54:42.204643+08 2025-12-31 17:54:42.204643+08 +36 輔仁大學(FJU) service 學術網路節點,隸屬 AS38845(Fu Jen Catholic University),為校園網路配發之 IP 位址,用於提供校內學術研究與連網服務。 {"asn": "AS38845", "scope": "academic", "asn_org": "Fu Jen Catholic University", "country": "TW"} 2025-12-31 17:56:56.895223+08 2025-12-31 17:56:56.895223+08 +25 中華電信(HiNet) service 用戶端公網 IP,隸屬 AS3462(Data Communication Business Group),為中華電信配發之 IP 位址,作為連網設備的網際網路出口。 {"asn": "AS3462", "scope": "subscriber", "asn_org": "Data Communication Business Group"} 2025-12-31 16:24:24.601576+08 2025-12-31 18:00:20.536188+08 +29 Rostelecom service 用戶端公網 IP,隸屬 AS12389(Rostelecom),為當地電信業者配發之 IP 位址,作為連網設備的網際網路出口。 {"asn": "AS12389", "scope": "subscriber", "asn_org": "Rostelecom", "country": "RU"} 2025-12-31 17:34:05.405786+08 2025-12-31 17:34:05.405786+08 +30 NOS Comunicações service 用戶端公網 IP,隸屬 AS2860(NOS Comunicações),為當地電信業者配發之 IP 位址,作為連網設備的網際網路出口。 {"asn": "AS2860", "scope": "subscriber", "asn_org": "NOS Comunicações", "country": "PT"} 2025-12-31 17:36:54.87063+08 2025-12-31 17:36:54.87063+08 +31 JSC ER-Telecom Holding service 用戶端公網 IP,隸屬 AS41754(JSC ER-Telecom Holding),為當地電信業者配發之 IP 位址,作為連網設備的網際網路出口。 {"asn": "AS41754", "scope": "subscriber", "asn_org": "JSC ER-Telecom Holding", "country": "RU"} 2025-12-31 17:38:55.481619+08 2025-12-31 17:38:55.481619+08 +32 Optibounce, LLC service 資料中心 / Hosting 節點,隸屬 AS214967(Optibounce, LLC),提供網路託管或伺服器服務,用於承載應用系統與流量轉送。 {"asn": "AS214967", "scope": "datacenter", "asn_org": "Optibounce, LLC", "country": "NL"} 2025-12-31 17:44:43.134361+08 2025-12-31 17:44:43.134361+08 +33 中華電信(HiNet) bras 區域寬頻接入節點(BRAS),位於ISP機房端。隸屬 AS3462(Data Communication Business Group),負責處理用戶端的 PPPoE 撥號連線與 IP 分配。 {"asn": "AS3462", "scope": "access", "asn_org": "Data Communication Business Group", "protocol": "PPPoE"} 2025-12-31 17:49:05.936504+08 2025-12-31 17:49:05.936504+08 +38 區域網路 (Local Network) access 區域網路內部 IP 位址(Private IP)。屬於 RFC 1918 定義之私有網段,僅供內網裝置使用,無法直接由網際網路存取。 {"rfc": "1918", "scope": "private"} 2025-12-31 18:02:23.666729+08 2025-12-31 18:02:23.666729+08 +39 DigitalOcean service VPS 主機或雲端服務節點,隸屬 AS14061(DigitalOcean),提供雲端運算與虛擬專用伺服器服務,用於承載應用系統與對外服務。 {"asn": "AS14061", "scope": "datacenter", "asn_org": "DigitalOcean", "country": "US"} 2026-01-02 07:31:41.668549+08 2026-01-02 07:40:03.590604+08 +41 網易(NetEase) service 伺服器或資料中心節點,隸屬 AS45062(NetEase),提供線上遊戲、雲端服務或內容傳遞網路服務,用於承載應用系統與流量轉送。 {"asn": "AS45062", "scope": "datacenter", "asn_org": "NetEase", "country": "CN"} 2026-01-02 07:45:34.885834+08 2026-01-02 07:45:34.885834+08 +42 中國電信(China Telecom) service 資料中心節點,隸屬 AS58461(CT-HangZhou-IDC),提供網際網路資料中心託管服務,用於承載應用系統與流量轉送。 {"asn": "AS58461", "scope": "datacenter", "asn_org": "CT-HangZhou-IDC", "country": "CN"} 2026-01-02 07:49:45.061776+08 2026-01-02 07:49:45.061776+08 +43 Guizhou GuiAn IDC service 資料中心節點,隸屬 AS139203(Guizhou GuiAn IDC),提供網際網路資料中心託管服務,用於承載應用系統與流量轉送。 {"asn": "AS139203", "scope": "datacenter", "asn_org": "Guizhou GuiAn IDC", "country": "CN"} 2026-01-02 07:52:53.476539+08 2026-01-02 07:52:53.476539+08 +45 中華電信(HiNet) service 寬頻用戶 IP 網段,隸屬 AS3462(Data Communication Business Group),廣泛配發給家庭光世代或企業用戶使用,作為網際網路出口。 {"asn": "AS3462", "scope": "subscriber", "asn_org": "Data Communication Business Group", "country": "TW"} 2026-01-02 07:59:11.226155+08 2026-01-02 08:00:54.890055+08 +47 台灣大哥大(Taiwan Mobile) service 行動網路與固網 IP 網段,隸屬 AS9924/AS9919(Taiwan Fixed Network / Taiwan Mobile),主要配發給 4G/5G 行動用戶及企業寬頻使用。 {"asn": ["AS9924", "AS9919"], "scope": "mobile-fixed", "asn_org": ["Taiwan Fixed Network", "Taiwan Mobile"], "country": "TW"} 2026-01-02 08:01:06.701168+08 2026-01-02 08:01:06.701168+08 +48 遠傳電信(Far EasTone) service 寬頻與行動網路 IP 網段,隸屬 AS4780/AS17421(Seednet / Far EasTone),配發給家用寬頻或行動裝置使用。 {"asn": ["AS4780", "AS17421"], "scope": "broadband-mobile", "asn_org": ["Seednet", "Far EasTone"], "country": "TW"} 2026-01-02 08:01:24.008002+08 2026-01-02 08:01:24.008002+08 +49 是方電訊(Chief Telecom) service IDC 與企業專線 IP 網段,隸屬 AS18049(Chief Telecom),主要提供資料中心託管、企業 VPN 或交換中心使用。 {"asn": "AS18049", "scope": "datacenter-enterprise", "asn_org": "Chief Telecom", "country": "TW"} 2026-01-02 08:01:33.48536+08 2026-01-02 08:01:33.48536+08 +51 遠傳電信(Far EasTone) service 行動網路 IP 網段,隸屬 AS17421 / AS9674,主要配發給 4G/5G 手機上網用戶,部分為原亞太線上(APOL)整併之網段。 {"asn": ["AS17421", "AS9674"], "scope": "mobile", "asn_org": ["Far EasTone", "Asia Pacific Online"], "country": "TW"} 2026-01-02 09:02:03.095643+08 2026-01-02 09:02:03.095643+08 +52 中華電信(HiNet) service 寬頻、企業與政府網段,隸屬 AS3462(Data Communication Business Group),涵蓋光世代動態 IP、企業固定制及政府網路使用。 {"asn": "AS3462", "scope": ["broadband", "enterprise", "government"], "asn_org": "Data Communication Business Group", "country": "TW"} 2026-01-02 09:06:13.128295+08 2026-01-02 09:06:13.128295+08 +53 台灣大哥大(Taiwan Mobile) service 行動網路網段,隸屬 AS9919,主要配發給 4G/5G 行動用戶使用(CGNAT)。 {"asn": "AS9919", "scope": "mobile", "country": "TW"} 2026-01-02 09:06:22.817204+08 2026-01-02 09:06:22.817204+08 +54 台灣固網(TFN)/ 台灣大哥大 service 固網與企業寬頻網段,隸屬 AS9924,主要提供企業專線與資料中心相關服務使用。 {"asn": "AS9924", "scope": "enterprise", "country": "TW"} 2026-01-02 09:06:36.47257+08 2026-01-02 09:06:36.47257+08 +55 遠傳電信(Seednet) service 家用寬頻網段,隸屬 AS4780(Digital United / Seednet),主要配發給 Seednet 光纖或 ADSL 用戶。 {"asn": "AS4780", "scope": "broadband", "asn_org": "Digital United / Seednet", "country": "TW"} 2026-01-02 09:06:44.255863+08 2026-01-02 09:06:44.255863+08 +56 遠傳電信(Far EasTone) service 寬頻與行動整合網段,隸屬 AS9674 / AS17421,原為亞太線上(APOL)網段,現整併入遠傳體系使用。 {"asn": ["AS9674", "AS17421"], "scope": ["broadband", "mobile"], "country": "TW"} 2026-01-02 09:06:53.283214+08 2026-01-02 09:06:53.283214+08 +57 台灣寬頻通訊(TBC) service 有線電視寬頻網段,隸屬 AS17408,提供 TBC 旗下有線電視系統之寬頻上網服務。 {"asn": "AS17408", "scope": "cable-broadband", "country": "TW"} 2026-01-02 09:07:06.78768+08 2026-01-02 09:07:06.78768+08 +58 凱擘大寬頻(Kbro) service 有線電視寬頻網段,隸屬 AS18044,提供凱擘旗下有線電視系統之寬頻上網服務。 {"asn": "AS18044", "scope": "cable-broadband", "country": "TW"} 2026-01-02 09:07:15.786728+08 2026-01-02 09:07:15.786728+08 +59 中華電信(HiNet) service 寬頻用戶 IP 網段,隸屬 AS3462(Data Communication Business Group),主要配發給家庭光世代或 ADSL 用戶使用(動態 IP),屬於常見的家用網段。 {"asn": "AS3462", "scope": "subscriber", "asn_org": "Data Communication Business Group", "country": "TW", "access_type": ["FTTH", "ADSL"]} 2026-01-02 09:12:02.616517+08 2026-01-02 09:12:02.616517+08 +60 中華電信(HiNet) service 行動網路 IP 網段,為 4G/5G 手機上網之主要使用區段,常見於行動裝置連線來源。 {"asn": "AS3462", "scope": "mobile", "country": "TW"} 2026-01-02 09:24:38.748982+08 2026-01-02 09:24:38.748982+08 +61 政府網際服務網(GSN) service 政府機關專用網路 IP 網段,由中華電信代管,僅配發給政府單位、公立學校與研究機構使用。 {"scope": "government", "country": "TW", "managed_by": "中華電信"} 2026-01-02 09:24:47.104014+08 2026-01-02 09:24:47.104014+08 +62 TBC / Kbro(台灣寬頻 / 凱擘) service 有線電視寬頻 IP 網段,主要為 Cable Modem 家用上網使用,分佈於全台各地。 {"scope": "cable-broadband", "country": "TW"} 2026-01-02 09:24:54.995013+08 2026-01-02 09:24:54.995013+08 +63 遠傳電信(Seednet) service 家用寬頻 IP 網段,Seednet 傳統使用區段,涵蓋大量 ADSL 與光纖家庭用戶。 {"asn": "AS4780", "scope": "broadband", "country": "TW"} 2026-01-02 09:25:03.180654+08 2026-01-02 09:25:03.180654+08 +64 中華電信(HiNet) service 寬頻與固網主力網段,隸屬 AS3462,涵蓋企業固定制與家庭光世代動態 IP,為台灣分佈最廣的網段之一。 {"asn": "AS3462", "scope": ["broadband", "enterprise"], "asn_org": "Data Communication Business Group", "country": "TW", "access_type": ["static", "dynamic"]} 2026-01-02 09:30:00.751707+08 2026-01-02 09:30:00.751707+08 +65 中華電信(HiNet) service 行動網路 IP 網段,隸屬 AS3462 / AS9668,主要配發給中華電信 4G/5G 手機用戶使用。 {"asn": ["AS3462", "AS9668"], "scope": "mobile", "country": "TW"} 2026-01-02 09:30:07.915218+08 2026-01-02 09:30:07.915218+08 +66 中華電信(HiNet) service 企業固定制與商業用途網段,隸屬 AS3462,常用於公司行號、伺服器託管或 FTTB 專線服務。 {"asn": "AS3462", "scope": ["enterprise", "datacenter"], "asn_org": "Data Communication Business Group", "country": "TW", "access_type": "static"} 2026-01-02 09:37:03.802976+08 2026-01-02 09:37:03.802976+08 +67 中華電信(HiNet) service 家用寬頻動態 IP 網段,隸屬 AS3462,涵蓋光世代與 ADSL 用戶,分布於台灣各地。 {"asn": "AS3462", "scope": "subscriber", "asn_org": "Data Communication Business Group", "country": "TW", "access_type": "dynamic"} 2026-01-02 09:37:12.70647+08 2026-01-02 09:37:12.70647+08 +68 政府網際服務網(GSN) service 政府機關專用網段,由中華電信代管,主要提供政府部門與公立學校使用。 {"scope": "government", "country": "TW", "managed_by": "中華電信"} 2026-01-02 09:37:21.357311+08 2026-01-02 09:37:21.357311+08 +69 遠傳電信(Seednet) service 家用寬頻網段,隸屬 AS4780(Seednet),主要配發給家庭光纖或 ADSL 用戶使用。 {"asn": "AS4780", "scope": "subscriber", "asn_org": "Seednet", "country": "TW"} 2026-01-02 09:37:29.108237+08 2026-01-02 09:37:29.108237+08 +70 遠傳電信(Far EasTone) service 寬頻與行動整合網段,隸屬 AS9674 / AS17421,原為亞太線上(APOL)使用,現整併至遠傳體系。 {"asn": ["AS9674", "AS17421"], "scope": ["broadband", "mobile"], "country": "TW"} 2026-01-02 09:37:37.656136+08 2026-01-02 09:37:37.656136+08 +71 台灣固網(TFN)/ 台灣大哥大 service 企業與固網寬頻網段,隸屬 AS9924,常用於企業專線、IDC 機房及相關寬頻服務。 {"asn": "AS9924", "scope": ["enterprise", "datacenter"], "asn_org": "Taiwan Fixed Network", "country": "TW"} 2026-01-02 09:37:46.924657+08 2026-01-02 09:37:46.924657+08 +44 Amazon.com (AWS) service 雲端服務節點,隸屬 AS16509(Amazon.com),提供雲端運算服務(AWS),用於承載應用系統與流量轉送。 {"asn": "AS16509", "scope": "cloud", "asn_org": "Amazon.com", "country": "US"} 2026-01-02 07:57:38.494313+08 2026-01-02 10:00:10.208025+08 +73 中華電信(HiNet) service 企業固定制與商業用途網段,隸屬 AS3462,常配發給申請多機型固定制或 FTTB 企業專線,用於架設網站、VPN 或郵件伺服器。 {"asn": "AS3462", "scope": ["enterprise", "datacenter"], "asn_org": "Data Communication Business Group", "country": "TW", "access_type": "static"} 2026-01-02 10:03:11.523115+08 2026-01-02 10:03:11.523115+08 +74 中華電信(HiNet) service 家用寬頻動態 IP 網段,隸屬 AS3462,為大規模光世代與 ADSL 用戶使用,具有明顯地域分佈特性。 {"asn": "AS3462", "scope": "subscriber", "asn_org": "Data Communication Business Group", "country": "TW", "access_type": "dynamic"} 2026-01-02 10:03:19.20386+08 2026-01-02 10:03:19.20386+08 +75 Serbia BroadBand (SBB) service 寬頻用戶 IP,隸屬 AS31042(Serbia BroadBand),為當地主要有線電視與寬頻業者配發,通常作為家庭或商業用戶的網際網路出口。 {"asn": "AS31042", "scope": "subscriber", "asn_org": "Serbia BroadBand", "country": "RS"} 2026-01-02 10:10:39.707664+08 2026-01-02 10:10:39.707664+08 +\. + + +-- +-- Name: network_node_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.network_node_id_seq', 75, true); + + +-- +-- Name: ip_address ip_address_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.ip_address + ADD CONSTRAINT ip_address_pkey PRIMARY KEY (ip); + + +-- +-- Name: network_node network_node_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.network_node + ADD CONSTRAINT network_node_pkey PRIMARY KEY (id); + + +-- +-- Name: ix_ip_address_node_id; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX ix_ip_address_node_id ON public.ip_address USING btree (node_id); + + +-- +-- Name: ix_network_node_fts; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX ix_network_node_fts ON public.network_node USING gin (fts); + + +-- +-- Name: ix_network_node_metadata; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX ix_network_node_metadata ON public.network_node USING gin (metadata); + + +-- +-- Name: ix_network_node_org; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX ix_network_node_org ON public.network_node USING btree (org); + + +-- +-- Name: ix_network_node_role; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE INDEX ix_network_node_role ON public.network_node USING btree (role); + + +-- +-- Name: ux_network_node_org_role_desc; Type: INDEX; Schema: public; Owner: myuser +-- + +CREATE UNIQUE INDEX ux_network_node_org_role_desc ON public.network_node USING btree (org, role, md5(description)); + + +-- +-- Name: INDEX ux_network_node_org_role_desc; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON INDEX public.ux_network_node_org_role_desc IS '避免同組織、同角色、同描述的重複資料。'; + + +-- +-- Name: network_node trg_network_node_updated_at; Type: TRIGGER; Schema: public; Owner: myuser +-- + +CREATE TRIGGER trg_network_node_updated_at BEFORE UPDATE ON public.network_node FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); + + +-- +-- Name: TRIGGER trg_network_node_updated_at ON network_node; Type: COMMENT; Schema: public; Owner: myuser +-- + +COMMENT ON TRIGGER trg_network_node_updated_at ON public.network_node IS 'network_node 更新觸發器,自動維護 updated_at。'; + + +-- +-- Name: ip_address ip_address_node_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.ip_address + ADD CONSTRAINT ip_address_node_id_fkey FOREIGN KEY (node_id) REFERENCES public.network_node(id) ON DELETE CASCADE; + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict Q4WpGms7fzf4wjhFCm4WDvwuXmx9BJApLRK21PfyCiZwM8lcxit6KlqiKHhVF9S + +-- +-- Database "postgres" dump +-- + +\connect postgres + +-- +-- PostgreSQL database dump +-- + +\restrict 0YfL9O5afocIRgmsdQcELr5usw6xQAbEOjnKLz1wqYJke4g4p6PhqR4KdvvEUy1 + +-- Dumped from database version 16.11 (Debian 16.11-1.pgdg13+1) +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict 0YfL9O5afocIRgmsdQcELr5usw6xQAbEOjnKLz1wqYJke4g4p6PhqR4KdvvEUy1 + +-- +-- PostgreSQL database cluster dump complete +-- + diff --git a/apps/postgres/deploy.sh b/apps/postgres/deploy.sh new file mode 100755 index 0000000..a50ba2e --- /dev/null +++ b/apps/postgres/deploy.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# 用途:部署或更新 Postgres(Secret / PVC / Deployment / Service) + +set -euo pipefail + +BASE_DIR="$(cd "$(dirname "$0")" && pwd)" +MANIFESTS="$BASE_DIR/manifests" + +echo "部署 Postgres 中..." + +kubectl apply -f "$MANIFESTS/postgres-secret.yaml" +kubectl apply -f "$MANIFESTS/postgres-pvc.yaml" +kubectl apply -f "$MANIFESTS/postgres-deployment.yaml" +kubectl apply -f "$MANIFESTS/postgres-service.yaml" + +echo "完成,等待 Pod 就緒..." +kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s + +echo "Postgres 已啟動" + diff --git a/apps/postgres/manifests/postgres-deployment.yaml b/apps/postgres/manifests/postgres-deployment.yaml new file mode 100644 index 0000000..11fedc8 --- /dev/null +++ b/apps/postgres/manifests/postgres-deployment.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:16 + imagePullPolicy: IfNotPresent + + ports: + - containerPort: 5432 + + # 🔑 關鍵:避免 lost+found 問題 + env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + + envFrom: + - secretRef: + name: postgres-secret + + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + + # 官方建議的健康檢查方式 + livenessProbe: + exec: + command: + - sh + - -c + - pg_isready -d "$POSTGRES_DB" -U "$POSTGRES_USER" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + exec: + command: + - sh + - -c + - pg_isready -d "$POSTGRES_DB" -U "$POSTGRES_USER" + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-pvc + diff --git a/apps/postgres/manifests/postgres-pvc.yaml b/apps/postgres/manifests/postgres-pvc.yaml new file mode 100644 index 0000000..00e538a --- /dev/null +++ b/apps/postgres/manifests/postgres-pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + diff --git a/apps/postgres/manifests/postgres-secret.yaml b/apps/postgres/manifests/postgres-secret.yaml new file mode 100644 index 0000000..7f15125 --- /dev/null +++ b/apps/postgres/manifests/postgres-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret +type: Opaque +stringData: + POSTGRES_DB: mydb + POSTGRES_USER: myuser + POSTGRES_PASSWORD: mypassword + diff --git a/apps/postgres/manifests/postgres-service.yaml b/apps/postgres/manifests/postgres-service.yaml new file mode 100644 index 0000000..9c50f81 --- /dev/null +++ b/apps/postgres/manifests/postgres-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + type: NodePort + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 + nodePort: 31532 + diff --git a/apps/postgres/status.sh b/apps/postgres/status.sh new file mode 100755 index 0000000..4a6b2a3 --- /dev/null +++ b/apps/postgres/status.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# 用途:查看 Postgres 狀態與連線資訊 + +set -euo pipefail + +echo +echo "=== Pod ===" +kubectl get pod -l app=postgres -o wide + +echo +echo "=== Service ===" +kubectl get svc postgres + +echo +echo "=== PVC ===" +kubectl get pvc postgres-pvc + +echo +echo "=== NodePort 連線 ===" +NODE_IPS=$(kubectl get nodes -o jsonpath='{range .items[*]}{.status.addresses[?(@.type=="InternalIP")].address}{" "}{end}') +PORT=$(kubectl get svc postgres -o jsonpath='{.spec.ports[0].nodePort}') + +for ip in $NODE_IPS; do + echo "- $ip:$PORT" +done + diff --git a/apps/postgres/stop.sh b/apps/postgres/stop.sh new file mode 100755 index 0000000..a4d909c --- /dev/null +++ b/apps/postgres/stop.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# 用途:停止 Postgres(不刪 PVC 與 Secret) + +set -euo pipefail + +echo "停止 Postgres 中..." + +kubectl delete deployment postgres --ignore-not-found +kubectl wait --for=delete pod -l app=postgres --timeout=60s || true +kubectl delete service postgres --ignore-not-found + +echo "Postgres 已停止(資料仍保留)" + diff --git a/cluster/check_access.sh b/cluster/check_access.sh new file mode 100755 index 0000000..d47c9b2 --- /dev/null +++ b/cluster/check_access.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# 用途:快速查看目前所有服務「怎麼連」 +# 不修改任何資源,純顯示資訊 + +set -euo pipefail + +echo +echo "=== Pods ===" +kubectl get pods -o wide + +echo +echo "=== Services ===" +kubectl get svc + +echo +echo "=== Node IPs ===" +kubectl get nodes -o wide | awk 'NR>1 {print $1 "\t" $6}' + +echo +echo "=== NodePort Access ===" + +NODE_IPS=$(kubectl get nodes -o jsonpath='{range .items[*]}{.status.addresses[?(@.type=="InternalIP")].address}{" "}{end}') +kubectl get svc --no-headers | awk '$2=="NodePort"' | while read -r name type clusterip external ports age; do + PORTS=$(echo "$ports" | tr ',' '\n' | awk -F: '{print $NF}') + for ip in $NODE_IPS; do + for p in $PORTS; do + echo "- $ip:$p ($name)" + done + done +done + +echo +echo "=== Ingress (HTTP/HTTPS) ===" +kubectl get ingress --no-headers | while read -r name class hosts address ports age; do + echo "- $name" + kubectl get ingress "$name" -o jsonpath='{range .spec.rules[*]}{.host}{"\n"}{end}' | while read -r host; do + TLS=$(kubectl get ingress "$name" -o jsonpath='{.spec.tls}') + if [[ -n "$TLS" && "$TLS" != "[]" ]]; then + echo " https://$host" + else + echo " http://$host" + fi + done +done + +echo +echo "=== PVC ===" +kubectl get pvc + diff --git a/cluster/check_nodes.sh b/cluster/check_nodes.sh new file mode 100755 index 0000000..10c397a --- /dev/null +++ b/cluster/check_nodes.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# 用途:檢查叢集節點狀態 + +set -euo pipefail + +kubectl get nodes -o wide + diff --git a/cluster/check_storage.sh b/cluster/check_storage.sh new file mode 100755 index 0000000..d6876e6 --- /dev/null +++ b/cluster/check_storage.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# 用途:檢查儲存狀態(PVC / Longhorn) + +set -euo pipefail + +echo "=== PVC ===" +kubectl get pvc + +if kubectl get ns longhorn-system >/dev/null 2>&1; then + echo + echo "=== Longhorn Volumes ===" + kubectl -n longhorn-system get volumes +fi +