diff --git a/CLAUDE.md b/CLAUDE.md index 9ca7a54..656bc9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,9 +18,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 1. **Только через CI/CD** — никогда не фикси что-либо вручную на сервере. Внеси изменение, закоммить, запуши → CI/CD сам задеплоит. Потом проверь результат. 2. **Syncthing удалён навсегда** — не упоминать, не предлагать вернуть. -3. **Секреты** — все в `inventory/group_vars/all/vault.yml` с префиксом `vault_`. В `main.yml` только алиасы `"{{ vault_* }}"`. Никаких plaintext секретов. -4. **Синтаксис-чек перед коммитом** — `ansible-playbook playbooks/deploy.yml --syntax-check`. -5. **Обновляй доки** — после каждого изменения обновляй `docs/STATUS.md`, `docs/BACKLOG.md` или `docs/DECISIONS.md`. +3. **Vaultwarden удалён навсегда** — не упоминать, не предлагать вернуть. +4. **Tools-сервер удалён навсегда** — один сервер `main`. Мониторинг через UptimeRobot (внешний). +5. **Секреты** — все в `inventory/group_vars/all/vault.yml` с префиксом `vault_`. В `main.yml` только алиасы `"{{ vault_* }}"`. Никаких plaintext секретов. +6. **Синтаксис-чек перед коммитом** — `ansible-playbook playbooks/deploy.yml --syntax-check`. +7. **Обновляй доки** — после каждого изменения обновляй `docs/STATUS.md`, `docs/BACKLOG.md` или `docs/DECISIONS.md`. --- @@ -34,21 +36,14 @@ echo "yourpassword" > ~/.vault-password-file && chmod 600 ~/.vault-password-file # Первичная настройка сервера (от root) ansible-playbook playbooks/bootstrap.yml -u root -# Деплой основного сервера +# Деплой ansible-playbook playbooks/deploy.yml -# Деплой tools-сервера -ansible-playbook playbooks/tools.yml - -# Деплой всего сразу -ansible-playbook playbooks/site.yml - # Редактировать секреты ansible-vault edit inventory/group_vars/all/vault.yml # Проверка синтаксиса (без подключения к серверу) ansible-playbook playbooks/deploy.yml --syntax-check -ansible-playbook playbooks/tools.yml --syntax-check # Dry run ansible-playbook playbooks/deploy.yml --check @@ -65,8 +60,7 @@ ansible-playbook playbooks/deploy.yml --tags backup ## Архитектура **Серверы:** -- `main` (87.249.49.32) — Traefik, Forgejo, Plane, Vaultwarden, Мониторинг, CI/CD runner -- `tools` (85.193.83.9) — Outline wiki, n8n, docker-mailserver, SnappyMail +- `main` (87.249.49.32) — единственный сервер: Traefik, Forgejo, Plane, Docmost, n8n, CI/CD runner **Трафик:** Internet → Cloudflare proxy → Traefik (80/443) → сервисы. Порты 80/443 открыты только для IP-адресов Cloudflare (UFW whitelist). @@ -74,14 +68,13 @@ ansible-playbook playbooks/deploy.yml --tags backup **Секреты:** `inventory/group_vars/all/vault.yml` (Ansible Vault AES-256). Пароль vault: `~/.vault-password-file` (в `.gitignore`, никогда не коммитить). -**TLS:** Wildcard сертификат `*.csrx.ru` через Cloudflare DNS-01 (Traefik certresolver). +**TLS:** Wildcard сертификат `*.walava.io` через Cloudflare DNS-01 (Traefik certresolver). **Роли:** - `base` — UFW, fail2ban, sshd, deploy user - `docker` — Docker CE + Compose plugin - `services` — основной стек (templates → `/opt/services/`, docker compose up) -- `tools` — tools-стек (templates → `/opt/tools/`, docker compose up) -- `backup` — backup каждые 6 часов → S3 (`visual-backup/data/`), только для main-сервера +- `backup` — backup каждые 6 часов → S3 (`walava-backup/`), только для main-сервера **Шаблоны → файлы на сервере:** - `roles/services/templates/docker-compose.yml.j2` → `/opt/services/docker-compose.yml` @@ -89,16 +82,11 @@ ansible-playbook playbooks/deploy.yml --tags backup - `roles/services/templates/traefik/traefik.yml.j2` → `/opt/services/traefik/traefik.yml` - `roles/services/templates/traefik/dynamic/routes.yml.j2` → `/opt/services/traefik/dynamic/routes.yml` -**Добавление нового сервиса на main:** +**Добавление нового сервиса:** 1. Добавить контейнер в `docker-compose.yml.j2` в сеть `backend` 2. Добавить роутер в `routes.yml.j2` (НЕ docker labels — используется file provider) 3. Добавить домен в `inventory/group_vars/all/main.yml` -4. Добавить DNS A-запись в Cloudflare - -**Добавление сервиса на tools:** -1. Добавить контейнер в `roles/tools/templates/docker-compose.yml.j2` -2. Добавить роутер в `routes.yml.j2` с `url: "http://{{ ip_tools }}:PORT"` -3. Открыть порт в UFW (задача в `roles/tools/tasks/main.yml`) +4. Добавить DNS A-запись в Cloudflare (через API или вручную) --- @@ -107,6 +95,18 @@ ansible-playbook playbooks/deploy.yml --tags backup Push в `master` → Forgejo Actions запускает: ``` ansible-playbook playbooks/deploy.yml -ansible-playbook playbooks/tools.yml ``` Workflow: `.forgejo/workflows/deploy.yml` + +--- + +## Домены (все на walava.io) + +| Сервис | Домен | +|--------|-------| +| Forgejo | git.walava.io | +| Plane | hub.walava.io | +| Docmost | wiki.walava.io | +| n8n | auto.walava.io | +| Traefik dashboard | traefik.walava.io | +| Landing | walava.io | diff --git a/roles/services/tasks/main.yml b/roles/services/tasks/main.yml index 38ff159..0b23304 100644 --- a/roles/services/tasks/main.yml +++ b/roles/services/tasks/main.yml @@ -39,40 +39,6 @@ until: compose_result is succeeded notify: Stack deployed -- name: Wait for MinIO to be ready - ansible.builtin.command: docker exec plane-minio curl -sf http://localhost:9000/minio/health/live - register: minio_ready - changed_when: false - retries: 15 - delay: 10 - until: minio_ready.rc == 0 - -- name: Get plane-internal network name - ansible.builtin.shell: > - docker inspect plane-minio | - python3 -c "import sys,json; d=json.load(sys.stdin)[0]; - print([k for k in d['NetworkSettings']['Networks'] if 'plane-internal' in k][0])" - register: plane_internal_network - changed_when: false - -- name: Create MinIO uploads bucket via mc container - # minio/mc entrypoint = mc, поэтому нужен --entrypoint sh - # access-key = имя пользователя MinIO (plane-minio), secret-key = пароль - ansible.builtin.shell: | - docker run --rm \ - --entrypoint sh \ - --network "{{ plane_internal_network.stdout | trim }}" \ - -e MC_ACCESS="{{ plane_minio_password }}" \ - minio/mc:RELEASE.2025-05-21T01-59-54Z \ - -c 'mc alias set local http://plane-minio:9000 plane-minio "{{ plane_minio_password }}" 2>/dev/null \ - && mc mb --ignore-existing local/uploads \ - && echo "Bucket created or already exists"' - register: minio_bucket - changed_when: "'Bucket created' in minio_bucket.stdout" - retries: 5 - delay: 10 - until: minio_bucket.rc == 0 - # ── Forgejo Discord webhooks (deploys → #deploys channel) ──────────────────── - name: Check Discord webhooks on Forgejo repos ansible.builtin.uri: diff --git a/roles/services/templates/env.outline.j2 b/roles/services/templates/env.outline.j2 deleted file mode 100644 index 7fe9599..0000000 --- a/roles/services/templates/env.outline.j2 +++ /dev/null @@ -1,41 +0,0 @@ -# Outline env — generated by Ansible -NODE_ENV=production -SECRET_KEY={{ outline_secret_key }} -UTILS_SECRET={{ outline_utils_secret }} - -# Database -DATABASE_URL=postgres://outline:{{ outline_db_password }}@outline-db:5432/outline -PGSSLMODE=disable - -# Redis -REDIS_URL=redis://outline-redis:6379 - -# App URL -URL=https://{{ domain_wiki }} -PORT=3000 - -# S3 file storage (Timeweb Object Storage) -AWS_ACCESS_KEY_ID={{ s3_access_key }} -AWS_SECRET_ACCESS_KEY={{ s3_secret_key }} -AWS_REGION=ru-1 -AWS_S3_UPLOAD_BUCKET_NAME=walava-outline -AWS_S3_UPLOAD_BUCKET_URL=https://s3.twcstorage.ru -AWS_S3_FORCE_PATH_STYLE=true -FILE_STORAGE=s3 - -# Auth -AUTH_PROVIDERS=email - -# SMTP via Resend (direct — main server has outbound SMTP) -SMTP_HOST=smtp.resend.com -SMTP_PORT=587 -SMTP_USERNAME=resend -SMTP_PASSWORD={{ resend_api_key }} -SMTP_FROM_EMAIL=noreply@{{ domain_base }} -SMTP_FROM_NAME=Visual Wiki -SMTP_SECURE=false - -# Optional -DEFAULT_LANGUAGE=en_US -RATE_LIMITER_ENABLED=true -ENABLE_UPDATES=false diff --git a/roles/services/templates/grafana/provisioning/dashboards/dashboards.yml.j2 b/roles/services/templates/grafana/provisioning/dashboards/dashboards.yml.j2 deleted file mode 100644 index fc7264f..0000000 --- a/roles/services/templates/grafana/provisioning/dashboards/dashboards.yml.j2 +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Ansible — do not edit manually -apiVersion: 1 - -providers: - - name: default - orgId: 1 - folder: "" - type: file - disableDeletion: false - updateIntervalSeconds: 30 - allowUiUpdates: false - options: - path: /etc/grafana/provisioning/dashboards/json diff --git a/roles/services/templates/grafana/provisioning/datasources/loki.yml.j2 b/roles/services/templates/grafana/provisioning/datasources/loki.yml.j2 deleted file mode 100644 index 4de25a0..0000000 --- a/roles/services/templates/grafana/provisioning/datasources/loki.yml.j2 +++ /dev/null @@ -1,10 +0,0 @@ -# Generated by Ansible — do not edit manually -apiVersion: 1 - -datasources: - - name: Loki - type: loki - access: proxy - url: http://loki:3100 - isDefault: false - editable: false diff --git a/roles/services/templates/grafana/provisioning/datasources/prometheus.yml.j2 b/roles/services/templates/grafana/provisioning/datasources/prometheus.yml.j2 deleted file mode 100644 index e695f24..0000000 --- a/roles/services/templates/grafana/provisioning/datasources/prometheus.yml.j2 +++ /dev/null @@ -1,10 +0,0 @@ -# Generated by Ansible — do not edit manually -apiVersion: 1 - -datasources: - - name: Prometheus - type: prometheus - access: proxy - url: http://prometheus:9090 - isDefault: true - editable: false