Merge pull request #7989 from renovate/ghcr.io-diced-zipline-4.x

Update Zipline from 4.8.0 to 4.8.1.
This commit is contained in:
okxlin
2026-10-28 20:25:17 +08:00
7 changed files with 673 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
CONTAINER_NAME=zipline
PANEL_APP_BIND_ADDRESS=127.0.0.1
PANEL_APP_PORT_HTTP=3000
POSTGRES_DB=zipline
POSTGRES_USER=zipline
POSTGRES_PASSWORD=generate
CORE_SECRET=generate
FILES_MAX_FILE_SIZE=100mb
APP_DATA_DIR=./data
+135
View File
@@ -0,0 +1,135 @@
additionalProperties:
formFields:
- default: 127.0.0.1
edit: true
envKey: PANEL_APP_BIND_ADDRESS
labelEn: Bind Address
labelZh: 绑定地址
label:
en: Bind Address
zh: 绑定地址
zh-Hant: 綁定位址
ja: バインドアドレス
ko: 바인드 주소
ru: Адрес привязки
ms: Alamat ikatan
pt-br: Endereco de vinculacao
required: true
type: text
- default: 3000
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: HTTP Port
labelZh: HTTP 端口
label:
en: HTTP Port
zh: HTTP 端口
zh-Hant: HTTP 連接埠
ja: HTTP ポート
ko: HTTP 포트
ru: HTTP-порт
ms: Port HTTP
pt-br: Porta HTTP
required: true
rule: paramPort
type: number
- default: 100mb
edit: true
envKey: FILES_MAX_FILE_SIZE
labelEn: Maximum File Size
labelZh: 最大文件大小
label:
en: Maximum File Size
zh: 最大文件大小
zh-Hant: 最大檔案大小
ja: 最大ファイルサイズ
ko: 최대 파일 크기
ru: Максимальный размер файла
ms: Saiz fail maksimum
pt-br: Tamanho maximo do arquivo
required: true
type: text
- default: zipline
edit: true
envKey: POSTGRES_DB
labelEn: Database Name
labelZh: 数据库名称
label:
en: Database Name
zh: 数据库名称
zh-Hant: 資料庫名稱
ja: データベース名
ko: 데이터베이스 이름
ru: Имя базы данных
ms: Nama pangkalan data
pt-br: Nome do banco de dados
random: true
required: true
rule: paramCommon
type: text
- default: zipline
edit: true
envKey: POSTGRES_USER
labelEn: Database User
labelZh: 数据库用户
label:
en: Database User
zh: 数据库用户
zh-Hant: 資料庫使用者
ja: データベースユーザー
ko: 데이터베이스 사용자
ru: Пользователь базы данных
ms: Pengguna pangkalan data
pt-br: Usuario do banco de dados
random: true
required: true
rule: paramCommon
type: text
- default: generate
edit: true
envKey: POSTGRES_PASSWORD
labelEn: Database Password
labelZh: 数据库密码
label:
en: Database Password
zh: 数据库密码
zh-Hant: 資料庫密碼
ja: データベースパスワード
ko: 데이터베이스 비밀번호
ru: Пароль базы данных
ms: Kata laluan pangkalan data
pt-br: Senha do banco de dados
required: true
type: password
- default: generate
edit: true
envKey: CORE_SECRET
labelEn: Session Secret
labelZh: 会话密钥
label:
en: Session Secret
zh: 会话密钥
zh-Hant: 工作階段密鑰
ja: セッションシークレット
ko: 세션 비밀 키
ru: Секрет сеанса
ms: Rahsia sesi
pt-br: Segredo da sessao
required: true
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: Diretorio de dados
required: true
type: text
+79
View File
@@ -0,0 +1,79 @@
services:
zipline-postgres:
image: postgres:16-alpine@sha256:deb28ee471183cb0c303113dac9b2433c51eeeeb678def7af9a9ec5820367815
container_name: ${CONTAINER_NAME}-postgres
restart: unless-stopped
user: "70:70"
networks:
- zipline-internal
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- "${APP_DATA_DIR}/postgres:/var/lib/postgresql/data"
read_only: true
tmpfs:
- /tmp:mode=1777,size=64m
- /var/run/postgresql:uid=70,gid=70,mode=0755,size=16m
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
labels:
createdBy: "Apps"
zipline:
image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f
container_name: ${CONTAINER_NAME}
restart: unless-stopped
user: "1000:1000"
depends_on:
zipline-postgres:
condition: service_healthy
networks:
- zipline-internal
- 1panel-network
ports:
- "${PANEL_APP_BIND_ADDRESS}:${PANEL_APP_PORT_HTTP}:3000"
environment:
- CORE_HOSTNAME=0.0.0.0
- CORE_PORT=3000
- CORE_SECRET=${CORE_SECRET}
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@zipline-postgres:5432/${POSTGRES_DB}
- DATASOURCE_TYPE=local
- DATASOURCE_LOCAL_DIRECTORY=/zipline/uploads
- CORE_TEMP_DIRECTORY=/tmp/zipline
- FILES_MAX_FILE_SIZE=${FILES_MAX_FILE_SIZE}
- FEATURES_HEALTHCHECK=true
volumes:
- "${APP_DATA_DIR}/uploads:/zipline/uploads"
- "${APP_DATA_DIR}/public:/zipline/public"
- "${APP_DATA_DIR}/themes:/zipline/themes"
read_only: true
tmpfs:
- /tmp:mode=1777,size=256m
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/healthcheck"]
interval: 15s
timeout: 5s
retries: 8
start_period: 60s
labels:
createdBy: "Apps"
networks:
zipline-internal:
internal: true
1panel-network:
external: true
+290
View File
@@ -0,0 +1,290 @@
#!/usr/bin/env bash
set -euo pipefail
ZIPLINE_STORED_POSTGRES_PASSWORD=""
ZIPLINE_STORED_CORE_SECRET=""
ZIPLINE_SNAPSHOT_POSTGRES_PASSWORD=""
ZIPLINE_SNAPSHOT_CORE_SECRET=""
zipline_fail() {
printf '%s\n' "$1" >&2
exit 1
}
zipline_read_env_value() {
local file="$1"
local key="$2"
local value
[[ -f "$file" && ! -L "$file" ]] || return 1
value="$(awk -v key="$key" '
index($0, key "=") == 1 {
count++
value = substr($0, length(key) + 2)
}
END {
if (count != 1) exit 1
print value
}
' "$file")" || return 1
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
printf '%s\n' "$value"
}
zipline_resolve_data_dir() {
local root_dir="$1"
local raw="$2"
local clean current resolved part
local -a parts=()
[[ -n "$raw" && "$raw" != /* ]] || zipline_fail "APP_DATA_DIR must be a non-empty relative path"
[[ ! "$raw" =~ [[:cntrl:]] ]] || zipline_fail "APP_DATA_DIR contains control characters"
case "$raw" in
*\\* | *:* | *'$'* | *'#'* | *'"'* | *"'"*)
zipline_fail "APP_DATA_DIR contains unsupported characters"
;;
esac
clean="${raw#./}"
[[ -n "$clean" ]] || zipline_fail "APP_DATA_DIR must not resolve to the version root"
IFS=/ read -r -a parts <<< "$clean"
current="$root_dir"
for part in "${parts[@]}"; do
[[ -n "$part" && "$part" != . && "$part" != .. ]] || zipline_fail "APP_DATA_DIR contains traversal"
current="$current/$part"
[[ ! -L "$current" ]] || zipline_fail "APP_DATA_DIR must not contain symbolic-link components"
done
resolved="$(realpath -m -- "$root_dir/$clean")" || zipline_fail "APP_DATA_DIR could not be resolved"
case "$resolved" in
"$root_dir"/*) ;;
*) zipline_fail "APP_DATA_DIR must stay inside the application version directory" ;;
esac
printf '%s\n' "$resolved"
}
zipline_validate_postgres_password() {
[[ "$1" =~ ^[A-Za-z0-9]{32,128}$ ]]
}
zipline_validate_core_secret() {
[[ "$1" =~ ^[A-Fa-f0-9]{64,128}$ ]]
}
zipline_generate_postgres_password() {
local material
material="$(openssl rand -base64 96 | tr -dc A-Za-z0-9)"
[[ ${#material} -ge 48 ]] || zipline_fail "unable to generate sufficient database password material"
printf '%s\n' "${material:0:48}"
}
zipline_write_env_credentials() {
local env_file="$1"
local postgres_password="$2"
local core_secret="$3"
local temp_file
temp_file="$(mktemp "${env_file}.zipline-tmp.XXXXXX")" || zipline_fail "unable to stage Zipline environment update"
if ! ZIPLINE_DB_PASSWORD="$postgres_password" ZIPLINE_CORE_SECRET="$core_secret" awk '
BEGIN {
db_password = ENVIRON["ZIPLINE_DB_PASSWORD"]
core_secret = ENVIRON["ZIPLINE_CORE_SECRET"]
}
/^POSTGRES_PASSWORD=/ {
if (!db_written++) print "POSTGRES_PASSWORD=" db_password
next
}
/^CORE_SECRET=/ {
if (!core_written++) print "CORE_SECRET=" core_secret
next
}
{ print }
END {
if (!db_written) print "POSTGRES_PASSWORD=" db_password
if (!core_written) print "CORE_SECRET=" core_secret
}
' "$env_file" > "$temp_file"; then
rm -f -- "$temp_file"
zipline_fail "unable to render Zipline environment update"
fi
chmod 600 "$temp_file"
mv -f -- "$temp_file" "$env_file"
chmod 600 "$env_file"
}
zipline_write_secret_state() {
local data_dir="$1"
local postgres_password="$2"
local core_secret="$3"
local secret_file="$data_dir/.zipline-secrets"
local temp_file
[[ -d "$data_dir" && ! -L "$data_dir" ]] || zipline_fail "Zipline data directory is unavailable"
[[ ! -L "$secret_file" ]] || zipline_fail "Zipline secret state must not be a symbolic link"
temp_file="$(mktemp "${secret_file}.tmp.XXXXXX")" || zipline_fail "unable to stage Zipline secret state"
if ! printf 'POSTGRES_PASSWORD=%s\nCORE_SECRET=%s\n' "$postgres_password" "$core_secret" > "$temp_file"; then
rm -f -- "$temp_file"
zipline_fail "unable to write Zipline secret state"
fi
chmod 600 "$temp_file"
chown 0:0 -- "$temp_file"
mv -f -- "$temp_file" "$secret_file"
chmod 600 "$secret_file"
chown 0:0 -- "$secret_file"
}
zipline_load_secret_state() {
local data_dir="$1"
local secret_file="$data_dir/.zipline-secrets"
[[ -e "$secret_file" || -L "$secret_file" ]] || return 1
[[ -f "$secret_file" && ! -L "$secret_file" ]] || zipline_fail "Zipline secret state is not a regular file"
ZIPLINE_STORED_POSTGRES_PASSWORD="$(zipline_read_env_value "$secret_file" POSTGRES_PASSWORD)" ||
zipline_fail "Zipline secret state is incomplete"
ZIPLINE_STORED_CORE_SECRET="$(zipline_read_env_value "$secret_file" CORE_SECRET)" ||
zipline_fail "Zipline secret state is incomplete"
zipline_validate_postgres_password "$ZIPLINE_STORED_POSTGRES_PASSWORD" ||
zipline_fail "Zipline secret state has an invalid database password"
zipline_validate_core_secret "$ZIPLINE_STORED_CORE_SECRET" ||
zipline_fail "Zipline secret state has an invalid session secret"
chmod 600 "$secret_file"
chown 0:0 -- "$secret_file"
}
zipline_init_credentials() {
local env_file="$1"
local data_dir="$2"
local postgres_password core_secret stored
local postgres_default=false core_default=false
[[ -f "$env_file" && ! -L "$env_file" ]] || zipline_fail "Zipline .env file is unavailable"
postgres_password="$(zipline_read_env_value "$env_file" POSTGRES_PASSWORD)" ||
zipline_fail "POSTGRES_PASSWORD is missing from Zipline .env"
core_secret="$(zipline_read_env_value "$env_file" CORE_SECRET)" ||
zipline_fail "CORE_SECRET is missing from Zipline .env"
[[ -n "$postgres_password" && "$postgres_password" != generate ]] || postgres_default=true
[[ -n "$core_secret" && "$core_secret" != generate ]] || core_default=true
if zipline_load_secret_state "$data_dir"; then
if [[ "$postgres_default" == true ]]; then
postgres_password="$ZIPLINE_STORED_POSTGRES_PASSWORD"
elif [[ "$postgres_password" != "$ZIPLINE_STORED_POSTGRES_PASSWORD" ]]; then
zipline_fail "POSTGRES_PASSWORD cannot change while Zipline data is retained"
fi
if [[ "$core_default" == true ]]; then
core_secret="$ZIPLINE_STORED_CORE_SECRET"
elif [[ "$core_secret" != "$ZIPLINE_STORED_CORE_SECRET" ]]; then
zipline_fail "CORE_SECRET cannot change while Zipline data is retained"
fi
else
if [[ -f "$data_dir/postgres/PG_VERSION" && ( "$postgres_default" == true || "$core_default" == true ) ]]; then
zipline_fail "existing PostgreSQL data has no Zipline secret state; supply its original credentials to preserve it"
fi
if [[ "$postgres_default" == true ]]; then
postgres_password="$(zipline_generate_postgres_password)"
fi
if [[ "$core_default" == true ]]; then
core_secret="$(openssl rand -hex 32)"
fi
fi
zipline_validate_postgres_password "$postgres_password" ||
zipline_fail "POSTGRES_PASSWORD must contain 32 to 128 alphanumeric characters"
zipline_validate_core_secret "$core_secret" ||
zipline_fail "CORE_SECRET must contain 64 to 128 hexadecimal characters"
zipline_write_env_credentials "$env_file" "$postgres_password" "$core_secret"
zipline_write_secret_state "$data_dir" "$postgres_password" "$core_secret"
}
zipline_find_upgrade_snapshot() {
local root_dir="$1"
local env_file="$2"
local current_name="$3"
local current_data_dir="$4"
local current_db="$5"
local current_user="$6"
local temp_root snapshot old_env old_compose old_name old_data old_db old_user
local snapshot_time env_time best_time=-1 tied=false snapshot_uid snapshot_mode old_env_uid old_env_mode
local best_snapshot=""
local -a candidates=()
temp_root="$(realpath -e -- "${TMPDIR:-/tmp}")" || zipline_fail "1Panel upgrade snapshot directory is unavailable"
[[ -d "$temp_root" ]] || zipline_fail "1Panel upgrade snapshot directory is unavailable"
env_time="$(stat -c '%Y' "$env_file")" || zipline_fail "Zipline .env timestamp is unavailable"
shopt -s nullglob
candidates=("$temp_root"/1panel-app-upgrade-*)
shopt -u nullglob
for snapshot in "${candidates[@]}"; do
[[ -d "$snapshot" && ! -L "$snapshot" ]] || continue
snapshot_uid="$(stat -c '%u' "$snapshot")" || continue
snapshot_mode="$(stat -c '%a' "$snapshot")" || continue
[[ "$snapshot_uid" == 0 ]] || continue
(( (8#$snapshot_mode & 077) == 0 )) || continue
old_env="$snapshot/.env"
old_compose="$snapshot/docker-compose.yml"
[[ -f "$old_env" && ! -L "$old_env" && -f "$old_compose" && ! -L "$old_compose" ]] || continue
old_env_uid="$(stat -c '%u' "$old_env")" || continue
old_env_mode="$(stat -c '%a' "$old_env")" || continue
[[ "$old_env_uid" == 0 ]] || continue
(( (8#$old_env_mode & 077) == 0 )) || continue
grep -Eq 'image:[[:space:]]*"?ghcr\.io/diced/zipline:4\.(6\.5|7\.0|8\.0)@sha256:[[:xdigit:]]{64}"?[[:space:]]*$' "$old_compose" || continue
old_name="$(zipline_read_env_value "$old_env" CONTAINER_NAME)" || continue
[[ "$old_name" == "$current_name" ]] || continue
old_data="$(zipline_read_env_value "$old_env" APP_DATA_DIR)" || zipline_fail "matching 1Panel snapshot lacks APP_DATA_DIR"
old_db="$(zipline_read_env_value "$old_env" POSTGRES_DB)" || zipline_fail "matching 1Panel snapshot lacks POSTGRES_DB"
old_user="$(zipline_read_env_value "$old_env" POSTGRES_USER)" || zipline_fail "matching 1Panel snapshot lacks POSTGRES_USER"
[[ "$old_data" == "$current_data_dir" && "$old_db" == "$current_db" && "$old_user" == "$current_user" ]] ||
zipline_fail "matching 1Panel snapshot does not preserve the configured data path and database identity"
ZIPLINE_SNAPSHOT_POSTGRES_PASSWORD="$(zipline_read_env_value "$old_env" POSTGRES_PASSWORD)" ||
zipline_fail "matching 1Panel snapshot lacks its database password"
ZIPLINE_SNAPSHOT_CORE_SECRET="$(zipline_read_env_value "$old_env" CORE_SECRET)" ||
zipline_fail "matching 1Panel snapshot lacks its session secret"
zipline_validate_postgres_password "$ZIPLINE_SNAPSHOT_POSTGRES_PASSWORD" ||
zipline_fail "matching 1Panel snapshot has an invalid database password"
zipline_validate_core_secret "$ZIPLINE_SNAPSHOT_CORE_SECRET" ||
zipline_fail "matching 1Panel snapshot has an invalid session secret"
snapshot_time="$(stat -c '%Y' "$snapshot")" || zipline_fail "1Panel upgrade snapshot timestamp is unavailable"
if (( snapshot_time > best_time )); then
best_time="$snapshot_time"
best_snapshot="$snapshot"
tied=false
elif (( snapshot_time == best_time )); then
tied=true
fi
done
[[ -n "$best_snapshot" ]] || zipline_fail "could not find this Zipline install's 1Panel upgrade snapshot"
[[ "$tied" == false ]] || zipline_fail "multiple matching 1Panel upgrade snapshots are ambiguous"
(( best_time <= env_time && env_time - best_time <= 600 )) ||
zipline_fail "matching 1Panel upgrade snapshot is stale"
}
zipline_restore_upgrade_credentials() {
local root_dir="$1"
local env_file="$2"
local raw_data_dir data_dir current_name current_db current_user
local postgres_password core_secret current_password current_core_secret
[[ -f "$env_file" && ! -L "$env_file" ]] || zipline_fail "Zipline .env file is unavailable"
current_name="$(zipline_read_env_value "$env_file" CONTAINER_NAME)" || zipline_fail "CONTAINER_NAME is missing from Zipline .env"
[[ "$current_name" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] || zipline_fail "CONTAINER_NAME is invalid"
raw_data_dir="$(zipline_read_env_value "$env_file" APP_DATA_DIR)" || zipline_fail "APP_DATA_DIR is missing from Zipline .env"
data_dir="$(zipline_resolve_data_dir "$root_dir" "$raw_data_dir")"
[[ -d "$data_dir" && ! -L "$data_dir" ]] || zipline_fail "configured Zipline data directory is missing"
current_db="$(zipline_read_env_value "$env_file" POSTGRES_DB)" || zipline_fail "POSTGRES_DB is missing from Zipline .env"
current_user="$(zipline_read_env_value "$env_file" POSTGRES_USER)" || zipline_fail "POSTGRES_USER is missing from Zipline .env"
current_password="$(zipline_read_env_value "$env_file" POSTGRES_PASSWORD)" || zipline_fail "POSTGRES_PASSWORD is missing from Zipline .env"
current_core_secret="$(zipline_read_env_value "$env_file" CORE_SECRET)" || zipline_fail "CORE_SECRET is missing from Zipline .env"
zipline_find_upgrade_snapshot "$root_dir" "$env_file" "$current_name" "$raw_data_dir" "$current_db" "$current_user"
postgres_password="$ZIPLINE_SNAPSHOT_POSTGRES_PASSWORD"
core_secret="$ZIPLINE_SNAPSHOT_CORE_SECRET"
if zipline_load_secret_state "$data_dir"; then
[[ "$ZIPLINE_STORED_POSTGRES_PASSWORD" == "$postgres_password" && "$ZIPLINE_STORED_CORE_SECRET" == "$core_secret" ]] ||
zipline_fail "Zipline secret state does not match the 1Panel upgrade snapshot"
fi
if [[ -n "$current_password" && "$current_password" != generate && "$current_password" != "$postgres_password" ]]; then
zipline_fail "POSTGRES_PASSWORD changed during upgrade; refusing to alter the existing database credential"
fi
if [[ -n "$current_core_secret" && "$current_core_secret" != generate && "$current_core_secret" != "$core_secret" ]]; then
zipline_fail "CORE_SECRET changed during upgrade; refusing to rotate the existing session secret"
fi
zipline_validate_postgres_password "$postgres_password" || zipline_fail "saved database password is invalid"
zipline_validate_core_secret "$core_secret" || zipline_fail "saved session secret is invalid"
zipline_write_env_credentials "$env_file" "$postgres_password" "$core_secret"
zipline_write_secret_state "$data_dir" "$postgres_password" "$core_secret"
printf '%s\n' "Zipline secrets were restored from the matching 1Panel upgrade snapshot."
}
+95
View File
@@ -0,0 +1,95 @@
#!/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
}
read_env_value() {
local key="$1"
local value
value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
printf '%s\n' "$value"
}
validate_ipv4() {
local key="$1"
local value="$2"
local octet
local -a octets
[[ "$value" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || fail "$key must be an IPv4 address"
IFS=. read -r -a octets <<< "$value"
for octet in "${octets[@]}"; do
((10#$octet <= 255)) || fail "$key contains an invalid IPv4 octet"
done
}
validate_port() {
local value="$1"
[[ "$value" =~ ^[0-9]+$ ]] || fail "PANEL_APP_PORT_HTTP must be an integer"
((10#$value >= 1 && 10#$value <= 65535)) || fail "PANEL_APP_PORT_HTTP must be between 1 and 65535"
}
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
[[ "$(id -u)" -eq 0 ]] || fail "Zipline init must run as root"
command -v openssl >/dev/null 2>&1 || fail "openssl is required to generate credentials"
source "${ROOT_DIR}/scripts/credentials.sh"
validate_ipv4 PANEL_APP_BIND_ADDRESS "$(read_env_value PANEL_APP_BIND_ADDRESS)"
validate_port "$(read_env_value PANEL_APP_PORT_HTTP)"
postgres_db="$(read_env_value POSTGRES_DB)"
postgres_user="$(read_env_value POSTGRES_USER)"
[[ "$postgres_db" =~ ^[A-Za-z_][A-Za-z0-9_]{0,62}$ ]] || fail "POSTGRES_DB is invalid"
[[ "$postgres_user" =~ ^[A-Za-z_][A-Za-z0-9_]{0,62}$ ]] || fail "POSTGRES_USER is invalid"
max_file_size="$(read_env_value FILES_MAX_FILE_SIZE)"
[[ "$max_file_size" =~ ^[1-9][0-9]*(b|kb|mb|gb|tb)$ ]] ||
fail "FILES_MAX_FILE_SIZE must be a positive byte quantity such as 100mb"
data_raw="$(read_env_value APP_DATA_DIR)"
[[ -n "$data_raw" && "$data_raw" != /* ]] || fail "APP_DATA_DIR must be a non-empty relative path"
case "$data_raw" in
*$'\n'* | *$'\r'* | *\\* | *:* | *'$'* | *'#'* | *'"'* | *"'"*)
fail "APP_DATA_DIR contains unsupported characters"
;;
esac
relative_data="${data_raw#./}"
[[ -n "$relative_data" ]] || fail "APP_DATA_DIR must not resolve to the version root"
current="$ROOT_DIR"
IFS=/ read -r -a components <<< "$relative_data"
for component in "${components[@]}"; do
[[ -n "$component" && "$component" != . && "$component" != .. ]] || fail "APP_DATA_DIR contains traversal"
current="$current/$component"
[[ ! -L "$current" ]] || fail "APP_DATA_DIR must not contain symbolic-link components"
done
data_dir="$(realpath -m -- "$ROOT_DIR/$relative_data")"
case "$data_dir" in
"$ROOT_DIR"/*) ;;
*) fail "APP_DATA_DIR must stay inside the application version directory" ;;
esac
install -d -m 0700 -- "$data_dir/postgres"
install -d -m 0750 -- "$data_dir/uploads" "$data_dir/public" "$data_dir/themes"
resolved_data="$(realpath -e -- "$data_dir")"
case "$resolved_data" in
"$ROOT_DIR"/*) ;;
*) fail "APP_DATA_DIR resolves outside the application version directory" ;;
esac
chown -R 70:70 -- "$resolved_data/postgres"
chown -R 1000:1000 -- "$resolved_data/uploads" "$resolved_data/public" "$resolved_data/themes"
chmod 0700 -- "$resolved_data/postgres"
zipline_init_credentials "$ENV_FILE" "$resolved_data"
chmod 600 "$ENV_FILE"
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# 1Panel removes the installation directory after this hook, including relative APP_DATA_DIR contents.
# Back up required data outside the application directory before uninstalling.
exit 0
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
source "${ROOT_DIR}/scripts/credentials.sh"
zipline_restore_upgrade_credentials "$ROOT_DIR" "$ENV_FILE"
command -v docker >/dev/null 2>&1 || zipline_fail "Docker is required to verify the Zipline migration before upgrading"
command -v timeout >/dev/null 2>&1 || zipline_fail "timeout is required to verify the Zipline migration before upgrading"
compose=(docker compose --project-directory "$ROOT_DIR" -f "$ROOT_DIR/docker-compose.yml")
"${compose[@]}" up -d zipline-postgres
postgres_db="$(zipline_read_env_value "$ENV_FILE" POSTGRES_DB)" ||
zipline_fail "POSTGRES_DB is missing from Zipline .env"
postgres_user="$(zipline_read_env_value "$ENV_FILE" POSTGRES_USER)" ||
zipline_fail "POSTGRES_USER is missing from Zipline .env"
ready=false
for attempt in {1..60}; do
if "${compose[@]}" exec -T zipline-postgres pg_isready -q -U "$postgres_user" -d "$postgres_db"; then
ready=true
break
fi
sleep 2
done
[[ "$ready" == true ]] || zipline_fail "Zipline PostgreSQL did not become ready for the staged upgrade"
drizzle_table_exists="$(timeout 15 "${compose[@]}" exec -T zipline-postgres \
sh -ec 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atq -c "$1"' sh \
"SELECT to_regclass('drizzle.__drizzle_migrations') IS NOT NULL")" ||
zipline_fail "Zipline migration history could not be verified; keep the existing version and repair the database before upgrading"
migration_state="prisma"
if [[ "$drizzle_table_exists" == t ]]; then
drizzle_history="$(timeout 15 "${compose[@]}" exec -T zipline-postgres \
sh -ec 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atq -c "$1"' sh \
"SELECT EXISTS (SELECT 1 FROM drizzle.__drizzle_migrations)")" ||
zipline_fail "Zipline Drizzle migration history could not be verified; keep the existing version and repair the database before upgrading"
if [[ "$drizzle_history" == t ]]; then
migration_state="drizzle"
fi
fi
if [[ "$migration_state" != drizzle ]]; then
prisma_migration_state="$(timeout 15 "${compose[@]}" exec -T zipline-postgres sh -ec 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atq -c "SELECT CASE WHEN count(*) = 1 AND bool_and(finished_at IS NOT NULL AND rolled_back_at IS NULL AND applied_steps_count >= 1) THEN '\''ok'\'' ELSE '\''bad'\'' END FROM public._prisma_migrations WHERE migration_name = '\''20260806081144_remove_version_api'\'' AND checksum = '\''08368d44344f934f07757dc014bd3a2022b14a8b377e1f8f5fb2d4836c3dc1ce'\'';"')" ||
zipline_fail "Zipline 4.7 migration history could not be verified; keep the existing version and repair the database before upgrading"
[[ "$prisma_migration_state" == ok ]] ||
zipline_fail "Zipline 4.7 migration is missing or incomplete; keep the existing version and allow its migration to finish before upgrading"
migration_state="prisma"
fi
if [[ "$migration_state" == drizzle ]]; then
printf '%s\n' "Zipline Drizzle migration history verified; proceeding with the 4.8 upgrade."
else
printf '%s\n' "Zipline 4.7 migration verified; proceeding with the 4.8 upgrade."
fi