Merge branch 'main' into metube

This commit is contained in:
forust committed 2025-11-16 16:22:30 +01:00
commit 8e4dae027e
15 files changed
+228 -192

No files matched your search

+26 -17
View File
@@ -1,18 +1,27 @@
.sync.ffs_db # FreeFileSync
volumes/*
traefik/certs/*
letsencrypt/acme.json
models/*
certs/*
.env*
traefik/letsencrypt/acme.json
edu_master/volumes/
dockmon/dockmon_data/
portainer_data/*
adguardhome/*
.python-version
gitea/*
traefik/dynamic/.services gigaforust copy.yml
traefik/dynamic/.services workstation.yml
traefik/logs/*
sync.ffs_lock sync.ffs_lock
.sync.ffs_db
# Environment
.env
.env.anna
.env.forust
# Volumes and data directories
gitea/gitea-db/*
gitea/gitea-data/*
n8n/n8n-data/*
n8n/n8n-node-data/*
adguardhome/data/*
dockmon/data/*
portainer/portainer_data/*
# Traefik files
traefik/letsencrypt/acme.json
traefik/logs/*
traefik/certs/*
# Python
.python-version
.venv/
venv/
@@ -12,8 +12,8 @@ services:
# - "68:68/tcp" # DHCP # - "68:68/tcp" # DHCP
- "3000:3000/tcp" - "3000:3000/tcp"
volumes: volumes:
- ./adguardhome/work:/opt/adguardhome/work - ./data/work:/opt/adguardhome/work
- ./adguardhome/conf:/opt/adguardhome/conf - ./data/conf:/opt/adguardhome/conf
networks: networks:
- traefik-proxy - traefik-proxy
labels: labels:
@@ -21,7 +21,7 @@ services:
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
- glance.name=adguard - glance.name=adguard
# - glance.icon=si:adguard # - glance.icon=si:adguard
- glance.url=https://adguard.${HOST}/ - glance.url=https://adguard.forust.xyz/
- glance.description=AdGuard Home is a network-wide software for blocking ads. - glance.description=AdGuard Home is a network-wide software for blocking ads.
networks: networks:
traefik-proxy: traefik-proxy:
@@ -8,7 +8,7 @@ services:
environment: environment:
- TZ=Europe/Bratislava - TZ=Europe/Bratislava
volumes: volumes:
- ./volumes/dockmon:/app/data - ./data:/app/data
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
healthcheck: healthcheck:
test: ["CMD", "curl", "-k", "-f", "https://localhost:443/health"] test: ["CMD", "curl", "-k", "-f", "https://localhost:443/health"]
@@ -22,7 +22,7 @@ services:
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
- glance.name=dockmon - glance.name=dockmon
# - glance.icon=sh:dockmon # - glance.icon=sh:dockmon
- glance.url=https://dockmon.${HOST}/ - glance.url=https://dockmon.forust.xyz/
- glance.description=Dockmon is a lightweight Docker container monitoring and management tool with a user-friendly web interface. - glance.description=Dockmon is a lightweight Docker container monitoring and management tool with a user-friendly web interface.
networks: networks:
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.11-slim FROM python:3.9-alpine
WORKDIR /app WORKDIR /app
+74 -64
View File
@@ -1,10 +1,3 @@
# Раздел "ТВАРИ, ЧИТАТЕ ВНИМАТЕЛЬНО!"
# Когда обновят сайт -- расскоментровать 641-646 и закоментировать 647.
# Поменять комменты 671 и 672 местами.
import requests import requests
import json import json
import asyncio import asyncio
@@ -89,7 +82,7 @@ def get_day_statistics(day_blocks: List[Dict]) -> Dict[str, int]:
# ============================================================================ # ============================================================================
def parse_html(html: str) -> List[Dict]: def parse_html(html: str) -> List[Dict]:
"""Парсит HTML с графиком отключений (нова логіка)""" """Парсит HTML с графиком отключений (логика от 15.11.2024)"""
soup = BeautifulSoup(html, "html.parser") soup = BeautifulSoup(html, "html.parser")
cells = soup.select(".disconnection-detailed-table-cell.cell") cells = soup.select(".disconnection-detailed-table-cell.cell")
@@ -103,56 +96,71 @@ def parse_html(html: str) -> List[Dict]:
cell_classes = cell.get('class', []) cell_classes = cell.get('class', [])
# Перевіряємо чи вся година вимкнена # ПРоверка статуса отключения на весь час
full_hour_off = 'has_disconnection' in cell_classes and 'full_hour' in cell_classes full_hour_off = 'has_disconnection' in cell_classes and 'full_hour' in cell_classes
no_disconnection = 'no_disconnection' in cell_classes no_disconnection_cell = 'no_disconnection' in cell_classes
hour_block = cell.select_one(".hour_block") hour_block = cell.select_one(".hour_block")
if not hour_block: if not hour_block:
continue continue
# Визначаємо статус та підтвердження для всієї години # Проверка подтверждённости отключения для всего часа
confirmed = None cell_confirmed = None
if 'confirm_1' in cell_classes: if 'confirm_1' in cell_classes:
confirmed = True cell_confirmed = True
elif 'confirm_0' in cell_classes: elif 'confirm_0' in cell_classes:
cell_confirmed = False
# Проверка половин часа
left = hour_block.select_one(".half.left")
right = hour_block.select_one(".half.right")
def parse_half(half, is_full_hour_off: bool) -> Dict:
"""Парсит половину часа"""
if not half:
return {"status": "on", "queue": None, "confirmed": None}
half_classes = half.get('class', [])
# Если вся ячейка full_hour - используем статус ячейки
if is_full_hour_off:
return {"status": "off", "queue": None, "confirmed": cell_confirmed}
# Определяем статус половины
if 'has_disconnection' in half_classes:
status = "off"
elif 'no_disconnection' in half_classes:
status = "on"
else:
status = "on" # По умолчанию считаем включенным
# Если выключено - ищем подробности
queue = None
confirmed = None
if status == "off":
disconnection_div = half.select_one(".disconnection")
if disconnection_div:
# Ищем номер черги в title
if disconnection_div.has_attr("title"):
title = disconnection_div["title"]
if "Номер черги" in title or "Номер черги:" in title:
try:
queue = title.split(":")[-1].strip()
except:
pass
# Определяем подтверждение
disc_classes = disconnection_div.get('class', [])
if 'disconnection_confirm_1' in disc_classes:
confirmed = True
elif 'disconnection_confirm_0' in disc_classes:
confirmed = False confirmed = False
# Шукаємо часткові відключення через div.fill return {"status": status, "queue": queue, "confirmed": confirmed}
fill_divs = hour_block.select(".fill")
first_half_data = {"status": "on", "queue": None, "confirmed": None} first_half_data = parse_half(left, full_hour_off)
second_half_data = {"status": "on", "queue": None, "confirmed": None} second_half_data = parse_half(right, full_hour_off)
if full_hour_off:
# Вся година вимкнена
first_half_data = {"status": "off", "queue": None, "confirmed": confirmed}
second_half_data = {"status": "off", "queue": None, "confirmed": confirmed}
elif no_disconnection and not fill_divs:
# Вся година ввімкнена
first_half_data = {"status": "on", "queue": None, "confirmed": None}
second_half_data = {"status": "on", "queue": None, "confirmed": None}
else:
# Перевіряємо часткові відключення
for fill_div in fill_divs:
title = fill_div.get('title', '')
style = fill_div.get('style', '')
fill_classes = fill_div.get('class', [])
# Визначаємо підтвердження
fill_confirmed = None
if 'confirmed' in fill_classes:
fill_confirmed = True
elif 'possible' in fill_classes:
fill_confirmed = False
# Парсимо стиль для визначення часу
# --start:0;--size:50 означає 0-30 хвилин (перша половина)
# --start:50;--size:50 означає 30-60 хвилин (друга половина)
if '--start:0' in style or title.endswith(':00—' + f'{current_hour:02d}:30'):
first_half_data = {"status": "off", "queue": None, "confirmed": fill_confirmed}
elif '--start:50' in style or (f'{current_hour:02d}:30' in title and title.endswith(':00')):
second_half_data = {"status": "off", "queue": None, "confirmed": fill_confirmed}
schedule.append({ schedule.append({
"hour": current_hour, "hour": current_hour,
@@ -168,6 +176,7 @@ def parse_html(html: str) -> List[Dict]:
return schedule return schedule
def get_voe_html(city_id: int, street_id: int, house_id: int) -> str: def get_voe_html(city_id: int, street_id: int, house_id: int) -> str:
"""Получает HTML с сайта VOE""" """Получает HTML с сайта VOE"""
url = "https://www.voe.com.ua/disconnection/detailed?ajax_form=1&_wrapper_format=drupal_ajax" url = "https://www.voe.com.ua/disconnection/detailed?ajax_form=1&_wrapper_format=drupal_ajax"
@@ -582,7 +591,12 @@ async def cmd_info(message: Message):
await message.answer( await message.answer(
"<b>ℹ️ Про бота</b>\n\n" "<b>ℹ️ Про бота</b>\n\n"
"🚀 <b>Версія:</b> 2.1 (Оптимізована)\n" "🚀 <b>Версія:</b> 2.2 (Стабільна)\n\n"
"📝 <b>Реліз-ноути:</b>\n"
"├ 15.11.2024: Адаптація під оновлену логіку сайту VOE\n"
"├ Виправлено парсинг half.left та half.right\n"
"├ Покращено визначення підтвердження відключень\n"
"└ Оптимізовано обробку статусу для всієї години\n\n"
"⚡ <b>Функціональність:</b>\n" "⚡ <b>Функціональність:</b>\n"
"├ Моніторинг графіку 24/7\n" "├ Моніторинг графіку 24/7\n"
"├ Сповіщення за 5 хвилин\n" "├ Сповіщення за 5 хвилин\n"
@@ -645,21 +659,18 @@ async def cmd_tomorrow(message: Message):
return return
try: try:
# """Закоментировал до лучших времен""" await message.answer("⏳ Завантаження графіку завтра...")
# await message.answer("⏳ Завантаження графіку завтра...") html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID)
# html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID) schedule = parse_html(html)
# schedule = parse_html(html) text = format_single_day_schedule(schedule, 1)
# text = format_single_day_schedule(schedule, 1) await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard())
# await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard()) # await message.answer("❌ Функція тимчасово недоступна. Чекаємо на оновлення сайту", parse_mode="HTML", reply_markup=get_main_keyboard())
await message.answer("❌ Функція тимчасово недоступна. Чекаємо на оновлення сайту", parse_mode="HTML", reply_markup=get_main_keyboard())
except Exception as e: except Exception as e:
await message.answer( await message.answer(
f"❌ <b>Помилка:</b> {str(e)}", f"❌ <b>Помилка:</b> {str(e)}",
parse_mode="HTML", parse_mode="HTML",
reply_markup=get_main_keyboard() reply_markup=get_main_keyboard()
) )
# Когда обновят сайт -- расскоментровать 641-646 и закоментировать 647.
# Поменять комменты 671 и 672 местами.
@dp.message(Command("check")) @dp.message(Command("check"))
@@ -675,8 +686,7 @@ async def cmd_check(message: Message):
new_schedule = parse_html(html) new_schedule = parse_html(html)
prefix = "✅ <b>Знайдено зміни!</b>\n\n" if schedules_differ(last_schedule, new_schedule) else "✓ <b>Графік без змін</b>\n\n" prefix = "✅ <b>Знайдено зміни!</b>\n\n" if schedules_differ(last_schedule, new_schedule) else "✓ <b>Графік без змін</b>\n\n"
# result = prefix + format_schedule_message(new_schedule, days_to_show=2) result = prefix + format_schedule_message(new_schedule, days_to_show=2)
result = prefix + format_schedule_message(new_schedule, days_to_show=1)
await message.answer(result, parse_mode="HTML", reply_markup=get_main_keyboard()) await message.answer(result, parse_mode="HTML", reply_markup=get_main_keyboard())
except Exception as e: except Exception as e:
@@ -703,9 +713,9 @@ async def handle_text(message: Message):
elif text == "📅 Сьогодні": elif text == "📅 Сьогодні":
await cmd_today(message) await cmd_today(message)
# # Кнопка "Завтра" # Кнопка "Завтра"
# elif text == "📅 Завтра": elif text == "📅 Завтра":
# await cmd_tomorrow(message) await cmd_tomorrow(message)
# Кнопка "Оновити" # Кнопка "Оновити"
elif text == "🔄 Оновити": elif text == "🔄 Оновити":
@@ -733,7 +743,7 @@ async def handle_text(message: Message):
async def main(): async def main():
"""Главная функция""" """Главная функция"""
logger.info("=" * 50) logger.info("=" * 50)
logger.info("🚀 ЗАПУСК БОТА V2.1 (ОПТИМІЗОВАНА ВЕРСІЯ)") logger.info("ЗАПУСК БОТА V2.2 (stable 2.2, 15.11.2025)")
logger.info("=" * 50) logger.info("=" * 50)
if not TELEGRAM_TOKEN or TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "YOUR_TOKEN_HERE") if not TELEGRAM_TOKEN or TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "YOUR_TOKEN_HERE")
@@ -744,7 +754,7 @@ async def main():
logger.error("❌ ALLOWED_CHAT_IDS не конфігуровані! Напишіть ID в .env файл") logger.error("❌ ALLOWED_CHAT_IDS не конфігуровані! Напишіть ID в .env файл")
return return
logger.info(f"🔌 Дозволені чати: {ALLOWED_CHAT_IDS}") logger.info(f"📌 Allowed chat ids: {ALLOWED_CHAT_IDS}")
logger.info(f"⏱ Інтервал перевірки: {CHECK_INTERVAL} сек") logger.info(f"⏱ Інтервал перевірки: {CHECK_INTERVAL} сек")
logger.info("=" * 50) logger.info("=" * 50)
@@ -1,6 +1,6 @@
x-app: &app x-app: &app
build: build:
context: ./edu_master/backend/ context: ./backend/
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
- VERSION=${VERSION:-v0.10.2} - VERSION=${VERSION:-v0.10.2}
@@ -100,8 +100,8 @@ services:
nginx: nginx:
image: nginx:alpine image: nginx:alpine
volumes: volumes:
- ./edu_master/nginx/nginx.conf:/etc/nginx/conf.d/default.conf.template - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf.template
- ./edu_master/nginx/entrypoint.sh:/entrypoint.sh - ./nginx/entrypoint.sh:/entrypoint.sh
environment: environment:
- MINIO_PRIVATE_BUCKET=${MINIO_PRIVATE_BUCKET:-private} - MINIO_PRIVATE_BUCKET=${MINIO_PRIVATE_BUCKET:-private}
- MINIO_PUBLIC_BUCKET=${MINIO_PUBLIC_BUCKET:-public} - MINIO_PUBLIC_BUCKET=${MINIO_PUBLIC_BUCKET:-public}
@@ -142,7 +142,7 @@ services:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z image: minio/minio:RELEASE.2024-11-07T00-52-20Z
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- ./volumes/edu_master/volumes/minio-data:/data - ./volumes/minio-data:/data
command: server /data --console-address ":9001" command: server /data --console-address ":9001"
environment: environment:
- MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL:-http://localhost/minio-console/} - MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL:-http://localhost/minio-console/}
@@ -173,7 +173,7 @@ services:
- POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_DB=${POSTGRES_DB:-postgres} - POSTGRES_DB=${POSTGRES_DB:-postgres}
volumes: volumes:
- ./volumes/edu_master/volumes/postgres-db:/var/lib/postgresql/data - ./volumes/postgres-db:/var/lib/postgresql/data
healthcheck: healthcheck:
test: [ "CMD-SHELL", "pg_isready" ] test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s interval: 10s
@@ -195,7 +195,7 @@ services:
image: ollama/ollama:latest image: ollama/ollama:latest
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- ./volumes/edu_master/volumes/ollama-models:/root/.ollama - ./volumes/ollama-models:/root/.ollama
environment: environment:
- OLLAMA_DISABLE_TELEMETRY=true - OLLAMA_DISABLE_TELEMETRY=true
- OLLAMA_KEEP_ALIVE=5m - OLLAMA_KEEP_ALIVE=5m
@@ -235,7 +235,7 @@ services:
phpsessid-bot: phpsessid-bot:
build: build:
context: edu_master/phpsessid_bot/ context: ./phpsessid_bot/
dockerfile: Dockerfile dockerfile: Dockerfile
restart: unless-stopped restart: unless-stopped
environment: environment:
+3
View File
@@ -0,0 +1,3 @@
GITEA_POSTGRES_USER=
GITEA_POSTGRES_PASSWORD=
GITEA_POSTGRES_DB=gitea
@@ -5,27 +5,28 @@ services:
environment: environment:
- USER_UID=1000 - USER_UID=1000
- USER_GID=1000 - USER_GID=1000
# Database
- GITEA__database__DB_TYPE=postgres - GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432 - GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea - GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea - GITEA__database__PASSWD=gitea
- GITEA__server__DOMAIN=gitea.forust.xyz - GITEA__database__NAME=gitea
- GITEA__server__ROOT_URL=https://gitea.forust.xyz/ #Server
- GITEA__server__SSH_DOMAIN=gitea.forust.xyz - GITEA__server__ROOT_URL=https://gitea.forust.xyz
- GITEA__server__SSH_PORT=2221
restart: always restart: always
networks: networks:
- gitea - gitea-db
- traefik-proxy - traefik-proxy
volumes: volumes:
- ./gitea:/data - ./gitea-data:/data
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
ports: ports:
- "222:22" - "2221:22"
depends_on: depends_on:
- db - db
@@ -37,12 +38,12 @@ services:
- POSTGRES_PASSWORD=gitea - POSTGRES_PASSWORD=gitea
- POSTGRES_DB=gitea - POSTGRES_DB=gitea
networks: networks:
- gitea - gitea-db
volumes: volumes:
- ./volumes/gitea/postgres:/var/lib/postgresql/data - ./gitea-db/:/var/lib/postgresql/data
networks: networks:
gitea: gitea-db:
external: false external: false
traefik-proxy: traefik-proxy:
external: true external: true
@@ -4,8 +4,8 @@ services:
image: glanceapp/glance image: glanceapp/glance
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- ./glance/config:/app/config - ./config:/app/config:ro
- ./glance/assets:/app/assets - ./assets:/app/assets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
env_file: .env env_file: .env
+6 -3
View File
@@ -1,6 +1,7 @@
services: services:
n8n: n8n:
image: docker.n8n.io/n8nio/n8n image: docker.n8n.io/n8nio/n8n
container_name: n8n
restart: unless-stopped restart: unless-stopped
environment: environment:
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
@@ -12,8 +13,8 @@ services:
- N8N_SECURE_COOKIE=false - N8N_SECURE_COOKIE=false
- N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
volumes: volumes:
- ./volumes/n8n_data:/home/node/.n8n - ./n8n-node-data:/home/node/.n8n
- ./volumes/n8n_local-files:/files - ./n8n-files:/files
extra_hosts: extra_hosts:
- "enterprise.n8n.io:104.26.13.187" - "enterprise.n8n.io:104.26.13.187"
- "enterprise.n8n.io:104.26.12.187" - "enterprise.n8n.io:104.26.12.187"
@@ -29,8 +30,10 @@ services:
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
- glance.name=n8n - glance.name=n8n
- glance.icon=si:n8n - glance.icon=si:n8n
- glance.url=https://n8n.${HOST}/ - glance.url=https://n8n.forust.xyz/
- glance.description=n8n is a workflow automation tool that enables you to connect various apps and services to automate tasks and processes. - glance.description=n8n is a workflow automation tool that enables you to connect various apps and services to automate tasks and processes.
networks: networks:
n8n: n8n:
external: false external: false
traefik-proxy:
external: true
@@ -20,7 +20,7 @@ services:
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
- glance.name=Nextcloud - glance.name=Nextcloud
# - glance.icon=si:nextcloud # - glance.icon=si:nextcloud
- glance.url=https://nextcloud.${HOST}/ - glance.url=https://nextcloud.forust.xyz/
- glance.description=Nextcloud is a suite of client-server software for creating and using file hosting services. - glance.description=Nextcloud is a suite of client-server software for creating and using file hosting services.
@@ -5,7 +5,7 @@ services:
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./volumes/portainer_data:/data - ./portainer_data:/data
ports: ports:
- 9443:9443 - 9443:9443
# - 8000:8000 # Remove if you do not intend to use Edge Agents # - 8000:8000 # Remove if you do not intend to use Edge Agents
@@ -16,7 +16,7 @@ services:
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
- glance.name=Portainer - glance.name=Portainer
# - glance.icon=si:portainer # - glance.icon=si:portainer
- glance.url=https://portainer.${HOST}/ - glance.url=https://portainer.forust.xyz/
- glance.description=Portainer is a lightweight management UI which allows you to easily manage your Docker environments. - glance.description=Portainer is a lightweight management UI which allows you to easily manage your Docker environments.
volumes: volumes:
@@ -4,30 +4,37 @@ services:
container_name: traefik container_name: traefik
restart: unless-stopped restart: unless-stopped
command: command:
# API
- "--api.insecure=false" - "--api.insecure=false"
- "--api.dashboard=true" - "--api.dashboard=true"
# Providers
- "--providers.docker=true" - "--providers.docker=true"
- "--providers.docker.exposedbydefault=false" - "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik-proxy" - "--providers.docker.network=traefik-proxy"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
# EntryPoints
- "--entryPoints.web.address=:80" - "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443" - "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.tls=true" - "--entryPoints.websecure.http.tls=true"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure" - "--entryPoints.web.http.redirections.entryPoint.to=websecure"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https" - "--entryPoints.web.http.redirections.entryPoint.scheme=https"
- "--providers.file.directory=/etc/traefik/dynamic" - "--entryPoints.ssh.address=:2221"
- "--providers.file.watch=true"
# Let's Encrypt STAGING # Let's Encrypt STAGING. CURRENTLY USING CF ORIGIN CA INSTEAD
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}" # - "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" # - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
# - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" # - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" # - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" # - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
# Cloudflare - получение реальных IP # Cloudflare
- "--entryPoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22" - "--entryPoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22"
- "--entryPoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22" - "--entryPoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22"
# Logging
- "--log.level=INFO" - "--log.level=INFO"
- "--log.filePath=/var/log/traefik/traefik.log" - "--log.filePath=/var/log/traefik/traefik.log"
- "--accesslog=true" - "--accesslog=true"
@@ -37,7 +44,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-proxy" - "traefik.docker.network=traefik-proxy"
- glance.name=Traefik - glance.name=Traefik
- glance.url=https://traefik.${HOST}/ - glance.url=https://traefik.forust.xyz/
- glance.description=Traefik is a modern reverse proxy and load balancer - glance.description=Traefik is a modern reverse proxy and load balancer
ports: ports:
@@ -46,10 +53,10 @@ services:
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/letsencrypt:/letsencrypt - ./dynamic:/etc/traefik/dynamic:ro
- ./traefik/dynamic:/etc/traefik/dynamic:ro - ./certs:/certs:ro
- ./traefik/logs:/var/log/traefik - ./logs:/var/log/traefik
- ./traefik/certs:/certs:ro # - ./traefik/letsencrypt:/letsencrypt
networks: networks:
- traefik-proxy - traefik-proxy
@@ -1,6 +1,6 @@
http: http:
routers: routers:
# Traefik Dashboard # Traefik Dashboard (ADMIN, local only)
traefik-dashboard: traefik-dashboard:
rule: "Host(`traefik.workstation`)" rule: "Host(`traefik.workstation`)"
entryPoints: entryPoints:
@@ -10,7 +10,16 @@ http:
# - auth # - auth
- security-headers - security-headers
tls: {} tls: {}
# certResolver: letsencrypt
# Nextcloud AIO Interface (ADMIN, local is better)
nextcloud-aio:
rule: "Host(`nextcloud-aio.workstation`)"
entryPoints:
- websecure
service: nextcloud-aio
middlewares:
- security-headers
tls: {}
# MeTube # MeTube
metube: metube:
@@ -24,7 +33,7 @@ http:
tls: tls:
certResolver: letsencrypt certResolver: letsencrypt
# Portainer # Portainer (public (account required))
portainer: portainer:
rule: "Host(`portainer.forust.xyz`)" rule: "Host(`portainer.forust.xyz`)"
entryPoints: entryPoints:
@@ -32,31 +41,20 @@ http:
service: portainer service: portainer
middlewares: middlewares:
- security-headers - security-headers
tls: tls: {}
certResolver: letsencrypt
# AdGuard Home # AdGuard Home (public (account required))
adguard: adguard:
rule: "Host(`adguard.forust.xyz`)" rule: "Host(`adguard.forust.xyz`)"
entryPoints: entryPoints:
- websecure - websecure
service: adguard service: adguard
middlewares:
- security-headers
tls:
certResolver: letsencrypt
# Nextcloud AIO Interface (локальный)
nextcloud-aio:
rule: "Host(`nextcloud-aio.workstation`)"
entryPoints:
- websecure
service: nextcloud-aio
middlewares: middlewares:
- security-headers - security-headers
tls: {} tls: {}
# Nextcloud Main
# Nextcloud Main (public (account required))
nextcloud: nextcloud:
rule: "Host(`nextcloud.forust.xyz`)" rule: "Host(`nextcloud.forust.xyz`)"
entrypoints: entrypoints:
@@ -64,10 +62,9 @@ http:
service: nextcloud service: nextcloud
middlewares: middlewares:
- nextcloud-chain - nextcloud-chain
tls: tls: {}
certResolver: letsencrypt
# Dockmon # Dockmon (public (account required))
dockmon: dockmon:
rule: "Host(`dockmon.forust.xyz`)" rule: "Host(`dockmon.forust.xyz`)"
entryPoints: entryPoints:
@@ -76,20 +73,30 @@ http:
middlewares: middlewares:
# - dockmon-auth # - dockmon-auth
- security-headers - security-headers
tls: tls: {}
certResolver: letsencrypt
# Glance (локальный) # Gitea (public (account required))
glance: gitea:
rule: "Host(`glance.workstation`)" rule: "Host(`gitea.forust.xyz`)"
entryPoints: entryPoints:
- websecure - websecure
service: glance - ssh
service: gitea
middlewares: middlewares:
- security-headers - security-headers
tls: {} tls: {}
# Watercrawl (локальный) # # Glance (local only)
# glance:
# rule: "Host(`glance.workstation`)"
# entryPoints:
# - websecure
# service: glance
# middlewares:
# - security-headers
# tls: {}
# Watercrawl (local only)
watercrawl: watercrawl:
rule: "Host(`watercrawl.workstation`)" rule: "Host(`watercrawl.workstation`)"
entryPoints: entryPoints:
@@ -99,7 +106,7 @@ http:
- security-headers - security-headers
tls: {} tls: {}
# N8N # N8N (local only)
n8n: n8n:
rule: "Host(`n8n.workstation`)" rule: "Host(`n8n.workstation`)"
entryPoints: entryPoints:
@@ -108,18 +115,7 @@ http:
middlewares: middlewares:
- security-headers - security-headers
tls: {} tls: {}
# certResolver: letsencrypt
# Gitea
gitea:
rule: "Host(`gitea.forust.xyz`)"
entryPoints:
- websecure
service: gitea
middlewares:
- security-headers
tls:
certResolver: letsencrypt
services: services:
# Portainer # Portainer
@@ -155,11 +151,11 @@ http:
- url: "https://dockmon:443" - url: "https://dockmon:443"
serversTransport: insecureTransport serversTransport: insecureTransport
# Glance # # Glance
glance: # glance:
loadBalancer: # loadBalancer:
servers: # servers:
- url: "http://glance:8080" # - url: "http://glance:8080"
# Watercrawl # Watercrawl
watercrawl: watercrawl:
@@ -202,21 +198,21 @@ http:
- "vv:$2y$05$oO.mQXBZSJq7aDRJF1iorudafu9HsrJCEfKb6Cev8UlzWOF8kzlY6" - "vv:$2y$05$oO.mQXBZSJq7aDRJF1iorudafu9HsrJCEfKb6Cev8UlzWOF8kzlY6"
realm: "MeTube Access" realm: "MeTube Access"
# Basic Auth для Traefik Dashboard # Basic Auth Traefik Dashboard
auth: auth:
basicAuth: basicAuth:
users: users:
- "admin:$$apr1$$57E60OUM$$JoYwmLr/uZKaTy6U4IQd9." - "admin:$$apr1$$57E60OUM$$JoYwmLr/uZKaTy6U4IQd9."
realm: "Traefik Dashboard" realm: "Traefik Dashboard"
# Basic Auth для Dockmon # Basic Auth Dockmon
dockmon-auth: dockmon-auth:
basicAuth: basicAuth:
users: users:
- "admin:$$apr1$$.bCpmIHl$$dxPEKdw5aZLAwo8wUz52b1" - "admin:$$apr1$$.bCpmIHl$$dxPEKdw5aZLAwo8wUz52b1"
realm: "Dockmon Access" realm: "Dockmon Access"
# Cloudflare IP Whitelist (опционально) # Cloudflare IP Whitelist
cloudflare-ipwhitelist: cloudflare-ipwhitelist:
ipWhiteList: ipWhiteList:
sourceRange: sourceRange:
@@ -272,3 +268,29 @@ http:
middlewares: middlewares:
- nextcloud-secure-headers - nextcloud-secure-headers
- security-headers - security-headers
# TLS Configuration
tls:
certificates:
# Cloudflare Origin CA *.forust.xyz
- certFile: /certs/cloudflare.pem
keyFile: /certs/cloudflare.key
# Local certificate
- certFile: /certs/workstation+1.pem
keyFile: /certs/workstation+1-key.pem
stores:
default:
defaultCertificate:
# Fallback local certificate
certFile: /certs/workstation+1.pem
keyFile: /certs/workstation+1-key.pem
options:
default:
minVersion: VersionTLS12
sniStrict: true
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
-19
View File
@@ -1,19 +0,0 @@
tls:
certificates:
- certFile: /certs/workstation+1.pem
keyFile: /certs/workstation+1-key.pem
stores:
default:
defaultCertificate:
certFile: /certs/cloudflare.pem
keyFile: /certs/cloudflare.key
options:
default:
minVersion: VersionTLS12
sniStrict: true
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305