fix(apps): split OCI Mirror gateway and egress roles

This commit is contained in:
okxlin
2026-07-20 21:10:51 +08:00
parent 5906672669
commit 3993c3aabe
5 changed files with 302 additions and 192 deletions
+8 -2
View File
@@ -1,9 +1,9 @@
CONTAINER_NAME=oci-mirror
OCI_MIRROR_ROLE=gateway
PANEL_APP_PORT_HTTP=8443
OCI_MIRROR_GATEWAY_MANAGEMENT_IP=127.0.0.1
PANEL_APP_PORT_MANAGEMENT=19090
PANEL_APP_PORT_EGRESS=19443
OCI_MIRROR_PUBLIC_HOST=mirror.example.com
OCI_MIRROR_SOURCE_IPV4=
OCI_MIRROR_IP_ALLOWLIST=
OCI_MIRROR_CHINA_CIDR_FILE=
OCI_MIRROR_CHINA_CIDR_SOURCE=APNIC delegated statistics
@@ -12,5 +12,11 @@ OCI_MIRROR_TLS_ENABLED=false
OCI_MIRROR_TLS_DIR=
APP_DATA_DIR=./data
OCI_MIRROR_MANAGEMENT_TOKEN=
OCI_MIRROR_EGRESS_NODE_ID=egress-a
OCI_MIRROR_EGRESS_PROXY_URL=http://10.66.0.2:8443
OCI_MIRROR_SOURCE_IPV4=
OCI_MIRROR_EGRESS_LISTEN_IP=10.66.0.2
OCI_MIRROR_GATEWAY_MANAGEMENT_URL=http://10.66.0.1:19090
OCI_MIRROR_GATEWAY_ALLOWED_PEER=10.66.0.1/32
OCI_MIRROR_EGRESS_TOKEN=
TIME_ZONE=Asia/Shanghai
+94 -44
View File
@@ -1,30 +1,42 @@
additionalProperties:
formFields:
- default: gateway
edit: true
envKey: OCI_MIRROR_ROLE
labelEn: Node Role
labelZh: 节点角色
label: {en: Node Role, zh: 节点角色, zh-Hant: 節點角色, ja: ノードロール, ko: 노드 역할, ru: Роль узла, ms: Peranan Nod, pt-br: Função do Nó}
required: true
type: select
values:
- label: Gateway
value: gateway
- label: Egress
value: egress
- default: 8443
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: Public Gateway Port
labelZh: 公网 Gateway 端口
label: {en: Public Gateway Port, zh: 公网 Gateway 端口, zh-Hant: 公網 Gateway 連接埠, ja: 公開 Gateway ポート, ko: 공용 Gateway 포트, ru: Публичный порт Gateway, ms: Port Gateway Awam, pt-br: Porta Pública do Gateway}
labelEn: Node Service Port
labelZh: 节点服务端口
label: {en: Node Service Port, zh: 节点服务端口, zh-Hant: 節點服務連接埠, ja: ノードサービスポート, ko: 노드 서비스 포트, ru: Сервисный порт узла, ms: Port Perkhidmatan Nod, pt-br: Porta de Serviço do Nó}
required: true
rule: paramPort
type: number
- default: 127.0.0.1
edit: true
envKey: OCI_MIRROR_GATEWAY_MANAGEMENT_IP
labelEn: Gateway Management Listen IP
labelZh: Gateway 管理监听 IP
label: {en: Gateway Management Listen IP, zh: Gateway 管理监听 IP, zh-Hant: Gateway 管理監聽 IP, ja: Gateway 管理リッスン IP, ko: Gateway 관리 수신 IP, ru: IP управления Gateway, ms: IP Dengar Pengurusan Gateway, pt-br: IP de Escuta de Gerenciamento do Gateway}
required: false
type: text
- default: 19090
edit: true
envKey: PANEL_APP_PORT_MANAGEMENT
labelEn: Local Management Port
labelZh: 本地管理端口
label: {en: Local Management Port, zh: 本地管理端口, zh-Hant: 本機管理連接埠, ja: ローカル管理ポート, ko: 로컬 관리 포트, ru: Локальный порт управления, ms: Port Pengurusan Setempat, pt-br: Porta de Gerenciamento Local}
required: true
rule: paramPort
type: number
- default: 19443
edit: true
envKey: PANEL_APP_PORT_EGRESS
labelEn: Local Egress Port
labelZh: 本地 Egress 端口
label: {en: Local Egress Port, zh: 本地 Egress 端口, zh-Hant: 本機 Egress 連接埠, ja: ローカル Egress ポート, ko: 로컬 Egress 포트, ru: Локальный порт Egress, ms: Port Egress Setempat, pt-br: Porta Egress Local}
required: true
labelEn: Gateway Management Port
labelZh: Gateway 管理端口
label: {en: Gateway Management Port, zh: Gateway 管理端口, zh-Hant: Gateway 管理連接埠, ja: Gateway 管理ポート, ko: Gateway 관리 포트, ru: Порт управления Gateway, ms: Port Pengurusan Gateway, pt-br: Porta de Gerenciamento do Gateway}
required: false
rule: paramPort
type: number
- default: mirror.example.com
@@ -33,15 +45,7 @@ additionalProperties:
labelEn: Registry Host
labelZh: Registry 主机名
label: {en: Registry Host, zh: Registry 主机名, zh-Hant: Registry 主機名稱, ja: Registry ホスト, ko: Registry 호스트, ru: Хост Registry, ms: Hos Registry, pt-br: Host do Registry}
required: true
type: text
- default: ''
edit: true
envKey: OCI_MIRROR_SOURCE_IPV4
labelEn: Egress Public IPv4
labelZh: 出口公网 IPv4
label: {en: Egress Public IPv4, zh: 出口公网 IPv4, zh-Hant: 出口公網 IPv4, ja: 送信元グローバル IPv4, ko: 송신 공인 IPv4, ru: Публичный исходящий IPv4, ms: IPv4 Awam Keluar, pt-br: IPv4 Público de Saída}
required: true
required: false
type: text
- default: ''
edit: true
@@ -57,7 +61,7 @@ additionalProperties:
labelEn: Mainland China CIDR File
labelZh: 中国大陆 CIDR 文件
label: {en: Mainland China CIDR File, zh: 中国大陆 CIDR 文件, zh-Hant: 中國大陸 CIDR 檔案, ja: 中国本土 CIDR ファイル, ko: 중국 본토 CIDR 파일, ru: Файл CIDR материкового Китая, ms: Fail CIDR Tanah Besar China, pt-br: Arquivo CIDR da China Continental}
required: true
required: false
type: text
- default: APNIC delegated statistics
edit: true
@@ -65,7 +69,7 @@ additionalProperties:
labelEn: CIDR Dataset Source
labelZh: CIDR 数据集来源
label: {en: CIDR Dataset Source, zh: CIDR 数据集来源, zh-Hant: CIDR 資料集來源, ja: CIDR データセット出典, ko: CIDR 데이터셋 출처, ru: Источник набора CIDR, ms: Sumber Set Data CIDR, pt-br: Fonte do Conjunto CIDR}
required: true
required: false
type: text
- default: ''
edit: true
@@ -73,7 +77,7 @@ additionalProperties:
labelEn: CIDR Dataset Date
labelZh: CIDR 数据集日期
label: {en: CIDR Dataset Date, zh: CIDR 数据集日期, zh-Hant: CIDR 資料集日期, ja: CIDR データセット日付, ko: CIDR 데이터셋 날짜, ru: Дата набора CIDR, ms: Tarikh Set Data CIDR, pt-br: Data do Conjunto CIDR}
required: true
required: false
type: text
- default: 'false'
edit: true
@@ -81,7 +85,7 @@ additionalProperties:
labelEn: Enable Gateway TLS
labelZh: 启用 Gateway TLS
label: {en: Enable Gateway TLS, zh: 启用 Gateway TLS, zh-Hant: 啟用 Gateway TLS, ja: Gateway TLS を有効化, ko: Gateway TLS 활성화, ru: Включить TLS Gateway, ms: Dayakan TLS Gateway, pt-br: Ativar TLS do Gateway}
required: true
required: false
type: select
values:
- label: 'true'
@@ -91,35 +95,81 @@ additionalProperties:
- default: ''
edit: true
envKey: OCI_MIRROR_TLS_DIR
labelEn: TLS Source Directory
labelZh: TLS 源目录
label: {en: TLS Source Directory, zh: TLS 源目录, zh-Hant: TLS 來源目錄, ja: TLS ソースディレクトリ, ko: TLS 원본 디렉터리, ru: Каталог исходных TLS-файлов, ms: Direktori Sumber TLS, pt-br: Diretório de Origem TLS}
labelEn: Gateway TLS Source Directory
labelZh: Gateway TLS 源目录
label: {en: Gateway TLS Source Directory, zh: Gateway TLS 源目录, zh-Hant: Gateway TLS 來源目錄, ja: Gateway TLS ソースディレクトリ, ko: Gateway TLS 원본 디렉터리, ru: Каталог исходных TLS-файлов Gateway, ms: Direktori Sumber TLS Gateway, pt-br: Diretório de Origem TLS do Gateway}
required: false
type: text
- default: ./data
edit: true
envKey: APP_DATA_DIR
labelEn: Cache and State Directory
labelZh: 缓存与状态目录
label: {en: Cache and State Directory, zh: 缓存与状态目录, zh-Hant: 快取與狀態目錄, ja: キャッシュと状態ディレクトリ, ko: 캐시 및 상태 디렉터리, ru: Каталог кеша и состояния, ms: Direktori Cache dan Keadaan, pt-br: Diretório de Cache e Estado}
labelEn: Gateway Cache and State Directory
labelZh: Gateway 缓存与状态目录
label: {en: Gateway Cache and State Directory, zh: Gateway 缓存与状态目录, zh-Hant: Gateway 快取與狀態目錄, ja: Gateway キャッシュと状態ディレクトリ, ko: Gateway 캐시 및 상태 디렉터리, ru: Каталог кеша и состояния Gateway, ms: Direktori Cache dan Keadaan Gateway, pt-br: Diretório de Cache e Estado do Gateway}
required: true
type: text
- default: ''
edit: true
envKey: OCI_MIRROR_MANAGEMENT_TOKEN
labelEn: Management Token
labelZh: 管理 Token
label: {en: Management Token, zh: 管理 Token, zh-Hant: 管理 Token, ja: 管理 Token, ko: 관리 Token, ru: Токен управления, ms: Token Pengurusan, pt-br: Token de Gerenciamento}
description: {en: Use a unique 32-4096 byte token containing letters, digits, . _ ~ + / - and optional trailing =., zh: 使用独立的 32-4096 字节 Token,仅可含字母、数字、. _ ~ + / -,末尾可有 =。, zh-Hant: 使用獨立的 32-4096 位元組 Token,僅可含字母、數字、. _ ~ + / -,結尾可有 =。, ja: 32-4096 バイトの一意な Token を使用し、英数字と . _ ~ + / -、末尾の = のみを含めます。, ko: 고유한 32-4096바이트 Token을 사용하며 영문자, 숫자, . _ ~ + / - 및 끝의 =만 허용됩니다., ru: Уникальный токен длиной 32-4096 байт; допустимы буквы, цифры, . _ ~ + / - и = только в конце., ms: Gunakan Token unik 32-4096 bait dengan huruf, digit, . _ ~ + / - dan = pilihan di hujung., pt-br: Use um Token exclusivo de 32-4096 bytes com letras, dígitos, . _ ~ + / - e = opcional no final.}
required: true
labelEn: Gateway Management Token
labelZh: Gateway 管理 Token
label: {en: Gateway Management Token, zh: Gateway 管理 Token, zh-Hant: Gateway 管理 Token, ja: Gateway 管理 Token, ko: Gateway 관리 Token, ru: Токен управления Gateway, ms: Token Pengurusan Gateway, pt-br: Token de Gerenciamento do Gateway}
required: false
type: password
- default: egress-a
edit: true
envKey: OCI_MIRROR_EGRESS_NODE_ID
labelEn: Egress Node ID
labelZh: Egress 节点 ID
label: {en: Egress Node ID, zh: Egress 节点 ID, zh-Hant: Egress 節點 ID, ja: Egress ノード ID, ko: Egress 노드 ID, ru: ID узла Egress, ms: ID Nod Egress, pt-br: ID do Nó Egress}
required: true
type: text
- default: http://10.66.0.2:8443
edit: true
envKey: OCI_MIRROR_EGRESS_PROXY_URL
labelEn: Egress Private Proxy URL
labelZh: Egress 私网代理 URL
label: {en: Egress Private Proxy URL, zh: Egress 私网代理 URL, zh-Hant: Egress 私網代理 URL, ja: Egress プライベートプロキシ URL, ko: Egress 사설 프록시 URL, ru: Приватный URL прокси Egress, ms: URL Proksi Peribadi Egress, pt-br: URL Privada do Proxy Egress}
required: false
type: text
- default: ''
edit: true
envKey: OCI_MIRROR_SOURCE_IPV4
labelEn: Egress Public Source IPv4
labelZh: Egress 公网源 IPv4
label: {en: Egress Public Source IPv4, zh: Egress 公网源 IPv4, zh-Hant: Egress 公網來源 IPv4, ja: Egress グローバル送信元 IPv4, ko: Egress 공인 원본 IPv4, ru: Публичный исходящий IPv4 Egress, ms: IPv4 Sumber Awam Egress, pt-br: IPv4 Público de Origem do Egress}
required: false
type: text
- default: 10.66.0.2
edit: true
envKey: OCI_MIRROR_EGRESS_LISTEN_IP
labelEn: Egress Private Listen IP
labelZh: Egress 私网监听 IP
label: {en: Egress Private Listen IP, zh: Egress 私网监听 IP, zh-Hant: Egress 私網監聽 IP, ja: Egress プライベートリッスン IP, ko: Egress 사설 수신 IP, ru: Приватный IP прослушивания Egress, ms: IP Dengar Peribadi Egress, pt-br: IP Privado de Escuta do Egress}
required: false
type: text
- default: http://10.66.0.1:19090
edit: true
envKey: OCI_MIRROR_GATEWAY_MANAGEMENT_URL
labelEn: Gateway Private Management URL
labelZh: Gateway 私网管理 URL
label: {en: Gateway Private Management URL, zh: Gateway 私网管理 URL, zh-Hant: Gateway 私網管理 URL, ja: Gateway プライベート管理 URL, ko: Gateway 사설 관리 URL, ru: Приватный URL управления Gateway, ms: URL Pengurusan Peribadi Gateway, pt-br: URL Privada de Gerenciamento do Gateway}
required: false
type: text
- default: 10.66.0.1/32
edit: true
envKey: OCI_MIRROR_GATEWAY_ALLOWED_PEER
labelEn: Allowed Gateway IP/CIDR
labelZh: 允许的 Gateway IP/CIDR
label: {en: Allowed Gateway IP/CIDR, zh: 允许的 Gateway IP/CIDR, zh-Hant: 允許的 Gateway IP/CIDR, ja: 許可する Gateway IP/CIDR, ko: 허용된 Gateway IP/CIDR, ru: Разрешённый IP/CIDR Gateway, ms: IP/CIDR Gateway Dibenarkan, pt-br: IP/CIDR Permitido do Gateway}
required: false
type: text
- default: ''
edit: true
envKey: OCI_MIRROR_EGRESS_TOKEN
labelEn: Egress Token
labelZh: Egress Token
label: {en: Egress Token, zh: Egress Token, zh-Hant: Egress Token, ja: Egress Token, ko: Egress Token, ru: Токен Egress, ms: Token Egress, pt-br: Token Egress}
description: {en: Use a different 32-4096 byte token containing letters, digits, . _ ~ + / - and optional trailing =., zh: 使用另一个独立的 32-4096 字节 Token,仅可含字母、数字、. _ ~ + / -,末尾可有 =。, zh-Hant: 使用另一個獨立的 32-4096 位元組 Token,僅可含字母、數字、. _ ~ + / -,結尾可有 =。, ja: 別の 32-4096 バイト Token を使用し、英数字と . _ ~ + / -、末尾の = のみを含めます。, ko: 별도의 32-4096바이트 Token을 사용하며 영문자, 숫자, . _ ~ + / - 및 끝의 =만 허용됩니다., ru: Другой уникальный токен длиной 32-4096 байт; допустимы буквы, цифры, . _ ~ + / - и = только в конце., ms: Gunakan Token lain 32-4096 bait dengan huruf, digit, . _ ~ + / - dan = pilihan di hujung., pt-br: Use outro Token exclusivo de 32-4096 bytes com letras, dígitos, . _ ~ + / - e = opcional no final.}
labelEn: Egress Node Token
labelZh: Egress 节点 Token
label: {en: Egress Node Token, zh: Egress 节点 Token, zh-Hant: Egress 節點 Token, ja: Egress ノード Token, ko: Egress 노드 Token, ru: Токен узла Egress, ms: Token Nod Egress, pt-br: Token do Nó Egress}
required: true
type: password
- default: Asia/Shanghai
+7 -47
View File
@@ -1,7 +1,7 @@
services:
gateway:
oci-mirror:
image: "moelin/oci-mirror:0.1.2"
container_name: ${CONTAINER_NAME}-gateway
container_name: ${CONTAINER_NAME}
network_mode: host
init: true
working_dir: /tmp
@@ -24,58 +24,18 @@ services:
max-size: 10m
max-file: '3'
command:
- gateway
- ${OCI_MIRROR_ROLE}
- --config
- /etc/oci-mirror/gateway.json
- /etc/oci-mirror/runtime.json
environment:
- TZ=${TIME_ZONE}
- OCI_MIRROR_MANAGEMENT_TOKEN=${OCI_MIRROR_MANAGEMENT_TOKEN}
- OCI_MIRROR_EGRESS_TOKEN=${OCI_MIRROR_EGRESS_TOKEN}
volumes:
- ./config/gateway.json:/etc/oci-mirror/gateway.json:ro
- ./config/china-mainland.cidr:/etc/oci-mirror/china-mainland.cidr:ro
- ./config/tls:/etc/oci-mirror/tls:ro
- ./config:/etc/oci-mirror:ro
- ${APP_DATA_DIR}:/var/lib/oci-mirror
labels:
createdBy: Apps
oci-mirror.public-port: ${PANEL_APP_PORT_HTTP}
oci-mirror.role: ${OCI_MIRROR_ROLE}
oci-mirror.service-port: ${PANEL_APP_PORT_HTTP}
oci-mirror.management-port: ${PANEL_APP_PORT_MANAGEMENT}
egress:
image: "moelin/oci-mirror:0.1.2"
container_name: ${CONTAINER_NAME}-egress
network_mode: host
init: true
working_dir: /tmp
read_only: true
restart: unless-stopped
stop_grace_period: 30s
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
ulimits:
nofile:
soft: 65536
hard: 65536
tmpfs:
- /tmp:size=16m,mode=1777
logging:
driver: json-file
options:
max-size: 10m
max-file: '3'
command:
- egress
- --config
- /etc/oci-mirror/egress.json
environment:
- TZ=${TIME_ZONE}
- OCI_MIRROR_EGRESS_TOKEN=${OCI_MIRROR_EGRESS_TOKEN}
volumes:
- ./config/egress.json:/etc/oci-mirror/egress.json:ro
depends_on:
- gateway
labels:
createdBy: Apps
oci-mirror.egress-port: ${PANEL_APP_PORT_EGRESS}
+135 -69
View File
@@ -36,6 +36,24 @@ valid_ipv4() {
awk -F. 'NF != 4 { exit 1 } { for (i = 1; i <= 4; i++) if ($i !~ /^[0-9]+$/ || $i < 0 || $i > 255) exit 1 }' <<<"$1"
}
valid_private_or_loopback_ipv4() {
valid_ipv4 "$1" || return 1
awk -F. '$1 == 10 || $1 == 127 || ($1 == 172 && $2 >= 16 && $2 <= 31) || ($1 == 192 && $2 == 168) { exit 0 } { exit 1 }' <<<"$1"
}
valid_dns_name() {
[[ "$1" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$ && "$1" != *..* ]]
}
valid_ipv4_cidr() {
local address=${1%%/*}
local prefix=32
if [[ "$1" == */* ]]; then
prefix=${1##*/}
fi
valid_ipv4 "$address" && [[ "$prefix" =~ ^[0-9]+$ ]] && ((10#$prefix >= 0 && 10#$prefix <= 32))
}
valid_allowlist() {
[[ -z "$1" || "$1" =~ ^[A-Fa-f0-9.:/]+$ ]]
}
@@ -46,6 +64,25 @@ valid_bearer_token() {
((length >= 32 && length <= 4096)) && [[ "$1" =~ ^[A-Za-z0-9._~+/-]+=*$ ]]
}
valid_node_id() {
[[ "$1" =~ ^[a-z][a-z0-9_-]{0,31}$ ]]
}
valid_private_http_origin() {
local value=$1
local host
local port
[[ "$value" =~ ^http://([^/:]+):([0-9]+)$ ]] || return 1
host=${BASH_REMATCH[1]}
port=${BASH_REMATCH[2]}
valid_port "$port" || return 1
if valid_ipv4 "$host"; then
valid_private_or_loopback_ipv4 "$host"
else
valid_dns_name "$host"
fi
}
safe_data_dir() {
case "$1" in
/ | /bin | /boot | /dev | /etc | /home | /lib | /lib64 | /media | /mnt | /opt | /proc | /root | /run | /sbin | /srv | /sys | /tmp | /usr | /var | /var/lib | /workspace)
@@ -56,85 +93,103 @@ safe_data_dir() {
CIDR_SOURCE_PATTERN='^[A-Za-z0-9._:/ -]+$'
PUBLIC_PORT="$(read_env PANEL_APP_PORT_HTTP)"
MANAGEMENT_PORT="$(read_env PANEL_APP_PORT_MANAGEMENT)"
EGRESS_PORT="$(read_env PANEL_APP_PORT_EGRESS)"
PUBLIC_HOST="$(read_env OCI_MIRROR_PUBLIC_HOST)"
SOURCE_IPV4="$(read_env OCI_MIRROR_SOURCE_IPV4)"
IP_ALLOWLIST="$(read_env OCI_MIRROR_IP_ALLOWLIST)"
CIDR_FILE="$(read_env OCI_MIRROR_CHINA_CIDR_FILE)"
CIDR_SOURCE="$(read_env OCI_MIRROR_CHINA_CIDR_SOURCE)"
CIDR_UPDATED="$(read_env OCI_MIRROR_CHINA_CIDR_UPDATED)"
TLS_ENABLED="$(read_env OCI_MIRROR_TLS_ENABLED)"
TLS_ENABLED="${TLS_ENABLED:-false}"
TLS_ENABLED="${TLS_ENABLED,,}"
TLS_DIR="$(read_env OCI_MIRROR_TLS_DIR)"
DATA_DIR="$(absolute_path "$(read_env APP_DATA_DIR)")"
MANAGEMENT_TOKEN="$(read_env OCI_MIRROR_MANAGEMENT_TOKEN)"
ROLE="$(read_env OCI_MIRROR_ROLE)"
ROLE="${ROLE,,}"
SERVICE_PORT="$(read_env PANEL_APP_PORT_HTTP)"
NODE_ID="$(read_env OCI_MIRROR_EGRESS_NODE_ID)"
EGRESS_TOKEN="$(read_env OCI_MIRROR_EGRESS_TOKEN)"
DATA_DIR_VALUE="$(read_env APP_DATA_DIR)"
[[ -n "$DATA_DIR_VALUE" ]] || die 'APP_DATA_DIR must not be empty'
DATA_DIR="$(absolute_path "$DATA_DIR_VALUE")"
valid_port "$PUBLIC_PORT" || die 'PANEL_APP_PORT_HTTP must be a valid port'
valid_port "$MANAGEMENT_PORT" || die 'PANEL_APP_PORT_MANAGEMENT must be a valid port'
valid_port "$EGRESS_PORT" || die 'PANEL_APP_PORT_EGRESS must be a valid port'
[[ "$PUBLIC_PORT" != "$MANAGEMENT_PORT" && "$PUBLIC_PORT" != "$EGRESS_PORT" && "$MANAGEMENT_PORT" != "$EGRESS_PORT" ]] || die 'all three ports must be different'
if ! valid_ipv4 "$PUBLIC_HOST"; then
[[ "$PUBLIC_HOST" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$ && "$PUBLIC_HOST" == *.* ]] || die 'OCI_MIRROR_PUBLIC_HOST must be a DNS hostname or IPv4 address'
fi
valid_ipv4 "$SOURCE_IPV4" || die 'OCI_MIRROR_SOURCE_IPV4 must be an IPv4 address assigned to this host'
valid_allowlist "$IP_ALLOWLIST" || die 'OCI_MIRROR_IP_ALLOWLIST must contain one IP address or CIDR'
[[ -n "$CIDR_FILE" ]] || die 'OCI_MIRROR_CHINA_CIDR_FILE is required'
CIDR_FILE="$(absolute_path "$CIDR_FILE")"
[[ -f "$CIDR_FILE" ]] || die "CIDR file does not exist: $CIDR_FILE"
[[ -n "$CIDR_SOURCE" && -n "$CIDR_UPDATED" ]] || die 'CIDR source and update date are required'
[[ "$CIDR_SOURCE" =~ $CIDR_SOURCE_PATTERN ]] || die 'OCI_MIRROR_CHINA_CIDR_SOURCE contains invalid characters'
[[ "$CIDR_UPDATED" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || die 'OCI_MIRROR_CHINA_CIDR_UPDATED must use YYYY-MM-DD'
[[ "$TLS_ENABLED" == true || "$TLS_ENABLED" == false ]] || die 'OCI_MIRROR_TLS_ENABLED must be true or false'
safe_data_dir "$DATA_DIR" || die 'APP_DATA_DIR is too broad'
valid_bearer_token "$MANAGEMENT_TOKEN" || die 'OCI_MIRROR_MANAGEMENT_TOKEN must be a 32 to 4096 byte bearer token'
[[ "$ROLE" == gateway || "$ROLE" == egress ]] || die 'OCI_MIRROR_ROLE must be gateway or egress'
valid_port "$SERVICE_PORT" || die 'PANEL_APP_PORT_HTTP must be a valid port for the selected node role'
valid_node_id "$NODE_ID" || die 'OCI_MIRROR_EGRESS_NODE_ID must match ^[a-z][a-z0-9_-]{0,31}$'
valid_bearer_token "$EGRESS_TOKEN" || die 'OCI_MIRROR_EGRESS_TOKEN must be a 32 to 4096 byte bearer token'
[[ "$MANAGEMENT_TOKEN" != "$EGRESS_TOKEN" ]] || die 'management and Egress tokens must be different'
safe_data_dir "$DATA_DIR" || die 'APP_DATA_DIR is too broad'
[[ "$DATA_DIR" != "$ROOT_DIR" && "$DATA_DIR" != "$ROOT_DIR/config" && "$DATA_DIR" != "$ROOT_DIR/config/"* ]] || die 'APP_DATA_DIR must not point to the application or its config directory'
ALLOWLIST_JSON='[]'
if [[ -n "$IP_ALLOWLIST" ]]; then
[[ "$IP_ALLOWLIST" != *'"'* && "$IP_ALLOWLIST" != *$'\n'* ]] || die 'OCI_MIRROR_IP_ALLOWLIST contains invalid characters'
ALLOWLIST_JSON="[\"$IP_ALLOWLIST\"]"
fi
install -d -m 0750 "$ROOT_DIR/config" "$ROOT_DIR/config/tls" "$DATA_DIR"
TLS_JSON=''
install -d -m 0750 "$ROOT_DIR/config/tls"
if [[ "$TLS_ENABLED" == true ]]; then
[[ -n "$TLS_DIR" ]] || die 'OCI_MIRROR_TLS_DIR is required when TLS is enabled'
TLS_DIR="$(absolute_path "$TLS_DIR")"
[[ -f "$TLS_DIR/server.crt" && -f "$TLS_DIR/server.key" ]] || die 'TLS directory must contain server.crt and server.key'
install -m 0644 "$TLS_DIR/server.crt" "$ROOT_DIR/config/tls/server.crt"
install -m 0600 "$TLS_DIR/server.key" "$ROOT_DIR/config/tls/server.key"
TLS_JSON=$' "tls_cert_file": "/etc/oci-mirror/tls/server.crt",\n "tls_key_file": "/etc/oci-mirror/tls/server.key",'
else
rm -f -- "$ROOT_DIR/config/tls/server.crt" "$ROOT_DIR/config/tls/server.key"
fi
if [[ "$ROLE" == gateway ]]; then
MANAGEMENT_IP="$(read_env OCI_MIRROR_GATEWAY_MANAGEMENT_IP)"
MANAGEMENT_PORT="$(read_env PANEL_APP_PORT_MANAGEMENT)"
PUBLIC_HOST="$(read_env OCI_MIRROR_PUBLIC_HOST)"
EGRESS_PROXY_URL="$(read_env OCI_MIRROR_EGRESS_PROXY_URL)"
SOURCE_IPV4="$(read_env OCI_MIRROR_SOURCE_IPV4)"
IP_ALLOWLIST="$(read_env OCI_MIRROR_IP_ALLOWLIST)"
CIDR_FILE="$(read_env OCI_MIRROR_CHINA_CIDR_FILE)"
CIDR_SOURCE="$(read_env OCI_MIRROR_CHINA_CIDR_SOURCE)"
CIDR_UPDATED="$(read_env OCI_MIRROR_CHINA_CIDR_UPDATED)"
TLS_ENABLED="$(read_env OCI_MIRROR_TLS_ENABLED)"
TLS_ENABLED="${TLS_ENABLED:-false}"
TLS_ENABLED="${TLS_ENABLED,,}"
TLS_DIR="$(read_env OCI_MIRROR_TLS_DIR)"
MANAGEMENT_TOKEN="$(read_env OCI_MIRROR_MANAGEMENT_TOKEN)"
install -d -m 0750 "$ROOT_DIR/config" "$DATA_DIR"
install -m 0644 "$CIDR_FILE" "$ROOT_DIR/config/china-mainland.cidr"
chown 65532:65532 "$DATA_DIR"
chown -R 65532:65532 "$ROOT_DIR/config"
valid_private_or_loopback_ipv4 "$MANAGEMENT_IP" || die 'OCI_MIRROR_GATEWAY_MANAGEMENT_IP must be a private or loopback IPv4 address assigned to this host'
valid_port "$MANAGEMENT_PORT" || die 'PANEL_APP_PORT_MANAGEMENT must be a valid port for the Gateway role'
[[ "$SERVICE_PORT" != "$MANAGEMENT_PORT" ]] || die 'Gateway public and management ports must be different'
if ! valid_ipv4 "$PUBLIC_HOST"; then
if ! valid_dns_name "$PUBLIC_HOST" || [[ "$PUBLIC_HOST" != *.* ]]; then
die 'OCI_MIRROR_PUBLIC_HOST must be a DNS hostname or IPv4 address'
fi
fi
valid_private_http_origin "$EGRESS_PROXY_URL" || die 'OCI_MIRROR_EGRESS_PROXY_URL must be an HTTP origin on a private IPv4 address or private DNS name with an explicit port'
valid_ipv4 "$SOURCE_IPV4" || die 'OCI_MIRROR_SOURCE_IPV4 must be an IPv4 address assigned to the Egress host'
valid_allowlist "$IP_ALLOWLIST" || die 'OCI_MIRROR_IP_ALLOWLIST must contain one IP address or CIDR'
[[ -n "$CIDR_FILE" ]] || die 'OCI_MIRROR_CHINA_CIDR_FILE is required for the Gateway role'
CIDR_FILE="$(absolute_path "$CIDR_FILE")"
[[ -f "$CIDR_FILE" ]] || die "CIDR file does not exist: $CIDR_FILE"
[[ -n "$CIDR_SOURCE" && -n "$CIDR_UPDATED" ]] || die 'CIDR source and update date are required for the Gateway role'
[[ "$CIDR_SOURCE" =~ $CIDR_SOURCE_PATTERN ]] || die 'OCI_MIRROR_CHINA_CIDR_SOURCE contains invalid characters'
[[ "$CIDR_UPDATED" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || die 'OCI_MIRROR_CHINA_CIDR_UPDATED must use YYYY-MM-DD'
[[ "$TLS_ENABLED" == true || "$TLS_ENABLED" == false ]] || die 'OCI_MIRROR_TLS_ENABLED must be true or false'
valid_bearer_token "$MANAGEMENT_TOKEN" || die 'OCI_MIRROR_MANAGEMENT_TOKEN must be a 32 to 4096 byte bearer token for the Gateway role'
[[ "$MANAGEMENT_TOKEN" != "$EGRESS_TOKEN" ]] || die 'management and Egress tokens must be different'
cat > "$ROOT_DIR/config/gateway.json" <<EOF
ALLOWLIST_JSON='[]'
if [[ -n "$IP_ALLOWLIST" ]]; then
[[ "$IP_ALLOWLIST" != *'"'* && "$IP_ALLOWLIST" != *$'\n'* ]] || die 'OCI_MIRROR_IP_ALLOWLIST contains invalid characters'
ALLOWLIST_JSON="[\"$IP_ALLOWLIST\"]"
fi
TLS_JSON=''
if [[ "$TLS_ENABLED" == true ]]; then
[[ -n "$TLS_DIR" ]] || die 'OCI_MIRROR_TLS_DIR is required when Gateway TLS is enabled'
TLS_DIR="$(absolute_path "$TLS_DIR")"
[[ -f "$TLS_DIR/server.crt" && -f "$TLS_DIR/server.key" ]] || die 'TLS directory must contain server.crt and server.key'
install -m 0644 "$TLS_DIR/server.crt" "$ROOT_DIR/config/tls/server.crt"
install -m 0600 "$TLS_DIR/server.key" "$ROOT_DIR/config/tls/server.key"
TLS_JSON=$' "tls_cert_file": "/etc/oci-mirror/tls/server.crt",\n "tls_key_file": "/etc/oci-mirror/tls/server.key",'
else
rm -f -- "$ROOT_DIR/config/tls/server.crt" "$ROOT_DIR/config/tls/server.key"
fi
install -m 0644 "$CIDR_FILE" "$ROOT_DIR/config/china-mainland.cidr"
chown 65532:65532 "$DATA_DIR"
cat > "$ROOT_DIR/config/runtime.json" <<EOF
{
"gateway": {
"listen": "0.0.0.0:${PUBLIC_PORT}",
"listen": "0.0.0.0:${SERVICE_PORT}",
"data_dir": "/var/lib/oci-mirror",
${TLS_JSON}
"egress_proxy_url": "http://127.0.0.1:${EGRESS_PORT}",
"egress_token_env": "OCI_MIRROR_EGRESS_TOKEN",
"egress_transport": {"mode": "private"},
"management": {
"listen": "127.0.0.1:${MANAGEMENT_PORT}",
"listen": "${MANAGEMENT_IP}:${MANAGEMENT_PORT}",
"token_env": "OCI_MIRROR_MANAGEMENT_TOKEN",
"public_admin": false
},
"egress_control": {
"snapshot_validity_seconds": 3600,
"probe_interval_seconds": 300,
"probe_interval_seconds": 300
},
"egress_nodes": [{
"id": "${NODE_ID}",
"proxy_url": "${EGRESS_PROXY_URL}",
"token_env": "OCI_MIRROR_EGRESS_TOKEN",
"weight": 1,
"transport": {"mode": "private"},
"pools": [{
"id": "dockerhub",
"policy": "sticky",
@@ -143,7 +198,7 @@ ${TLS_JSON}
"cooldown_base_seconds": 60,
"cooldown_max_seconds": 3600
}]
},
}],
"trusted_proxies": [],
"registries": [{
"id": "dockerhub",
@@ -189,14 +244,24 @@ ${TLS_JSON}
}
}
EOF
else
EGRESS_LISTEN_IP="$(read_env OCI_MIRROR_EGRESS_LISTEN_IP)"
GATEWAY_MANAGEMENT_URL="$(read_env OCI_MIRROR_GATEWAY_MANAGEMENT_URL)"
GATEWAY_ALLOWED_PEER="$(read_env OCI_MIRROR_GATEWAY_ALLOWED_PEER)"
cat > "$ROOT_DIR/config/egress.json" <<EOF
valid_private_or_loopback_ipv4 "$EGRESS_LISTEN_IP" || die 'OCI_MIRROR_EGRESS_LISTEN_IP must be a private or loopback IPv4 address assigned to this host'
valid_private_http_origin "$GATEWAY_MANAGEMENT_URL" || die 'OCI_MIRROR_GATEWAY_MANAGEMENT_URL must be an HTTP origin on a private IPv4 address or private DNS name with an explicit port'
valid_ipv4_cidr "$GATEWAY_ALLOWED_PEER" || die 'OCI_MIRROR_GATEWAY_ALLOWED_PEER must be one IPv4 address or CIDR'
rm -f -- "$ROOT_DIR/config/china-mainland.cidr" "$ROOT_DIR/config/tls/server.crt" "$ROOT_DIR/config/tls/server.key"
cat > "$ROOT_DIR/config/runtime.json" <<EOF
{
"egress": {
"listen": "127.0.0.1:${EGRESS_PORT}",
"node_id": "${NODE_ID}",
"listen": "${EGRESS_LISTEN_IP}:${SERVICE_PORT}",
"token_env": "OCI_MIRROR_EGRESS_TOKEN",
"allowed_peers": ["127.0.0.1/32"],
"snapshot_url": "http://127.0.0.1:${MANAGEMENT_PORT}/v1/egress/snapshot",
"allowed_peers": ["${GATEWAY_ALLOWED_PEER}"],
"snapshot_url": "${GATEWAY_MANAGEMENT_URL}/v1/egress/nodes/${NODE_ID}/snapshot",
"transport": {"mode": "private"},
"snapshot_refresh_seconds": 60,
"max_concurrent_tunnels": 128,
@@ -204,6 +269,7 @@ cat > "$ROOT_DIR/config/egress.json" <<EOF
}
}
EOF
fi
chmod 0640 "$ROOT_DIR/config/gateway.json" "$ROOT_DIR/config/egress.json"
chown 65532:65532 "$ROOT_DIR/config/gateway.json" "$ROOT_DIR/config/egress.json"
chmod 0640 "$ROOT_DIR/config/runtime.json"
chown -R 65532:65532 "$ROOT_DIR/config"
+58 -30
View File
@@ -2,59 +2,87 @@
## 产品介绍
OCI Mirror 是一个只读 OCI 拉取缓存和固定源代理。本应用采用上游支持的单机双服务部署:Gateway 负责公网入口、访问策略、配额、缓存和状态,Egress 通过本机真实公网 IPv4 访问 Docker Hub。
OCI Mirror 是一个只读 OCI 拉取缓存和固定源代理。Gateway 负责公网入口、访问策略、配额、缓存和调度;Egress 负责从独立出口节点连接 Docker Hub。两种角色使用同一个应用包,但每次安装只启动一个服务,适合分别部署到不同的 1Panel 主机。
## 主要功能
- 为 Docker Hub 提供只读 Registry API、清单与 Blob 缓存。
- 使用独立 Egress 服务和指定的宿主机公网 IPv4 访问上游。
- 提供中国大陆 CIDR、额外放行名单、访客配额和全局并发限制。
- 持久化 SQLite 状态、缓存对象、用量和健康状态。
- Gateway 提供只读 Registry API、清单与 Blob 缓存、访问策略和配额。
- Egress 使用指定的宿主机公网 IPv4 访问 Docker Hub。
- 命名节点快照让 Gateway 与独立 Egress 节点保持一致的目标和源地址配置。
- Gateway 持久化 SQLite 状态、缓存对象、用量和健康状态。
## 安装前提
## 部署拓扑
本应用不是通用的一键镜像加速器。安装前必须准备:
```text
Docker 客户端 -> Gateway 节点 -> WireGuard/私网 -> Egress 节点 -> Docker Hub
```
1. 一个解析到服务器的 Registry 域名;受控测试可以使用服务器 IPv4。
2. 一个真实分配到服务器网卡的公网 IPv4,不能填写 NAT 出口探测值或未绑定地址。
3. 按上游文档生成的中国大陆 IPv4/IPv6 CIDR 文件。应用不会下载或内置可能过期的数据集。
4. 正式对外服务时,准备包含 `server.crt` 和 `server.key` 的目录,证书必须覆盖 Registry 域名。
5. 两个不同且长度为 32-4096 字节的高强度 Token,分别用于管理接口和 Gateway/Egress 通信。
1. 先在入口服务器安装 `Gateway` 角色。
2. 再在出口服务器安装 `Egress` 角色。
3. 两次安装必须使用相同的 Egress 节点 ID 和 Egress Token。
4. Gateway 的 Egress 私网代理 URL 必须指向 Egress 的私网监听 IP 与节点服务端口。
5. Egress 的 Gateway 私网管理 URL 必须指向 Gateway 的管理监听 IP 与端口。
当前包只配置 Docker Hub。GHCR、Quay、GCR、固定下载源、多 Egress、IPv6 出口池、WireGuard 和 mTLS 节点需要按上游部署文档进行专门规划,不应直接修改生成配置后期待面板升级保留。
一个 Gateway 可调度多个 Egress,但当前 1Panel 表单每个 Gateway 安装配置一个 Egress 节点。需要多出口节点时,应按上游文档扩展 Gateway 配置并规划独立升级维护流程。
## 访问说明
- Gateway 使用 host network,直接监听安装表单中的公网端口。
- 管理和 Egress 端口只绑定 `127.0.0.1`,不应对公网开放。
- 为避免依赖安装前已存在的证书文件,表单默认关闭 TLS。正式对外服务应启用 TLS 并填写证书目录;无 TLS 模式仅适用于受控测试,或由宿主机反向代理终止 TLS 且已正确配置客户端身份传递的场景。
- Docker 客户端只访问 Gateway 的公网端口,不直接访问 Egress。
- Gateway 管理端口和 Egress 代理端口只应通过可信私网互通。
- Gateway 默认关闭 TLS,仅适合受控测试或由宿主机反向代理终止 TLS 的部署;正式直接对外服务应启用 TLS。
- Docker 客户端需要信任 Gateway 证书,并按上游文档配置 Registry Mirror 或显式 Registry 地址。
## Introduction
## 安装前提
OCI Mirror is a read-only OCI pull-through cache and fixed-origin proxy. This package deploys the upstream-supported single-host Gateway and Egress topology for Docker Hub.
- Gateway 节点需要 Registry 域名、中国大陆 CIDR 文件、持久化目录和管理 Token。
- Egress 节点需要实际分配到该主机的公网 IPv4;该地址同时填写到 Gateway 的 `Egress 公网源 IPv4`。
- 两个节点之间需要 WireGuard、专用网络或其他可信私网。默认 private 模式不提供控制面 TLS,不能直接暴露到公网。
- Gateway 管理监听 IP 和 Egress 监听 IP 必须已分配到各自宿主机;默认示例地址需要按实际网络修改。
- 正式公开 Gateway 时,应启用 TLS 并提供包含 `server.crt` 和 `server.key` 的目录。
## Features
## 角色字段
- Proxies Docker Hub Registry API requests through a dedicated Egress service.
- Caches manifests and blobs with persistent SQLite state and usage accounting.
- Enforces mainland China CIDR policy, an optional allowlist, quotas, and concurrency limits.
- Supports an explicit Gateway TLS certificate directory for production deployments.
### Gateway
- `Gateway 管理监听 IP`:供 Egress 拉取节点配置快照的私网地址。`127.0.0.1` 仅适合本机测试。
- `Egress 私网代理 URL`:例如 `http://10.66.0.2:8443`。
- `Egress 公网源 IPv4`:实际绑定到远端 Egress 主机的公网源地址。
- `Egress 节点 ID` 与 `Egress 节点 Token`:必须和远端 Egress 安装一致。
### Egress
- `节点服务端口`:Gateway 角色下是公网 Registry 端口,Egress 角色下是私网代理端口。
- `Egress 私网监听 IP`:例如 WireGuard 地址 `10.66.0.2`。
- `Gateway 私网管理 URL`:例如 `http://10.66.0.1:19090`,应用会自动追加节点快照路径。
- `允许的 Gateway IP/CIDR`:限制可连接 Egress 的 Gateway 地址,例如 `10.66.0.1/32`。
- `Egress 节点 ID` 与 `Egress 节点 Token`:必须和 Gateway 安装一致。
## 持久化与升级
- SQLite、缓存对象和运行状态保存在 `APP_DATA_DIR`。
- 备份时同时保存数据目录、外部 CIDR 文件、TLS 证书/私钥和安装表单参数。
- `init.sh` 与 `upgrade.sh` 会根据表单重新生成 Gateway/Egress 配置;手工修改生成的 `config/*.json` 不属于稳定升级接口。
- 镜像采用固定标签 `moelin/oci-mirror:0.1.2`。当前公开仓库没有可用的 `latest` 标签。
- Gateway 的 SQLite、缓存对象、用量和健康状态保存在 `APP_DATA_DIR`。
- Egress 不保存业务数据;Compose 保留相同数据挂载以维持单一服务模板,但 Egress 运行模式不会使用该目录。
- 备份 Gateway 时同时保存数据目录、外部 CIDR 文件、TLS 证书和安装表单参数。
- `init.sh` 与 `upgrade.sh` 会按当前角色和表单重新生成 `config/runtime.json`;手工修改生成配置不属于稳定升级接口。
- 镜像固定为 `moelin/oci-mirror:0.1.2`。
## Introduction
OCI Mirror is a read-only OCI pull-through cache and fixed-origin proxy. This package installs one role per 1Panel instance: Gateway for the public entry, policy, cache, and scheduling, or Egress for outbound Docker Hub access from a separate node.
## Features
- Deploys Gateway and Egress independently on different 1Panel hosts.
- Caches Docker Hub manifests and blobs with persistent Gateway state.
- Distributes node-scoped configuration snapshots from Gateway to Egress.
- Restricts the private control path by listener address, peer CIDR, node ID, and a dedicated bearer token.
## 安全与网络风险
> **高风险网络权限:Gateway 与 Egress 使用宿主机网络。**
> **高风险网络权限:两个角色都使用宿主机网络。**
host network 是上游绑定真实出口源地址、回环控制端口及公网 Gateway 监听器的核心要求。应用保留上游的只读根文件系统、非 root UID `65532`、`cap_drop: ALL` 和 `no-new-privileges`,不挂载 Docker Socket,也不启用 privileged 模式。
host network 是上游绑定真实出口源地址、私网控制地址及 Gateway 公网监听器的核心要求。应用保留上游的只读根文件系统、非 root UID `65532`、`cap_drop: ALL` 和 `no-new-privileges`,不挂载 Docker Socket,也不启用 privileged 模式。
错误的域名、证书、出口地址、CIDR 或防火墙配置可能导致服务不可用、意外暴露或访问策略失效。正式使用前请完成上游的 Registry API、Docker 客户端、出口源地址和控制平面检查。
private 模式会校验 Gateway 管理地址、Egress 代理地址和允许的对端网段,但防火墙仍必须只允许两个节点通过可信私网互访。公网跨节点部署应按上游文档改用 mTLS,当前表单不自动配置证书身份。
## 资料