Compare commits

...

5 Commits

Author SHA1 Message Date
ChenKaiLiuG
2927b431cc Add mc FRP 2026-03-28 22:06:06 +08:00
ChenKaiLiuG
d7bf9c2e85 Add mc FRP 2026-03-28 22:05:27 +08:00
ChenKaiLiuG
555e5e70f1 Remove unnessary files 2026-03-28 21:57:44 +08:00
ChenKaiLiuG
997d078b64 Add FRP 2026-02-28 04:38:23 +08:00
ChenKaiLiuG
12773633ce Rebase 2026-01-04 00:29:37 +08:00
22 changed files with 858 additions and 65 deletions

6
.gitignore vendored
View File

@@ -1 +1,5 @@
karylab-minecraft/example-server/
karylab-complex/example-server/
karylab-complex/others/
karylab-entrance/others/exchange.md
karylab-vps/exchange.md
vs-code-server/

View File

@@ -10,6 +10,9 @@ services:
- TZ=Asia/Taipei
ports:
- "8000:8443" # 管理介面
- "1110:1110"
- "1120:1120"
- "11100:11100"
volumes:
- /docker/minecraft/crafty/backups:/crafty/backups
- /docker/minecraft/crafty/logs:/crafty/logs
@@ -78,7 +81,7 @@ services:
- DOZZLE_TAILSIZE=500 # 日誌顯示行數
- DOZZLE_FILTER=name=crafty*|name=minecraft* # 只顯示 MC 相關容器
ports:
- "8020:8080" # 訪問即可看到所有容器日誌
- "8020:8080" # 訪問 http://localhost:8020 即可看到所有容器日誌
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
@@ -88,39 +91,17 @@ services:
limits:
memory: 256M
# 4. GoEdge 管理後台 + API 節點 + MySQL
# 官方建議使用 goedgelabs/goedge-admin 映像,內建 MariaDB
goedge-admin:
image: goedgelabs/goedge-admin:latest
container_name: goedge_admin
restart: unless-stopped
environment:
- TZ=Asia/Taipei
volumes:
- goedge_admin_mysql:/var/lib/mysql # MySQL 資料庫
- goedge_admin_data:/usr/local/goedge/edge-admin # Admin 設定 + 日誌 + API 設定
ports:
- "8030:7788" # 管理後台登入端口 (預設 admin/123456)
networks:
- minecraft_net
# 5. GoEdge 邊緣節點 (實際負責轉發的 Proxy)
goedge-node:
image: goedgelabs/goedge-node:latest
container_name: goedge_node
restart: unless-stopped
environment:
- TZ=Asia/Taipei
volumes:
- goedge_node_data:/usr/local/goedge/edge-node
ports:
- "1110-1200:1110-1200" # 預留 1110 到 1200 之間的 90 個端口
networks:
- minecraft_net
depends_on:
- goedge-admin
# 6. 備份服務 (可選) - 自動備份到雲端或其他位置
# 4. FRP 客戶端 (負責打通內網)
# frpc:
# image: snowdreamtech/frpc:latest
# container_name: frpc_tunnel
# restart: unless-stopped
# 使用 host 模式,讓 frpc 能直接抓到 Crafty 開出的所有子伺服器 port
# network_mode: "host"
# volumes:
# - /docker/minecraft/frpc/frpc.toml:/etc/frp/frpc.toml
# 5. 備份服務 (可選) - 自動備份到雲端或其他位置
# duplicati:
# image: lscr.io/linuxserver/duplicati:latest
# container_name: minecraft_backup
@@ -138,7 +119,7 @@ services:
# networks:
# - minecraft_net
# 7. Watchtower - 自動更新容器映像檔(可選)
# 6. Watchtower - 自動更新容器映像檔(可選)
# watchtower:
# image: containrrr/watchtower:latest
# container_name: watchtower_minecraft
@@ -160,7 +141,4 @@ networks:
volumes:
netdataconfig:
netdatalib:
netdatacache:
goedge_admin_mysql: # MySQL 資料庫(單獨備份用)
goedge_admin_data: # Admin 所有資料(設定 + 日誌 + API
goedge_node_data: # Node 所有資料(設定 + 日誌)
netdatacache:

View File

