Кнопка «Помахать» с общим счётчиком, домен, шапка
deploy / deploy (push) Successful in 4s

- Главная: тихая кнопка «Помахать» (web/js/wave.js), общий счётчик для всех посетителей
- server/server.mjs: мини-API (/api/waves, /api/wave), счётчик хранится вне репозитория
  (/var/lib/vizitka-site/waves.json), лимит раз в секунду с IP, проверка Origin
- deploy: systemd-сервис vizitka-api, проксирование /api в nginx, setup-server.sh ставит nodejs
- CI: после git pull перезапускает API
- Домен https://me.ibrash.duckdns.org вместо заглушки, исправлен пробел в шапке
- README и заметки обновлены

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 16:55:30 +05:00
co-authored by Claude Sonnet 5.5
parent 8de0a05848
commit 6ddece7017
23 changed files with 332 additions and 52 deletions
+20 -9
View File
@@ -5,7 +5,7 @@
- Репозиторий: `git@git.ibrash.duckdns.org:aibrash/vizitka-site.git` (Gitea 1.27), ветка `main`
- Сервер: `root@192.168.10.112` (Debian 12, имя хоста `visitka-site`): клон репозитория в `/opt/vizitka-site`, nginx отдаёт его папку `web/`, пока по HTTP внутри сети
- Публичного домена пока нет: в OG-тегах и `sitemap.xml` стоит заглушка `https://example.com` (как заменить: раздел 5)
- Домен: `https://me.ibrash.duckdns.org` (HTTPS терминирует отдельный прокси на openresty перед сервером). Он прописан в OG-тегах, `sitemap.xml` и `robots.txt`; как сменить: раздел 5
---
@@ -71,6 +71,7 @@ web/ то, что уходит на сервер (nginx root)
js/card.js заполняет главную из config.json
js/nav.js верхнее меню из config.pages (скрывает draft), подсветка текущего раздела
js/count.js числа «набегают» при появлении
js/wave.js кнопка «Помахать» со счётчиком на главной (говорит с /api)
arch/ список проектов + arch/<проект>/ (три страницы)
projects/ список + projects/amans-world/ + projects/homelab/
training/ work/ fixed/ ЧЕРНОВИКИ будущих страниц (draft, noindex), в меню не видны
@@ -79,8 +80,9 @@ web/ то, что уходит на сервер (nginx root)
fonts/ woff2 Onest и лицензия
404.html, favicon.svg, robots.txt, sitemap.xml
templates/page.html шаблон новой страницы
server/ server.mjs: мини-API счётчика «Помахать» (Node, без зависимостей)
tools/ gen_arch.py, gen_projects.py: генераторы страниц архитектуры и проектов
deploy/ nginx-site.conf, setup-server.sh (разовая настройка сервера)
deploy/ nginx-site.conf, vizitka-api.service, setup-server.sh (разовая настройка сервера)
.gitea/workflows/deploy.yml CI
```
@@ -88,6 +90,15 @@ deploy/ nginx-site.conf, setup-server.sh (разовая наст
Содержимое берётся из `web/config.json` (имя, `photo`, `status`, `tagline`, `facts`, `tags`, `cta`, `email`, `socials`). Если `photo` пустое, показываются инициалы. До загрузки конфига страница скрыта (`html.js:not(.ready) body[data-hide] .page`), чтобы вёрстка не прыгала; без JS всё видно сразу.
Хоумлаб на главной не упоминается **намеренно** (так просил владелец).
### Кнопка «Помахать» (интерактив на главной)
Тихая «призрачная» кнопка внизу карточки «Давай общаться»: прозрачная, тонкая рамка, цвет `--muted`, не конкурирует с ссылками. При клике ладонь машет (`@keyframes wave`, как в приветствии), число плавно докручивается, всплывает маленькое «+1». Счётчик общий для всех посетителей.
- Фронтенд: `web/js/wave.js` и блок `.wave-row` в `index.html`. Если API недоступно (например, при просмотре через `python3 -m http.server`), блок прячется (`visibility:hidden`, место зарезервировано, чтобы вёрстка не прыгала).
- Бэкенд: `server/server.mjs`, `GET /api/waves` и `POST /api/wave`. Лимит: не чаще раза в секунду с одного IP и не больше 300 в минуту на всех; POST принимается только с разрешённых сайтов (`ALLOWED_ORIGINS`); тело запроса игнорируется, от клиента данные не берутся.
- **Счётчик не сбрасывается:** хранится в `/var/lib/vizitka-site/waves.json` (создаётся systemd, вне репозитория), пишется атомарно, перед каждой записью предыдущее значение копируется в `waves.json.bak`, при остановке сервиса значение сохраняется. `git pull`, пуши и перезапуски сервера его не трогают. Не клади файл счётчика внутрь репозитория.
- Сервис `vizitka-api` (systemd, `DynamicUser`, песочница, `MemoryMax=96M`), настройки в `/etc/vizitka-api.env` (создаются один раз и потом не перезаписываются), nginx проксирует `/api/` на `127.0.0.1:8787` и передаёт IP посетителя (`X-Client-IP`).
- Локальная проверка: `DATA_FILE=/tmp/waves.json STATIC_DIR=web PORT=8791 node server/server.mjs` и открыть `http://localhost:8791`.
- Править текст кнопки и числа: прямо в `web/index.html` и `web/js/wave.js`.
### Меню и страницы
Меню рисует `js/nav.js` из `config.json` → `pages`. На главной оно появляется, только если есть опубликованные страницы. Внутренние страницы всегда показывают имя и пункты. На узких экранах фамилия в шапке прячется (чтобы влезли все пункты).
@@ -142,15 +153,15 @@ magick src.png -strip -resize 640x640\> -quality 74 имя-sm.webp
## 5. Деплой и CI
### Схема (простая)
`push в main` → Gitea Actions (`.gitea/workflows/deploy.yml`) заходит по ssh на `root@192.168.10.112` и делает `git pull` в `/opt/vizitka-site` → nginx раздаёт `/opt/vizitka-site/web`, перезапуск не нужен. Сборки нет: что лежит в `web/`, то и на сайте.
`push в main` → Gitea Actions (`.gitea/workflows/deploy.yml`) заходит по ssh на `root@192.168.10.112`, делает `git pull` в `/opt/vizitka-site` и перезапускает API счётчика (`systemctl restart vizitka-api`) → nginx раздаёт `/opt/vizitka-site/web`. Для статики перезапуск не нужен; сборки нет: что лежит в `web/`, то и на сайте.
Репозиторий публичный, поэтому сервер клонирует и обновляет его по HTTPS без ключей и паролей: `https://git.ibrash.duckdns.org/aibrash/vizitka-site.git`. Если репозиторий станет закрытым, на сервер придётся добавить deploy-ключ (в Gitea: репозиторий → Настройки → Deploy keys) или токен.
### Что настроено на сервере (сделано, `deploy/setup-server.sh`)
```bash
./deploy/setup-server.sh root@192.168.10.112 # ставит nginx и git, клонирует репозиторий в /opt/vizitka-site, подключает deploy/nginx-site.conf
./deploy/setup-server.sh root@192.168.10.112 # ставит nginx, git и nodejs, клонирует репозиторий в /opt/vizitka-site, включает сервис vizitka-api и подключает deploy/nginx-site.conf
```
Скрипт безопасно запускать повторно: если клон уже есть, он делает `git pull`, и заново применяет конфиг nginx (там `root /opt/vizitka-site/web`, сжатие, кэш, 404, строгий CSP, скрытые файлы закрыты).
Скрипт безопасно запускать повторно: если клон уже есть, он делает `git pull`, обновляет systemd-юнит и заново применяет конфиг nginx (`/etc/vizitka-api.env` не перезаписывается, счётчик не трогается) (там `root /opt/vizitka-site/web`, сжатие, кэш, 404, строгий CSP, скрытые файлы закрыты).
Если нужно поменять конфиг nginx: правь `deploy/nginx-site.conf` и снова запусти скрипт. Если `/opt/vizitka-site` уже был не репозиторием, скрипт откладывает его в `/opt/vizitka-site.old-<время>`, а не удаляет (одна такая папка там сейчас лежит, это старая выкладка через rsync, её можно удалить).
### Что нужно для работы CI
@@ -166,11 +177,11 @@ ssh root@192.168.10.112 'cd /opt/vizitka-site && git pull --ff-only'
### HTTPS и домен (позже)
Сейчас сервер отдаёт HTTP только внутри сети. Для публичного доступа лучше поставить перед ним обратный прокси (Caddy или nginx на отдельном хосте с сертификатом) или Cloudflare Tunnel, а сам сервер с сайтом не выставлять наружу напрямую рядом с остальными домашними устройствами.
Заглушка `https://example.com` лежит в OG-тегах всех `.html`, в `web/sitemap.xml` и `web/robots.txt`. Раньше домен подставлял скрипт выкладки, теперь выкладки-скрипта нет, поэтому когда выберешь домен, замени один раз и закоммить:
Домен `me.ibrash.duckdns.org` уже подставлен в OG-теги, `sitemap.xml`, `robots.txt` и в `tools/`. Если домен сменится, замени один раз и закоммить (и поправь `ORIGINS`/`ALLOWED_ORIGINS` для API: `/etc/vizitka-api.env`, потом `systemctl restart vizitka-api`):
```bash
grep -rlF 'https://example.com' web tools | xargs sed -i 's|https://example.com|https://ТВОЙ.ДОМЕН|g'
grep -rlF 'https://me.ibrash.duckdns.org' web tools | xargs sed -i 's|https://me.ibrash.duckdns.org|https://НОВЫЙ.ДОМЕН|g'
```
(в `tools/` она тоже есть, чтобы генераторы не вернули заглушку).
Реальный IP посетителя nginx берёт из `X-Forwarded-For` (`set_real_ip_from` для частных сетей в `deploy/nginx-site.conf`): если передний прокси поменяется и не будет присылать этот заголовок, лимит «раз в секунду» станет общим на всех, проверь по `access.log`.
---
@@ -205,7 +216,7 @@ python3 -m http.server 8787 --directory web # локальный просм
**Что осталось сделать (на момент написания).**
1. Убедиться, что в Gitea есть runner и CI срабатывает на пуш (секрет `DEPLOY_SSH_KEY` уже добавлен). Удалить с сервера `/opt/vizitka-site.old-*`.
2. Выбрать домен, включить HTTPS через прокси или туннель, заменить заглушку `example.com` (раздел 5).
2. Проверить, что кнопка «Помахать» в проде получает реальные IP посетителей (смотреть `/var/log/nginx/access.log`) и что счётчик переживает перезапуск (`systemctl restart vizitka-api`).
3. Реальные ники (в `config.json` у Telegram и Instagram стоят короткие ники и их handle могут быть временными), проверка текстов про игру и про хоумлаб.
4. Страницы Тренировки, Работа, Фикс (черновики уже лежат в `web/`).
5. Фото сервера для хоумлаба вместо иллюстрации, больше кадров игры (Backrooms, песочница, паркур).