Compare commits

...

12 commits

Author SHA1 Message Date
3b875f57d2 fix: disable discord-bot and walava-web until images exist in registry
Some checks failed
CI/CD / syntax-check (push) Successful in 3m0s
CI/CD / deploy (push) Failing after 1m39s
These custom images (discord-bot, walava-web) are built by their own
repos' CI/CD and pushed to git.walava.io registry. On a fresh server
Forgejo hasn't run yet so images don't exist — bootstrap chicken/egg.
Re-enable after Forgejo is up and images are pushed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 06:26:14 +07:00
f4688ed8be fix: disable outline-mcp until image is built and pushed to registry
outline-mcp uses git.walava.io/jack/outline-mcp:latest which doesn't
exist in Forgejo registry yet (Forgejo itself wasn't running).
Comment out the service; re-enable after building the image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 06:05:30 +07:00
7aa5574098 chore: remove mon from inventory, update server descriptions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 04:29:26 +07:00
f704ede1cd chore: rename servers to main and tools in Timeweb
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 04:27:23 +07:00
8a3aaa2fca feat: Terraform infra-as-code + delete mon server + fix S3/Outline
Terraform: imported main (7004701) + tools (7076013) into state,
destroyed mon (7076015, 188.225.79.34). State: No changes.

S3: fix endpoint s3.timeweb.cloud → s3.twcstorage.ru (actual Timeweb
endpoint), remove AWS_S3_ACL=private (Timeweb doesn't support per-object
ACLs — was causing Outline upload failures).

Vault: added vault_timeweb_token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 04:26:33 +07:00
862eac5f11 feat: add Terraform config for Timeweb Cloud infrastructure
Manages main + tools servers and S3 buckets (walava-backup, walava-outline).
Includes mon server resource for import + destroy workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 04:15:27 +07:00
fde51352d7 feat: migrate monitoring to tools server, fix Outline S3 uploads
Monitoring stack (Prometheus, AlertManager, Grafana, Loki, Uptime Kuma)
moved from main to tools server. Prometheus now scrapes main exporters
over network (ip_main:9100/8080). Promtail pushes logs to ip_tools:3100.
Traefik routes for dash/status.walava.io updated to ip_tools. discord-bot
PROMETHEUS_URL updated to http://ip_tools:9090.

Outline S3 fix: remove AWS_S3_ACL=private (Timeweb doesn't support
per-object ACLs — caused upload failures). Add CORS configuration task
for browser-side presigned uploads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 04:10:28 +07:00
d6015b76a3 fix: add proxy network to Outline and n8n for outbound internet access
Outline needs proxy network for SMTP (Resend) and S3 (Timeweb).
n8n needs proxy network for external API calls in workflows.
Both were only on backend (internal:true) so DNS/TCP to internet was blocked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 03:54:19 +07:00
036b80501f ci: retrigger deploy after image retag 2026-03-27 03:33:33 +07:00
521c806ed9 docs: update STATUS.md — reflect walava.io migration and service layout 2026-03-27 03:11:54 +07:00
36be9fb33d chore: remove SMTP relay, clean up tools role after Outline/n8n migration to main
- Remove smtp-relay (postfix) container — Outline now on main, uses Resend directly
- Remove UFW port 1025 rule (SMTP relay no longer needed)
- Remove postfix-relay from image pull list
- Clean up tools role: remove Outline/n8n/env.j2, simplify tasks/main.yml
- tools docker-compose now empty (pending monitoring migration)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 03:10:56 +07:00
489791403c feat: migrate Outline + n8n to main server, rename S3 buckets to walava-*
- Add Outline, outline-db, outline-redis, n8n, outline-mcp containers to main docker-compose
- Add env.outline.j2 template with Resend SMTP and S3 (walava-outline bucket)
- Update Traefik routes: wiki → outline:3000, auto → n8n:5678 (local, not cross-server)
- Rename S3 buckets: visual-backup → walava-backup, visual-outline → walava-outline
- Extend backup.sh.j2: add Outline DB, n8n, Plane MinIO to backup scope
- Add outline_image, n8n_image, outline_mcp_image to services/defaults
- Remove Authelia config deployment tasks from configs.yml
- Add outline-internal and n8n-internal networks to docker-compose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 03:04:54 +07:00
34 changed files with 17726 additions and 679 deletions

View file

@ -1,7 +1,7 @@
# Статус инфраструктуры
> Обновляй этот файл при каждом значимом изменении.
> Последнее обновление: 2026-03-23
> Последнее обновление: 2026-03-27
---
@ -9,8 +9,10 @@
| Сервер | IP | Роль | Состояние |
|--------|----|------|-----------|
| **main** | 87.249.49.32 | Основные сервисы + мониторинг | ✅ Работает |
| **tools** | 85.193.83.9 | Wiki + автоматизация + почта | ✅ Работает |
| **main** | 87.249.49.32 | Все продуктовые сервисы + мониторинг | ✅ Работает |
| **tools** | 85.193.83.9 | Вспомогательные сервисы (пусто, ожидает мониторинг) | ✅ Работает |
> mon (188.225.79.34) — планируется к отключению.
---
@ -20,95 +22,91 @@
| Сервис | Домен | Статус | Заметки |
|--------|-------|--------|---------|
| Traefik | — | ✅ | Реверс-прокси, TLS wildcard `*.csrx.ru` через Cloudflare DNS-01 |
| Vaultwarden | vault.csrx.ru | ✅ | Менеджер паролей |
| Forgejo | git.csrx.ru | ✅ | Git-сервер, SSH на порту 2222 |
| Traefik | — | ✅ | Реверс-прокси, TLS wildcard `*.walava.io` через Cloudflare DNS-01 |
| Vaultwarden | vault.walava.io | ✅ | Менеджер паролей |
| Forgejo | git.walava.io | ✅ | Git-сервер, SSH на порту 2222 |
| Forgejo Actions | — | ✅ | CI/CD runner, деплой через push в master |
| Plane | plane.csrx.ru | ✅ | Управление проектами |
| Grafana | dash.csrx.ru | ✅ | Дашборды мониторинга |
| Plane | plane.walava.io | ✅ | Управление проектами |
| Outline Wiki | wiki.walava.io | 🔄 Переезд (CI в процессе) | SMTP: Resend через walava.io |
| n8n | auto.walava.io | 🔄 Переезд (CI в процессе) | Workflow автоматизация |
| outline-mcp | — | 🔄 Переезд | MCP сервер для Claude |
| discord-bot | — | ✅ | Деплой-нотификации в Discord |
| walava-web | walava.io | ✅ | Лендинг (заглушка) |
| Grafana | dash.walava.io | ✅ | Дашборды мониторинга |
| Prometheus | — | ✅ | Сбор метрик, 30 дней хранения |
| Loki + Promtail | — | ✅ | Сбор логов |
| AlertManager | — | ✅ | Алерты в Telegram |
| CrowdSec | — | ✅ | IDS, банит злоумышленников |
| Authelia | auth.csrx.ru | ✅ | 2FA SSO, защищает traefik dashboard и plane/god-mode |
| Uptime Kuma | status.csrx.ru | ✅ | Публичная страница статуса |
| Бэкап | — | ✅ | Каждые 6 часов (00/06/12/18) → S3 `visual-backup/data/`, 7 дней |
| Uptime Kuma | status.walava.io | ✅ | Публичная страница статуса |
| Бэкап | — | ✅ | Каждые 6 часов → S3 `walava-backup/data/`, 7 дней |
### Tools-сервер (tools, 85.193.83.9)
| Сервис | Домен | Статус | Заметки |
|--------|-------|--------|---------|
| Outline | wiki.csrx.ru | ✅ | Wiki, аутентификация через email magic link |
| n8n | n8n.csrx.ru | ✅ | Автоматизация workflow |
| docker-mailserver | mx.csrx.ru | ✅ | Postfix + Dovecot, порты 25/465/587/993, DKIM+SPF+DMARC |
| SnappyMail | mail.csrx.ru | ✅ | Веб-клиент почты |
Outline и n8n **переехали на main**. Сервер ожидает переноса мониторинга.
### Почта (@csrx.ru)
---
**Аккаунты:**
## Почта
| Аккаунт | Назначение |
|---------|-----------|
| noreply@csrx.ru | Системные письма (Outline magic link) |
| admin@csrx.ru | Администратор |
| jack@csrx.ru | Личный |
Используется **Resend** (resend.com) для исходящей почты.
- Домен `walava.io` верифицирован в Resend
- Отправитель: `noreply@walava.io`
- Outline шлёт magic link напрямую через `smtp.resend.com:587`
- API ключ: в vault как `vault_resend_api_key`
**Архитектура почты:**
```
Входящая: internet → port 25 → mx.csrx.ru (85.193.83.9) → Postfix → Dovecot → IMAP
Исходящая: Outlook/SnappyMail → port 587 (STARTTLS) → mx.csrx.ru → Postfix → internet
Outline: outline контейнер → mailserver:587 (Docker mail-internal сеть) → noreply@
```
**Входящая почта не настроена** (нет MX, не нужна).
**Настройки почтового клиента:**
- IMAP: `mx.csrx.ru` порт 993 (SSL/TLS)
- SMTP: `mx.csrx.ru` порт 587 (STARTTLS) или 465 (SSL/TLS)
- Веб-клиент: https://mail.csrx.ru (SnappyMail)
---
**DNS-записи для почты** (все должны быть в Cloudflare):
```
A mx → 85.193.83.9 DNS-only (НЕ proxied!)
A mail → 87.249.49.32 Proxied (webmail через Traefik)
MX @ 10 → mx.csrx.ru.
TXT @ → "v=spf1 mx -all"
TXT _dmarc → "v=DMARC1; p=quarantine; rua=mailto:admin@csrx.ru; ..."
TXT mail._domainkey → "v=DKIM1; k=rsa; p=<KEY>" (генерируется при деплое)
CNAME autoconfig → mx.csrx.ru. для Thunderbird autodiscover
CNAME autodiscover → mx.csrx.ru. для Outlook autodiscover
```
## S3 (Timeweb Object Storage)
**rDNS (PTR-запись)** — настроить в панели Timeweb:
`85.193.83.9 → mx.csrx.ru` (критично для доставки в Gmail/Yandex!)
**Автообновление TLS-сертификата:**
- certbot renew cron: каждый день в 03:15 и 15:15
- deploy-hook: после обновления автоматически перезагружает Postfix+Dovecot
| Bucket | Назначение |
|--------|-----------|
| `walava-backup` | Бэкапы (каждые 6 часов, 7 дней хранения) |
| `walava-outline` | Файлы Outline (вложения, изображения) |
---
## CI/CD
- Репозиторий: `git.csrx.ru/jack/infra`
- Репозиторий: `git.walava.io/jack/infra`
- Триггер: push в `master` запускает `ansible-playbook playbooks/deploy.yml` + `playbooks/tools.yml`
- Runner: `act_runner` на основном сервере
- Runner: `act_runner` на main-сервере
- **Правило**: все изменения только через git, никаких ручных правок на сервере
---
## Известные проблемы
## Бэкап (что входит)
| Проблема | Статус |
|----------|--------|
| PTR-запись 85.193.83.9 → mx.csrx.ru | ⏳ Настроена в Timeweb, обновляется 324 ч |
| Tools-сервер не бэкапится | ⚠️ outline-db, n8n, mailserver/config не входят в бэкап |
| Tools-сервер вне мониторинга | ⚠️ Prometheus не скрейпит tools-сервер |
| SnappyMail домен csrx.ru не настроен | ⚠️ Нужно в админке: IMAP mailserver:993, SMTP mailserver:587 |
| Данные | Метод |
|--------|-------|
| Forgejo DB | pg_dump → gzip |
| Forgejo data | tar volume |
| Plane DB | pg_dump → gzip |
| Plane MinIO | tar volume |
| Outline DB | pg_dump → gzip |
| n8n workflows | tar volume |
| Vaultwarden | tar volume |
| Uptime Kuma | tar volume |
| Traefik acme.json | в составе volumes |
Расписание: 00:00, 06:00, 12:00, 18:00 UTC. Хранение: 7 дней.
---
## Сети Docker
## Известные проблемы / TODO
| Проблема | Статус |
|----------|--------|
| Outline + n8n переезд на main | 🔄 CI задеплоен, ожидаем старт контейнеров |
| Authelia всё ещё запущена | ⚠️ Нужно удалить после деплоя (remove_orphans уберёт) |
| Мониторинг переезд на tools | ⏳ Следующий шаг |
| Отключить mon-сервер | ⏳ После переноса мониторинга |
---
## Сети Docker (main)
### Основной сервер
- `proxy` — публичная, только для Traefik (нужна для ACME)
- `backend` — internal, Traefik ↔ сервисы
- `forgejo-db` — internal, Forgejo ↔ PostgreSQL
@ -116,11 +114,5 @@ CNAME autodiscover → mx.csrx.ru. для Outlook autodiscover
- `plane-internal` — internal, все компоненты Plane
- `runner-jobs` — публичная, для job-контейнеров CI/CD
- `monitoring` — internal, стек мониторинга
- `authelia-internal` — internal, Authelia ↔ Redis
### Tools-сервер
- `front` — публичная, для port binding хоста
- `outline-internal` — internal, Outline ↔ DB ↔ Redis
- `n8n-internal` — internal, n8n изоляция
- `mail-internal` — internal, Outline → mailserver (SMTP без auth)
- `webmail-internal` — internal, SnappyMail изоляция

View file

@ -48,6 +48,7 @@ discord_bot_token: "{{ vault_discord_bot_token }}"
discord_bot_app_id: "{{ vault_discord_bot_app_id }}"
discord_bot_public_key: "{{ vault_discord_bot_public_key }}"
outline_mcp_api_key: "{{ vault_outline_mcp_api_key }}"
timeweb_token: "{{ vault_timeweb_token }}"
# Server IPs (used for cross-server Traefik routing)
ip_main: "87.249.49.32"

View file

@ -1,146 +1,183 @@
$ANSIBLE_VAULT;1.1;AES256
64373338333466646131363639303563393130303436613766383337383937336663643765636563
6438303632663737356166633739383065363535653234380a316266613236313532353361356339
39343137333435333734623665393033383136383932306634323563623135626235633430333232
3166623031613436650a356538646633343266323533653466623961363231303730333161313332
62353133336462636564643835613361303039343737333938343162336566303737336534376364
65646236633934623937393963653166356331393137333730306663333133326631393163386361
62363065653232623634383032376165366330343533386266656334623334623333366332313065
36666133383535383537663339656162616638396464653238313732646435376537626335663563
64656237353263376466303632343432613065373061353632346539353562623064616534656466
34663534303838396165333761313337393261386433376631313066393765613232633535313863
31643535653431646564613232323538363061343664396332373565303531383438383665656233
31383731323633353635373232613036633433376636386537353136396336373663326635353838
36396234396436636565646638303761633061653761366238656266323533303466343666326365
64336164326566646630643361613264636533373330646630386266663965353934633765326434
64646162306365303065303830613636346232333964633035373463333630386132396632313035
62623139393430633665333034663661313965663134373534623166333435343132346662393361
32643332623731386535373366633563333961326632356265633330313839616463313834636164
37626330313465316364313334623031383531393132303563303263646637383838623863633566
37383036623030653630343235393336616435346231363338386334653732633634353565396638
61306565623166333332376439636636333133333934363631613166366664303362636362326534
37323561313538343466666161313937353630396162333361343437383537363966383730373364
61363733646237623761386436653165616136633131316538323266666262373761663066653934
63313266396232383662663735333530393633616637653466383734326636663137336462383134
35353536353763616531623433653764623535623464363432613635663137363738323231353939
64653363396634613737376462343139316337366234653639386335656462643661353764646363
62636537326639373665616134363837633237613734383761346662363931346634323161383364
36613236646163396636383036323566373664393963623961366634333337353833643439313565
32313631333433613139313533306436346334383239366337303865336562313235643734366332
30323530643034303336336531363433626431346464303562396366333336386561313964373364
35333037353030383861663165366534396637343634653239653732663138346566653035386135
34333732336263643532333133323063613363393037313639663966373938393762316663333066
35393463613232303338386535333935646466623162623531653663666235383263383461656462
39663162373537653735653536396164366330616164613561663363323463313634626632383964
37303134663764643163653062366537383630376463333363313839366134313535653866626332
63386435653065306231616532616563336330333235303562393731613434366438643038623234
66313038623137306666643762393234356332333532316333353266346163303036393366383238
35383232636164633132643339623936633732663966613961613964333631653433343731333962
64656239303631386461663036393039326330393435613562356263363461363261366430393736
39336536393834636336643837663038623863373362306364393166326135343430356437313435
63643064343438373661306363306233623563363061613032386165303262303533363433383062
39653166646534633733333335356464646339323961653038383165363663313738643363656663
34633664343833656332643663323036303665616265633463323462363330303333363836306331
62613937363936633533633965323530373761376438303061643063316162336566613934393562
65653463376233353662336536303731613632623836623263616435353235656232313438663236
64663265623431333831383866316237663237333235363733623739343134306636366366613737
62616430666233646131353463396236316366613430663231643435653161366562353265356632
65613164376437353566643435623366323266336635666336373465303936646665656135346135
38663938633063346164623962383733333239623565336539663531646330366536363336643032
30376562663364303864376330326638336131656362313264346361343236386664376338333963
34356236346534336233626361386231613761386135303335303233303730323939383735643138
63393433363734346436366436633064643532613335613062306231343163393962343031613062
30303962666434616235613062376339636433636466633935303837343937613239663161353038
61663130396262636339316161313636636339343033303132373733336663643433613161653361
31316237653461336335626138646139376264643733336434326339623330373337333761656333
65353837323563623561663865663664356534626433373934393263353234626630383738656362
31333338663363366531646230393663646462613164326366356238373137363230343561623934
65353963643336376231613130626466353735396166373165643965313464346562323362306161
62353830346130326261323963613938333136393665343566656636653939306466316432653431
66393466393164346237366365393039353563373861303134656662646364626562633538316531
62346161653038303836643932656563363230613732373662396133353736323138336162343936
34383334333031383537366533313161373937313261306563653431363961383563346565646333
66656433663538373038646266373538313365656532616634333861303139616236663765353532
32346638633434376232666635306331633363623639383464303434633936653162356264373766
31376431633261346230363938396437643938343637646564643966346261303932613563646166
61643263396163313735326239353561636333323765653734393132623062346630643730366235
35623535373039623933383131373032646330383764616565613364646431343631646430383538
30376535376261633738326238393634656433316234333432303439393137653362383139353466
64323965633337343161323165376531363066356432303832393065663639653363313531373436
35323130636161626565666632626633393834363136633337623839323939623464653330343963
33336161333865613634303334336436336561653338633439396335396635363832333064376637
30656665363434666439373033633462613136353339623730376238643435616532633563383764
39363666313732333030333338616532636463623335633366333235333961636136636436336534
37353063393763646339613938366132396565313833613066303664666165646439623832336432
34383762623735306233386566363230643063653635383636666639303637316131383163356133
66363530383734313136646633613166323761393531356537666231613339353066303332373462
63663233316631653234383234656262346238373762616138363130386664383133356239656161
38313638333231653066646165636231306239353766313437336634346664336330616465343430
62373661326237393666363737396630643034356666663338346664643837303331613961376634
64633761653436343135356364363362306563643536656437663836613766643763623334333361
65636366663339636438346631616239393865653138656262653632386238303566333762616634
61623931663466383736306563653231333234633963386333323939316439306461653064373662
65616638343930636636633230366131356536333236306339363562383063383035326635346335
65653462626538666364626635663331393263386630323235326334613830653432613334306461
37353232346337363034653633313565336565333934633062623136623062663262386331373862
32303730313034396337633132303531353436643662646564343635386163643538623935613661
32643836666438323535656332336339323333373634363664383866393765646430393364653563
33376634316137363066393337646336623065623636643862393534316630303562633761666639
65326233626435616531383939323035663333626134303631336532643136383938303839323639
34316261613761373934396335323763336663306264373431626134313935343062333930336133
39656161373532343934323263316666666430316462636439653762656236656635613531643731
33386265623531353335376636623564633234616465653166363830363531393933393163353033
62323237346263633032613161363831303963356432313534313832386138393335336166643436
30353762363434373836353966396265666166636561346436373934333339626637646662613261
39643664303034353364333538356536666163623538656261613265633839316163303732646637
38363465303362303566343165623364376532626137353237333165343162363537333237646230
35373464333365663163303634333439333938643334393136643437303064396631323331643662
62386534303630373236343730626562323738313561326339633061356534323862323033356539
39316162656435663133663533646331393636613037643866303534646166646662613664663561
62383035313831333736653831333739356535623864666165343362373933336366326264396331
63653837623433396636323265356165396437316538656533363064393033353061626463636533
39636463646233363233376365323731636433663765326232613335356234626635663538343061
39613837656661363439346662386563653361613435626163376232306635376537373931646637
64313537356431626466303165646538323234303065363163323431663962323030623263623233
34636662366266653538323337656662633938616437323862343064353533306437656136323939
34376137333135343333383633326465373164643636313239343365306237316238323534373239
33383434313033633337386438613134326430306536643666656534326538396166656265346531
30613434343334383135376337383034373365313762663131396234323330663565666431383264
63396430663733646337656164336630386164373964376439663465626165656632623635333766
36396439316538323530363266303366326230366564616639613738623463623835313264353561
32633065646161633462346634393737616333333566353630666565656431303162353633646138
62643634316263313034383063643438396537393361373632323739336262393639666537646266
34313466313461663664623430363634383236636330376165633430303665613261346631643938
39326632383565346663303937653138623433643038386131343435366361393137353062346562
62316337616435623762313630643966343836353163356534666363346239363638303031383231
33363830376337656537356635303636323037623763306136363761353037623137643832356562
33333937656239613562643661343634613230386130353439323139313965393266376565656338
31663438336538316663623939363633316363656661646162363365303065653766313161663334
33326236643039333862363034306432326634626330373862653761616238616435363233616337
36653532316362643366333566616664353938363032613766656235386536343737313231613334
64363661333664616337633565336137343361323131363034346437343265343634356139336263
33626261623864613039306335386536656636303238316265313863616134306239616661656133
32303036376431646238663337303737616232666130623730383166646265333062303263666463
65363164316634613231323065353331363035353335386662636334373930646437376239353531
36323361323661363739373665373838346138346662386235353136653230393939393332653638
38623866623461646330376233333837653334333665393665396261653065623835313831323936
36366438623563373937663233666433666363353132373333613734393330663133313966363162
31356364643830353630646263313162346664353736383236333235633838366633643636353032
38383633323438383433366632343433353633633537366231613537333938396438643732383530
37323665316435633961323230396265353930343537366666323034333235616431376138386536
65386432623530323935633938393161633430663063303837353565616561396434333063346462
35616366633438343330663232303366333662356364373964346439343961396561613963396238
37643430376130386366316533316561323466333061616266643962336331633761343935613337
30613235316130626165343232656165646536383531383430366365616466353939346638656561
38373638313864663862653634613631313361363161666135333532666430373764333233333938
62313330383337316566363531373236373834613166356538653037663137336131663138333039
62333634326434353736363662346530353939316464313364373135323765383232323135353065
63393163623937366135356233653866656236366133366530393864366165373436333235376165
62333763323237343133323538613832363938353431316566373966393365346337653638303139
39633337633733316465376161636134616637313733303663373766383664333030393431636534
32373362366264626462396366346236343635643865316562353532353166653636623736333232
64636563643332363534363733303662633331613664653532346138333334316561646636376465
30656266383536333165353234393665376235646132386630346466633466346464386361666363
31353036356435393039363162323062303831613138306131643936373266323065663865356235
39636465303762386533626231383732636138363863346439346536323663656366353139653661
3931
61333332643965336563306337363339366563626363333837343831316137346335343031653735
3139333134386564376566616637613761386663346337320a613937373264653435636334633432
63373735386239383034343935336235646432316364366361323664393062383233393336343434
3238623361636136310a666262363065346234636630316264613662336261336234356135333662
37313233396265313961316135376334356662303161643132623734303864313765303264313934
62636265356161343635626337396466623766323839336266353063316333336534313138663336
61303834306233363739326237376339616638323162333631346565366232343966663037396161
39373437353732393765306230373864356135666630613930306335343466643435326630373831
64663363646334306339363265363162323561333531343932303034633331316262356533666363
63303431386130386536616436393764666136323636643431336565623537393638646632363762
34383731336163656236303937346466323332616433613938663134363661323538363963393533
38646338303464303765623433376163663232653931353034346633656336346530663864396465
31313864333730356434343066636331663838343635633235623162393963366436336437396363
62393761386636366664316436323535343032356537363833333962303761663563623833396638
66343033306237376437633238656634346264613561336335306432353635313363356333303561
35663839366434303366376266663033616364306636643437663464343866336466356133366135
63646233303037663563616661316533366264366535636333316333633437333036613039626636
38356363303761643630653964616136303566626363343436633834346230356430363132643637
32633761333564616639383463613662373163386264353935396332623065633366306330386266
39356665666530333331613632303461663764356638303234623031623731613431636666343366
30396132323536363838653730656263366165633964666463316236346562666639323339313333
61613962646232356264643932663962366266373332333938366230636232353630396532373161
34633230386162626535633032353330613337626237623937646165636132666630323038623233
35363032643936306230376165613632633061353430386366636238653034313038356366323064
39643961623136343130646666646630346638653136326365386437336466636263333238666266
39373861376261393365373238623662626137313862326333333135343730376439336434363633
34303230373933346563656363613034393535646539356562353635663065383137643337386564
62376230373334626239313661633835333062656432633931333735653736636238663331613165
34343538663264353536656661643265383865333665656363326166326239666136646435616130
33343730386232336232363561363439383666313762613933666235363665356264386161656631
30386331663232653537653036653531346265386239326134323066316139656434356232303938
66633562313037363066363332353262623831326263316339376436333833313231363662336432
32333961616231303162383233666562636164633735326338323363323965323436623537366232
30643533383733333532383766366265363636366136653332666634336263306563643638373739
32313937303832353236656437626662306132323566393763643665633736376436356235616332
62653838356162323066613565323835313263366134666132393136306264306337353834646334
38346166383539383864386362393932353164636239383366383664636632313534353866643663
63613338333466653365663262313561313763323035316638376666643035616663646664313061
64663535376432656439636534653431373133616239373161653134303039373163633866303233
31363539643264323434393535353233303435626230303636646364646331356266396239373438
38376638383339626139363863646565316433373634633132616633383438663534306665373166
34633935373430653666666238633634323132313664373562653931313131633564363437626161
61346263386235626465316561393064646333326165633338393037363732323936336439363564
32366532663539316638366439646634313665623365343566623232376161346439623433336665
34613964303366396266373835356333393433343461633765663537366261313965626133346638
64326131616439306532366162616239343234616563393436343062373933313762316236386666
30626162376335313738663566386138323564326134623563663134326662626362393362666236
38643237323062363433303833616533383437636262663762363266383962333936623032373334
34323138613463623537643262663662356534313031666564383761326133613530363665613933
62393432346231373063366238326361306161306235393333366532306263333636616536353363
30646165613738656332363337383031326539393462393365613766346438323539636535366137
36383334383038343539666562343137623030663935663639303333643933633566373264326330
38313037346635663430643238323863363831393064346436636463383738643832336362373537
63333064396433666666653935306339613062373539386665386264623462653535303933396263
34336337626462353265336633346439316330303639323563613561346532376530376530666238
63626436326339336436303232666665623632343433306362306661366565306436613765656539
63373764313835353437653535333935626136613563396662336162623237613431623463663561
34643936393030303938386339306435326561663062316539623661323861303330653765613065
63323532666139336631343839316638616132636366353438366566396664393561333330323861
31383236316336643238653132326362343235343534633032376564356463633539396331646330
61663232616534626130613331396161373538396430383963346262623230326638396338376631
32343630353734656163343931396238653731316562356433656335636339616231323763633033
65666539386134646561366264376333656361386362636330363434343865383336313535383736
32633465663230356566303134633131383565366163653333613433633035616565393733306466
33626534383436366162633831306231663361363836363034393462303861373862626165326163
34656233636161336534633562333263346639636133663235626436393966666438653739386436
39336364313238303737643762356530333537623565663361646233663336376533623233373738
36336164343935633533376463373437663830303965383134383431303463376265316663663862
38663964653133616464646231353435353863613131633139393331356634643334656133623834
33656230343465343035656532633939336339353934623466663537363163626463633737303738
62346563313061343661356564326565363531353464653135353763333038326333313434306132
63656535623064396530663932656434666661663763666535613661333038306362653031363533
65373234386130313634613934653836353434363432653463376464643935643836376237636236
65323261373334613630343865393039396262336230663030323335363730646361633363613131
32363664613862653035383436666161363366396161313437643662363635613532373765666237
30303261333363373663616535343436306530346666323036326365616334383134383435306637
32653935363962393363306137373861383461346439633030323935636564623264303638353539
32366363616636383337313763393765316637656638356365373765306639336332363631383734
39393131393765383165326662633438366330633632343665653735313032643037653430363761
36646365623835343466313764636236623936666330643730386238353565646565656664623861
36333438306161666232613839356131306166376434336431323036383634383539306134323038
32333666663237643865643461373538626264656335373534303236616435303039346661633338
66363238386233303532633431666263323235613335626361326461353466613661616433613736
38636331663635396636303931353338333437393936373631366130366632313037363262613333
30303261633263306661633862326665313338326433373033333639656663393564343536373063
64336464353937393238346331313166326238303033663838306339303463643364333330396435
36313139636338323233353262373864643463386464343037333733343236303132366236353231
62316533613734313038633461323864303862616339356236313030623963376432376365366166
34303564333863326265373066346233363964633061626335623636373839633366336266343161
33323866353937653835396534346235646236326563386234386465333839333464653462633264
62396163353039613436363866623938326164323638346531653533326239636562353334366564
34353235303838353765333636626261383462373539646230303332653364346635616232313131
33636636616439666134383536383965363532333635663232323063623031666562653461646132
66353530373466363731633435653732663337373434323436306539376632376538373636613561
62393631343639623736306336376237396239363232303732653434666366396162346463373864
34386435663438353031376639613530356437633665653266336436616539333262646362623836
65646537663031616466303065646137633333623932666534326139373064633834393666366661
32343065386362343331383637333766313330383532326132373338386435353463616233323633
65666362663638663938643133333631636539623864653864366263303830366230306537663162
63396236636634616238353863323332626565393139663537636435646132323864303539316364
36353034333664316338383131316335333133303930633030376238616164633264303662653363
62376361376637373466666534663136363639383262343235366366303530343361663764333066
34346130396365386232313365646138353733376430373066323561363235633236636533303433
33623839343532326163663865313332366663643861323737616362636233343963613934383437
63336565323935303337336434303561356566616537666330323039346531623039656162636538
36353232346633653135393266313865663532373834613663386261666535363965653366643666
38626563353765356435653534356630663730313932323739623866373335333338646434326539
34323561313237633935643364646162633834616466653138373639326464393837666630386236
65653066346361616131626238616564303033623966303230343638353464373036333863373465
63313562386237623532323432663562333862626262656630303032656565613635313034303566
38656563393934663364366333316431653563643963383838396366333338323236343164626363
66323131363361643031343366393034373131623631643265383532633864623362613566316565
39373538626364613163333836376666643764386330616564376364626362373962623838613533
64386438666365656163373065653832326132343535353238363165663461623264343831343037
35323433613832643934343633376136663733653465653762313963636535373539356565383937
66383538643435643461616337333864336166663030353762316630613536656466303761353531
35666539376633383236396535336335303764663435373633373335383863376466313633393830
37323638633437373966343935623536353161316366623431376565306666616662316561316130
36336239663232316262363462343431323664373330643361656464343938646330373631623565
33306365653037656232326461343336623134643433666561346164626236383565653061383961
34363734633237643064376337336163623035316630646631666564653634323339656164363063
30363165643663613138623537363234313939653461353130366366336662663236323131303239
35316462316132633330366634366533336563623730633664316564393263633436306463353639
32383031386638313533613665623966363630636637656164363736353937303162373839623339
35393834393235363966613032643935663139653733316261366566623566396565636636663136
30306433386533363038633666383862343064313338363835623030316466653564366562393733
32343761623234353665313061353330623666333037393633316333303436373530653666646632
33393637363333643638333666623430623963373963303739653261356461393933646533623035
63343936616233353035626565633439376339386237396433663933633335316131333834623264
35353866336564623166323236663733333532333465633661663666316664626239653631643232
32393135316533393464636461636163373762363638333439376335643237383262663131383032
36656134666130633237383733333532646365323131626430653031326363626438336436656135
66303430373230386166396132316530316536646165666633386164313061376439653663326363
35376634343735393735396365366239643865356231633530313865643438633934656664356366
61306136623635663165366637376565366461383363656365623136353533663963623766376334
63363661303262353939653366383931623235643632663035353431356434396230623366666439
39623265343763303030366133323263356261383361646662386565363238306437653732616232
32303461373239353737666638313130373765626462376137363162386430333762623663663934
62383331396331323961303461623130663931303537353831303664306564643866623739356566
65643631643739313039333337646162653065366462383938303163636132356263386461323535
62626436336464333961633535333164363532616163353337633430653063626336386635633331
30313862356165613833316662613764316139363335633833303864656434383062616434643864
66646233333937393462336236643161663135313163613664376665633235306233353561313435
30383363613330306131303262646130633032386531333565643833313566636231323130626135
30383831306136373537346532383866326638663263333737643737363535653037653163346536
35663331356330313665643337383639366535616333663233356165366363626530346538623636
62393261333938333031396138303965303762353838333038643864626439633935393538633164
34353333393535363939616366376335353435366161313563396632333665363538323962366362
64353631393730303162653131306130376131323665626339636130613137343665303036383862
31383066353238396131336538656133626335636635643366313838323635323537323638303162
33353466383738353637623263633564383236633434373939313936393638303862623536363035
63656232376562373963633865346536373532636334656238356131656435343333386233363132
61636433613866373664363138323764356538343363613232626539366237666263333165383566
63663033336266663266333335636264666431306435636133363331623364336432616166346130
64303437316633306663633933646262653336666263313738326465616366666537643662383235
32633534346233303336623565323937356538366532313537363161393030653534323033316535
63646335623535623935383231373438313936333339326263353861623332336239653765636333
31333865326164353937363863353865626531373337396639353339653732356161333733663961
64313031306530663234313630613463383236373331356230616433623562346266356162373034
30363832646538383138393836303331323562633737386132393739373832326432323831616565
64303937653130643766663132306662313639333962323564613935336231323564333236636330
32633937656137343939653964306633326237313736633535393338346330383363383465313937
66613965326630356131333334393865646132633366623335653733393462333166643537393762
65656234636334653363656438633361313039323239353031316333323037636364636438626562
32363834616630373738323064633363356536336336343138373761643935356339623761383138
31633638323934343965346539353232383736663738373130626264383631343463383563373430
35373736313663346662356564636331396464353735643262373362313261306661363136663239
32313036356562333465613366386439323266613437653539313364356631366365633439356261
32316465386266373435383632653564623166656330393637393830336462383561353032376535
34326165626639646364396565363365643131666533353433643132386333613863383066323632
63656566383161303038346138383835303430353161656662313336373536653236356461356664
64386532343061616136343536613064323236613532356237386239373938366636323438373732
62326430646539613363376530626331303636393865353438666434376561666539343737623439
33653962353061666335653663643861636564363761346332326436653339613132393730646535
35353630616637653731396438653635306663626363346565306662623237666531333338333263
63663232633437386434356631373864366234653965336338383965363334623833333437383937
35383864336431656136666630643564316163393037636161303339653233393866616531623437
39643666626166353831616137623530323261373465323436613535313362616131633266373332
62363163316335626461653434643532666630643936666139643634393765366234356633333534
36376330333461326263343164323031366232623264353930336561643637323230336537363236
65353136363932623930376234653732323562663239393337336538383131616564616133613834
3665613863646666663431363062396330366438666431393536

View file

@ -1,7 +1,6 @@
# ── VISUAL Infrastructure ─────────────────────────────────────────────────────
# main 87.249.49.32 — core apps (Traefik, Forgejo, Plane, Vaultwarden)
# tools 85.193.83.9 — team tools (Outline, Uptime Kuma)
# mon 188.225.79.34 — monitoring (Grafana, Prometheus, Loki, AlertManager)
# main 87.249.49.32 — все сервисы (Traefik, Forgejo, Plane, Vaultwarden, Outline, n8n, CI/CD)
# tools 85.193.83.9 — мониторинг (Grafana, Prometheus, Loki, AlertManager, Uptime Kuma)
[main]
main ansible_host=87.249.49.32
@ -9,20 +8,14 @@ main ansible_host=87.249.49.32
[tools]
tools ansible_host=85.193.83.9
[mon]
mon ansible_host=188.225.79.34
# ── Group for roles that run on ALL servers ───────────────────────────────────
[all_servers:children]
main
tools
mon
[all_servers:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_user=deploy
# ── Legacy alias (keep for backwards compatibility with old playbooks) ────────
[servers]
main ansible_host=87.249.49.32

View file

@ -5,4 +5,4 @@ backup_user: deploy
# Timeweb S3 offsite backups
s3_endpoint: "https://s3.timeweb.cloud"
s3_bucket: "visual-backup"
s3_bucket: "walava-backup"

View file

@ -32,6 +32,12 @@ docker exec plane-db pg_dump -U plane plane \
| gzip > "${WORK_DIR}/data/databases/plane.sql.gz"
log " → databases/plane.sql.gz ($(du -sh "${WORK_DIR}/data/databases/plane.sql.gz" | cut -f1))"
# ── PostgreSQL: Outline ──────────────────────────────────────────────────────
log "Dumping outline-db..."
docker exec outline-db pg_dump -U outline outline \
| gzip > "${WORK_DIR}/data/databases/outline.sql.gz"
log " → databases/outline.sql.gz ($(du -sh "${WORK_DIR}/data/databases/outline.sql.gz" | cut -f1))"
# ── Forgejo data volume (repos, attachments, LFS) ───────────────────────────
log "Backing up Forgejo data..."
docker run --rm \
@ -50,6 +56,24 @@ docker run --rm \
tar czf /backup/uptime-kuma.tar.gz /app/data
log " → volumes/uptime-kuma.tar.gz ($(du -sh "${WORK_DIR}/data/volumes/uptime-kuma.tar.gz" | cut -f1))"
# ── n8n workflows + credentials ──────────────────────────────────────────────
log "Backing up n8n..."
docker run --rm \
--volumes-from n8n \
-v "${WORK_DIR}/data/volumes:/backup" \
alpine:3 \
tar czf /backup/n8n.tar.gz /home/node/.n8n
log " → volumes/n8n.tar.gz ($(du -sh "${WORK_DIR}/data/volumes/n8n.tar.gz" | cut -f1))"
# ── Plane MinIO (uploaded files / attachments) ───────────────────────────────
log "Backing up Plane MinIO..."
docker run --rm \
--volumes-from plane-minio \
-v "${WORK_DIR}/data/volumes:/backup" \
alpine:3 \
tar czf /backup/plane-minio.tar.gz /data
log " → volumes/plane-minio.tar.gz ($(du -sh "${WORK_DIR}/data/volumes/plane-minio.tar.gz" | cut -f1))"
# ── Add restore instructions ─────────────────────────────────────────────────
cat > "${WORK_DIR}/data/RESTORE.md" << 'RESTORE_EOF'
# Restore Instructions
@ -72,6 +96,9 @@ zcat data/databases/forgejo.sql.gz | docker exec -i forgejo-db psql -U forgejo f
# Plane DB
zcat data/databases/plane.sql.gz | docker exec -i plane-db psql -U plane plane
# Outline DB
zcat data/databases/outline.sql.gz | docker exec -i outline-db psql -U outline outline
```
## Step 3 — Restore volume data
@ -80,9 +107,17 @@ zcat data/databases/plane.sql.gz | docker exec -i plane-db psql -U plane plane
docker run --rm --volumes-from forgejo -v $(pwd)/data/volumes:/backup \
alpine:3 sh -c "cd / && tar xzf /backup/forgejo.tar.gz"
# Uptime Kuma — extracts /app/data/ into the container
# Uptime Kuma
docker run --rm --volumes-from uptime-kuma -v $(pwd)/data/volumes:/backup \
alpine:3 sh -c "cd / && tar xzf /backup/uptime-kuma.tar.gz"
# n8n
docker run --rm --volumes-from n8n -v $(pwd)/data/volumes:/backup \
alpine:3 sh -c "cd / && tar xzf /backup/n8n.tar.gz"
# Plane MinIO (uploaded files)
docker run --rm --volumes-from plane-minio -v $(pwd)/data/volumes:/backup \
alpine:3 sh -c "cd / && tar xzf /backup/plane-minio.tar.gz"
```
## Step 4 — Restart services

View file

@ -28,3 +28,8 @@ promtail_image: "grafana/promtail:3.4.3" # https://hub
crowdsec_image: "crowdsecurity/crowdsec:v1.6.8" # https://hub.docker.com/r/crowdsecurity/crowdsec/tags
redis_image: "redis:7-alpine"
uptime_kuma_image: "louislam/uptime-kuma:1" # https://hub.docker.com/r/louislam/uptime-kuma/tags
outline_image: "outlinewiki/outline:0.80.2" # https://hub.docker.com/r/outlinewiki/outline/tags
outline_db_image: "postgres:15-alpine"
outline_redis_image: "redis:7-alpine"
n8n_image: "n8nio/n8n:1.89.2" # https://hub.docker.com/r/n8nio/n8n/tags
outline_mcp_image: "git.{{ domain_base }}/jack/outline-mcp:latest"

View file

@ -8,6 +8,15 @@
mode: "0600"
notify: Restart stack
- name: Deploy Outline .env file
ansible.builtin.template:
src: env.outline.j2
dest: "{{ services_root }}/.env.outline"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
notify: Restart stack
- name: Deploy docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
@ -44,77 +53,19 @@
mode: "0644"
notify: Restart stack
- name: Deploy Prometheus config
ansible.builtin.template:
src: prometheus/prometheus.yml.j2
dest: "{{ services_root }}/prometheus/prometheus.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy Grafana datasource provisioning
ansible.builtin.template:
src: grafana/provisioning/datasources/prometheus.yml.j2
dest: "{{ services_root }}/grafana/provisioning/datasources/prometheus.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy Grafana dashboard provisioning config
ansible.builtin.template:
src: grafana/provisioning/dashboards/dashboards.yml.j2
dest: "{{ services_root }}/grafana/provisioning/dashboards/dashboards.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy Node Exporter Full dashboard JSON
ansible.builtin.copy:
src: grafana/dashboards/node-exporter-full.json
dest: "{{ services_root }}/grafana/provisioning/dashboards/json/node-exporter-full.json"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy cAdvisor dashboard JSON
ansible.builtin.copy:
src: grafana/dashboards/cadvisor.json
dest: "{{ services_root }}/grafana/provisioning/dashboards/json/cadvisor.json"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy Prometheus alert rules
ansible.builtin.template:
src: prometheus/rules/alerts.yml.j2
dest: "{{ services_root }}/prometheus/rules/alerts.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy AlertManager config
ansible.builtin.template:
src: prometheus/alertmanager.yml.j2
dest: "{{ services_root }}/prometheus/alertmanager.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy Loki config
ansible.builtin.template:
src: loki/loki.yml.j2
dest: "{{ services_root }}/loki/loki.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Configure CORS on walava-outline S3 bucket (required for browser uploads)
ansible.builtin.shell: |
docker run --rm \
-e AWS_ACCESS_KEY_ID={{ s3_access_key }} \
-e AWS_SECRET_ACCESS_KEY={{ s3_secret_key }} \
-e AWS_DEFAULT_REGION=ru-1 \
amazon/aws-cli:latest \
--endpoint-url https://s3.timeweb.cloud \
s3api put-bucket-cors \
--bucket walava-outline \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["https://{{ domain_wiki }}"],"AllowedMethods":["GET","PUT","POST","DELETE","HEAD"],"AllowedHeaders":["*"],"ExposeHeaders":["ETag"],"MaxAgeSeconds":3000}]}'
changed_when: false
ignore_errors: true
- name: Deploy Promtail config
ansible.builtin.template:
@ -125,15 +76,6 @@
mode: "0644"
notify: Restart stack
- name: Deploy Grafana Loki datasource
ansible.builtin.template:
src: grafana/provisioning/datasources/loki.yml.j2
dest: "{{ services_root }}/grafana/provisioning/datasources/loki.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy CrowdSec acquisition config
ansible.builtin.template:
src: crowdsec/acquis.yaml.j2
@ -143,24 +85,6 @@
mode: "0644"
notify: Restart stack
- name: Deploy Authelia configuration
ansible.builtin.template:
src: authelia/configuration.yml.j2
dest: "{{ services_root }}/authelia/configuration.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
notify: Restart stack
- name: Deploy Authelia users database
ansible.builtin.template:
src: authelia/users.yml.j2
dest: "{{ services_root }}/authelia/users.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
notify: Restart stack
- name: Deploy Traefik logrotate config
ansible.builtin.template:
src: logrotate/traefik.j2

View file

@ -22,12 +22,6 @@
- plane/pgdata
- plane/media
- act_runner
- prometheus
- grafana/provisioning/datasources
- grafana/provisioning/dashboards
- grafana/provisioning/dashboards/json
- prometheus/rules
- loki
- traefik/logs
- crowdsec
- authelia

View file

@ -16,29 +16,43 @@
- "{{ plane_redis_image }}"
- "{{ plane_minio_image }}"
- "{{ act_runner_image }}"
- "{{ prometheus_image }}"
- "{{ node_exporter_image }}"
- "{{ cadvisor_image }}"
- "{{ grafana_image }}"
- "{{ alertmanager_image }}"
- "{{ loki_image }}"
- "{{ promtail_image }}"
- "{{ crowdsec_image }}"
- "{{ uptime_kuma_image }}"
- "tecnativa/postfix-relay"
- "{{ outline_image }}"
- "{{ outline_db_image }}"
- "{{ outline_redis_image }}"
- "{{ n8n_image }}"
register: pull_result
changed_when: "'Status: Downloaded newer image' in pull_result.stdout"
retries: 5
delay: 30
until: pull_result.rc == 0
- name: Allow SMTP relay port from tools server
# ── UFW: allow tools Prometheus to scrape exporters on main ──────────────────
- name: Allow tools server to scrape node-exporter
community.general.ufw:
rule: allow
port: "9100"
proto: tcp
src: "{{ ip_tools }}"
- name: Allow tools server to scrape cAdvisor
community.general.ufw:
rule: allow
port: "8080"
proto: tcp
src: "{{ ip_tools }}"
- name: Remove legacy SMTP relay UFW rule (port 1025)
community.general.ufw:
rule: allow
port: "1025"
proto: tcp
src: "{{ ip_tools }}"
comment: "SMTP relay for tools-server Outline"
delete: true
failed_when: false
- name: Deploy Docker Compose stack
community.docker.docker_compose_v2:

View file

@ -26,6 +26,12 @@ networks:
monitoring:
driver: bridge
internal: true
outline-internal:
driver: bridge
internal: true
n8n-internal:
driver: bridge
internal: true
volumes:
forgejo_data:
forgejo_db_data:
@ -34,11 +40,10 @@ volumes:
plane_minio_data:
plane_media:
act_runner_data:
prometheus_data:
grafana_data:
loki_data:
crowdsec_data:
uptime_kuma_data:
outline_db_data:
outline_redis_data:
n8n_data:
services:
@ -372,52 +377,16 @@ services:
- backend
- runner-jobs
# ── Monitoring Stack ───────────────────────────────────────────────────────
prometheus:
image: {{ prometheus_image }}
container_name: prometheus
restart: unless-stopped
networks:
- monitoring
volumes:
- prometheus_data:/prometheus
- {{ services_root }}/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- {{ services_root }}/prometheus/rules:/etc/prometheus/rules:ro
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=30d"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
alertmanager:
image: {{ alertmanager_image }}
container_name: alertmanager
restart: unless-stopped
networks:
- monitoring
volumes:
- {{ services_root }}/prometheus/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
command:
- "--config.file=/etc/alertmanager/alertmanager.yml"
- "--storage.path=/alertmanager"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9093/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
# ── Monitoring exporters (metrics scraped by tools Prometheus over network) ──
# Ports exposed: tools server must have UFW rules allowing ip_main:9100/8080
node-exporter:
image: {{ node_exporter_image }}
container_name: node-exporter
restart: unless-stopped
networks:
- monitoring
ports:
- "9100:9100"
pid: host
volumes:
- /proc:/host/proc:ro
@ -434,6 +403,8 @@ services:
restart: unless-stopped
networks:
- monitoring
ports:
- "8080:8080"
privileged: true
devices:
- /dev/kmsg
@ -444,50 +415,7 @@ services:
- /var/lib/docker:/var/lib/docker:ro
- /dev/disk:/dev/disk:ro
grafana:
image: {{ grafana_image }}
container_name: grafana
restart: unless-stopped
security_opt:
- no-new-privileges:true
depends_on:
- prometheus
networks:
- backend
- monitoring
volumes:
- grafana_data:/var/lib/grafana
- {{ services_root }}/grafana/provisioning:/etc/grafana/provisioning:ro
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
- GF_USERS_ALLOW_SIGN_UP=false
- GF_SERVER_DOMAIN={{ domain_dashboard }}
- GF_SERVER_ROOT_URL=https://{{ domain_dashboard }}
- GF_AUTH_ANONYMOUS_ENABLED=false
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
# ── Logging Stack ──────────────────────────────────────────────────────────
loki:
image: {{ loki_image }}
container_name: loki
restart: unless-stopped
networks:
- monitoring
volumes:
- loki_data:/loki
- {{ services_root }}/loki/loki.yml:/etc/loki/local-config.yaml:ro
command: -config.file=/etc/loki/local-config.yaml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3100/ready"]
interval: 30s
timeout: 5s
retries: 3
# ── Logging (Promtail pushes to Loki on tools server) ─────────────────────
promtail:
image: {{ promtail_image }}
container_name: promtail
@ -523,75 +451,155 @@ services:
# ── Discord Bot ────────────────────────────────────────────────────────────
# Infrastructure management bot: /status /logs /restart /deploy /metrics /backup
# Image is built and pushed by the discord-bot repo CI/CD
discord-bot:
image: git.{{ domain_base }}/jack/discord-bot:latest
container_name: discord-bot
restart: unless-stopped
environment:
DISCORD_TOKEN: "${DISCORD_BOT_TOKEN}"
DISCORD_APP_ID: "{{ discord_bot_app_id }}"
FORGEJO_TOKEN: "${FORGEJO_RUNNER_TOKEN}"
FORGEJO_URL: "https://{{ domain_git }}"
FORGEJO_REPO: "jack/infra"
PROMETHEUS_URL: "http://prometheus:9090"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy # Discord API (internet)
- monitoring # Prometheus metrics
# NOTE: disabled until image is built & pushed to Forgejo registry
# discord-bot:
# image: git.{{ domain_base }}/jack/discord-bot:latest
# container_name: discord-bot
# restart: unless-stopped
# environment:
# DISCORD_TOKEN: "${DISCORD_BOT_TOKEN}"
# DISCORD_APP_ID: "{{ discord_bot_app_id }}"
# FORGEJO_TOKEN: "${FORGEJO_RUNNER_TOKEN}"
# FORGEJO_URL: "https://{{ domain_git }}"
# FORGEJO_REPO: "jack/infra"
# PROMETHEUS_URL: "http://{{ ip_tools }}:9090"
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
# networks:
# - proxy
# ── Walava Landing ─────────────────────────────────────────────────────────
# Landing page for walava.io — image built by walava-web repo CI/CD
walava-web:
image: git.{{ domain_base }}/jack/walava-web:latest
container_name: walava-web
restart: unless-stopped
networks:
- proxy
# NOTE: disabled until image is built & pushed to Forgejo registry
# walava-web:
# image: git.{{ domain_base }}/jack/walava-web:latest
# container_name: walava-web
# restart: unless-stopped
# networks:
# - proxy
# ── Uptime Kuma ────────────────────────────────────────────────────────────
# Мониторинг доступности сервисов + публичная статус-страница
# Доступен по адресу: https://{{ domain_status }}
uptime-kuma:
image: {{ uptime_kuma_image }}
container_name: uptime-kuma
# ── Outline wiki ────────────────────────────────────────────────────────────
outline:
image: {{ outline_image }}
container_name: outline
restart: unless-stopped
security_opt:
- no-new-privileges:true
env_file: .env.outline
networks:
- outline-internal
- backend
- proxy # needs internet access for Discord/Telegram notifications
volumes:
- uptime_kuma_data:/app/data
- proxy # needs outbound internet for SMTP (Resend) and S3 (Timeweb)
depends_on:
outline-db:
condition: service_healthy
outline-redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:3001/"]
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/_health"]
interval: 30s
timeout: 5s
retries: 3
# ── SMTP Relay ─────────────────────────────────────────────────────────────
# Forwards mail from tools-server (85.193.83.9) to Resend SMTP.
# tools-server has outbound SMTP blocked by the VPS provider.
# Listens on 85.193.83.9:1025 (UFW allows only from ip_tools).
smtp-relay:
image: tecnativa/postfix-relay
container_name: smtp-relay
restart: unless-stopped
ports:
- "{{ ip_tools }}:1025:25"
networks:
- proxy
environment:
- MAILNAME={{ domain_base }}
- MAIL_RELAY_HOST=smtp.resend.com
- MAIL_RELAY_PORT=587
- MAIL_RELAY_USER=resend
- MAIL_RELAY_PASS={{ resend_api_key }}
- MAIL_RELAY_MYHOSTNAME=mail.{{ domain_base }}
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
max-size: "10m"
max-file: "3"
outline-db:
image: {{ outline_db_image }}
container_name: outline-db
restart: unless-stopped
environment:
POSTGRES_DB: outline
POSTGRES_USER: outline
POSTGRES_PASSWORD: {{ outline_db_password }}
networks:
- outline-internal
volumes:
- outline_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U outline"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
outline-redis:
image: {{ outline_redis_image }}
container_name: outline-redis
restart: unless-stopped
networks:
- outline-internal
volumes:
- outline_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── n8n workflow automation ──────────────────────────────────────────────────
n8n:
image: {{ n8n_image }}
container_name: n8n
restart: unless-stopped
networks:
- n8n-internal
- backend
- proxy # needs outbound internet for workflow API calls
volumes:
- n8n_data:/home/node/.n8n
environment:
- N8N_HOST={{ domain_n8n }}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://{{ domain_n8n }}/
- N8N_ENCRYPTION_KEY={{ n8n_encryption_key }}
- N8N_USER_MANAGEMENT_JWT_SECRET={{ n8n_jwt_secret }}
- GENERIC_TIMEZONE=Europe/Moscow
- TZ=Europe/Moscow
- N8N_METRICS=false
- N8N_LOG_LEVEL=warn
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=336
- N8N_DIAGNOSTICS_ENABLED=false
- N8N_VERSION_NOTIFICATIONS_ENABLED=false
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5678/healthz"]
interval: 30s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── Outline MCP server ───────────────────────────────────────────────────────
# NOTE: image must be built & pushed to git.walava.io/jack/outline-mcp:latest first
# Disabled until Forgejo registry has the image
# outline-mcp:
# image: {{ outline_mcp_image }}
# container_name: outline-mcp
# restart: unless-stopped
# networks:
# - backend
# environment:
# - OUTLINE_URL=https://{{ domain_wiki }}
# - OUTLINE_API_KEY={{ outline_mcp_api_key }}
# - PORT=8765
# - HOST=0.0.0.0
# - LOG_LEVEL=INFO
# logging:
# driver: json-file
# options:
# max-size: "10m"
# max-file: "3"

View file

@ -0,0 +1,41 @@
# 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

View file

@ -7,7 +7,7 @@ positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
- url: http://{{ ip_tools }}:3100/loki/api/v1/push
scrape_configs:
- job_name: docker

View file

@ -89,7 +89,6 @@ http:
service: walava-landing
middlewares: [rate-limit-default]
# ── Cross-server: tools ({{ ip_tools }}) ─────────────────────────────────
wiki:
rule: "Host(`{{ domain_wiki }}`)"
entrypoints: [websecure]
@ -135,28 +134,27 @@ http:
grafana:
loadBalancer:
servers:
- url: "http://grafana:3000"
- url: "http://{{ ip_tools }}:3000"
uptime-kuma:
loadBalancer:
servers:
- url: "http://uptime-kuma:3001"
- url: "http://{{ ip_tools }}:3001"
walava-landing:
loadBalancer:
servers:
- url: "http://walava-web:80"
# ── Cross-server services ─────────────────────────────────────────────────
wiki:
loadBalancer:
servers:
- url: "http://{{ ip_tools }}:3000"
- url: "http://outline:3000"
n8n:
loadBalancer:
servers:
- url: "http://{{ ip_tools }}:5678"
- url: "http://n8n:5678"
middlewares:
# ── Security Headers (applied globally via entrypoint) ─────────────────

View file

@ -1,7 +1,11 @@
---
tools_root: /opt/tools
outline_image: "outlinewiki/outline:0.80.2"
outline_db_image: "postgres:15-alpine"
outline_redis_image: "redis:7-alpine"
n8n_image: "n8nio/n8n:1.89.2" # https://hub.docker.com/r/n8nio/n8n/tags
outline_mcp_image: "git.{{ domain_base }}/jack/outline-mcp:latest"
# Image versions (mirrors services role — keep in sync)
prometheus_image: "prom/prometheus:v3.4.0"
node_exporter_image: "prom/node-exporter:v1.9.1"
cadvisor_image: "gcr.io/cadvisor/cadvisor:v0.52.1"
grafana_image: "grafana/grafana:11.6.1"
alertmanager_image: "prom/alertmanager:v0.28.1"
loki_image: "grafana/loki:3.4.3"
uptime_kuma_image: "louislam/uptime-kuma:1"

View file

@ -0,0 +1,817 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "Prometheus as the datasource is obligatory",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "7.4.5"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": ""
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": 14282,
"graphTooltip": 0,
"id": null,
"iteration": 1617715580880,
"links": [],
"panels": [
{
"collapsed": false,
"datasource": "${DS_PROMETHEUS}",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 8,
"panels": [],
"title": "CPU",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 1
},
"hiddenSeries": false,
"id": 15,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(rate(container_cpu_usage_seconds_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name) *100",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "CPU Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:606",
"format": "percent",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:607",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"collapsed": false,
"datasource": "${DS_PROMETHEUS}",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 8
},
"id": 11,
"panels": [],
"title": "Memory",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"hiddenSeries": false,
"id": 9,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(container_memory_rss{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:606",
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:607",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"hiddenSeries": false,
"id": 14,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(container_memory_cache{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Cached",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:606",
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:607",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"collapsed": false,
"datasource": "${DS_PROMETHEUS}",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 17
},
"id": 2,
"panels": [],
"title": "Network",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 18
},
"hiddenSeries": false,
"id": 4,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"hideEmpty": false,
"hideZero": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"sideWidth": null,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum(rate(container_network_receive_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Received Network Traffic",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:674",
"format": "Bps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:675",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 18
},
"hiddenSeries": false,
"id": 6,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum(rate(container_network_transmit_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Sent Network Traffic",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:832",
"format": "Bps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:833",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"collapsed": false,
"datasource": "${DS_PROMETHEUS}",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 26
},
"id": 19,
"panels": [],
"title": "Misc",
"type": "row"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {
"align": null,
"filterable": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "id"
},
"properties": [
{
"id": "custom.width",
"value": 260
}
]
},
{
"matcher": {
"id": "byName",
"options": "Running"
},
"properties": [
{
"id": "unit",
"value": "d"
},
{
"id": "decimals",
"value": 1
},
{
"id": "custom.displayMode",
"value": "color-text"
},
{
"id": "color",
"value": {
"fixedColor": "dark-green",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 27
},
"id": 17,
"options": {
"showHeader": true,
"sortBy": []
},
"pluginVersion": "7.4.5",
"targets": [
{
"expr": "(time() - container_start_time_seconds{instance=~\"$host\",name=~\"$container\",name=~\".+\"})/86400",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Containers Info",
"transformations": [
{
"id": "filterFieldsByName",
"options": {
"include": {
"names": [
"container_label_com_docker_compose_project",
"container_label_com_docker_compose_project_working_dir",
"image",
"instance",
"name",
"Value",
"container_label_com_docker_compose_service"
]
}
}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {},
"renameByName": {
"Value": "Running",
"container_label_com_docker_compose_project": "Label",
"container_label_com_docker_compose_project_working_dir": "Working dir",
"container_label_com_docker_compose_service": "Service",
"image": "Registry Image",
"instance": "Instance",
"name": "Name"
}
}
}
],
"type": "table"
}
],
"schemaVersion": 27,
"style": "dark",
"tags": [
"cadvisor",
"docker"
],
"templating": {
"list": [
{
"allValue": ".*",
"current": {},
"datasource": "${DS_PROMETHEUS}",
"definition": "label_values({__name__=~\"container.*\"},instance)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": "Host",
"multi": false,
"name": "host",
"options": [],
"query": {
"query": "label_values({__name__=~\"container.*\"},instance)",
"refId": "Prometheus-host-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {},
"datasource": "${DS_PROMETHEUS}",
"definition": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": "Container",
"multi": false,
"name": "container",
"options": [],
"query": {
"query": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
"refId": "Prometheus-container-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Cadvisor exporter",
"uid": "pMEd7m0Mz",
"version": 1,
"description": "Simple exporter for cadvisor only"
}

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,29 @@
group: "{{ deploy_group }}"
mode: "0750"
# ── Deploy configs and start stack ────────────────────────────────────────────
- name: Create tools subdirectories
ansible.builtin.file:
path: "{{ tools_root }}/{{ item }}"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0755"
loop:
- prometheus
- prometheus/rules
- grafana/provisioning/datasources
- grafana/provisioning/dashboards
- grafana/provisioning/dashboards/json
- loki
- name: Deploy .env file
ansible.builtin.template:
src: env.j2
dest: "{{ tools_root }}/.env"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
- name: Deploy docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
@ -16,27 +38,130 @@
group: "{{ deploy_group }}"
mode: "0640"
- name: Deploy .env
- name: Deploy Prometheus config
ansible.builtin.template:
src: env.j2
dest: "{{ tools_root }}/.env"
src: prometheus/prometheus.yml.j2
dest: "{{ tools_root }}/prometheus/prometheus.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
mode: "0644"
- name: Pull images
community.docker.docker_image:
name: "{{ item }}"
source: pull
- name: Deploy Prometheus alert rules
ansible.builtin.template:
src: prometheus/rules/alerts.yml.j2
dest: "{{ tools_root }}/prometheus/rules/alerts.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy AlertManager config
ansible.builtin.template:
src: prometheus/alertmanager.yml.j2
dest: "{{ tools_root }}/prometheus/alertmanager.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy Loki config
ansible.builtin.template:
src: loki/loki.yml.j2
dest: "{{ tools_root }}/loki/loki.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy Grafana Prometheus datasource
ansible.builtin.template:
src: grafana/provisioning/datasources/prometheus.yml.j2
dest: "{{ tools_root }}/grafana/provisioning/datasources/prometheus.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy Grafana Loki datasource
ansible.builtin.template:
src: grafana/provisioning/datasources/loki.yml.j2
dest: "{{ tools_root }}/grafana/provisioning/datasources/loki.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy Grafana dashboard provisioning config
ansible.builtin.template:
src: grafana/provisioning/dashboards/dashboards.yml.j2
dest: "{{ tools_root }}/grafana/provisioning/dashboards/dashboards.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy Node Exporter Full dashboard JSON
ansible.builtin.copy:
src: grafana/dashboards/node-exporter-full.json
dest: "{{ tools_root }}/grafana/provisioning/dashboards/json/node-exporter-full.json"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Deploy cAdvisor dashboard JSON
ansible.builtin.copy:
src: grafana/dashboards/cadvisor.json
dest: "{{ tools_root }}/grafana/provisioning/dashboards/json/cadvisor.json"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
- name: Pull monitoring images
ansible.builtin.command: docker pull {{ item }}
loop:
- "{{ outline_image }}"
- "{{ outline_db_image }}"
- "{{ outline_redis_image }}"
- "{{ n8n_image }}"
- "{{ prometheus_image }}"
- "{{ alertmanager_image }}"
- "{{ node_exporter_image }}"
- "{{ cadvisor_image }}"
- "{{ grafana_image }}"
- "{{ loki_image }}"
- "{{ uptime_kuma_image }}"
register: pull_result
changed_when: "'Status: Downloaded newer image' in pull_result.stdout"
retries: 5
delay: 30
until: pull_result.rc == 0
# ── UFW: allow main server to reach monitoring services ───────────────────────
- name: Allow main server to reach Loki (Promtail log push)
community.general.ufw:
rule: allow
port: "3100"
proto: tcp
src: "{{ ip_main }}"
- name: Allow main server to reach Prometheus (discord-bot metrics)
community.general.ufw:
rule: allow
port: "9090"
proto: tcp
src: "{{ ip_main }}"
- name: Allow main Traefik to reach Grafana
community.general.ufw:
rule: allow
port: "3000"
proto: tcp
src: "{{ ip_main }}"
- name: Allow main Traefik to reach Uptime Kuma
community.general.ufw:
rule: allow
port: "3001"
proto: tcp
src: "{{ ip_main }}"
- name: Start tools stack
community.docker.docker_compose_v2:
project_src: "{{ tools_root }}"
state: present
pull: missing
pull: never
remove_orphans: true
retries: 3
delay: 15
register: compose_result
until: compose_result is succeeded

View file

@ -1,150 +1,157 @@
# Tools stack — generated by Ansible
# Do not edit manually; re-run ansible-playbook playbooks/tools.yml
# Monitoring: Prometheus, Grafana, Loki, AlertManager, Uptime Kuma, node-exporter, cAdvisor
networks:
# front — non-internal: needed for Docker port binding to work (expose ports to host)
# Docker does not create DNAT rules for containers only on internal networks
front:
monitoring:
driver: bridge
outline-internal:
driver: bridge
internal: true
n8n-internal:
driver: bridge
internal: true
volumes:
outline_db_data:
outline_redis_data:
n8n_data:
prometheus_data:
grafana_data:
loki_data:
uptime_kuma_data:
services:
# ── Outline wiki ────────────────────────────────────────────────────────────
outline:
image: {{ outline_image }}
container_name: outline
# ── Prometheus ─────────────────────────────────────────────────────────────
prometheus:
image: {{ prometheus_image }}
container_name: prometheus
restart: unless-stopped
env_file: .env
networks:
- outline-internal
- front # needed for host port binding
- monitoring
ports:
# Exposed only to main Traefik (access controlled by UFW)
- "{{ ip_tools }}:3000:3000"
- "127.0.0.1:9090:9090" # exposed to main via UFW rule for discord-bot
volumes:
- prometheus_data:/prometheus
- {{ tools_root }}/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- {{ tools_root }}/prometheus/rules:/etc/prometheus/rules:ro
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=30d"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
alertmanager:
image: {{ alertmanager_image }}
container_name: alertmanager
restart: unless-stopped
networks:
- monitoring
volumes:
- {{ tools_root }}/prometheus/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
command:
- "--config.file=/etc/alertmanager/alertmanager.yml"
- "--storage.path=/alertmanager"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9093/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
# ── Exporters (monitor the tools host itself) ───────────────────────────────
node-exporter:
image: {{ node_exporter_image }}
container_name: node-exporter
restart: unless-stopped
networks:
- monitoring
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- "--path.procfs=/host/proc"
- "--path.sysfs=/host/sys"
- "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
cadvisor:
image: {{ cadvisor_image }}
container_name: cadvisor
restart: unless-stopped
networks:
- monitoring
privileged: true
devices:
- /dev/kmsg
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
- /dev/disk:/dev/disk:ro
# ── Grafana ─────────────────────────────────────────────────────────────────
grafana:
image: {{ grafana_image }}
container_name: grafana
restart: unless-stopped
security_opt:
- no-new-privileges:true
depends_on:
outline-db:
condition: service_healthy
outline-redis:
condition: service_healthy
- prometheus
networks:
- monitoring
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- {{ tools_root }}/grafana/provisioning:/etc/grafana/provisioning:ro
env_file: .env
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_USERS_ALLOW_SIGN_UP=false
- GF_SERVER_DOMAIN={{ domain_dashboard }}
- GF_SERVER_ROOT_URL=https://{{ domain_dashboard }}
- GF_AUTH_ANONYMOUS_ENABLED=false
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/_health"]
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
outline-db:
image: {{ outline_db_image }}
container_name: outline-db
restart: unless-stopped
environment:
POSTGRES_DB: outline
POSTGRES_USER: outline
POSTGRES_PASSWORD: ${OUTLINE_DB_PASSWORD}
networks:
- outline-internal
volumes:
- outline_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U outline"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
outline-redis:
image: {{ outline_redis_image }}
container_name: outline-redis
# ── Loki ────────────────────────────────────────────────────────────────────
loki:
image: {{ loki_image }}
container_name: loki
restart: unless-stopped
networks:
- outline-internal
volumes:
- outline_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── Outline MCP server ───────────────────────────────────────────────────────
# MCP server exposing Outline wiki to Claude/AI clients (port 8765, internal only)
outline-mcp:
image: {{ outline_mcp_image }}
container_name: outline-mcp
restart: unless-stopped
networks:
- front # needed for host port binding
- monitoring
ports:
- "127.0.0.1:8765:8765"
environment:
- OUTLINE_URL=https://{{ domain_wiki }}
- OUTLINE_API_KEY={{ outline_mcp_api_key }}
- PORT=8765
- HOST=0.0.0.0
- LOG_LEVEL=INFO
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── n8n workflow automation ──────────────────────────────────────────────────
n8n:
image: {{ n8n_image }}
container_name: n8n
restart: unless-stopped
networks:
- n8n-internal
- front # needed for host port binding
ports:
# Exposed only to main Traefik (access controlled by UFW)
- "{{ ip_tools }}:5678:5678"
- "3100:3100" # exposed to main for Promtail log ingestion
volumes:
- n8n_data:/home/node/.n8n
environment:
- N8N_HOST={{ domain_n8n }}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://{{ domain_n8n }}/
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_USER_MANAGEMENT_JWT_SECRET=${N8N_JWT_SECRET}
- GENERIC_TIMEZONE=Europe/Moscow
- TZ=Europe/Moscow
- N8N_METRICS=false
- N8N_LOG_LEVEL=warn
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=336
- loki_data:/loki
- {{ tools_root }}/loki/loki.yml:/etc/loki/local-config.yaml:ro
command: -config.file=/etc/loki/local-config.yaml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5678/healthz"]
test: ["CMD", "wget", "-qO-", "http://localhost:3100/ready"]
interval: 30s
timeout: 5s
retries: 3
# ── Uptime Kuma ─────────────────────────────────────────────────────────────
uptime-kuma:
image: {{ uptime_kuma_image }}
container_name: uptime-kuma
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- monitoring
ports:
- "3001:3001"
volumes:
- uptime_kuma_data:/app/data
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:3001/"]
interval: 30s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

View file

@ -1,47 +1,2 @@
# 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=visual-outline
AWS_S3_UPLOAD_BUCKET_URL=https://s3.timeweb.cloud
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
FILE_STORAGE=s3
# Auth
AUTH_PROVIDERS=email
# SMTP via relay on main server (tools-server has outbound SMTP blocked)
SMTP_HOST={{ ip_main }}
SMTP_PORT=1025
SMTP_FROM_EMAIL=noreply@{{ domain_base }}
SMTP_FROM_NAME=Visual Wiki
SMTP_SECURE=false
# Outline DB password (used in docker-compose)
OUTLINE_DB_PASSWORD={{ outline_db_password }}
# Optional
DEFAULT_LANGUAGE=en_US
RATE_LIMITER_ENABLED=true
ENABLE_UPDATES=false
# n8n secrets
N8N_ENCRYPTION_KEY={{ n8n_encryption_key }}
N8N_JWT_SECRET={{ n8n_jwt_secret }}
# Generated by Ansible — do not edit manually
GF_SECURITY_ADMIN_PASSWORD={{ grafana_admin_password }}

View file

@ -0,0 +1,13 @@
# 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

View file

@ -0,0 +1,10 @@
# Generated by Ansible — do not edit manually
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: false
editable: false

View file

@ -0,0 +1,10 @@
# Generated by Ansible — do not edit manually
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false

View file

@ -0,0 +1,36 @@
# Generated by Ansible — do not edit manually
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 30d
compactor:
working_directory: /loki/retention
delete_request_store: filesystem
retention_enabled: true

View file

@ -0,0 +1,38 @@
# Generated by Ansible — do not edit manually
global:
resolve_timeout: 5m
route:
group_by: [alertname, severity]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: all
receivers:
- name: all
telegram_configs:
- bot_token: "{{ alertmanager_telegram_token }}"
chat_id: {{ alertmanager_telegram_chat_id }}
message: |
{{ '{{' }} range .Alerts {{ '}}' }}
{{ '{{' }} if eq .Status "firing" {{ '}}' }}🔴{{ '{{' }} else {{ '}}' }}🟢{{ '{{' }} end {{ '}}' }} *{{ '{{' }} .Labels.alertname {{ '}}' }}*
{{ '{{' }} .Annotations.summary {{ '}}' }}
{{ '{{' }} .Annotations.description {{ '}}' }}
{{ '{{' }} end {{ '}}' }}
parse_mode: Markdown
discord_configs:
- webhook_url: "{{ discord_webhook_alerts }}"
title: >-
{{ '{{' }} if eq (index .Alerts 0).Status "firing" {{ '}}' }}🔴 Alert{{ '{{' }} else {{ '}}' }}🟢 Resolved{{ '{{' }} end {{ '}}' }}
message: |
{{ '{{' }} range .Alerts {{ '}}' }}
**{{ '{{' }} .Labels.alertname {{ '}}' }}**
{{ '{{' }} .Annotations.summary {{ '}}' }}
{{ '{{' }} .Annotations.description {{ '}}' }}
{{ '{{' }} end {{ '}}' }}
inhibit_rules:
- source_matchers: [severity="critical"]
target_matchers: [severity="warning"]
equal: [alertname]

View file

@ -0,0 +1,49 @@
# Generated by Ansible — do not edit manually
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
instance: "{{ domain_base }}"
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
# tools server metrics
- job_name: node-exporter-tools
static_configs:
- targets: ["node-exporter:9100"]
labels:
host: tools
- job_name: cadvisor-tools
static_configs:
- targets: ["cadvisor:8080"]
labels:
host: tools
- job_name: alertmanager
static_configs:
- targets: ["alertmanager:9093"]
# main server metrics (scraped over network)
- job_name: node-exporter-main
static_configs:
- targets: ["{{ ip_main }}:9100"]
labels:
host: main
- job_name: cadvisor-main
static_configs:
- targets: ["{{ ip_main }}:8080"]
labels:
host: main

View file

@ -0,0 +1,86 @@
# Generated by Ansible — do not edit manually
groups:
- name: host
rules:
- alert: HighCPULoad
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: warning
annotations:
summary: "Высокая нагрузка CPU ({{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%)"
description: "CPU загружен более 85% на протяжении 5 минут."
- alert: HighMemoryUsage
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "Высокое использование RAM ({{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%)"
description: "Использование RAM превысило 85%."
- alert: CriticalMemoryUsage
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95
for: 2m
labels:
severity: critical
annotations:
summary: "Критическое использование RAM ({{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%)"
description: "RAM заполнена на 95%+. Возможны OOM kills."
- alert: DiskSpaceWarning
expr: (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|aufs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|aufs"})) * 100 > 75
for: 5m
labels:
severity: warning
annotations:
summary: "Заканчивается место на диске ({{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%)"
description: "Диск {{ '{{' }} $labels.mountpoint {{ '}}' }} занят на {{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%."
- alert: DiskSpaceCritical
expr: (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|aufs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|aufs"})) * 100 > 90
for: 2m
labels:
severity: critical
annotations:
summary: "Критически мало места на диске ({{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%)"
description: "Диск {{ '{{' }} $labels.mountpoint {{ '}}' }} занят на {{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%."
- alert: SwapUsageHigh
expr: (1 - (node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes)) * 100 > 50
for: 5m
labels:
severity: warning
annotations:
summary: "Высокое использование swap ({{ '{{' }} $value | printf \"%.0f\" {{ '}}' }}%)"
description: "Swap используется более чем на 50% — RAM под давлением."
- name: containers
rules:
- alert: ContainerDown
expr: absent(container_last_seen{name=~".+"}) or time() - container_last_seen{name=~".+"} > 60
for: 2m
labels:
severity: critical
annotations:
summary: "Контейнер {{ '{{' }} $labels.name {{ '}}' }} недоступен"
description: "Контейнер не отвечает более 2 минут."
- alert: ContainerHighMemory
expr: (container_memory_usage_bytes{name=~".+"} / (container_spec_memory_limit_bytes{name=~".+"} > 0)) * 100 > 90
for: 5m
labels:
severity: warning
annotations:
summary: "Контейнер {{ '{{' }} $labels.name {{ '}}' }} использует 90%+ памяти"
description: "Контейнер близок к mem_limit — возможен OOM kill."
- alert: ContainerRestarting
expr: increase(container_last_seen{name=~".+"}[5m]) == 0 and rate(container_cpu_usage_seconds_total{name=~".+"}[5m]) == 0
for: 0m
labels:
severity: warning
annotations:
summary: "Контейнер {{ '{{' }} $labels.name {{ '}}' }} возможно перезапускается"
description: "Контейнер не активен — проверьте docker ps."

7
terraform/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# Секреты и state — никогда не коммитить
terraform.tfvars
*.tfstate
*.tfstate.backup
.terraform/
.terraform.lock.hcl
crash.log

9
terraform/outputs.tf Normal file
View file

@ -0,0 +1,9 @@
output "main_ip" {
description = "IP main-сервера"
value = "87.249.49.32"
}
output "tools_ip" {
description = "IP tools-сервера"
value = "85.193.83.9"
}

14
terraform/providers.tf Normal file
View file

@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.5"
required_providers {
twc = {
source = "timeweb-cloud/timeweb-cloud"
version = "~> 1.0"
}
}
}
provider "twc" {
token = var.timeweb_token
}

25
terraform/servers.tf Normal file
View file

@ -0,0 +1,25 @@
# Серверы
resource "twc_server" "main" {
name = "main"
comment = "Основной: Traefik, Forgejo, Plane, Vaultwarden, Outline, n8n, CI/CD"
os_id = 99
preset_id = 2453
lifecycle {
prevent_destroy = true
ignore_changes = [is_root_password_required]
}
}
resource "twc_server" "tools" {
name = "tools"
comment = "Мониторинг: Grafana, Prometheus, Loki, AlertManager, Uptime Kuma"
os_id = 99
preset_id = 2449
lifecycle {
prevent_destroy = true
ignore_changes = [is_root_password_required]
}
}

27
terraform/storage.tf Normal file
View file

@ -0,0 +1,27 @@
# S3 Object Storage
# Импортировано через:
# terraform import twc_s3_bucket.backup 481333
# terraform import twc_s3_bucket.outline 481335
resource "twc_s3_bucket" "backup" {
name = "walava-backup"
type = "private"
preset_id = 2669
lifecycle {
# name/type write-once поля, нельзя менять после создания
ignore_changes = [name, type]
prevent_destroy = true
}
}
resource "twc_s3_bucket" "outline" {
name = "walava-outline"
type = "private"
preset_id = 2669
lifecycle {
ignore_changes = [name, type]
prevent_destroy = true
}
}

View file

@ -0,0 +1,2 @@
# Скопируй в terraform.tfvars (он в .gitignore)
timeweb_token = "your-api-token"

5
terraform/variables.tf Normal file
View file

@ -0,0 +1,5 @@
variable "timeweb_token" {
description = "Timeweb Cloud API token"
type = string
sensitive = true
}