From 79556f790310aa1b69e1d5e7c879001e790d7706 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 00:58:37 +0000 Subject: [PATCH 1/3] Update ghcr.io/diced/zipline Docker tag to v4.8.1 --- apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml | 2 +- apps/zipline/4.6.5/docker-compose.yml | 2 +- apps/zipline/4.7.0/docker-compose.yml | 2 +- apps/zipline/4.8.0/docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml b/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml index a1b337010..6104f7cff 100644 --- a/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml +++ b/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.6.5@sha256:bfd5b0f7b5b8b3ed058a81667c78a14a7f997115d8433bec273620ec81be51d4 + image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.6.5/docker-compose.yml b/apps/zipline/4.6.5/docker-compose.yml index a1b337010..6104f7cff 100644 --- a/apps/zipline/4.6.5/docker-compose.yml +++ b/apps/zipline/4.6.5/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.6.5@sha256:bfd5b0f7b5b8b3ed058a81667c78a14a7f997115d8433bec273620ec81be51d4 + image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.7.0/docker-compose.yml b/apps/zipline/4.7.0/docker-compose.yml index 8efd1c7de..6104f7cff 100644 --- a/apps/zipline/4.7.0/docker-compose.yml +++ b/apps/zipline/4.7.0/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.7.0@sha256:526fa08d2d3cf46bd959b2008987ca8d2a38b1ba840991b68cd9ff4b8c6a3fab + image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.8.0/docker-compose.yml b/apps/zipline/4.8.0/docker-compose.yml index da3423d7e..6104f7cff 100644 --- a/apps/zipline/4.8.0/docker-compose.yml +++ b/apps/zipline/4.8.0/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.8.0@sha256:8ae3cdf8cdbba2ac98754e9fc4ccec67bbaa86b6e6ab1ccf9683ddcff85eee72 + image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" From 087a69d5f2da5abef9642aff8f0758c92045c28a Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:25:50 +0800 Subject: [PATCH 2/3] fix(zipline): add 4.8.1 package version --- .../docker-compose.yml | 2 +- apps/zipline/4.6.5/docker-compose.yml | 2 +- apps/zipline/4.7.0/docker-compose.yml | 2 +- apps/zipline/4.8.0/docker-compose.yml | 2 +- apps/zipline/4.8.1/.env.sample | 9 + apps/zipline/4.8.1/data.yml | 135 ++++++++ apps/zipline/4.8.1/docker-compose.yml | 79 +++++ apps/zipline/4.8.1/scripts/credentials.sh | 290 ++++++++++++++++++ apps/zipline/4.8.1/scripts/init.sh | 95 ++++++ apps/zipline/4.8.1/scripts/uninstall.sh | 6 + apps/zipline/4.8.1/scripts/upgrade.sh | 36 +++ 11 files changed, 654 insertions(+), 4 deletions(-) create mode 100644 apps/zipline/4.8.1/.env.sample create mode 100644 apps/zipline/4.8.1/data.yml create mode 100644 apps/zipline/4.8.1/docker-compose.yml create mode 100644 apps/zipline/4.8.1/scripts/credentials.sh create mode 100755 apps/zipline/4.8.1/scripts/init.sh create mode 100755 apps/zipline/4.8.1/scripts/uninstall.sh create mode 100755 apps/zipline/4.8.1/scripts/upgrade.sh diff --git a/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml b/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml index 6104f7cff..a1b337010 100644 --- a/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml +++ b/apps/zipline/4.6.5-postgres16-digest-721873c/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f + image: ghcr.io/diced/zipline:4.6.5@sha256:bfd5b0f7b5b8b3ed058a81667c78a14a7f997115d8433bec273620ec81be51d4 container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.6.5/docker-compose.yml b/apps/zipline/4.6.5/docker-compose.yml index 6104f7cff..a1b337010 100644 --- a/apps/zipline/4.6.5/docker-compose.yml +++ b/apps/zipline/4.6.5/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f + image: ghcr.io/diced/zipline:4.6.5@sha256:bfd5b0f7b5b8b3ed058a81667c78a14a7f997115d8433bec273620ec81be51d4 container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.7.0/docker-compose.yml b/apps/zipline/4.7.0/docker-compose.yml index 6104f7cff..8efd1c7de 100644 --- a/apps/zipline/4.7.0/docker-compose.yml +++ b/apps/zipline/4.7.0/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f + image: ghcr.io/diced/zipline:4.7.0@sha256:526fa08d2d3cf46bd959b2008987ca8d2a38b1ba840991b68cd9ff4b8c6a3fab container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.8.0/docker-compose.yml b/apps/zipline/4.8.0/docker-compose.yml index 6104f7cff..da3423d7e 100644 --- a/apps/zipline/4.8.0/docker-compose.yml +++ b/apps/zipline/4.8.0/docker-compose.yml @@ -30,7 +30,7 @@ services: createdBy: "Apps" zipline: - image: ghcr.io/diced/zipline:4.8.1@sha256:c6ec6d72ff3d2d96cafaf673d64b17e709d0abdae20db218bd13a29348fb212f + image: ghcr.io/diced/zipline:4.8.0@sha256:8ae3cdf8cdbba2ac98754e9fc4ccec67bbaa86b6e6ab1ccf9683ddcff85eee72 container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/zipline/4.8.1/.env.sample b/apps/zipline/4.8.1/.env.sample new file mode 100644 index 000000000..845005ffa --- /dev/null +++ b/apps/zipline/4.8.1/.env.sample @@ -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 diff --git a/apps/zipline/4.8.1/data.yml b/apps/zipline/4.8.1/data.yml new file mode 100644 index 000000000..8f8719194 --- /dev/null +++ b/apps/zipline/4.8.1/data.yml @@ -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 diff --git a/apps/zipline/4.8.1/docker-compose.yml b/apps/zipline/4.8.1/docker-compose.yml new file mode 100644 index 000000000..6104f7cff --- /dev/null +++ b/apps/zipline/4.8.1/docker-compose.yml @@ -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 diff --git a/apps/zipline/4.8.1/scripts/credentials.sh b/apps/zipline/4.8.1/scripts/credentials.sh new file mode 100644 index 000000000..f0d6b6cf2 --- /dev/null +++ b/apps/zipline/4.8.1/scripts/credentials.sh @@ -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." +} diff --git a/apps/zipline/4.8.1/scripts/init.sh b/apps/zipline/4.8.1/scripts/init.sh new file mode 100755 index 000000000..868a2fb3f --- /dev/null +++ b/apps/zipline/4.8.1/scripts/init.sh @@ -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" diff --git a/apps/zipline/4.8.1/scripts/uninstall.sh b/apps/zipline/4.8.1/scripts/uninstall.sh new file mode 100755 index 000000000..42b518ef4 --- /dev/null +++ b/apps/zipline/4.8.1/scripts/uninstall.sh @@ -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 diff --git a/apps/zipline/4.8.1/scripts/upgrade.sh b/apps/zipline/4.8.1/scripts/upgrade.sh new file mode 100755 index 000000000..8d6e8ec31 --- /dev/null +++ b/apps/zipline/4.8.1/scripts/upgrade.sh @@ -0,0 +1,36 @@ +#!/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" + +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" +[[ "$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" + +printf '%s\n' "Zipline 4.7 migration verified; proceeding with the 4.8 upgrade." From fc3a22418841be4191718e556511ffb2575d17aa Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:38:52 +0800 Subject: [PATCH 3/3] fix(zipline): support Drizzle-backed upgrades --- apps/zipline/4.8.1/scripts/upgrade.sh | 33 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/zipline/4.8.1/scripts/upgrade.sh b/apps/zipline/4.8.1/scripts/upgrade.sh index 8d6e8ec31..e4b9aeabd 100755 --- a/apps/zipline/4.8.1/scripts/upgrade.sh +++ b/apps/zipline/4.8.1/scripts/upgrade.sh @@ -28,9 +28,32 @@ for attempt in {1..60}; do done [[ "$ready" == true ]] || zipline_fail "Zipline PostgreSQL did not become ready for the staged upgrade" -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" -[[ "$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" +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" -printf '%s\n' "Zipline 4.7 migration verified; proceeding with the 4.8 upgrade." +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