diff --git a/README_DOCKER.md b/README_DOCKER.md index 9ac82da..dd5921e 100644 --- a/README_DOCKER.md +++ b/README_DOCKER.md @@ -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 打包整个副本目录,排除本地缓存和归档备份: diff --git a/scripts/migrate_container_db_to_volume.sh b/scripts/migrate_container_db_to_volume.sh index 9409082..23c0092 100755 --- a/scripts/migrate_container_db_to_volume.sh +++ b/scripts/migrate_container_db_to_volume.sh @@ -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=." - 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= 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"