mirror of
https://github.com/okxlin/appstore.git
synced 2026-11-13 08:06:29 +00:00
Update opencloudeu/opencloud-rolling Docker tag to v9.1.0 (#8592)
* Update opencloudeu/opencloud-rolling Docker tag to v9.1.0 * Update app version [skip ci] * Fix OpenCloud Traefik routing without Docker socket Serve OpenCloud through a persistent file-provider route and remove Traefik Docker API access. Create the route only when absent so existing dynamic config is preserved, and avoid nested file mounts beneath the read-only dynamic-config bind mount. --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: github-action update-app-version <githubaction@githubaction.com> Co-authored-by: okxlin <61420215+okxlin@users.noreply.github.com>
This commit is contained in:
co-authored by
github-action update-app-version
okxlin
parent
6e1137875f
commit
96afa1b369
@@ -4,5 +4,4 @@ PANEL_APP_PORT_HTTP=4080
|
||||
OC_DOMAIN=cloud.opencloud.test
|
||||
INITIAL_ADMIN_PASSWORD=replace-with-a-strong-password
|
||||
APP_DATA_DIR=./data
|
||||
DOCKER_SOCKET_PATH=/var/run/docker.sock
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
http:
|
||||
routers:
|
||||
opencloud:
|
||||
entryPoints:
|
||||
- https
|
||||
rule: "Host(`@OC_DOMAIN@`)"
|
||||
service: opencloud
|
||||
tls: {}
|
||||
services:
|
||||
opencloud:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://opencloud:9200
|
||||
@@ -84,22 +84,6 @@ additionalProperties:
|
||||
pt-br: Diretório de dados
|
||||
required: true
|
||||
type: text
|
||||
- default: /var/run/docker.sock
|
||||
edit: true
|
||||
envKey: DOCKER_SOCKET_PATH
|
||||
labelEn: Docker Socket Path
|
||||
labelZh: Docker Socket 路径
|
||||
label:
|
||||
en: Docker Socket Path
|
||||
zh: Docker Socket 路径
|
||||
zh-Hant: Docker Socket 路徑
|
||||
ja: Docker Socket パス
|
||||
ko: Docker Socket 경로
|
||||
ru: Путь к Docker Socket
|
||||
ms: Laluan Docker Socket
|
||||
pt-br: Caminho do Docker Socket
|
||||
required: true
|
||||
type: text
|
||||
- default: Asia/Shanghai
|
||||
edit: true
|
||||
envKey: TZ
|
||||
|
||||
@@ -14,10 +14,6 @@ services:
|
||||
- --entryPoints.https.transport.respondingTimeouts.readTimeout=12h
|
||||
- --entryPoints.https.transport.respondingTimeouts.writeTimeout=12h
|
||||
- --entryPoints.https.transport.respondingTimeouts.idleTimeout=3m
|
||||
- --providers.docker.endpoint=unix:///var/run/docker.sock
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --providers.docker.network=${CONTAINER_NAME}-network
|
||||
- "--providers.docker.constraints=Label(`opencloud.instance`,`${CONTAINER_NAME}`)"
|
||||
- --providers.file.directory=/etc/traefik/dynamic
|
||||
- --providers.file.watch=true
|
||||
networks:
|
||||
@@ -29,7 +25,6 @@ services:
|
||||
- "${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
|
||||
- "${PANEL_APP_PORT_HTTPS}:${PANEL_APP_PORT_HTTPS}"
|
||||
volumes:
|
||||
- "${DOCKER_SOCKET_PATH}:/var/run/docker.sock:ro"
|
||||
- "${APP_DATA_DIR}/traefik/dynamic:/etc/traefik/dynamic:ro"
|
||||
- "${APP_DATA_DIR}/traefik/certs:/certs:ro"
|
||||
healthcheck:
|
||||
@@ -95,13 +90,6 @@ services:
|
||||
retries: 5
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
opencloud.instance: "${CONTAINER_NAME}"
|
||||
traefik.enable: "true"
|
||||
traefik.http.routers.opencloud.entrypoints: https
|
||||
traefik.http.routers.opencloud.rule: Host(`${OC_DOMAIN}`)
|
||||
traefik.http.routers.opencloud.service: opencloud
|
||||
traefik.http.routers.opencloud.tls: "true"
|
||||
traefik.http.services.opencloud.loadbalancer.server.port: "9200"
|
||||
|
||||
networks:
|
||||
opencloud-network:
|
||||
|
||||
@@ -9,6 +9,36 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_no_symlink_components() {
|
||||
local path="$1"
|
||||
local current="/"
|
||||
local part
|
||||
local -a components
|
||||
|
||||
[[ "$path" == /* ]] || fail "Path must be absolute: $path"
|
||||
IFS='/' read -r -a components <<< "$path"
|
||||
for part in "${components[@]}"; do
|
||||
case "$part" in
|
||||
"" | .) continue ;;
|
||||
..)
|
||||
if [[ "$current" == /*/* ]]; then
|
||||
current="${current%/*}"
|
||||
else
|
||||
current="/"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [[ "$current" == / ]]; then
|
||||
current="/$part"
|
||||
else
|
||||
current="$current/$part"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
[[ ! -L "$current" ]] || fail "Data path must not contain a symbolic link: $current"
|
||||
done
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
local value=""
|
||||
@@ -31,14 +61,24 @@ else
|
||||
fi
|
||||
APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW:-./data}"
|
||||
|
||||
if [[ ${OC_DOMAIN+x} ]]; then
|
||||
OC_DOMAIN_RAW="$OC_DOMAIN"
|
||||
else
|
||||
OC_DOMAIN_RAW="$(read_env_value OC_DOMAIN)"
|
||||
fi
|
||||
|
||||
[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty"
|
||||
case "$APP_DATA_DIR_RAW" in
|
||||
*$'\n'* | *$'\r'* | *'$'* | *'#'*) fail "APP_DATA_DIR contains unsupported dotenv characters" ;;
|
||||
esac
|
||||
|
||||
case "$APP_DATA_DIR_RAW" in
|
||||
/*) APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" ;;
|
||||
/*)
|
||||
ensure_no_symlink_components "$APP_DATA_DIR_RAW"
|
||||
APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")"
|
||||
;;
|
||||
*)
|
||||
ensure_no_symlink_components "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}"
|
||||
APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")"
|
||||
case "$APP_DATA_DIR_ABS" in
|
||||
"$ROOT_DIR" | "$ROOT_DIR"/*) ;;
|
||||
@@ -63,8 +103,38 @@ traefik_paths=(
|
||||
"$APP_DATA_DIR_ABS/traefik/certs"
|
||||
)
|
||||
|
||||
for path in "${opencloud_paths[@]}" "${traefik_paths[@]}"; do
|
||||
ensure_no_symlink_components "$path"
|
||||
done
|
||||
|
||||
mkdir -p -- "${opencloud_paths[@]}" "${traefik_paths[@]}"
|
||||
for path in "${opencloud_paths[@]}"; do
|
||||
[[ ! -L "$path" ]] || fail "OpenCloud data subdirectory must not be a symbolic link: $path"
|
||||
chown 1000:1000 -- "$path" 2>/dev/null || true
|
||||
done
|
||||
|
||||
[[ "$OC_DOMAIN_RAW" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$ ]] || fail "OC_DOMAIN must be a hostname without a scheme, port, or path"
|
||||
[[ ${#OC_DOMAIN_RAW} -le 253 ]] || fail "OC_DOMAIN is too long"
|
||||
|
||||
route_template="$ROOT_DIR/config/traefik/opencloud.yml"
|
||||
route_file="$APP_DATA_DIR_ABS/traefik/dynamic/1panel-opencloud-route.yml"
|
||||
[[ -f "$route_template" && ! -L "$route_template" ]] || fail "OpenCloud Traefik route template is missing or invalid"
|
||||
[[ ! -L "$route_file" ]] || fail "OpenCloud Traefik route must not be a symbolic link: $route_file"
|
||||
if [[ -e "$route_file" && ! -f "$route_file" ]]; then
|
||||
fail "OpenCloud Traefik route must be a regular file: $route_file"
|
||||
fi
|
||||
|
||||
if [[ ! -e "$route_file" ]]; then
|
||||
route_tmp="$(mktemp "${route_file}.tmp.XXXXXX")"
|
||||
if ! sed "s/@OC_DOMAIN@/${OC_DOMAIN_RAW}/g" "$route_template" > "$route_tmp"; then
|
||||
rm -f -- "$route_tmp"
|
||||
fail "Could not create OpenCloud Traefik route"
|
||||
fi
|
||||
chmod 0644 -- "$route_tmp"
|
||||
if ! ln -- "$route_tmp" "$route_file" 2>/dev/null; then
|
||||
if [[ ! -f "$route_file" || -L "$route_file" ]]; then
|
||||
rm -f -- "$route_tmp"
|
||||
fail "Could not create OpenCloud Traefik route"
|
||||
fi
|
||||
fi
|
||||
rm -f -- "$route_tmp"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
CONTAINER_NAME=opencloud
|
||||
PANEL_APP_PORT_HTTPS=4443
|
||||
PANEL_APP_PORT_HTTP=4080
|
||||
OC_DOMAIN=cloud.opencloud.test
|
||||
INITIAL_ADMIN_PASSWORD=replace-with-a-strong-password
|
||||
APP_DATA_DIR=./data
|
||||
TZ=Asia/Shanghai
|
||||
@@ -0,0 +1,5 @@
|
||||
password
|
||||
12345678
|
||||
123
|
||||
OpenCloud
|
||||
OpenCloud-1
|
||||
@@ -0,0 +1,51 @@
|
||||
directives:
|
||||
child-src:
|
||||
- '''self'''
|
||||
connect-src:
|
||||
- '''self'''
|
||||
- 'blob:'
|
||||
- 'https://${COMPANION_DOMAIN|companion.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'wss://${COMPANION_DOMAIN|companion.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
|
||||
- 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://update.opencloud.eu/'
|
||||
- 'https://tile.openstreetmap.org/'
|
||||
default-src:
|
||||
- '''none'''
|
||||
font-src:
|
||||
- '''self'''
|
||||
frame-ancestors:
|
||||
- '''self'''
|
||||
frame-src:
|
||||
- '''self'''
|
||||
- 'blob:'
|
||||
- 'https://embed.diagrams.net/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://${EURO_OFFICE_DOMAIN|euro-office.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://docs.opencloud.eu'
|
||||
- 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
img-src:
|
||||
- '''self'''
|
||||
- 'data:'
|
||||
- 'blob:'
|
||||
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
|
||||
- 'https://tile.openstreetmap.org/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://${EURO_OFFICE_DOMAIN|euro-office.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
manifest-src:
|
||||
- '''self'''
|
||||
media-src:
|
||||
- '''self'''
|
||||
object-src:
|
||||
- '''self'''
|
||||
- 'blob:'
|
||||
script-src:
|
||||
- '''self'''
|
||||
- '''unsafe-inline'''
|
||||
- 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
style-src:
|
||||
- '''self'''
|
||||
- '''unsafe-inline'''
|
||||
worker-src:
|
||||
- "'self'"
|
||||
- 'blob:'
|
||||
@@ -0,0 +1,13 @@
|
||||
http:
|
||||
routers:
|
||||
opencloud:
|
||||
entryPoints:
|
||||
- https
|
||||
rule: "Host(`@OC_DOMAIN@`)"
|
||||
service: opencloud
|
||||
tls: {}
|
||||
services:
|
||||
opencloud:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://opencloud:9200
|
||||
@@ -0,0 +1,102 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 4443
|
||||
edit: true
|
||||
envKey: PANEL_APP_PORT_HTTPS
|
||||
labelEn: HTTPS Port
|
||||
labelZh: HTTPS 端口
|
||||
label:
|
||||
en: HTTPS Port
|
||||
zh: HTTPS 端口
|
||||
zh-Hant: HTTPS 連接埠
|
||||
ja: HTTPS ポート
|
||||
ko: HTTPS 포트
|
||||
ru: HTTPS-порт
|
||||
ms: Port HTTPS
|
||||
pt-br: Porta HTTPS
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
- default: 4080
|
||||
edit: true
|
||||
envKey: PANEL_APP_PORT_HTTP
|
||||
labelEn: HTTP Redirect Port
|
||||
labelZh: HTTP 跳转端口
|
||||
label:
|
||||
en: HTTP Redirect Port
|
||||
zh: HTTP 跳转端口
|
||||
zh-Hant: HTTP 重新導向連接埠
|
||||
ja: HTTP リダイレクトポート
|
||||
ko: HTTP 리디렉션 포트
|
||||
ru: Порт перенаправления HTTP
|
||||
ms: Port Ubah Hala HTTP
|
||||
pt-br: Porta de redirecionamento HTTP
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
- default: cloud.opencloud.test
|
||||
edit: true
|
||||
envKey: OC_DOMAIN
|
||||
labelEn: OpenCloud Domain
|
||||
labelZh: OpenCloud 域名
|
||||
label:
|
||||
en: OpenCloud Domain
|
||||
zh: OpenCloud 域名
|
||||
zh-Hant: OpenCloud 網域
|
||||
ja: OpenCloud ドメイン
|
||||
ko: OpenCloud 도메인
|
||||
ru: Домен OpenCloud
|
||||
ms: Domain OpenCloud
|
||||
pt-br: Domínio do OpenCloud
|
||||
required: true
|
||||
type: text
|
||||
- default: ''
|
||||
edit: true
|
||||
envKey: INITIAL_ADMIN_PASSWORD
|
||||
labelEn: Initial Administrator Password
|
||||
labelZh: 初始管理员密码
|
||||
label:
|
||||
en: Initial Administrator Password
|
||||
zh: 初始管理员密码
|
||||
zh-Hant: 初始管理員密碼
|
||||
ja: 初期管理者パスワード
|
||||
ko: 초기 관리자 비밀번호
|
||||
ru: Начальный пароль администратора
|
||||
ms: Kata Laluan Pentadbir Awal
|
||||
pt-br: Senha inicial do administrador
|
||||
random: true
|
||||
required: true
|
||||
rule: paramComplexity
|
||||
type: password
|
||||
- default: ./data
|
||||
edit: true
|
||||
envKey: APP_DATA_DIR
|
||||
labelEn: Data Directory
|
||||
labelZh: 数据目录
|
||||
label:
|
||||
en: Data Directory
|
||||
zh: 数据目录
|
||||
zh-Hant: 資料目錄
|
||||
ja: データディレクトリ
|
||||
ko: 데이터 디렉터리
|
||||
ru: Каталог данных
|
||||
ms: Direktori Data
|
||||
pt-br: Diretório de dados
|
||||
required: true
|
||||
type: text
|
||||
- default: Asia/Shanghai
|
||||
edit: true
|
||||
envKey: TZ
|
||||
labelEn: Timezone
|
||||
labelZh: 时区
|
||||
label:
|
||||
en: Timezone
|
||||
zh: 时区
|
||||
zh-Hant: 時區
|
||||
ja: タイムゾーン
|
||||
ko: 시간대
|
||||
ru: Часовой пояс
|
||||
ms: Zon waktu
|
||||
pt-br: Fuso horário
|
||||
required: true
|
||||
type: text
|
||||
@@ -0,0 +1,98 @@
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v3.7.17"
|
||||
container_name: ${CONTAINER_NAME}-traefik
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --log.level=ERROR
|
||||
- --api.dashboard=false
|
||||
- --ping=true
|
||||
- --entryPoints.http.address=:${PANEL_APP_PORT_HTTP}
|
||||
- --entryPoints.http.http.redirections.entryPoint.to=https
|
||||
- --entryPoints.http.http.redirections.entryPoint.scheme=https
|
||||
- --entryPoints.https.address=:${PANEL_APP_PORT_HTTPS}
|
||||
- --entryPoints.https.transport.respondingTimeouts.readTimeout=12h
|
||||
- --entryPoints.https.transport.respondingTimeouts.writeTimeout=12h
|
||||
- --entryPoints.https.transport.respondingTimeouts.idleTimeout=3m
|
||||
- --providers.file.directory=/etc/traefik/dynamic
|
||||
- --providers.file.watch=true
|
||||
networks:
|
||||
opencloud-network:
|
||||
aliases:
|
||||
- ${OC_DOMAIN}
|
||||
1panel-network:
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
|
||||
- "${PANEL_APP_PORT_HTTPS}:${PANEL_APP_PORT_HTTPS}"
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}/traefik/dynamic:/etc/traefik/dynamic:ro"
|
||||
- "${APP_DATA_DIR}/traefik/certs:/certs:ro"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- traefik
|
||||
- healthcheck
|
||||
- --ping
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
start_period: 15s
|
||||
retries: 5
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
|
||||
opencloud:
|
||||
image: "opencloudeu/opencloud-rolling:9.1.0"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
restart: unless-stopped
|
||||
user: "1000:1000"
|
||||
depends_on:
|
||||
traefik:
|
||||
condition: service_healthy
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
- -c
|
||||
- opencloud init || true; exec opencloud server
|
||||
environment:
|
||||
- OC_URL=https://${OC_DOMAIN}:${PANEL_APP_PORT_HTTPS}
|
||||
- OC_LOG_LEVEL=info
|
||||
- OC_LOG_COLOR=false
|
||||
- OC_LOG_PRETTY=false
|
||||
- OC_INSECURE=true
|
||||
- OC_ADD_RUN_SERVICES=
|
||||
- PROXY_TLS=false
|
||||
- PROXY_HTTP_ADDR=0.0.0.0:9200
|
||||
- PROXY_ENABLE_BASIC_AUTH=false
|
||||
- PROXY_CSP_CONFIG_FILE_LOCATION=/etc/opencloud/csp.yaml
|
||||
- IDM_CREATE_DEMO_USERS=false
|
||||
- IDM_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD}
|
||||
- OC_PASSWORD_POLICY_BANNED_PASSWORDS_LIST=banned-password-list.txt
|
||||
- OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD=true
|
||||
- OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD=false
|
||||
- FRONTEND_CHECK_FOR_UPDATES=true
|
||||
- TRAEFIK_PORT_HTTPS=:${PANEL_APP_PORT_HTTPS}
|
||||
- TZ=${TZ}
|
||||
networks:
|
||||
- opencloud-network
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}/config:/etc/opencloud"
|
||||
- "${APP_DATA_DIR}/storage:/var/lib/opencloud"
|
||||
- "${APP_DATA_DIR}/apps:/var/lib/opencloud/web/assets/apps"
|
||||
- "./config/opencloud/csp.yaml:/etc/opencloud/csp.yaml:ro"
|
||||
- "./config/opencloud/banned-password-list.txt:/etc/opencloud/banned-password-list.txt:ro"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- curl --fail --silent --show-error http://127.0.0.1:9200/status.php >/dev/null
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
start_period: 120s
|
||||
retries: 5
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
|
||||
networks:
|
||||
opencloud-network:
|
||||
name: ${CONTAINER_NAME}-network
|
||||
1panel-network:
|
||||
external: true
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
|
||||
|
||||
fail() {
|
||||
printf '%s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_no_symlink_components() {
|
||||
local path="$1"
|
||||
local current="/"
|
||||
local part
|
||||
local -a components
|
||||
|
||||
[[ "$path" == /* ]] || fail "Path must be absolute: $path"
|
||||
IFS='/' read -r -a components <<< "$path"
|
||||
for part in "${components[@]}"; do
|
||||
case "$part" in
|
||||
"" | .) continue ;;
|
||||
..)
|
||||
if [[ "$current" == /*/* ]]; then
|
||||
current="${current%/*}"
|
||||
else
|
||||
current="/"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [[ "$current" == / ]]; then
|
||||
current="/$part"
|
||||
else
|
||||
current="$current/$part"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
[[ ! -L "$current" ]] || fail "Data path must not contain a symbolic link: $current"
|
||||
done
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
local value=""
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)"
|
||||
fi
|
||||
if [[ ${#value} -ge 2 && "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then
|
||||
value="${value:1:${#value}-2}"
|
||||
elif [[ ${#value} -ge 2 && "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
|
||||
value="${value:1:${#value}-2}"
|
||||
fi
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
if [[ ${APP_DATA_DIR+x} ]]; then
|
||||
APP_DATA_DIR_RAW="$APP_DATA_DIR"
|
||||
else
|
||||
APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)"
|
||||
fi
|
||||
APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW:-./data}"
|
||||
|
||||
if [[ ${OC_DOMAIN+x} ]]; then
|
||||
OC_DOMAIN_RAW="$OC_DOMAIN"
|
||||
else
|
||||
OC_DOMAIN_RAW="$(read_env_value OC_DOMAIN)"
|
||||
fi
|
||||
|
||||
[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty"
|
||||
case "$APP_DATA_DIR_RAW" in
|
||||
*$'\n'* | *$'\r'* | *'$'* | *'#'*) fail "APP_DATA_DIR contains unsupported dotenv characters" ;;
|
||||
esac
|
||||
|
||||
case "$APP_DATA_DIR_RAW" in
|
||||
/*)
|
||||
ensure_no_symlink_components "$APP_DATA_DIR_RAW"
|
||||
APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")"
|
||||
;;
|
||||
*)
|
||||
ensure_no_symlink_components "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}"
|
||||
APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")"
|
||||
case "$APP_DATA_DIR_ABS" in
|
||||
"$ROOT_DIR" | "$ROOT_DIR"/*) ;;
|
||||
*) fail "Relative APP_DATA_DIR must stay inside the application directory" ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "$APP_DATA_DIR_ABS" != / ]] || fail "APP_DATA_DIR must not be the filesystem root"
|
||||
[[ ! -L "$APP_DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must not be a symbolic link"
|
||||
if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then
|
||||
fail "APP_DATA_DIR must be a directory"
|
||||
fi
|
||||
|
||||
opencloud_paths=(
|
||||
"$APP_DATA_DIR_ABS/config"
|
||||
"$APP_DATA_DIR_ABS/storage"
|
||||
"$APP_DATA_DIR_ABS/apps"
|
||||
)
|
||||
traefik_paths=(
|
||||
"$APP_DATA_DIR_ABS/traefik/dynamic"
|
||||
"$APP_DATA_DIR_ABS/traefik/certs"
|
||||
)
|
||||
|
||||
for path in "${opencloud_paths[@]}" "${traefik_paths[@]}"; do
|
||||
ensure_no_symlink_components "$path"
|
||||
done
|
||||
|
||||
mkdir -p -- "${opencloud_paths[@]}" "${traefik_paths[@]}"
|
||||
for path in "${opencloud_paths[@]}"; do
|
||||
chown 1000:1000 -- "$path" 2>/dev/null || true
|
||||
done
|
||||
|
||||
[[ "$OC_DOMAIN_RAW" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$ ]] || fail "OC_DOMAIN must be a hostname without a scheme, port, or path"
|
||||
[[ ${#OC_DOMAIN_RAW} -le 253 ]] || fail "OC_DOMAIN is too long"
|
||||
|
||||
route_template="$ROOT_DIR/config/traefik/opencloud.yml"
|
||||
route_file="$APP_DATA_DIR_ABS/traefik/dynamic/1panel-opencloud-route.yml"
|
||||
[[ -f "$route_template" && ! -L "$route_template" ]] || fail "OpenCloud Traefik route template is missing or invalid"
|
||||
[[ ! -L "$route_file" ]] || fail "OpenCloud Traefik route must not be a symbolic link: $route_file"
|
||||
if [[ -e "$route_file" && ! -f "$route_file" ]]; then
|
||||
fail "OpenCloud Traefik route must be a regular file: $route_file"
|
||||
fi
|
||||
|
||||
if [[ ! -e "$route_file" ]]; then
|
||||
route_tmp="$(mktemp "${route_file}.tmp.XXXXXX")"
|
||||
if ! sed "s/@OC_DOMAIN@/${OC_DOMAIN_RAW}/g" "$route_template" > "$route_tmp"; then
|
||||
rm -f -- "$route_tmp"
|
||||
fail "Could not create OpenCloud Traefik route"
|
||||
fi
|
||||
chmod 0644 -- "$route_tmp"
|
||||
if ! ln -- "$route_tmp" "$route_file" 2>/dev/null; then
|
||||
if [[ ! -f "$route_file" || -L "$route_file" ]]; then
|
||||
rm -f -- "$route_tmp"
|
||||
fail "Could not create OpenCloud Traefik route"
|
||||
fi
|
||||
fi
|
||||
rm -f -- "$route_tmp"
|
||||
fi
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
docker-compose down --remove-orphans
|
||||
else
|
||||
docker compose down --remove-orphans
|
||||
fi
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
exec bash "${SCRIPT_DIR}/init.sh"
|
||||
@@ -14,8 +14,8 @@ OpenCloud 是一个开源的文件管理、共享与协作平台,提供 Web
|
||||
## 来源与版本
|
||||
|
||||
- OpenCloud 源码仓库由 `opencloud-eu` 官方组织维护,采用 Apache License 2.0。
|
||||
- 本应用基于官方 `opencloud-compose` 的基础服务与 Traefik TLS 部署方式,Compose 证据固定到提交 `8d2d89f283faa410bc9ed9f63e8247f6518d5c43`。
|
||||
- OpenCloud [`v8.1.0`](https://github.com/opencloud-eu/opencloud/releases/tag/v8.1.0) 于 2026-10-05 作为 rolling release 发布。本应用使用官方镜像 `opencloudeu/opencloud-rolling:8.1.0` 的版本化 tag,不使用浮动 `latest` tag。
|
||||
- 本应用基于官方 `opencloud-compose` 的基础服务与 Traefik TLS 部署方式,并在初始化时将随包提供的 file-provider 路由配置安全地创建到持久化目录;Compose 证据固定到提交 `8d2d89f283faa410bc9ed9f63e8247f6518d5c43`。
|
||||
- 本应用使用官方版本化镜像 tag,不使用浮动 `latest` tag。
|
||||
- OpenCloud 与 Traefik 镜像均提供 `linux/amd64` 和 `linux/arm64` manifest。
|
||||
|
||||
## 部署拓扑
|
||||
@@ -25,7 +25,7 @@ OpenCloud 是一个开源的文件管理、共享与协作平台,提供 Web
|
||||
- `opencloud`:使用官方 OpenCloud 镜像,以 UID/GID `1000:1000` 运行并持久化配置、文件数据和 Web 扩展。
|
||||
- `traefik`:使用官方 OpenCloud Compose 指定的 Traefik 版本,为 OpenCloud 提供必须的 HTTPS 入口和 HTTP 到 HTTPS 跳转。
|
||||
|
||||
两个 service 通过以 1Panel 实例容器名命名的专用 Docker 网络联动;OpenCloud 后端只加入该专用网络,Traefik 另行加入 `1panel-network` 作为商店入口。每个 Traefik 通过实例标签只发现对应的 OpenCloud 容器,不同安装不会互相加载或覆盖同名路由。
|
||||
两个 service 通过以 1Panel 实例容器名命名的专用 Docker 网络联动;OpenCloud 后端只加入该专用网络,Traefik 另行加入 `1panel-network` 作为商店入口。Traefik 从应用包内的 file-provider 配置读取路由,并通过专用网络访问 `opencloud:9200`,不需要发现或控制 Docker 容器。
|
||||
|
||||
OpenCloud 的 IDM、IDP、NATS、存储和 Web 服务由同一个官方镜像内部管理,不需要 MySQL、PostgreSQL、Redis 或其他 1Panel Runtime。可选的 Collabora、全文检索、杀毒、外部 LDAP/Keycloak 等扩展没有包含在此基础应用中。
|
||||
|
||||
@@ -57,15 +57,17 @@ tls:
|
||||
- `${APP_DATA_DIR}/config`:OpenCloud 生成的配置和随机密钥。
|
||||
- `${APP_DATA_DIR}/storage`:用户文件、元数据和应用状态。
|
||||
- `${APP_DATA_DIR}/apps`:本地 Web 扩展。
|
||||
- `${APP_DATA_DIR}/traefik`:TLS 动态配置和证书。
|
||||
- `${APP_DATA_DIR}/traefik/dynamic/1panel-opencloud-route.yml`:应用首次初始化时创建的 OpenCloud 路由。已存在的文件不会被覆盖。
|
||||
- `${APP_DATA_DIR}/traefik/dynamic`:可选 TLS 动态配置。
|
||||
- `${APP_DATA_DIR}/traefik/certs`:TLS 证书。
|
||||
|
||||
初始管理员密码只在空数据目录第一次启动时生效,后续修改 `.env` 不会重置密码。升级前必须整体备份 `${APP_DATA_DIR}`;配置、存储和证书应作为同一个一致性边界备份和恢复。
|
||||
|
||||
`scripts/upgrade.sh` 只复用安装初始化逻辑:创建缺失目录,并校正 OpenCloud 持久化目录顶层的 UID/GID,不递归改写文件、不删除数据、不重建配置。卸载脚本只停止 Compose service,不删除 `${APP_DATA_DIR}`。
|
||||
`scripts/upgrade.sh` 复用安装初始化逻辑:创建缺失目录和缺失的 OpenCloud Traefik 路由,并校正 OpenCloud 持久化目录顶层的 UID/GID,不递归改写已有文件、不删除数据。卸载脚本只停止 Compose service,不删除 `${APP_DATA_DIR}`。
|
||||
|
||||
## 权限与安全
|
||||
|
||||
Traefik 按官方 Compose 方案以只读方式挂载 Docker Socket,用于读取容器标签和发现 OpenCloud 路由。即使挂载标记为只读,Docker Socket 仍属于高权限主机接口;只应在可信主机上使用,并保持 Traefik 镜像及时更新。Compose 已关闭 `exposedByDefault`,Traefik 只处理明确标记的 OpenCloud service。
|
||||
Traefik 使用应用包生成并保存在 `${APP_DATA_DIR}` 中的静态路由配置,不挂载 Docker Socket,也不读取其他容器的标签。HTTP 请求经 Traefik 转发至同一专用网络中的 OpenCloud 服务。
|
||||
|
||||
OpenCloud service 不使用 privileged、host network、额外 capability 或设备映射。请使用强管理员密码、受信任 TLS 证书和主机防火墙,并限制不必要的公网入口。
|
||||
|
||||
@@ -79,9 +81,9 @@ OpenCloud service 不使用 privileged、host network、额外 capability 或设
|
||||
|
||||
## Introduction
|
||||
|
||||
OpenCloud is an open-source platform for file management, sharing, and collaboration. This package follows the official OpenCloud Compose topology: one fixed-version OpenCloud container and one Traefik container providing the HTTPS endpoint required by the built-in identity provider.
|
||||
OpenCloud is an open-source platform for file management, sharing, and collaboration. This package follows the official OpenCloud Compose service topology and creates a package-provided Traefik file-provider route in persistent app data to serve OpenCloud over HTTPS without Docker API access. Existing route files are preserved.
|
||||
|
||||
OpenCloud [v8.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v8.1.0) is an official rolling release published on 2026-10-05. This package uses the versioned official image tag `opencloudeu/opencloud-rolling:8.1.0` rather than a floating `latest` tag.
|
||||
This package uses versioned official image tags rather than a floating `latest` tag.
|
||||
|
||||
A trusted certificate is required for production. Back up the complete `${APP_DATA_DIR}` before upgrading. The lifecycle scripts do not delete persisted data or recursively rewrite existing files.
|
||||
|
||||
|
||||
@@ -4,5 +4,4 @@ PANEL_APP_PORT_HTTP=4080
|
||||
OC_DOMAIN=cloud.opencloud.test
|
||||
INITIAL_ADMIN_PASSWORD=replace-with-a-strong-password
|
||||
APP_DATA_DIR=./data
|
||||
DOCKER_SOCKET_PATH=/var/run/docker.sock
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
http:
|
||||
routers:
|
||||
opencloud:
|
||||
entryPoints:
|
||||
- https
|
||||
rule: "Host(`@OC_DOMAIN@`)"
|
||||
service: opencloud
|
||||
tls: {}
|
||||
services:
|
||||
opencloud:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://opencloud:9200
|
||||
@@ -84,22 +84,6 @@ additionalProperties:
|
||||
pt-br: Diretório de dados
|
||||
required: true
|
||||
type: text
|
||||
- default: /var/run/docker.sock
|
||||
edit: true
|
||||
envKey: DOCKER_SOCKET_PATH
|
||||
labelEn: Docker Socket Path
|
||||
labelZh: Docker Socket 路径
|
||||
label:
|
||||
en: Docker Socket Path
|
||||
zh: Docker Socket 路径
|
||||
zh-Hant: Docker Socket 路徑
|
||||
ja: Docker Socket パス
|
||||
ko: Docker Socket 경로
|
||||
ru: Путь к Docker Socket
|
||||
ms: Laluan Docker Socket
|
||||
pt-br: Caminho do Docker Socket
|
||||
required: true
|
||||
type: text
|
||||
- default: Asia/Shanghai
|
||||
edit: true
|
||||
envKey: TZ
|
||||
|
||||
@@ -14,10 +14,6 @@ services:
|
||||
- --entryPoints.https.transport.respondingTimeouts.readTimeout=12h
|
||||
- --entryPoints.https.transport.respondingTimeouts.writeTimeout=12h
|
||||
- --entryPoints.https.transport.respondingTimeouts.idleTimeout=3m
|
||||
- --providers.docker.endpoint=unix:///var/run/docker.sock
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --providers.docker.network=${CONTAINER_NAME}-network
|
||||
- "--providers.docker.constraints=Label(`opencloud.instance`,`${CONTAINER_NAME}`)"
|
||||
- --providers.file.directory=/etc/traefik/dynamic
|
||||
- --providers.file.watch=true
|
||||
networks:
|
||||
@@ -29,7 +25,6 @@ services:
|
||||
- "${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
|
||||
- "${PANEL_APP_PORT_HTTPS}:${PANEL_APP_PORT_HTTPS}"
|
||||
volumes:
|
||||
- "${DOCKER_SOCKET_PATH}:/var/run/docker.sock:ro"
|
||||
- "${APP_DATA_DIR}/traefik/dynamic:/etc/traefik/dynamic:ro"
|
||||
- "${APP_DATA_DIR}/traefik/certs:/certs:ro"
|
||||
healthcheck:
|
||||
@@ -95,13 +90,6 @@ services:
|
||||
retries: 5
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
opencloud.instance: "${CONTAINER_NAME}"
|
||||
traefik.enable: "true"
|
||||
traefik.http.routers.opencloud.entrypoints: https
|
||||
traefik.http.routers.opencloud.rule: Host(`${OC_DOMAIN}`)
|
||||
traefik.http.routers.opencloud.service: opencloud
|
||||
traefik.http.routers.opencloud.tls: "true"
|
||||
traefik.http.services.opencloud.loadbalancer.server.port: "9200"
|
||||
|
||||
networks:
|
||||
opencloud-network:
|
||||
|
||||
@@ -9,6 +9,36 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_no_symlink_components() {
|
||||
local path="$1"
|
||||
local current="/"
|
||||
local part
|
||||
local -a components
|
||||
|
||||
[[ "$path" == /* ]] || fail "Path must be absolute: $path"
|
||||
IFS='/' read -r -a components <<< "$path"
|
||||
for part in "${components[@]}"; do
|
||||
case "$part" in
|
||||
"" | .) continue ;;
|
||||
..)
|
||||
if [[ "$current" == /*/* ]]; then
|
||||
current="${current%/*}"
|
||||
else
|
||||
current="/"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [[ "$current" == / ]]; then
|
||||
current="/$part"
|
||||
else
|
||||
current="$current/$part"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
[[ ! -L "$current" ]] || fail "Data path must not contain a symbolic link: $current"
|
||||
done
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
local value=""
|
||||
@@ -31,14 +61,24 @@ else
|
||||
fi
|
||||
APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW:-./data}"
|
||||
|
||||
if [[ ${OC_DOMAIN+x} ]]; then
|
||||
OC_DOMAIN_RAW="$OC_DOMAIN"
|
||||
else
|
||||
OC_DOMAIN_RAW="$(read_env_value OC_DOMAIN)"
|
||||
fi
|
||||
|
||||
[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty"
|
||||
case "$APP_DATA_DIR_RAW" in
|
||||
*$'\n'* | *$'\r'* | *'$'* | *'#'*) fail "APP_DATA_DIR contains unsupported dotenv characters" ;;
|
||||
esac
|
||||
|
||||
case "$APP_DATA_DIR_RAW" in
|
||||
/*) APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" ;;
|
||||
/*)
|
||||
ensure_no_symlink_components "$APP_DATA_DIR_RAW"
|
||||
APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")"
|
||||
;;
|
||||
*)
|
||||
ensure_no_symlink_components "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}"
|
||||
APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")"
|
||||
case "$APP_DATA_DIR_ABS" in
|
||||
"$ROOT_DIR" | "$ROOT_DIR"/*) ;;
|
||||
@@ -63,8 +103,38 @@ traefik_paths=(
|
||||
"$APP_DATA_DIR_ABS/traefik/certs"
|
||||
)
|
||||
|
||||
for path in "${opencloud_paths[@]}" "${traefik_paths[@]}"; do
|
||||
ensure_no_symlink_components "$path"
|
||||
done
|
||||
|
||||
mkdir -p -- "${opencloud_paths[@]}" "${traefik_paths[@]}"
|
||||
for path in "${opencloud_paths[@]}"; do
|
||||
[[ ! -L "$path" ]] || fail "OpenCloud data subdirectory must not be a symbolic link: $path"
|
||||
chown 1000:1000 -- "$path" 2>/dev/null || true
|
||||
done
|
||||
|
||||
[[ "$OC_DOMAIN_RAW" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$ ]] || fail "OC_DOMAIN must be a hostname without a scheme, port, or path"
|
||||
[[ ${#OC_DOMAIN_RAW} -le 253 ]] || fail "OC_DOMAIN is too long"
|
||||
|
||||
route_template="$ROOT_DIR/config/traefik/opencloud.yml"
|
||||
route_file="$APP_DATA_DIR_ABS/traefik/dynamic/1panel-opencloud-route.yml"
|
||||
[[ -f "$route_template" && ! -L "$route_template" ]] || fail "OpenCloud Traefik route template is missing or invalid"
|
||||
[[ ! -L "$route_file" ]] || fail "OpenCloud Traefik route must not be a symbolic link: $route_file"
|
||||
if [[ -e "$route_file" && ! -f "$route_file" ]]; then
|
||||
fail "OpenCloud Traefik route must be a regular file: $route_file"
|
||||
fi
|
||||
|
||||
if [[ ! -e "$route_file" ]]; then
|
||||
route_tmp="$(mktemp "${route_file}.tmp.XXXXXX")"
|
||||
if ! sed "s/@OC_DOMAIN@/${OC_DOMAIN_RAW}/g" "$route_template" > "$route_tmp"; then
|
||||
rm -f -- "$route_tmp"
|
||||
fail "Could not create OpenCloud Traefik route"
|
||||
fi
|
||||
chmod 0644 -- "$route_tmp"
|
||||
if ! ln -- "$route_tmp" "$route_file" 2>/dev/null; then
|
||||
if [[ ! -f "$route_file" || -L "$route_file" ]]; then
|
||||
rm -f -- "$route_tmp"
|
||||
fail "Could not create OpenCloud Traefik route"
|
||||
fi
|
||||
fi
|
||||
rm -f -- "$route_tmp"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user