@@ -0,0 +1,153 @@
version: '3'
services:
# 1. Harbor Core Service
harbor-core:
image: docker.io/bitnami/harbor-core:latest
container_name: harbor-core
restart: always
environment:
- BITNAMI_DEBUG=false
- HARBOR_ADMIN_PASSWORD=${HARBOR_ADMIN_PASSWORD} # <--- 請修改管理員密碼
# Harbor 核心服務的資料庫連線設定
- HARBOR_DATABASE_HOST=harbor-db
- HARBOR_DATABASE_PORT_NUMBER=5432
- HARBOR_DATABASE_USER=bn_harbor
- HARBOR_DATABASE_PASSWORD=${HARBOR_GENERAL_PASSWORD}
- HARBOR_DATABASE_NAME=bitnami_harbor
# Redis 連線設定
- HARBOR_REDIS_HOST=harbor-redis
- HARBOR_REDIS_PORT_NUMBER=6379
- HARBOR_REDIS_PASSWORD=${HARBOR_GENERAL_PASSWORD}
# 重要:如果有域名,請改成 https://你的域名 (NPM有處理不加埠號);只是內網用,改成 http://你的IP:port
- HARBOR_EXTERNAL_URL=http://192.168.10.100:7700
- HARBOR_REGISTRY_URL=http://harbor-registry:5000
# Notary 服務設定(如果不使用 Notary可以忽略這些設定
- HARBOR_NOTARY_SERVER_HOSTNAME=harbor-notary-server
- HARBOR_NOTARY_SIGNER_HOSTNAME=harbor-notary-signer
depends_on:
- harbor-db
- harbor-redis
- harbor-registry
volumes:
- /mnt/data/External/harbor/core:/data
networks:
- harbor-network
# 2. Harbor Portal Service
harbor-portal:
image: docker.io/bitnami/harbor-portal:latest
container_name: harbor-portal
restart: always
ports:
- "7700:8080" # HTTP 入口 (NPM 反代請指到這裡)
- "7750:8443" # HTTPS 入口
environment:
- BITNAMI_DEBUG=false
- HARBOR_PORTAL_API_URL=http://harbor-core:8080/api
- NGINX_HTTP_PORT_NUMBER=8080
- NGINX_HTTPS_PORT_NUMBER=8443
networks:
- harbor-network
depends_on:
- harbor-core
# 3. Harbor Job Service
harbor-jobservice:
image: docker.io/bitnami/harbor-jobservice:latest
container_name: harbor-jobservice
restart: always
environment:
- BITNAMI_DEBUG=false
- HARBOR_JOBSERVICE_DATABASE_HOST=harbor-db
- HARBOR_JOBSERVICE_DATABASE_USER=bn_harbor
- HARBOR_JOBSERVICE_DATABASE_PASSWORD=${HARBOR_GENERAL_PASSWORD}
- HARBOR_JOBSERVICE_DATABASE_NAME=bitnami_harbor
- HARBOR_JOBSERVICE_REDIS_HOST=harbor-redis
- HARBOR_JOBSERVICE_REDIS_PORT_NUMBER=6379
- HARBOR_JOBSERVICE_REDIS_PASSWORD=${HARBOR_GENERAL_PASSWORD}
- HARBOR_JOBSERVICE_CORE_URL=http://harbor-core:8080
depends_on:
- harbor-core
- harbor-redis
volumes:
- harbor-jobservice:/var/log/jobs
networks:
- harbor-network
# 4. Harbor Registry Service (映像檔儲存)
harbor-registry:
image: docker.io/bitnami/harbor-registry:latest
container_name: harbor-registry
restart: always
environment:
- BITNAMI_DEBUG=false
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/storage
- REGISTRY_HTTP_ADDR=:5000
- REGISTRY_REDIS_HOST=harbor-redis
- REGISTRY_REDIS_PORT=6379
- REGISTRY_REDIS_PASSWORD=${HARBOR_GENERAL_PASSWORD}
- REGISTRY_AUTH_TOKEN_REALM=http://harbor-core:8080/service/token
# 為了方便,這裡關閉了部分複雜的安全驗證,依賴 Harbor Core 控制
volumes:
- /mnt/data/External/harbor/registry:/storage
networks:
- harbor-network
depends_on:
- harbor-redis
# 5. Harbor Registry Controller
harbor-registryctl:
image: docker.io/bitnami/harbor-registryctl:latest
container_name: harbor-registryctl
restart: always
environment:
- BITNAMI_DEBUG=false
- REGISTRYCTL_REGISTRY_HOST=harbor-registry
- REGISTRYCTL_REGISTRY_PORT=5000
- REGISTRYCTL_REDIS_HOST=harbor-redis
- REGISTRYCTL_REDIS_PORT=6379
- REGISTRYCTL_REDIS_PASSWORD=${HARBOR_GENERAL_PASSWORD}
- REGISTRYCTL_CHECK_INTERVAL=30m
depends_on:
- harbor-registry
- harbor-redis
volumes:
- /mnt/data/External/harbor/registry:/storage
networks:
- harbor-network
# 6. Harbor Database (PostgreSQL資料庫)
harbor-db:
image: docker.io/bitnami/postgresql:latest
container_name: harbor-db
restart: always
environment:
- POSTGRESQL_USERNAME=bn_harbor
- POSTGRESQL_PASSWORD=${HARBOR_GENERAL_PASSWORD}
- POSTGRESQL_DATABASE=bitnami_harbor
volumes:
- harbor-db:/bitnami/postgresql
networks:
- harbor-network
# 7. Redis Cache (快取)
harbor-redis:
image: docker.io/bitnami/redis:latest
container_name: harbor-redis
restart: always
environment:
- REDIS_PASSWORD=${HARBOR_GENERAL_PASSWORD}
volumes:
- harbor-redis:/bitnami/redis/data
networks:
- harbor-network
volumes:
harbor-db:
harbor-redis:
harbor-jobservice:
networks:
harbor-network:
driver: bridge

