fix: avoid GHCR 403 in default installer

This commit is contained in:
Андрей Бобырев
2026-06-06 01:32:41 +03:00
parent 3475af1710
commit c4186415c4
5 changed files with 191 additions and 152 deletions

View File

@@ -1,6 +1,10 @@
# Скопируйте в .env и заполните значения из закрытого поста Boosty (только для подписчиков).
# Legacy GHCR mode only.
# Обычная установка больше не требует .env и GHCR PAT:
# curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh | sudo bash
#
# Этот файл нужен только если вы сознательно запускаете USE_GHCR=1.
# Образ из GHCR (пример; ваш продавец подставит точное значение)
# Образ из GHCR. Если package закрыт или PAT без read:packages, Docker вернёт 403 Forbidden.
GHCR_IMAGE=ghcr.io/andrey271192/amnezia-admin-pro
IMAGE_TAG=v1.0.0

169
README.md
View File

@@ -1,136 +1,113 @@
# Amnezia Admin Pro — установка (Docker / GHCR)
# Amnezia Web PRO — установка
Скрипты, которые снимают FREE-панель [amnezia_web](https://github.com/andrey271192/amnezia_web) и поднимают PRO-образ с GitHub Container Registry. VPN-контейнеры (**`amnezia-awg`**, **`amnezia-awg2`**) не трогаем.
Публичный установщик для **Amnezia Web PRO**. По умолчанию ставит панель из открытого GitHub repo:
## Что понадобится на VPS
- source: [`andrey271192/amnezia_web-PRO_test`](https://github.com/andrey271192/amnezia_web-PRO_test)
- текущий релиз: `v1.5.2`
- old GHCR image больше не используется по умолчанию, чтобы у пользователей не было `403 Forbidden`.
- **Docker** и работающий демон.
- **`docker compose`** v2 (не старый `docker-compose` 1.x — будет `ContainerConfig` / KeyError).
- **curl**, при первой установке — **git**.
- **Ключ** с Boosty: GitHub PAT с `read:packages` (не светите в чатах и на скринах).
## Быстрая установка
`quick-install.sh` сам подтянет Compose v2, если в apt его нет (`scripts/lib-compose-v2.sh`).
## Как выглядит PRO
После установки — та же панель, что в **amnezia_web-PRO**:
<p align="center">
<img src="docs/screenshots/panel-users-table.png" alt="Панель: пользователи AmneziaWG, инстансы, время, статусы" width="780"/>
<br/><br/>
<img src="docs/screenshots/panel-overview-password.png" alt="Панель: заголовок, форма смены пароля" width="780"/>
</p>
## Одна команда — FREE → PRO (спросит только ключ)
На VPS **от root**:
На VPS от root:
```bash
curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh | sudo bash
```
Скрипт **перед установкой PRO**:
1. **Убирает FREE-панель** из типового `install.sh` (**amnezia_web**): контейнеры `amnezia-admin`, `amnezia-web-landing`, локальные образы `amnezia-admin:latest` / `amnezia-web-landing:latest`, каталог сборки **`/opt/amnezia-admin`**.
2. **Не трогает** контейнеры VPN (**AmneziaWG / AWG**): **`amnezia-awg`**, **`amnezia-awg2`** и т.п.
3. Подставляет **`AWG_CONTAINER`** автоматически, если на сервере уже есть `amnezia-awg` или `amnezia-awg2`.
4. Клонирует/обновляет **`/opt/amnezia-web-pro-deploy`**, тянет тег из [`PRO_IMAGE_TAG`](PRO_IMAGE_TAG), запрашивает **ключ PAT** (ввод с **`/dev/tty`**).
Перед всем этим, если **`docker compose`** отсутствует, установщик **подтягивает** модуль Compose v2 (см. выше).
Отключить снос FREE (редко нужно): `SKIP_REMOVE_FREE=1 curl … | sudo -E bash`.
Если **нет интерактива** на stdin:
То же самое с явным первым паролем admin:
```bash
curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh -o /tmp/amnezia-quick-install.sh
sudo bash /tmp/amnezia-quick-install.sh
curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh \
| sudo ADMIN_PASSWORD='СЛОЖНЫЙ_ПАРОЛЬ' bash
```
**Порт:** если после удаления FREE **8080** всё ещё занят — скрипт предложит другой или задайте `HOST_PORT=8081 curl … | sudo -E bash`.
## Частые проблемы после установки
### `Bind for 0.0.0.0:8080 failed: port is already allocated`
На **8080** уже слушает другой процесс или контейнер (не только снятая FREE-панель). Посмотреть занятость: `ss -tlnp | grep ':8080'`. Обход: переустановка с **`HOST_PORT=8081`** (или свободный порт):
`quick-install.sh` скачает и запустит рабочий установщик:
```bash
HOST_PORT=8081 curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh | sudo -E bash
https://raw.githubusercontent.com/andrey271192/amnezia_web-PRO_test/main/install.sh
```
### `KeyError: 'ContainerConfig'` и в трассировке указан **`docker-compose` 1.29.x** (`/usr/lib/python3/...`)
## Почему больше не GHCR
Старый **Docker Compose v1** из пакета `docker-compose` **непонимает** образы из GHCR с современным OCI-манифестом без поля **`ContainerConfig`**. Нужна **Compose v2****`docker compose`**.
Старый сценарий тянул private image:
На актуальной ветке **`main`** установщики **не дергают legacy `docker-compose` 1.x**, а поднимают Compose v2 через APT (**если пакет виден**) либо **официальный CLIплагин с GitHub**.
```bash
sudo git -C /opt/amnezia-web-pro-deploy fetch origin main
sudo git -C /opt/amnezia-web-pro-deploy reset --hard origin/main
curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh | sudo bash
```text
ghcr.io/andrey271192/amnezia-admin-pro:v1.0.0
```
Вручную: **`docker-compose-plugin`** из [репозитория Docker CE](https://docs.docker.com/engine/install/ubuntu/) или шаг из справки в **`scripts/lib-compose-v2.sh`**.
Если у GitHub PAT нет доступа к package или package закрыт, Docker показывает:
Если после неудачного `up` образовался полубитый контейнер:
```bash
sudo docker rm -f amnezia-admin-pro 2>/dev/null || true
sudo bash /opt/amnezia-web-pro-deploy/scripts/install.sh
```text
unexpected status ... 403 Forbidden
```
Если файл **`.env` удалён или пуст**, снова запустите **`quick-install.sh`** — он заново попросит PAT.
Это не ошибка VPS и не ошибка Docker. Это отказ GitHub Container Registry. Чтобы не ловить эту ошибку у подписчиков, обычная установка теперь идёт из GitHub source.
### `Unable to locate package docker-compose-plugin` (Ubuntu без репозитория Docker)
Предупреждение Docker:
Пакет поставляет официальный репозиторий Docker CE на Ubuntu/Debian. Если его подключать не хотите или `apt-get` всё равно пишет, что пакета нет:
1. Просто снова запустите обновлённый **`quick-install.sh`** после `git pull` репозитория установки — он попытается **скачать плагин Compose v2 напрямую** с GitHub (нужны `curl` и исходящий HTTPS до `github.com`).
Либо отдельно:
```bash
sudo curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/scripts/lib-compose-v2.sh -o /tmp/lib-compose-v2.sh
sudo bash -c 'source /tmp/lib-compose-v2.sh && ensure_compose_v2'
sudo rm -f /tmp/lib-compose-v2.sh
docker compose version
```text
WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'
```
## Ручной способ (git + .env)
не является причиной ошибки. Это только предупреждение Docker.
## Legacy GHCR mode
Оставлен только для ручного использования, если у вас реально есть доступ к package:
```bash
git clone https://github.com/andrey271192/amnezia-web-pro-deploy.git
cd amnezia-web-pro-deploy
cp .env.example .env
# заполните GHCR_* из закрытого поста Boosty
sudo bash scripts/install.sh
USE_GHCR=1 curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh | sudo -E bash
```
или:
```bash
USE_GHCR=1 sudo -E bash scripts/install.sh
```
Нужны:
- GitHub PAT с `read:packages`;
- доступ к package `ghcr.io/andrey271192/amnezia-admin-pro`;
- существующий tag из `PRO_IMAGE_TAG`.
## Удаление
Обычная остановка панели:
```bash
curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia_web-PRO_test/main/uninstall.sh | sudo bash
```
Полная очистка с данными:
```bash
curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia_web-PRO_test/main/uninstall.sh \
| sudo REMOVE_DATA=1 REMOVE_DIR=1 bash
```
## Обновление
После объявления нового тега образа обновите `IMAGE_TAG` в `.env` и выполните:
В панели: **Настройки → О программе и обновления → Проверить обновления**.
Доступно:
- release channel;
- beta channel;
- выбор версии;
- rollback на старый tag.
Ручное обновление source-установки:
```bash
sudo bash scripts/update.sh
cd /opt/amnezia-web-pro
git pull
docker compose build
docker compose up -d
```
## Шаблон поста для Boosty
См. [docs/BOOSTY_SUBSCRIBERS_POST.md](docs/BOOSTY_SUBSCRIBERS_POST.md).
## Безопасность
- Файл `.env` с токеном не должен попадать в issue, чаты и скриншоты.
- При утечке токена автор перевыпускает его в GitHub; подписчикам выдаётся новый токен в обновлённом посте.
---
## Поддержка проекта
---
- **GitHub:** [andrey271192/amnezia-web-pro-deploy](https://github.com/andrey271192/amnezia-web-pro-deploy)
- 💖 **Boosty:** [boosty.to/andrey27/donate](https://boosty.to/andrey27/donate)
- 💳 **Ozon Bank (СБП):** [ссылка](https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798)
- ✉️ **Telegram:** [@lot_andrey](https://t.me/lot_andrey)
- GitHub: [andrey271192/amnezia_web-PRO_test](https://github.com/andrey271192/amnezia_web-PRO_test)
- Boosty: [boosty.to/andrey27/donate](https://boosty.to/andrey27/donate)
- Telegram: [@lot_andrey](https://t.me/lot_andrey)

View File

@@ -1,13 +1,10 @@
#!/usr/bin/env bash
# Одна команда: curl … | sudo bash — спросит только ключ (PAT для ghcr.io).
# One-command installer kept for old links. Default path now installs from the
# open source repo, not the private GHCR image, so subscribers do not hit 403.
set -euo pipefail
DEPLOY_REPO="${DEPLOY_REPO:-https://github.com/andrey271192/amnezia-web-pro-deploy.git}"
RAW_BASE="${RAW_BASE:-https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main}"
INSTALL_ROOT="${INSTALL_ROOT:-/opt/amnezia-web-pro-deploy}"
DEFAULT_GHCR_IMAGE="${DEFAULT_GHCR_IMAGE:-ghcr.io/andrey271192/amnezia-admin-pro}"
DEFAULT_GHCR_USERNAME="${DEFAULT_GHCR_USERNAME:-andrey271192}"
SOURCE_INSTALL_URL="${SOURCE_INSTALL_URL:-https://raw.githubusercontent.com/andrey271192/amnezia_web-PRO_test/main/install.sh}"
need_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
@@ -18,11 +15,38 @@ need_root() {
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "Нужна утилита: $1 (например: apt-get install -y $1)"
echo "Нужна утилита: $1"
exit 1
}
}
need_root
need_cmd curl
if [[ "${USE_GHCR:-0}" != "1" ]]; then
echo "→ GHCR-установка отключена: старый private image часто даёт 403 Forbidden."
echo "→ Ставлю Amnezia Web PRO из GitHub: ${SOURCE_INSTALL_URL}"
echo "→ Для старого GHCR-сценария запустите: USE_GHCR=1 curl ... | sudo -E bash"
echo ""
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl -fsSL "${SOURCE_INSTALL_URL}" -o "$tmp"
exec bash "$tmp"
fi
DEPLOY_REPO="${DEPLOY_REPO:-https://github.com/andrey271192/amnezia-web-pro-deploy.git}"
INSTALL_ROOT="${INSTALL_ROOT:-/opt/amnezia-web-pro-deploy}"
DEFAULT_GHCR_IMAGE="${DEFAULT_GHCR_IMAGE:-ghcr.io/andrey271192/amnezia-admin-pro}"
DEFAULT_GHCR_USERNAME="${DEFAULT_GHCR_USERNAME:-andrey271192}"
need_cmd docker
if ! docker info >/dev/null 2>&1; then
echo "Docker daemon недоступен. Запустите Docker и повторите."
exit 1
fi
trim() {
local v="$1"
v="${v//$'\r'/}"
@@ -31,15 +55,6 @@ trim() {
printf '%s' "$v"
}
need_root
need_cmd curl
need_cmd docker
if ! docker info >/dev/null 2>&1; then
echo "Docker daemon недоступен. Запустите Docker и повторите."
exit 1
fi
if ! docker compose version >/dev/null 2>&1; then
_lib="$(mktemp)"
if ! curl -fsSL "${RAW_BASE}/scripts/lib-compose-v2.sh" -o "${_lib}"; then
@@ -53,8 +68,6 @@ if ! docker compose version >/dev/null 2>&1; then
ensure_compose_v2 || exit 1
fi
# Снимаем FREE-панель (amnezia_web / install.sh): контейнеры + локальный образ + каталог сборки.
# VPN/AWG контейнеры (amnezia-awg, amnezia-awg2 и т.д.) не останавливаем.
remove_free_amnezia_web_panel() {
if [[ "${SKIP_REMOVE_FREE:-}" == "1" ]]; then
echo "→ SKIP_REMOVE_FREE=1 — FREE-панель не удаляю."
@@ -93,17 +106,12 @@ else
fi
echo ""
# При «curl … | sudo bash» stdin — это поток скрипта, а не клавиатура.
# Читаем ключ с настоящего терминала; иначе read получает EOF и ломает .env.
echo "Введите ключ доступа к образу (GitHub PAT с правом read:packages)."
echo "Ввод скрыт — символы не отображаются."
if [[ -r /dev/tty ]]; then
echo "Введите ключ доступа к образу (GitHub PAT с правом read:packages)."
echo "Ввод скрыт — символы не отображаются."
IFS= read -rs GHCR_KEY < /dev/tty || true
else
echo "Нет доступа к /dev/tty (интерактивный ввод недоступен)."
echo "Скачайте скрипт и запустите файлом:"
echo " curl -fsSL ${RAW_BASE}/quick-install.sh -o /tmp/amnezia-quick-install.sh"
echo " sudo bash /tmp/amnezia-quick-install.sh"
echo "Нет доступа к /dev/tty. Скачайте скрипт и запустите файлом."
exit 1
fi
echo ""
@@ -114,7 +122,6 @@ if [[ -z "${GHCR_KEY}" ]]; then
exit 1
fi
# Порт панели на хосте (8080 часто занят старой установкой amnezia-admin и т.п.)
tcp_port_in_use() {
local port="$1"
command -v ss >/dev/null 2>&1 || return 1
@@ -122,7 +129,6 @@ tcp_port_in_use() {
}
pick_host_port() {
# Явный HOST_PORT=… (часто с sudo -E) — не переопределяем.
if [[ -n "${HOST_PORT:-}" ]]; then
printf '%s' "${HOST_PORT}"
return
@@ -132,31 +138,18 @@ pick_host_port() {
printf '%s' "${want}"
return
fi
echo "⚠ Порт ${want} уже занят на этом сервере (часто это старая панель или другой контейнер)." >&2
local alt="8081"
if [[ -r /dev/tty ]]; then
echo "Введите свободный порт для веб-панели [${alt}]:" >&2
echo "⚠ Порт ${want} занят. Введите свободный порт [${alt}]:" >&2
IFS= read -r line < /dev/tty || true
line="$(trim "${line}")"
[[ -z "${line}" ]] && line="${alt}"
while tcp_port_in_use "${line}"; do
echo "⚠ Порт ${line} тоже занят. Укажите другой:" >&2
IFS= read -r line < /dev/tty || true
line="$(trim "${line}")"
[[ -z "${line}" ]] && line="${alt}"
while tcp_port_in_use "${line}"; do
echo "⚠ Порт ${line} тоже занят. Укажите другой:" >&2
IFS= read -r line < /dev/tty || true
line="$(trim "${line}")"
[[ -z "${line}" ]] && {
echo "Порт не указан — выход." >&2
exit 1
}
done
printf '%s' "${line}"
return
fi
echo "Нет /dev/tty — пробую ${alt}. При необходимости задайте HOST_PORT=… и повторите запуск." >&2
if tcp_port_in_use "${alt}"; then
echo "И ${alt} занят. Задайте явно: HOST_PORT=9080 curl … | sudo -E bash" >&2
exit 1
fi
printf '%s' "${alt}"
[[ -z "${line}" ]] && exit 1
done
printf '%s' "${line}"
}
pick_awg_container_name() {
@@ -179,7 +172,6 @@ SELECTED_HOST_PORT="$(pick_host_port)"
SELECTED_AWG="$(pick_awg_container_name)"
umask 077
# %q экранирует $ и прочее — безопасно для «source .env» в install.sh
{
printf 'GHCR_IMAGE=%q\n' "${DEFAULT_GHCR_IMAGE}"
printf 'IMAGE_TAG=%q\n' "${IMAGE_TAG}"
@@ -188,6 +180,9 @@ umask 077
printf 'HOST_PORT=%q\n' "${SELECTED_HOST_PORT}"
printf 'CONTAINER_NAME=%q\n' "${CONTAINER_NAME:-amnezia-admin-pro}"
printf 'AWG_CONTAINER=%q\n' "${SELECTED_AWG}"
if [[ -n "${ADMIN_PASSWORD:-}" ]]; then
printf 'ADMIN_PASSWORD=%q\n' "${ADMIN_PASSWORD}"
fi
} >"${INSTALL_ROOT}/.env"
echo "→ Запуск установки из ${INSTALL_ROOT}"

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# Установка Pro с приватного GHCR (без исходников). Публичный репозиторий — только этот скрипт и compose.
# Legacy GHCR installer. Default public install now goes through quick-install.sh
# -> amnezia_web-PRO_test/install.sh to avoid GHCR 403 for users without package access.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@@ -31,6 +32,16 @@ need_docker
ensure_compose_v2 || exit 1
COMPOSE=(docker compose)
if [[ "${USE_GHCR:-0}" != "1" ]]; then
echo "Этот сценарий использует private GHCR image и может дать 403 Forbidden."
echo "Для обычной установки используйте:"
echo " curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia-web-pro-deploy/main/quick-install.sh | sudo bash"
echo ""
echo "Если точно нужен старый GHCR-режим, запустите:"
echo " USE_GHCR=1 sudo -E bash scripts/install.sh"
exit 1
fi
if [[ ! -f "${ENV_FILE}" ]]; then
echo "Создайте файл .env из .env.example и вставьте GHCR_* из закрытого поста Boosty:"
echo " cp .env.example .env && nano .env"
@@ -54,6 +65,7 @@ GHCR_IMAGE="$(trim_crlf "${GHCR_IMAGE:-}")"
IMAGE_TAG="$(trim_crlf "${IMAGE_TAG:-}")"
GHCR_USERNAME="$(trim_crlf "${GHCR_USERNAME:-}")"
GHCR_TOKEN="$(trim_crlf "${GHCR_TOKEN:-}")"
ADMIN_PASSWORD="$(trim_crlf "${ADMIN_PASSWORD:-}")"
: "${GHCR_IMAGE:?Укажите GHCR_IMAGE в .env}"
: "${IMAGE_TAG:?Укажите IMAGE_TAG в .env}"
@@ -66,8 +78,26 @@ printf '%s\n' "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USERNAME}" --pas
echo "→ pull ${GHCR_IMAGE}:${IMAGE_TAG}"
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" pull
RESET_PANEL_PASSWORD=0
if [[ -n "${ADMIN_PASSWORD}" ]]; then
echo "→ ADMIN_PASSWORD задан — сбрасываю старый пароль панели в volume."
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" run --rm --no-deps --entrypoint sh panel -lc '
set -eu
if [ -f /data/password.hash ]; then
cp -p /data/password.hash "/data/password.hash.bak.$(date -u +%Y%m%dT%H%M%SZ)" 2>/dev/null || true
rm -f /data/password.hash
rm -f /data/session.secret
fi
'
RESET_PANEL_PASSWORD=1
fi
echo "→ up -d"
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" up -d
UP_ARGS=(up -d)
if [[ "${RESET_PANEL_PASSWORD}" == "1" ]]; then
UP_ARGS+=(--force-recreate)
fi
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" "${UP_ARGS[@]}"
IP="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
echo ""

View File

@@ -14,6 +14,19 @@ fi
ensure_compose_v2 || exit 1
COMPOSE=(docker compose)
if [[ "${USE_GHCR:-0}" != "1" ]]; then
echo "Этот update.sh относится к legacy GHCR-режиму и может дать 403 Forbidden."
echo "Для source-установки обновляйтесь из панели или вручную:"
echo " cd /opt/amnezia-web-pro"
echo " git pull"
echo " docker compose build"
echo " docker compose up -d"
echo ""
echo "Если точно нужен старый GHCR-режим, запустите:"
echo " USE_GHCR=1 sudo -E bash scripts/update.sh"
exit 1
fi
set -a
# shellcheck disable=SC1090
source "${ENV_FILE}"
@@ -28,9 +41,29 @@ trim_crlf() {
}
GHCR_USERNAME="$(trim_crlf "${GHCR_USERNAME:-}")"
GHCR_TOKEN="$(trim_crlf "${GHCR_TOKEN:-}")"
ADMIN_PASSWORD="$(trim_crlf "${ADMIN_PASSWORD:-}")"
printf '%s\n' "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" pull
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" up -d
RESET_PANEL_PASSWORD=0
if [[ -n "${ADMIN_PASSWORD}" ]]; then
echo "→ ADMIN_PASSWORD задан — сбрасываю старый пароль панели в volume."
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" run --rm --no-deps --entrypoint sh panel -lc '
set -eu
if [ -f /data/password.hash ]; then
cp -p /data/password.hash "/data/password.hash.bak.$(date -u +%Y%m%dT%H%M%SZ)" 2>/dev/null || true
rm -f /data/password.hash
rm -f /data/session.secret
fi
'
RESET_PANEL_PASSWORD=1
fi
UP_ARGS=(up -d)
if [[ "${RESET_PANEL_PASSWORD}" == "1" ]]; then
UP_ARGS+=(--force-recreate)
fi
"${COMPOSE[@]}" -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" "${UP_ARGS[@]}"
echo "Обновлено."