docs: project name change, migration script
This commit is contained in:
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user