diff --git a/.gitignore b/.gitignore index 2761f85..49b651c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,17 +2,12 @@ sync.ffs_lock .sync.ffs_db -# Environment -.env -.env.anna -.env.forust -.env.* -!.env.*example +# Copyparty +*.hist/ # Volumes and data directories gitea/gitea-db/ gitea/gitea-data/* -gitea/*runner/* n8n/n8n-data/* n8n/n8n-node-data/* adguardhome/data/* @@ -24,6 +19,8 @@ termix/termix-data/* cfddns/config.json checkmk/checkmk/* downtify/Downtify_downloads +headscale/config/* +headscale/data/* # Steaming services files streaming/jellyfin/* @@ -36,10 +33,11 @@ streaming/prowlarr/* # Homepage homepages/forust_files/assets/images/team/* - +homepages/forust_files/.well-known/* # Traefik files traefik/letsencrypt/acme.json +traefik/dynamic/fileservers.yml traefik/logs/* # SSL Certificates @@ -87,3 +85,10 @@ replacements.txt # Temp files edu_master/temp/ temp/* + +# Environment +.env +.env.anna +.env.forust +.env.* +!*example \ No newline at end of file diff --git a/adguardhome/compose.yaml b/adguardhome/compose.yaml index e2d40f6..503a82d 100644 --- a/adguardhome/compose.yaml +++ b/adguardhome/compose.yaml @@ -9,7 +9,7 @@ services: - "853:853/tcp" # DNS over TLS # - "67:67/udp" # DHCP # - "68:68/tcp" # DHCP - - "3000:3000/tcp" + # - "3000:3000/tcp" volumes: - ./data/work:/opt/adguardhome/work - ./data/conf:/opt/adguardhome/conf diff --git a/authentik/compose.yaml b/authentik/compose.yaml index 2311c1c..4acee8e 100644 --- a/authentik/compose.yaml +++ b/authentik/compose.yaml @@ -26,9 +26,9 @@ services: command: server container_name: authentik-server restart: unless-stopped - ports: - - ${PORT_HTTP:-9000}:9000 - - ${PORT_HTTPS:-9443}:9443 + # ports: + # - ${PORT_HTTP:-9000}:9000 + # - ${PORT_HTTPS:-9443}:9443 env_file: - .env environment: diff --git a/edu_master/phpsessid-bot/bot.py b/edu_master/phpsessid-bot/bot.py index 48aa466..d74cc76 100644 --- a/edu_master/phpsessid-bot/bot.py +++ b/edu_master/phpsessid-bot/bot.py @@ -12,15 +12,26 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -# Load configuration -LOGIN = os.getenv('EDU_LOGIN') -PASSWORD = os.getenv('EDU_PASSWORD') -URL_LOGIN = os.getenv('EDU_URL_LOGIN', 'https://edu.edu.vn.ua/user/login') -URL_VERIFY = os.getenv('EDU_URL_VERIFY', 'https://edu.edu.vn.ua/course/userlist') -INTERVAL = int(os.getenv('PHPSESSID_INTERVAL', 10)) -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') -REDIS_PORT = int(os.getenv('REDIS_PORT', 6379)) +# Load configuration (adapted to .env keys) +def _env(key, default=None): + v = os.getenv(key, default) + if isinstance(v, str) and len(v) >= 2 and ((v[0] == '"' and v[-1] == '"') or (v[0] == "'" and v[-1] == "'")): + return v[1:-1] + return v + +LOGIN = _env('KEEPER_LOGIN') +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' diff --git a/edu_master/webinar-checker/checker.py b/edu_master/webinar-checker/checker.py index 0477481..60c3dbb 100644 --- a/edu_master/webinar-checker/checker.py +++ b/edu_master/webinar-checker/checker.py @@ -3,7 +3,8 @@ import logging import redis 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 playwright.async_api import async_playwright @@ -15,14 +16,23 @@ logging.basicConfig( logger = logging.getLogger(__name__) # Load environment variables -WEBINAR_URL = os.getenv('WEBINAR_URL', 'https://edu.edu.vn.ua/webinar/useractive') -WEBINAR_CHECK_INTERVAL = int(os.getenv('WEBINAR_CHECK_INTERVAL', 60)) -REDIS_HOST = os.getenv('REDIS_HOST', 'redis') -REDIS_PORT = int(os.getenv('REDIS_PORT', 6379)) -PLAYWRIGHT_WS = os.getenv('PLAYWRIGHT_WS', 'ws://playwright-service:3000/ws') -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') -ADMIN_ID = int(os.getenv('WEBINAR_ADMIN_ID', '0')) +def _env(key, default=None): + v = os.getenv(key, default) + if isinstance(v, str) and len(v) >= 2 and ((v[0] == '"' and v[-1] == '"') or (v[0] == "'" and v[-1] == "'")): + return v[1:-1] + return v + +EDU_BASE = _env('EDU_URL_BASE', 'https://edu.edu.vn.ua') +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 KEY_WHITELIST = "bot:whitelist" @@ -48,7 +58,7 @@ TRANSLATIONS = { 'welcome_admin': "\n\n👑 Режим администратора активен", 'access_denied': "⛔ Доступ запрещен. Вас нет в белом списке.", 'help_title': "🤖 Помощь по боту\n\n", - 'help_commands': "/start - Подписаться на уведомления\n/help - Показать это сообщение\n/language - Сменить язык", + 'help_commands': "/start - Подписаться на уведомления\n/stop - Отписаться от уведомлений\n/help - Показать это сообщение\n/language - Сменить язык", 'help_admin': "\nКоманды администратора:\n/adduser [user_id] - Добавить пользователя в белый список\n/removeuser [user_id] - Удалить пользователя из белого списка\nИли используйте панель ниже для управления настройками.", 'admin_only': "⛔ Только для администратора!", 'user_added': "✅ Пользователь {user_id} добавлен в белый список", @@ -85,7 +95,7 @@ TRANSLATIONS = { 'welcome_admin': "\n\n👑 Режим адміністратора активний", 'access_denied': "⛔ Доступ заборонено. Вас немає в білому списку.", 'help_title': "🤖 Довідка по боту\n\n", - 'help_commands': "/start - Підписатися на сповіщення\n/help - Показати це повідомлення\n/language - Змінити мову", + 'help_commands': "/start - Підписатися на сповіщення\n/stop - Відписатися від сповіщень\n/help - Показати це повідомлення\n/language - Змінити мову", 'help_admin': "\nКоманди адміністратора:\n/adduser [user_id] - Додати користувача до білого списку\n/removeuser [user_id] - Видалити користувача з білого списку\nАбо використовуйте панель нижче для керування налаштуваннями.", 'admin_only': "⛔ Тільки для адміністратора!", 'user_added': "✅ Користувач {user_id} доданий до білого списку", @@ -122,7 +132,7 @@ TRANSLATIONS = { 'welcome_admin': "\n\n👑 Admin Mode Active", 'access_denied': "⛔ Access denied. You are not on the whitelist.", 'help_title': "🤖 Bot Help\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': "\nAdmin Commands:\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!", 'user_added': "✅ User {user_id} added to whitelist", @@ -203,6 +213,21 @@ def is_whitelisted(user_id: int) -> bool: 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): """Generate admin panel keyboard.""" whitelist_enabled = redis_client.get(KEY_WHITELIST_ENABLED) != "0" @@ -221,19 +246,21 @@ def get_admin_keyboard(user_id: int): async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /start command.""" 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): await update.message.reply_text(t(user.id, 'access_denied')) return - # Add to subscribers - redis_client.sadd(KEY_SUBSCRIBERS, user.id) + # Add to subscribers (Chat 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: - msg += t(user.id, 'welcome_admin') + if user.id == ADMIN_ID and chat.type == "private": + msg += t(chat.id, 'welcome_admin') await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user.id)) else: await update.message.reply_text(msg, parse_mode='HTML') @@ -241,19 +268,39 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /help command.""" 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: - msg += t(user_id, 'help_admin') + if user_id == ADMIN_ID and update.effective_chat.type == "private": + msg += t(chat_id, 'help_admin') await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user_id)) else: 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): """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( - t(user_id, 'select_language'), + t(chat.id, 'select_language'), parse_mode='HTML', reply_markup=get_language_keyboard() ) @@ -535,6 +582,7 @@ async def check_webinars_job(context: ContextTypes.DEFAULT_TYPE): for sub_id in subscribers: try: # 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([ t(int(sub_id), 'webinar_item', name=w['name'], url=w['url']) for w in new_webinars @@ -569,6 +617,7 @@ def main(): # Handlers app.add_handler(CommandHandler("start", start)) + app.add_handler(CommandHandler("stop", stop_command)) app.add_handler(CommandHandler("help", help_command)) app.add_handler(CommandHandler("language", language_command)) app.add_handler(CommandHandler("adduser", add_user)) diff --git a/gitea/.env.example b/gitea/.env.example index 672c2e6..916e272 100644 --- a/gitea/.env.example +++ b/gitea/.env.example @@ -1,7 +1,6 @@ GITEA_POSTGRES_USER= GITEA_POSTGRES_PASSWORD= GITEA_POSTGRES_DB=gitea -BASIC-RUNNER_TOKEN= GITEA_SMTP_PASS= MAILER_ADDR= SERVICE_EMAIL=email.used.by.services@domain.tld \ No newline at end of file diff --git a/gitea/compose.yaml b/gitea/compose.yaml index bf2d696..16ca04f 100644 --- a/gitea/compose.yaml +++ b/gitea/compose.yaml @@ -65,25 +65,6 @@ services: depends_on: - db - runner: - image: gitea/act_runner:0.2.11 - container_name: gitea-runner - restart: always - depends_on: - - server - env_file: - - .env - networks: - - gitea-db - environment: - - GITEA_INSTANCE_URL=http://server:3000 - - GITEA_RUNNER_REGISTRATION_TOKEN=${BASIC-RUNNER_TOKEN} - - GITEA_RUNNER_NAME=basic-runner - - GITEA_RUNNER_LABELS=docker:docker://node:20-bookworm,ubuntu-latest:docker://node:20-bookworm - volumes: - - ./gitea-runner:/data - - /var/run/docker.sock:/var/run/docker.sock - db: image: docker.io/library/postgres:14 restart: always diff --git a/glance/compose.yaml b/glance/compose.yaml index cd853ab..2ebc851 100644 --- a/glance/compose.yaml +++ b/glance/compose.yaml @@ -16,7 +16,7 @@ services: # Prod Router - "traefik.http.routers.glance.rule=Host(`glance.forust.xyz`)" - "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" # Local Router @@ -28,7 +28,7 @@ services: # Dev Router - "traefik.http.routers.glance-dev.rule=Host(`glance.gigaforust.internal`)" - "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" networks: - proxy diff --git a/headscale/compose.yaml b/headscale/compose.yaml new file mode 100644 index 0000000..e4c299e --- /dev/null +++ b/headscale/compose.yaml @@ -0,0 +1,84 @@ +services: + server: + image: headscale/headscale:latest + restart: unless-stopped + command: serve + networks: + - proxy + volumes: + - ./config:/etc/headscale + - ./data:/var/lib/headscale + labels: + - "traefik.enable=true" + - "traefik.docker.network=proxy" + - "traefik.http.services.headscale.loadbalancer.server.port=8080" + - "traefik.http.services.headscale-metrics.loadbalancer.server.port=9090" + + ## SERVICE + # Prod Router + - "traefik.http.routers.headscale.rule=Host(`hs.forust.xyz`)" + - "traefik.http.routers.headscale.entrypoints=websecure" + - "traefik.http.routers.headscale.service=headscale" + - "traefik.http.routers.headscale.tls=true" + # Local Router + - "traefik.http.routers.headscale-local.rule=Host(`hs.workstation.internal`)" + - "traefik.http.routers.headscale-local.entrypoints=websecure" + - "traefik.http.routers.headscale-local.service=headscale" + - "traefik.http.routers.headscale-local.tls=true" + # Dev Router + - "traefik.http.routers.headscale-dev.rule=Host(`hs.gigaforust.internal`)" + - "traefik.http.routers.headscale-dev.entrypoints=websecure" + - "traefik.http.routers.headscale-dev.service=headscale" + - "traefik.http.routers.headscale-dev.tls=true" + + ## METRICS + # Prod Router + - "traefik.http.routers.headscale-metrics.rule=Host(`hs.forust.xyz`) && PathPrefix(`/metrics`)" + - "traefik.http.routers.headscale-metrics.entrypoints=websecure" + - "traefik.http.routers.headscale-metrics.service=headscale-metrics" + - "traefik.http.routers.headscale-metrics.tls=true" + # Local Router + - "traefik.http.routers.headscale-metrics-local.rule=Host(`hs.workstation.internal`) && PathPrefix(`/metrics`)" + - "traefik.http.routers.headscale-metrics-local.entrypoints=websecure" + - "traefik.http.routers.headscale-metrics-local.service=headscale-metrics" + - "traefik.http.routers.headscale-metrics-local.tls=true" + # Dev Router + - "traefik.http.routers.headscale-metrics-dev.rule=Host(`hs.gigaforust.internal`) && PathPrefix(`/metrics`)" + - "traefik.http.routers.headscale-metrics-dev.entrypoints=websecure" + - "traefik.http.routers.headscale-metrics-dev.service=headscale-metrics" + - "traefik.http.routers.headscale-metrics-dev.tls=true" + dns: + - 1.1.1.1 + - 1.0.0.1 + web: + image: goodieshq/headscale-admin:latest + restart: unless-stopped + networks: + - proxy + labels: + - "traefik.enable=true" + - "treafik.docker.network=proxy" + - "traefik.http.services.headscale-ui.loadbalancer.server.port=80" + + # Prod Router + - "traefik.http.routers.headscale-ui.rule=Host(`hs.forust.xyz`) && PathPrefix(`/admin`)" + - "traefik.http.routers.headscale-ui.entrypoints=websecure" + - "traefik.http.routers.headscale-ui.middlewares=security-chain@file" + - "traefik.http.routers.headscale-ui.service=headscale-ui" + - "traefik.http.routers.headscale-ui.tls=true" + # Local Router + - "traefik.http.routers.headscale-ui-local.rule=Host(`hs.workstation.internal`) && PathPrefix(`/admin`)" + - "traefik.http.routers.headscale-ui-local.entrypoints=websecure" + - "traefik.http.routers.headscale-ui-local.middlewares=security-chain@file" + - "traefik.http.routers.headscale-ui-local.service=headscale-ui" + - "traefik.http.routers.headscale-ui-local.tls=true" + # Dev Router + - "traefik.http.routers.headscale-ui-dev.rule=Host(`hs.gigaforust.internal`) && PathPrefix(`/admin`)" + - "traefik.http.routers.headscale-ui-dev.entrypoints=websecure" + - "traefik.http.routers.headscale-ui-dev.middlewares=security-chain@file" + - "traefik.http.routers.headscale-ui-dev.service=headscale-ui" + - "traefik.http.routers.headscale-ui-dev.tls=true" + +networks: + proxy: + external: true diff --git a/headscale/config/config.yaml.example b/headscale/config/config.yaml.example new file mode 100644 index 0000000..1a485bf --- /dev/null +++ b/headscale/config/config.yaml.example @@ -0,0 +1,79 @@ +# https://wiki.serversatho.me/en/headscale + +# # Prod server_url +# server_url: https://hs.example.com +# # Local server_url +# server_url: https://hs.internal_domain.internal +# # Dev server_url +# server_url: https://hs.dev_internal_domain.internal +listen_addr: 0.0.0.0:8080 +metrics_listen_addr: 127.0.0.1:9090 +grpc_listen_addr: 127.0.0.1:50443 +grpc_allow_insecure: false +noise: + private_key_path: /var/lib/headscale/noise_private.key +prefixes: + v4: 100.64.0.0/10 + v6: fd7a:115c:a1e0::/48 + allocation: sequential +derp: + server: + enabled: true + region_id: 999 + region_code: "headscale" + region_name: "Headscale Embedded DERP" + stun_listen_addr: "0.0.0.0:3478" + private_key_path: /var/lib/headscale/derp_server_private.key + automatically_add_embedded_derp_region: true + ipv4: 1.2.3.4 + ipv6: 2001:db8::1 + urls: + - https://controlplane.tailscale.com/derpmap/default + paths: [] + auto_update_enabled: true + update_frequency: 24h +disable_check_updates: false +ephemeral_node_inactivity_timeout: 30m +database: + type: sqlite + debug: false + gorm: + prepare_stmt: true + parameterized_queries: true + skip_err_record_not_found: true + slow_threshold: 1000 + sqlite: + path: /var/lib/headscale/db.sqlite + write_ahead_log: true + wal_autocheckpoint: 1000 +acme_url: https://acme-v02.api.letsencrypt.org/directory +acme_email: "" +tls_letsencrypt_hostname: "" +tls_letsencrypt_cache_dir: /var/lib/headscale/cache +tls_letsencrypt_challenge_type: HTTP-01 +tls_letsencrypt_listen: ":http" +tls_cert_path: "" +tls_key_path: "" +log: + format: text + level: info +policy: + mode: database + path: "" +dns: + magic_dns: true + base_domain: example.com + nameservers: + global: + - 1.1.1.1 + - 1.0.0.1 + - 2606:4700:4700::1111 + - 2606:4700:4700::1001 + split: {} + search_domains: [] + extra_records: [] +unix_socket: /var/run/headscale/headscale.sock +unix_socket_permission: "0770" +logtail: + enabled: false +randomize_client_port: false diff --git a/homepages/compose.yaml b/homepages/compose.yaml index eeabd28..17c3fac 100644 --- a/homepages/compose.yaml +++ b/homepages/compose.yaml @@ -3,8 +3,8 @@ services: build: context: . dockerfile: Dockerfile.forust - ports: - - "8085:80" + # ports: + # - "8085:80" restart: unless-stopped volumes: - ./forust_files:/usr/share/nginx/html @@ -42,8 +42,8 @@ services: build: context: . dockerfile: Dockerfile.xdfnx - ports: - - "8086:80" + # ports: + # - "8086:80" restart: unless-stopped volumes: - ./xdfnx_files:/usr/share/nginx/html @@ -77,7 +77,6 @@ services: - "traefik.http.routers.xdfnx-dev.service=xdfnx-homepage" - "traefik.http.routers.xdfnx-dev.tls=true" - networks: proxy: external: true diff --git a/homepages/forust_files/index.html b/homepages/forust_files/index.html index 3c2f5eb..8aff9c9 100644 --- a/homepages/forust_files/index.html +++ b/homepages/forust_files/index.html @@ -41,7 +41,14 @@
# PGP Key Fingerprint: A777 7CB7 D9C4 0A97 443D CCF0 7A3D A455 F820 5B82