diff --git a/apps/serverkit/1.11.4/scripts/upgrade.sh b/apps/serverkit/1.11.4/scripts/upgrade.sh new file mode 100755 index 000000000..6abb887c4 --- /dev/null +++ b/apps/serverkit/1.11.4/scripts/upgrade.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +export PATH + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)" +DATA_DIR="$ROOT_DIR/data" +COMPOSE_FILE="$ROOT_DIR/docker-compose.yml" +LEGACY_VOLUME="serverkit-data" +MIGRATION_IMAGE="jhd3197/serverkit:1.11.4" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +has_data_files() { + local entry + for entry in "$DATA_DIR"/* "$DATA_DIR"/.[!.]* "$DATA_DIR"/..?*; do + if [[ -e "$entry" || -L "$entry" ]]; then + return 0 + fi + done + return 1 +} + +bash "$SCRIPT_DIR/init.sh" + +if ! grep -Fq 'serverkit-data:/app/instance' "$COMPOSE_FILE"; then + exit 0 +fi + +if [[ -f "$DATA_DIR/serverkit.db" ]]; then + exit 0 +fi + +if has_data_files; then + fail "ServerKit data directory is non-empty but serverkit.db is missing; refusing legacy-volume migration" +fi + +command -v docker >/dev/null 2>&1 || fail "docker is required for legacy ServerKit volume migration" +if ! docker volume inspect "$LEGACY_VOLUME" >/dev/null 2>&1; then + exit 0 +fi +docker image inspect "$MIGRATION_IMAGE" >/dev/null 2>&1 || fail "migration image is unavailable: $MIGRATION_IMAGE" + +docker run --rm --network none --user 0 --entrypoint /bin/sh \ + --volume "$LEGACY_VOLUME:/legacy:ro" \ + --volume "$DATA_DIR:/target:rw" \ + "$MIGRATION_IMAGE" \ + -c ' +set -eu +stage=/target/.serverkit-data-migration +[ ! -e "$stage" ] && [ ! -L "$stage" ] || { echo "migration staging path already exists" >&2; exit 1; } +mkdir "$stage" +cleanup() { + rm -rf -- "$stage" +} +trap cleanup EXIT + +found=0 +found_db=0 +for item in /legacy/* /legacy/.[!.]* /legacy/..?*; do + [ -e "$item" ] || [ -L "$item" ] || continue + [ -f "$item" ] && [ ! -L "$item" ] || { echo "legacy volume contains an unsupported non-regular file" >&2; exit 1; } + name=${item##*/} + case "$name" in + serverkit.db|serverkit.db-wal|serverkit.db-shm) ;; + *) echo "legacy volume contains an unexpected file" >&2; exit 1 ;; + esac + cp -p -- "$item" "$stage/$name" + found=1 + [ "$name" = serverkit.db ] && found_db=1 +done + +[ "$found" = 0 ] && exit 0 +[ "$found_db" = 1 ] || { echo "legacy volume does not contain serverkit.db" >&2; exit 1; } + +for name in serverkit.db-wal serverkit.db-shm serverkit.db; do + if [ -e "$stage/$name" ]; then + [ ! -e "/target/$name" ] && [ ! -L "/target/$name" ] || { echo "target data file already exists" >&2; exit 1; } + chown 999:999 "$stage/$name" + chmod 0640 "$stage/$name" + mv -- "$stage/$name" "/target/$name" + fi +done +' + +bash "$SCRIPT_DIR/init.sh" diff --git a/apps/serverkit/README.md b/apps/serverkit/README.md index f3a4566bf..ea4e5e6bc 100644 --- a/apps/serverkit/README.md +++ b/apps/serverkit/README.md @@ -29,7 +29,7 @@ ServerKit 是一个自托管的远程服务器管理与运维面板,提供服 3. 通过安装时配置的 HTTP 端口打开 ServerKit。首次打开时使用注册页面创建第一个用户;第一个用户会被授予管理员角色。 4. 如果使用反向代理,请填写 `SERVERKIT_PUBLIC_URL`,并仅在请求一定经过可信反向代理时将 `TRUST_PROXY_HEADERS` 设为 `true`。需要同时允许多个浏览器来源时,在 `CORS_ORIGINS` 中用逗号分隔填写。 -SQLite 数据库持久化在应用安装目录的 `data/serverkit.db`,并以 bind mount 映射到容器内的 `/app/instance`。该目录位于 1Panel 应用目录中,便于随应用数据一起备份。不要删除该目录,否则会丢失 ServerKit 数据。 +SQLite 数据库持久化在应用安装目录的 `data/serverkit.db`,并以 bind mount 映射到容器内的 `/app/instance`。该目录位于 1Panel 应用目录中,便于随应用数据一起备份。不要删除该目录,否则会丢失 ServerKit 数据。由旧版 `serverkit-data` 卷升级时,目标版本会在新目录为空时自动迁移 SQLite 文件;旧卷会保留,不会自动删除。 ## 版本 diff --git a/apps/serverkit/README_en.md b/apps/serverkit/README_en.md index 2171330da..b56f5b97d 100644 --- a/apps/serverkit/README_en.md +++ b/apps/serverkit/README_en.md @@ -29,7 +29,7 @@ This is ServerKit's containerized deployment. The package does not mount the Doc 3. Open ServerKit on the configured HTTP port. On the first visit, use the registration page to create the first user; the first user is given the administrator role. 4. When using a reverse proxy, set `SERVERKIT_PUBLIC_URL`. Set `TRUST_PROXY_HEADERS` to `true` only when every request is guaranteed to pass through a trusted proxy. Add multiple browser origins as a comma-separated `CORS_ORIGINS` value. -The SQLite database is persisted at `data/serverkit.db` in the application install directory and bind-mounted to `/app/instance`. This keeps the data under the 1Panel application directory for application backups. Do not remove this directory unless the ServerKit data can be discarded. +The SQLite database is persisted at `data/serverkit.db` in the application install directory and bind-mounted to `/app/instance`. This keeps the data under the 1Panel application directory for application backups. Do not remove this directory unless the ServerKit data can be discarded. When upgrading from the legacy `serverkit-data` volume, the target version migrates the SQLite files when the new directory is empty; the legacy volume is retained and is not deleted automatically. ## Versions diff --git a/apps/serverkit/latest/scripts/upgrade.sh b/apps/serverkit/latest/scripts/upgrade.sh new file mode 100755 index 000000000..3d997cf94 --- /dev/null +++ b/apps/serverkit/latest/scripts/upgrade.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +export PATH + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)" +DATA_DIR="$ROOT_DIR/data" +COMPOSE_FILE="$ROOT_DIR/docker-compose.yml" +LEGACY_VOLUME="serverkit-data" +MIGRATION_IMAGE="jhd3197/serverkit:latest" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +has_data_files() { + local entry + for entry in "$DATA_DIR"/* "$DATA_DIR"/.[!.]* "$DATA_DIR"/..?*; do + if [[ -e "$entry" || -L "$entry" ]]; then + return 0 + fi + done + return 1 +} + +bash "$SCRIPT_DIR/init.sh" + +if ! grep -Fq 'serverkit-data:/app/instance' "$COMPOSE_FILE"; then + exit 0 +fi + +if [[ -f "$DATA_DIR/serverkit.db" ]]; then + exit 0 +fi + +if has_data_files; then + fail "ServerKit data directory is non-empty but serverkit.db is missing; refusing legacy-volume migration" +fi + +command -v docker >/dev/null 2>&1 || fail "docker is required for legacy ServerKit volume migration" +if ! docker volume inspect "$LEGACY_VOLUME" >/dev/null 2>&1; then + exit 0 +fi +docker image inspect "$MIGRATION_IMAGE" >/dev/null 2>&1 || fail "migration image is unavailable: $MIGRATION_IMAGE" + +docker run --rm --network none --user 0 --entrypoint /bin/sh \ + --volume "$LEGACY_VOLUME:/legacy:ro" \ + --volume "$DATA_DIR:/target:rw" \ + "$MIGRATION_IMAGE" \ + -c ' +set -eu +stage=/target/.serverkit-data-migration +[ ! -e "$stage" ] && [ ! -L "$stage" ] || { echo "migration staging path already exists" >&2; exit 1; } +mkdir "$stage" +cleanup() { + rm -rf -- "$stage" +} +trap cleanup EXIT + +found=0 +found_db=0 +for item in /legacy/* /legacy/.[!.]* /legacy/..?*; do + [ -e "$item" ] || [ -L "$item" ] || continue + [ -f "$item" ] && [ ! -L "$item" ] || { echo "legacy volume contains an unsupported non-regular file" >&2; exit 1; } + name=${item##*/} + case "$name" in + serverkit.db|serverkit.db-wal|serverkit.db-shm) ;; + *) echo "legacy volume contains an unexpected file" >&2; exit 1 ;; + esac + cp -p -- "$item" "$stage/$name" + found=1 + [ "$name" = serverkit.db ] && found_db=1 +done + +[ "$found" = 0 ] && exit 0 +[ "$found_db" = 1 ] || { echo "legacy volume does not contain serverkit.db" >&2; exit 1; } + +for name in serverkit.db-wal serverkit.db-shm serverkit.db; do + if [ -e "$stage/$name" ]; then + [ ! -e "/target/$name" ] && [ ! -L "/target/$name" ] || { echo "target data file already exists" >&2; exit 1; } + chown 999:999 "$stage/$name" + chmod 0640 "$stage/$name" + mv -- "$stage/$name" "/target/$name" + fi +done +' + +bash "$SCRIPT_DIR/init.sh"