diff --git a/README.md b/README.md
index ca52255..1cdb8df 100644
--- a/README.md
+++ b/README.md
@@ -39,8 +39,11 @@ cd /opt/amnezia-admin && chmod +x scripts/install.sh && sudo SKIP_DOWNLOAD=1 bas
| `ADMIN_PASSWORD` | _(генерируется)_ | Первый пароль вместо файла |
| `SKIP_DOWNLOAD` | `0` | `1` — не качать GitHub, собрать из `INSTALL_DIR` |
| `ALLOW_DEFAULT_PASSWORD` | `0` | `1` — см. раздел «Пароль» ниже |
+| `SKIP_LANDING` | `0` | `1` — не ставить публичную страницу с футером поддержки |
+| `LANDING_PORT` | `80` | Порт nginx-лендинга (если `80` занят — например `8081`) |
+| `LANDING_CONTAINER` | `amnezia-web-landing` | Имя контейнера лендинга |
-После установки откройте `http://IP_СЕРВЕРА:8080`.
+После установки: **админ-панель** `http://IP:8080` (или ваш `HOST_PORT`), **страница с поддержкой проекта** `http://IP/` на порту лендинга (по умолчанию **80**). Кнопка на лендинге ведёт на админку с тем же `HOST_PORT`.
---
@@ -111,20 +114,22 @@ ADMIN_PASSWORD=localtest node server.js
## Support links
-Те же способы поддержки и контакты, что в проекте [domen_hydra](https://github.com/andrey271192/domen_hydra):
+Как на главной странице установки (**лендинг на порту 80**): **Kaskad WebUI** · GitHub · Boosty · Ozon СБП · Telegram · текст поддержки.
-Если проект оказался полезным — буду рад поддержке. Это помогает развивать и поддерживать его дальше.
+Поддержать проект — поставь звезду на GitHub **И донат**. Связаться с автором — Telegram.
| Способ | Ссылка |
|--------|--------|
| GitHub | [Amnezia_web](https://github.com/andrey271192/Amnezia_web) |
| Boosty | [boosty.to/andrey27/donate](https://boosty.to/andrey27/donate) |
-| Ozon СБП | [ссылка оплаты Ozon](https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798) |
-| Telegram | [@Iot_andrey](https://t.me/Iot_andrey) — вопросы, идеи, обратная связь |
+| Ozon СБП | [Ozon СБП](https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798) |
+| Telegram | [@lot_andrey](https://t.me/lot_andrey) |
-Файл [`.github/FUNDING.yml`](.github/FUNDING.yml) задаёт кнопку **Sponsor** на GitHub (как в [domen_hydra](https://github.com/andrey271192/domen_hydra)).
+Ссылки совпадают с блоком в репозитории [domen_hydra](https://github.com/andrey271192/domen_hydra) (Boosty / Ozon), отображаются на **публичной странице** после установки и в футере админ-панели.
-Тот же блок поддержки продублирован в **футере веб-интерфейса**.
+Файл [`.github/FUNDING.yml`](.github/FUNDING.yml) задаёт кнопку **Sponsor** на GitHub.
+
+Тот же блок поддержки продублирован в **футере веб-интерфейса** и на **`http://сервер:80/`** (если лендинг не отключён).
---
diff --git a/landing/Dockerfile b/landing/Dockerfile
new file mode 100644
index 0000000..313f0cc
--- /dev/null
+++ b/landing/Dockerfile
@@ -0,0 +1,3 @@
+FROM nginx:1.27-alpine
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+COPY index.html styles.css admin-port.js /usr/share/nginx/html/
diff --git a/landing/admin-port.js b/landing/admin-port.js
new file mode 100644
index 0000000..52d4a1b
--- /dev/null
+++ b/landing/admin-port.js
@@ -0,0 +1 @@
+window.__AMNEZIA_ADMIN_PORT__ = "8080";
diff --git a/landing/index.html b/landing/index.html
new file mode 100644
index 0000000..2a39a14
--- /dev/null
+++ b/landing/index.html
@@ -0,0 +1,59 @@
+
+
+
+
+ Этот сервер
+ Управление AmneziaWG
+
+ Здесь установлена админ-панель для клиентов VPN. Перейдите по ссылке ниже (порт задаётся при установке, по умолчанию 8080).
+
+
+
+
+
+
+
+
+
+
diff --git a/landing/nginx.conf b/landing/nginx.conf
new file mode 100644
index 0000000..7fdc4bd
--- /dev/null
+++ b/landing/nginx.conf
@@ -0,0 +1,20 @@
+server {
+ listen 80;
+ server_name _;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ gzip on;
+ gzip_types text/css text/plain application/json application/javascript;
+
+ add_header X-Content-Type-Options nosniff always;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location ~* \.css$ {
+ expires 1h;
+ add_header Cache-Control "public, max-age=3600";
+ }
+}
diff --git a/landing/styles.css b/landing/styles.css
new file mode 100644
index 0000000..2ab7652
--- /dev/null
+++ b/landing/styles.css
@@ -0,0 +1,124 @@
+:root {
+ --bg: #070b10;
+ --card: #111722;
+ --line: rgba(255, 255, 255, 0.08);
+ --text: #f4f7ff;
+ --muted: #94a3b8;
+ --accent: #7dd3fc;
+ font-family: "DM Sans", system-ui, sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body.page {
+ margin: 0;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background: radial-gradient(circle at 20% 20%, rgba(125, 211, 252, 0.08), transparent 35%),
+ radial-gradient(circle at 80% 0%, rgba(94, 234, 212, 0.06), transparent 40%),
+ var(--bg);
+ color: var(--text);
+}
+
+.landing-main {
+ flex: 1;
+ max-width: 560px;
+ margin: 0 auto;
+ padding: clamp(2rem, 6vw, 4rem) 1.25rem;
+}
+
+.eyebrow {
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ font-size: 0.72rem;
+ color: var(--accent);
+ margin: 0 0 0.5rem;
+}
+
+.landing-main h1 {
+ margin: 0 0 1rem;
+ font-size: clamp(1.5rem, 4vw, 1.85rem);
+ font-weight: 700;
+}
+
+.lead {
+ margin: 0 0 1.25rem;
+ line-height: 1.6;
+ color: var(--muted);
+}
+
+.admin-row {
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.5rem;
+}
+
+.admin-btn {
+ display: inline-block;
+ padding: 0.55rem 1rem;
+ border-radius: 11px;
+ font-weight: 600;
+ text-decoration: none;
+ background: linear-gradient(135deg, #38bdf8, #22d3ee);
+ color: #041018;
+}
+
+.admin-btn:hover {
+ filter: brightness(1.05);
+}
+
+.muted-inline {
+ font-size: 0.82rem;
+ color: var(--muted);
+ word-break: break-all;
+}
+
+.support-footer {
+ flex-shrink: 0;
+ margin-top: auto;
+ padding: 1.25rem clamp(1rem, 3vw, 1.5rem) 1.5rem;
+ border-top: 1px solid var(--line);
+ background: rgba(10, 15, 22, 0.92);
+}
+
+.support-line {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.35rem 0.5rem;
+ font-size: 0.92rem;
+ color: var(--muted);
+}
+
+.support-title {
+ color: var(--text);
+ font-weight: 700;
+}
+
+.support-sep {
+ opacity: 0.55;
+ user-select: none;
+}
+
+.support-line a {
+ color: var(--accent);
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+.support-line a:hover {
+ text-decoration: underline;
+}
+
+.support-blurb {
+ margin: 0.65rem 0 0;
+ font-size: 0.82rem;
+ line-height: 1.45;
+ color: var(--muted);
+ max-width: 62rem;
+}
diff --git a/public/index.html b/public/index.html
index 0b19ee2..6022cc6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -87,7 +87,7 @@
diff --git a/scripts/install.sh b/scripts/install.sh
index 6371718..8caed67 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -8,6 +8,9 @@ INSTALL_DIR="${INSTALL_DIR:-/opt/amnezia-admin}"
DATA_DIR="${DATA_DIR:-/opt/amnezia-admin-data}"
CONTAINER_NAME="${CONTAINER_NAME:-amnezia-admin}"
HOST_PORT="${HOST_PORT:-8080}"
+LANDING_CONTAINER="${LANDING_CONTAINER:-amnezia-web-landing}"
+LANDING_IMAGE="${LANDING_IMAGE:-amnezia-web-landing:latest}"
+LANDING_PORT="${LANDING_PORT:-80}"
need_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
@@ -81,6 +84,8 @@ elif [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "1" ]] || [[ "${ALLOW_DEFAULT_PASSWORD:
RUN_ENV+=( -e "ALLOW_DEFAULT_PASSWORD=1" )
fi
+IP="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
+
docker run -d --name "${CONTAINER_NAME}" --restart unless-stopped \
-p "${HOST_PORT}:3980" \
-v /var/run/docker.sock:/var/run/docker.sock \
@@ -88,10 +93,28 @@ docker run -d --name "${CONTAINER_NAME}" --restart unless-stopped \
"${RUN_ENV[@]}" \
amnezia-admin:latest
-IP="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
+if [[ "${SKIP_LANDING:-}" != "1" ]] && [[ -d "${INSTALL_DIR}/landing" ]]; then
+ printf "window.__AMNEZIA_ADMIN_PORT__='%s';\n" "${HOST_PORT}" >"${INSTALL_DIR}/landing/admin-port.js"
+ echo "→ Сборка образа ${LANDING_IMAGE} (страница на порту ${LANDING_PORT})..."
+ docker build -t "${LANDING_IMAGE}" "${INSTALL_DIR}/landing"
+ docker rm -f "${LANDING_CONTAINER}" 2>/dev/null || true
+ if docker run -d --name "${LANDING_CONTAINER}" --restart unless-stopped \
+ -p "${LANDING_PORT}:80" \
+ "${LANDING_IMAGE}"; then
+ echo "→ Лендинг с блоком поддержки: http://${IP:-SERVER_IP}:${LANDING_PORT}/"
+ else
+ echo "⚠ Не удалось запустить лендинг (часто порт ${LANDING_PORT} занят). Поставьте LANDING_PORT=8081 или SKIP_LANDING=1."
+ fi
+else
+ echo "→ Лендинг пропущен (SKIP_LANDING=1 или нет каталога landing)."
+fi
+
echo ""
echo "=== Готово ==="
-echo "Откройте в браузере: http://${IP:-SERVER_IP}:${HOST_PORT}"
+echo "Админ-панель: http://${IP:-SERVER_IP}:${HOST_PORT}"
+if [[ "${SKIP_LANDING:-}" != "1" ]]; then
+ echo "Страница с поддержкой проекта: http://${IP:-SERVER_IP}:${LANDING_PORT}/"
+fi
if [[ -f "${PASS_FILE}" ]]; then
echo "Первый пароль: $(cat "${PASS_FILE}")"
fi
diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh
index 49f6224..876b8b5 100755
--- a/scripts/uninstall.sh
+++ b/scripts/uninstall.sh
@@ -16,9 +16,17 @@ need_root() {
need_root
+echo "→ Останавливаю лендинг ${LANDING_CONTAINER:-amnezia-web-landing}..."
+docker rm -f "${LANDING_CONTAINER:-amnezia-web-landing}" 2>/dev/null || echo "(лендинг уже отсутствует)"
+
echo "→ Останавливаю контейнер ${CONTAINER_NAME}..."
docker rm -f "${CONTAINER_NAME}" 2>/dev/null || echo "(контейнер уже отсутствует)"
+if [[ "${REMOVE_LANDING_IMAGE:-}" == "1" ]]; then
+ echo "→ Удаляю образ ${LANDING_IMAGE:-amnezia-web-landing:latest}..."
+ docker rmi "${LANDING_IMAGE:-amnezia-web-landing:latest}" 2>/dev/null || true
+fi
+
if [[ "${REMOVE_IMAGE:-}" == "1" ]]; then
echo "→ Удаляю образ ${IMAGE_NAME}..."
docker rmi "${IMAGE_NAME}" 2>/dev/null || true
@@ -35,4 +43,4 @@ if [[ "${REMOVE_SRC:-}" == "1" ]]; then
fi
echo "Готово."
-echo "Подсказка: REMOVE_DATA=1 REMOVE_SRC=1 REMOVE_IMAGE=1 curl ... | sudo bash — полная очистка."
+echo "Подсказка: REMOVE_LANDING_IMAGE=1 — удалить образ лендинга; REMOVE_DATA=1 REMOVE_SRC=1 REMOVE_IMAGE=1 curl ... | sudo bash — полная очистка."