This commit is contained in:
aaron 2026-05-14 10:32:57 +08:00
parent ef56008ccb
commit 3446b08781
2 changed files with 87 additions and 17 deletions

View File

@ -140,6 +140,9 @@ bash scripts/migrate_container_db_to_volume.sh
# 指定旧容器名
SOURCE_CONTAINER=old-alphax-web bash scripts/migrate_container_db_to_volume.sh
# 旧容器已经停止也可以复制主 DB如果仍在运行脚本会用更安全的 SQLite backup
ALLOW_STOPPED=1 SOURCE_CONTAINER=old-alphax-web bash scripts/migrate_container_db_to_volume.sh
# 只复制,不自动重启 compose 服务
RECREATE=0 bash scripts/migrate_container_db_to_volume.sh
@ -147,6 +150,19 @@ RECREATE=0 bash scripts/migrate_container_db_to_volume.sh
FORCE=1 bash scripts/migrate_container_db_to_volume.sh
```
如果脚本提示找不到 `alphax-web`,先查看服务器上的真实容器名:
```bash
docker compose ps
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
```
然后用真实容器名执行:
```bash
SOURCE_CONTAINER=<真实容器名> bash scripts/migrate_container_db_to_volume.sh
```
## 打包迁移到新服务器
建议只打包代码和配置骨架,不把 DB 直接打进镜像。可以用 tar 打包整个副本目录,排除本地缓存和归档备份:

View File

@ -17,6 +17,7 @@ set -euo pipefail
# SOURCE_CONTAINER=old-alphax-web bash scripts/migrate_container_db_to_volume.sh
# SERVICE=alphax-web TARGET_DB=data/altcoin_monitor.db bash scripts/migrate_container_db_to_volume.sh
# RECREATE=0 bash scripts/migrate_container_db_to_volume.sh
# ALLOW_STOPPED=1 SOURCE_CONTAINER=old-alphax-web bash scripts/migrate_container_db_to_volume.sh
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
@ -31,6 +32,7 @@ BACKUP_DIR="${BACKUP_DIR:-data/backups}"
RECREATE="${RECREATE:-1}"
STOP_SCHEDULER="${STOP_SCHEDULER:-1}"
FORCE="${FORCE:-0}"
ALLOW_STOPPED="${ALLOW_STOPPED:-1}"
compose() {
${COMPOSE_CMD} "$@"
@ -49,6 +51,15 @@ service_exists() {
compose config --services 2>/dev/null | grep -qx "$1"
}
container_running() {
local cid="$1"
[ "$(docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null || echo false)" = "true" ]
}
container_name_for_log() {
docker inspect -f '{{.Name}}' "$1" 2>/dev/null | sed 's#^/##'
}
container_id() {
if [ -n "$SOURCE_CONTAINER" ]; then
docker inspect -f '{{.Id}}' "$SOURCE_CONTAINER" >/dev/null 2>&1 || die "SOURCE_CONTAINER not found: $SOURCE_CONTAINER"
@ -57,8 +68,30 @@ container_id() {
fi
local cid
cid="$(compose ps -q "$SERVICE" 2>/dev/null || true)"
[ -n "$cid" ] || die "service '$SERVICE' is not running. Start it first, or set SOURCE_CONTAINER=<container_name>."
echo "$cid"
if [ -n "$cid" ]; then
echo "$cid"
return
fi
# Fallback for servers where the old container was not created by the current
# compose project, or the compose service name/project name changed.
for name in "$SERVICE" alphax-web alphax_web alphax; do
cid="$(docker ps -aq --filter "name=^/${name}$" | head -n 1)"
if [ -n "$cid" ]; then
echo "$cid"
return
fi
done
echo "ERROR: service '$SERVICE' is not running and no fallback container was found." >&2
echo "" >&2
echo "Run one of these on the server:" >&2
echo " docker compose ps" >&2
echo " docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'" >&2
echo "" >&2
echo "Then rerun with the actual container name, for example:" >&2
echo " SOURCE_CONTAINER=<container_name> bash scripts/migrate_container_db_to_volume.sh" >&2
exit 1
}
is_source_db_under_mount() {
@ -124,17 +157,27 @@ STAMP="$(date +%Y%m%d_%H%M%S)"
TMP_IN_CONTAINER="/tmp/alphax_container_db_${STAMP}.db"
TMP_ON_HOST="${BACKUP_DIR}/altcoin_monitor.from_container.${STAMP}.tmp.db"
info "source container: ${SOURCE_CONTAINER:-$SERVICE} ($CID)"
SOURCE_LABEL="${SOURCE_CONTAINER:-$(container_name_for_log "$CID")}"
info "source container: ${SOURCE_LABEL:-$SERVICE} ($CID)"
info "source db: $SOURCE_DB"
info "target db: $TARGET_DB"
docker exec -e SOURCE_DB="$SOURCE_DB" "$CID" sh -lc 'test -s "$SOURCE_DB"' \
|| die "source database does not exist or is empty inside container: $SOURCE_DB"
RUNNING=0
if container_running "$CID"; then
RUNNING=1
docker exec -e SOURCE_DB="$SOURCE_DB" "$CID" sh -lc 'test -s "$SOURCE_DB"' \
|| die "source database does not exist or is empty inside container: $SOURCE_DB"
if is_source_db_under_mount "$CID" && [ "$FORCE" != "1" ]; then
info "container path '$SOURCE_DB' is already under a mounted volume/bind mount."
info "No migration needed. Set FORCE=1 only if you intentionally want to copy it anyway."
exit 0
if is_source_db_under_mount "$CID" && [ "$FORCE" != "1" ]; then
info "container path '$SOURCE_DB' is already under a mounted volume/bind mount."
info "No migration needed. Set FORCE=1 only if you intentionally want to copy it anyway."
exit 0
fi
else
if [ "$ALLOW_STOPPED" != "1" ]; then
die "source container is stopped. Start it first, or set ALLOW_STOPPED=1 to docker cp the DB from the stopped container."
fi
info "source container is stopped; falling back to docker cp. SQLite WAL files cannot be checkpointed in this mode."
fi
mkdir -p "$(dirname "$TARGET_DB")" "$BACKUP_DIR"
@ -144,11 +187,12 @@ if [ "$STOP_SCHEDULER" = "1" ] && service_exists "$SCHEDULER_SERVICE"; then
compose stop "$SCHEDULER_SERVICE" >/dev/null 2>&1 || true
fi
info "creating SQLite backup inside container"
docker exec \
-e SOURCE_DB="$SOURCE_DB" \
-e TMP_DB="$TMP_IN_CONTAINER" \
"$CID" python - <<'PY'
if [ "$RUNNING" = "1" ]; then
info "creating SQLite backup inside running container"
docker exec \
-e SOURCE_DB="$SOURCE_DB" \
-e TMP_DB="$TMP_IN_CONTAINER" \
"$CID" python - <<'PY'
import os
import sqlite3
@ -173,9 +217,19 @@ if result != "ok":
print(f"backup_created={dst}")
PY
info "copying backup from container to host"
docker cp "${CID}:${TMP_IN_CONTAINER}" "$TMP_ON_HOST"
docker exec -e TMP_DB="$TMP_IN_CONTAINER" "$CID" sh -lc 'rm -f "$TMP_DB"' >/dev/null 2>&1 || true
info "copying backup from container to host"
docker cp "${CID}:${TMP_IN_CONTAINER}" "$TMP_ON_HOST"
docker exec -e TMP_DB="$TMP_IN_CONTAINER" "$CID" sh -lc 'rm -f "$TMP_DB"' >/dev/null 2>&1 || true
else
info "copying raw DB from stopped container"
docker cp "${CID}:${SOURCE_DB}" "$TMP_ON_HOST" \
|| die "failed to copy $SOURCE_DB from stopped container. Check SOURCE_DB path."
for suffix in "-wal" "-shm"; do
if docker cp "${CID}:${SOURCE_DB}${suffix}" "${TMP_ON_HOST}${suffix}" >/dev/null 2>&1; then
info "copied sidecar ${SOURCE_DB}${suffix}"
fi
done
fi
verify_db_on_host_if_possible "$TMP_ON_HOST"