View File

@@ -0,0 +1,42 @@
# =====================================================
# FRP Client (frpc) 設定
# 部署位置UCG 內網機器(與 npm container 同網段)
# 對接目標PVE VM 上的 frps具備固定 IP
# =====================================================
serverAddr = "your.vps.fixed.ip" # TODO: 填入 PVE VM 的固定 IP
serverPort = 7000 # 與 frps 的 bindPort 對應
# TLS 加密控制通道(與 frps 的 transport.tls.force = true 對應)
[transport]
tls.enable = true
# 連線驗證(需與 frps 的 auth token 相同)
[auth]
method = "token"
token = "your_strong_secret_token" # TODO: 改為強密碼frps 端要一致
# -------------------------------------------------------
# HTTP (Port 80) → NPM
# -------------------------------------------------------
[[proxies]]
name = "npm-http"
type = "tcp"
localIP = "npm" # 同 frp_network 內直接用 container name
localPort = 80
remotePort = 80
# -------------------------------------------------------
# HTTPS (Port 443) → NPM
# -------------------------------------------------------
[[proxies]]
name = "npm-https"
type = "tcp"
localIP = "npm"
localPort = 443
remotePort = 443
# -------------------------------------------------------
# NPM 管理後台 (Port 81) 僅供內網存取,不對外 tunnel
# 請直接在內網透過 http://內網機器IP:81 登入
# -------------------------------------------------------

View File

@@ -0,0 +1,50 @@
log:
level: info
runner:
file: .runner
capacity: 1
# 關鍵:在 runner.envs 注入環境變數給所有 Job 容器
envs:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_VERIFY: ""
env_file: .env
timeout: 3h
shutdown_timeout: 0s
fetch_timeout: 0s
fetch_interval: 2s
# 這裡定義你的 Workflow 可以用的 runs-on 標籤
# 格式: "標籤名:docker://映像檔名"
labels:
- "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
- "ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04"
- "debian-latest:docker://node:16-bullseye"
cache:
enabled: true
dir: ""
host: ""
port: 0
container:
# 留空讓 act_runner 自動建立網路給 job 容器使用
network: ""
enable_ipv6: false
# 關鍵:指定 DinD 的 TCP 地址
# 文檔說明:如果非空且非 "-",將使用指定的 docker host
docker_host: "tcp://docker:2375"
privileged: true # DinD 需要 privileged 模式
# 让 job 容器能解析 hostname
# docker: DinD daemon (使用 gitea-net 中的固定 IP 172.24.0.11)
# server: Gitea 服务器 (使用固定 IP 172.24.0.10)
options: "--add-host=docker:172.24.0.11 --add-host=server:172.24.0.10"
workdir_parent: /workspace
# 允許掛載的路徑,設為無限制以免綁手綁腳
valid_volumes:
- '**'
force_pull: false
host:
workdir_parent: /workspace

View File

