Merge pull request #6954 from okxlin/codex/serverkit-bind-final-20260917

fix: keep ServerKit data inside the 1Panel app directory
This commit is contained in:
okxlin
2026-09-18 09:17:13 +08:00
committed by GitHub
8 changed files with 554 additions and 12 deletions
+1 -5
View File
@@ -17,7 +17,7 @@ services:
- "TRUSTED_PROXY_HOPS=1"
- "SERVERKIT_GITHUB_REPO=jhd3197/ServerKit"
volumes:
- "serverkit-data:/app/instance"
- "./data:/app/instance"
networks:
- "1panel-network"
labels:
@@ -35,10 +35,6 @@ services:
max-size: "10m"
max-file: "3"
volumes:
serverkit-data:
name: serverkit-data
networks:
1panel-network:
external: true
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
read_env_value() {
local key="$1"
[[ -f "$ENV_FILE" ]] || return 0
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"
}
configured_value() {
local key="$1"
local default_value="$2"
local value
value="${!key:-}"
if [[ -z "$value" ]]; then
value="$(read_env_value "$key")"
fi
printf '%s\n' "${value:-$default_value}"
}
resolve_app_path() {
local key="$1"
local raw="$2"
local clean candidate resolved current part
local -a parts=()
case "$raw" in
""|/*|.|..|../*|*/../*|*/..) echo "unsafe ${key} path" >&2; return 1 ;;
esac
if [[ "$raw" =~ [[:cntrl:]] ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
clean="${raw#./}"
[[ -n "$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
command -v realpath >/dev/null 2>&1 || { echo "realpath is required" >&2; return 1; }
candidate="$ROOT_DIR/$clean"
resolved="$(realpath -m -- "$candidate")" || { echo "unsafe ${key} path" >&2; return 1; }
case "$resolved" in
"$ROOT_DIR"/*) ;;
*) echo "unsafe ${key} path" >&2; return 1 ;;
esac
current="$ROOT_DIR"
IFS='/' read -r -a parts <<< "$clean"
for part in "${parts[@]}"; do
[[ -z "$part" || "$part" == "." ]] && continue
current="$current/$part"
if [[ -L "$current" ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
done
printf '%s\n' "$resolved"
}
ensure_dir() {
local key="$1"
local raw
local path
raw="$(configured_value "$key" "$2")"
path="$(resolve_app_path "$key" "$raw")"
mkdir -p -- "$path"
[[ "$(resolve_app_path "$key" "$raw")" == "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
}
resolve_direct_child() {
local key="$1"
local raw="$2"
local clean path
clean="${raw#./}"
if [[ -z "$clean" || "$clean" == */* ]]; then
echo "unsafe ${key} path: lifecycle directories must be direct children of the version root" >&2
return 1
fi
path="$(resolve_app_path "$key" "$raw")"
[[ "$path" == "$ROOT_DIR/$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
printf '%s\n' "$path"
}
verify_trusted_root_chain() {
local current owner mode
[[ "$(id -u)" == "0" ]] || { echo "directory ownership initialization must run as root" >&2; return 1; }
command -v stat >/dev/null 2>&1 || { echo "stat is required" >&2; return 1; }
current="$ROOT_DIR"
while [[ "$current" != "/" ]]; do
[[ -d "$current" && ! -L "$current" ]] || { echo "unsafe version root chain: $current" >&2; return 1; }
IFS=':' read -r owner mode < <(stat -c '%u:%a' -- "$current")
if [[ "$current" == "$ROOT_DIR" ]]; then
# 1Panel local-app copies may be owned by the panel agent UID 1001.
[[ "$owner" == "0" || "$owner" == "1001" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
else
[[ "$owner" == "0" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
fi
[[ "$mode" =~ ^[0-7]{3,4}$ ]] || { echo "unsafe version root chain mode: $current" >&2; return 1; }
(( (8#$mode & 0022) == 0 )) || { echo "unsafe version root chain permissions: $current" >&2; return 1; }
current="$(dirname -- "$current")"
done
}
declare -A OWNED_PATHS=()
declare -A OWNED_KEYS_BY_PATH=()
register_owned_dir() {
local key="$1"
local raw="$2"
local path previous_key
path="$(resolve_direct_child "$key" "$raw")"
if [[ -n "${OWNED_KEYS_BY_PATH[$path]+x}" ]]; then
previous_key="${OWNED_KEYS_BY_PATH[$path]}"
echo "duplicate directory ownership target: $path ($previous_key and $key)" >&2
return 1
fi
OWNED_KEYS_BY_PATH["$path"]="$key"
OWNED_PATHS["$key"]="$path"
}
register_configured_owned_dir() {
local key="$1"
local default_value="$2"
local raw
raw="$(configured_value "$key" "$default_value")"
register_owned_dir "$key" "$raw"
}
register_fixed_owned_dir() {
local source="$1"
register_owned_dir "fixed directory $source" "$source"
}
apply_owned_dir() {
local key="$1"
local uid="$2"
local gid="$3"
local mode="$4"
local path actual expected_mode
[[ -n "${OWNED_PATHS[$key]+x}" ]] || { echo "missing directory ownership preflight: $key" >&2; return 1; }
path="${OWNED_PATHS[$key]}"
verify_trusted_root_chain
if [[ -e "$path" || -L "$path" ]]; then
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
else
mkdir -- "$path"
fi
chmod "$mode" -- "$path"
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
chown --no-dereference "$uid:$gid" -- "$path"
expected_mode="${mode#0}"
actual="$(stat -c '%u:%g:%a' -- "$path")"
[[ "$actual" == "$uid:$gid:$expected_mode" ]] || { echo "${key} ownership/mode mismatch: expected ${uid}:${gid}:${expected_mode}, got ${actual}" >&2; return 1; }
}
ensure_owned_dir() {
local key="$1"
local default_value="$2"
[[ -n "$default_value" ]] || { echo "missing directory ownership default: $key" >&2; return 1; }
apply_owned_dir "$key" "$3" "$4" "$5"
}
ensure_fixed_owned_dir() {
local source="$1"
apply_owned_dir "fixed directory $source" "$2" "$3" "$4"
}
register_fixed_owned_dir "./data"
ensure_fixed_owned_dir "./data" "999" "999" "0750"
+90
View File
@@ -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"
+11 -1
View File
@@ -29,7 +29,17 @@ ServerKit 是一个自托管的远程服务器管理与运维面板,提供服
3. 通过安装时配置的 HTTP 端口打开 ServerKit。首次打开时使用注册页面创建第一个用户;第一个用户会被授予管理员角色。
4. 如果使用反向代理,请填写 `SERVERKIT_PUBLIC_URL`,并仅在请求一定经过可信反向代理时将 `TRUST_PROXY_HEADERS` 设为 `true`。需要同时允许多个浏览器来源时,在 `CORS_ORIGINS` 中用逗号分隔填写。
SQLite 数据库持久化在 `serverkit-data` 卷的 `/app/instance/serverkit.db`。不要删除该,否则会丢失 ServerKit 数据。
SQLite 数据库持久化在应用安装目录的 `data/serverkit.db`,并以 bind mount 映射到容器内的 `/app/instance`。该目录位于 1Panel 应用目录中,便于随应用数据一起备份。不要删除该目录,否则会丢失 ServerKit 数据。由旧版 `serverkit-data` 卷升级时,目标版本会在新目录为空时自动迁移 SQLite 文件;旧卷会保留,不会自动删除。
## 安全提示
维护侧使用 Trivy 对 `serverkit` 的每个版本镜像进行了漏洞扫描;当前每个扫描报告包含 Critical=5、High=77、Total=82(两个版本标签解析到相同镜像内容)。这些风险来自上游镜像及其基础系统组件,本应用包未对其进行修复。请优先在可信内网中使用,并关注上游镜像更新。
高风险示例:
- Critical `CVE-2025-7458``libsqlite3-0`):当前暂无修复版本。
- Critical `CVE-2026-13221`、`CVE-2026-42496`、`CVE-2026-8376``perl-base`):当前暂无修复版本。
- Critical `CVE-2023-45853``zlib1g`):当前暂无修复版本。
## 版本
+11 -1
View File
@@ -29,7 +29,17 @@ 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 `/app/instance/serverkit.db` in the `serverkit-data` volume. Do not remove that volume 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.
## Security notice
The maintainer scanned each packaged `serverkit` image tag with Trivy; each report contains Critical=5, High=77, Total=82 (both tags resolve to the same image content). These findings originate from the upstream image and its base-system components and are not remediated by this package. Prefer trusted private networks and monitor upstream image updates.
High-risk examples:
- Critical `CVE-2025-7458` (`libsqlite3-0`): no fix is currently available.
- Critical `CVE-2026-13221`, `CVE-2026-42496`, and `CVE-2026-8376` (`perl-base`): no fix is currently available.
- Critical `CVE-2023-45853` (`zlib1g`): no fix is currently available.
## Versions
+1 -5
View File
@@ -17,7 +17,7 @@ services:
- "TRUSTED_PROXY_HOPS=1"
- "SERVERKIT_GITHUB_REPO=jhd3197/ServerKit"
volumes:
- "serverkit-data:/app/instance"
- "./data:/app/instance"
networks:
- "1panel-network"
labels:
@@ -35,10 +35,6 @@ services:
max-size: "10m"
max-file: "3"
volumes:
serverkit-data:
name: serverkit-data
networks:
1panel-network:
external: true
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
read_env_value() {
local key="$1"
[[ -f "$ENV_FILE" ]] || return 0
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"
}
configured_value() {
local key="$1"
local default_value="$2"
local value
value="${!key:-}"
if [[ -z "$value" ]]; then
value="$(read_env_value "$key")"
fi
printf '%s\n' "${value:-$default_value}"
}
resolve_app_path() {
local key="$1"
local raw="$2"
local clean candidate resolved current part
local -a parts=()
case "$raw" in
""|/*|.|..|../*|*/../*|*/..) echo "unsafe ${key} path" >&2; return 1 ;;
esac
if [[ "$raw" =~ [[:cntrl:]] ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
clean="${raw#./}"
[[ -n "$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
command -v realpath >/dev/null 2>&1 || { echo "realpath is required" >&2; return 1; }
candidate="$ROOT_DIR/$clean"
resolved="$(realpath -m -- "$candidate")" || { echo "unsafe ${key} path" >&2; return 1; }
case "$resolved" in
"$ROOT_DIR"/*) ;;
*) echo "unsafe ${key} path" >&2; return 1 ;;
esac
current="$ROOT_DIR"
IFS='/' read -r -a parts <<< "$clean"
for part in "${parts[@]}"; do
[[ -z "$part" || "$part" == "." ]] && continue
current="$current/$part"
if [[ -L "$current" ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
done
printf '%s\n' "$resolved"
}
ensure_dir() {
local key="$1"
local raw
local path
raw="$(configured_value "$key" "$2")"
path="$(resolve_app_path "$key" "$raw")"
mkdir -p -- "$path"
[[ "$(resolve_app_path "$key" "$raw")" == "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
}
resolve_direct_child() {
local key="$1"
local raw="$2"
local clean path
clean="${raw#./}"
if [[ -z "$clean" || "$clean" == */* ]]; then
echo "unsafe ${key} path: lifecycle directories must be direct children of the version root" >&2
return 1
fi
path="$(resolve_app_path "$key" "$raw")"
[[ "$path" == "$ROOT_DIR/$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
printf '%s\n' "$path"
}
verify_trusted_root_chain() {
local current owner mode
[[ "$(id -u)" == "0" ]] || { echo "directory ownership initialization must run as root" >&2; return 1; }
command -v stat >/dev/null 2>&1 || { echo "stat is required" >&2; return 1; }
current="$ROOT_DIR"
while [[ "$current" != "/" ]]; do
[[ -d "$current" && ! -L "$current" ]] || { echo "unsafe version root chain: $current" >&2; return 1; }
IFS=':' read -r owner mode < <(stat -c '%u:%a' -- "$current")
if [[ "$current" == "$ROOT_DIR" ]]; then
# 1Panel local-app copies may be owned by the panel agent UID 1001.
[[ "$owner" == "0" || "$owner" == "1001" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
else
[[ "$owner" == "0" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
fi
[[ "$mode" =~ ^[0-7]{3,4}$ ]] || { echo "unsafe version root chain mode: $current" >&2; return 1; }
(( (8#$mode & 0022) == 0 )) || { echo "unsafe version root chain permissions: $current" >&2; return 1; }
current="$(dirname -- "$current")"
done
}
declare -A OWNED_PATHS=()
declare -A OWNED_KEYS_BY_PATH=()
register_owned_dir() {
local key="$1"
local raw="$2"
local path previous_key
path="$(resolve_direct_child "$key" "$raw")"
if [[ -n "${OWNED_KEYS_BY_PATH[$path]+x}" ]]; then
previous_key="${OWNED_KEYS_BY_PATH[$path]}"
echo "duplicate directory ownership target: $path ($previous_key and $key)" >&2
return 1
fi
OWNED_KEYS_BY_PATH["$path"]="$key"
OWNED_PATHS["$key"]="$path"
}
register_configured_owned_dir() {
local key="$1"
local default_value="$2"
local raw
raw="$(configured_value "$key" "$default_value")"
register_owned_dir "$key" "$raw"
}
register_fixed_owned_dir() {
local source="$1"
register_owned_dir "fixed directory $source" "$source"
}
apply_owned_dir() {
local key="$1"
local uid="$2"
local gid="$3"
local mode="$4"
local path actual expected_mode
[[ -n "${OWNED_PATHS[$key]+x}" ]] || { echo "missing directory ownership preflight: $key" >&2; return 1; }
path="${OWNED_PATHS[$key]}"
verify_trusted_root_chain
if [[ -e "$path" || -L "$path" ]]; then
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
else
mkdir -- "$path"
fi
chmod "$mode" -- "$path"
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
chown --no-dereference "$uid:$gid" -- "$path"
expected_mode="${mode#0}"
actual="$(stat -c '%u:%g:%a' -- "$path")"
[[ "$actual" == "$uid:$gid:$expected_mode" ]] || { echo "${key} ownership/mode mismatch: expected ${uid}:${gid}:${expected_mode}, got ${actual}" >&2; return 1; }
}
ensure_owned_dir() {
local key="$1"
local default_value="$2"
[[ -n "$default_value" ]] || { echo "missing directory ownership default: $key" >&2; return 1; }
apply_owned_dir "$key" "$3" "$4" "$5"
}
ensure_fixed_owned_dir() {
local source="$1"
apply_owned_dir "fixed directory $source" "$2" "$3" "$4"
}
register_fixed_owned_dir "./data"
ensure_fixed_owned_dir "./data" "999" "999" "0750"
+90
View File
@@ -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"