docs: project name change, migration script

This commit is contained in:
3252a8
2026-04-27 16:11:06 +03:00
parent 3120884ed5
commit b39fb73c30
8 changed files with 489 additions and 56 deletions
+292
View File
@@ -0,0 +1,292 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=""
OLD_PREFIX="remnawave-tg-shop"
NEW_PREFIX="remnawave-minishop"
OLD_DB_VOLUME="${OLD_PREFIX}-db-data"
NEW_DB_VOLUME="${NEW_PREFIX}-db-data"
OLD_CADDY_VOLUMES=("${OLD_PREFIX}-caddy-data" "${OLD_PREFIX}-caddy-config")
NEW_CADDY_VOLUMES=("${NEW_PREFIX}-caddy-data" "${NEW_PREFIX}-caddy-config")
KNOWN_CONTAINERS=(
"${OLD_PREFIX}"
"${OLD_PREFIX}-db"
"${OLD_PREFIX}-caddy"
"${NEW_PREFIX}"
"${NEW_PREFIX}-db"
"${NEW_PREFIX}-caddy"
)
log() {
printf '%s\n' "$*"
}
die() {
printf 'Ошибка: %s\n' "$*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Не найдено обязательное средство \`$1\` в PATH."
}
resolve_root() {
if [[ -n "${PROJECT_ROOT:-}" ]]; then
[[ -d "$PROJECT_ROOT" ]] || die "PROJECT_ROOT не существует: $PROJECT_ROOT"
(cd -- "$PROJECT_ROOT" >/dev/null && pwd -P)
return
fi
local git_root
if git_root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
printf '%s\n' "$git_root"
return
fi
pwd -P
}
run() {
log "+ $*"
"$@"
}
container_exists() {
docker inspect "$1" >/dev/null 2>&1
}
container_running() {
[[ "$(docker inspect -f '{{.State.Running}}' "$1" 2>/dev/null || true)" == "true" ]]
}
stop_container() {
local name="$1"
if ! container_exists "$name"; then
return 1
fi
if container_running "$name"; then
docker stop "$name" >/dev/null
fi
docker rm "$name" >/dev/null
}
volume_exists() {
docker volume inspect "$1" >/dev/null 2>&1
}
volume_is_empty() {
docker run --rm -v "$1:/data" alpine sh -c 'test -z "$(find /data -mindepth 1 -print -quit)"' >/dev/null 2>&1
}
copy_volume() {
local source="$1"
local target="$2"
if ! volume_exists "$source"; then
log " - Пропускаю том \`$source\`: исходный том не найден."
return 1
fi
if volume_exists "$target" && ! volume_is_empty "$target"; then
log " - Пропускаю том \`$target\`: он уже не пустой."
return 1
fi
docker volume create "$target" >/dev/null
docker run --rm -v "$source:/from:ro" -v "$target:/to" alpine sh -c 'cd /from && cp -a . /to/'
}
is_old_postgres_host() {
grep -Eq "^[[:space:]]*POSTGRES_HOST[[:space:]]*=[[:space:]]*${OLD_PREFIX}-db[[:space:]]*(#.*)?$" "$ROOT/.env"
}
is_new_postgres_host() {
grep -Eq "^[[:space:]]*POSTGRES_HOST[[:space:]]*=[[:space:]]*${NEW_PREFIX}-db[[:space:]]*(#.*)?$" "$ROOT/.env"
}
update_postgres_host() {
if is_old_postgres_host; then
sed -i.bak -E "s|^([[:space:]]*POSTGRES_HOST[[:space:]]*=[[:space:]]*)${OLD_PREFIX}-db([[:space:]]*(#.*)?)$|\\1${NEW_PREFIX}-db\\2|" "$ROOT/.env"
log " - \`.env\` обновлён, резервная копия сохранена в \`.env.bak\`."
elif is_new_postgres_host; then
log " - \`POSTGRES_HOST\` уже указывает на новый контейнер, ничего менять не нужно."
else
log " - \`POSTGRES_HOST\` не похож на старую схему, пропускаю изменение."
fi
}
main() {
require_cmd git
require_cmd docker
docker info >/dev/null
ROOT="$(resolve_root)"
local compose_file="${COMPOSE_FILE:-docker-compose.yml}"
local target_branch="${TARGET_BRANCH:-main}"
local git_remote="${GIT_REMOTE:-origin}"
local new_origin_url="${NEW_ORIGIN_URL:-}"
local assume_yes="${ASSUME_YES:-0}"
local current_origin
local current_branch
local remote_ref
local head_commit
local compose_has_build=0
local compose_has_caddy=0
local -a compose_cmd
local -a running_containers=()
local -a summary=()
local -a up_args
local name
local source
local target
local answer
if [[ $compose_file != /* ]]; then
compose_file="$ROOT/$compose_file"
fi
[[ -f "$compose_file" ]] || die "Compose-файл не найден: $compose_file"
[[ -e "$ROOT/.git" ]] || die "Скрипт нужно запускать из корня git-репозитория."
[[ -f "$ROOT/.env" ]] || die "Не найден \`.env\` в корне репозитория."
if docker compose version >/dev/null 2>&1; then
compose_cmd=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
compose_cmd=(docker-compose)
else
die "Не найден ни \`docker compose\`, ни \`docker-compose\`."
fi
if [[ -n "$(git -C "$ROOT" status --porcelain=v1)" ]]; then
die "В рабочем дереве есть незакоммиченные изменения. Сначала сохраните их, чтобы миграция не затёрла чужие правки."
fi
if grep -Eq '^[[:space:]]*build:[[:space:]]*' "$compose_file"; then
compose_has_build=1
fi
if grep -Eq '^[[:space:]]*caddy:[[:space:]]*$' "$compose_file"; then
compose_has_caddy=1
fi
for name in "${KNOWN_CONTAINERS[@]}"; do
if container_exists "$name"; then
running_containers+=("$name")
fi
done
current_origin="$(git -C "$ROOT" remote get-url "$git_remote")"
if [[ -n "$new_origin_url" && "$current_origin" != "$new_origin_url" ]]; then
summary+=( "обновить $git_remote с \`$current_origin\` на \`$new_origin_url\`" )
fi
summary+=( "скачать ветку \`$target_branch\` из \`$git_remote\`" )
if volume_exists "$OLD_DB_VOLUME"; then
summary+=( "проверить том БД \`$OLD_DB_VOLUME\` и перенести в \`$NEW_DB_VOLUME\` при необходимости" )
fi
if ((compose_has_caddy)); then
for i in 0 1; do
source="${OLD_CADDY_VOLUMES[$i]}"
target="${NEW_CADDY_VOLUMES[$i]}"
if volume_exists "$source"; then
summary+=( "проверить том \`$source\` и перенести в \`$target\` при необходимости" )
fi
done
fi
if is_old_postgres_host; then
summary+=( "обновить \`POSTGRES_HOST\` в \`.env\`" )
fi
summary+=( "запустить compose-файл \`$(basename "$compose_file")\`" )
if [[ "$assume_yes" != "1" ]]; then
if [[ ! -t 0 ]]; then
die "Скрипт ожидает интерактивное подтверждение. Запустите с \`ASSUME_YES=1\` для неинтерактивного режима."
fi
log "План миграции:"
for name in "${summary[@]}"; do
log " - $name"
done
read -r -p "Продолжить? [y/N]: " answer
case "$answer" in
y|Y|yes|YES|Yes)
;;
*)
die "Миграция отменена пользователем."
;;
esac
fi
log "1. Останавливаю старый стек"
if ((${#running_containers[@]})); then
for name in "${running_containers[@]}"; do
stop_container "$name"
log " - контейнер \`$name\` остановлен/удалён"
done
else
log " - запущенных контейнеров старой схемы не найдено"
fi
if [[ -n "$new_origin_url" && "$current_origin" != "$new_origin_url" ]]; then
log "2. Обновляю origin"
run git -C "$ROOT" remote set-url "$git_remote" "$new_origin_url"
else
log "2. Origin уже актуален, пропускаю"
fi
log "3. Обновляю git до ветки \`$target_branch\`"
run git -C "$ROOT" fetch "$git_remote" "$target_branch"
current_branch="$(git -C "$ROOT" branch --show-current || true)"
if [[ -z "$current_branch" ]]; then
current_branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD)"
fi
if [[ "$current_branch" != "$target_branch" ]]; then
if git -C "$ROOT" show-ref --verify --quiet "refs/heads/$target_branch"; then
run git -C "$ROOT" switch "$target_branch"
else
run git -C "$ROOT" switch -c "$target_branch" --track "$git_remote/$target_branch"
fi
else
log " - уже на ветке \`$target_branch\`"
fi
remote_ref="$(git -C "$ROOT" rev-parse "$git_remote/$target_branch")"
head_commit="$(git -C "$ROOT" rev-parse HEAD)"
if [[ "$head_commit" != "$remote_ref" ]]; then
run git -C "$ROOT" pull --ff-only "$git_remote" "$target_branch"
else
log " - локальная ветка уже совпадает с удалённой, \`git pull\` не нужен"
fi
log "4. Переношу тома"
if copy_volume "$OLD_DB_VOLUME" "$NEW_DB_VOLUME"; then
log " - БД перенесена в \`$NEW_DB_VOLUME\`"
fi
if ((compose_has_caddy)); then
for i in 0 1; do
source="${OLD_CADDY_VOLUMES[$i]}"
target="${NEW_CADDY_VOLUMES[$i]}"
if copy_volume "$source" "$target"; then
log " - \`$source\` перенесён в \`$target\`"
fi
done
fi
log "5. Обновляю \`.env\`"
update_postgres_host
log "6. Запускаю новый стек"
if ((compose_has_build)); then
up_args=(up -d --build --remove-orphans)
else
up_args=(up -d --remove-orphans)
fi
run "${compose_cmd[@]}" -f "$compose_file" "${up_args[@]}"
run "${compose_cmd[@]}" -f "$compose_file" ps
log "Готово."
}
main "$@"