Compare commits

..

1 Commits

Author SHA1 Message Date
forust 5b3ef39102 template for lampa service 2026-01-21 11:36:36 +01:00
62 changed files with 199 additions and 2124 deletions
-39
View File
@@ -1,39 +0,0 @@
name: Deploy to Server
run-name: Deploying to ${{ runner.os}} server on ${{ gitea.ref }}
on:
push:
branches:
- main
- ci/gitea-actions
jobs:
deploy:
runs-on: prod
steps:
- name: Fetch and Diff Analysis
id: diff
run: |
cd ${{ secrets.PROD_DIR }}
git fetch origin main
CHANGES=$(git diff --name-only HEAD origin/main | cut -d/ -f1 | sort -u | tr '\n' ' ')
echo "dirs=$CHANGES" >> $GITHUB_OUTPUT
echo "Changed dirs: $CHANGES"
- name: Sync Server Files
run: |
cd ${{ secrets.PROD_DIR }}
git reset --hard origin/main
echo "Server files synced with origin/main"
- name: Deploy Services
run: |
cd ${{ secrets.PROD_DIR }}
for dir in ${{ steps.diff.outputs.dirs }}; do
if [ -d "$dir" ] && ([ -f "$dir/compose.yaml" ] || [ -f "$dir/docker-compose.yaml" ]); then
echo ">>> Deploying $dir"
cd "$dir"
DOCKER_BUILDKIT=1 BUILDKIT_PROGRESS=plain docker compose up -d --build --no-color
cd ..
else
echo ">>> Skipping $dir: no compose file found"
fi
done
-41
View File
@@ -1,41 +0,0 @@
## BINARY MODE, USE WITH THE GITHUB RUNNER BINARY INSTALLED ON THE SERVER
name: Deploy to Server
run-name: Deploying onto server on ${{ github.ref }}
on:
push:
branches:
- main
- ci/actions
jobs:
deploy:
runs-on: [prod, self-hosted]
steps:
- name: Fetch and Diff Analysis
id: diff
run: |
cd ${{ secrets.PROD_DIR }}
git fetch origin main
CHANGES=$(git diff --name-only HEAD origin/main | cut -d/ -f1 | sort -u | tr '\n' ' ')
echo "dirs=$CHANGES" >> $GITHUB_OUTPUT
echo "Changed dirs: $CHANGES"
- name: Sync Server Files
run: |
cd ${{ secrets.PROD_DIR }}
git reset --hard origin/main
echo "Server files synced with origin/main"
- name: Deploy Services
run: |
cd ${{ secrets.PROD_DIR }}
for dir in ${{ steps.diff.outputs.dirs }}; do
if [ -d "$dir" ] && ([ -f "$dir/compose.yaml" ] || [ -f "$dir/docker-compose.yaml" ]); then
echo ">>> Deploying $dir"
cd "$dir"
DOCKER_BUILDKIT=1 BUILDKIT_PROGRESS=plain docker compose up -d --build --no-color
cd ..
else
echo ">>> Skipping $dir: no compose file found"
fi
done
+4 -10
View File
@@ -5,12 +5,12 @@ sync.ffs_lock
# Copyparty
*.hist/
# Volumes, configs and data directories
# Volumes and data directories
gitea/gitea-db/
gitea/gitea-data/*
n8n/n8n-data/*
n8n/n8n-node-data/*
adguardhome/conf/*
adguardhome/data/*
dockmon/data/*
portainer/portainer_data/*
metube/MeTube_downloads
@@ -21,7 +21,6 @@ checkmk/checkmk/*
downtify/Downtify_downloads
headscale/config/*
headscale/data/*
searxng/core-config/*
# Steaming services files
streaming/jellyfin/*
@@ -33,15 +32,12 @@ streaming/qbittorrent/*
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/dynamic/*.local.y*ml.*
traefik/dynamic/*.external.y*ml
traefik/logs/*
# SSL Certificates
@@ -81,8 +77,6 @@ replacements.txt
# Git
.gitattributes
# Gitea/github Runners
.runner
# Misc
.DS_Store
@@ -97,4 +91,4 @@ temp/*
.env.anna
.env.forust
.env.*
!*example
!*example
+7 -5
View File
@@ -11,24 +11,28 @@ services:
# - "68:68/tcp" # DHCP
# - "3000:3000/tcp"
volumes:
- data:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
- ./data/work:/opt/adguardhome/work
- ./data/conf:/opt/adguardhome/conf
- ./certs:/certs:ro
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.adguard.loadbalancer.server.port=3000"
# Prod Router
- "traefik.http.routers.adguard.rule=Host(`dns.forust.xyz`) || Host(`adguard.forust.xyz`)"
- "traefik.http.routers.adguard.entrypoints=websecure"
- "traefik.http.routers.adguard.middlewares=security-headers@file"
- "traefik.http.routers.adguard.tls=true"
# Local Router
- "traefik.http.routers.adguard-local.rule=Host(`adguard.workstation.internal`) || Host(`dns.workstation.internal`)"
- "traefik.http.routers.adguard-local.entrypoints=websecure"
- "traefik.http.routers.adguard-local.middlewares=security-headers@file"
- "traefik.http.routers.adguard-local.tls=true"
# Dev Router
- "traefik.http.routers.adguard-dev.rule=Host(`adguard.gigaforust.internal`) || Host(`dns.gigaforust.internal`)"
- "traefik.http.routers.adguard-dev.rule=Host(`adguard.gigaforust.internal`) Host(`dns.gigaforust.internal`)"
- "traefik.http.routers.adguard-dev.entrypoints=websecure"
- "traefik.http.routers.adguard-dev.middlewares=security-headers@file"
- "traefik.http.routers.adguard-dev.tls=true"
# DoH Router
- "traefik.http.routers.dns.rule=(Host(`dns.forust.xyz`) && PathPrefix(`/dns-query`))"
@@ -41,8 +45,6 @@ services:
- glance.description=AdGuard Home is a network-wide software for blocking ads.
networks:
- proxy
volumes:
data:
networks:
proxy:
external: true
+7 -1
View File
@@ -42,20 +42,26 @@ services:
- ./custom-templates:/templates
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.authentik-server.loadbalancer.server.port=9000"
# Prod Router
- "traefik.http.routers.authentik-server.rule=Host(`auth.forust.xyz`)"
- "traefik.http.routers.authentik-server.entrypoints=websecure"
- "traefik.http.routers.authentik-server.middlewares=security-headers@file"
- "traefik.http.routers.authentik-server.service=authentik-server"
- "traefik.http.routers.authentik-server.tls=true"
# Local Router
- "traefik.http.routers.authentik-server-local.rule=Host(`auth.workstation.internal`)"
- "traefik.http.routers.authentik-server-local.rule=Host(`auth.workstation.internal`) || Host(`auth-dashboard.internal`)"
- "traefik.http.routers.authentik-server-local.entrypoints=websecure"
- "traefik.http.routers.authentik-server-local.middlewares=security-headers@file"
- "traefik.http.routers.authentik-server-local.service=authentik-server"
- "traefik.http.routers.authentik-server-local.tls=true"
# Dev Router
- "traefik.http.routers.authentik-server-dev.rule=Host(`auth.gigaforust.internal`)"
- "traefik.http.routers.authentik-server-dev.entrypoints=websecure"
- "traefik.http.routers.authentik-server-dev.middlewares=security-headers@file"
- "traefik.http.routers.authentik-server-dev.service=authentik-server"
- "traefik.http.routers.authentik-server-dev.tls=true"
networks:
- proxy
-15
View File
@@ -1,15 +0,0 @@
CLOUDFLARE_API_TOKEN=YOUR_CLOUDFLARE_API_TOKEN
DOMAINS=example.com,dns.example.com,mc.example.com,auth.example.com,ssh.example.com
IP4_DOMAINS=
IP6_DOMAINS=
IP4_PROVIDER=cloudflare.trace
IP6_PROVIDER=none # change if you want to update AAAA
UPDATE_CRON=@every 5m
UPDATE_ON_START=true
DELETE_ON_STOP=false
DELETE_ON_FAILURE=true
TTL=1
PROXIED=!is(dns.example.com) && !is(mc.example.com) && !is(ssh.example.com)
EMOJI=true
UPTIMEKUMA=https://uptime-kuma.example.com/api/push/AsaSDFGFkfklaFALSKffkfFKfkfkfkFK?status=up&msg=OK&ping=
REJECT_CLOUDFLARE_IPS=true
+4 -21
View File
@@ -6,25 +6,8 @@ services:
security_opt:
- no-new-privileges:true
network_mode: 'host'
# https://github.com/timothymiller/cloudflare-ddns#-quick-start
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?Cloudflare API token is required}
- DOMAINS=${DOMAINS:-}
- IP4_DOMAINS=${IP4_DOMAINS:-}
- IP6_DOMAINS=${IP6_DOMAINS:-}
- IP4_PROVIDER=${IP4_PROVIDER:-cloudflare.trace}
- IP6_PROVIDER=${IP6_PROVIDER:-none}
- UPDATE_CRON=${UPDATE_CRON:-@every 5m}
- UPDATE_ON_START=${UPDATE_ON_START:-true}
- DELETE_ON_STOP=${DELETE_ON_STOP:-false}
- DELETE_ON_FAILURE=${DELETE_ON_FAILURE:-true}
- TTL=${TTL:-1} # 1=auto
# to proxy only "dns.example.com" and "wfs.example.com" use "!is(dns.domain.com) && !is (wfs.domain.com)"
- PROXIED=${PROXIED:-true}
- EMOJI=${EMOJI:-true}
- UPTIMEKUMA=${UPTIMEKUMA:-}
- HEALTHCHECKS=${HEALTHCHECKS:-}
- REJECT_CLOUDFLARE_IPS=${REJECT_CLOUDFLARE_IPS:-true}
# volumes:
# Prefer using environment variables for configuration, config.json legacy support
# - ./config.json:/config.json
- PUID=1000
- PGID=1000
volumes:
- ./config.json:/config.json
+4 -1
View File
@@ -13,21 +13,24 @@ services:
environment:
- CMK_PASSWORD=${CMK_PASSWORD:-password}
- CMK_SITE_ID=cmk
- TZ=${TZ:-Etc/UTC}
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.middlewares=security-headers@file"
- "traefik.http.routers.checkmk.tls=true"
# Local Router
- "traefik.http.routers.checkmk-local.rule=Host(`cmk.workstation.internal`)"
- "traefik.http.routers.checkmk-local.entrypoints=websecure"
- "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.entrypoints=websecure"
- "traefik.http.routers.checkmk-dev.tls=true"
networks:
+5 -1
View File
@@ -15,6 +15,7 @@ services:
retries: 3
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.dockmon.loadbalancer.server.port=443"
- "traefik.http.services.dockmon.loadbalancer.server.scheme=https"
- "traefik.http.services.dockmon.loadbalancer.serverstransport=insecureTransport@file"
@@ -22,15 +23,18 @@ services:
# Prod Router
- "traefik.http.routers.dockmon.rule=Host(`dockmon.forust.xyz`)"
- "traefik.http.routers.dockmon.entrypoints=websecure"
- "traefik.http.routers.dockmon.middlewares=security-headers@file"
- "traefik.http.routers.dockmon.middlewares=security-chain@file"
- "traefik.http.routers.dockmon.service=dockmon"
- "traefik.http.routers.dockmon.tls=true"
# Local Router
- "traefik.http.routers.dockmon-local.rule=Host(`dockmon.workstation.internal`)"
- "traefik.http.routers.dockmon-local.entrypoints=websecure"
- "traefik.http.routers.dockmon-local.middlewares=security-headers@file"
- "traefik.http.routers.dockmon-local.tls=true"
# Dev Router
- "traefik.http.routers.dockmon-dev.rule=Host(`dockmon.gigaforust.internal`)"
- "traefik.http.routers.dockmon-dev.entrypoints=websecure"
- "traefik.http.routers.dockmon-dev.middlewares=security-chain@file"
- "traefik.http.routers.dockmon-dev.tls=true"
# Glance Metadata
+15 -13
View File
@@ -2,28 +2,30 @@ services:
downtify:
container_name: downtify
image: ghcr.io/henriquesebastiao/downtify:latest
restart: unless-stopped
# ports:
# - '7077:8000'
volumes:
- ./Downtify_downloads:/downloads
labels:
- "traefik.enable=true"
- "traefik.http.services.downtify.loadbalancer.server.port=8000"
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.services.downtify.loadbalancer.server.port=8000
# Prod Router
- "traefik.http.routers.downtify.rule=Host(`downtify.forust.xyz`)"
- "traefik.http.routers.downtify.entrypoints=websecure"
- "traefik.http.routers.downtify.middlewares=security-chain@file"
- "traefik.http.routers.downtify.tls=true"
- traefik.http.routers.downtify.rule=Host(`downtify.forust.xyz`)
- traefik.http.routers.downtify.entrypoints=websecure
- traefik.http.routers.downtify.middlewares=security-chain@file
- traefik.http.routers.downtify.tls=true
# Local Router
- "traefik.http.routers.downtify-local.rule=Host(`downtify.workstation.internal`)"
- "traefik.http.routers.downtify-local.entrypoints=websecure"
- "traefik.http.routers.downtify-local.tls=true"
- traefik.http.routers.downtify-local.rule=Host(`downtify.workstation.internal`) || Host(`downtify.internal`)
- traefik.http.routers.downtify-local.entrypoints=websecure
- traefik.http.routers.downtify-local.middlewares=security-headers@file
- traefik.http.routers.downtify-local.tls=true
# Dev Router
- "traefik.http.routers.downtify-dev.rule=Host(`downtify.gigaforust.internal`)"
- "traefik.http.routers.downtify-dev.entrypoints=websecure"
- "traefik.http.routers.downtify-dev.tls=true"
- traefik.http.routers.downtify-dev.rule=Host(`downtify.gigaforust.internal`)
- traefik.http.routers.downtify-dev.entrypoints=websecure
- traefik.http.routers.downtify-dev.middlewares=security-chain@file
- traefik.http.routers.downtify-dev.tls=true
networks:
- proxy
networks:
+2 -3
View File
@@ -3,13 +3,12 @@ services:
build:
context: .
dockerfile: Dockerfile
image: gcr.forust.xyz/forust/dtek-notif:latest
pull_policy: build
restart: unless-stopped
environment:
- TZ=Europe/Kyiv
dns:
- 1.1.1.1
- 8.8.8.8
networks:
- default
- default
-4
View File
@@ -17,8 +17,6 @@ services:
session-keeper:
build: ./phpsessid-bot
image: gcr.forust.xyz/forust/session-keeper:latest
pull_policy: build
env_file: .env
restart: unless-stopped
depends_on:
@@ -33,8 +31,6 @@ services:
webinar-checker:
build: ./webinar-checker
image: gcr.forust.xyz/forust/webinar-checker:latest
pull_policy: build
env_file: .env
restart: unless-stopped
depends_on:
+1 -274
View File
@@ -1,10 +1,7 @@
import os
import re
import logging
import redis
import json
import time
from datetime import datetime, timedelta
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ChatMember
from telegram.constants import ChatType
@@ -33,7 +30,6 @@ def _env(key, default=None):
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('/')}"
DIARY_URL = f"{EDU_BASE.rstrip('/')}/user/diary"
WEBINAR_CHECK_INTERVAL = int(_env('WEBINAR_CHECK_INTERVAL', 60))
REDIS_HOST = _env('REDIS_HOST', 'redis')
@@ -256,205 +252,6 @@ def get_admin_keyboard(user_id: int):
]
return InlineKeyboardMarkup(keyboard)
# --- Diary Functions ---
DIARY_MONTH_NAMES = ['', 'Січня', 'Лютого', 'Березня', 'Квітня', 'Травня', 'Червня',
'Липня', 'Серпня', 'Вересня', 'Жовтня', 'Листопада', 'Грудня']
DIARY_WEEKDAYS_SHORT = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Нд']
def get_diary_keyboard():
today = datetime.now()
keyboard = [
[
InlineKeyboardButton(f"📌 Сьогодні ({today.day}.{today.month:02d})", callback_data="diary_today"),
InlineKeyboardButton("📌 Завтра", callback_data="diary_tomorrow"),
],
[
InlineKeyboardButton("📅 Цей тиждень", callback_data="diary_week"),
InlineKeyboardButton("📅 Весь місяць", callback_data="diary_month"),
],
]
return InlineKeyboardMarkup(keyboard)
def _parse_calendar_html(table_html: str) -> tuple:
"""Parse calendar HTML table into (month_text, {day_num: {weekday, events}})."""
days = {}
weekdays = []
rows = re.findall(r'<tr[^>]*>(.*?)</tr>', table_html, re.DOTALL)
month_text = ''
for r_idx, row in enumerate(rows):
cells = re.findall(r'<t[dh][^>]*>(.*?)</t[dh]>', row, re.DOTALL)
if r_idx == 0:
# Month navigation row: extract "Травень 2026" from nav text
raw = re.sub(r'<[^>]+>', ' ', row).strip()
raw = re.sub(r'\s+', ' ', raw)
m = re.search(r'([А-Яа-яіїєґ\']+\s*:?\s*\d{4})', raw)
if m:
month_text = m.group(1).replace(' : ', ' ').strip()
else:
month_text = raw
elif r_idx == 1:
# Day names row
for cell in cells:
name = re.sub(r'<[^>]+>', '', cell).strip()
if name:
weekdays.append(name)
else:
# Data rows: each cell = a day
for col_idx, cell in enumerate(cells):
# Extract day number — first number in the cell text
text = re.sub(r'<[^>]+>', ' ', cell).strip()
text = re.sub(r'\s+', ' ', text)
dm = re.match(r'(\d+)', text)
if not dm:
continue
day_num = dm.group(1)
# Extract events: title attribute (full name) of ALL <a> tags inside the cell
events = []
for a_match in re.finditer(r'<a[^>]*>(.*?)</a>', cell, re.DOTALL):
a_tag = a_match.group(0)
# Prefer the title attribute (contains full name, not truncated)
title_m = re.search(r'title\s*=\s*"([^"]*)"', a_tag)
if title_m:
et = title_m.group(1).strip()
else:
et = re.sub(r'<[^>]+>', '', a_match.group(1)).strip()
if et:
events.append(et)
weekday = weekdays[col_idx] if col_idx < len(weekdays) else ''
days[day_num] = {'weekday': weekday, 'events': events}
return month_text, days
async def fetch_diary_data(phpsessid: str) -> dict | None:
logger.info("Fetching diary data via Playwright...")
try:
async with async_playwright() as p:
browser = await p.chromium.connect(PLAYWRIGHT_WS)
try:
context_browser = await browser.new_context(user_agent=USER_AGENT)
await context_browser.add_cookies([{
'name': 'PHPSESSID',
'value': phpsessid,
'domain': 'edu.edu.vn.ua',
'path': '/'
}])
page = await context_browser.new_page()
try:
await page.goto(DIARY_URL, wait_until='domcontentloaded')
await page.wait_for_selector('table.calendar', timeout=10000)
await page.wait_for_timeout(1500)
table_html = await page.evaluate("""
() => {
const t = document.querySelector('table.calendar');
return t ? t.outerHTML : null;
}
""")
if not table_html:
logger.error("table.calendar not found in DOM")
return None
# Debug: save HTML for troubleshooting
try:
with open('/tmp/diary_debug.html', 'w', encoding='utf-8') as f:
f.write(table_html)
except Exception:
pass
month_text, days = _parse_calendar_html(table_html)
logger.info(f"Diary parsed: month={month_text!r}, days_with_events={sum(1 for d in days.values() if d['events'])}/{len(days)}")
return {'monthFullText': month_text, 'days': days}
except Exception as e:
logger.error(f"Error parsing diary: {e}")
return None
finally:
await page.close()
await context_browser.close()
finally:
await browser.close()
except Exception as e:
logger.error(f"Playwright error in diary fetch: {e}")
return None
def _parse_diary_month(text: str) -> str:
match = re.search(r'([А-Яа-яіїєґ\']+\s*:\s*\d{4})', text)
if match:
return match.group(1).replace(' : ', ' ').strip()
return text.strip()
def format_diary_day(data: dict, day_num: int) -> str:
days = data.get('days', {})
month_str = _parse_diary_month(data.get('monthFullText', ''))
day_data = days.get(str(day_num))
lines = [f"📅 <b>{day_num} {month_str}</b>", "" * 18]
if not day_data or not day_data.get('events'):
lines.append("Немає подій")
else:
for e in day_data['events']:
lines.append(f"📌 {e}")
lines.append(f"\n🔗 {DIARY_URL}")
return "\n".join(lines)
def format_diary_week(data: dict, today: datetime) -> str:
days = data.get('days', {})
month_str = _parse_diary_month(data.get('monthFullText', ''))
monday = today - timedelta(days=today.weekday())
sunday = monday + timedelta(days=6)
lines = [f"📅 <b>Тиждень {monday.day}.{monday.month} {sunday.day}.{sunday.month}</b>\n"]
for i in range(7):
d = monday + timedelta(days=i)
day_data = days.get(str(d.day))
lines.append(f"─ <b>{DIARY_WEEKDAYS_SHORT[i]} {d.day}.{d.month}</b> ─")
if not day_data or not day_data.get('events'):
lines.append("Немає подій\n")
else:
for e in day_data['events']:
lines.append(f"📌 {e}")
lines.append("")
lines.append(f"🔗 {DIARY_URL}")
return "\n".join(lines)
def format_diary_month(data: dict) -> str:
days = data.get('days', {})
month_str = _parse_diary_month(data.get('monthFullText', ''))
lines = [f"📅 <b>{month_str}</b>\n"]
for day_num in sorted(days.keys(), key=int):
day_data = days[day_num]
events = day_data.get('events', [])
weekday = day_data.get('weekday', '')
lines.append(f"─ <b>{weekday} {day_num}</b> ─")
if not events:
lines.append("Немає подій\n")
else:
for e in events:
lines.append(f"📌 {e}")
lines.append("")
lines.append(f"🔗 {DIARY_URL}")
return "\n".join(lines)
async def _get_diary_data(context: ContextTypes.DEFAULT_TYPE) -> dict | None:
cached = context.user_data.get('diary_cache')
now_ts = time.time()
if cached and (now_ts - cached.get('timestamp', 0)) < 300:
return cached['data']
phpsessid = redis_client.get(KEY_PHPSESSID)
if not phpsessid:
return None
data = await fetch_diary_data(phpsessid)
if data:
context.user_data['diary_cache'] = {'data': data, 'timestamp': now_ts}
return data
# --- Command Handlers ---
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -579,74 +376,6 @@ async def clear_history(update: Update, context: ContextTypes.DEFAULT_TYPE):
logger.error(f"Failed to clear history: {e}")
await update.message.reply_text(t(admin_id, 'history_clear_failed'))
async def diary_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
user = update.effective_user
chat = update.effective_chat
if not is_whitelisted(user.id):
await update.message.reply_text(t(user.id, 'access_denied'))
return
await update.message.reply_text(
"📅 <b>Щоденник</b> — виберіть період:",
parse_mode='HTML',
reply_markup=get_diary_keyboard()
)
async def diary_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
user_id = query.from_user.id
await query.answer()
if user_id != ADMIN_ID:
if not is_whitelisted(user_id):
await query.edit_message_text("⛔ Доступ заборонено.")
return
data = query.data
if data == "diary_refresh":
context.user_data.pop('diary_cache', None)
await query.edit_message_text("🔄 Завантажую щоденник...")
diary_data = await _get_diary_data(context)
if not diary_data:
await query.edit_message_text("❌ Не вдалося завантажити щоденник. Немає сесії або помилка.")
return
await query.edit_message_text(
"📅 <b>Щоденник</b> — виберіть період:",
parse_mode='HTML',
reply_markup=get_diary_keyboard()
)
return
await query.edit_message_text("🔄 Завантажую щоденник...")
diary_data = await _get_diary_data(context)
if not diary_data:
await query.edit_message_text("❌ Не вдалося завантажити щоденник.")
return
today = datetime.now()
if data == "diary_today":
text = format_diary_day(diary_data, today.day)
elif data == "diary_tomorrow":
tomorrow = today + timedelta(days=1)
if tomorrow.day < today.day:
text = "❌ Дані за наступний місяць недоступні. Перейдіть на сайт."
else:
text = format_diary_day(diary_data, tomorrow.day)
elif data == "diary_week":
text = format_diary_week(diary_data, today)
elif data == "diary_month":
text = format_diary_month(diary_data)
else:
return
if len(text) > 4096:
text = text[:4090] + "\n\n✂️ ...(обрізано)"
await query.edit_message_text(
text,
parse_mode='HTML',
reply_markup=get_diary_keyboard()
)
# --- Admin Callbacks ---
async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -920,10 +649,8 @@ def main():
app.add_handler(CommandHandler("adduser", add_user))
app.add_handler(CommandHandler("removeuser", remove_user))
app.add_handler(CommandHandler("clearhistory", clear_history))
app.add_handler(CommandHandler("diary", diary_command))
# Callback handlers - diary first, then language selection, then admin panel
app.add_handler(CallbackQueryHandler(diary_callback, pattern="^diary_"))
# Callback handlers - language selection first, then admin panel
app.add_handler(CallbackQueryHandler(language_callback, pattern="^lang_"))
app.add_handler(CallbackQueryHandler(admin_callback))
-5
View File
@@ -1,5 +0,0 @@
FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY html /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
-21
View File
@@ -1,21 +0,0 @@
services:
errorpage:
build: .
image: gcr.forust.xyz/forust/error-pages:latest
pull_policy: build
container_name: error-pages
restart: unless-stopped
# ports:
# - 1234:80
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.services.error-pages.loadbalancer.server.port=80"
# Error handler middleware
- "traefik.http.middlewares.error-pages.errors.status=400,402-599"
- "traefik.http.middlewares.error-pages.errors.service=error-pages"
- "traefik.http.middlewares.error-pages.errors.query=/{status}.html"
networks:
proxy:
external: true
-208
View File
@@ -1,208 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>403 // Forbidden</title>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* hidden in a plain sight? */
:root {
--bg-color: #050505;
--text-color: #e0e0e0;
--accent: #ffffff;
--dim: #666666;
--font-mono: 'Courier New', Courier, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-mono);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
a {
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--dim);
transition: all 0.2s;
}
a:hover {
background-color: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.container {
max-width: 800px;
margin: 0 auto;
}
/* TEXT */
h1 {
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: -2px;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--dim);
display: inline-block;
padding-right: 20px;
}
.subtitle {
color: var(--dim);
margin-bottom: 2rem;
}
hr {
border: 0;
border-top: 1px dashed var(--dim);
margin: 2rem 0;
}
.comment {
color: var(--dim);
font-size: 0.9rem;
margin-left: 10px;
}
/* SECTIONS */
section {
margin-bottom: 3rem;
}
/* LISTS */
ul {
list-style: none;
}
.link-list li {
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 15px;
}
/* STACK GRID */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.skill-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.level {
font-weight: bold;
}
.special .level {
color: var(--text-color);
text-shadow: 1px 0 0 red, -1px 0 0 blue;
}
/* my dudes */
.team-grid {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.member {
text-align: center;
width: 100px;
}
.avatar {
width: 80px;
height: 80px;
background-color: #222;
border: 2px solid var(--text-color);
margin: 0 auto 10px auto;
background-size: cover;
}
/* if no avatar added: */
.placeholder::before {
content: "?";
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 2rem;
color: var(--dim);
}
/* REPOS */
.repo-list li {
margin-bottom: 1rem;
}
/* FOOTER */
footer {
text-align: center;
color: var(--dim);
font-size: 0.8rem;
/* flag{why-are-you-here?} */
margin-top: 4rem;
}
/* SMTH RESPONSIVE */
@media (max-width: 600px) {
.grid-2 {
grid-template-columns: 1fr;
gap: 0;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1 class="glitch" data-text="403">403</h1>
<p class="subtitle">> Forbidden / Access Denied.</p>
</header>
<hr>
<section id="message">
<h2>./error_message</h2>
<p>You do not have permission to access this resource.</p>
<br>
<p>Check the <a href="https://status.forust.xyz">System Status</a> if you believe this is an
error.</p>
</section>
<footer>
<p>root@error:~$ sudo access_resource</p>
<p>User is not in the sudoers file. This incident will be reported.</p>
<p>&copy; XRock - Just Signal.</p>
</footer>
</div>
</body>
</html>
-207
View File
@@ -1,207 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 // Not Found</title>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* hidden in a plain sight? */
:root {
--bg-color: #050505;
--text-color: #e0e0e0;
--accent: #ffffff;
--dim: #666666;
--font-mono: 'Courier New', Courier, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-mono);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
a {
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--dim);
transition: all 0.2s;
}
a:hover {
background-color: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.container {
max-width: 800px;
margin: 0 auto;
}
/* TEXT */
h1 {
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: -2px;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--dim);
display: inline-block;
padding-right: 20px;
}
.subtitle {
color: var(--dim);
margin-bottom: 2rem;
}
hr {
border: 0;
border-top: 1px dashed var(--dim);
margin: 2rem 0;
}
.comment {
color: var(--dim);
font-size: 0.9rem;
margin-left: 10px;
}
/* SECTIONS */
section {
margin-bottom: 3rem;
}
/* LISTS */
ul {
list-style: none;
}
.link-list li {
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 15px;
}
/* STACK GRID */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.skill-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.level {
font-weight: bold;
}
.special .level {
color: var(--text-color);
text-shadow: 1px 0 0 red, -1px 0 0 blue;
}
/* my dudes */
.team-grid {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.member {
text-align: center;
width: 100px;
}
.avatar {
width: 80px;
height: 80px;
background-color: #222;
border: 2px solid var(--text-color);
margin: 0 auto 10px auto;
background-size: cover;
}
/* if no avatar added: */
.placeholder::before {
content: "?";
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 2rem;
color: var(--dim);
}
/* REPOS */
.repo-list li {
margin-bottom: 1rem;
}
/* FOOTER */
footer {
text-align: center;
color: var(--dim);
font-size: 0.8rem;
/* flag{why-are-you-here?} */
margin-top: 4rem;
}
/* SMTH RESPONSIVE */
@media (max-width: 600px) {
.grid-2 {
grid-template-columns: 1fr;
gap: 0;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1 class="glitch" data-text="404">404</h1>
<p class="subtitle">> Page Not Found / Lost in the Void.</p>
</header>
<hr>
<section id="message">
<h2>./error_message</h2>
<p>The page you are looking for does not exist or has been moved.</p>
<br>
<p>Check the <a href="https://status.forust.xyz">System Status</a> if you believe this is an error.</p>
</section>
<footer>
<p>root@error:~$ ping target</p>
<p>Destination Host Unreachable</p>
<p>&copy; XRock - Just Signal.</p>
</footer>
</div>
</body>
</html>
-207
View File
@@ -1,207 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>500 // Server Error</title>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* hidden in a plain sight? */
:root {
--bg-color: #050505;
--text-color: #e0e0e0;
--accent: #ffffff;
--dim: #666666;
--font-mono: 'Courier New', Courier, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-mono);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
a {
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--dim);
transition: all 0.2s;
}
a:hover {
background-color: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.container {
max-width: 800px;
margin: 0 auto;
}
/* TEXT */
h1 {
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: -2px;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--dim);
display: inline-block;
padding-right: 20px;
}
.subtitle {
color: var(--dim);
margin-bottom: 2rem;
}
hr {
border: 0;
border-top: 1px dashed var(--dim);
margin: 2rem 0;
}
.comment {
color: var(--dim);
font-size: 0.9rem;
margin-left: 10px;
}
/* SECTIONS */
section {
margin-bottom: 3rem;
}
/* LISTS */
ul {
list-style: none;
}
.link-list li {
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 15px;
}
/* STACK GRID */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.skill-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.level {
font-weight: bold;
}
.special .level {
color: var(--text-color);
text-shadow: 1px 0 0 red, -1px 0 0 blue;
}
/* my dudes */
.team-grid {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.member {
text-align: center;
width: 100px;
}
.avatar {
width: 80px;
height: 80px;
background-color: #222;
border: 2px solid var(--text-color);
margin: 0 auto 10px auto;
background-size: cover;
}
/* if no avatar added: */
.placeholder::before {
content: "?";
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 2rem;
color: var(--dim);
}
/* REPOS */
.repo-list li {
margin-bottom: 1rem;
}
/* FOOTER */
footer {
text-align: center;
color: var(--dim);
font-size: 0.8rem;
/* flag{why-are-you-here?} */
margin-top: 4rem;
}
/* SMTH RESPONSIVE */
@media (max-width: 600px) {
.grid-2 {
grid-template-columns: 1fr;
gap: 0;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1 class="glitch" data-text="500">500</h1>
<p class="subtitle">> Internal Server Error / System Failure.</p>
</header>
<hr>
<section id="message">
<h2>./error_message</h2>
<p>Something went wrong on our end. We are working to fix it.</p>
<br>
<p>Check the <a href="https://status.forust.xyz">System Status</a> for more information.</p>
</section>
<footer>
<p>root@error:~$ systemctl status service</p>
<p>Active: failed (Result: core-dump)</p>
<p>&copy; XRock - Just Signal.</p>
</footer>
</div>
</body>
</html>
-207
View File
@@ -1,207 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>502 // Bad Gateway</title>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* hidden in a plain sight? */
:root {
--bg-color: #050505;
--text-color: #e0e0e0;
--accent: #ffffff;
--dim: #666666;
--font-mono: 'Courier New', Courier, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-mono);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
a {
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--dim);
transition: all 0.2s;
}
a:hover {
background-color: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.container {
max-width: 800px;
margin: 0 auto;
}
/* TEXT */
h1 {
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: -2px;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--dim);
display: inline-block;
padding-right: 20px;
}
.subtitle {
color: var(--dim);
margin-bottom: 2rem;
}
hr {
border: 0;
border-top: 1px dashed var(--dim);
margin: 2rem 0;
}
.comment {
color: var(--dim);
font-size: 0.9rem;
margin-left: 10px;
}
/* SECTIONS */
section {
margin-bottom: 3rem;
}
/* LISTS */
ul {
list-style: none;
}
.link-list li {
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 15px;
}
/* STACK GRID */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.skill-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.level {
font-weight: bold;
}
.special .level {
color: var(--text-color);
text-shadow: 1px 0 0 red, -1px 0 0 blue;
}
/* my dudes */
.team-grid {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.member {
text-align: center;
width: 100px;
}
.avatar {
width: 80px;
height: 80px;
background-color: #222;
border: 2px solid var(--text-color);
margin: 0 auto 10px auto;
background-size: cover;
}
/* if no avatar added: */
.placeholder::before {
content: "?";
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 2rem;
color: var(--dim);
}
/* REPOS */
.repo-list li {
margin-bottom: 1rem;
}
/* FOOTER */
footer {
text-align: center;
color: var(--dim);
font-size: 0.8rem;
/* flag{why-are-you-here?} */
margin-top: 4rem;
}
/* SMTH RESPONSIVE */
@media (max-width: 600px) {
.grid-2 {
grid-template-columns: 1fr;
gap: 0;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1 class="glitch" data-text="502">502</h1>
<p class="subtitle">> Bad Gateway / System Failure.</p>
</header>
<hr>
<section id="message">
<h2>./error_message</h2>
<p>The server received an invalid response from the upstream server.</p>
<br>
<p>Check the <a href="https://status.forust.xyz">System Status</a> for more information.</p>
</section>
<footer>
<p>root@error:~$ curl -I upstream_host</p>
<p>HTTP/1.1 502 Bad Gateway</p>
<p>&copy; XRock - Just Signal.</p>
</footer>
</div>
</body>
</html>
-207
View File
@@ -1,207 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>503 // Service Unavailable</title>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* hidden in a plain sight? */
:root {
--bg-color: #050505;
--text-color: #e0e0e0;
--accent: #ffffff;
--dim: #666666;
--font-mono: 'Courier New', Courier, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-mono);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
a {
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--dim);
transition: all 0.2s;
}
a:hover {
background-color: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.container {
max-width: 800px;
margin: 0 auto;
}
/* TEXT */
h1 {
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: -2px;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--dim);
display: inline-block;
padding-right: 20px;
}
.subtitle {
color: var(--dim);
margin-bottom: 2rem;
}
hr {
border: 0;
border-top: 1px dashed var(--dim);
margin: 2rem 0;
}
.comment {
color: var(--dim);
font-size: 0.9rem;
margin-left: 10px;
}
/* SECTIONS */
section {
margin-bottom: 3rem;
}
/* LISTS */
ul {
list-style: none;
}
.link-list li {
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 15px;
}
/* STACK GRID */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.skill-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.level {
font-weight: bold;
}
.special .level {
color: var(--text-color);
text-shadow: 1px 0 0 red, -1px 0 0 blue;
}
/* my dudes */
.team-grid {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.member {
text-align: center;
width: 100px;
}
.avatar {
width: 80px;
height: 80px;
background-color: #222;
border: 2px solid var(--text-color);
margin: 0 auto 10px auto;
background-size: cover;
}
/* if no avatar added: */
.placeholder::before {
content: "?";
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 2rem;
color: var(--dim);
}
/* REPOS */
.repo-list li {
margin-bottom: 1rem;
}
/* FOOTER */
footer {
text-align: center;
color: var(--dim);
font-size: 0.8rem;
/* flag{why-are-you-here?} */
margin-top: 4rem;
}
/* SMTH RESPONSIVE */
@media (max-width: 600px) {
.grid-2 {
grid-template-columns: 1fr;
gap: 0;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1 class="glitch" data-text="503">503</h1>
<p class="subtitle">> Service Unavailable / System Failure.</p>
</header>
<hr>
<section id="message">
<h2>./error_message</h2>
<p>The server is currently unable to handle the request due to maintenance or overload.</p>
<br>
<p>Check the <a href="https://status.forust.xyz">System Status</a> for more information.</p>
</section>
<footer>
<p>root@error:~$ systemctl start service</p>
<p>Job for service failed because the control process exited with error code.</p>
<p>&copy; XRock - Just Signal.</p>
</footer>
</div>
</body>
</html>
-207
View File
@@ -1,207 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>504 // Gateway Timeout</title>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* hidden in a plain sight? */
:root {
--bg-color: #050505;
--text-color: #e0e0e0;
--accent: #ffffff;
--dim: #666666;
--font-mono: 'Courier New', Courier, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-mono);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
a {
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--dim);
transition: all 0.2s;
}
a:hover {
background-color: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.container {
max-width: 800px;
margin: 0 auto;
}
/* TEXT */
h1 {
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: -2px;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--dim);
display: inline-block;
padding-right: 20px;
}
.subtitle {
color: var(--dim);
margin-bottom: 2rem;
}
hr {
border: 0;
border-top: 1px dashed var(--dim);
margin: 2rem 0;
}
.comment {
color: var(--dim);
font-size: 0.9rem;
margin-left: 10px;
}
/* SECTIONS */
section {
margin-bottom: 3rem;
}
/* LISTS */
ul {
list-style: none;
}
.link-list li {
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 15px;
}
/* STACK GRID */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.skill-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.level {
font-weight: bold;
}
.special .level {
color: var(--text-color);
text-shadow: 1px 0 0 red, -1px 0 0 blue;
}
/* my dudes */
.team-grid {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.member {
text-align: center;
width: 100px;
}
.avatar {
width: 80px;
height: 80px;
background-color: #222;
border: 2px solid var(--text-color);
margin: 0 auto 10px auto;
background-size: cover;
}
/* if no avatar added: */
.placeholder::before {
content: "?";
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 2rem;
color: var(--dim);
}
/* REPOS */
.repo-list li {
margin-bottom: 1rem;
}
/* FOOTER */
footer {
text-align: center;
color: var(--dim);
font-size: 0.8rem;
/* flag{why-are-you-here?} */
margin-top: 4rem;
}
/* SMTH RESPONSIVE */
@media (max-width: 600px) {
.grid-2 {
grid-template-columns: 1fr;
gap: 0;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1 class="glitch" data-text="504">504</h1>
<p class="subtitle">> Gateway Timeout / System Failure.</p>
</header>
<hr>
<section id="message">
<h2>./error_message</h2>
<p>The server did not receive a timely response from the upstream server.</p>
<br>
<p>Check the <a href="https://status.forust.xyz">System Status</a> for more information.</p>
</section>
<footer>
<p>root@error:~$ timeout 30s curl upstream</p>
<p>curl: (28) Operation timed out after 30001 milliseconds with 0 bytes received</p>
<p>&copy; XRock - Just Signal.</p>
</footer>
</div>
</body>
</html>
+5 -6
View File
@@ -1,6 +1,6 @@
services:
server:
image: docker.gitea.com/gitea:1.26
image: docker.gitea.com/gitea:1.25.1
container_name: gitea
restart: always
environment:
@@ -31,29 +31,28 @@ services:
- /etc/localtime:/etc/localtime:ro
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
# Prod Router
- "traefik.http.routers.gitea.rule=Host(`gitea.forust.xyz`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.middlewares=security-headers@file"
- "traefik.http.routers.gitea.tls=true"
# Local Router
- "traefik.http.routers.gitea-local.rule=Host(`gitea.workstation.internal`)"
- "traefik.http.routers.gitea-local.entrypoints=websecure"
- "traefik.http.routers.gitea-local.middlewares=security-headers@file"
- "traefik.http.routers.gitea-local.tls=true"
# Dev Router
- "traefik.http.routers.gitea-dev.rule=Host(`gitea.gigaforust.internal`)"
- "traefik.http.routers.gitea-dev.entrypoints=websecure"
- "traefik.http.routers.gitea-dev.middlewares=security-headers@file"
- "traefik.http.routers.gitea-dev.tls=true"
# SSH Router
- "traefik.tcp.services.gitea.loadbalancer.server.port=22"
- "traefik.tcp.routers.gitea.entrypoints=ssh"
- "traefik.tcp.routers.gitea.rule=HostSNI(`*`)"
# Gitea container registry Router
- "traefik.http.routers.gitea-registry.rule=Host(`gcr.forust.xyz`) && PathPrefix(`/v2`)"
- "traefik.http.routers.gitea-registry.entrypoints=websecure"
- "traefik.http.routers.gitea-registry.tls.certresolver=letsencrypt"
- "traefik.http.routers.gitea-registry.tls=true"
ports:
- "2221:22"
networks:
+4 -1
View File
@@ -12,19 +12,22 @@ services:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.services.glance.loadbalancer.server.port=8080"
# FIXME: traefik.services.glance.loadbalancer.server.port ??
# Prod Router
- "traefik.http.routers.glance.rule=Host(`glance.forust.xyz`)"
- "traefik.http.routers.glance.entrypoints=websecure"
- "traefik.http.routers.glance.middlewares=security-headers@file"
- "traefik.http.routers.glance.tls=true"
# Local Router
- "traefik.http.routers.glance-local.rule=Host(`glance.workstation.internal`)"
- "traefik.http.routers.glance-local.entrypoints=websecure"
- "traefik.http.routers.glance-local.middlewares=security-headers@file"
- "traefik.http.routers.glance-local.tls=true"
# 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-headers@file"
- "traefik.http.routers.glance-dev.tls=true"
networks:
- proxy
+7 -23
View File
@@ -9,10 +9,10 @@ services:
volumes:
- ./config/headscale.yaml:/etc/headscale/config.yaml
- data:/var/lib/headscale
- ./config/policy.json:/var/lib/headscale/policy.json
labels:
- "me.tale.headplane.target: headscale"
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.headscale.loadbalancer.server.port=8080"
- "traefik.http.services.headscale-metrics.loadbalancer.server.port=9090"
@@ -62,28 +62,12 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy
web:
image: goodieshq/headscale-admin:latest
restart: unless-stopped
labels:
- "traefik.enable=true"
- "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.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.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.tls=true"
networks:
- proxy
healthcheck:
test: [ "CMD", "/bin/hp_healthcheck" ]
interval: 30s
timeout: 5s
start_period: 5s
retries: 3
volumes:
data:
headplane-data:
-26
View File
@@ -1,26 +0,0 @@
{
"groups": {
"group:admin": [
"admin@"
],
"group:users": []
},
"tagOwners": {},
"hosts": {},
"acls": [
{
"#ha-meta": {
"name": "users",
"open": true
},
"action": "accept",
"src": [
"autogroup:member"
],
"dst": [
"autogroup:self:*"
]
}
],
"ssh": []
}
+8 -5
View File
@@ -3,8 +3,6 @@ services:
build:
context: .
dockerfile: Dockerfile.forust
image: gcr.forust.xyz/forust/forust-homepage:latest
pull_policy: build
# ports:
# - "8085:80"
restart: unless-stopped
@@ -14,26 +12,28 @@ services:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.forust-homepage.loadbalancer.server.port=80"
# Prod Router
- "traefik.http.routers.forust-homepage.rule=Host(`forust.xyz`)"
- "traefik.http.routers.forust-homepage.entrypoints=websecure"
- "traefik.http.routers.forust-homepage.middlewares=security-headers@file"
- "traefik.http.routers.forust-homepage.tls=true"
# Local Router
- "traefik.http.routers.forust-homepage-local.rule=Host(`landing.workstation.internal`)"
- "traefik.http.routers.forust-homepage-local.entrypoints=websecure"
- "traefik.http.routers.forust-homepage-local.middlewares=security-headers@file"
- "traefik.http.routers.forust-homepage-local.tls=true"
# Dev Router
- "traefik.http.routers.forust-homepage-dev.rule=Host(`landing.gigaforust.internal`)"
- "traefik.http.routers.forust-homepage-dev.entrypoints=websecure"
- "traefik.http.routers.forust-homepage-dev.middlewares=security-headers@file"
- "traefik.http.routers.forust-homepage-dev.tls=true"
xdfnx:
build:
context: .
dockerfile: Dockerfile.xdfnx
image: gcr.forust.xyz/forust/xdfnx-homepage:latest
pull_policy: build
restart: unless-stopped
# ports:
# - "8086:80"
@@ -41,20 +41,23 @@ services:
- ./xdfnx_files:/usr/share/nginx/html
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.xdfnx-homepage.loadbalancer.server.port=80"
# Prod Router
- "traefik.http.routers.xdfnx.rule=Host(`xdfnx.cfd`)"
- "traefik.http.routers.xdfnx.entrypoints=websecure"
- "traefik.http.routers.xdfnx.tls.certresolver=letsencrypt"
- "traefik.http.routers.xdfnx.middlewares=security-headers@file"
- "traefik.http.routers.xdfnx.tls=true"
# Local Router
- "traefik.http.routers.xdfnx-local.rule=Host(`xdfnx.workstation.internal`)"
- "traefik.http.routers.xdfnx-local.entrypoints=websecure"
- "traefik.http.routers.xdfnx-local.middlewares=security-headers@file"
- "traefik.http.routers.xdfnx-local.tls=true"
# Dev Router
- "traefik.http.routers.xdfnx-dev.rule=Host(`xdfnx.gigaforust.internal`)"
- "traefik.http.routers.xdfnx-dev.entrypoints=websecure"
- "traefik.http.routers.xdfnx-dev.middlewares=security-headers@file"
- "traefik.http.routers.xdfnx-dev.tls=true"
networks:
- proxy
Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

+2 -2
View File
@@ -41,7 +41,7 @@
</li>
<li>
<i class="fas fa-envelope"></i>
<a href="mailto:contact@forust.xyz">mail/contact@forust.xyz</a>
<a href="mailto:forust@forust.xyz">mail/contact@forust.xyz</a>
</li>
<li>
<i class="fa-solid fa-key"></i>
@@ -116,7 +116,7 @@
<div class="avatar"
style="background-image: url('assets/images/team/anna.jpg'); background-size: cover; background-position: center;">
</div>
<a href="./assets/images/love.png" target="_blank">Anna~</a>
<a href="" target="_blank">Anna~</a>
</div>
<div class="member">
-54
View File
@@ -1,54 +0,0 @@
# Resolved: Overlay FS failure (and so containers)
15-12-2025 03:02 AM EET: Degraded control panels' performances, following by full cascade docker failure
15-12-2025 04:36 AM EET: Identified: Services are terminated due to server software (Overlay FS) + hardware issues (HDD).
15-12-2025 08:45 PM EET: Restored NextCloud service with few tweaks to lower I/O
---
16-12-2025 08:34 PM EET: Ordered new HDD, ETA 22nd of December - 2nd of January
---
17-12-2025 02:47 AM EET: To avoid additional I/O into kuma's database, disabled uptime monitoring for non-critical services, such as:
- Game servers
- Gitea (no public projects being hosted yet)
- Landings
- Cloud services
- PenPot
- Auth provider
- Secondary management tools
- Chernuha's non-important infrastructure
These can be identified by seeing ">2m ago" under monitor's heartbeats.
---
09-01-2026 02:32 PM EET: NextCloud's frontend files are corrupted due to the unknown issue. All user data is integrity-verified. To prevent user data corruption, NextCloud service will be restored after new hardware will be available.
---
14-01-2026 08:42 PM EET: After planned updating and restarting server, critical firmware software were corrupted because of physical degradation of the disk. Server inaccessible in any way
---
15-01-2026 11:30 AM EET: A new NAS-Grade HDD (Seagate IronWolf Pro) was ordered. ETA 16-01-2026 EET Before 12:00 PM
---
16-01-2026 12:47 AM EET: New server system is installed, data backed up. Experiencing docker memory leak.
---
17-01-2026 01:27 PM EET: All services except NextCloud and Satisfactory server are online.
17-01-2026 03:48 PM EET: Nextcloud is online. Satisfactory will be provided on-demand. Monitoring status
---
##### Status: All services are online
**Solution: moving all infrastructure onto new NAS-Grade HDD with fresh OS install**
-3
View File
@@ -1,3 +0,0 @@
KENER_SECRET_KEY=your_secret_key_here
ORIGIN=http://localhost:3000
TZ=Etc/UTC
-59
View File
@@ -1,59 +0,0 @@
services:
kener:
image: rajnandan1/kener:4.0.23
container_name: kener
restart: unless-stopped
# ports:
# - 3000:3000/tcp
environment:
- KENER_SECRET_KEY=${KENER_SECRET_KEY?Kener requires a secret key}
- ORIGIN=${ORIGIN:-http://localhost:3000}
- REDIS_URL=redis://redis:6379
- TZ:${TZ:-Etc/UTC}
depends_on:
redis:
condition: service_healthy
volumes:
- db:/app/database
- uploads:/app/uploads
labels:
- "traefik.enable=true"
- "traefik.http.services.kener.loadbalancer.server.port=3000"
# Prod Router
- "traefik.http.routers.kener.rule=Host(`status.forust.xyz`)"
- "traefik.http.routers.kener.entrypoints=websecure"
- "traefik.http.routers.kener.tls=true"
# Local Router
- "traefik.http.routers.kener-local.rule=Host(`status.workstation.internal`)"
- "traefik.http.routers.kener-local.entrypoints=websecure"
- "traefik.http.routers.kener-local.tls=true"
# Dev Router
- "traefik.http.routers.kener-dev.rule=Host(`status.gigaforust.internal`)"
- "traefik.http.routers.kener-dev.entrypoints=websecure"
- "traefik.http.routers.kener-dev.tls=true"
networks:
- proxy
- kener
redis:
image: redis:7-alpine
container_name: kener-redis
restart: unless-stopped
volumes:
- redis:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 6s
timeout: 5s
retries: 5
networks:
- kener
volumes:
db:
uploads:
redis:
networks:
proxy:
external: true
kener:
external: false
+4 -1
View File
@@ -14,6 +14,7 @@ services:
- ./MeTube_downloads:/downloads
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.metube.loadbalancer.server.port=8081"
# Prod Router
@@ -22,12 +23,14 @@ services:
- "traefik.http.routers.metube.middlewares=security-chain@file"
- "traefik.http.routers.metube.tls=true"
# Local Router
- "traefik.http.routers.metube-local.rule=Host(`metube.workstation.internal`)"
- "traefik.http.routers.metube-local.rule=Host(`metube.workstation.internal`))"
- "traefik.http.routers.metube-local.entrypoints=websecure"
- "traefik.http.routers.metube-local.middlewares=security-headers@file"
- "traefik.http.routers.metube-local.tls=true"
# Dev Router
- "traefik.http.routers.metube-dev.rule=Host(`metube.gigaforust.internal`)"
- "traefik.http.routers.metube-dev.entrypoints=websecure"
- "traefik.http.routers.metube-dev.middlewares=security-chain@file"
- "traefik.http.routers.metube-dev.tls=true"
networks:
- proxy
+13 -8
View File
@@ -13,8 +13,8 @@ services:
- N8N_SECURE_COOKIE=false
- N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
volumes:
- node-data:/home/node/.n8n
- files:/files
- ./n8n-node-data:/home/node/.n8n
- ./n8n-files:/files
extra_hosts:
- "enterprise.n8n.io:104.26.13.187"
- "enterprise.n8n.io:104.26.12.187"
@@ -27,19 +27,28 @@ services:
- n8n
labels:
- "traefik.enable=true"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
- "traefik.docker.network=proxy"
# Prod Router
- "traefik.http.routers.n8n.rule=Host(`n8n.forust.xyz`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.middlewares=security-headers@file"
- "traefik.http.routers.n8n.service=n8n"
- "traefik.http.routers.n8n.tls=true"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
# Local Router
- "traefik.http.routers.n8n-local.rule=Host(`n8n.workstation.internal`)"
- "traefik.http.routers.n8n-local.rule=Host(`n8n.workstation.internal`) || Host(`n8n.internal`)"
- "traefik.http.routers.n8n-local.entrypoints=websecure"
- "traefik.http.routers.n8n-local.middlewares=security-headers@file"
- "traefik.http.routers.n8n-local.service=n8n"
- "traefik.http.routers.n8n-local.tls=true"
# Dev Router
- "traefik.http.routers.n8n-dev.rule=Host(`n8n.gigaforust.internal`)"
- "traefik.http.routers.n8n-dev.entrypoints=websecure"
- "traefik.http.routers.n8n-dev.middlewares=security-headers@file"
- "traefik.http.routers.n8n-dev.service=n8n"
- "traefik.http.routers.n8n-dev.tls=true"
- glance.name=n8n
@@ -51,7 +60,3 @@ networks:
external: false
proxy:
external: true
volumes:
node-data:
files:
-29
View File
@@ -1,29 +0,0 @@
## https://github.com/autobrr/netronome?tab=readme-ov-file#environment-variables
# Server settings
NETRONOME__HOST=0.0.0.0 # Listen address
NETRONOME__PORT=7575 # Web UI port
NETRONOME__BASE_URL=/netronome # Base URL for reverse proxy
# Database (SQLite by default)
NETRONOME__DB_TYPE=postgres # sqlite or postgres
NETRONOME__DB_PATH=netronome.db # SQLite database path
# PostgreSQL (when DB_TYPE=postgres)
NETRONOME__DB_TYPE=postgres
NETRONOME__DB_HOST=postgres
NETRONOME__DB_PORT=5432
NETRONOME__DB_USER=netronome
NETRONOME__DB_PASSWORD=netronome
NETRONOME__DB_NAME=netronome
NETRONOME__DB_SSLMODE=disable
# Authentication
NETRONOME__AUTH_WHITELIST=127.0.0.1/32,192.168.1.0/24 # IP whitelist (comma-separated)
NETRONOME__SESSION_SECRET= # Session secret (auto-generated if empty)
# OIDC (optional)
NETRONOME__OIDC_ISSUER=https://accounts.google.com
NETRONOME__OIDC_CLIENT_ID=your-client-id
NETRONOME__OIDC_CLIENT_SECRET=your-secret
NETRONOME__OIDC_REDIRECT_URL=https://example.com/api/auth/oidc/callback
-59
View File
@@ -1,59 +0,0 @@
services:
netronome:
image: ghcr.io/autobrr/netronome:latest
restart: unless-stopped
container_name: netronome
ports:
- "7575:7575"
cap_add:
- NET_RAW
env_file:
- .env
labels:
- "traefik.enable=true"
- "traefik.http.services.netronome.loadbalancer.server.port=7575"
# Prod Router
- "traefik.http.routers.netronome.rule=Host(`nm.forust.xyz`)"
- "traefik.http.routers.netronome.entrypoints=websecure"
- "traefik.http.routers.netronome.tls=true"
# Local Router
- "traefik.http.routers.netronome-local.rule=Host(`nm.workstation.internal`)"
- "traefik.http.routers.netronome-local.entrypoints=websecure"
- "traefik.http.routers.netronome-local.tls=true"
# Dev Router
- "traefik.http.routers.netronome-dev.rule=Host(`nm.gigaforust.internal`)"
- "traefik.http.routers.netronome-dev.entrypoints=websecure"
- "traefik.http.routers.netronome-dev.tls=true"
networks:
- proxy
- netronome
depends_on:
postgres:
condition: service_healthy
postgres:
container_name: netronome-postgres
image: postgres:17-alpine
environment:
- POSTGRES_USER=netronome
- POSTGRES_PASSWORD=netronome
- POSTGRES_DB=netronome
volumes:
- data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U netronome" ]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- netronome
volumes:
data:
networks:
proxy:
external: true
netronome:
external: false
+5 -3
View File
@@ -7,7 +7,6 @@ services:
volumes:
- nextcloud_aio_mastercontainer:/mnt/docker-aio-config # Do not change (backup)
- /var/run/docker.sock:/var/run/docker.sock:ro
- /dev/dri:/dev/dri
networks:
- nextcloud-aio
- proxy # Optional: Connects the mastercontainer to the proxy network in order to make the built-in reverse proxy detection work. See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
@@ -18,6 +17,7 @@ services:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
# AIO Services configuration
- "traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080"
- "traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https"
@@ -27,14 +27,17 @@ services:
# - "traefik.http.routers.nextcloud-aio.rule=Host(`naio.forust.xyz`)"
# - "traefik.http.routers.nextcloud-aio.entrypoints=websecure"
# - "traefik.http.routers.nextcloud-aio.middlewares=security-chain@file"
# - "traefik.http.routers.nextcloud-aio.service=nextcloud-aio"
# - "traefik.http.routers.nextcloud-aio.tls=true"
# Local Router
- "traefik.http.routers.nextcloud-aio-local.rule=Host(`naio.workstation.internal`)"
- "traefik.http.routers.nextcloud-aio-local.entrypoints=websecure"
- "traefik.http.routers.nextcloud-aio-local.middlewares=security-headers@file"
- "traefik.http.routers.nextcloud-aio-local.tls=true"
# Dev Router
- "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.middlewares=security-headers@file"
- "traefik.http.routers.nextcloud-aio-dev.tls=true"
# Glance Metadata
@@ -58,7 +61,7 @@ services:
NEXTCLOUD_STARTUP_APPS: deck twofactor_totp tasks calendar contacts notes # Allows to modify the Nextcloud apps that are installed on starting AIO the first time. See https://github.com/nextcloud/all-in-one#how-to-change-the-nextcloud-apps-that-are-installed-on-the-first-startup
NEXTCLOUD_ADDITIONAL_APKS: imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value. See https://github.com/nextcloud/all-in-one#how-to-add-os-packages-permanently-to-the-nextcloud-container
NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS: imagick # dditional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value. See https://github.com/nextcloud/all-in-one#how-to-add-php-extensions-permanently-to-the-nextcloud-container
# NEXTCLOUD_ENABLE_DRI_DEVICE: true # This allows to enable the /dev/dri device for containers that profit from it. ⚠️⚠️⚠️ Warning: this only works if the '/dev/dri' device is present on the host! If it should not exist on your host, don't set this to true as otherwise the Nextcloud container will fail to start! See https://github.com/nextcloud/all-in-one#how-to-enable-hardware-acceleration-for-nextcloud
NEXTCLOUD_ENABLE_DRI_DEVICE: true # This allows to enable the /dev/dri device for containers that profit from it. ⚠️⚠️⚠️ Warning: this only works if the '/dev/dri' device is present on the host! If it should not exist on your host, don't set this to true as otherwise the Nextcloud container will fail to start! See https://github.com/nextcloud/all-in-one#how-to-enable-hardware-acceleration-for-nextcloud
# NEXTCLOUD_KEEP_DISABLED_APPS: false # Setting this to true will keep Nextcloud apps that are disabled in the AIO interface and not uninstall them if they should be installed. See https://github.com/nextcloud/all-in-one#how-to-keep-disabled-apps
SKIP_DOMAIN_VALIDATION: true # This should only be set to true if things are correctly configured. See https://github.com/nextcloud/all-in-one?tab=readme-ov-file#how-to-skip-the-domain-validation
# TALK_PORT: 3478 # This a-llows to adjust the port that the talk container is using which is exposed on the host. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-talk-port
@@ -68,7 +71,6 @@ networks:
proxy:
external: true
nextcloud-aio:
name: nextcloud-aio
driver: bridge
external: false
volumes:
+4
View File
@@ -102,19 +102,23 @@ services:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.penpot.loadbalancer.server.port=8080"
# Prod Router
- "traefik.http.routers.penpot.rule=Host(`penpot.forust.xyz`)"
- "traefik.http.routers.penpot.entrypoints=websecure"
- "traefik.http.routers.penpot.middlewares=security-headers@file"
- "traefik.http.routers.penpot.tls=true"
# Local Router
- "traefik.http.routers.penpot-local.rule=Host(`penpot.workstation.internal`)"
- "traefik.http.routers.penpot-local.entrypoints=websecure"
- "traefik.http.routers.penpot-local.middlewares=security-headers@file"
- "traefik.http.routers.penpot-local.tls=true"
# Dev Router
- "traefik.http.routers.penpot-dev.rule=Host(`penpot.gigaforust.internal`)"
- "traefik.http.routers.penpot-dev.entrypoints=websecure"
- "traefik.http.routers.penpot-dev.middlewares=security-headers@file"
- "traefik.http.routers.penpot-dev.tls=true"
environment:
<<: [ *penpot-flags, *penpot-http-body-size ]
+9 -3
View File
@@ -1,29 +1,35 @@
services:
portainer:
image: portainer/portainer-ce:2.41.0
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- data:/data
ports:
- 9000:9000
- 9443:9443
# - 8000:8000 # Remove if you do not intend to use Edge Agents
labels:
- "traefik.enable=true"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
- "traefik.docker.network=proxy"
- "traefik.http.services.portainer.loadbalancer.server.port=9443"
- "traefik.http.services.portainer.loadbalancer.server.scheme=https"
- "traefik.http.services.portainer.loadbalancer.serverstransport=insecureTransport@file"
# Prod Router
- "traefik.http.routers.portainer.rule=Host(`portainer.forust.xyz`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.middlewares=security-headers@file"
- "traefik.http.routers.portainer.tls=true"
# Local Router
- "traefik.http.routers.portainer-local.rule=Host(`portainer.workstation.internal`)"
- "traefik.http.routers.portainer-local.entrypoints=websecure"
- "traefik.http.routers.portainer-local.middlewares=security-headers@file"
- "traefik.http.routers.portainer-local.tls=true"
# Dev Router
- "traefik.http.routers.portainer-dev.rule=Host(`portainer.gigaforust.internal`)"
- "traefik.http.routers.portainer-dev.entrypoints=websecure"
- "traefik.http.routers.portainer-dev.middlewares=security-headers@file"
- "traefik.http.routers.portainer-dev.tls=true"
# Glance Metadata
-15
View File
@@ -1,15 +0,0 @@
# Read the documentation before using the `docker-compose.yml` file:
# https://docs.searxng.org/admin/installation-docker.html
#
# Additional ENVs:
# https://docs.searxng.org/admin/settings/settings_general.html#settings-general
# https://docs.searxng.org/admin/settings/settings_server.html#settings-server
# Use a specific version tag. E.g. "latest" or "2026.3.25-541c6c3cb".
#SEARXNG_VERSION=latest
# Listen to a specific address.
#SEARXNG_HOST=[::]
# Listen to a specific port.
SEARXNG_PORT=8080
-45
View File
@@ -1,45 +0,0 @@
# Read the documentation before using the `docker-compose.yml` file:
# https://docs.searxng.org/admin/installation-docker.html
services:
core:
container_name: searxng-core
image: docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}
restart: unless-stopped
# ports:
# - ${SEARXNG_PORT:-8080}
env_file: .env
labels:
- "traefik.enable=true"
- "traefik.http.services.searxng.loadbalancer.server.port=8080"
# Prod Router
- "traefik.http.routers.searxng.rule=Host(`s.forust.xyz` || `searxng.forust.xyz`)"
- "traefik.http.routers.searxng.entrypoints=websecure"
- "traefik.http.routers.searxng.tls=true"
# Local Router
- "traefik.http.routers.searxng-local.rule=Host(`s.workstation.internal` || `searxng.workstation.internal`)"
- "traefik.http.routers.searxng-local.entrypoints=websecure"
- "traefik.http.routers.searxng-local.tls=true"
# Dev Router
- "traefik.http.routers.searxng-dev.rule=Host(`searxng.gigaforust.internal`)"
- "traefik.http.routers.searxng-dev.entrypoints=websecure"
- "traefik.http.routers.searxng-dev.tls=true"
networks:
- proxy
volumes:
- ./core-config/:/etc/searxng/:Z
- core-data:/var/cache/searxng/
valkey:
container_name: searxng-valkey
image: docker.io/valkey/valkey:9-alpine
command: valkey-server --save 30 1 --loglevel warning
restart: unless-stopped
volumes:
- valkey-data:/data/
volumes:
core-data:
valkey-data:
networks:
proxy:
external: true
+26
View File
@@ -0,0 +1,26 @@
# sudo mount /dev/sdc5 /mnt/mediaserver
# volumes:
# - /mnt/mediaserver:/<somepath>
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.lampa.loadbalancer.server.port=<container port>"
# Prod Router
- "traefik.http.routers.lampa.rule=Host(`media.forust.xyz`)"
- "traefik.http.routers.lampa.entrypoints=websecure"
- "traefik.http.routers.lampa.tls=true"
# Local Router
- "traefik.http.routers.lampa-local.rule=Host(`media.workstation.internal`)"
- "traefik.http.routers.lampa-local.entrypoints=websecure"
- "traefik.http.routers.lampa-local.tls=true"
# Dev Router
- "traefik.http.routers.lampa-dev.rule=Host(`media.gigaforust.internal`)"
- "traefik.http.routers.jellyfin-dev.entrypoints=websecure"
- "traefik.http.routers.jellyfin-dev.tls=true"
networks:
- proxy
networks:
proxy:
external: true
+4
View File
@@ -11,19 +11,23 @@ services:
PORT: "8080"
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.termix.loadbalancer.server.port=8080"
# Prod Router
- "traefik.http.routers.termix.rule=Host(`termix.forust.xyz`)"
- "traefik.http.routers.termix.entrypoints=websecure"
- "traefik.http.routers.termix.middlewares=security-headers@file"
- "traefik.http.routers.termix.tls=true"
# Local Router
- "traefik.http.routers.termix-local.rule=Host(`termix.workstation.internal`)"
- "traefik.http.routers.termix-local.entrypoints=websecure"
- "traefik.http.routers.termix-local.middlewares=security-headers@file"
- "traefik.http.routers.termix-local.tls=true"
# Dev Router
- "traefik.http.routers.termix-dev.rule=Host(`termix.gigaforust.internal`)"
- "traefik.http.routers.termix-dev.entrypoints=websecure"
- "traefik.http.routers.termix-dev.middlewares=security-headers@file"
- "traefik.http.routers.termix-dev.tls=true"
networks:
- proxy
+13 -11
View File
@@ -1,6 +1,6 @@
services:
traefik:
image: traefik:v3.7
image: traefik:latest
container_name: traefik
restart: unless-stopped
command:
@@ -17,11 +17,10 @@ services:
# EntryPoints
- "--entryPoints.web.address=:80"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.middlewares=error-pages@docker"
- "--entryPoints.websecure.http.tls=true"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https"
- "--entryPoints.ssh.address=:2221"
# Let's Encrypt
@@ -35,14 +34,15 @@ services:
# 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.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.filePath=/var/log/traefik/traefik.log"
- "--log.format=json"
- "--accesslog=true"
- "--accesslog.filepath=/var/log/traefik/access.log"
# # Logging
# - "--log.level=INFO"
# - "--log.filePath=/var/log/traefik/traefik.log"
# - "--accesslog=true"
# - "--accesslog.filepath=/var/log/traefik/access.log"
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
# Prod Router (Dash)
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.forust.xyz`)"
@@ -53,11 +53,13 @@ services:
# Local Router
- "traefik.http.routers.traefik-dashboard-local.rule=Host(`traefik.workstation.internal`)"
- "traefik.http.routers.traefik-dashboard-local.entrypoints=websecure"
- "traefik.http.routers.traefik-dashboard-local.middlewares=security-headers@file"
- "traefik.http.routers.traefik-dashboard-local.service=api@internal"
- "traefik.http.routers.traefik-dashboard-local.tls=true"
# Dev Router
- "traefik.http.routers.traefik-dashboard-dev.rule=Host(`traefik.gigaforust.internal`)"
- "traefik.http.routers.traefik-dashboard-dev.entrypoints=websecure"
- "traefik.http.routers.traefik-dashboard-dev.middlewares=security-chain@file"
- "traefik.http.routers.traefik-dashboard-dev.service=api@internal"
- "traefik.http.routers.traefik-dashboard-dev.tls=true"
@@ -78,4 +80,4 @@ services:
- proxy
networks:
proxy:
external: true
external: true
+1 -1
View File
@@ -1,4 +1,4 @@
http:
serversTransports:
insecureTransport:
insecureSkipVerify: true
insecureSkipVerify: true
-1
View File
@@ -35,7 +35,6 @@ http:
forwardAuth:
address: "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
maxResponseBodySize: 1048576
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
+2
View File
@@ -15,6 +15,7 @@ http:
rule: "Host(`nextcloud.workstation.internal`)"
entrypoints:
- websecure
- web
service: nextcloud
middlewares:
- nextcloud-chain
@@ -25,6 +26,7 @@ http:
rule: "Host(`nextcloud.gigaforust.internal`)"
entrypoints:
- websecure
- web
service: nextcloud
middlewares:
- nextcloud-chain
+10 -8
View File
@@ -2,14 +2,16 @@
tls:
certificates:
# Cloudflare Origin CA *.forust.xyz
# - certFile: /certs/cloudflare.pem
# keyFile: /certs/cloudflare.key
# stores:
# default:
# defaultCertificate:
# # Fallback local certificate
# certFile: /certs/local.pem
# keyFile: /certs/local-key.pem
- certFile: /certs/cloudflare.pem
keyFile: /certs/cloudflare.key
- certFile: /certs/xdfnx.pem
keyFile: /certs/xdfnx.key
stores:
default:
defaultCertificate:
# Fallback local certificate
certFile: /certs/gigaforust.internal+1.pem
keyFile: /certs/gigaforust.internal+1-key.pem
options:
default:
+1
View File
@@ -9,6 +9,7 @@ services:
# - "3001:3001"
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.uptime-kuma.loadbalancer.server.port=3001"
# Prod Router
-11
View File
@@ -1,12 +1 @@
.unused
Downloads
.venv
volumes
.env
.env.*
my_account.session
.gitignore
README.md
.git
uv.lock
.deepsource.toml
+8 -16
View File
@@ -1,18 +1,10 @@
FROM python:3.11-slim
FROM python:3.11
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
git wget ffmpeg mediainfo && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt /app/
RUN python -m pip install --no-cache-dir --upgrade pip && \
python -m pip install --no-cache-dir -r /app/requirements.txt
COPY . /app
ENV PYTHONUNBUFFERED=1
CMD ["python", "-u", "main.py"]
RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo\
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN python -m venv --copies /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "main.py"]
+5 -8
View File
@@ -3,8 +3,6 @@ services:
build:
context: .
dockerfile: Dockerfile
image: gcr.forust.xyz/forust/userbot:latest
pull_policy: build
restart: unless-stopped
env_file:
- .env
@@ -12,16 +10,15 @@ services:
volumes:
- ./volumes/data_forust:/app/data
- ./Downloads:/app/downloads
- ./volumes/logs_forust:/app/logs
- ./volumes/logs_forust-:/app/logs
dns:
- 8.8.8.8
- 1.1.1.1
anna:
image: gcr.forust.xyz/forust/userbot:latest
pull_policy: build
depends_on:
- forust
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
env_file:
- .env
@@ -32,4 +29,4 @@ services:
- ./volumes/logs_anna:/app/logs
dns:
- 8.8.8.8
- 1.1.1.1
- 1.1.1.1
+5 -13
View File
@@ -90,19 +90,11 @@ def load_missing_modules():
os.makedirs(custom_modules_path, exist_ok=True)
try:
resp = requests.get(
"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt",
timeout=10,
)
if not resp.ok:
logging.error(
"Failed to fetch custom modules list: HTTP %s",
resp.status_code,
)
return
f = resp.text
except Exception as e:
logging.error("Failed to fetch custom modules list: %s", e)
f = requests.get(
"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt"
).text
except Exception:
logging.error("Failed to fetch custom modules list")
return
modules_dict = {
line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines()
+1 -1
View File
@@ -3,7 +3,7 @@ name = "userbot"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
requires-python = ">=3.13"
dependencies = [
"aiofiles>=25.1.0",
"aiohttp>=3.13.2",
-2
View File
@@ -18,5 +18,3 @@ aiohttp
aiofiles
pySmartDL
qrcode
bottle
dulwich
+1 -1
View File
@@ -1,8 +1,8 @@
import os
import environs
env = environs.Env()
try:
env = environs.Env()
env.read_env("./.env")
except FileNotFoundError:
print("No .env file found, using os.environ.")
+12 -12
View File
@@ -22,7 +22,6 @@ import re
import shlex
import subprocess
import sys
import tempfile
import time
import traceback
from PIL import Image
@@ -86,17 +85,13 @@ async def edit_or_send_as_file(
return
if len(tex) > 1024:
await message.edit("<code>OutPut is Too Large, Sending As File!</code>")
with tempfile.NamedTemporaryFile(
"w", delete=False, suffix=".txt", prefix=f"{file_name}_"
) as fn:
file_names = f"{file_name}.txt"
with open(file_names, "w") as fn:
fn.write(tex)
temp_path = fn.name
try:
await client.send_document(message.chat.id, temp_path, caption=caption)
await message.delete()
finally:
if os.path.exists(temp_path):
os.remove(temp_path)
await client.send_document(message.chat.id, file_names, caption=caption)
await message.delete()
if os.path.exists(file_names):
os.remove(file_names)
return
return await message.edit(tex)
@@ -254,7 +249,12 @@ def is_admin(func):
chat_member = await client.get_chat_member(
message.chat.id, message.from_user.id
)
if chat_member.status in ("administrator", "creator"):
chat_admins = []
async for member in client.get_chat_members(
message.chat.id, filter=ChatMembersFilter.ADMINISTRATORS
):
chat_admins.append(member)
if chat_member in chat_admins:
return await func(client, message)
await message.edit("You need to be an admin to perform this action.")
except UserNotParticipant: