Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3be9556eb4
|
|||
|
e952c8161a
|
|||
|
8d665a7f34
|
|||
|
6e4f8c06b4
|
|||
|
d7c05fd058
|
|||
| c13056fba1 | |||
|
5fe8af82d5
|
|||
| 6d97246997 | |||
| 6970279311 | |||
|
02f4e0ab42
|
|||
|
4a25622552
|
|||
|
0138fbd276
|
|||
|
94b5f39207
|
|||
| 403e88d548 | |||
| d03a4844fb | |||
| 48ff08529e | |||
|
81592b6142
|
|||
| 9480576966 | |||
| 9b43a9bef4 | |||
| 2b03335af5 | |||
| ea738ec14c | |||
|
e4e9d96a0b
|
|||
| 1f1e13ff39 | |||
| c80a6c5351 | |||
| bfb21adff7 | |||
| 1c75382a6e | |||
| 9c5e037567 | |||
| 9f784d2c31 | |||
| a07e27bff6 | |||
| 8cd122d50d | |||
| f531393481 | |||
| ce43b34ce0 | |||
| ddb755e7e5 | |||
| 682a5b949f | |||
| 6c72acc59e | |||
| b381c7b012 | |||
| a3c12855fe | |||
| bbd374590e | |||
| fd72b415c5 | |||
| 97f6aeb538 |
+7
-2
@@ -2,7 +2,8 @@
|
|||||||
sync.ffs_lock
|
sync.ffs_lock
|
||||||
.sync.ffs_db
|
.sync.ffs_db
|
||||||
|
|
||||||
|
# Copyparty
|
||||||
|
*.hist/
|
||||||
|
|
||||||
# Volumes and data directories
|
# Volumes and data directories
|
||||||
gitea/gitea-db/
|
gitea/gitea-db/
|
||||||
@@ -21,6 +22,9 @@ downtify/Downtify_downloads
|
|||||||
headscale/config/*
|
headscale/config/*
|
||||||
headscale/data/*
|
headscale/data/*
|
||||||
|
|
||||||
|
# Steaming services files
|
||||||
|
streaming/config/*
|
||||||
|
|
||||||
# Steaming services files
|
# Steaming services files
|
||||||
streaming/jellyfin/*
|
streaming/jellyfin/*
|
||||||
streaming/jellyseerr/*
|
streaming/jellyseerr/*
|
||||||
@@ -32,10 +36,11 @@ streaming/prowlarr/*
|
|||||||
|
|
||||||
# Homepage
|
# Homepage
|
||||||
homepages/forust_files/assets/images/team/*
|
homepages/forust_files/assets/images/team/*
|
||||||
|
homepages/forust_files/.well-known/*
|
||||||
|
|
||||||
# Traefik files
|
# Traefik files
|
||||||
traefik/letsencrypt/acme.json
|
traefik/letsencrypt/acme.json
|
||||||
|
traefik/dynamic/fileservers.yml
|
||||||
traefik/logs/*
|
traefik/logs/*
|
||||||
|
|
||||||
# SSL Certificates
|
# SSL Certificates
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CMK_PASSWORD=password
|
||||||
|
TZ=Europe/Berlin
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
services:
|
||||||
|
checkmk:
|
||||||
|
image: "checkmk/check-mk-raw:2.4.0-latest"
|
||||||
|
container_name: "checkmk"
|
||||||
|
environment:
|
||||||
|
- CMK_PASSWORD=${CMK_PASSWORD:-password}
|
||||||
|
- CMK_SITE_ID=cmk
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- sites:/omd/sites
|
||||||
|
tmpfs:
|
||||||
|
- /opt/omd/sites/cmk/tmp:uid=1000,gid=1000
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
- 6776:8000
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=proxy"
|
||||||
|
- "traefik.http.services.checkmk.loadbalancer.server.port=5000"
|
||||||
|
|
||||||
|
# Prod Router
|
||||||
|
- "traefik.http.routers.checkmk.rule=Host(`cmk.forust.xyz`)"
|
||||||
|
- "traefik.http.routers.checkmk.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.checkmk.service=checkmk"
|
||||||
|
- "traefik.http.routers.checkmk.middlewares=security-headers@file"
|
||||||
|
- "traefik.http.routers.checkmk.tls=true"
|
||||||
|
|
||||||
|
# Local Router
|
||||||
|
- "traefik.http.routers.checkmk-local.rule=Host(`cmk.workstation.internal`) || Host(`cmk.internal`)"
|
||||||
|
- "traefik.http.routers.checkmk-local.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.checkmk-local.service=checkmk"
|
||||||
|
- "traefik.http.routers.checkmk-local.middlewares=security-headers@file"
|
||||||
|
- "traefik.http.routers.checkmk-local.tls=true"
|
||||||
|
|
||||||
|
# Dev Router
|
||||||
|
- "traefik.http.routers.checkmk-dev.rule=Host(`cmk.gigaforust.internal`)"
|
||||||
|
- "traefik.http.routers.checkmk-dev.middlewares=security-headers@file"
|
||||||
|
- "traefik.http.routers.checkmk-dev.service=checkmk"
|
||||||
|
- "traefik.http.routers.checkmk-dev.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.checkmk-dev.tls=true"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
volumes:
|
||||||
|
sites:
|
||||||
@@ -12,15 +12,26 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Load configuration
|
# Load configuration (adapted to .env keys)
|
||||||
LOGIN = os.getenv('EDU_LOGIN')
|
def _env(key, default=None):
|
||||||
PASSWORD = os.getenv('EDU_PASSWORD')
|
v = os.getenv(key, default)
|
||||||
URL_LOGIN = os.getenv('EDU_URL_LOGIN', 'https://edu.edu.vn.ua/user/login')
|
if isinstance(v, str) and len(v) >= 2 and ((v[0] == '"' and v[-1] == '"') or (v[0] == "'" and v[-1] == "'")):
|
||||||
URL_VERIFY = os.getenv('EDU_URL_VERIFY', 'https://edu.edu.vn.ua/course/userlist')
|
return v[1:-1]
|
||||||
INTERVAL = int(os.getenv('PHPSESSID_INTERVAL', 10))
|
return v
|
||||||
USER_AGENT = os.getenv('USER_AGENT', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36')
|
|
||||||
REDIS_HOST = os.getenv('REDIS_HOST', 'redis')
|
LOGIN = _env('KEEPER_LOGIN')
|
||||||
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
|
PASSWORD = _env('KEEPER_PASSWORD')
|
||||||
|
|
||||||
|
EDU_BASE = _env('EDU_URL_BASE', 'https://edu.edu.vn.ua')
|
||||||
|
EDU_LOGIN_PATH = _env('EDU_URL_LOGIN', '/user/login')
|
||||||
|
EDU_COURSES_PATH = _env('EDU_URL_COURSES', '/course/userlist')
|
||||||
|
URL_LOGIN = f"{EDU_BASE.rstrip('/')}/{EDU_LOGIN_PATH.lstrip('/')}"
|
||||||
|
URL_VERIFY = f"{EDU_BASE.rstrip('/')}/{EDU_COURSES_PATH.lstrip('/')}"
|
||||||
|
|
||||||
|
INTERVAL = int(_env('KEEPER_INTERVAL', 10))
|
||||||
|
USER_AGENT = _env('USER_AGENT', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36')
|
||||||
|
REDIS_HOST = _env('REDIS_HOST', 'redis')
|
||||||
|
REDIS_PORT = int(_env('REDIS_PORT', 6379))
|
||||||
|
|
||||||
SUCCESS_FILE = '/tmp/last_success'
|
SUCCESS_FILE = '/tmp/last_success'
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import logging
|
|||||||
import redis
|
import redis
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ChatMember
|
||||||
|
from telegram.constants import ChatType
|
||||||
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes
|
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes
|
||||||
from playwright.async_api import async_playwright
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
@@ -14,22 +15,36 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Suppress HTTP request logs
|
||||||
|
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('telegram.ext._application').setLevel(logging.WARNING)
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
WEBINAR_URL = os.getenv('WEBINAR_URL', 'https://edu.edu.vn.ua/webinar/useractive')
|
def _env(key, default=None):
|
||||||
WEBINAR_CHECK_INTERVAL = int(os.getenv('WEBINAR_CHECK_INTERVAL', 60))
|
v = os.getenv(key, default)
|
||||||
REDIS_HOST = os.getenv('REDIS_HOST', 'redis')
|
if isinstance(v, str) and len(v) >= 2 and ((v[0] == '"' and v[-1] == '"') or (v[0] == "'" and v[-1] == "'")):
|
||||||
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
|
return v[1:-1]
|
||||||
PLAYWRIGHT_WS = os.getenv('PLAYWRIGHT_WS', 'ws://playwright-service:3000/ws')
|
return v
|
||||||
USER_AGENT = os.getenv('USER_AGENT', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36')
|
|
||||||
WEBINAR_TELEGRAM_TOKEN = os.getenv('WEBINAR_TELEGRAM_TOKEN')
|
EDU_BASE = _env('EDU_URL_BASE', 'https://edu.edu.vn.ua')
|
||||||
ADMIN_ID = int(os.getenv('WEBINAR_ADMIN_ID', '0'))
|
EDU_WEBINAR_PATH = _env('EDU_URL_WEBINAR', '/webinar/useractive')
|
||||||
|
WEBINAR_URL = f"{EDU_BASE.rstrip('/')}/{EDU_WEBINAR_PATH.lstrip('/')}"
|
||||||
|
|
||||||
|
WEBINAR_CHECK_INTERVAL = int(_env('WEBINAR_CHECK_INTERVAL', 60))
|
||||||
|
REDIS_HOST = _env('REDIS_HOST', 'redis')
|
||||||
|
REDIS_PORT = int(_env('REDIS_PORT', 6379))
|
||||||
|
PLAYWRIGHT_WS = _env('PLAYWRIGHT_WS', 'ws://playwright-service:3000/ws')
|
||||||
|
USER_AGENT = _env('USER_AGENT', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36')
|
||||||
|
WEBINAR_TELEGRAM_TOKEN = _env('WEBINAR_TELEGRAM_TOKEN')
|
||||||
|
ADMIN_ID = int(_env('WEBINAR_ADMIN_ID', '0'))
|
||||||
|
|
||||||
# Redis Keys
|
# Redis Keys
|
||||||
KEY_WHITELIST = "bot:whitelist"
|
KEY_WHITELIST = "bot:whitelist"
|
||||||
KEY_WHITELIST_ENABLED = "bot:whitelist_enabled"
|
KEY_WHITELIST_ENABLED = "bot:whitelist_enabled"
|
||||||
KEY_SUBSCRIBERS = "bot:subscribers"
|
KEY_SUBSCRIBERS = "bot:subscribers"
|
||||||
KEY_PHPSESSID = "EDU_PHPSESSID"
|
KEY_PHPSESSID = "EDU_PHPSESSID"
|
||||||
KEY_WEBINAR_HISTORY = "bot:webinar_history" # Stores last 5 webinars
|
KEY_WEBINAR_HISTORY = "bot:webinar_history" # Stores last 3 webinars
|
||||||
|
|
||||||
# Initialize Redis
|
# Initialize Redis
|
||||||
try:
|
try:
|
||||||
@@ -48,7 +63,7 @@ TRANSLATIONS = {
|
|||||||
'welcome_admin': "\n\n👑 <b>Режим администратора активен</b>",
|
'welcome_admin': "\n\n👑 <b>Режим администратора активен</b>",
|
||||||
'access_denied': "⛔ Доступ запрещен. Вас нет в белом списке.",
|
'access_denied': "⛔ Доступ запрещен. Вас нет в белом списке.",
|
||||||
'help_title': "🤖 <b>Помощь по боту</b>\n\n",
|
'help_title': "🤖 <b>Помощь по боту</b>\n\n",
|
||||||
'help_commands': "/start - Подписаться на уведомления\n/help - Показать это сообщение\n/language - Сменить язык",
|
'help_commands': "/start - Подписаться на уведомления\n/stop - Отписаться от уведомлений\n/help - Показать это сообщение\n/language - Сменить язык",
|
||||||
'help_admin': "\n<b>Команды администратора:</b>\n/adduser [user_id] - Добавить пользователя в белый список\n/removeuser [user_id] - Удалить пользователя из белого списка\nИли используйте панель ниже для управления настройками.",
|
'help_admin': "\n<b>Команды администратора:</b>\n/adduser [user_id] - Добавить пользователя в белый список\n/removeuser [user_id] - Удалить пользователя из белого списка\nИли используйте панель ниже для управления настройками.",
|
||||||
'admin_only': "⛔ Только для администратора!",
|
'admin_only': "⛔ Только для администратора!",
|
||||||
'user_added': "✅ Пользователь {user_id} добавлен в белый список",
|
'user_added': "✅ Пользователь {user_id} добавлен в белый список",
|
||||||
@@ -79,13 +94,15 @@ TRANSLATIONS = {
|
|||||||
'flag_ru': "🇷🇺 Русский",
|
'flag_ru': "🇷🇺 Русский",
|
||||||
'flag_uk': "🇺🇦 Українська",
|
'flag_uk': "🇺🇦 Українська",
|
||||||
'flag_en': "🇬🇧 English",
|
'flag_en': "🇬🇧 English",
|
||||||
|
'history_cleared': "✅ История вебинаров очищена",
|
||||||
|
'history_clear_failed': "❌ Ошибка при очистке истории",
|
||||||
},
|
},
|
||||||
'uk': {
|
'uk': {
|
||||||
'welcome': "👋 Привіт, {name}!\n\nЯ бот-сповіщувач про вебінари. Я повідомлятиму вас, коли з'явиться новий вебінар.\nВи підписані на сповіщення.",
|
'welcome': "👋 Привіт, {name}!\n\nЯ бот-сповіщувач про вебінари. Я повідомлятиму вас, коли з'явиться новий вебінар.\nВи підписані на сповіщення.",
|
||||||
'welcome_admin': "\n\n👑 <b>Режим адміністратора активний</b>",
|
'welcome_admin': "\n\n👑 <b>Режим адміністратора активний</b>",
|
||||||
'access_denied': "⛔ Доступ заборонено. Вас немає в білому списку.",
|
'access_denied': "⛔ Доступ заборонено. Вас немає в білому списку.",
|
||||||
'help_title': "🤖 <b>Довідка по боту</b>\n\n",
|
'help_title': "🤖 <b>Довідка по боту</b>\n\n",
|
||||||
'help_commands': "/start - Підписатися на сповіщення\n/help - Показати це повідомлення\n/language - Змінити мову",
|
'help_commands': "/start - Підписатися на сповіщення\n/stop - Відписатися від сповіщень\n/help - Показати це повідомлення\n/language - Змінити мову",
|
||||||
'help_admin': "\n<b>Команди адміністратора:</b>\n/adduser [user_id] - Додати користувача до білого списку\n/removeuser [user_id] - Видалити користувача з білого списку\nАбо використовуйте панель нижче для керування налаштуваннями.",
|
'help_admin': "\n<b>Команди адміністратора:</b>\n/adduser [user_id] - Додати користувача до білого списку\n/removeuser [user_id] - Видалити користувача з білого списку\nАбо використовуйте панель нижче для керування налаштуваннями.",
|
||||||
'admin_only': "⛔ Тільки для адміністратора!",
|
'admin_only': "⛔ Тільки для адміністратора!",
|
||||||
'user_added': "✅ Користувач {user_id} доданий до білого списку",
|
'user_added': "✅ Користувач {user_id} доданий до білого списку",
|
||||||
@@ -116,13 +133,15 @@ TRANSLATIONS = {
|
|||||||
'flag_ru': "🇷🇺 Русский",
|
'flag_ru': "🇷🇺 Русский",
|
||||||
'flag_uk': "🇺🇦 Українська",
|
'flag_uk': "🇺🇦 Українська",
|
||||||
'flag_en': "🇬🇧 English",
|
'flag_en': "🇬🇧 English",
|
||||||
|
'history_cleared': "✅ Історія вебінарів очищена",
|
||||||
|
'history_clear_failed': "❌ Помилка при очищенні історії",
|
||||||
},
|
},
|
||||||
'en': {
|
'en': {
|
||||||
'welcome': "👋 Hello, {name}!\n\nI am the Webinar Checker Bot. I will notify you when a new webinar appears.\nYou have been subscribed to notifications.",
|
'welcome': "👋 Hello, {name}!\n\nI am the Webinar Checker Bot. I will notify you when a new webinar appears.\nYou have been subscribed to notifications.",
|
||||||
'welcome_admin': "\n\n👑 <b>Admin Mode Active</b>",
|
'welcome_admin': "\n\n👑 <b>Admin Mode Active</b>",
|
||||||
'access_denied': "⛔ Access denied. You are not on the whitelist.",
|
'access_denied': "⛔ Access denied. You are not on the whitelist.",
|
||||||
'help_title': "🤖 <b>Bot Help</b>\n\n",
|
'help_title': "🤖 <b>Bot Help</b>\n\n",
|
||||||
'help_commands': "/start - Subscribe to notifications\n/help - Show this message\n/language - Change language",
|
'help_commands': "/start - Subscribe to notifications\n/stop - Unsubscribe from notifications\n/help - Show this message\n/language - Change language",
|
||||||
'help_admin': "\n<b>Admin Commands:</b>\n/adduser [user_id] - Add user to whitelist\n/removeuser [user_id] - Remove user from whitelist\nOr use the panel below to manage settings.",
|
'help_admin': "\n<b>Admin Commands:</b>\n/adduser [user_id] - Add user to whitelist\n/removeuser [user_id] - Remove user from whitelist\nOr use the panel below to manage settings.",
|
||||||
'admin_only': "⛔ Admin only!",
|
'admin_only': "⛔ Admin only!",
|
||||||
'user_added': "✅ User {user_id} added to whitelist",
|
'user_added': "✅ User {user_id} added to whitelist",
|
||||||
@@ -153,6 +172,8 @@ TRANSLATIONS = {
|
|||||||
'flag_ru': "🇷🇺 Русский",
|
'flag_ru': "🇷🇺 Русский",
|
||||||
'flag_uk': "🇺🇦 Українська",
|
'flag_uk': "🇺🇦 Українська",
|
||||||
'flag_en': "🇬🇧 English",
|
'flag_en': "🇬🇧 English",
|
||||||
|
'history_cleared': "✅ Webinar history cleared",
|
||||||
|
'history_clear_failed': "❌ Error clearing history",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +224,21 @@ def is_whitelisted(user_id: int) -> bool:
|
|||||||
|
|
||||||
return redis_client.sismember(KEY_WHITELIST, str(user_id))
|
return redis_client.sismember(KEY_WHITELIST, str(user_id))
|
||||||
|
|
||||||
|
async def is_group_admin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
||||||
|
"""Check if the user is an administrator in the group."""
|
||||||
|
user = update.effective_user
|
||||||
|
chat = update.effective_chat
|
||||||
|
|
||||||
|
if chat.type in [ChatType.PRIVATE, "private"]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
member = await context.bot.get_chat_member(chat.id, user.id)
|
||||||
|
return member.status in [ChatMember.OWNER, ChatMember.ADMINISTRATOR]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to check admin status: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_admin_keyboard(user_id: int):
|
def get_admin_keyboard(user_id: int):
|
||||||
"""Generate admin panel keyboard."""
|
"""Generate admin panel keyboard."""
|
||||||
whitelist_enabled = redis_client.get(KEY_WHITELIST_ENABLED) != "0"
|
whitelist_enabled = redis_client.get(KEY_WHITELIST_ENABLED) != "0"
|
||||||
@@ -221,19 +257,21 @@ def get_admin_keyboard(user_id: int):
|
|||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Handle /start command."""
|
"""Handle /start command."""
|
||||||
user = update.effective_user
|
user = update.effective_user
|
||||||
logger.info(f"User {user.id} ({user.username}) started the bot.")
|
chat = update.effective_chat
|
||||||
|
logger.info(f"User {user.id} ({user.username}) started the bot in chat {chat.id} ({chat.type}).")
|
||||||
|
|
||||||
|
# Check whitelist - MUST be the user executing the command
|
||||||
if not is_whitelisted(user.id):
|
if not is_whitelisted(user.id):
|
||||||
await update.message.reply_text(t(user.id, 'access_denied'))
|
await update.message.reply_text(t(user.id, 'access_denied'))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Add to subscribers
|
# Add to subscribers (Chat ID!)
|
||||||
redis_client.sadd(KEY_SUBSCRIBERS, user.id)
|
redis_client.sadd(KEY_SUBSCRIBERS, chat.id)
|
||||||
|
|
||||||
msg = t(user.id, 'welcome', name=user.first_name)
|
msg = t(chat.id, 'welcome', name=user.first_name)
|
||||||
|
|
||||||
if user.id == ADMIN_ID:
|
if user.id == ADMIN_ID and chat.type == "private":
|
||||||
msg += t(user.id, 'welcome_admin')
|
msg += t(chat.id, 'welcome_admin')
|
||||||
await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user.id))
|
await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user.id))
|
||||||
else:
|
else:
|
||||||
await update.message.reply_text(msg, parse_mode='HTML')
|
await update.message.reply_text(msg, parse_mode='HTML')
|
||||||
@@ -241,19 +279,39 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Handle /help command."""
|
"""Handle /help command."""
|
||||||
user_id = update.effective_user.id
|
user_id = update.effective_user.id
|
||||||
msg = t(user_id, 'help_title') + t(user_id, 'help_commands')
|
chat_id = update.effective_chat.id
|
||||||
|
msg = t(chat_id, 'help_title') + t(chat_id, 'help_commands')
|
||||||
|
|
||||||
if user_id == ADMIN_ID:
|
if user_id == ADMIN_ID and update.effective_chat.type == "private":
|
||||||
msg += t(user_id, 'help_admin')
|
msg += t(chat_id, 'help_admin')
|
||||||
await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user_id))
|
await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user_id))
|
||||||
else:
|
else:
|
||||||
await update.message.reply_text(msg, parse_mode='HTML')
|
await update.message.reply_text(msg, parse_mode='HTML')
|
||||||
|
|
||||||
|
async def stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Handle /stop command (unsubscribe)."""
|
||||||
|
user = update.effective_user
|
||||||
|
chat = update.effective_chat
|
||||||
|
|
||||||
|
# Permission check: Whitelisted user OR Group Admin
|
||||||
|
if not (is_whitelisted(user.id) or await is_group_admin(update, context)):
|
||||||
|
await update.message.reply_text(t(chat.id, 'access_denied')) # Or specific "admin only" message
|
||||||
|
return
|
||||||
|
|
||||||
|
redis_client.srem(KEY_SUBSCRIBERS, chat.id)
|
||||||
|
await update.message.reply_text(t(chat.id, 'whitelist_disabled').replace(" whitelist", " notifications").replace("Білий список", "Сповіщення").replace("Белый список", "Уведомления") if chat.id else "Unsubscribed")
|
||||||
|
|
||||||
async def language_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def language_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Handle /language command."""
|
"""Handle /language command."""
|
||||||
user_id = update.effective_user.id
|
user = update.effective_user
|
||||||
|
chat = update.effective_chat
|
||||||
|
|
||||||
|
# Permission check for groups
|
||||||
|
if not (is_whitelisted(user.id) or await is_group_admin(update, context)):
|
||||||
|
return
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
t(user_id, 'select_language'),
|
t(chat.id, 'select_language'),
|
||||||
parse_mode='HTML',
|
parse_mode='HTML',
|
||||||
reply_markup=get_language_keyboard()
|
reply_markup=get_language_keyboard()
|
||||||
)
|
)
|
||||||
@@ -303,6 +361,21 @@ async def remove_user(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
await update.message.reply_text(t(admin_id, 'invalid_user_id'))
|
await update.message.reply_text(t(admin_id, 'invalid_user_id'))
|
||||||
|
|
||||||
|
async def clear_history(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Clear webinar history (admin only)."""
|
||||||
|
admin_id = update.effective_user.id
|
||||||
|
if admin_id != ADMIN_ID:
|
||||||
|
await update.message.reply_text(t(admin_id, 'admin_only'))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
redis_client.delete(KEY_WEBINAR_HISTORY)
|
||||||
|
await update.message.reply_text(t(admin_id, 'history_cleared'))
|
||||||
|
logger.info("Admin cleared webinar history")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to clear history: {e}")
|
||||||
|
await update.message.reply_text(t(admin_id, 'history_clear_failed'))
|
||||||
|
|
||||||
# --- Admin Callbacks ---
|
# --- Admin Callbacks ---
|
||||||
|
|
||||||
async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
@@ -363,9 +436,9 @@ async def language_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
|
|
||||||
# --- Webinar Checking Job ---
|
# --- Webinar Checking Job ---
|
||||||
|
|
||||||
def get_webinar_key(name: str, url: str) -> str:
|
def get_webinar_key(url: str) -> str:
|
||||||
"""Generate unique key for a webinar based on name and URL."""
|
"""Generate unique key for a webinar based on URL."""
|
||||||
return f"{name}|{url}"
|
return url
|
||||||
|
|
||||||
def get_stored_webinars() -> list:
|
def get_stored_webinars() -> list:
|
||||||
"""Get list of stored webinar keys from Redis."""
|
"""Get list of stored webinar keys from Redis."""
|
||||||
@@ -378,9 +451,9 @@ def get_stored_webinars() -> list:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def store_webinars(webinar_keys: list):
|
def store_webinars(webinar_keys: list):
|
||||||
"""Store up to 5 most recent webinar keys in Redis."""
|
"""Store up to 3 most recent webinar keys in Redis."""
|
||||||
# Keep only last 5
|
# Keep only last 3
|
||||||
webinar_keys = webinar_keys[-5:]
|
webinar_keys = webinar_keys[-3:]
|
||||||
try:
|
try:
|
||||||
redis_client.set(KEY_WEBINAR_HISTORY, json.dumps(webinar_keys))
|
redis_client.set(KEY_WEBINAR_HISTORY, json.dumps(webinar_keys))
|
||||||
logger.info(f"Stored {len(webinar_keys)} webinar(s) in history")
|
logger.info(f"Stored {len(webinar_keys)} webinar(s) in history")
|
||||||
@@ -515,7 +588,7 @@ async def check_webinars_job(context: ContextTypes.DEFAULT_TYPE):
|
|||||||
current_keys = []
|
current_keys = []
|
||||||
|
|
||||||
for webinar in current_webinars:
|
for webinar in current_webinars:
|
||||||
key = get_webinar_key(webinar['name'], webinar['url'])
|
key = get_webinar_key(webinar['url'])
|
||||||
current_keys.append(key)
|
current_keys.append(key)
|
||||||
|
|
||||||
if key not in stored_keys:
|
if key not in stored_keys:
|
||||||
@@ -535,6 +608,7 @@ async def check_webinars_job(context: ContextTypes.DEFAULT_TYPE):
|
|||||||
for sub_id in subscribers:
|
for sub_id in subscribers:
|
||||||
try:
|
try:
|
||||||
# Build message in user's language
|
# Build message in user's language
|
||||||
|
# sub_id comes from redis set as string, convert to int for translation lookup
|
||||||
webinar_items = "\n\n".join([
|
webinar_items = "\n\n".join([
|
||||||
t(int(sub_id), 'webinar_item', name=w['name'], url=w['url'])
|
t(int(sub_id), 'webinar_item', name=w['name'], url=w['url'])
|
||||||
for w in new_webinars
|
for w in new_webinars
|
||||||
@@ -569,10 +643,12 @@ def main():
|
|||||||
|
|
||||||
# Handlers
|
# Handlers
|
||||||
app.add_handler(CommandHandler("start", start))
|
app.add_handler(CommandHandler("start", start))
|
||||||
|
app.add_handler(CommandHandler("stop", stop_command))
|
||||||
app.add_handler(CommandHandler("help", help_command))
|
app.add_handler(CommandHandler("help", help_command))
|
||||||
app.add_handler(CommandHandler("language", language_command))
|
app.add_handler(CommandHandler("language", language_command))
|
||||||
app.add_handler(CommandHandler("adduser", add_user))
|
app.add_handler(CommandHandler("adduser", add_user))
|
||||||
app.add_handler(CommandHandler("removeuser", remove_user))
|
app.add_handler(CommandHandler("removeuser", remove_user))
|
||||||
|
app.add_handler(CommandHandler("clearhistory", clear_history))
|
||||||
|
|
||||||
# Callback handlers - language selection first, then admin panel
|
# Callback handlers - language selection first, then admin panel
|
||||||
app.add_handler(CallbackQueryHandler(language_callback, pattern="^lang_"))
|
app.add_handler(CallbackQueryHandler(language_callback, pattern="^lang_"))
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@ services:
|
|||||||
# Prod Router
|
# Prod Router
|
||||||
- "traefik.http.routers.glance.rule=Host(`glance.forust.xyz`)"
|
- "traefik.http.routers.glance.rule=Host(`glance.forust.xyz`)"
|
||||||
- "traefik.http.routers.glance.entrypoints=websecure"
|
- "traefik.http.routers.glance.entrypoints=websecure"
|
||||||
- "traefik.http.routers.glance.middlewares=security-chain@file"
|
- "traefik.http.routers.glance.middlewares=security-headers@file"
|
||||||
- "traefik.http.routers.glance.tls=true"
|
- "traefik.http.routers.glance.tls=true"
|
||||||
|
|
||||||
# Local Router
|
# Local Router
|
||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
# Dev Router
|
# Dev Router
|
||||||
- "traefik.http.routers.glance-dev.rule=Host(`glance.gigaforust.internal`)"
|
- "traefik.http.routers.glance-dev.rule=Host(`glance.gigaforust.internal`)"
|
||||||
- "traefik.http.routers.glance-dev.entrypoints=websecure"
|
- "traefik.http.routers.glance-dev.entrypoints=websecure"
|
||||||
- "traefik.http.routers.glance-dev.middlewares=security-chain@file"
|
- "traefik.http.routers.glance-dev.middlewares=security-headers@file"
|
||||||
- "traefik.http.routers.glance-dev.tls=true"
|
- "traefik.http.routers.glance-dev.tls=true"
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
|
|||||||
@@ -41,7 +41,14 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<i class="fas fa-envelope"></i>
|
<i class="fas fa-envelope"></i>
|
||||||
<a href="mailto:forust@forust.xyz">forust@forust.xyz</a>
|
<a href="mailto:forust@forust.xyz">mail/contact@forust.xyz</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fa-solid fa-key"></i>
|
||||||
|
<a href=".well-known/pgp-key.asc">security/PGP Key</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p class="comment"># PGP Key Fingerprint: A777 7CB7 D9C4 0A97 443D CCF0 7A3D A455 F820 5B82</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -25,21 +25,21 @@ services:
|
|||||||
- "traefik.http.services.nextcloud-aio.loadbalancer.serverstransport=insecureTransport@file"
|
- "traefik.http.services.nextcloud-aio.loadbalancer.serverstransport=insecureTransport@file"
|
||||||
|
|
||||||
# Prod Router
|
# Prod Router
|
||||||
# - "traefik.http.routers.nextcloud-aio.rule=Host(`nextcloud-aio.forust.xyz`)"
|
# - "traefik.http.routers.nextcloud-aio.rule=Host(`naio.forust.xyz`)"
|
||||||
# - "traefik.http.routers.nextcloud-aio.entrypoints=websecure"
|
# - "traefik.http.routers.nextcloud-aio.entrypoints=websecure"
|
||||||
# - "traefik.http.routers.nextcloud-aio.middlewares=security-headers"
|
# - "traefik.http.routers.nextcloud-aio.middlewares=security-chain@file"
|
||||||
# - "traefik.http.routers.nextcloud-aio.service=nextcloud-aio"
|
# - "traefik.http.routers.nextcloud-aio.service=nextcloud-aio"
|
||||||
# - "traefik.http.routers.nextcloud-aio.tls=true"
|
# - "traefik.http.routers.nextcloud-aio.tls=true"
|
||||||
|
|
||||||
# Local Router
|
# Local Router
|
||||||
- "traefik.http.routers.nextcloud-aio-local.rule=Host(`nextcloud-aio.workstation.internal`) || Host(`nextcloud-aio.internal`)"
|
- "traefik.http.routers.nextcloud-aio-local.rule=Host(`naio.workstation.internal`) || Host(`nextcloud-aio.internal`)"
|
||||||
- "traefik.http.routers.nextcloud-aio-local.entrypoints=websecure"
|
- "traefik.http.routers.nextcloud-aio-local.entrypoints=websecure"
|
||||||
- "traefik.http.routers.nextcloud-aio-local.middlewares=security-headers@file"
|
- "traefik.http.routers.nextcloud-aio-local.middlewares=security-headers@file"
|
||||||
- "traefik.http.routers.nextcloud-aio-local.service=nextcloud-aio"
|
- "traefik.http.routers.nextcloud-aio-local.service=nextcloud-aio"
|
||||||
- "traefik.http.routers.nextcloud-aio-local.tls=true"
|
- "traefik.http.routers.nextcloud-aio-local.tls=true"
|
||||||
|
|
||||||
# Dev Router
|
# Dev Router
|
||||||
- "traefik.http.routers.nextcloud-aio-dev.rule=Host(`nextcloud-aio.gigaforust.internal`)"
|
- "traefik.http.routers.nextcloud-aio-dev.rule=Host(`naio.gigaforust.internal`)"
|
||||||
- "traefik.http.routers.nextcloud-aio-dev.entrypoints=websecure"
|
- "traefik.http.routers.nextcloud-aio-dev.entrypoints=websecure"
|
||||||
- "traefik.http.routers.nextcloud-aio-dev.middlewares=security-headers@file"
|
- "traefik.http.routers.nextcloud-aio-dev.middlewares=security-headers@file"
|
||||||
- "traefik.http.routers.nextcloud-aio-dev.service=nextcloud-aio"
|
- "traefik.http.routers.nextcloud-aio-dev.service=nextcloud-aio"
|
||||||
@@ -59,7 +59,7 @@ services:
|
|||||||
BORG_RETENTION_POLICY: --keep-within=7d --keep-weekly=4 --keep-monthly=6 # Backup retention See https://github.com/nextcloud/all-in-one#how-to-adjust-borgs-retention-policy
|
BORG_RETENTION_POLICY: --keep-within=7d --keep-weekly=4 --keep-monthly=6 # Backup retention See https://github.com/nextcloud/all-in-one#how-to-adjust-borgs-retention-policy
|
||||||
COLLABORA_SECCOMP_DISABLED: false # Setting this to true allows to disable Collabora's Seccomp feature. See https://github.com/nextcloud/all-in-one#how-to-disable-collaboras-seccomp-feature
|
COLLABORA_SECCOMP_DISABLED: false # Setting this to true allows to disable Collabora's Seccomp feature. See https://github.com/nextcloud/all-in-one#how-to-disable-collaboras-seccomp-feature
|
||||||
FULLTEXTSEARCH_JAVA_OPTIONS: "-Xms1024M -Xmx1024M" # adjust fulltextsearch java options. https://github.com/nextcloud/all-in-one#how-to-adjust-the-fulltextsearch-java-options
|
FULLTEXTSEARCH_JAVA_OPTIONS: "-Xms1024M -Xmx1024M" # adjust fulltextsearch java options. https://github.com/nextcloud/all-in-one#how-to-adjust-the-fulltextsearch-java-options
|
||||||
NEXTCLOUD_DATADIR: /media/forust/nextcloud/ncdata # Allows to set the host directory for Nextcloud's datadir. ⚠️⚠️⚠️ Warning: do not set or adjust this value after the initial Nextcloud installation is done! See https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir
|
NEXTCLOUD_DATADIR: /mnt/nextcloud/ncdata # Allows to set the host directory for Nextcloud's datadir. ⚠️⚠️⚠️ Warning: do not set or adjust this value after the initial Nextcloud installation is done! See https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir
|
||||||
NEXTCLOUD_MOUNT: /mnt/ # Allows the Nextcloud container to access the chosen directory on the host. See https://github.com/nextcloud/all-in-one#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host
|
NEXTCLOUD_MOUNT: /mnt/ # Allows the Nextcloud container to access the chosen directory on the host. See https://github.com/nextcloud/all-in-one#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host
|
||||||
NEXTCLOUD_UPLOAD_LIMIT: 16G # https://github.com/nextcloud/all-in-one#how-to-adjust-the-upload-limit-for-nextcloud
|
NEXTCLOUD_UPLOAD_LIMIT: 16G # https://github.com/nextcloud/all-in-one#how-to-adjust-the-upload-limit-for-nextcloud
|
||||||
NEXTCLOUD_MAX_TIME: 7200 # Max uploading time See https://github.com/nextcloud/all-in-one#how-to-adjust-the-max-execution-time-for-nextcloud
|
NEXTCLOUD_MAX_TIME: 7200 # Max uploading time See https://github.com/nextcloud/all-in-one#how-to-adjust-the-max-execution-time-for-nextcloud
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
TZ=Europe/Moscow
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
services:
|
||||||
|
jellyfin:
|
||||||
|
image: lscr.io/linuxserver/jellyfin:latest
|
||||||
|
container_name: jellyfin
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8096:8096/tcp"
|
||||||
|
- "7359:7359/udp"
|
||||||
|
volumes:
|
||||||
|
# HACK: Binding while bootstrapping, testing
|
||||||
|
# - jellyfin-cfg:/config
|
||||||
|
- ./config/jellyfin:/config
|
||||||
|
- /mnt/mediaserver/media/movies:/media/movies
|
||||||
|
- /mnt/mediaserver/media/movies:/media/movies
|
||||||
|
devices:
|
||||||
|
- /dev/dri:/dev/dri
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=proxy"
|
||||||
|
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
|
||||||
|
# Prod Router
|
||||||
|
- "traefik.http.routers.jellyfin.rule=Host(`media.forust.xyz`)"
|
||||||
|
- "traefik.http.routers.jellyfin.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.jellyfin.tls=true"
|
||||||
|
# Local Router
|
||||||
|
- "traefik.http.routers.jellyfin-local.rule=Host(`media.workstation.internal`) || Host(`ms.internal`)"
|
||||||
|
- "traefik.http.routers.jellyfin-local.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.jellyfin-local.tls=true"
|
||||||
|
# Dev Router
|
||||||
|
- "traefik.http.routers.jellyfin-dev.rule=Host(`media.gigaforust.internal`)"
|
||||||
|
- "traefik.http.routers.jellyfin-dev.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.jellyfin-dev.tls=true"
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
- streaming
|
||||||
|
qbittorrent:
|
||||||
|
image: lscr.io/linuxserver/qbittorrent:latest
|
||||||
|
container_name: qbittorrent
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- WEBUI_PORT=8080
|
||||||
|
volumes:
|
||||||
|
# HACK: Binding while bootstrapping, testing
|
||||||
|
# - qbittorrent-cfg:/config
|
||||||
|
- ./config/qbittorrent:/config
|
||||||
|
- /mnt/mediaserver/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
- 6881:6881
|
||||||
|
- 6881:6881/udp
|
||||||
|
networks:
|
||||||
|
- streaming
|
||||||
|
radarr:
|
||||||
|
image: lscr.io/linuxserver/radarr:latest
|
||||||
|
container_name: radarr
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 7878:7878
|
||||||
|
volumes:
|
||||||
|
# HACK: Binding while bootstrapping, testing
|
||||||
|
# - radarr-cfg:/config
|
||||||
|
- ./config/radarr:/config
|
||||||
|
- /mnt/mediaserver/downloads:/downloads
|
||||||
|
- /mnt/mediaserver/movies:/movies
|
||||||
|
networks:
|
||||||
|
- streaming
|
||||||
|
volumes:
|
||||||
|
jellyfin-cfg:
|
||||||
|
qbittorrent-cfg:
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
streaming:
|
||||||
|
name: streaming
|
||||||
@@ -1 +0,0 @@
|
|||||||
TS_AUTHKEY=tskey-auth-xxxxx-CNTRL
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
services:
|
|
||||||
tailscale:
|
|
||||||
image: tailscale/tailscale:latest
|
|
||||||
container_name: tailscale
|
|
||||||
network_mode: host
|
|
||||||
restart: unless-stopped
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
- NET_RAW
|
|
||||||
volumes:
|
|
||||||
- tailscale_data:/var/lib/tailscale
|
|
||||||
- /dev/net/tun:/dev/net/tun
|
|
||||||
environment:
|
|
||||||
- TS_AUTHKEY=${TS_AUTHKEY}
|
|
||||||
- TS_STATE_DIR=/var/lib/tailscale
|
|
||||||
- TS_HOSTNAME=forust-server
|
|
||||||
# - TS_EXTRA_ARGS=--advertise-tags=tag:container
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
tailscale_data:
|
|
||||||
name: tailscale_data
|
|
||||||
@@ -19,11 +19,14 @@ services:
|
|||||||
- "--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.scheme=https"
|
||||||
- "--entryPoints.ssh.address=:2221"
|
- "--entryPoints.ssh.address=:2221"
|
||||||
|
|
||||||
# Let's Encrypt
|
# Let's Encrypt
|
||||||
- "--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.httpchallenge=true"
|
||||||
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
||||||
# # STAGING
|
# # STAGING
|
||||||
# - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
|
# - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
http:
|
||||||
|
routers:
|
||||||
|
fs1-public:
|
||||||
|
rule: "Host(`fs1.domain.xyz`)"
|
||||||
|
entrypoints:
|
||||||
|
- websecure
|
||||||
|
service: fs1
|
||||||
|
middlewares:
|
||||||
|
- security-chain@file
|
||||||
|
tls: {}
|
||||||
|
|
||||||
|
fs1-workstation:
|
||||||
|
rule: "Host(`fs1.workstation.internal`)"
|
||||||
|
entrypoints:
|
||||||
|
- websecure
|
||||||
|
service: fs1
|
||||||
|
tls: {}
|
||||||
|
|
||||||
|
services:
|
||||||
|
fs1:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://127.0.0.1:3923" # Copyparty port example
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
http:
|
http:
|
||||||
middlewares:
|
middlewares:
|
||||||
|
# HTTPS Redirect
|
||||||
|
redirect-https:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
permanent: true
|
||||||
# Cloudflare IP Whitelist
|
# Cloudflare IP Whitelist
|
||||||
cloudflare-ipwhitelist:
|
cloudflare-ipwhitelist:
|
||||||
ipWhiteList:
|
ipWhiteList:
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
http:
|
http:
|
||||||
middlewares:
|
|
||||||
redirect-https:
|
|
||||||
redirectScheme:
|
|
||||||
scheme: https
|
|
||||||
permanent: true
|
|
||||||
|
|
||||||
routers:
|
routers:
|
||||||
acme-challenge-exempt:
|
acme-challenge-exempt:
|
||||||
rule: "PathPrefix(`/.well-known/acme-challenge`)"
|
rule: "PathPrefix(`/.well-known/acme-challenge`)"
|
||||||
@@ -12,12 +6,3 @@ http:
|
|||||||
- web
|
- web
|
||||||
service: noop@internal
|
service: noop@internal
|
||||||
priority: 100
|
priority: 100
|
||||||
|
|
||||||
http-catchall:
|
|
||||||
rule: "HostRegexp(`{host:.+}`)"
|
|
||||||
entryPoints:
|
|
||||||
- web
|
|
||||||
middlewares:
|
|
||||||
- redirect-https
|
|
||||||
service: noop@internal
|
|
||||||
priority: 1
|
|
||||||
|
|||||||
Reference in New Issue
Block a user