@@ -11,7 +11,8 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=gitea
networks:
- gitea-net
gitea-net:
ipv4_address: 172.24.0.3
volumes:
- postgres_db:/var/lib/postgresql/data
@@ -31,11 +32,14 @@ services:
- GITEA__database__PASSWD=${POSTGRES_PASSWORD}
# 啟用 Actions (關鍵設定)
- GITEA__actions__ENABLED=true
# 開啟內建 Registry 功能
- GITEA__packages__ENABLED=true
# 允許發送 webhook 到內部 IP
- GITEA__webhook__ALLOWED_HOST_LIST=*
networks:
- gitea-net
- webproxy
gitea-net:
ipv4_address: 172.24.0.10
webproxy: {}
depends_on:
- db
ports:
@@ -54,8 +58,19 @@ services:
privileged: true # DinD 必須開啟此權限才能運作
environment:
- DOCKER_TLS_CERTDIR= # 設為空字串以關閉 TLS簡化內部連線
# 允許連回 Gitea 的 Registry (因為是 HTTP)
# 設置 DNS 讓內部容器能解析 gitea-net 的 service name
command:
- "dockerd"
- "--host=unix:///var/run/docker.sock"
- "--host=tcp://0.0.0.0:2375"
- "--insecure-registry=172.24.0.10:3000"
- "--insecure-registry=server:3000"
- "--dns=172.24.0.1"
- "--dns=8.8.8.8"
networks:
- gitea-net
gitea-net:
ipv4_address: 172.24.0.11
volumes:
- gitea_docker_certs:/certs/client
- gitea_docker_data:/var/lib/docker # 持久化,避免重啟後又要重新 pull image
@@ -65,22 +80,21 @@ services:
image: gitea/act_runner:latest
container_name: gitea_runner
restart: always
networks:
gitea-net:
ipv4_address: 172.24.0.5
volumes:
- /mnt/data/External/gitea/runner_data:/data
environment:
- CONFIG_FILE=/data/config.yaml
# 注意Runner 需要註冊 Token我們在啟動後手動輸入一次即可
- GITEA_INSTANCE_URL=http://server:3000
# 關鍵修改:告訴 Runner 不要找 Socket而是用 TCP 連線到 docker 容器
- DOCKER_HOST=tcp://docker:2375
- GITEA_RUNNER_REGISTRATION_TOKEN=${REGISTRATION_TOKEN}
depends_on:
- server
- docker
networks:
- gitea-net
volumes:
- /mnt/data/External/gitea/runner_data:/data
environment:
- CONFIG_FILE=/data/config.yaml
# 注意Runner 需要註冊 Token我們在啟動後手動輸入一次即可
- GITEA_INSTANCE_URL=http://server:3000
# 關鍵修改:告訴 Runner 不要找 Socket而是用 TCP 連線到 docker 容器
- DOCKER_HOST=tcp://docker:2375
volumes:
postgres_db:
@@ -90,6 +104,9 @@ volumes:
networks:
gitea-net:
driver: bridge
ipam:
config:
- subnet: 172.24.0.0/16
# npm bridge
webproxy:
external: true

View File

@@ -1,5 +0,0 @@
version: '3.8'
services:
# 1. 核心郵件伺服器DMS v14+
mailserver:

View File

@@ -0,0 +1,103 @@
version: '3.8'
services:
# 1. Harbor 映像服務器 - 提供容器映像管理功能
harbor:
image: goharbor/harbor-core:v2.9.3
container_name: harbor-core
restart: always
ports:
- "5700:8080"
environment:
CORE_URL: http://harbor:8080
DATABASE_TYPE: postgresql
POSTGRES_HOST: harbor-db
POSTGRES_PORT: 5432
POSTGRES_USERNAME: ${POSTGRES_USERNAME}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: registry
REGISTRY_URL: http://harbor-registry:5000
REGISTRY_CONTROLLER_URL: http://harbor-registry:5000
LOG_LEVEL: info
PERMITTED_REGISTRY_TYPES: docker-registry
QUOTA_PER_PROJECT_ENABLED: "true"
READ_ONLY: "false"
volumes:
- harbor_core_data:/data
networks:
- harbor-network
- webproxy
depends_on:
- harbor-db
- harbor-registry
# 2. Harbor 資料庫 - 儲存 Harbor 的元數據
harbor-db:
image: postgres:14
container_name: harbor-db
restart: always
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: registry
volumes:
- harbor_db_data:/var/lib/postgresql/data
networks:
- harbor-network
# 3. Harbor Registry - 儲存容器映像的註冊表
harbor-registry:
image: registry:2
container_name: harbor-registry
restart: always
ports:
- "5600:5000"
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:5000
REGISTRY_HTTP_RELATIVEURLS: 'true'
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- /mnt/data/External/harbor/registry:/var/lib/registry
networks:
- harbor-network
# 4. BuildKit 編譯服務 - 用於高效構建容器映像
buildkitd:
image: moby/buildkit:latest
container_name: buildkitd
restart: always
privileged: true
ports:
- "5500:1234"
environment:
BUILDKIT_HOST: tcp://0.0.0.0:1234
volumes:
- buildkit_data:/var/lib/buildkit
networks:
- harbor-network
# 5. BuildKit Web UI - 提供 BuildKit 的 Web 界面
buildkit-ui:
image: tonistiiii/buildkit-ui:latest
container_name: buildkit-ui
restart: always
ports:
- "5400:8080"
environment:
BUILDKIT_HOST: tcp://buildkitd:1234
networks:
- harbor-network
- webproxy
depends_on:
- buildkitd
volumes:
harbor_core_data:
harbor_db_data:
buildkit_data:
buildkit_cache:
networks:
harbor-network:
driver: bridge
webproxy:
external: true

View File

@@ -112,10 +112,8 @@ services:
DRONE_SERVER_HOST: dronedocker.karylab.com
DRONE_SERVER_PROTO: https
DRONE_RPC_SECRET: ${DRONE_RANDOM_SECRET}
# Webhook 密鑰設定(必須和 Gitea Webhook 設定一致)
DRONE_WEBHOOK_SECRET: a1212416fb0515155c2c88f00a7879ad
# Gitea 配置 - 連接到本機 Gitea 服務
DRONE_GITEA_SERVER: http://git.karylab.com
DRONE_GITEA_SERVER: https://git.karylab.com
DRONE_GITEA_CLIENT_ID: ${GITEA_DRONE_CLIENT_ID:-drone_client}
DRONE_GITEA_CLIENT_SECRET: ${GITEA_DRONE_SECRET:-drone_secret}
volumes:

