Compare commits

...

69 Commits

Author SHA1 Message Date
forust a68c01a68f chore: add gitea container registry compose support for localy builded apps
Deploy to Server / deploy (push) Has been cancelled
2026-06-07 14:37:57 +02:00
forust bdcdb1cb3d feat: router for gitea container registry (unproxied) 2026-06-07 14:37:53 +02:00
forust fe2b80b51f fix: errorpages intercepted gitea container registry endpoint
gitea registry introduced
2026-06-07 13:39:54 +02:00
forust b8d5bc747d hack: commented out local certs until i figure out how to route certs for local routers 2026-06-07 13:38:14 +02:00
forust 6f77b7d845 switch to embedded dockmon login page
Deploy to Server / deploy (push) Has been cancelled
2026-05-27 20:11:48 +02:00
forust ea286e117d feat: add diary (/diary) command with calendar parsing via Playwright
Deploy to Server / deploy (push) Has been cancelled
- Parse school diary calendar HTML table for daily/weekly/monthly views
- Cache diary data with 5-minute TTL
- Inline keyboard for today/tomorrow/week/month selection
- Ukrainian month/weekday names and formatting
2026-05-27 19:39:45 +02:00
forust 6a050669e8 chore: ignore all generated searxng core-config files 2026-05-27 19:39:41 +02:00
forust b9c11b8eab Merge branch 'main' into optimize/userbot
Deploy to Server / deploy (push) Has been cancelled
2026-05-25 01:48:31 +02:00
forust 6dac841b2c updated traefik to v3.7,
Deploy to Server / deploy (push) Has been cancelled
resolved maxResponseBodySize not set
2026-05-25 01:47:59 +02:00
forust 526a2b617a refactor: update .dockerignore
pull_policy never to use local images
2026-05-25 01:43:46 +02:00
forust 1e08391e0e refactor: improved error handling, timeouts and use temp files 2026-05-21 22:14:29 +02:00
forust 0a31601b77 refactor: imporved layer caching for dockerfile
using existing built image for account 2
2026-05-21 22:12:33 +02:00
forust 25eb89c902 feat: add searxng service (metasearch engine) 2026-05-21 21:34:40 +02:00
forust d988b0f7db refactor: change Cloudlfare DDNS config to env-based
Deploy to Server / deploy (push) Failing after 14m7s
2026-05-08 17:38:41 +02:00
forust e873f37159 refactor: nextcloud now requires mounting /dev/dri inside the container.
Deploy to Server / deploy (push) Failing after 0s
Changed network name for nextcloud
2026-05-08 16:39:09 +02:00
forust 8362f45bdc upd: updated image tags for services:
- gitea 1.25.1 --> 1.26
- portainer-ce latest --> 2.41.0
- traefik latest --> 3.6.15
2026-05-08 16:37:26 +02:00
forust fd5ea6cadd upd: kener v4.0.23
Deploy to Server / deploy (push) Failing after 0s
2026-04-10 02:29:02 +02:00
forust aa3598ee3d Merge pull request 'upd: updated kener to v4' (#14) from upd/kener-v4 into main
Deploy to Server / deploy (push) Failing after 1s
Reviewed-on: #14
2026-02-27 13:06:36 +01:00
forust c0205fa49b upd: updated kener to v4 2026-02-27 13:05:56 +01:00
forust a22707770f feat: update certificates logic:
Deploy to Server / deploy (push) Failing after 1s
- Switched xdfnx's homepage certificates to traefik-managed mode
- Renamed local fallback certs
2026-02-25 21:12:44 +01:00
forust 646f988a86 chore: Pinned stable kener version
Deploy to Server / deploy (push) Failing after 5s
2026-02-24 12:09:06 +01:00
forust 414d17d839 deleted test gitea workflow
Deploy to Server / deploy (push) Successful in 1s
2026-02-22 03:53:51 +01:00
forust 812e4eb87a ci: add copy of the gitea workflow for the github
Deploy to Server / deploy (push) Successful in 1s
2026-02-22 03:37:45 +01:00
forust 70b3b203fb fix: update email address
Deploy to Server / deploy (push) Has been cancelled
2026-02-22 00:28:19 +01:00
forust d857f3838d revert testing message
Deploy to Server / deploy (push) Successful in 1s
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-02-20 20:41:01 +01:00
forust f8620349a9 fix deploy pipeline to prevent logspam
Deploy to Server / deploy (push) Successful in 16s
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-02-20 20:38:39 +01:00
forust b1acff5c85 ci: add run-name to deployment workflow
Deploy to Server / deploy (push) Successful in 19s
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-02-20 20:25:17 +01:00
forust beb6dca6a2 CI test change 2026-02-20 20:23:11 +01:00
forust aed6ff31f7 fix: add .runner (act runner settings) to .gitignore 2026-02-20 20:22:10 +01:00
forust 73684af21b ci: deploy workflow with local act_runner
Deploy to Server / deploy (push) Successful in 1s
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-02-20 20:09:26 +01:00
forust cd5c90adcc Merge branch 'main' into ci/gitea-actions 2026-02-20 20:08:48 +01:00
forust 578a1ca544 revert: naio needs insecure transport
2593b54402 chore: add backquotes for downtify traefik labels removed insecureTransport
2026-02-20 01:25:23 +01:00
forust 12ed20a306 fix: support for external dynamic traefik configs 2026-02-20 01:16:54 +01:00
forust 400e7b6595 Starting learning cicd from scratch
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Demo gitea action
2026-02-19 00:36:02 +01:00
forust f58e96a25d add support for external dynamic traefik configs 2026-02-07 01:56:10 +01:00
forust a3e5f5a78b fix: let traefik handle https to portainer 2026-02-05 02:42:39 +01:00
forust 1a09a62a4c chore: deploy and lint workflows 2026-02-05 02:17:47 +01:00
forust 2593b54402 chore: add backquotes for downtify traefik labels
removed insecureTransport
2026-02-05 01:33:04 +01:00
forust 42826a037c refactor: store n8n data in named volumes 2026-02-04 22:26:43 +01:00
forust d7a68237e5 chore: remove redundant or unnececary traefik labels
- traefik.docker.network= (defined by traefik cli)
- traefik.http.routers.<routername>.middlewares=security-headers@file" (applied globally by traefik cli)
2026-02-04 22:26:27 +01:00
forust bbb8bdf0a7 feat: traefik routers for netronome, exported netronome env to .env 2026-02-04 11:00:44 +01:00
forust f4d3bd9c6d feat: basic netronome service 2026-02-03 14:25:53 +01:00
forust 6b919df7c6 chore: restart downtify service unless stopped 2026-02-01 17:24:50 +01:00
forust 3ee0b96ed5 feat: add acl policies to the headscale 2026-02-01 01:14:58 +01:00
forust d25d213d9b feat: add headscale admin web admin (along with headplane accessible via port) 2026-01-31 13:38:33 +01:00
forust a3b7c4c889 commit team avatars (and love) 2026-01-29 21:19:34 +01:00
forust 3dbb50c924 json logging 2026-01-29 17:30:09 +01:00
forust 54da82432b fix: remove headplane healtcheck (always false negative) 2026-01-29 17:22:47 +01:00
forust e5eb234c41 chore: gitignore for adguard 2026-01-29 17:22:40 +01:00
forust 721348e67f refactor: switch adguard data to named volume 2026-01-29 17:22:33 +01:00
forust d58ae2a5e7 chore: new statuspage for errorpages 2026-01-25 23:41:42 +01:00
forust aa516c3445 Merge pull request 'fix: errorpage styles fixes #11' (#12) from fix/errorpage-styles into main
Reviewed-on: #12
2026-01-25 23:39:10 +01:00
forust b5cc7d9a0d fix: errorpage styles fixes #11 2026-01-25 23:38:34 +01:00
forust 5dfa9c879b chore: comment-out errorpage ports 2026-01-24 01:24:18 +01:00
forust 3c5702a0fb Merge pull request 'feat/kener' (#9) from feat/kener into main
Reviewed-on: #9
2026-01-24 01:19:52 +01:00
forust dd0ca27f4f fix: add TZ env for checkmk 2026-01-24 01:16:24 +01:00
forust 7f7d0de090 fix: comment out kener ports 2026-01-24 00:58:09 +01:00
forust bee3f16cb2 fix: set restart policy of kener to Unelss stopped 2026-01-24 00:55:43 +01:00
forust 303d23968d fix: set restart policy of errorpages to Unelss stopped 2026-01-24 00:53:41 +01:00
forust b7ecf88052 fix: correct metube local router rule and fix TLS configuration comments 2026-01-23 17:54:00 +01:00
forust 5068591b8b fix: xdfnx's certificate 2026-01-23 17:48:00 +01:00
forust 8e57f21e44 feat: add traefik routers for kener (status subdomain) 2026-01-23 11:26:55 +01:00
forust 073d9ff569 feat: add initial kener instance (uptime monitoring) 2026-01-23 11:12:36 +01:00
forust c6d00e525b fix: comment-out CF Origin CA. (google trust service) 2026-01-22 00:51:15 +01:00
forust 539994a145 Merge branch 'feat/error-page' 2026-01-21 20:14:26 +01:00
forust 95f0afbbee feat: add traefik integration for error-handler
TODO: fix css
2026-01-21 20:14:02 +01:00
forust 9f86bd1142 feat: add errorpage-handler service (403,404, 500, 502-504) 2026-01-21 17:39:14 +01:00
forust af9668e8e6 md: archive overlayfs incident 2026-01-21 17:33:18 +01:00
forust 53b71a33ad fix: correct adguard traefik devrule 2026-01-21 11:41:37 +01:00
61 changed files with 2125 additions and 174 deletions
+39
View File
@@ -0,0 +1,39 @@
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
@@ -0,0 +1,41 @@
## 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
+10 -4
View File
@@ -5,12 +5,12 @@ sync.ffs_lock
# Copyparty # Copyparty
*.hist/ *.hist/
# Volumes and data directories # Volumes, configs and data directories
gitea/gitea-db/ gitea/gitea-db/
gitea/gitea-data/* gitea/gitea-data/*
n8n/n8n-data/* n8n/n8n-data/*
n8n/n8n-node-data/* n8n/n8n-node-data/*
adguardhome/data/* adguardhome/conf/*
dockmon/data/* dockmon/data/*
portainer/portainer_data/* portainer/portainer_data/*
metube/MeTube_downloads metube/MeTube_downloads
@@ -21,6 +21,7 @@ checkmk/checkmk/*
downtify/Downtify_downloads downtify/Downtify_downloads
headscale/config/* headscale/config/*
headscale/data/* headscale/data/*
searxng/core-config/*
# Steaming services files # Steaming services files
streaming/jellyfin/* streaming/jellyfin/*
@@ -32,12 +33,15 @@ streaming/qbittorrent/*
streaming/prowlarr/* streaming/prowlarr/*
# Homepage # Homepage
homepages/forust_files/assets/images/team/*
homepages/forust_files/.well-known/* homepages/forust_files/.well-known/*
# Traefik files # Traefik files
traefik/letsencrypt/acme.json traefik/letsencrypt/acme.json
traefik/dynamic/fileservers.yml traefik/dynamic/fileservers.yml
traefik/dynamic/*.local.y*ml.*
traefik/dynamic/*.external.y*ml
traefik/logs/* traefik/logs/*
# SSL Certificates # SSL Certificates
@@ -77,6 +81,8 @@ replacements.txt
# Git # Git
.gitattributes .gitattributes
# Gitea/github Runners
.runner
# Misc # Misc
.DS_Store .DS_Store
@@ -91,4 +97,4 @@ temp/*
.env.anna .env.anna
.env.forust .env.forust
.env.* .env.*
!*example !*example
+5 -7
View File
@@ -11,28 +11,24 @@ services:
# - "68:68/tcp" # DHCP # - "68:68/tcp" # DHCP
# - "3000:3000/tcp" # - "3000:3000/tcp"
volumes: volumes:
- ./data/work:/opt/adguardhome/work - data:/opt/adguardhome/work
- ./data/conf:/opt/adguardhome/conf - ./conf:/opt/adguardhome/conf
- ./certs:/certs:ro - ./certs:/certs:ro
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.adguard.loadbalancer.server.port=3000" - "traefik.http.services.adguard.loadbalancer.server.port=3000"
# Prod Router # Prod Router
- "traefik.http.routers.adguard.rule=Host(`dns.forust.xyz`) || Host(`adguard.forust.xyz`)" - "traefik.http.routers.adguard.rule=Host(`dns.forust.xyz`) || Host(`adguard.forust.xyz`)"
- "traefik.http.routers.adguard.entrypoints=websecure" - "traefik.http.routers.adguard.entrypoints=websecure"
- "traefik.http.routers.adguard.middlewares=security-headers@file"
- "traefik.http.routers.adguard.tls=true" - "traefik.http.routers.adguard.tls=true"
# Local Router # Local Router
- "traefik.http.routers.adguard-local.rule=Host(`adguard.workstation.internal`) || Host(`dns.workstation.internal`)" - "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.entrypoints=websecure"
- "traefik.http.routers.adguard-local.middlewares=security-headers@file"
- "traefik.http.routers.adguard-local.tls=true" - "traefik.http.routers.adguard-local.tls=true"
# Dev Router # 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.entrypoints=websecure"
- "traefik.http.routers.adguard-dev.middlewares=security-headers@file"
- "traefik.http.routers.adguard-dev.tls=true" - "traefik.http.routers.adguard-dev.tls=true"
# DoH Router # DoH Router
- "traefik.http.routers.dns.rule=(Host(`dns.forust.xyz`) && PathPrefix(`/dns-query`))" - "traefik.http.routers.dns.rule=(Host(`dns.forust.xyz`) && PathPrefix(`/dns-query`))"
@@ -45,6 +41,8 @@ services:
- glance.description=AdGuard Home is a network-wide software for blocking ads. - glance.description=AdGuard Home is a network-wide software for blocking ads.
networks: networks:
- proxy - proxy
volumes:
data:
networks: networks:
proxy: proxy:
external: true external: true
+1 -7
View File
@@ -42,26 +42,20 @@ services:
- ./custom-templates:/templates - ./custom-templates:/templates
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.authentik-server.loadbalancer.server.port=9000" - "traefik.http.services.authentik-server.loadbalancer.server.port=9000"
# Prod Router # Prod Router
- "traefik.http.routers.authentik-server.rule=Host(`auth.forust.xyz`)" - "traefik.http.routers.authentik-server.rule=Host(`auth.forust.xyz`)"
- "traefik.http.routers.authentik-server.entrypoints=websecure" - "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" - "traefik.http.routers.authentik-server.tls=true"
# Local Router # Local Router
- "traefik.http.routers.authentik-server-local.rule=Host(`auth.workstation.internal`) || Host(`auth-dashboard.internal`)" - "traefik.http.routers.authentik-server-local.rule=Host(`auth.workstation.internal`)"
- "traefik.http.routers.authentik-server-local.entrypoints=websecure" - "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" - "traefik.http.routers.authentik-server-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.authentik-server-dev.rule=Host(`auth.gigaforust.internal`)" - "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.entrypoints=websecure"
- "traefik.http.routers.authentik-server-dev.middlewares=security-headers@file" - "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" - "traefik.http.routers.authentik-server-dev.tls=true"
networks: networks:
- proxy - proxy
+15
View File
@@ -0,0 +1,15 @@
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
+21 -4
View File
@@ -6,8 +6,25 @@ services:
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
network_mode: 'host' network_mode: 'host'
# https://github.com/timothymiller/cloudflare-ddns#-quick-start
environment: environment:
- PUID=1000 - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?Cloudflare API token is required}
- PGID=1000 - DOMAINS=${DOMAINS:-}
volumes: - IP4_DOMAINS=${IP4_DOMAINS:-}
- ./config.json:/config.json - 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
+1 -4
View File
@@ -13,24 +13,21 @@ services:
environment: environment:
- CMK_PASSWORD=${CMK_PASSWORD:-password} - CMK_PASSWORD=${CMK_PASSWORD:-password}
- CMK_SITE_ID=cmk - CMK_SITE_ID=cmk
- TZ=${TZ:-Etc/UTC}
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.checkmk.loadbalancer.server.port=5000" - "traefik.http.services.checkmk.loadbalancer.server.port=5000"
# Prod Router # Prod Router
- "traefik.http.routers.checkmk.rule=Host(`cmk.forust.xyz`)" - "traefik.http.routers.checkmk.rule=Host(`cmk.forust.xyz`)"
- "traefik.http.routers.checkmk.entrypoints=websecure" - "traefik.http.routers.checkmk.entrypoints=websecure"
- "traefik.http.routers.checkmk.middlewares=security-headers@file"
- "traefik.http.routers.checkmk.tls=true" - "traefik.http.routers.checkmk.tls=true"
# Local Router # Local Router
- "traefik.http.routers.checkmk-local.rule=Host(`cmk.workstation.internal`)" - "traefik.http.routers.checkmk-local.rule=Host(`cmk.workstation.internal`)"
- "traefik.http.routers.checkmk-local.entrypoints=websecure" - "traefik.http.routers.checkmk-local.entrypoints=websecure"
- "traefik.http.routers.checkmk-local.middlewares=security-headers@file"
- "traefik.http.routers.checkmk-local.tls=true" - "traefik.http.routers.checkmk-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.checkmk-dev.rule=Host(`cmk.gigaforust.internal`)" - "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.entrypoints=websecure"
- "traefik.http.routers.checkmk-dev.tls=true" - "traefik.http.routers.checkmk-dev.tls=true"
networks: networks:
+1 -5
View File
@@ -15,7 +15,6 @@ services:
retries: 3 retries: 3
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.dockmon.loadbalancer.server.port=443" - "traefik.http.services.dockmon.loadbalancer.server.port=443"
- "traefik.http.services.dockmon.loadbalancer.server.scheme=https" - "traefik.http.services.dockmon.loadbalancer.server.scheme=https"
- "traefik.http.services.dockmon.loadbalancer.serverstransport=insecureTransport@file" - "traefik.http.services.dockmon.loadbalancer.serverstransport=insecureTransport@file"
@@ -23,18 +22,15 @@ services:
# Prod Router # Prod Router
- "traefik.http.routers.dockmon.rule=Host(`dockmon.forust.xyz`)" - "traefik.http.routers.dockmon.rule=Host(`dockmon.forust.xyz`)"
- "traefik.http.routers.dockmon.entrypoints=websecure" - "traefik.http.routers.dockmon.entrypoints=websecure"
- "traefik.http.routers.dockmon.middlewares=security-chain@file" - "traefik.http.routers.dockmon.middlewares=security-headers@file"
- "traefik.http.routers.dockmon.service=dockmon"
- "traefik.http.routers.dockmon.tls=true" - "traefik.http.routers.dockmon.tls=true"
# Local Router # Local Router
- "traefik.http.routers.dockmon-local.rule=Host(`dockmon.workstation.internal`)" - "traefik.http.routers.dockmon-local.rule=Host(`dockmon.workstation.internal`)"
- "traefik.http.routers.dockmon-local.entrypoints=websecure" - "traefik.http.routers.dockmon-local.entrypoints=websecure"
- "traefik.http.routers.dockmon-local.middlewares=security-headers@file"
- "traefik.http.routers.dockmon-local.tls=true" - "traefik.http.routers.dockmon-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.dockmon-dev.rule=Host(`dockmon.gigaforust.internal`)" - "traefik.http.routers.dockmon-dev.rule=Host(`dockmon.gigaforust.internal`)"
- "traefik.http.routers.dockmon-dev.entrypoints=websecure" - "traefik.http.routers.dockmon-dev.entrypoints=websecure"
- "traefik.http.routers.dockmon-dev.middlewares=security-chain@file"
- "traefik.http.routers.dockmon-dev.tls=true" - "traefik.http.routers.dockmon-dev.tls=true"
# Glance Metadata # Glance Metadata
+13 -15
View File
@@ -2,30 +2,28 @@ services:
downtify: downtify:
container_name: downtify container_name: downtify
image: ghcr.io/henriquesebastiao/downtify:latest image: ghcr.io/henriquesebastiao/downtify:latest
restart: unless-stopped
# ports: # ports:
# - '7077:8000' # - '7077:8000'
volumes: volumes:
- ./Downtify_downloads:/downloads - ./Downtify_downloads:/downloads
labels: labels:
- traefik.enable=true - "traefik.enable=true"
- traefik.docker.network=proxy - "traefik.http.services.downtify.loadbalancer.server.port=8000"
- traefik.http.services.downtify.loadbalancer.server.port=8000
# Prod Router # Prod Router
- traefik.http.routers.downtify.rule=Host(`downtify.forust.xyz`) - "traefik.http.routers.downtify.rule=Host(`downtify.forust.xyz`)"
- traefik.http.routers.downtify.entrypoints=websecure - "traefik.http.routers.downtify.entrypoints=websecure"
- traefik.http.routers.downtify.middlewares=security-chain@file - "traefik.http.routers.downtify.middlewares=security-chain@file"
- traefik.http.routers.downtify.tls=true - "traefik.http.routers.downtify.tls=true"
# Local Router # Local Router
- traefik.http.routers.downtify-local.rule=Host(`downtify.workstation.internal`) || Host(`downtify.internal`) - "traefik.http.routers.downtify-local.rule=Host(`downtify.workstation.internal`)"
- traefik.http.routers.downtify-local.entrypoints=websecure - "traefik.http.routers.downtify-local.entrypoints=websecure"
- traefik.http.routers.downtify-local.middlewares=security-headers@file - "traefik.http.routers.downtify-local.tls=true"
- traefik.http.routers.downtify-local.tls=true
# Dev Router # Dev Router
- traefik.http.routers.downtify-dev.rule=Host(`downtify.gigaforust.internal`) - "traefik.http.routers.downtify-dev.rule=Host(`downtify.gigaforust.internal`)"
- traefik.http.routers.downtify-dev.entrypoints=websecure - "traefik.http.routers.downtify-dev.entrypoints=websecure"
- traefik.http.routers.downtify-dev.middlewares=security-chain@file - "traefik.http.routers.downtify-dev.tls=true"
- traefik.http.routers.downtify-dev.tls=true
networks: networks:
- proxy - proxy
networks: networks:
+3 -2
View File
@@ -3,12 +3,13 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
image: gcr.forust.xyz/forust/dtek-notif:latest
pull_policy: build
restart: unless-stopped restart: unless-stopped
environment: environment:
- TZ=Europe/Kyiv - TZ=Europe/Kyiv
dns: dns:
- 1.1.1.1 - 1.1.1.1
- 8.8.8.8 - 8.8.8.8
networks: networks:
- default - default
+4
View File
@@ -17,6 +17,8 @@ services:
session-keeper: session-keeper:
build: ./phpsessid-bot build: ./phpsessid-bot
image: gcr.forust.xyz/forust/session-keeper:latest
pull_policy: build
env_file: .env env_file: .env
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@@ -31,6 +33,8 @@ services:
webinar-checker: webinar-checker:
build: ./webinar-checker build: ./webinar-checker
image: gcr.forust.xyz/forust/webinar-checker:latest
pull_policy: build
env_file: .env env_file: .env
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
+274 -1
View File
@@ -1,7 +1,10 @@
import os import os
import re
import logging import logging
import redis import redis
import json import json
import time
from datetime import datetime, timedelta
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ChatMember from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ChatMember
from telegram.constants import ChatType from telegram.constants import ChatType
@@ -30,6 +33,7 @@ def _env(key, default=None):
EDU_BASE = _env('EDU_URL_BASE', 'https://edu.edu.vn.ua') EDU_BASE = _env('EDU_URL_BASE', 'https://edu.edu.vn.ua')
EDU_WEBINAR_PATH = _env('EDU_URL_WEBINAR', '/webinar/useractive') EDU_WEBINAR_PATH = _env('EDU_URL_WEBINAR', '/webinar/useractive')
WEBINAR_URL = f"{EDU_BASE.rstrip('/')}/{EDU_WEBINAR_PATH.lstrip('/')}" 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)) WEBINAR_CHECK_INTERVAL = int(_env('WEBINAR_CHECK_INTERVAL', 60))
REDIS_HOST = _env('REDIS_HOST', 'redis') REDIS_HOST = _env('REDIS_HOST', 'redis')
@@ -252,6 +256,205 @@ def get_admin_keyboard(user_id: int):
] ]
return InlineKeyboardMarkup(keyboard) 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 --- # --- Command Handlers ---
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -376,6 +579,74 @@ async def clear_history(update: Update, context: ContextTypes.DEFAULT_TYPE):
logger.error(f"Failed to clear history: {e}") logger.error(f"Failed to clear history: {e}")
await update.message.reply_text(t(admin_id, 'history_clear_failed')) 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 --- # --- Admin Callbacks ---
async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -649,8 +920,10 @@ def main():
app.add_handler(CommandHandler("adduser", add_user)) app.add_handler(CommandHandler("adduser", add_user))
app.add_handler(CommandHandler("removeuser", remove_user)) app.add_handler(CommandHandler("removeuser", remove_user))
app.add_handler(CommandHandler("clearhistory", clear_history)) app.add_handler(CommandHandler("clearhistory", clear_history))
app.add_handler(CommandHandler("diary", diary_command))
# Callback handlers - language selection first, then admin panel # Callback handlers - diary first, then language selection, then admin panel
app.add_handler(CallbackQueryHandler(diary_callback, pattern="^diary_"))
app.add_handler(CallbackQueryHandler(language_callback, pattern="^lang_")) app.add_handler(CallbackQueryHandler(language_callback, pattern="^lang_"))
app.add_handler(CallbackQueryHandler(admin_callback)) app.add_handler(CallbackQueryHandler(admin_callback))
+5
View File
@@ -0,0 +1,5 @@
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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,208 @@
<!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
@@ -0,0 +1,207 @@
<!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
@@ -0,0 +1,207 @@
<!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
@@ -0,0 +1,207 @@
<!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
@@ -0,0 +1,207 @@
<!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
@@ -0,0 +1,207 @@
<!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>
+6 -5
View File
@@ -1,6 +1,6 @@
services: services:
server: server:
image: docker.gitea.com/gitea:1.25.1 image: docker.gitea.com/gitea:1.26
container_name: gitea container_name: gitea
restart: always restart: always
environment: environment:
@@ -31,28 +31,29 @@ services:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.gitea.loadbalancer.server.port=3000" - "traefik.http.services.gitea.loadbalancer.server.port=3000"
# Prod Router # Prod Router
- "traefik.http.routers.gitea.rule=Host(`gitea.forust.xyz`)" - "traefik.http.routers.gitea.rule=Host(`gitea.forust.xyz`)"
- "traefik.http.routers.gitea.entrypoints=websecure" - "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.middlewares=security-headers@file"
- "traefik.http.routers.gitea.tls=true" - "traefik.http.routers.gitea.tls=true"
# Local Router # Local Router
- "traefik.http.routers.gitea-local.rule=Host(`gitea.workstation.internal`)" - "traefik.http.routers.gitea-local.rule=Host(`gitea.workstation.internal`)"
- "traefik.http.routers.gitea-local.entrypoints=websecure" - "traefik.http.routers.gitea-local.entrypoints=websecure"
- "traefik.http.routers.gitea-local.middlewares=security-headers@file"
- "traefik.http.routers.gitea-local.tls=true" - "traefik.http.routers.gitea-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.gitea-dev.rule=Host(`gitea.gigaforust.internal`)" - "traefik.http.routers.gitea-dev.rule=Host(`gitea.gigaforust.internal`)"
- "traefik.http.routers.gitea-dev.entrypoints=websecure" - "traefik.http.routers.gitea-dev.entrypoints=websecure"
- "traefik.http.routers.gitea-dev.middlewares=security-headers@file"
- "traefik.http.routers.gitea-dev.tls=true" - "traefik.http.routers.gitea-dev.tls=true"
# SSH Router # SSH Router
- "traefik.tcp.services.gitea.loadbalancer.server.port=22" - "traefik.tcp.services.gitea.loadbalancer.server.port=22"
- "traefik.tcp.routers.gitea.entrypoints=ssh" - "traefik.tcp.routers.gitea.entrypoints=ssh"
- "traefik.tcp.routers.gitea.rule=HostSNI(`*`)" - "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: ports:
- "2221:22" - "2221:22"
networks: networks:
+1 -4
View File
@@ -12,22 +12,19 @@ services:
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy" - "traefik.docker.network=proxy"
# FIXME: traefik.services.glance.loadbalancer.server.port ?? - "traefik.services.glance.loadbalancer.server.port=8080"
# Prod Router # Prod Router
- "traefik.http.routers.glance.rule=Host(`glance.forust.xyz`)" - "traefik.http.routers.glance.rule=Host(`glance.forust.xyz`)"
- "traefik.http.routers.glance.entrypoints=websecure" - "traefik.http.routers.glance.entrypoints=websecure"
- "traefik.http.routers.glance.middlewares=security-headers@file"
- "traefik.http.routers.glance.tls=true" - "traefik.http.routers.glance.tls=true"
# Local Router # Local Router
- "traefik.http.routers.glance-local.rule=Host(`glance.workstation.internal`)" - "traefik.http.routers.glance-local.rule=Host(`glance.workstation.internal`)"
- "traefik.http.routers.glance-local.entrypoints=websecure" - "traefik.http.routers.glance-local.entrypoints=websecure"
- "traefik.http.routers.glance-local.middlewares=security-headers@file"
- "traefik.http.routers.glance-local.tls=true" - "traefik.http.routers.glance-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.glance-dev.rule=Host(`glance.gigaforust.internal`)" - "traefik.http.routers.glance-dev.rule=Host(`glance.gigaforust.internal`)"
- "traefik.http.routers.glance-dev.entrypoints=websecure" - "traefik.http.routers.glance-dev.entrypoints=websecure"
- "traefik.http.routers.glance-dev.middlewares=security-headers@file"
- "traefik.http.routers.glance-dev.tls=true" - "traefik.http.routers.glance-dev.tls=true"
networks: networks:
- proxy - proxy
+23 -7
View File
@@ -9,10 +9,10 @@ services:
volumes: volumes:
- ./config/headscale.yaml:/etc/headscale/config.yaml - ./config/headscale.yaml:/etc/headscale/config.yaml
- data:/var/lib/headscale - data:/var/lib/headscale
- ./config/policy.json:/var/lib/headscale/policy.json
labels: labels:
- "me.tale.headplane.target: headscale" - "me.tale.headplane.target: headscale"
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.headscale.loadbalancer.server.port=8080" - "traefik.http.services.headscale.loadbalancer.server.port=8080"
- "traefik.http.services.headscale-metrics.loadbalancer.server.port=9090" - "traefik.http.services.headscale-metrics.loadbalancer.server.port=9090"
@@ -62,12 +62,28 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
networks: networks:
- proxy - proxy
healthcheck: web:
test: [ "CMD", "/bin/hp_healthcheck" ] image: goodieshq/headscale-admin:latest
interval: 30s restart: unless-stopped
timeout: 5s labels:
start_period: 5s - "traefik.enable=true"
retries: 3 - "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
volumes: volumes:
data: data:
headplane-data: headplane-data:
+26
View File
@@ -0,0 +1,26 @@
{
"groups": {
"group:admin": [
"admin@"
],
"group:users": []
},
"tagOwners": {},
"hosts": {},
"acls": [
{
"#ha-meta": {
"name": "users",
"open": true
},
"action": "accept",
"src": [
"autogroup:member"
],
"dst": [
"autogroup:self:*"
]
}
],
"ssh": []
}
+5 -8
View File
@@ -3,6 +3,8 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile.forust dockerfile: Dockerfile.forust
image: gcr.forust.xyz/forust/forust-homepage:latest
pull_policy: build
# ports: # ports:
# - "8085:80" # - "8085:80"
restart: unless-stopped restart: unless-stopped
@@ -12,28 +14,26 @@ services:
- proxy - proxy
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.forust-homepage.loadbalancer.server.port=80" - "traefik.http.services.forust-homepage.loadbalancer.server.port=80"
# Prod Router # Prod Router
- "traefik.http.routers.forust-homepage.rule=Host(`forust.xyz`)" - "traefik.http.routers.forust-homepage.rule=Host(`forust.xyz`)"
- "traefik.http.routers.forust-homepage.entrypoints=websecure" - "traefik.http.routers.forust-homepage.entrypoints=websecure"
- "traefik.http.routers.forust-homepage.middlewares=security-headers@file"
- "traefik.http.routers.forust-homepage.tls=true" - "traefik.http.routers.forust-homepage.tls=true"
# Local Router # Local Router
- "traefik.http.routers.forust-homepage-local.rule=Host(`landing.workstation.internal`)" - "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.entrypoints=websecure"
- "traefik.http.routers.forust-homepage-local.middlewares=security-headers@file"
- "traefik.http.routers.forust-homepage-local.tls=true" - "traefik.http.routers.forust-homepage-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.forust-homepage-dev.rule=Host(`landing.gigaforust.internal`)" - "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.entrypoints=websecure"
- "traefik.http.routers.forust-homepage-dev.middlewares=security-headers@file"
- "traefik.http.routers.forust-homepage-dev.tls=true" - "traefik.http.routers.forust-homepage-dev.tls=true"
xdfnx: xdfnx:
build: build:
context: . context: .
dockerfile: Dockerfile.xdfnx dockerfile: Dockerfile.xdfnx
image: gcr.forust.xyz/forust/xdfnx-homepage:latest
pull_policy: build
restart: unless-stopped restart: unless-stopped
# ports: # ports:
# - "8086:80" # - "8086:80"
@@ -41,23 +41,20 @@ services:
- ./xdfnx_files:/usr/share/nginx/html - ./xdfnx_files:/usr/share/nginx/html
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.xdfnx-homepage.loadbalancer.server.port=80" - "traefik.http.services.xdfnx-homepage.loadbalancer.server.port=80"
# Prod Router # Prod Router
- "traefik.http.routers.xdfnx.rule=Host(`xdfnx.cfd`)" - "traefik.http.routers.xdfnx.rule=Host(`xdfnx.cfd`)"
- "traefik.http.routers.xdfnx.entrypoints=websecure" - "traefik.http.routers.xdfnx.entrypoints=websecure"
- "traefik.http.routers.xdfnx.middlewares=security-headers@file" - "traefik.http.routers.xdfnx.tls.certresolver=letsencrypt"
- "traefik.http.routers.xdfnx.tls=true" - "traefik.http.routers.xdfnx.tls=true"
# Local Router # Local Router
- "traefik.http.routers.xdfnx-local.rule=Host(`xdfnx.workstation.internal`)" - "traefik.http.routers.xdfnx-local.rule=Host(`xdfnx.workstation.internal`)"
- "traefik.http.routers.xdfnx-local.entrypoints=websecure" - "traefik.http.routers.xdfnx-local.entrypoints=websecure"
- "traefik.http.routers.xdfnx-local.middlewares=security-headers@file"
- "traefik.http.routers.xdfnx-local.tls=true" - "traefik.http.routers.xdfnx-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.xdfnx-dev.rule=Host(`xdfnx.gigaforust.internal`)" - "traefik.http.routers.xdfnx-dev.rule=Host(`xdfnx.gigaforust.internal`)"
- "traefik.http.routers.xdfnx-dev.entrypoints=websecure" - "traefik.http.routers.xdfnx-dev.entrypoints=websecure"
- "traefik.http.routers.xdfnx-dev.middlewares=security-headers@file"
- "traefik.http.routers.xdfnx-dev.tls=true" - "traefik.http.routers.xdfnx-dev.tls=true"
networks: networks:
- proxy - proxy
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+2 -2
View File
@@ -41,7 +41,7 @@
</li> </li>
<li> <li>
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
<a href="mailto:forust@forust.xyz">mail/contact@forust.xyz</a> <a href="mailto:contact@forust.xyz">mail/contact@forust.xyz</a>
</li> </li>
<li> <li>
<i class="fa-solid fa-key"></i> <i class="fa-solid fa-key"></i>
@@ -116,7 +116,7 @@
<div class="avatar" <div class="avatar"
style="background-image: url('assets/images/team/anna.jpg'); background-size: cover; background-position: center;"> style="background-image: url('assets/images/team/anna.jpg'); background-size: cover; background-position: center;">
</div> </div>
<a href="" target="_blank">Anna~</a> <a href="./assets/images/love.png" target="_blank">Anna~</a>
</div> </div>
<div class="member"> <div class="member">
+54
View File
@@ -0,0 +1,54 @@
# 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
@@ -0,0 +1,3 @@
KENER_SECRET_KEY=your_secret_key_here
ORIGIN=http://localhost:3000
TZ=Etc/UTC
+59
View File
@@ -0,0 +1,59 @@
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
+1 -4
View File
@@ -14,7 +14,6 @@ services:
- ./MeTube_downloads:/downloads - ./MeTube_downloads:/downloads
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.metube.loadbalancer.server.port=8081" - "traefik.http.services.metube.loadbalancer.server.port=8081"
# Prod Router # Prod Router
@@ -23,14 +22,12 @@ services:
- "traefik.http.routers.metube.middlewares=security-chain@file" - "traefik.http.routers.metube.middlewares=security-chain@file"
- "traefik.http.routers.metube.tls=true" - "traefik.http.routers.metube.tls=true"
# Local Router # 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.entrypoints=websecure"
- "traefik.http.routers.metube-local.middlewares=security-headers@file"
- "traefik.http.routers.metube-local.tls=true" - "traefik.http.routers.metube-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.metube-dev.rule=Host(`metube.gigaforust.internal`)" - "traefik.http.routers.metube-dev.rule=Host(`metube.gigaforust.internal`)"
- "traefik.http.routers.metube-dev.entrypoints=websecure" - "traefik.http.routers.metube-dev.entrypoints=websecure"
- "traefik.http.routers.metube-dev.middlewares=security-chain@file"
- "traefik.http.routers.metube-dev.tls=true" - "traefik.http.routers.metube-dev.tls=true"
networks: networks:
- proxy - proxy
+8 -13
View File
@@ -13,8 +13,8 @@ services:
- N8N_SECURE_COOKIE=false - N8N_SECURE_COOKIE=false
- N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
volumes: volumes:
- ./n8n-node-data:/home/node/.n8n - node-data:/home/node/.n8n
- ./n8n-files:/files - files:/files
extra_hosts: extra_hosts:
- "enterprise.n8n.io:104.26.13.187" - "enterprise.n8n.io:104.26.13.187"
- "enterprise.n8n.io:104.26.12.187" - "enterprise.n8n.io:104.26.12.187"
@@ -27,28 +27,19 @@ services:
- n8n - n8n
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy" - "traefik.http.services.n8n.loadbalancer.server.port=5678"
# Prod Router # Prod Router
- "traefik.http.routers.n8n.rule=Host(`n8n.forust.xyz`)" - "traefik.http.routers.n8n.rule=Host(`n8n.forust.xyz`)"
- "traefik.http.routers.n8n.entrypoints=websecure" - "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.routers.n8n.tls=true"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
# Local Router # Local Router
- "traefik.http.routers.n8n-local.rule=Host(`n8n.workstation.internal`) || Host(`n8n.internal`)" - "traefik.http.routers.n8n-local.rule=Host(`n8n.workstation.internal`)"
- "traefik.http.routers.n8n-local.entrypoints=websecure" - "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" - "traefik.http.routers.n8n-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.n8n-dev.rule=Host(`n8n.gigaforust.internal`)" - "traefik.http.routers.n8n-dev.rule=Host(`n8n.gigaforust.internal`)"
- "traefik.http.routers.n8n-dev.entrypoints=websecure" - "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" - "traefik.http.routers.n8n-dev.tls=true"
- glance.name=n8n - glance.name=n8n
@@ -60,3 +51,7 @@ networks:
external: false external: false
proxy: proxy:
external: true external: true
volumes:
node-data:
files:
+29
View File
@@ -0,0 +1,29 @@
## 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
@@ -0,0 +1,59 @@
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
+3 -5
View File
@@ -7,6 +7,7 @@ services:
volumes: volumes:
- nextcloud_aio_mastercontainer:/mnt/docker-aio-config # Do not change (backup) - nextcloud_aio_mastercontainer:/mnt/docker-aio-config # Do not change (backup)
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
- /dev/dri:/dev/dri
networks: networks:
- nextcloud-aio - 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 - 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
@@ -17,7 +18,6 @@ services:
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
# AIO Services configuration # AIO Services configuration
- "traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080" - "traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080"
- "traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https" - "traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https"
@@ -27,17 +27,14 @@ services:
# - "traefik.http.routers.nextcloud-aio.rule=Host(`naio.forust.xyz`)" # - "traefik.http.routers.nextcloud-aio.rule=Host(`naio.forust.xyz`)"
# - "traefik.http.routers.nextcloud-aio.entrypoints=websecure" # - "traefik.http.routers.nextcloud-aio.entrypoints=websecure"
# - "traefik.http.routers.nextcloud-aio.middlewares=security-chain@file" # - "traefik.http.routers.nextcloud-aio.middlewares=security-chain@file"
# - "traefik.http.routers.nextcloud-aio.service=nextcloud-aio"
# - "traefik.http.routers.nextcloud-aio.tls=true" # - "traefik.http.routers.nextcloud-aio.tls=true"
# Local Router # Local Router
- "traefik.http.routers.nextcloud-aio-local.rule=Host(`naio.workstation.internal`)" - "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.entrypoints=websecure"
- "traefik.http.routers.nextcloud-aio-local.middlewares=security-headers@file"
- "traefik.http.routers.nextcloud-aio-local.tls=true" - "traefik.http.routers.nextcloud-aio-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.nextcloud-aio-dev.rule=Host(`naio.gigaforust.internal`)" - "traefik.http.routers.nextcloud-aio-dev.rule=Host(`naio.gigaforust.internal`)"
- "traefik.http.routers.nextcloud-aio-dev.entrypoints=websecure" - "traefik.http.routers.nextcloud-aio-dev.entrypoints=websecure"
- "traefik.http.routers.nextcloud-aio-dev.middlewares=security-headers@file"
- "traefik.http.routers.nextcloud-aio-dev.tls=true" - "traefik.http.routers.nextcloud-aio-dev.tls=true"
# Glance Metadata # Glance Metadata
@@ -61,7 +58,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_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_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_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 # 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 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 # 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
@@ -71,6 +68,7 @@ networks:
proxy: proxy:
external: true external: true
nextcloud-aio: nextcloud-aio:
name: nextcloud-aio
driver: bridge driver: bridge
external: false external: false
volumes: volumes:
-4
View File
@@ -102,23 +102,19 @@ services:
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.penpot.loadbalancer.server.port=8080" - "traefik.http.services.penpot.loadbalancer.server.port=8080"
# Prod Router # Prod Router
- "traefik.http.routers.penpot.rule=Host(`penpot.forust.xyz`)" - "traefik.http.routers.penpot.rule=Host(`penpot.forust.xyz`)"
- "traefik.http.routers.penpot.entrypoints=websecure" - "traefik.http.routers.penpot.entrypoints=websecure"
- "traefik.http.routers.penpot.middlewares=security-headers@file"
- "traefik.http.routers.penpot.tls=true" - "traefik.http.routers.penpot.tls=true"
# Local Router # Local Router
- "traefik.http.routers.penpot-local.rule=Host(`penpot.workstation.internal`)" - "traefik.http.routers.penpot-local.rule=Host(`penpot.workstation.internal`)"
- "traefik.http.routers.penpot-local.entrypoints=websecure" - "traefik.http.routers.penpot-local.entrypoints=websecure"
- "traefik.http.routers.penpot-local.middlewares=security-headers@file"
- "traefik.http.routers.penpot-local.tls=true" - "traefik.http.routers.penpot-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.penpot-dev.rule=Host(`penpot.gigaforust.internal`)" - "traefik.http.routers.penpot-dev.rule=Host(`penpot.gigaforust.internal`)"
- "traefik.http.routers.penpot-dev.entrypoints=websecure" - "traefik.http.routers.penpot-dev.entrypoints=websecure"
- "traefik.http.routers.penpot-dev.middlewares=security-headers@file"
- "traefik.http.routers.penpot-dev.tls=true" - "traefik.http.routers.penpot-dev.tls=true"
environment: environment:
<<: [ *penpot-flags, *penpot-http-body-size ] <<: [ *penpot-flags, *penpot-http-body-size ]
+3 -9
View File
@@ -1,35 +1,29 @@
services: services:
portainer: portainer:
image: portainer/portainer-ce:latest image: portainer/portainer-ce:2.41.0
container_name: portainer container_name: portainer
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- data:/data - data:/data
ports: ports:
- 9443:9443 - 9000:9000
# - 8000:8000 # Remove if you do not intend to use Edge Agents # - 8000:8000 # Remove if you do not intend to use Edge Agents
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy" - "traefik.http.services.portainer.loadbalancer.server.port=9000"
- "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 # Prod Router
- "traefik.http.routers.portainer.rule=Host(`portainer.forust.xyz`)" - "traefik.http.routers.portainer.rule=Host(`portainer.forust.xyz`)"
- "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.middlewares=security-headers@file"
- "traefik.http.routers.portainer.tls=true" - "traefik.http.routers.portainer.tls=true"
# Local Router # Local Router
- "traefik.http.routers.portainer-local.rule=Host(`portainer.workstation.internal`)" - "traefik.http.routers.portainer-local.rule=Host(`portainer.workstation.internal`)"
- "traefik.http.routers.portainer-local.entrypoints=websecure" - "traefik.http.routers.portainer-local.entrypoints=websecure"
- "traefik.http.routers.portainer-local.middlewares=security-headers@file"
- "traefik.http.routers.portainer-local.tls=true" - "traefik.http.routers.portainer-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.portainer-dev.rule=Host(`portainer.gigaforust.internal`)" - "traefik.http.routers.portainer-dev.rule=Host(`portainer.gigaforust.internal`)"
- "traefik.http.routers.portainer-dev.entrypoints=websecure" - "traefik.http.routers.portainer-dev.entrypoints=websecure"
- "traefik.http.routers.portainer-dev.middlewares=security-headers@file"
- "traefik.http.routers.portainer-dev.tls=true" - "traefik.http.routers.portainer-dev.tls=true"
# Glance Metadata # Glance Metadata
+15
View File
@@ -0,0 +1,15 @@
# 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
@@ -0,0 +1,45 @@
# 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
-4
View File
@@ -11,23 +11,19 @@ services:
PORT: "8080" PORT: "8080"
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.termix.loadbalancer.server.port=8080" - "traefik.http.services.termix.loadbalancer.server.port=8080"
# Prod Router # Prod Router
- "traefik.http.routers.termix.rule=Host(`termix.forust.xyz`)" - "traefik.http.routers.termix.rule=Host(`termix.forust.xyz`)"
- "traefik.http.routers.termix.entrypoints=websecure" - "traefik.http.routers.termix.entrypoints=websecure"
- "traefik.http.routers.termix.middlewares=security-headers@file"
- "traefik.http.routers.termix.tls=true" - "traefik.http.routers.termix.tls=true"
# Local Router # Local Router
- "traefik.http.routers.termix-local.rule=Host(`termix.workstation.internal`)" - "traefik.http.routers.termix-local.rule=Host(`termix.workstation.internal`)"
- "traefik.http.routers.termix-local.entrypoints=websecure" - "traefik.http.routers.termix-local.entrypoints=websecure"
- "traefik.http.routers.termix-local.middlewares=security-headers@file"
- "traefik.http.routers.termix-local.tls=true" - "traefik.http.routers.termix-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.termix-dev.rule=Host(`termix.gigaforust.internal`)" - "traefik.http.routers.termix-dev.rule=Host(`termix.gigaforust.internal`)"
- "traefik.http.routers.termix-dev.entrypoints=websecure" - "traefik.http.routers.termix-dev.entrypoints=websecure"
- "traefik.http.routers.termix-dev.middlewares=security-headers@file"
- "traefik.http.routers.termix-dev.tls=true" - "traefik.http.routers.termix-dev.tls=true"
networks: networks:
- proxy - proxy
+12 -14
View File
@@ -1,6 +1,6 @@
services: services:
traefik: traefik:
image: traefik:latest image: traefik:v3.7
container_name: traefik container_name: traefik
restart: unless-stopped restart: unless-stopped
command: command:
@@ -17,10 +17,11 @@ services:
# EntryPoints # EntryPoints
- "--entryPoints.web.address=:80" - "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.tls=true"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https" - "--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.ssh.address=:2221" - "--entryPoints.ssh.address=:2221"
# Let's Encrypt # Let's Encrypt
@@ -34,15 +35,14 @@ services:
# Cloudflare # Cloudflare
- "--entryPoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22" - "--entryPoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22"
- "--entryPoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22" - "--entryPoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22"
# Logging
# # Logging - "--log.level=INFO"
# - "--log.level=INFO" - "--log.filePath=/var/log/traefik/traefik.log"
# - "--log.filePath=/var/log/traefik/traefik.log" - "--log.format=json"
# - "--accesslog=true" - "--accesslog=true"
# - "--accesslog.filepath=/var/log/traefik/access.log" - "--accesslog.filepath=/var/log/traefik/access.log"
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
# Prod Router (Dash) # Prod Router (Dash)
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.forust.xyz`)" - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.forust.xyz`)"
@@ -53,13 +53,11 @@ services:
# Local Router # Local Router
- "traefik.http.routers.traefik-dashboard-local.rule=Host(`traefik.workstation.internal`)" - "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.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.service=api@internal"
- "traefik.http.routers.traefik-dashboard-local.tls=true" - "traefik.http.routers.traefik-dashboard-local.tls=true"
# Dev Router # Dev Router
- "traefik.http.routers.traefik-dashboard-dev.rule=Host(`traefik.gigaforust.internal`)" - "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.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.service=api@internal"
- "traefik.http.routers.traefik-dashboard-dev.tls=true" - "traefik.http.routers.traefik-dashboard-dev.tls=true"
@@ -80,4 +78,4 @@ services:
- proxy - proxy
networks: networks:
proxy: proxy:
external: true external: true
+1 -1
View File
@@ -1,4 +1,4 @@
http: http:
serversTransports: serversTransports:
insecureTransport: insecureTransport:
insecureSkipVerify: true insecureSkipVerify: true
+1
View File
@@ -35,6 +35,7 @@ http:
forwardAuth: forwardAuth:
address: "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik" address: "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true trustForwardHeader: true
maxResponseBodySize: 1048576
authResponseHeaders: authResponseHeaders:
- X-authentik-username - X-authentik-username
- X-authentik-groups - X-authentik-groups
-2
View File
@@ -15,7 +15,6 @@ http:
rule: "Host(`nextcloud.workstation.internal`)" rule: "Host(`nextcloud.workstation.internal`)"
entrypoints: entrypoints:
- websecure - websecure
- web
service: nextcloud service: nextcloud
middlewares: middlewares:
- nextcloud-chain - nextcloud-chain
@@ -26,7 +25,6 @@ http:
rule: "Host(`nextcloud.gigaforust.internal`)" rule: "Host(`nextcloud.gigaforust.internal`)"
entrypoints: entrypoints:
- websecure - websecure
- web
service: nextcloud service: nextcloud
middlewares: middlewares:
- nextcloud-chain - nextcloud-chain
+8 -10
View File
@@ -2,16 +2,14 @@
tls: tls:
certificates: certificates:
# Cloudflare Origin CA *.forust.xyz # Cloudflare Origin CA *.forust.xyz
- certFile: /certs/cloudflare.pem # - certFile: /certs/cloudflare.pem
keyFile: /certs/cloudflare.key # keyFile: /certs/cloudflare.key
- certFile: /certs/xdfnx.pem # stores:
keyFile: /certs/xdfnx.key # default:
stores: # defaultCertificate:
default: # # Fallback local certificate
defaultCertificate: # certFile: /certs/local.pem
# Fallback local certificate # keyFile: /certs/local-key.pem
certFile: /certs/gigaforust.internal+1.pem
keyFile: /certs/gigaforust.internal+1-key.pem
options: options:
default: default:
-1
View File
@@ -9,7 +9,6 @@ services:
# - "3001:3001" # - "3001:3001"
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.services.uptime-kuma.loadbalancer.server.port=3001" - "traefik.http.services.uptime-kuma.loadbalancer.server.port=3001"
# Prod Router # Prod Router
+11
View File
@@ -1 +1,12 @@
.unused .unused
Downloads
.venv
volumes
.env
.env.*
my_account.session
.gitignore
README.md
.git
uv.lock
.deepsource.toml
+16 -8
View File
@@ -1,10 +1,18 @@
FROM python:3.11 FROM python:3.11-slim
WORKDIR /app 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 COPY . /app
RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo\
&& apt-get clean \ ENV PYTHONUNBUFFERED=1
&& rm -rf /var/lib/apt/lists/*
RUN python -m venv --copies /opt/venv CMD ["python", "-u", "main.py"]
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "main.py"]
+8 -5
View File
@@ -3,6 +3,8 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
image: gcr.forust.xyz/forust/userbot:latest
pull_policy: build
restart: unless-stopped restart: unless-stopped
env_file: env_file:
- .env - .env
@@ -10,15 +12,16 @@ services:
volumes: volumes:
- ./volumes/data_forust:/app/data - ./volumes/data_forust:/app/data
- ./Downloads:/app/downloads - ./Downloads:/app/downloads
- ./volumes/logs_forust-:/app/logs - ./volumes/logs_forust:/app/logs
dns: dns:
- 8.8.8.8 - 8.8.8.8
- 1.1.1.1 - 1.1.1.1
anna: anna:
build: image: gcr.forust.xyz/forust/userbot:latest
context: . pull_policy: build
dockerfile: Dockerfile depends_on:
- forust
restart: unless-stopped restart: unless-stopped
env_file: env_file:
- .env - .env
@@ -29,4 +32,4 @@ services:
- ./volumes/logs_anna:/app/logs - ./volumes/logs_anna:/app/logs
dns: dns:
- 8.8.8.8 - 8.8.8.8
- 1.1.1.1 - 1.1.1.1
+13 -5
View File
@@ -90,11 +90,19 @@ def load_missing_modules():
os.makedirs(custom_modules_path, exist_ok=True) os.makedirs(custom_modules_path, exist_ok=True)
try: try:
f = requests.get( resp = requests.get(
"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt" "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt",
).text timeout=10,
except Exception: )
logging.error("Failed to fetch custom modules list") 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)
return return
modules_dict = { modules_dict = {
line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines() 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" version = "0.1.0"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.11"
dependencies = [ dependencies = [
"aiofiles>=25.1.0", "aiofiles>=25.1.0",
"aiohttp>=3.13.2", "aiohttp>=3.13.2",
+2
View File
@@ -18,3 +18,5 @@ aiohttp
aiofiles aiofiles
pySmartDL pySmartDL
qrcode qrcode
bottle
dulwich
+1 -1
View File
@@ -1,8 +1,8 @@
import os import os
import environs import environs
env = environs.Env()
try: try:
env = environs.Env()
env.read_env("./.env") env.read_env("./.env")
except FileNotFoundError: except FileNotFoundError:
print("No .env file found, using os.environ.") print("No .env file found, using os.environ.")
+12 -12
View File
@@ -22,6 +22,7 @@ import re
import shlex import shlex
import subprocess import subprocess
import sys import sys
import tempfile
import time import time
import traceback import traceback
from PIL import Image from PIL import Image
@@ -85,13 +86,17 @@ async def edit_or_send_as_file(
return return
if len(tex) > 1024: if len(tex) > 1024:
await message.edit("<code>OutPut is Too Large, Sending As File!</code>") await message.edit("<code>OutPut is Too Large, Sending As File!</code>")
file_names = f"{file_name}.txt" with tempfile.NamedTemporaryFile(
with open(file_names, "w") as fn: "w", delete=False, suffix=".txt", prefix=f"{file_name}_"
) as fn:
fn.write(tex) fn.write(tex)
await client.send_document(message.chat.id, file_names, caption=caption) temp_path = fn.name
await message.delete() try:
if os.path.exists(file_names): await client.send_document(message.chat.id, temp_path, caption=caption)
os.remove(file_names) await message.delete()
finally:
if os.path.exists(temp_path):
os.remove(temp_path)
return return
return await message.edit(tex) return await message.edit(tex)
@@ -249,12 +254,7 @@ def is_admin(func):
chat_member = await client.get_chat_member( chat_member = await client.get_chat_member(
message.chat.id, message.from_user.id message.chat.id, message.from_user.id
) )
chat_admins = [] if chat_member.status in ("administrator", "creator"):
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) return await func(client, message)
await message.edit("You need to be an admin to perform this action.") await message.edit("You need to be an admin to perform this action.")
except UserNotParticipant: except UserNotParticipant: