chore: initial infrastructure setup with Syncthing, Git and documentation

Set up three-tier synchronization: Syncthing (real-time), GitHub (version control), rsync (disaster recovery). Includes complete documentation for future Claude sessions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
SamoilenkoVadym 2025-11-05 16:41:12 +00:00
commit a987d45fbc
592 changed files with 79469 additions and 0 deletions

View file

@ -0,0 +1,404 @@
# Структура проекта OVHserver
## Общая информация
**Сервер:** ubuntu@51.89.231.46
**Локальная копия:** /Volumes/SSD/Aimpress_Cloud_Prod/
**GitHub:** git@github.com:SamoilenkoVadym/OVHserver.git
---
## Основная структура
```
/Volumes/SSD/Aimpress_Cloud_Prod/
├── opt/ # Основные проекты и сервисы
├── data/ # Данные приложений
├── home/ # Пользовательские файлы ubuntu
├── .git/ # Git репозиторий
├── .gitignore # Исключения для Git
├── .claude/ # Документация для Claude
│ ├── workflow.md # Рабочий процесс
│ └── project-structure.md # Этот файл
└── README.md # Общее описание
```
---
## Детальная структура /opt
### 00-infrastructure/ — Инфраструктурные сервисы
Базовые компоненты инфраструктуры, необходимые для работы других сервисов.
```
00-infrastructure/
├── postgres/ # PostgreSQL база данных
│ ├── docker-compose.yml
│ └── data/ # Данные БД
├── redis/ # Redis кэш
├── rabbitmq/ # RabbitMQ message broker
├── traefik/ # Traefik reverse proxy
│ ├── docker-compose.yml
│ ├── traefik.yml # Основной конфиг
│ └── acme.json # SSL сертификаты
├── vault/ # HashiCorp Vault (секреты)
│ ├── docker-compose.yml
│ └── data/ # Хранилище секретов
├── blackbox/ # Blackbox Exporter (мониторинг)
└── loki/ # Loki (агрегация логов)
```
**Назначение:**
- PostgreSQL: Общая база данных для всех сервисов
- Redis: Кэширование и очереди задач
- RabbitMQ: Message broker для асинхронной обработки
- Traefik: Reverse proxy с автоматическим SSL
- Vault: Управление секретами и credentials
- Loki: Сбор и хранение логов
- Blackbox: Мониторинг доступности эндпоинтов
---
### 01-security/ — Безопасность и аутентификация
Сервисы для управления доступом и безопасностью.
```
01-security/
├── authentik/ # Authentik SSO (Single Sign-On)
│ ├── docker-compose.yml
│ ├── custom-templates/
│ └── media/ # Статические файлы
└── vaultwarden/ # Vaultwarden (менеджер паролей)
├── docker-compose.yml
└── data/ # Хранилище паролей
```
**Назначение:**
- Authentik: Единая система аутентификации (SSO) для всех сервисов
- Vaultwarden: Корпоративный менеджер паролей (Bitwarden compatible)
---
### 02-core/ — Ядро системы
Основные рабочие сервисы, критичные для бизнеса.
```
02-core/
├── n8n-shared/ # n8n (workflow automation) - общий
│ ├── docker-compose.yml
│ └── .n8n/ # Workflows и credentials
├── n8n-vip/ # n8n VIP instance (отдельный)
├── evolution-api/ # Evolution API (WhatsApp Business API)
│ ├── docker-compose.yml
│ ├── evolution-instances/ # Инстансы WhatsApp
│ └── evolution-store/ # Хранилище данных
└── supabase/ # Supabase (Backend-as-a-Service)
├── docker-compose.yml
└── volumes/ # Данные Supabase
```
**Назначение:**
- n8n: Автоматизация бизнес-процессов (workflow automation)
- n8n-shared: Для общих workflow
- n8n-vip: Для VIP клиентов или критичных задач
- Evolution API: WhatsApp Business API для мессенджер-интеграций
- Supabase: Backend с БД, Auth, Storage для приложений
---
### 03-business/ — Бизнес-приложения
Приложения для работы бизнеса и команды.
```
03-business/
├── odoo/ # Odoo ERP
│ ├── docker-compose.yml
│ ├── addons/ # Кастомные модули
│ ├── config/ # Конфигурация
│ └── filestore/ # Файлы и attachments
├── outline/ # Outline (корпоративная wiki)
│ ├── docker-compose.yml
│ └── data/ # Документы
├── documenso/ # Documenso (электронная подпись)
│ ├── docker-compose.yml
│ └── uploads/ # Загруженные документы
└── wikijs/ # Wiki.js (документация)
├── docker-compose.yml
└── data/ # Wiki контент
```
**Назначение:**
- Odoo: ERP система для управления бизнесом
- CRM, Sales, Inventory, Accounting, HR
- Кастомные модули в /addons
- Outline: Корпоративная база знаний (wiki)
- Documenso: Система электронного документооборота и подписи
- Wiki.js: Техническая документация
---
### 04-tools/ — Утилиты и мониторинг
Инструменты для управления и мониторинга инфраструктуры.
```
04-tools/
├── portainer/ # Portainer (Docker UI)
│ └── docker-compose.yml
├── grafana/ # Grafana (визуализация метрик)
│ ├── docker-compose.yml
│ └── dashboards/ # Дашборды
├── monitoring/ # Prometheus + Node Exporter
│ ├── prometheus/
│ ├── alertmanager/
│ └── node-exporter/
├── uptime-kuma/ # Uptime Kuma (мониторинг доступности)
│ └── docker-compose.yml
├── watchtower/ # Watchtower (авто-обновление Docker)
└── sftp/ # SFTP сервер
```
**Назначение:**
- Portainer: Web UI для управления Docker контейнерами
- Grafana: Дашборды для визуализации метрик
- Monitoring: Сбор метрик (CPU, RAM, disk, etc.)
- Uptime Kuma: Мониторинг доступности сервисов
- Watchtower: Автоматическое обновление Docker образов
- SFTP: Файловый сервер для обмена файлами
---
### 05-backups/ — Бэкапы и скрипты
Резервные копии, логи и административные скрипты.
⚠️ **Исключено из Git** (большие файлы, бинарные данные)
```
05-backups/
├── scripts/ # Bash скрипты для обслуживания
│ ├── backup-*.sh
│ ├── restore-*.sh
│ └── maintenance-*.sh
├── reports/ # Отчеты о бэкапах
├── logs/ # Логи бэкапов
├── data/ # Временные данные
├── config-versions/ # Версии конфигураций
├── credentials/ # Экспортированные credentials
├── restic/ # Restic бэкапы
├── docs/ # Документация по скриптам
├── SCRIPTS-REGISTRY.md # Реестр всех скриптов
└── README-SCRIPTS.md # Документация скриптов
```
**Назначение:**
- Автоматические бэкапы всех сервисов
- Скрипты для обслуживания и восстановления
- Логи всех операций
- Версионирование конфигураций
---
### 06-webflow/ — Webflow интеграция
Landing pages и интеграция с Webflow.
```
06-webflow/
└── landing/ # Landing page
├── index.html
├── css/
└── js/
```
**Назначение:**
- Публичные landing pages
- Интеграция с Webflow CMS
---
### Другие файлы в /opt
```
/opt/
├── infrastructure-docs/ # Документация инфраструктуры
│ ├── setup-guides/
│ ├── troubleshooting/
│ └── architecture/
├── postiz-config/ # Конфигурация Postiz
├── containerd/ # Containerd данные (системные)
└── fix_odoo_pass.py # Утилита для сброса пароля Odoo
```
---
## Структура /data
```
/data/
├── docker/ # Docker данные (volumes, overlay2)
├── app-data/ # Данные приложений
└── temp/ # Временные файлы
```
**Назначение:** Хранилище данных Docker контейнеров и приложений.
---
## Структура /home/ubuntu
```
/home/ubuntu/
├── .ssh/ # SSH ключи
├── .bashrc # Bash конфигурация
├── scripts/ # Персональные скрипты
└── .local/ # Локальные данные пользователя
```
**Назначение:** Домашняя директория пользователя ubuntu.
---
## Технологический стек
### Оркестрация
- **Docker** + **Docker Compose** - контейнеризация всех сервисов
### Reverse Proxy & SSL
- **Traefik** - автоматический SSL (Let's Encrypt), маршрутизация
### Базы данных
- **PostgreSQL** - основная RDBMS
- **Redis** - кэш и очереди
- **RabbitMQ** - message broker
### Безопасность
- **HashiCorp Vault** - управление секретами
- **Authentik** - SSO и Identity Provider
- **Vaultwarden** - менеджер паролей
### Мониторинг
- **Prometheus** - сбор метрик
- **Grafana** - визуализация
- **Loki** - агрегация логов
- **Uptime Kuma** - проверка доступности
- **Blackbox Exporter** - HTTP/TCP мониторинг
### Бизнес-приложения
- **Odoo** - ERP
- **n8n** - workflow automation
- **Evolution API** - WhatsApp Business API
- **Supabase** - BaaS
- **Outline** - wiki
- **Documenso** - e-signature
### DevOps
- **Portainer** - Docker UI
- **Watchtower** - авто-обновления
- **Restic** - инкрементальные бэкапы
---
## Сетевая архитектура
```
Internet
Traefik (51.89.231.46:80/443)
├→ authentik.domain.com → Authentik
├→ n8n.domain.com → n8n
├→ odoo.domain.com → Odoo
├→ outline.domain.com → Outline
├→ vault.domain.com → Vault
├→ portainer.domain.com → Portainer
├→ grafana.domain.com → Grafana
└→ etc.
```
Все сервисы доступны через Traefik с автоматическим SSL.
---
## Важные замечания
### Права доступа
- Большинство сервисов запущены от пользователя `ubuntu`
- Некоторые системные файлы (vault/data, containerd) принадлежат root
- Syncthing может не иметь доступа к root-файлам (это нормально)
### Бэкапы
- Автоматические бэкапы в `/opt/05-backups/`
- Restic для инкрементальных бэкапов
- Бэкапы НЕ коммитятся в Git (большие файлы)
### Секреты
- Хранятся в HashiCorp Vault
- .env файлы исключены из Git
- Credentials в `/opt/05-backups/credentials/` (не в Git)
### Docker
- Все сервисы в Docker контейнерах
- docker-compose.yml для каждого сервиса
- Volumes в `/data/` и локальных директориях
---
## Зависимости между сервисами
```
Traefik (обязательно для всех)
PostgreSQL (используется: Odoo, Outline, Authentik, n8n, Supabase)
Redis (используется: Odoo, n8n, Outline)
Vault (хранит секреты для всех сервисов)
Authentik (SSO для: Outline, Grafana, Portainer, и др.)
```
**Порядок запуска:**
1. Traefik
2. PostgreSQL, Redis, RabbitMQ
3. Vault
4. Authentik
5. Остальные сервисы
---
## Полезные пути
### Конфигурации
- `/opt/00-infrastructure/traefik/traefik.yml` - основной конфиг Traefik
- `/opt/*/docker-compose.yml` - конфигурации всех сервисов
### Логи
- `/opt/05-backups/logs/` - логи бэкапов
- `docker logs <container>` - логи контейнеров
### Данные
- `/data/` - Docker volumes
- `/opt/*/data/` - данные конкретных сервисов
### Скрипты
- `/opt/05-backups/scripts/` - административные скрипты
- `/opt/05-backups/SCRIPTS-REGISTRY.md` - список всех скриптов
---
## Как найти нужный сервис
1. **Инфраструктура?**`00-infrastructure/`
2. **Аутентификация?**`01-security/`
3. **Автоматизация?**`02-core/n8n-*`
4. **CRM/ERP?**`03-business/odoo/`
5. **Документация?**`03-business/outline/` или `03-business/wikijs/`
6. **Мониторинг?**`04-tools/grafana/` или `04-tools/monitoring/`
7. **Управление Docker?**`04-tools/portainer/`
8. **Бэкапы?**`05-backups/`
---
## Обновления структуры
Этот документ должен обновляться при добавлении новых сервисов или изменении структуры проекта.
**Последнее обновление:** 2025-11-05

237
.claude/workflow.md Normal file
View file

@ -0,0 +1,237 @@
# Claude Code Workflow для OVHserver
## Общая архитектура проекта
Этот проект использует **трёхуровневую систему синхронизации и бэкапа**:
### 1. Syncthing (Синхронизация в реальном времени)
- **Назначение:** Мгновенная двусторонняя синхронизация между Mac и сервером
- **Режим:** Send & Receive (bidirectional)
- **Синхронизируемые папки:**
- `/opt` (сервер) ↔ `/Volumes/SSD/Aimpress_Cloud_Prod/opt` (Mac)
- `/data` (сервер) ↔ `/Volumes/SSD/Aimpress_Cloud_Prod/data` (Mac)
- `/home/ubuntu` (сервер) ↔ `/Volumes/SSD/Aimpress_Cloud_Prod/home` (Mac)
- **Автозапуск:** Да, через systemd (сервер) и brew services (Mac)
### 2. GitHub (Версионный контроль)
- **Репозиторий:** git@github.com:SamoilenkoVadym/OVHserver.git
- **Что хранится:** Весь код из `/opt`, конфигурации, документация
- **Исключения:** См. `.gitignore` - логи, секреты, бэкапы, зависимости
### 3. rsync (Полный системный бэкап)
- **Назначение:** Полная копия системы для восстановления
- **Запуск:** По требованию через скрипт
- **Место хранения:** `/Volumes/SSD/Aimpress_Cloud_Prod/system-backup/`
---
## Как я (Claude) работаю с проектом
### ⚠️ ВАЖНО: Пользователь работает ТОЛЬКО через меня
- Все изменения в проект вносятся через меня
- Я работаю с **локальными файлами** на Mac
- После моих изменений Syncthing автоматически синхронизирует их на сервер (10-30 сек)
### Процесс работы:
```
1. Пользователь: "Claude, добавь функцию X"
2. Я читаю локальные файлы из /Volumes/SSD/Aimpress_Cloud_Prod/opt
3. Я вношу изменения в файлы
4. Syncthing: Автоматически синхронизирует на сервер (10-30 сек)
5. Я делаю git commit с осмысленным сообщением:
"feat: add authentication module with JWT"
6. Git hook: Автоматически выполняет git push в GitHub
```
### Когда делать git commit:
✅ **ВСЕГДА делаю commit после каждого значимого изменения:**
- Добавлена новая функция → `feat: <description>`
- Исправлен баг`fix: <description>`
- Рефакторинг кода → `refactor: <description>`
- Обновлена документация → `docs: <description>`
- Изменена конфигурация → `chore: update <config name>`
- Улучшена производительность → `perf: <description>`
✅ **Формат сообщений коммита:**
```
<type>: <short description>
<detailed explanation if needed>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
```
❌ **НЕ коммичу:**
- Временные файлы (они в .gitignore)
- Логи (они в .gitignore)
- Секреты типа .env, .key (они в .gitignore + pre-commit hook блокирует)
- Незавершенные изменения (только работающий код)
---
## Git Hooks
### pre-commit
- **Проверяет:** Наличие секретов в коммите (.env, .key, .pem файлы)
- **Действие:** Блокирует коммит если найдены секреты
### post-commit
- **Действие:** Автоматически пушит каждый коммит в GitHub
- **Ветка:** main или master
---
## Структура проекта
```
/Volumes/SSD/Aimpress_Cloud_Prod/
├── opt/ # Основные проекты и приложения
│ ├── 00-infrastructure/ # Инфраструктура (vault, nginx, etc.)
│ ├── 01-security/ # Безопасность
│ ├── 02-core/ # Ядро системы
│ ├── 03-business/ # Бизнес-логика
│ ├── 04-tools/ # Утилиты и инструменты
│ ├── 05-backups/ # Бэкапы (исключены из Git)
│ └── 06-webflow/ # Webflow интеграция
├── data/ # Данные приложений
├── home/ # Пользовательские файлы ubuntu
├── .git/ # Git репозиторий
├── .gitignore # Исключения для Git
├── .claude/ # Документация для Claude
│ ├── workflow.md # Этот файл
│ └── project-structure.md # Детальная структура проекта
└── README.md # Общая информация
На сервере:
/opt/ # Синхронизировано с Mac
/data/ # Синхронизировано с Mac
/home/ubuntu/ # Синхронизировано с Mac
```
---
## Проверка состояния систем
### Syncthing:
```bash
# На сервере:
ssh ubuntu@51.89.231.46 "systemctl --user status syncthing"
# На Mac:
brew services list | grep syncthing
```
### Git:
```bash
# Проверить статус:
git status
# Проверить последние коммиты:
git log --oneline -10
# Проверить remote:
git remote -v
```
### Размер синхронизированных данных:
```bash
du -sh /Volumes/SSD/Aimpress_Cloud_Prod/{opt,data,home}
```
---
## Типичные задачи
### Добавить новую функцию:
1. Читаю существующий код
2. Вношу изменения
3. Жду синхронизации (10-30 сек)
4. Делаю коммит: `git commit -m "feat: add new feature"`
5. Push происходит автоматически через hook
### Исправить баг:
1. Нахожу проблемный код
2. Исправляю
3. Жду синхронизации
4. Коммит: `git commit -m "fix: resolve issue with X"`
### Обновить конфигурацию:
1. Изменяю конфиг файл
2. Жду синхронизации
3. Коммит: `git commit -m "chore: update nginx config"`
### Создать документацию:
1. Создаю .md файл
2. Коммит: `git commit -m "docs: add API documentation"`
---
## Важные замечания
⚠️ **Права доступа:**
- Некоторые файлы на сервере принадлежат root (например vault/data)
- Syncthing может не иметь прав на их чтение
- Это нормально, игнорируем эти файлы через .stignore
⚠️ **Большие файлы:**
- Бэкапы из `/opt/05-backups/` исключены из Git
- Логи не синхронизируются (в .stignore)
- SQL дампы и архивы не коммитятся
⚠️ **Секреты:**
- .env файлы в .gitignore
- pre-commit hook блокирует коммит секретов
- Если нужно - использую vault или отдельное хранилище
---
## Полезные команды
```bash
# Проверить синхронизацию Syncthing
syncthing cli show system
# Посмотреть что синхронизируется
du -sh /Volumes/SSD/Aimpress_Cloud_Prod/*
# Проверить Git статус
git status
# Посмотреть последние изменения
git log --oneline -20
# Создать коммит вручную (если нужно)
git add .
git commit -m "type: description"
# Push произойдет автоматически
# Проверить что на сервере
ssh ubuntu@51.89.231.46 "ls -la /opt"
```
---
## В следующих сессиях
Когда я (Claude) начинаю новую сессию:
1. Читаю этот файл (.claude/workflow.md)
2. Понимаю что проект работает через Syncthing + Git
3. Работаю с локальными файлами
4. Делаю коммиты после каждого изменения
5. Не спрашиваю пользователя о workflow - он задокументирован здесь
Этот workflow обеспечивает:
✅ Мгновенную синхронизацию изменений
✅ Полную историю версий в Git
✅ Автоматический бэкап в GitHub
✅ Безопасность (pre-commit hooks)
✅ Возможность восстановления системы (через rsync)

103
.gitignore vendored Normal file
View file

@ -0,0 +1,103 @@
# Dependencies
node_modules/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info/
dist/
build/
.venv/
venv/
env/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.project
.classpath
.settings/
# Logs
*.log
logs/
*.out
# Temporary files
*.tmp
*.temp
.cache/
.pytest_cache/
.coverage
htmlcov/
# System files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
desktop.ini
# Secrets and credentials
.env
.env.*
*.key
*.pem
*.p12
*.pfx
credentials.json
secrets.yaml
config/secrets/
# Database dumps and large backups
*.sql
*.sql.gz
*.dump
*.tar.gz
*.zip
*.bak
opt/05-backups/
**/backups/*.tar.gz
**/backups/*.sql.gz
# Docker volumes and data
**/docker-data/
**/volumes/
# Syncthing internal files
.stfolder
.stversions
.stignore
# Nested git repositories
opt/02-core/supabase/supabase/
# Compiled files
*.class
*.dll
*.exe
*.o
*.so
# Package files
*.jar
*.war
*.nar
*.ear
*.rar
# OS generated files
.fuse_hidden*
.directory
.Trash-*
.nfs*

255
README.md Normal file
View file

@ -0,0 +1,255 @@
# OVHserver Infrastructure
Production infrastructure для AI-Impress с полной автоматизацией, мониторингом и системой бэкапов.
**Сервер:** ubuntu@51.89.231.46
**GitHub:** [SamoilenkoVadym/OVHserver](https://github.com/SamoilenkoVadym/OVHserver)
---
## Трёхуровневая система синхронизации
### 🔄 1. Syncthing (Реального времени)
Мгновенная двусторонняя синхронизация между Mac и сервером.
- **Режим:** Send & Receive (bidirectional)
- **Папки:** `/opt`, `/data`, `/home/ubuntu`
- **Автозапуск:** systemd (сервер) + brew services (Mac)
- **Скорость:** 10-30 секунд
### 📦 2. GitHub (Версионный контроль)
Полная история изменений кода и конфигураций.
- **Репозиторий:** git@github.com:SamoilenkoVadym/OVHserver.git
- **Автопуш:** Через post-commit hook
- **Защита:** pre-commit hook блокирует коммит секретов
### 💾 3. rsync (Системный бэкап)
Полная копия сервера для восстановления.
- **Назначение:** Disaster recovery
- **Запуск:** По требованию
- **Хранение:** `/Volumes/SSD/Aimpress_Cloud_Prod/system-backup/`
---
## Структура проекта
```
.
├── opt/ # Основные проекты
│ ├── 00-infrastructure/ # Traefik, PostgreSQL, Redis, Vault, RabbitMQ
│ ├── 01-security/ # Authentik (SSO), Vaultwarden
│ ├── 02-core/ # n8n, Evolution API, Supabase
│ ├── 03-business/ # Odoo, Outline, Documenso, Wiki.js
│ ├── 04-tools/ # Portainer, Grafana, Monitoring, Uptime Kuma
│ ├── 05-backups/ # Бэкапы и скрипты (не в Git)
│ └── 06-webflow/ # Landing pages
├── data/ # Данные приложений
├── home/ # /home/ubuntu
└── .claude/ # Документация для Claude Code
├── workflow.md # Рабочий процесс
└── project-structure.md # Детальная структура
```
Подробная документация в [.claude/project-structure.md](.claude/project-structure.md)
---
## Технологический стек
### Инфраструктура
- **Docker** + **Docker Compose** - контейнеризация
- **Traefik** - reverse proxy с автоматическим SSL
- **PostgreSQL** - основная БД
- **Redis** - кэш и очереди
- **RabbitMQ** - message broker
### Безопасность
- **HashiCorp Vault** - управление секретами
- **Authentik** - SSO (Single Sign-On)
- **Vaultwarden** - менеджер паролей
### Мониторинг
- **Prometheus** + **Grafana** - метрики и дашборды
- **Loki** - агрегация логов
- **Uptime Kuma** - проверка доступности
### Бизнес-приложения
- **Odoo** - ERP система
- **n8n** - workflow automation
- **Evolution API** - WhatsApp Business API
- **Supabase** - Backend-as-a-Service
- **Outline** - корпоративная wiki
- **Documenso** - электронная подпись
---
## Быстрый старт
### Проверка синхронизации
```bash
# Статус Syncthing на сервере
ssh ubuntu@51.89.231.46 "systemctl --user status syncthing"
# Статус на Mac
brew services list | grep syncthing
# Размер синхронизированных данных
du -sh /Volumes/SSD/Aimpress_Cloud_Prod/{opt,data,home}
```
### Git операции
```bash
# Проверить статус
git status
# Посмотреть последние коммиты
git log --oneline -20
# Сделать коммит (push автоматический через hook)
git add .
git commit -m "feat: add new feature"
```
### Доступ к сервисам
Все сервисы доступны через Traefik с автоматическим SSL:
- **Portainer:** portainer.domain.com
- **Grafana:** grafana.domain.com
- **Authentik:** authentik.domain.com
- **Odoo:** odoo.domain.com
- **n8n:** n8n.domain.com
---
## Работа с проектом через Claude Code
### Рабочий процесс
1. Пользователь даёт задачу Claude
2. Claude работает с **локальными файлами** на Mac
3. Syncthing автоматически синхронизирует изменения на сервер (10-30 сек)
4. Claude делает git commit с понятным сообщением
5. Git hook автоматически пушит в GitHub
### Формат коммитов
```
<type>: <short description>
<detailed explanation if needed>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
```
**Типы коммитов:**
- `feat:` - новая функция
- `fix:` - исправление бага
- `refactor:` - рефакторинг кода
- `docs:` - документация
- `chore:` - обслуживание (конфиги, зависимости)
- `perf:` - улучшение производительности
Полная документация в [.claude/workflow.md](.claude/workflow.md)
---
## Безопасность
### Git Hooks
**pre-commit:**
- Блокирует коммит файлов `.env`, `.key`, `.pem`
- Предупреждает о потенциальных секретах в коде
- Можно пропустить: `git commit --no-verify`
**post-commit:**
- Автоматически пушит каждый коммит в GitHub
- Работает с ветками main/master
### .gitignore
Исключены из Git:
- Зависимости: `node_modules`, `__pycache__`, `venv`
- Логи: `*.log`, `logs/`
- Секреты: `.env`, `*.key`, `*.pem`
- Бэкапы: `05-backups/`, `*.sql.gz`, `*.tar.gz`
- Временные: `.cache`, `*.tmp`, `.DS_Store`
---
## Мониторинг и поддержка
### Проверка здоровья сервисов
```bash
# Docker контейнеры
ssh ubuntu@51.89.231.46 "docker ps"
# Логи контейнера
ssh ubuntu@51.89.231.46 "docker logs <container_name>"
# Disk usage
ssh ubuntu@51.89.231.46 "df -h"
```
### Бэкапы
- **Автоматические:** В `/opt/05-backups/`
- **Restic:** Инкрементальные бэкапы
- **Скрипты:** В `/opt/05-backups/scripts/`
- **Документация:** `/opt/05-backups/SCRIPTS-REGISTRY.md`
### Uptime Kuma
Мониторинг доступности всех сервисов 24/7 через веб-интерфейс.
---
## Восстановление системы
### Из rsync бэкапа
```bash
# Полное восстановление (осторожно!)
rsync -avz --progress /Volumes/SSD/Aimpress_Cloud_Prod/system-backup/ ubuntu@51.89.231.46:/
```
### Из Git
```bash
# Откат к предыдущему коммиту
git log --oneline
git checkout <commit_hash> -- <file_path>
git commit -m "chore: rollback to <commit>"
```
### Из Restic
```bash
# Посмотреть snapshots
restic snapshots
# Восстановить конкретный snapshot
restic restore <snapshot_id> --target /restore/path
```
---
## Документация
- **[.claude/workflow.md](.claude/workflow.md)** - Рабочий процесс Claude Code
- **[.claude/project-structure.md](.claude/project-structure.md)** - Детальная структура проекта
- **[opt/05-backups/SCRIPTS-REGISTRY.md](opt/05-backups/SCRIPTS-REGISTRY.md)** - Реестр скриптов
- **[opt/05-backups/README-SCRIPTS.md](opt/05-backups/README-SCRIPTS.md)** - Документация скриптов
---
## Контакты и поддержка
- **GitHub Issues:** [SamoilenkoVadym/OVHserver/issues](https://github.com/SamoilenkoVadym/OVHserver/issues)
- **Сервер:** ubuntu@51.89.231.46
---
## Лицензия
Private repository - все права защищены.
---
**Последнее обновление:** 2025-11-05
**Версия инфраструктуры:** 1.0.0

1705
home/$OUT Normal file

File diff suppressed because it is too large Load diff

268
home/$OUT_FILE Normal file
View file

@ -0,0 +1,268 @@
## 🖥️ System Overview
Static hostname: ai-impress-prod
Icon name: computer-vm
Chassis: vm 🖴
Machine ID: 2c3c6d97e54246e9bcfdeb513b8947f3
Boot ID: e0fb6e0813b54674ac1ca99a87864c74
Virtualization: kvm
Operating System: Ubuntu 24.04.3 LTS
Kernel: Linux 6.8.0-86-generic
Architecture: x86-64
Hardware Vendor: OpenStack Foundation
Hardware Model: OpenStack Nova
Firmware Version: 1.16.3-debian-1.16.3-2~bpo12+1
Firmware Date: Tue 2014-04-01
Firmware Age: 11y 6month 4w 1d
**Uptime:** $(uptime -p)
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda1 ext4 387G 44G 344G 12% /
/dev/sda16 ext4 881M 117M 703M 15% /boot
/dev/sda15 vfat 105M 6.2M 99M 6% /boot/efi
/dev/sdc ext4 393G 3.6G 370G 1% /mnt/backups
/dev/sdb ext4 295G 256M 279G 1% /mnt/psql-data
## 🐳 Docker Summary
Server: 28.5.1, Containers: 46, Running: 46, Images: 37, Driver: overlay2
authentik-postgres postgres:16-alpine Up 12 minutes (healthy)
authentik-proxy ghcr.io/goauthentik/proxy:2025.10 Up 12 minutes (healthy)
authentik-redis redis:alpine Up 12 minutes (healthy)
authentik-server ghcr.io/goauthentik/server:2025.10 Up 12 minutes (healthy)
authentik-worker ghcr.io/goauthentik/server:2025.10 Up 12 minutes (healthy)
evolution-api atendai/evolution-api:latest Up 2 days
inspiring_williams ghcr.io/czlonkowski/n8n-mcp:latest Up 4 hours (unhealthy)
landing-page nginx:alpine Up 4 days
mautic-db mariadb:11 Up 2 days
mautic mautic/mautic:latest Up 2 days
mautic-odoo-sync-v2 python:3.11-slim Up 2 days (healthy)
mautic-sync-webhook python:3.11-slim Up 2 days
modest_archimedes ghcr.io/czlonkowski/n8n-mcp:latest Up 3 hours (unhealthy)
n8n-shared n8nio/n8n:latest Up 2 days
n8n-worker-1 n8nio/n8n:latest Up 2 days
n8n-worker-2 n8nio/n8n:latest Up 2 days
n8n-worker-3 n8nio/n8n:latest Up 2 days
n8n-worker-4 n8nio/n8n:latest Up 2 days
NAMES IMAGE STATUS
odoo odoo:17 Up 45 hours
outline outlinewiki/outline:latest Up 16 hours (healthy)
pgadmin dpage/pgadmin4:9.9 Up 2 days
portainer portainer/portainer-ce:latest Up 2 days
postgres-main postgres:16-alpine Up 2 days (healthy)
postiz ghcr.io/gitroomhq/postiz-app:latest Up 3 hours
postiz-postgres postgres:16-alpine Up 3 hours (healthy)
postiz-redis redis:7-alpine Up 3 hours (healthy)
rabbitmq rabbitmq:3.13-management-alpine Up 2 days (healthy)
realtime-dev.supabase-realtime supabase/realtime:v2.51.11 Up 2 days (healthy)
redis-main redis:7-alpine Up 4 days (healthy)
sftp-server atmoz/sftp:latest Up 4 days
supabase-analytics supabase/logflare:1.22.6 Up 2 days (healthy)
supabase-auth supabase/gotrue:v2.180.0 Up 2 days (healthy)
supabase-db supabase/postgres:15.8.1.085 Up 2 days (healthy)
supabase-edge-functions supabase/edge-runtime:v1.69.6 Up 2 days
supabase-imgproxy darthsim/imgproxy:v3.8.0 Up 2 days (healthy)
supabase-kong kong:2.8.1 Up 2 days (healthy)
supabase-meta supabase/postgres-meta:v0.91.6 Up 2 days (healthy)
supabase-pooler supabase/supavisor:2.7.0 Up 2 days (healthy)
supabase-rest postgrest/postgrest:v13.0.7 Up 2 days
supabase-storage supabase/storage-api:v1.28.0 Up 2 days (healthy)
supabase-studio supabase/studio:2025.10.01-sha-8460121 Up 2 days (healthy)
supabase-vector timberio/vector:0.28.1-alpine Up 2 days (healthy)
traefik traefik:v3.0 Up 2 days
uptime-kuma louislam/uptime-kuma:latest Up 2 days (healthy)
vault hashicorp/vault:1.15 Up 4 days (healthy)
vaultwarden vaultwarden/server:1.30.5-alpine Up 2 days (healthy)
## 🌐 Docker Networks
NETWORK ID NAME DRIVER SCOPE
5e188b0a38a2 authentik_authentik-internal bridge local
f02abd181cda bridge bridge local
cdaceadfcefb database-internal bridge local
4b9a877223ee host host local
a0dd7c187961 mautic-internal bridge local
f2be33f79fe4 mautic_mautic-internal bridge local
7c3083a44e08 monitoring bridge local
01cd476e5307 n8n-shared bridge local
d21678395b05 none null local
39a0028fdb88 odoo-internal bridge local
e581070d94e4 postiz-config_postiz-internal bridge local
9e103eeb0591 postiz_postiz-internal bridge local
c797531af13d sftp_default bridge local
36cf0e468fc7 supabase_default bridge local
b28f70541993 traefik-public bridge local
4bc92c6c74a8 vault-internal bridge local
## 📁 /opt Structure
/opt
/opt/00-infrastructure
/opt/00-infrastructure/postgres
/opt/00-infrastructure/postgres/backups
/opt/00-infrastructure/postgres/init-scripts
/opt/00-infrastructure/postgres/pgadmin-config
/opt/00-infrastructure/postgres/scripts
/opt/00-infrastructure/rabbitmq
/opt/00-infrastructure/redis
/opt/00-infrastructure/traefik
/opt/00-infrastructure/traefik/acme
/opt/00-infrastructure/traefik/config
/opt/00-infrastructure/traefik/logs
/opt/00-infrastructure/vault
/opt/00-infrastructure/vault/config
/opt/00-infrastructure/vault/data
/opt/00-infrastructure/vault/logs
/opt/00-infrastructure/vault/policies
/opt/01-security
/opt/01-security/authentik
/opt/01-security/authentik/blueprints
/opt/01-security/authentik/certs
/opt/01-security/authentik/custom-templates
/opt/01-security/authentik/media
/opt/01-security/vaultwarden
/opt/02-core
/opt/02-core/evolution-api
/opt/02-core/n8n-shared
/opt/02-core/n8n-shared/custom-nodes
/opt/02-core/n8n-vip
/opt/02-core/supabase
/opt/02-core/supabase/supabase
/opt/03-business
/opt/03-business/mautic
/opt/03-business/mautic/sync
/opt/03-business/mautic/sync_v2
/opt/03-business/odoo
/opt/03-business/outline
/opt/04-tools
/opt/04-tools/portainer
/opt/04-tools/uptime-kuma
/opt/05-backups
/opt/05-backups/credentials
/opt/05-backups/data
/opt/05-backups/docs
/opt/05-backups/docs/full
/opt/05-backups/logs
/opt/05-backups/migration-20251026-171124
/opt/05-backups/restic
/opt/05-backups/scripts
/opt/infrastructure-docs
/opt/postiz-config
## 💾 Databases
### PostgreSQL
- postgres-main:
postgres
authentik
supabase
n8n_shared
odoo
outline
vaultwarden
evolution
aimpress_admin
authelia
- supabase-db:
postgres
_supabase
- authentik-postgres:
postgres
authentik
- postiz-postgres:
postgres
postiz
### MariaDB (Mautic)
Database
information_schema
mautic
### Redis
- authentik-redis
- postiz-redis
redis_version:7.4.6
connected_clients:12
used_memory_human:2.44M
- redis-main
## 🔐 Authentik
- Config: /opt/01-security/authentik/.env
AUTHENTIK_EMAIL__HOST=aiimpress-com0e.mail.protection.outlook.com
AUTHENTIK_EMAIL__FROM=noreply@ai-impress.com
- Blueprints:
outline-app.yaml
postiz-app.yaml
- Proxy token:
present
## 🏦 Vault
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 1.15.6
Build Date 2024-02-28T17:07:34Z
Storage Type file
Cluster Name vault-cluster-bdcdf7bc
Cluster ID a8ec6e5c-2298-eb9d-b0c5-636a68d96a1d
HA Enabled false
- Config:
vault.hcl
- Token:
present
- Policies:
aimpress-admin.hcl
## 🕒 Cron Jobs
No root cron
## 📦 Backup Scripts
authentik-services-manager.sh
auto-update.sh
backup-app.sh
backup-authentik-improved.sh
backup-authentik.sh
backup-full.sh
backup-full.sh.backup-20251024-231153
backup-full.sh.backup-20251026-145737
backup-full.sh.bak
check-updates.sh
check-updates.sh.backup
check-updates.sh.backup-20251020-115409
create-client-database.sh
create-n8n-vip-instance.sh
generate-env-from-vault.sh
generate-env-from-vault.sh.backup-20251023-225538
get-client-credentials.sh
health-check-improved.sh
health-check.sh
health-check.sh.backup-20251020-160110
health-check.sh.backup-20251024-223943
health-check.sh.backup-20251025-223638
health-check.sh.backup-20251025-233447
health-check.sh.backup-20251026-040001
health-check.sh.backup-20251026-181026
health-check.sh.backup-20251027-122447
health-check.sh.backup-20251029-135119
infrastructure-scanner.sh
migrate-authelia-to-authentik.sh
README.md
scripts-help.sh
update-app.sh
update-app.sh.backup-20251020-115409
update-manager.sh
update-manager.sh.backup-20251020-115409
upload-full-docs.sh
vault-helper.sh

2000
home/.bash_history Normal file

File diff suppressed because it is too large Load diff

7
home/.bash_logout Normal file
View file

@ -0,0 +1,7 @@
# ~/.bash_logout: executed by bash(1) when login shell exits.
# when leaving the console clear the screen to increase privacy
if [ "$SHLVL" = 1 ]; then
[ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
fi

164
home/.bashrc Normal file
View file

@ -0,0 +1,164 @@
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth
# append to the history file, don't overwrite it
shopt -s histappend
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# If set, the pattern "**" used in a pathname expansion context will
# match all files and zero or more directories and subdirectories.
#shopt -s globstar
# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color|*-256color) color_prompt=yes;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# colored GCC warnings and errors
#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Add an "alert" alias for long running commands. Use like so:
# sleep 10; alert
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi
export LANG=en_US.UTF-8
export VAULT_ADDR="http://127.0.0.1:8200"
export VAULT_TOKEN="hvs.CAESIKhabWn_M1AmzRqaW-WBZx7MGYP1cHsmdrcQmIMcmXR6Gh4KHGh2cy55bVk1WmpFZ3Y0TE82YVQzTWlmWWc5Z2Q"
export VAULT_ADDR="http://127.0.0.1:8200"
export VAULT_TOKEN="hvs.CAESIKhabWn_M1AmzRqaW-WBZx7MGYP1cHsmdrcQmIMcmXR6Gh4KHGh2cy55bVk1WmpFZ3Y0TE82YVQzTWlmWWc5Z2Q"
alias vault-secret='/opt/05-backups/scripts/vault-helper.sh'
# ============================================
# AImpress Aliases
# ============================================
# Vault
alias vault-secret='/opt/05-backups/scripts/vault-helper.sh'
alias vault-unseal='/opt/00-infrastructure/vault/unseal.sh'
# Client Management
alias create-client='/opt/05-backups/scripts/create-client-database.sh'
alias create-n8n='/opt/05-backups/scripts/create-n8n-vip-instance.sh'
alias get-client='/opt/05-backups/scripts/get-client-credentials.sh'
# System
alias health-check='/opt/05-backups/scripts/health-check.sh'
alias backup-now='export VAULT_TOKEN=$(cat /opt/00-infrastructure/vault/.vault-token) && /opt/05-backups/scripts/backup-full.sh'
alias generate-env='/opt/05-backups/scripts/generate-env-from-vault.sh'
# Docker shortcuts
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dlogs='docker compose logs -f'
alias dstats='docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"'
# Logs
alias logs-traefik='docker logs -f traefik'
alias logs-vault='docker logs -f vault'
alias logs-postgres='docker logs -f postgres-main'
alias logs-n8n='docker logs -f n8n-shared'
# ════════════════════════════════════════════════════════════════
# AImpress Infrastructure Tools Aliases
# ════════════════════════════════════════════════════════════════
alias scripts='/opt/05-backups/scripts/scripts-help.sh'
alias health='/opt/05-backups/scripts/health-check.sh'
alias scan='/opt/05-backups/scripts/infrastructure-scanner.sh'
alias update-all='/opt/05-backups/scripts/auto-update.sh'
alias backup='/opt/05-backups/scripts/backup-full.sh'

View file

@ -0,0 +1,7 @@
[r2]
type = s3
provider = Cloudflare
access_key_id = a92cb6c958a5392fd1ba5e4d1cb857af
secret_access_key = 0a4a0863e3afe2088b2990cca9152166b036343975ff3b3e5ad39fa923323aed
endpoint = https://6aff840a680098927b58beb93b59dd03.r2.cloudflarestorage.com
acl = private

View file

@ -0,0 +1 @@
/usr/lib/systemd/user/syncthing.service

5
home/.docker/.token_seed Normal file
View file

@ -0,0 +1,5 @@
{
"registry-1.docker.io": {
"Seed": "nUB+D7wwNzJAYuxW98LG7g=="
}
}

View file

View file

@ -0,0 +1 @@
35a9aa8bc7025bac

View file

View file

@ -0,0 +1 @@
2025-11-03T14:52:20Z

View file

@ -0,0 +1 @@
{"Key":"unix:///var/run/docker.sock","Name":"","Global":false}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["coxvioxaj8vn5e7s8hqzbaycd"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["xh9axp3ym4qcf852ndxxjqh9w"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["ug6s8rodxw9ykroetepcbjupy"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["i4dyv81q364vcs58lvd98z656"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["sfyrmppbk0udq6ufxesh218rc"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["ik8cdrh2rs30uu9a72pgfbedh"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["515b7gh11hhcbv361w7dbtm65"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["vhxqpunvttin7o4usxjo75i5l"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["81y7sj7ratv81ybnbr0ai4gre"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["dj3if82z7hjqil787j05p9jkc"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["5xp225v1k3a620x0fye8mcfwq"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["vm5uw7sg76nl7zb4k2klk3rh9"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["favyuxo9e6kn826ezfz3d1q52"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["l62xyapi5dg0mtgfvslfkbsgw"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["bcvzf1etshxh3rdq4d41oowwz"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["wi4ktf3n548ihwrfx187mke5z"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["7ykc3ahz4iz1v32fbkqxjrf1x"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["id3wnjohsddxxzr8thxhv55tf"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["iv7s2neqb2jt7jcr5szaxvti5"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["40kmufup0z67r0v9zsf7o7gfj"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["tofs8bynq7za75ytn0cb1d53o"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["oev1eyfznq7dg4f5drzxa586a"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["5zegqe73g8ptmt0cra4no368u"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["r40pa45kyv9oi6va2lldcx4i5"]}

View file

@ -0,0 +1 @@
{"Targets":["default"],"Refs":["zsu0k3o94hvmavzbfczpixynt"]}

View file

@ -0,0 +1 @@
{"Target":"server","LocalPath":"/mnt/psql-data/n8n-mcp/outline-mcp","DockerfilePath":"/mnt/psql-data/n8n-mcp/outline-mcp/Dockerfile","GroupRef":"w2nf8x2relyavrmj7lrv76eqh"}

View file

@ -0,0 +1 @@
{"Target":"copilotkit-runtime","LocalPath":"/opt/03-business/copilotkit-runtime-service","DockerfilePath":"/opt/03-business/copilotkit-runtime-service/Dockerfile","GroupRef":"cpjevk8wn71rvx99p1euk0lqm"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"ixh7pp32pu7kg9r8hx3ytun4n"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"xc49wmh8rud8vu34fkf1cn535"}

View file

@ -0,0 +1 @@
{"Target":"mautic-ai-widget","LocalPath":"/opt/03-business/mautic-ai-widget","DockerfilePath":"/opt/03-business/mautic-ai-widget/Dockerfile","GroupRef":"s9xg5aq0eqyjdrrvlnqoa5uc0"}

View file

@ -0,0 +1 @@
{"Target":"n8n-mcp-api","LocalPath":"/mnt/psql-data/n8n-mcp/kingler-server","DockerfilePath":"/mnt/psql-data/n8n-mcp/kingler-server/Dockerfile","GroupRef":"exr9lr5ee663ndyy94kq9cq5g"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"ndxdhvirihbemq1bpofv6wog5"}

View file

@ -0,0 +1 @@
{"Target":"mautic-ai-widget","LocalPath":"/opt/03-business/mautic-ai-widget","DockerfilePath":"/opt/03-business/mautic-ai-widget/Dockerfile","GroupRef":"00ia54h1lqk6h9rjfac8316un"}

View file

@ -0,0 +1 @@
{"Target":"n8n-mcp-api","LocalPath":"/mnt/psql-data/n8n-mcp/kingler-server","DockerfilePath":"/mnt/psql-data/n8n-mcp/kingler-server/Dockerfile","GroupRef":"ff5asjlwpnnq81p2yofifgdri"}

View file

@ -0,0 +1 @@
{"Target":"mautic-ai-backend","LocalPath":"/opt/03-business/mautic-ai-backend","DockerfilePath":"/opt/03-business/mautic-ai-backend/Dockerfile","GroupRef":"jc0kd7x0574c7m7uuo2qnpzko"}

View file

@ -0,0 +1 @@
{"Target":"web","LocalPath":"/opt/03-business/mautic","DockerfilePath":"/opt/03-business/mautic/Dockerfile","GroupRef":"bj0782i1o9upeefa9gzzw3zm8"}

View file

@ -0,0 +1 @@
{"Target":"mautic-ai-widget","LocalPath":"/opt/03-business/mautic-ai-widget","DockerfilePath":"/opt/03-business/mautic-ai-widget/Dockerfile","GroupRef":"sut5234cf718r4a56undefm69"}

View file

@ -0,0 +1 @@
{"Target":"mautic-ai-backend","LocalPath":"/opt/03-business/mautic-ai-backend","DockerfilePath":"/opt/03-business/mautic-ai-backend/Dockerfile","GroupRef":"c0xphwi7ajbpkmf2xza8rkqb0"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"vvrivck3v35gyrl3qxg0dsucp"}

View file

@ -0,0 +1 @@
{"Target":"web","LocalPath":"/opt/03-business/mautic","DockerfilePath":"/opt/03-business/mautic/Dockerfile","GroupRef":"jmwbygb44wgoncgdwhs79znqr"}

View file

@ -0,0 +1 @@
{"Target":"web","LocalPath":"/opt/03-business/mautic","DockerfilePath":"/opt/03-business/mautic/Dockerfile","GroupRef":"x23hsxj6p8lmly9be8jvrjn2o"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"xx79s0dohu8wjb609hydgrz3o"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"bp02x9xpyuktgx5itlmsvvlpm"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"w5ko1fi47iblxe5ymwph8cctx"}

View file

@ -0,0 +1 @@
{"Target":"default","LocalPath":"/opt/03-business/mautic-ai-widget","DockerfilePath":""}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"3vdznzboq7zfi6hp0sjevglbn"}

View file

@ -0,0 +1 @@
{"Target":"documenso","LocalPath":"/opt/03-business/documenso","DockerfilePath":"/opt/03-business/documenso/Dockerfile.custom","GroupRef":"e5l54xhahkmg5fhy0uajmb4n2"}

View file

@ -0,0 +1 @@
{"Target":"authelia-manager","LocalPath":"/home/ubuntu/authelia-user-manager","DockerfilePath":"/home/ubuntu/authelia-user-manager/Dockerfile","GroupRef":"j1k19adj7iy9rrmr1j8srvd2w"}

View file

@ -0,0 +1 @@
{"Target":"web","LocalPath":"/opt/03-business/mautic","DockerfilePath":"/opt/03-business/mautic/Dockerfile","GroupRef":"qv1sro1gt0rerxsnwkm95nn4k"}

View file

@ -0,0 +1 @@
{"Target":"n8n-mcp-api","LocalPath":"/mnt/psql-data/n8n-mcp/kingler-server","DockerfilePath":"/mnt/psql-data/n8n-mcp/kingler-server/Dockerfile","GroupRef":"39nztb9c2xc3exglqyc5z316p"}

View file

@ -0,0 +1 @@
{"Target":"n8n-mcp-api","LocalPath":"/mnt/psql-data/n8n-mcp/kingler-server","DockerfilePath":"/mnt/psql-data/n8n-mcp/kingler-server/Dockerfile","GroupRef":"yrur461lr1zgjzx79rlhok1hi"}

View file

@ -0,0 +1 @@
2

3
home/.lesshst Normal file
View file

@ -0,0 +1,3 @@
.less-history-file:
.search
"q

View file

@ -0,0 +1,316 @@
<configuration version="37">
<folder id="data" label="data" path="/data" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="FVT5C5F-R6BTJTF-6Y4NOWA-BS2EU3P-WM5MPNK-NQGJHWH-4MMZ4VK-PB4U6AT" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="">0</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<folder id="default" label="Default Folder" path="/home/ubuntu/Sync" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<folder id="home" label="home" path="/home/ubuntu" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="FVT5C5F-R6BTJTF-6Y4NOWA-BS2EU3P-WM5MPNK-NQGJHWH-4MMZ4VK-PB4U6AT" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="">0</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<folder id="opt" label="opt" path="/opt" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="FVT5C5F-R6BTJTF-6Y4NOWA-BS2EU3P-WM5MPNK-NQGJHWH-4MMZ4VK-PB4U6AT" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="">0</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<device id="FVT5C5F-R6BTJTF-6Y4NOWA-BS2EU3P-WM5MPNK-NQGJHWH-4MMZ4VK-PB4U6AT" name="Mac" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>dynamic</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort>
<numConnections>0</numConnections>
</device>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" name="ai-impress-prod" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>dynamic</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort>
<numConnections>0</numConnections>
</device>
<gui enabled="true" tls="false" debugging="false" sendBasicAuthPrompt="false">
<address>127.0.0.1:8384</address>
<metricsWithoutAuth>false</metricsWithoutAuth>
<apikey>zPRt2MVENXuPrYx6QiqX9CorrdPd2CvJ</apikey>
<theme>default</theme>
</gui>
<ldap></ldap>
<options>
<listenAddress>default</listenAddress>
<globalAnnounceServer>default</globalAnnounceServer>
<globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
<localAnnouncePort>21027</localAnnouncePort>
<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<reconnectionIntervalS>60</reconnectionIntervalS>
<relaysEnabled>true</relaysEnabled>
<relayReconnectIntervalM>10</relayReconnectIntervalM>
<startBrowser>true</startBrowser>
<natEnabled>true</natEnabled>
<natLeaseMinutes>60</natLeaseMinutes>
<natRenewalMinutes>30</natRenewalMinutes>
<natTimeoutSeconds>10</natTimeoutSeconds>
<urAccepted>0</urAccepted>
<urSeen>0</urSeen>
<urUniqueID></urUniqueID>
<urURL>https://data.syncthing.net/newdata</urURL>
<urPostInsecurely>false</urPostInsecurely>
<urInitialDelayS>1800</urInitialDelayS>
<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
<upgradeToPreReleases>false</upgradeToPreReleases>
<keepTemporariesH>24</keepTemporariesH>
<cacheIgnoredFiles>false</cacheIgnoredFiles>
<progressUpdateIntervalS>5</progressUpdateIntervalS>
<limitBandwidthInLan>false</limitBandwidthInLan>
<minHomeDiskFree unit="%">1</minHomeDiskFree>
<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
<tempIndexMinBlocks>10</tempIndexMinBlocks>
<unackedNotificationID>authenticationUserAndPassword</unackedNotificationID>
<trafficClass>0</trafficClass>
<setLowPriority>true</setLowPriority>
<maxFolderConcurrency>0</maxFolderConcurrency>
<crashReportingURL>https://crash.syncthing.net/newcrash</crashReportingURL>
<crashReportingEnabled>true</crashReportingEnabled>
<stunKeepaliveStartS>180</stunKeepaliveStartS>
<stunKeepaliveMinS>20</stunKeepaliveMinS>
<stunServer>default</stunServer>
<databaseTuning>auto</databaseTuning>
<maxConcurrentIncomingRequestKiB>0</maxConcurrentIncomingRequestKiB>
<announceLANAddresses>true</announceLANAddresses>
<sendFullIndexOnUpgrade>false</sendFullIndexOnUpgrade>
<auditEnabled>false</auditEnabled>
<auditFile></auditFile>
<connectionLimitEnough>0</connectionLimitEnough>
<connectionLimitMax>0</connectionLimitMax>
<connectionPriorityTcpLan>10</connectionPriorityTcpLan>
<connectionPriorityQuicLan>20</connectionPriorityQuicLan>
<connectionPriorityTcpWan>30</connectionPriorityTcpWan>
<connectionPriorityQuicWan>40</connectionPriorityQuicWan>
<connectionPriorityRelay>50</connectionPriorityRelay>
<connectionPriorityUpgradeThreshold>0</connectionPriorityUpgradeThreshold>
</options>
<defaults>
<folder id="" label="" path="" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<device id="" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>dynamic</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort>
<numConnections>0</numConnections>
</device>
<ignores></ignores>
</defaults>
</configuration>

View file

@ -0,0 +1,173 @@
<configuration version="37">
<folder id="default" label="Default Folder" path="/home/ubuntu/Sync" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" name="ai-impress-prod" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>dynamic</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort>
<numConnections>0</numConnections>
</device>
<gui enabled="true" tls="false" debugging="false" sendBasicAuthPrompt="false">
<address>127.0.0.1:8384</address>
<metricsWithoutAuth>false</metricsWithoutAuth>
<apikey>zPRt2MVENXuPrYx6QiqX9CorrdPd2CvJ</apikey>
<theme>default</theme>
</gui>
<ldap></ldap>
<options>
<listenAddress>default</listenAddress>
<globalAnnounceServer>default</globalAnnounceServer>
<globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
<localAnnouncePort>21027</localAnnouncePort>
<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<reconnectionIntervalS>60</reconnectionIntervalS>
<relaysEnabled>true</relaysEnabled>
<relayReconnectIntervalM>10</relayReconnectIntervalM>
<startBrowser>true</startBrowser>
<natEnabled>true</natEnabled>
<natLeaseMinutes>60</natLeaseMinutes>
<natRenewalMinutes>30</natRenewalMinutes>
<natTimeoutSeconds>10</natTimeoutSeconds>
<urAccepted>0</urAccepted>
<urSeen>0</urSeen>
<urUniqueID></urUniqueID>
<urURL>https://data.syncthing.net/newdata</urURL>
<urPostInsecurely>false</urPostInsecurely>
<urInitialDelayS>1800</urInitialDelayS>
<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
<upgradeToPreReleases>false</upgradeToPreReleases>
<keepTemporariesH>24</keepTemporariesH>
<cacheIgnoredFiles>false</cacheIgnoredFiles>
<progressUpdateIntervalS>5</progressUpdateIntervalS>
<limitBandwidthInLan>false</limitBandwidthInLan>
<minHomeDiskFree unit="%">1</minHomeDiskFree>
<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
<tempIndexMinBlocks>10</tempIndexMinBlocks>
<unackedNotificationID>authenticationUserAndPassword</unackedNotificationID>
<trafficClass>0</trafficClass>
<setLowPriority>true</setLowPriority>
<maxFolderConcurrency>0</maxFolderConcurrency>
<crashReportingURL>https://crash.syncthing.net/newcrash</crashReportingURL>
<crashReportingEnabled>true</crashReportingEnabled>
<stunKeepaliveStartS>180</stunKeepaliveStartS>
<stunKeepaliveMinS>20</stunKeepaliveMinS>
<stunServer>default</stunServer>
<databaseTuning>auto</databaseTuning>
<maxConcurrentIncomingRequestKiB>0</maxConcurrentIncomingRequestKiB>
<announceLANAddresses>true</announceLANAddresses>
<sendFullIndexOnUpgrade>false</sendFullIndexOnUpgrade>
<auditEnabled>false</auditEnabled>
<auditFile></auditFile>
<connectionLimitEnough>0</connectionLimitEnough>
<connectionLimitMax>0</connectionLimitMax>
<connectionPriorityTcpLan>10</connectionPriorityTcpLan>
<connectionPriorityQuicLan>20</connectionPriorityQuicLan>
<connectionPriorityTcpWan>30</connectionPriorityTcpWan>
<connectionPriorityQuicWan>40</connectionPriorityQuicWan>
<connectionPriorityRelay>50</connectionPriorityRelay>
<connectionPriorityUpgradeThreshold>0</connectionPriorityUpgradeThreshold>
</options>
<defaults>
<folder id="" label="" path="" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="7WWT3Q3-IL2RSA2-XHO6JKN-LCL2XEL-NWFZCDJ-6XVWIZ7-NYJ6JKJ-C53YCAC" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</versioning>
<copiers>0</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressIntervalS>0</scanProgressIntervalS>
<pullerPauseS>0</pullerPauseS>
<maxConflicts>10</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<paused>false</paused>
<weakHashThresholdPct>25</weakHashThresholdPct>
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
<maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>false</junctionsAsDirs>
<syncOwnership>false</syncOwnership>
<sendOwnership>false</sendOwnership>
<syncXattrs>false</syncXattrs>
<sendXattrs>false</sendXattrs>
<xattrFilter>
<maxSingleEntrySize>1024</maxSingleEntrySize>
<maxTotalSize>4096</maxTotalSize>
</xattrFilter>
</folder>
<device id="" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>dynamic</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort>
<numConnections>0</numConnections>
</device>
<ignores></ignores>
</defaults>
</configuration>

View file

@ -0,0 +1 @@
MANIFEST-000009

View file

@ -0,0 +1,43 @@
=============== Nov 5, 2025 (UTC) ===============
16:21:02.095451 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
16:21:02.097634 db@open opening
16:21:02.098162 version@stat F·[] S·0B[] Sc·[]
16:21:02.098963 db@janitor F·2 G·0
16:21:02.098986 db@open done T·1.332791ms
16:21:02.565455 memdb@flush N·18 S·582B
16:21:02.566837 memdb@flush created L0@3 N·18 S·301B "\ndb..ion,v18":"\ndb..ion,v1"
16:21:02.566888 version@stat F·[1] S·301B[301B] Sc·[0.25]
16:21:02.567407 memdb@flush committed F·1 T·1.832323ms
16:21:02.567453 journal@remove removed @1
16:21:02.567486 table@compaction range L-1 "":""
16:21:02.567496 table@compaction L0·1 -> L1·0 S·301B Q·18
16:21:02.568675 table@build created L1@4 N·3 S·173B "\ndb..ion,v18":"\ndb..ion,v16"
16:21:02.568715 version@stat F·[0 1] S·173B[0B 173B] Sc·[0.00 0.00]
16:21:02.569234 table@compaction committed F~ S-128B Ke·0 D·15 T·1.721146ms
16:21:02.569401 table@remove removed @3
16:30:19.132255 memdb@flush N·97086 S·15MiB
16:30:19.227732 memdb@flush created L0@6 N·97086 S·6MiB "\x00\x00\x00..ure,v396":"\x0e\x00\x00..4e8,v90139"
16:30:19.227791 version@stat F·[1 1] S·6MiB[6MiB 173B] Sc·[0.25 0.00]
16:30:19.228349 memdb@flush committed F·1 T·96.040008ms
16:30:19.230900 journal@remove removed @2
16:31:01.663311 db@close closing
16:31:01.664259 db@close done T·949.337µs
=============== Nov 5, 2025 (UTC) ===============
16:31:01.729761 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
16:31:01.729976 version@stat F·[1 1] S·6MiB[6MiB 173B] Sc·[0.25 0.00]
16:31:01.730003 db@open opening
16:31:01.730046 journal@recovery F·1
16:31:01.730247 journal@recovery recovering @5
16:31:01.746293 memdb@flush created L0@7 N·7157 S·582KiB "\x00\x00\x00..log,v101359":"\x0e\x00\x00..ldb,v98820"
16:31:01.746723 version@stat F·[2 1] S·6MiB[6MiB 173B] Sc·[0.50 0.00]
16:31:01.750307 db@janitor F·5 G·0
16:31:01.750334 db@open done T·20.324227ms
16:31:02.225492 table@compaction L0·2 -> L1·1 S·6MiB Q·104262
16:31:02.261800 table@build created L1@10 N·17452 S·2MiB "\x00\x00\x00..ure,v396":"\x00\x00\x00..tsx,v103408"
16:31:02.303994 table@build created L1@11 N·38055 S·2MiB "\x00\x00\x00..age,v96100":"\x02\x00\x00..png,v52688"
16:31:02.338967 table@build created L1@12 N·31731 S·2MiB "\x02\x00\x00...ts,v81746":"\x0e\x00\x00..ico,v82015"
16:31:02.349374 table@build created L1@13 N·8854 S·691KiB "\x0e\x00\x00...js,v80031":"\x0e\x00\x00..4e8,v90139"
16:31:02.349433 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.07]
16:31:02.350017 table@compaction committed F+1 S-189KiB Ke·0 D·8154 T·124.48305ms
16:31:02.351922 table@remove removed @6
16:31:02.352011 table@remove removed @4

27
home/.profile Normal file
View file

@ -0,0 +1,27 @@
# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.
# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022
# if running bash
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDdbM/5/qdfeoSCewWWl9Tf2o1ZlBTsDL4ZXc0Jn15tM v.samoilenko@ai-impress.com

1
home/.ssh/known_hosts Normal file
View file

@ -0,0 +1 @@
|1|D6vS3jNOr8eht2FeXycOs0z3pWI=|7nZwF6dhYU13ez7/I+yBMLh52qY= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDdrqXcRKI5FrDkNhEXPosajA7tY9hTVsm5YVandGqr3

View file

4
home/.wget-hsts Normal file
View file

@ -0,0 +1,4 @@
# HSTS 1.0 Known Hosts database for GNU Wget.
# Edit at your own risk.
# <hostname> <port> <incl. subdomains> <created> <max-age>
apt.releases.hashicorp.com 0 1 1760633236 31536000

View file

@ -0,0 +1,14 @@
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y docker.io && rm -rf /var/lib/apt/lists/*
RUN pip install flask pyyaml argon2-cffi gunicorn
COPY app.py .
COPY templates/ templates/
EXPOSE 5000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]

View file

@ -0,0 +1,105 @@
# Authelia User Manager
Веб-интерфейс для управления пользователями Authelia с автоматическим предоставлением доступа к сервисам.
## Возможности
- Добавление пользователей через веб-интерфейс
- Автоматическая генерация безопасных паролей
- Управление группами доступа
- Автоматическое предоставление доступа к сервисам на основе групп
- Удаление пользователей
## Группы и доступ
### admins
Полный доступ ко всем сервисам:
- traefik.ai-impress.com
- pgadmin.ai-impress.com
- rabbitmq.ai-impress.com
- n8n.ai-impress.com
- portainer.ai-impress.com
- status.ai-impress.com
- supabase.ai-impress.com
- wpp.ai-impress.com
- odoo.ai-impress.com
### developers
Доступ к tech-сервисам:
- n8n.ai-impress.com
- portainer.ai-impress.com
- traefik.ai-impress.com
- status.ai-impress.com
- odoo.ai-impress.com
### analysts
Доступ к аналитике и данным:
- pgadmin.ai-impress.com
- status.ai-impress.com
- odoo.ai-impress.com
### users
Базовый доступ:
- wiki.ai-impress.com
- status.ai-impress.com
## Установка
1. Обновите ACL в Authelia:
```bash
# Скопируйте содержимое update_acl.yml в /path/to/authelia/configuration.yml
# Замените секцию access_control
```
2. Запустите User Manager:
```bash
cd authelia-user-manager
docker-compose up -d
```
3. Откройте веб-интерфейс:
```
http://your-server:5000
```
## Использование
### Добавление пользователя
1. Откройте веб-интерфейс
2. Заполните форму:
- Имя пользователя (username)
- Email
- Отображаемое имя
- Выберите группы доступа
3. Нажмите "Создать пользователя"
4. Сохраните сгенерированный пароль!
### Удаление пользователя
1. Найдите пользователя в списке
2. Нажмите кнопку "Удалить"
3. Подтвердите действие
## Безопасность
- Пароли генерируются автоматически (16 символов)
- Используется Argon2id для хеширования
- Пароли показываются только один раз при создании
- Рекомендуется защитить веб-интерфейс через Authelia
## Интеграция с Traefik
Добавьте в docker-compose.yml Authelia Manager:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.authelia-manager.rule=Host(`users.ai-impress.com`)"
- "traefik.http.routers.authelia-manager.entrypoints=websecure"
- "traefik.http.routers.authelia-manager.tls.certresolver=letsencrypt"
- "traefik.http.routers.authelia-manager.middlewares=authelia@docker"
- "traefik.http.services.authelia-manager.loadbalancer.server.port=5000"
```
Это защитит интерфейс управления через Authelia.

View file

@ -0,0 +1,257 @@
#!/usr/bin/env python3
from flask import Flask, render_template, request, jsonify
import yaml
import secrets
import string
import subprocess
from argon2 import PasswordHasher
app = Flask(__name__)
SERVICE_GROUPS = {
'admins': [
'traefik.ai-impress.com',
'pgadmin.ai-impress.com',
'rabbitmq.ai-impress.com',
'n8n.ai-impress.com',
'portainer.ai-impress.com',
'status.ai-impress.com',
'supabase.ai-impress.com',
'wpp.ai-impress.com',
'social.ai-impress.com',
'odoo.ai-impress.com',
'vault.ai-impress.com',
'chat.ai-impress.com',
'crm.ai-impress.com',
'wiki.ai-impress.com',
'portal.ai-impress.com',
'llm.ai-impress.com'
],
'developers': [
'n8n.ai-impress.com',
'portainer.ai-impress.com',
'traefik.ai-impress.com',
'status.ai-impress.com',
'odoo.ai-impress.com',
'supabase.ai-impress.com',
'vault.ai-impress.com',
'llm.ai-impress.com'
],
'analysts': [
'pgadmin.ai-impress.com',
'status.ai-impress.com',
'odoo.ai-impress.com',
'crm.ai-impress.com'
],
'users': [
'wiki.ai-impress.com',
'status.ai-impress.com',
'chat.ai-impress.com',
'crm.ai-impress.com',
'portal.ai-impress.com'
]
}
USERS_FILE = '/config/users_database.yml'
ph = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=32,
salt_len=16
)
def generate_password(length=16):
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(alphabet) for _ in range(length))
def hash_password(password):
return ph.hash(password)
def restart_authelia():
try:
subprocess.run(['docker', 'restart', 'authelia'], check=True, capture_output=True)
return True
except Exception as e:
print(f"Failed to restart authelia: {e}")
return False
def load_users():
with open(USERS_FILE, 'r') as f:
return yaml.safe_load(f) or {'users': {}}
def save_users(data):
with open(USERS_FILE, 'w') as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
def get_all_services():
"""Extract all unique services from SERVICE_GROUPS"""
all_services = set()
for services in SERVICE_GROUPS.values():
all_services.update(services)
return sorted(all_services)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/groups')
def get_groups():
return jsonify({
'groups': list(SERVICE_GROUPS.keys()),
'services': SERVICE_GROUPS
})
@app.route('/api/services')
def get_services():
"""Return all available services"""
return jsonify({
'services': get_all_services()
})
@app.route('/api/users')
def get_users():
data = load_users()
users = []
for username, info in data['users'].items():
# Get services from groups
group_services = set()
for group in info.get('groups', []):
group_services.update(SERVICE_GROUPS.get(group, []))
# Get individual services
individual_services = info.get('services', [])
users.append({
'username': username,
'email': info.get('email', ''),
'displayname': info.get('displayname', ''),
'groups': info.get('groups', []),
'group_services': sorted(group_services),
'individual_services': individual_services,
'all_services': sorted(set(list(group_services) + individual_services))
})
return jsonify(users)
@app.route('/api/users', methods=['POST'])
def add_user():
try:
data = request.json
email = data.get('email')
displayname = data.get('displayname')
groups = data.get('groups', [])
services = data.get('services', []) # Individual services
# Use email as username for consistency with Authelia login
username = email
users_data = load_users()
if username in users_data['users']:
return jsonify({'error': 'User already exists'}), 400
password = generate_password()
password_hash = hash_password(password)
users_data['users'][username] = {
'displayname': displayname,
'password': password_hash,
'email': email,
'groups': groups,
'services': services
}
save_users(users_data)
restart_authelia()
return jsonify({
'success': True,
'username': username,
'password': password
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>', methods=['DELETE'])
def delete_user(username):
try:
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
del users_data['users'][username]
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/groups', methods=['PUT'])
def update_groups(username):
try:
data = request.json
groups = data.get('groups', [])
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
users_data['users'][username]['groups'] = groups
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/services', methods=['PUT'])
def update_services(username):
"""Update individual services for a user"""
try:
data = request.json
services = data.get('services', [])
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
users_data['users'][username]['services'] = services
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/reset-password', methods=['POST'])
def reset_password(username):
try:
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
# Generate new password and hash it
new_password = generate_password()
password_hash = hash_password(new_password)
# Update the password in the database
users_data['users'][username]['password'] = password_hash
save_users(users_data)
restart_authelia()
return jsonify({
'success': True,
'username': username,
'password': new_password
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

View file

@ -0,0 +1,197 @@
#!/usr/bin/env python3
from flask import Flask, render_template, request, jsonify
import yaml
import secrets
import string
import subprocess
from argon2 import PasswordHasher
app = Flask(__name__)
SERVICE_GROUPS = {
'admins': [
'traefik.ai-impress.com',
'pgadmin.ai-impress.com',
'rabbitmq.ai-impress.com',
'n8n.ai-impress.com',
'portainer.ai-impress.com',
'status.ai-impress.com',
'supabase.ai-impress.com',
'wpp.ai-impress.com',
'odoo.ai-impress.com'
],
'developers': [
'n8n.ai-impress.com',
'portainer.ai-impress.com',
'traefik.ai-impress.com',
'status.ai-impress.com',
'odoo.ai-impress.com'
],
'analysts': [
'pgadmin.ai-impress.com',
'status.ai-impress.com',
'odoo.ai-impress.com'
],
'users': [
'wiki.ai-impress.com',
'status.ai-impress.com'
]
}
USERS_FILE = '/config/users_database.yml'
ph = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=32,
salt_len=16
)
def generate_password(length=16):
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(alphabet) for _ in range(length))
def hash_password(password):
return ph.hash(password)
def restart_authelia():
try:
subprocess.run(['docker', 'restart', 'authelia'], check=True, capture_output=True)
return True
except Exception as e:
print(f"Failed to restart authelia: {e}")
return False
def load_users():
with open(USERS_FILE, 'r') as f:
return yaml.safe_load(f) or {'users': {}}
def save_users(data):
with open(USERS_FILE, 'w') as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/groups')
def get_groups():
return jsonify({
'groups': list(SERVICE_GROUPS.keys()),
'services': SERVICE_GROUPS
})
@app.route('/api/users')
def get_users():
data = load_users()
users = []
for username, info in data['users'].items():
services = set()
for group in info.get('groups', []):
services.update(SERVICE_GROUPS.get(group, []))
users.append({
'username': username,
'email': info.get('email', ''),
'displayname': info.get('displayname', ''),
'groups': info.get('groups', []),
'services': sorted(services)
})
return jsonify(users)
@app.route('/api/users', methods=['POST'])
def add_user():
try:
data = request.json
username = data.get('username')
email = data.get('email')
displayname = data.get('displayname')
groups = data.get('groups', [])
users_data = load_users()
if username in users_data['users']:
return jsonify({'error': 'User already exists'}), 400
password = generate_password()
password_hash = hash_password(password)
users_data['users'][username] = {
'displayname': displayname,
'password': password_hash,
'email': email,
'groups': groups
}
save_users(users_data)
restart_authelia()
return jsonify({
'success': True,
'username': username,
'password': password
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>', methods=['DELETE'])
def delete_user(username):
try:
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
del users_data['users'][username]
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/groups', methods=['PUT'])
def update_groups(username):
try:
data = request.json
groups = data.get('groups', [])
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
users_data['users'][username]['groups'] = groups
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
@app.route('/api/users/<username>/reset-password', methods=['POST'])
def reset_password(username):
try:
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
# Generate new password and hash it
new_password = generate_password()
password_hash = hash_password(new_password)
# Update the password in the database
users_data['users'][username]['password'] = password_hash
save_users(users_data)
restart_authelia()
return jsonify({
'success': True,
'username': username,
'password': new_password
})
except Exception as e:
return jsonify({'error': str(e)}), 500

View file

@ -0,0 +1,241 @@
#!/usr/bin/env python3
from flask import Flask, render_template, request, jsonify
import yaml
import secrets
import string
import subprocess
from argon2 import PasswordHasher
app = Flask(__name__)
SERVICE_GROUPS = {
'admins': [
'traefik.ai-impress.com',
'pgadmin.ai-impress.com',
'rabbitmq.ai-impress.com',
'n8n.ai-impress.com',
'portainer.ai-impress.com',
'status.ai-impress.com',
'supabase.ai-impress.com',
'wpp.ai-impress.com',
'odoo.ai-impress.com'
],
'developers': [
'n8n.ai-impress.com',
'portainer.ai-impress.com',
'traefik.ai-impress.com',
'status.ai-impress.com',
'odoo.ai-impress.com'
],
'analysts': [
'pgadmin.ai-impress.com',
'status.ai-impress.com',
'odoo.ai-impress.com'
],
'users': [
'wiki.ai-impress.com',
'status.ai-impress.com'
]
}
USERS_FILE = '/config/users_database.yml'
ph = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=32,
salt_len=16
)
def generate_password(length=16):
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(alphabet) for _ in range(length))
def hash_password(password):
return ph.hash(password)
def restart_authelia():
try:
subprocess.run(['docker', 'restart', 'authelia'], check=True, capture_output=True)
return True
except Exception as e:
print(f"Failed to restart authelia: {e}")
return False
def load_users():
with open(USERS_FILE, 'r') as f:
return yaml.safe_load(f) or {'users': {}}
def save_users(data):
with open(USERS_FILE, 'w') as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
def get_all_services():
"""Extract all unique services from SERVICE_GROUPS"""
all_services = set()
for services in SERVICE_GROUPS.values():
all_services.update(services)
return sorted(all_services)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/groups')
def get_groups():
return jsonify({
'groups': list(SERVICE_GROUPS.keys()),
'services': SERVICE_GROUPS
})
@app.route('/api/services')
def get_services():
"""Return all available services"""
return jsonify({
'services': get_all_services()
})
@app.route('/api/users')
def get_users():
data = load_users()
users = []
for username, info in data['users'].items():
# Get services from groups
group_services = set()
for group in info.get('groups', []):
group_services.update(SERVICE_GROUPS.get(group, []))
# Get individual services
individual_services = info.get('services', [])
users.append({
'username': username,
'email': info.get('email', ''),
'displayname': info.get('displayname', ''),
'groups': info.get('groups', []),
'group_services': sorted(group_services),
'individual_services': individual_services,
'all_services': sorted(set(list(group_services) + individual_services))
})
return jsonify(users)
@app.route('/api/users', methods=['POST'])
def add_user():
try:
data = request.json
username = data.get('username')
email = data.get('email')
displayname = data.get('displayname')
groups = data.get('groups', [])
services = data.get('services', []) # Individual services
users_data = load_users()
if username in users_data['users']:
return jsonify({'error': 'User already exists'}), 400
password = generate_password()
password_hash = hash_password(password)
users_data['users'][username] = {
'displayname': displayname,
'password': password_hash,
'email': email,
'groups': groups,
'services': services
}
save_users(users_data)
restart_authelia()
return jsonify({
'success': True,
'username': username,
'password': password
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>', methods=['DELETE'])
def delete_user(username):
try:
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
del users_data['users'][username]
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/groups', methods=['PUT'])
def update_groups(username):
try:
data = request.json
groups = data.get('groups', [])
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
users_data['users'][username]['groups'] = groups
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/services', methods=['PUT'])
def update_services(username):
"""Update individual services for a user"""
try:
data = request.json
services = data.get('services', [])
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
users_data['users'][username]['services'] = services
save_users(users_data)
restart_authelia()
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/users/<username>/reset-password', methods=['POST'])
def reset_password(username):
try:
users_data = load_users()
if username not in users_data['users']:
return jsonify({'error': 'User not found'}), 404
# Generate new password and hash it
new_password = generate_password()
password_hash = hash_password(new_password)
# Update the password in the database
users_data['users'][username]['password'] = password_hash
save_users(users_data)
restart_authelia()
return jsonify({
'success': True,
'username': username,
'password': new_password
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

View file

@ -0,0 +1,24 @@
version: '3.8'
services:
authelia-manager:
build: .
container_name: authelia-manager
volumes:
- /opt/01-security/authelia/config:/config
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik-public
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.authelia-manager.rule=Host(`users.ai-impress.com`)"
- "traefik.http.routers.authelia-manager.entrypoints=websecure"
- "traefik.http.routers.authelia-manager.tls.certresolver=letsencrypt"
- "traefik.http.routers.authelia-manager.middlewares=authelia@file"
- "traefik.http.services.authelia-manager.loadbalancer.server.port=5000"
- "traefik.docker.network=traefik-public"
networks:
traefik-public:
external: true

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,595 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Authelia User Manager</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
h1 {
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
}
.add-user-section {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #333;
font-weight: 500;
}
input[type="text"],
input[type="email"] {
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 5px;
font-size: 14px;
}
input:focus {
outline: none;
border-color: #667eea;
}
.checkbox-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.checkbox-item {
display: flex;
flex-direction: column;
padding: 12px;
background: #f8f9fa;
border-radius: 5px;
border: 2px solid #e0e0e0;
transition: all 0.2s;
}
.checkbox-item:hover {
border-color: #667eea;
background: #f0f4ff;
}
.checkbox-header {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.checkbox-header input {
margin-right: 10px;
cursor: pointer;
}
.checkbox-header label {
margin: 0;
font-weight: 600;
color: #333;
cursor: pointer;
}
.group-services {
margin-left: 24px;
font-size: 11px;
color: #666;
line-height: 1.5;
}
.service-tag {
display: inline-block;
background: #e8eaf6;
color: #5568d3;
padding: 2px 6px;
border-radius: 3px;
margin: 2px 2px 2px 0;
font-size: 10px;
font-weight: 500;
}
.btn {
background: #667eea;
color: white;
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
.btn:hover {
background: #5568d3;
}
.btn-danger {
background: #e74c3c;
}
.btn-danger:hover {
background: #c0392b;
}
.btn-edit {
background: #3498db;
padding: 8px 16px;
font-size: 14px;
margin-right: 10px;
}
.btn-edit:hover {
background: #2980b9;
}
.btn-warning {
background: #f39c12;
padding: 8px 16px;
font-size: 14px;
margin-right: 10px;
}
.btn-warning:hover {
background: #e67e22;
}
.users-list {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.user-card {
border: 2px solid #e0e0e0;
padding: 20px;
border-radius: 8px;
margin-bottom: 15px;
transition: transform 0.2s;
}
.user-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.user-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.user-actions {
display: flex;
gap: 10px;
}
.user-info h3 {
color: #333;
margin-bottom: 5px;
}
.user-email {
color: #666;
font-size: 14px;
}
.user-groups {
margin: 10px 0;
}
.group-badge {
display: inline-block;
padding: 5px 12px;
background: #667eea;
color: white;
border-radius: 20px;
font-size: 12px;
margin-right: 5px;
margin-bottom: 5px;
}
.services-list {
margin-top: 10px;
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
}
.service-item {
color: #666;
font-size: 13px;
padding: 3px 0;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 10px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.modal-close:hover {
color: #333;
}
.password-display {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
font-family: monospace;
word-break: break-all;
margin: 10px 0;
font-size: 16px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Authelia User Manager</h1>
<p class="subtitle">Управление пользователями и доступом к сервисам</p>
</div>
<div class="add-user-section">
<h2 style="margin-bottom: 20px;">Добавить пользователя</h2>
<form id="addUserForm">
<div class="form-group">
<label for="username">Имя пользователя:</label>
<input type="text" id="username" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" required>
</div>
<div class="form-group">
<label for="displayname">Отображаемое имя:</label>
<input type="text" id="displayname" required>
</div>
<div class="form-group">
<label>Группы доступа:</label>
<div class="checkbox-group" id="groupsCheckbox"></div>
</div>
<button type="submit" class="btn">Создать пользователя</button>
</form>
</div>
<div class="users-list">
<h2 style="margin-bottom: 20px;">Список пользователей</h2>
<div id="usersList"></div>
</div>
</div>
<div id="passwordModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Пользователь создан!</h2>
<button class="modal-close" onclick="closePasswordModal()">&times;</button>
</div>
<p>Сохраните пароль в надёжном месте:</p>
<div class="password-display" id="generatedPassword"></div>
<button class="btn" onclick="closePasswordModal()">Закрыть</button>
</div>
</div>
<div id="resetPasswordModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Пароль сброшен!</h2>
<button class="modal-close" onclick="closeResetPasswordModal()">&times;</button>
</div>
<p>Новый пароль для пользователя: <strong id="resetUsername"></strong></p>
<div class="password-display" id="resetPassword"></div>
<p style="color: #e74c3c; margin-top: 10px;">Сохраните пароль в надёжном месте! Он больше не будет показан.</p>
<button class="btn" onclick="closeResetPasswordModal()">Закрыть</button>
</div>
</div>
<div id="editGroupsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Изменить группы</h2>
<button class="modal-close" onclick="closeEditGroupsModal()">&times;</button>
</div>
<p>Пользователь: <strong id="editUsername"></strong></p>
<div class="form-group" style="margin-top: 20px;">
<label>Группы доступа:</label>
<div class="checkbox-group" id="editGroupsCheckbox"></div>
</div>
<button class="btn" onclick="saveUserGroups()">Сохранить</button>
</div>
</div>
<script>
let groups = [];
let serviceGroups = {};
let currentEditUser = null;
async function loadGroups() {
const response = await fetch('/api/groups');
const data = await response.json();
groups = data.groups;
serviceGroups = data.services;
const container = document.getElementById('groupsCheckbox');
const editContainer = document.getElementById('editGroupsCheckbox');
[container, editContainer].forEach(cont => {
cont.innerHTML = '';
groups.forEach(group => {
const div = document.createElement('div');
div.className = 'checkbox-item';
const prefix = cont.id === 'groupsCheckbox' ? 'group_' : 'edit_group_';
// Get services for this group
const services = serviceGroups[group] || [];
const servicesHtml = services.length > 0
? services.map(s => `<span class="service-tag">${s}</span>`).join('')
: '<span class="service-tag">No services</span>';
div.innerHTML = `
<div class="checkbox-header">
<input type="checkbox" id="${prefix}${group}" value="${group}">
<label for="${prefix}${group}">${group}</label>
</div>
<div class="group-services">
${servicesHtml}
</div>
`;
cont.appendChild(div);
});
});
}
async function loadUsers() {
const response = await fetch('/api/users');
const users = await response.json();
const container = document.getElementById('usersList');
container.innerHTML = '';
users.forEach(user => {
const card = document.createElement('div');
card.className = 'user-card';
card.innerHTML = `
<div class="user-header">
<div class="user-info">
<h3>${user.displayname}</h3>
<p class="user-email">${user.email}</p>
</div>
<div class="user-actions">
<button class="btn btn-edit" onclick="editUserGroups('${user.username}')">Изменить группы</button>
<button class="btn btn-warning" onclick="resetUserPassword('${user.username}')">Сбросить пароль</button>
<button class="btn btn-danger" onclick="deleteUser('${user.username}')">Удалить</button>
</div>
</div>
<div class="user-groups">
${user.groups.map(g => `<span class="group-badge">${g}</span>`).join('')}
</div>
<div class="services-list">
<strong>Доступ к сервисам:</strong>
${user.services.map(s => `<div class="service-item">• ${s}</div>`).join('')}
</div>
`;
container.appendChild(card);
});
}
document.getElementById('addUserForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const email = document.getElementById('email').value;
const displayname = document.getElementById('displayname').value;
const selectedGroups = [];
groups.forEach(group => {
if (document.getElementById(`group_${group}`).checked) {
selectedGroups.push(group);
}
});
if (selectedGroups.length === 0) {
alert('Выберите хотя бы одну группу!');
return;
}
const response = await fetch('/api/users', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username,
email,
displayname,
groups: selectedGroups
})
});
const result = await response.json();
if (result.success) {
document.getElementById('generatedPassword').textContent = result.password;
document.getElementById('passwordModal').classList.add('active');
document.getElementById('addUserForm').reset();
groups.forEach(group => {
document.getElementById(`group_${group}`).checked = false;
});
loadUsers();
} else {
alert('Ошибка: ' + result.error);
}
});
async function editUserGroups(username) {
currentEditUser = username;
const response = await fetch('/api/users');
const users = await response.json();
const user = users.find(u => u.username === username);
document.getElementById('editUsername').textContent = username;
groups.forEach(group => {
const checkbox = document.getElementById(`edit_group_${group}`);
checkbox.checked = user.groups.includes(group);
});
document.getElementById('editGroupsModal').classList.add('active');
}
async function saveUserGroups() {
const selectedGroups = [];
groups.forEach(group => {
if (document.getElementById(`edit_group_${group}`).checked) {
selectedGroups.push(group);
}
});
if (selectedGroups.length === 0) {
alert('Выберите хотя бы одну группу!');
return;
}
const response = await fetch(`/api/users/${currentEditUser}/groups`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ groups: selectedGroups })
});
const result = await response.json();
if (result.success) {
closeEditGroupsModal();
loadUsers();
} else {
alert('Ошибка: ' + result.error);
}
}
async function resetUserPassword(username) {
if (!confirm(`Сбросить пароль для пользователя ${username}?`)) return;
const response = await fetch(`/api/users/${username}/reset-password`, {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
const result = await response.json();
if (result.success) {
document.getElementById('resetUsername').textContent = username;
document.getElementById('resetPassword').textContent = result.password;
document.getElementById('resetPasswordModal').classList.add('active');
loadUsers();
} else {
alert('Ошибка: ' + result.error);
}
}
async function deleteUser(username) {
if (!confirm(`Удалить пользователя ${username}?`)) return;
await fetch(`/api/users/${username}`, {method: 'DELETE'});
loadUsers();
}
function closePasswordModal() {
document.getElementById('passwordModal').classList.remove('active');
}
function closeResetPasswordModal() {
document.getElementById('resetPasswordModal').classList.remove('active');
}
function closeEditGroupsModal() {
document.getElementById('editGroupsModal').classList.remove('active');
currentEditUser = null;
}
loadGroups();
loadUsers();
setInterval(loadUsers, 5000);
</script>
</body>
</html>

View file

@ -0,0 +1,55 @@
# Обновленная конфигурация ACL с группами и odoo
# Добавьте это в configuration.yml вашего Authelia
access_control:
default_policy: deny
rules:
# Admins - полный доступ
- domain:
- traefik.ai-impress.com
- pgadmin.ai-impress.com
- rabbitmq.ai-impress.com
- n8n.ai-impress.com
- portainer.ai-impress.com
- status.ai-impress.com
- supabase.ai-impress.com
- wpp.ai-impress.com
- odoo.ai-impress.com
policy: two_factor
subject:
- "group:admins"
# Developers - tech сервисы
- domain:
- n8n.ai-impress.com
- portainer.ai-impress.com
- traefik.ai-impress.com
- status.ai-impress.com
- odoo.ai-impress.com
policy: two_factor
subject:
- "group:developers"
# Analysts - аналитика и данные
- domain:
- pgadmin.ai-impress.com
- status.ai-impress.com
- odoo.ai-impress.com
policy: two_factor
subject:
- "group:analysts"
# Users - базовый доступ
- domain:
- wiki.ai-impress.com
- status.ai-impress.com
policy: one_factor
subject:
- "group:users"
# Odoo database manager - публичный доступ (как было)
- domain: odoo.ai-impress.com
resources:
- "^/web/database.*$"
policy: bypass

720
home/get-docker.sh Normal file
View file

@ -0,0 +1,720 @@
#!/bin/sh
set -e
# Docker Engine for Linux installation script.
#
# This script is intended as a convenient way to configure docker's package
# repositories and to install Docker Engine, This script is not recommended
# for production environments. Before running this script, make yourself familiar
# with potential risks and limitations, and refer to the installation manual
# at https://docs.docker.com/engine/install/ for alternative installation methods.
#
# The script:
#
# - Requires `root` or `sudo` privileges to run.
# - Attempts to detect your Linux distribution and version and configure your
# package management system for you.
# - Doesn't allow you to customize most installation parameters.
# - Installs dependencies and recommendations without asking for confirmation.
# - Installs the latest stable release (by default) of Docker CLI, Docker Engine,
# Docker Buildx, Docker Compose, containerd, and runc. When using this script
# to provision a machine, this may result in unexpected major version upgrades
# of these packages. Always test upgrades in a test environment before
# deploying to your production systems.
# - Isn't designed to upgrade an existing Docker installation. When using the
# script to update an existing installation, dependencies may not be updated
# to the expected version, resulting in outdated versions.
#
# Source code is available at https://github.com/docker/docker-install/
#
# Usage
# ==============================================================================
#
# To install the latest stable versions of Docker CLI, Docker Engine, and their
# dependencies:
#
# 1. download the script
#
# $ curl -fsSL https://get.docker.com -o install-docker.sh
#
# 2. verify the script's content
#
# $ cat install-docker.sh
#
# 3. run the script with --dry-run to verify the steps it executes
#
# $ sh install-docker.sh --dry-run
#
# 4. run the script either as root, or using sudo to perform the installation.
#
# $ sudo sh install-docker.sh
#
# Command-line options
# ==============================================================================
#
# --version <VERSION>
# Use the --version option to install a specific version, for example:
#
# $ sudo sh install-docker.sh --version 23.0
#
# --channel <stable|test>
#
# Use the --channel option to install from an alternative installation channel.
# The following example installs the latest versions from the "test" channel,
# which includes pre-releases (alpha, beta, rc):
#
# $ sudo sh install-docker.sh --channel test
#
# Alternatively, use the script at https://test.docker.com, which uses the test
# channel as default.
#
# --mirror <Aliyun|AzureChinaCloud>
#
# Use the --mirror option to install from a mirror supported by this script.
# Available mirrors are "Aliyun" (https://mirrors.aliyun.com/docker-ce), and
# "AzureChinaCloud" (https://mirror.azure.cn/docker-ce), for example:
#
# $ sudo sh install-docker.sh --mirror AzureChinaCloud
#
# --setup-repo
#
# Use the --setup-repo option to configure Docker's package repositories without
# installing Docker packages. This is useful when you want to add the repository
# but install packages separately:
#
# $ sudo sh install-docker.sh --setup-repo
#
# ==============================================================================
# Git commit from https://github.com/docker/docker-install when
# the script was uploaded (Should only be modified by upload job):
SCRIPT_COMMIT_SHA="86415efcfe5f8d966625843da41a0f798238cce5"
# strip "v" prefix if present
VERSION="${VERSION#v}"
# The channel to install from:
# * stable
# * test
DEFAULT_CHANNEL_VALUE="stable"
if [ -z "$CHANNEL" ]; then
CHANNEL=$DEFAULT_CHANNEL_VALUE
fi
DEFAULT_DOWNLOAD_URL="https://download.docker.com"
if [ -z "$DOWNLOAD_URL" ]; then
DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL
fi
DEFAULT_REPO_FILE="docker-ce.repo"
if [ -z "$REPO_FILE" ]; then
REPO_FILE="$DEFAULT_REPO_FILE"
# Automatically default to a staging repo fora
# a staging download url (download-stage.docker.com)
case "$DOWNLOAD_URL" in
*-stage*) REPO_FILE="docker-ce-staging.repo";;
esac
fi
mirror=''
DRY_RUN=${DRY_RUN:-}
REPO_ONLY=${REPO_ONLY:-0}
while [ $# -gt 0 ]; do
case "$1" in
--channel)
CHANNEL="$2"
shift
;;
--dry-run)
DRY_RUN=1
;;
--mirror)
mirror="$2"
shift
;;
--version)
VERSION="${2#v}"
shift
;;
--setup-repo)
REPO_ONLY=1
shift
;;
--*)
echo "Illegal option $1"
;;
esac
shift $(( $# > 0 ? 1 : 0 ))
done
case "$mirror" in
Aliyun)
DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
;;
AzureChinaCloud)
DOWNLOAD_URL="https://mirror.azure.cn/docker-ce"
;;
"")
;;
*)
>&2 echo "unknown mirror '$mirror': use either 'Aliyun', or 'AzureChinaCloud'."
exit 1
;;
esac
case "$CHANNEL" in
stable|test)
;;
*)
>&2 echo "unknown CHANNEL '$CHANNEL': use either stable or test."
exit 1
;;
esac
command_exists() {
command -v "$@" > /dev/null 2>&1
}
# version_gte checks if the version specified in $VERSION is at least the given
# SemVer (Maj.Minor[.Patch]), or CalVer (YY.MM) version.It returns 0 (success)
# if $VERSION is either unset (=latest) or newer or equal than the specified
# version, or returns 1 (fail) otherwise.
#
# examples:
#
# VERSION=23.0
# version_gte 23.0 // 0 (success)
# version_gte 20.10 // 0 (success)
# version_gte 19.03 // 0 (success)
# version_gte 26.1 // 1 (fail)
version_gte() {
if [ -z "$VERSION" ]; then
return 0
fi
version_compare "$VERSION" "$1"
}
# version_compare compares two version strings (either SemVer (Major.Minor.Path),
# or CalVer (YY.MM) version strings. It returns 0 (success) if version A is newer
# or equal than version B, or 1 (fail) otherwise. Patch releases and pre-release
# (-alpha/-beta) are not taken into account
#
# examples:
#
# version_compare 23.0.0 20.10 // 0 (success)
# version_compare 23.0 20.10 // 0 (success)
# version_compare 20.10 19.03 // 0 (success)
# version_compare 20.10 20.10 // 0 (success)
# version_compare 19.03 20.10 // 1 (fail)
version_compare() (
set +x
yy_a="$(echo "$1" | cut -d'.' -f1)"
yy_b="$(echo "$2" | cut -d'.' -f1)"
if [ "$yy_a" -lt "$yy_b" ]; then
return 1
fi
if [ "$yy_a" -gt "$yy_b" ]; then
return 0
fi
mm_a="$(echo "$1" | cut -d'.' -f2)"
mm_b="$(echo "$2" | cut -d'.' -f2)"
# trim leading zeros to accommodate CalVer
mm_a="${mm_a#0}"
mm_b="${mm_b#0}"
if [ "${mm_a:-0}" -lt "${mm_b:-0}" ]; then
return 1
fi
return 0
)
is_dry_run() {
if [ -z "$DRY_RUN" ]; then
return 1
else
return 0
fi
}
is_wsl() {
case "$(uname -r)" in
*microsoft* ) true ;; # WSL 2
*Microsoft* ) true ;; # WSL 1
* ) false;;
esac
}
is_darwin() {
case "$(uname -s)" in
*darwin* ) true ;;
*Darwin* ) true ;;
* ) false;;
esac
}
deprecation_notice() {
distro=$1
distro_version=$2
echo
printf "\033[91;1mDEPRECATION WARNING\033[0m\n"
printf " This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version"
echo " No updates or security fixes will be released for this distribution, and users are recommended"
echo " to upgrade to a currently maintained version of $distro."
echo
printf "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue."
echo
sleep 10
}
get_distribution() {
lsb_dist=""
# Every system that we officially support has /etc/os-release
if [ -r /etc/os-release ]; then
lsb_dist="$(. /etc/os-release && echo "$ID")"
fi
# Returning an empty string here should be alright since the
# case statements don't act unless you provide an actual value
echo "$lsb_dist"
}
echo_docker_as_nonroot() {
if is_dry_run; then
return
fi
if command_exists docker && [ -e /var/run/docker.sock ]; then
(
set -x
$sh_c 'docker version'
) || true
fi
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
echo
echo "================================================================================"
echo
if version_gte "20.10"; then
echo "To run Docker as a non-privileged user, consider setting up the"
echo "Docker daemon in rootless mode for your user:"
echo
echo " dockerd-rootless-setuptool.sh install"
echo
echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode."
echo
fi
echo
echo "To run the Docker daemon as a fully privileged service, but granting non-root"
echo "users access, refer to https://docs.docker.com/go/daemon-access/"
echo
echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent"
echo " to root access on the host. Refer to the 'Docker daemon attack surface'"
echo " documentation for details: https://docs.docker.com/go/attack-surface/"
echo
echo "================================================================================"
echo
}
# Check if this is a forked Linux distro
check_forked() {
# Check for lsb_release command existence, it usually exists in forked distros
if command_exists lsb_release; then
# Check if the `-u` option is supported
set +e
lsb_release -a -u > /dev/null 2>&1
lsb_release_exit_code=$?
set -e
# Check if the command has exited successfully, it means we're in a forked distro
if [ "$lsb_release_exit_code" = "0" ]; then
# Print info about current distro
cat <<-EOF
You're using '$lsb_dist' version '$dist_version'.
EOF
# Get the upstream release info
lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]')
dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]')
# Print info about upstream distro
cat <<-EOF
Upstream release is '$lsb_dist' version '$dist_version'.
EOF
else
if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then
if [ "$lsb_dist" = "osmc" ]; then
# OSMC runs Raspbian
lsb_dist=raspbian
else
# We're Debian and don't even know it!
lsb_dist=debian
fi
dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
case "$dist_version" in
13)
dist_version="trixie"
;;
12)
dist_version="bookworm"
;;
11)
dist_version="bullseye"
;;
10)
dist_version="buster"
;;
9)
dist_version="stretch"
;;
8)
dist_version="jessie"
;;
esac
fi
fi
fi
}
do_install() {
echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA"
if command_exists docker; then
cat >&2 <<-'EOF'
Warning: the "docker" command appears to already exist on this system.
If you already have Docker installed, this script can cause trouble, which is
why we're displaying this warning and provide the opportunity to cancel the
installation.
If you installed the current Docker package using this script and are using it
again to update Docker, you can ignore this message, but be aware that the
script resets any custom changes in the deb and rpm repo configuration
files to match the parameters passed to the script.
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
user="$(id -un 2>/dev/null || true)"
sh_c='sh -c'
if [ "$user" != 'root' ]; then
if command_exists sudo; then
sh_c='sudo -E sh -c'
elif command_exists su; then
sh_c='su -c'
else
cat >&2 <<-'EOF'
Error: this installer needs the ability to run commands as root.
We are unable to find either "sudo" or "su" available to make this happen.
EOF
exit 1
fi
fi
if is_dry_run; then
sh_c="echo"
fi
# perform some very rudimentary platform detection
lsb_dist=$( get_distribution )
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
if is_wsl; then
echo
echo "WSL DETECTED: We recommend using Docker Desktop for Windows."
echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop/"
echo
cat >&2 <<-'EOF'
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
case "$lsb_dist" in
ubuntu)
if command_exists lsb_release; then
dist_version="$(lsb_release --codename | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
fi
;;
debian|raspbian)
dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
case "$dist_version" in
13)
dist_version="trixie"
;;
12)
dist_version="bookworm"
;;
11)
dist_version="bullseye"
;;
10)
dist_version="buster"
;;
9)
dist_version="stretch"
;;
8)
dist_version="jessie"
;;
esac
;;
centos|rhel)
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
*)
if command_exists lsb_release; then
dist_version="$(lsb_release --release | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
esac
# Check if this is a forked Linux distro
check_forked
# Print deprecation warnings for distro versions that recently reached EOL,
# but may still be commonly used (especially LTS versions).
case "$lsb_dist.$dist_version" in
centos.8|centos.7|rhel.7)
deprecation_notice "$lsb_dist" "$dist_version"
;;
debian.buster|debian.stretch|debian.jessie)
deprecation_notice "$lsb_dist" "$dist_version"
;;
raspbian.buster|raspbian.stretch|raspbian.jessie)
deprecation_notice "$lsb_dist" "$dist_version"
;;
ubuntu.focal|ubuntu.bionic|ubuntu.xenial|ubuntu.trusty)
deprecation_notice "$lsb_dist" "$dist_version"
;;
ubuntu.oracular|ubuntu.mantic|ubuntu.lunar|ubuntu.kinetic|ubuntu.impish|ubuntu.hirsute|ubuntu.groovy|ubuntu.eoan|ubuntu.disco|ubuntu.cosmic)
deprecation_notice "$lsb_dist" "$dist_version"
;;
fedora.*)
if [ "$dist_version" -lt 41 ]; then
deprecation_notice "$lsb_dist" "$dist_version"
fi
;;
esac
# Run setup for each distro accordingly
case "$lsb_dist" in
ubuntu|debian|raspbian)
pre_reqs="ca-certificates curl"
apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL"
(
if ! is_dry_run; then
set -x
fi
$sh_c 'apt-get -qq update >/dev/null'
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get -y -qq install $pre_reqs >/dev/null"
$sh_c 'install -m 0755 -d /etc/apt/keyrings'
$sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" -o /etc/apt/keyrings/docker.asc"
$sh_c "chmod a+r /etc/apt/keyrings/docker.asc"
$sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list"
$sh_c 'apt-get -qq update >/dev/null'
)
if [ "$REPO_ONLY" = "1" ]; then
exit 0
fi
pkg_version=""
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
# Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel
pkg_pattern="$(echo "$VERSION" | sed 's/-ce-/~ce~.*/g' | sed 's/-/.*/g')"
search_command="apt-cache madison docker-ce | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst apt-cache madison results"
echo
exit 1
fi
if version_gte "18.09"; then
search_command="apt-cache madison docker-ce-cli | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
echo "INFO: $search_command"
cli_pkg_version="=$($sh_c "$search_command")"
fi
pkg_version="=$pkg_version"
fi
fi
(
pkgs="docker-ce${pkg_version%=}"
if version_gte "18.09"; then
# older versions didn't ship the cli and containerd as separate packages
pkgs="$pkgs docker-ce-cli${cli_pkg_version%=} containerd.io"
fi
if version_gte "20.10"; then
pkgs="$pkgs docker-compose-plugin docker-ce-rootless-extras$pkg_version"
fi
if version_gte "23.0"; then
pkgs="$pkgs docker-buildx-plugin"
fi
if version_gte "28.2"; then
pkgs="$pkgs docker-model-plugin"
fi
if ! is_dry_run; then
set -x
fi
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get -y -qq install $pkgs >/dev/null"
)
echo_docker_as_nonroot
exit 0
;;
centos|fedora|rhel)
if [ "$(uname -m)" = "s390x" ]; then
echo "Effective v27.5, please consult RHEL distro statement for s390x support."
exit 1
fi
repo_file_url="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
(
if ! is_dry_run; then
set -x
fi
if command_exists dnf5; then
$sh_c "dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core"
$sh_c "dnf5 config-manager addrepo --overwrite --save-filename=docker-ce.repo --from-repofile='$repo_file_url'"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "dnf5 config-manager setopt \"docker-ce-*.enabled=0\""
$sh_c "dnf5 config-manager setopt \"docker-ce-$CHANNEL.enabled=1\""
fi
$sh_c "dnf makecache"
elif command_exists dnf; then
$sh_c "dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core"
$sh_c "rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo"
$sh_c "dnf config-manager --add-repo $repo_file_url"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "dnf config-manager --set-disabled \"docker-ce-*\""
$sh_c "dnf config-manager --set-enabled \"docker-ce-$CHANNEL\""
fi
$sh_c "dnf makecache"
else
$sh_c "yum -y -q install yum-utils"
$sh_c "rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo"
$sh_c "yum-config-manager --add-repo $repo_file_url"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "yum-config-manager --disable \"docker-ce-*\""
$sh_c "yum-config-manager --enable \"docker-ce-$CHANNEL\""
fi
$sh_c "yum makecache"
fi
)
if [ "$REPO_ONLY" = "1" ]; then
exit 0
fi
pkg_version=""
if command_exists dnf; then
pkg_manager="dnf"
pkg_manager_flags="-y -q --best"
else
pkg_manager="yum"
pkg_manager_flags="-y -q"
fi
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
if [ "$lsb_dist" = "fedora" ]; then
pkg_suffix="fc$dist_version"
else
pkg_suffix="el"
fi
pkg_pattern="$(echo "$VERSION" | sed 's/-ce-/\\\\.ce.*/g' | sed 's/-/.*/g').*$pkg_suffix"
search_command="$pkg_manager list --showduplicates docker-ce | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst $pkg_manager list results"
echo
exit 1
fi
if version_gte "18.09"; then
# older versions don't support a cli package
search_command="$pkg_manager list --showduplicates docker-ce-cli | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)"
fi
# Cut out the epoch and prefix with a '-'
pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)"
fi
fi
(
pkgs="docker-ce$pkg_version"
if version_gte "18.09"; then
# older versions didn't ship the cli and containerd as separate packages
if [ -n "$cli_pkg_version" ]; then
pkgs="$pkgs docker-ce-cli-$cli_pkg_version containerd.io"
else
pkgs="$pkgs docker-ce-cli containerd.io"
fi
fi
if version_gte "20.10"; then
pkgs="$pkgs docker-compose-plugin docker-ce-rootless-extras$pkg_version"
fi
if version_gte "23.0"; then
pkgs="$pkgs docker-buildx-plugin docker-model-plugin"
fi
if ! is_dry_run; then
set -x
fi
$sh_c "$pkg_manager $pkg_manager_flags install $pkgs"
)
echo_docker_as_nonroot
exit 0
;;
sles)
echo "Effective v27.5, please consult SLES distro statement for s390x support."
exit 1
;;
*)
if [ -z "$lsb_dist" ]; then
if is_darwin; then
echo
echo "ERROR: Unsupported operating system 'macOS'"
echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
echo
exit 1
fi
fi
echo
echo "ERROR: Unsupported distribution '$lsb_dist'"
echo
exit 1
;;
esac
exit 1
}
# wrapped up in a function so that we have some protection against only getting
# half the file during "curl | sh"
do_install

View file

@ -0,0 +1,62 @@
modules:
http_2xx:
prober: http
timeout: 10s
http:
valid_status_codes: [200, 301, 302]
method: GET
preferred_ip_protocol: "ip4"
http_post_2xx:
prober: http
timeout: 10s
http:
method: POST
preferred_ip_protocol: "ip4"
tcp_connect:
prober: tcp
timeout: 10s
pop3s_banner:
prober: tcp
tcp:
query_response:
- expect: "^+OK"
tls: true
tls_config:
insecure_skip_verify: false
ssh_banner:
prober: tcp
timeout: 10s
tcp:
query_response:
- expect: "^SSH-"
irc_banner:
prober: tcp
timeout: 10s
tcp:
query_response:
- send: "NICK prober"
- send: "USER prober prober prober :prober"
- expect: "PING :([^ ]+)"
- send: "PONG :${1}"
- expect: "^:[^ ]+ 001"
icmp:
prober: icmp
timeout: 10s
icmp:
preferred_ip_protocol: "ip4"
# SSL certificate check
ssl_check:
prober: http
timeout: 10s
http:
method: GET
preferred_ip_protocol: "ip4"
tls_config:
insecure_skip_verify: false

View file

@ -0,0 +1,19 @@
version: '3.8'
services:
blackbox-exporter:
image: prom/blackbox-exporter:latest
container_name: blackbox-exporter
restart: unless-stopped
ports:
- "9115:9115"
volumes:
- ./blackbox.yml:/etc/blackbox_exporter/config.yml
command:
- '--config.file=/etc/blackbox_exporter/config.yml'
networks:
- monitoring_monitoring
networks:
monitoring_monitoring:
external: true

View file

@ -0,0 +1,76 @@
global:
scrape_interval: 30s
evaluation_interval: 30s
scrape_timeout: 10s
# Оптимизация retention политики
storage:
tsdb:
retention: 15d
retention_size: 10GB
out_of_order_time_window: 2h
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
scrape_interval: 30s
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
scrape_interval: 30s
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
scrape_interval: 30s
metric_relabel_configs:
- source_labels: [__name__]
regex: '(container_tasks_state|container_memory_failures_total)'
action: drop
- job_name: 'alertmanager'
static_configs:
- targets: ['alertmanager:9093']
scrape_interval: 30s
- job_name: 'blackbox-ssl'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://ai-impress.com
- https://auth.ai-impress.com
- https://marketing.ai-impress.com
- https://n8n.ai-impress.com
- https://odoo.ai-impress.com
- https://pgadmin.ai-impress.com
- https://portainer.ai-impress.com
- https://rabbitmq.ai-impress.com
- https://social.ai-impress.com
- https://status.ai-impress.com
- https://supabase.ai-impress.com
- https://traefik.ai-impress.com
- https://uploads.ai-impress.com
- https://vault-admin.ai-impress.com
- https://vault.ai-impress.com
- https://webhook.ai-impress.com
- https://wiki.ai-impress.com
- https://wpp.ai-impress.com
- https://www.ai-impress.com
scrape_interval: 5m
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115

Some files were not shown because too many files have changed in this diff Show more