View File

@@ -0,0 +1,68 @@
# 工程師交接
## 1. 批量網頁容器
### coolify 方案說明:
**Coolify 是什麼:**
- 開源的容器部署和應用管理平台,類似 Heroku、Vercel
- 功能自動化部署、容器管理、多應用支持、環境變數管理、監控日誌、自動備份、SSL/TLS 證書管理
- 應用場景:自建 PaaS小型團隊部署多個項目在自己服務器上管理應用
**Coolify 核心架構:**
- coolify 主服務Web UI 管理後台,連接到 PostgreSQL 和 Redis
- coolify-proxyTraefik 反向代理,處理容器路由轉發
- coolify-dbPostgreSQL 數據庫存儲配置
- coolify-redisRedis 緩存服務
- 雙網絡coolify-infra 內部通信webproxy 跨 Stack 路由
**部署選項:**
1. **直接掛載 Socket 模式**(當前配置)
- 優點:簡單穩定,性能最好
- 缺點:安全風險高,給予容器完整 Docker 權限
2. **DinD (Docker-in-Docker) 模式**
- 原理Coolify 內部運行自己的 Docker daemon
- 問題性能開銷大、存儲驅動衝突、Cgroups 限制無效、鏡像存儲浪費、網路配置複雜、穩定性差
- 適用:輕量級用途(如 CI/CD不適合生產環境
3. **Agent 模式**(推薦)
- 架構:中央 Coolify 服務器 → 遠程機器上的 Coolify Agent → 該機器的 Docker
- 優點:解決權限問題、分布式部署、隔離性好、避免 DinD 問題、穩定性高
- 缺點需要額外機器、Agent 與 Coolify 需要網絡連通
- 適用場景:有多台服務器、無法給容器開放 socket、生產環境
### 目前問題:
**核心問題:環境安全策略不允許開放 `/var/run/docker.sock` 權限**
- Coolify 需要此權限來創建和管理容器、拉取鏡像、控制容器生命週期、查看日誌和狀態
- 當前三種方案都無法在同一台主機實施:
- 直接掛載 socket權限不開放 ❌
- DinD 模式Agent 容器仍需要 socket問題未解決 ❌
- 單機 AgentAgent 容器仍需要 socket權限問題依然存在 ❌
**解決方案:**
- **建議方案**:在另一台機器上部署 Coolify Agent避免在受限主機上運行需要 socket 的容器
- 需要確認是否有空閒機器或可開啟虛擬機
---
## 2. 單一多應用管理容器架構 (Multi-App Container 方案)
既然需求是「由一個容器統一管理眾多網頁」,但「不需要去生出新的子容器」,且「必須支援動態網頁並隔離資料夾」,這相當於把 **「虛擬主機 (Shared Hosting)」** 的概念封裝進單一個 Docker 容器裡。不用掛載 `docker.sock`,所有隔離都透過容器內的 Linux 作業系統層級來達成:
### 方案一:新一代多語言伺服器 NGINX Unit 容器化 (最推薦、最現代的解法)
**運作原理:**
部署一個官方的 **NGINX Unit** Docker 容器。只掛載一個根目錄 (例如 `/var/www/apps`),裡面再分 `app1/`, `app2/`。透過 REST API 告訴 NGINX Unit 新增站點,它會在容器內**原生地啟動獨立的隔離行程**。
**如何滿足條件與優點:**
- **免生子容器**:它不是 Docker而是一個能直接執行 PHP, Python, Node.js, Go 的萬能 App 伺服器,全在一個容器內搞定。
- **動態語言支援**:完美支援動態網頁。
- **嚴格隔離**:透過設定檔,你可以讓 `app1` 由容器內的 `user1` 執行、`app2``user2` 執行,彼此完全看不到對方的資料夾,連記憶體行程也徹底分開。
### 方案二:全能 Web 面板容器 (PaaS in a Box如 aaPanel / CyberPanel 容器版)
**運作原理:**
找一個將知名 Web 面板(例如 aaPanel 寶塔面板國際版、或 CyberPanel打包好的單一 Docker 容器。進入該容器的 Web 介面後,你就像在使用早期的 cPanel 虛擬主機一樣。
**如何滿足條件與優點:**
- **自帶管理介面**:跟 Coolify 一樣有好看的按鈕可以按,但它是用「新增網站、安裝 Node.js 環境」的方式,而不是跑出新的 Docker 容器。
- **權限與資料夾隔離**:這類面板的標準作法就是為每一個新增的網站建立獨立的 Linux 使用者,並利用 `open_basedir` 或是 `chroot` 將行程鎖死在該網站自己的目錄裡(如 `/www/wwwroot/site1`)。完全符合你的資料夾隔離需求。

View File

@@ -0,0 +1,180 @@
version: '3.8'
services:
# 1. Docker 編譯伺服器 - Docker-in-Docker隔離編譯環境
build-server:
image: docker:dind
container_name: docker-build-server
restart: always
privileged: true # DinD 必要權限
command:
- dockerd
- --host=unix:///var/run/docker.sock
- --host=tcp://0.0.0.0:2375
- --storage-driver=overlay2
# 允許來自內部的 HTTP 推送 (關鍵)
- --insecure-registry=registry:5000
environment:
DOCKER_TLS_CERTDIR: "" # 關閉 TLS 方便內部通訊
volumes:
- build-cache:/var/lib/docker
networks:
- rundeck-network
healthcheck:
test: ["CMD", "test", "-f", "/shared-bin/docker"]
interval: 5s
timeout: 2s
retries: 3
start_period: 15s
# 2. Docker Registry - 儲存編譯好的 Image
registry:
image: registry:2
container_name: local-registry
restart: always
ports:
- "5700:5000" # 外部 Portainer 拉取用 192.168.1.XX:5700
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:5000
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- /mnt/data/External/rundeck/registry_data:/var/lib/registry
networks:
- rundeck-network
depends_on:
build-server:
condition: service_healthy
# 3. Registry Web UI - 可視化管理鏡像
registry-ui:
image: joxit/docker-registry-ui:latest
container_name: docker-registry-ui
restart: always
ports:
- "5600:80"
environment:
REGISTRY_TITLE: "Docker Registry"
SINGLE_REGISTRY: 'true'
REGISTRY_SECURED: 'false'
DELETE_IMAGES: 'true'
SHOW_CATALOG_NB_TAGS: 'true'
NGINX_PROXY_PASS_URL: 'http://registry:5000'
networks:
- rundeck-network
depends_on:
- registry
# 4. Rundeck - 編譯任務編排(通過網路連接到 DinD不掛載宿主 socket
rundeck:
image: rundeck/rundeck:5.18.0
container_name: rundeck-builder
restart: always
ports:
- "5500:4440"
volumes:
- rundeck-data:/home/rundeck/server/data
- docker-bin:/usr/local/bin/docker-share
environment:
# 請修改為你的外部 IP (讓瀏覽器能正確轉址)
- RUNDECK_GRAILS_URL=http://192.168.10.100:5500
- RUNDECK_SERVER_FORWARDED=true
# DinD 連接參數 告訴 Rundeck 裡的 docker 指令去連線 build-server
- DOCKER_HOST=tcp://build-server:2375
- DOCKER_TLS_VERIFY=
# 將掛載進來的 docker 資料夾加入 PATH這樣就能直接打 docker 指令了
- PATH=/usr/local/bin/docker-share:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 資料庫設定
- RUNDECK_DATABASE_URL=jdbc:mysql://rundeck-db/rundeck?autoReconnect=true&useSSL=false
- RUNDECK_DATABASE_USERNAME=rundeck
- RUNDECK_DATABASE_PASSWORD=${ROOT_PASSWORD}
- RUNDECK_DATABASE_DRIVER=org.mariadb.jdbc.Driver
networks:
- rundeck-network
- webproxy
depends_on:
- build-server
- rundeck-db
# 5. Rundeck 資料庫 - 儲存 Rundeck 的任務和設定
rundeck-db:
image: mariadb:10
container_name: rundeck-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
MYSQL_DATABASE: rundeck
MYSQL_USER: rundeck
MYSQL_PASSWORD: ${ROOT_PASSWORD}
volumes:
- rundeck-db-data:/var/lib/mysql
networks:
- rundeck-network
# 6. 編譯伺服器垃圾回收 - 通過 TCP 清理編譯緩存和未使用的層
build-server-gc:
image: docker:cli
container_name: docker-build-gc
restart: always
environment:
DOCKER_HOST: tcp://build-server:2375
networks:
- rundeck-network
entrypoint: |
sh -c 'while true; do
echo "[$(date)] 清理構建緩存..." >> /proc/1/fd/1
docker builder prune -af || true
echo "[$(date)] 清理懸空鏡像..." >> /proc/1/fd/1
docker image prune -af || true
echo "[$(date)] 清理完成24小時後再執行..." >> /proc/1/fd/1
sleep 86400
done'
depends_on:
build-server:
condition: service_healthy
# 7. Registry 垃圾回收 - 定期清理未被引用的 blobs
registry-gc:
image: registry:2
container_name: docker-registry-gc
restart: always
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:5000
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- /mnt/data/External/rundeck/registry_data:/var/lib/registry
- registry_gc_logs:/tmp
networks:
- rundeck-network
entrypoint: |
sh -c 'while true; do
echo "[$(date)] 執行 Registry 垃圾回收..." >> /tmp/gc.log
registry garbage-collect /etc/docker/registry/config.yml >> /tmp/gc.log 2>&1
echo "[$(date)] Registry 垃圾回收完成24小時後再執行..." >> /tmp/gc.log
sleep 86400
done'
depends_on:
- registry
# 7. Docker CLI Loader (Portainer 專用技巧)
# 因為不能用 Dockerfile開一個容器把 docker 執行檔複製出來
docker-cli-loader:
image: docker:cli
container_name: tool-docker-loader
# 執行完複製就自動退出,不需要 restart
restart: "no"
command: sh -c "cp /usr/local/bin/docker /shared-bin/docker && chmod +x /shared-bin/docker"
volumes:
- docker-bin:/shared-bin
volumes:
build-cache:
docker-bin:
rundeck-data:
rundeck-db-data:
registry_gc_logs:
networks:
rundeck-network:
driver: bridge
webproxy:
external: true

View File

@@ -0,0 +1,43 @@
version: "3.9"
services:
frpc:
image: snowdreamtech/frpc:latest
container_name: frpc
restart: unless-stopped
networks:
- frp_network
volumes:
- /opt/cloudflare/frp/frpc.toml:/etc/frp/frpc.toml:ro
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "81:81"
- "443:443"
networks:
- frp_network
- webproxy
volumes:
- /opt/cloudflare/npm/data:/data
- /opt/cloudflare/npm/letsencrypt:/etc/letsencrypt
filebrowser:
image: filebrowser/filebrowser:latest
container_name: filebrowser
restart: unless-stopped
ports:
- "18080:80"
networks:
- frp_network
volumes:
- /opt/cloudflare/files:/srv
networks:
frp_network:
driver: bridge
webproxy:
external: true

View File

@@ -0,0 +1,23 @@
# =====================================================
# FRP Server (frps) 設定
# 部署位置PVE VM具備固定 IP
# =====================================================
bindPort = 7000 # 與 frpc 的 serverPort 對應
# TLS 加密控制通道(強制要求 frpc 必須使用 TLS 連線)
[transport.tls]
force = true # 拒絕所有非 TLS 的 frpc 連線
# 連線驗證(需與 frpc 的 token 相同)
[auth]
method = "token"
token = "your_strong_secret_token" # TODO: 改為強密碼,需與 frpc.toml 一致
# Dashboard可選
# 若要啟用 frps web 管理介面,取消下方註解
# [webServer]
# addr = "0.0.0.0"
# port = 7500
# user = "admin"
# password = "your_dashboard_password"

122
karylab-vps/exchange.md Normal file
View File

@@ -0,0 +1,122 @@
# 工程師交接
## 1. cloudflared -> vps tunnel已完成
### 實作結果採用方案一FRP
#### 檔案清單
| 檔案 | 位置 | 說明 |
|---|---|---|
| `vpstunnel+npm.yml` | `karylab-entrance/` | 內網 compose stackfrpc + npm + filebrowser |
| `frpc.toml` | `karylab-entrance/config/` | frpc 設定,掛載至 frpc container |
| `vps-tunnel.yml` | `karylab-vps/` | VPS compose stackfrps |
| `frps.toml` | `karylab-vps/config/` | frps 設定,掛載至 frps container |
#### 架構說明
```
[外部訪客]
│ TCP 80/443
[PVE VM固定 IP]
└─ frps container
│ TLS 加密隧道Port 7000
[UCG 內網]
└─ frpc container ─→ npm containerPort 80/443
NAS / 其他內網服務
```
#### 安全設計
- **控制通道 TLS**frps 強制 `transport.tls.force = true`frpc 開啟 `tls.enable = true`,控制通道全程加密
- **Token 驗證**frpc 與 frps 使用 HMAC-SHA256 簽名的 token 驗證身份
- **NPM 管理後台Port 81不對外 tunnel**:只能從內網直接存取 `http://內網機器IP:81`
- **建議**:在 VPS 防火牆UFW / iptables限制 Port 7000 只允許內網出口 IP 連入
#### 部署注意事項
1. **VPS 防火牆**必須開放 inbound TCP Port **80、443、7000**,否則 frpc 會出現 `i/o timeout`
2. `frpc.toml``serverAddr` 填入 VPS 固定 IP 或可解析的網域皆可
3. `frpc.toml``frps.toml``token` 必須設定為**同一組強密碼**
4. 設定檔路徑:
- VPS`/opt/frp/frps.toml`
- 內網:`/opt/cloudflare/frp/frpc.toml`
---
### 目標:
原來是cloudflare tunnel對接npm 再讓npm去找相關的設備
直接把tunnel換成其他容器 然後對接vps(pve上的vm)
### 方案說明:
部署在 PVE 上的那台「固定 IP VM」當作私有的 Cloudflare 節點(也就是您的自建 VPS然後用特定的「隧道容器」來取代 cloudflared+npm.yml 中的 cloudflared打通兩個隔離的網段。新的版本更新到 vpstunnel+npm.yml。
要達成這個目的,有兩種主流的開源工具/容器可以完美取代 Cloudflare Tunnel以下為您分析兩種作法
**方案一:使用 FRP (Fast Reverse Proxy) —— 最直接的 1:1 替換**
FRP 的運作邏輯與 Cloudflare Tunnel 幾乎一模一樣,只是伺服器從 Cloudflare 變成了您的 PVE VM。
架構配置:
```
前端 (您的 PVE VM - 具備固定 IP):安裝並運行 frps (FRP Server) 容器。它負責監聽來自網際網路的請求(如 Port 80, 443, 25565
後端 (您的 UCG 網段內):部署一個 frpc (FRP Client) 容器。這個容器會主動向 frps 發起連線,建立一條加密隧道。
路由分發frpc 收到流量後,將 HTTP/HTTPS 流量轉交給同在 UCG 網段內的 NPM (Nginx Proxy Manager),再由 NPM 去找 UCG 底下的 NAS 或其他設備。
```
優點:架構與 Cloudflare Tunnel 概念完全一致,完全不用開 Port因為是 frpc 由內向外主動連線的。
缺點:設定檔是基於 ini/yaml需要稍微學習一下語法。
參考資源FRP 官方 GitHub 專案庫與文件
**方案二:使用 WireGuard 或 Tailscale —— 網路層的虛擬區網 (推薦)**
與其只代理特定 Port不如在 PVE VM 和 UCG 網段之間建立一個專屬的「虛擬私有區域網路 (VPN)」。
架構配置:
```
前端 (您的 PVE VM):安裝 NPM 以及 Tailscale (或 WireGuard) 容器。將 Zyxel 的 80/443 Port Forwarding 到這個 VM。
後端 (您的 UCG 網段內):只需要在負責提供服務的機器(或 NPM上安裝 Tailscale讓它們加入同一個虛擬網路。
路由分發:當外部訪客連線到您的固定 IP (PVE VM) 時VM 上的 NPM 會解析網域,並透過 Tailscale 給的虛擬 IP例如 100.x.x.x將流量安全地轉發到 UCG 底下的設備。
```
優點:
極致的隔離Tailscale / WireGuard 使用點對點加密,流量穿梭在 Zyxel 網段與 UCG 網段之間時是完全加密的。
管理方便:所有伺服器都在同一個虛擬網段內,互相溝通就像在同一個 Switch 下一樣簡單。
參考資源Tailscale 官方網站、WireGuard 官方網站
**流量動線解析 (以方案二為例)**
假設訪客要訪問您在 UCG 網段下的 Nextcloud
外部訪客輸入 cloud.example.com。
DNS (Cloudflare 灰雲) 將網域解析到您的 中華電信固定 IP。
流量抵達 vps 側路由器,透過 Port Forwarding 送入 PVE VM (自建 VPS) 的 Port 443。
PVE VM 上的 NPM 接收到請求,查看設定規則,發現要把流量送給 Nextcloud。
流量進入 隧道容器 (WireGuard/Tailscale),被加密打包。
加密封包穿越實體網路,抵達 UCG 網段下的目標機器。
目標機器解密封包,交由 Nextcloud 容器 處理。
這樣做的好處?
擺脫限制:完全沒有 Cloudflare 的 100MB 檔案大小限制,也沒有任何協議的限制(您可以傳輸 TCP, UDP, 甚至跑自訂協議)。
完美隔離:您的 UCG 路由器不需要開放任何對外 Port (Inbound 規則維持全擋),所有的外部威脅第一時間都是由 Zyxel 底下的 PVE VM 承受。就算 VM 被攻破,攻擊者也無法直接看見 UCG 底下的設備(除非他們破解了加密隧道)。
掌控權資料不再經過第三方的邊緣節點解密Cloudflare 橘雲會解密 HTTPS 流量),真正的端到端都在您自己的掌控之下。

View File

@@ -0,0 +1,17 @@
version: "3.9"
services:
frps:
image: snowdreamtech/frps:latest
container_name: frps
restart: unless-stopped
ports:
- "80:80" # HTTP → 轉給內網 NPM
- "443:443" # HTTPS → 轉給內網 NPM
- "7000:7000" # frpc 控制通道(建議用防火牆限制來源 IP
volumes:
- /opt/frp/frps.toml:/etc/frp/frps.toml:ro
networks:
default:
driver: bridge