From a91ff2856e4980d790a2c9926758bf35cafcc2ac Mon Sep 17 00:00:00 2001 From: "Wesley R. Elsberry" Date: Wed, 19 Nov 2025 13:53:03 -0500 Subject: [PATCH] Initial files commit --- .gitignore | 231 ++---------------------- core-proxy/authelia/configuration.yml | 53 ++++++ core-proxy/authelia/users_database.yml | 11 ++ core-proxy/docker-compose.yml | 58 ++++++ core-proxy/traefik/dynamic/authelia.yml | 27 +++ core-proxy/traefik/traefik.yml | 7 + firewall/nftables.conf.example | 37 ++++ setup.sh | 7 + sites/forgejo/docker-compose.yml | 89 +++++++++ sites/static-site/docker-compose.yml | 31 ++++ sites/wordpress-site/docker-compose.yml | 51 ++++++ 11 files changed, 387 insertions(+), 215 deletions(-) create mode 100644 core-proxy/authelia/configuration.yml create mode 100644 core-proxy/authelia/users_database.yml create mode 100644 core-proxy/docker-compose.yml create mode 100644 core-proxy/traefik/dynamic/authelia.yml create mode 100644 core-proxy/traefik/traefik.yml create mode 100644 firewall/nftables.conf.example create mode 100644 setup.sh create mode 100644 sites/forgejo/docker-compose.yml create mode 100644 sites/static-site/docker-compose.yml create mode 100644 sites/wordpress-site/docker-compose.yml diff --git a/.gitignore b/.gitignore index ab9d941..5709157 100644 --- a/.gitignore +++ b/.gitignore @@ -1,218 +1,19 @@ -# ---> Python -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class +# Traefik ACME cert storage +core-proxy/traefik/acme.json -# C extensions -*.so +# Authelia runtime DB +core-proxy/authelia/db.sqlite3 +core-proxy/authelia/notification.log -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# ---> Emacs -# -*- mode: gitignore; -*- -*~ -\#*\# -/.emacs.desktop -/.emacs.desktop.lock -*.elc -auto-save-list -tramp -.\#* - -# Org-mode -.org-id-locations -*_archive - -# flymake-mode -*_flymake.* - -# eshell files -/eshell/history -/eshell/lastdir - -# elpa packages -/elpa/ - -# reftex files -*.rel - -# AUCTeX auto folder -/auto/ - -# cask packages -.cask/ -dist/ - -# Flycheck -flycheck_*.el - -# server auth directory -/server/ - -# projectiles files -.projectile - -# directory configuration -.dir-locals.el - -# network security -/network-security.data - - -# ---> GNOMEShellExtension -# Ignored files for GNOME extension git repository - -*.zip +# Site data +sites/**/db/ +sites/**/wp/ +sites/**/data/ +sites/**/runner/ +sites/**/nextcloud/ +sites/**/redis/ +# Editor junk +*.swp +*.swo +.DS_Store diff --git a/core-proxy/authelia/configuration.yml b/core-proxy/authelia/configuration.yml new file mode 100644 index 0000000..4c6d684 --- /dev/null +++ b/core-proxy/authelia/configuration.yml @@ -0,0 +1,53 @@ +host: 0.0.0.0 +port: 9091 + +log: + level: info + +jwt: + secret: "CHANGE_ME_TO_A_LONG_RANDOM_STRING" + +default_redirection_url: "https://auth.example.com" + +totp: + issuer: "example.com" + +authentication_backend: + file: + path: /config/users_database.yml + password: + algorithm: argon2id + iterations: 3 + key_length: 32 + salt_length: 16 + parallelism: 2 + memory: 64 + +access_control: + default_policy: deny + + # Any request that reaches Authelia via forward-auth + # requires at least one_factor authentication. + rules: + - domain_regex: ".*" + policy: one_factor + +session: + name: authelia_session + secret: "CHANGE_ME_SESSION_SECRET" + same_site: lax + expiration: 3600 + inactivity: 300 + domain: "example.com" + + redis: + enabled: false + +storage: + local: + path: /config/db.sqlite3 + +notifier: + filesystem: + filename: /config/notification.log + diff --git a/core-proxy/authelia/users_database.yml b/core-proxy/authelia/users_database.yml new file mode 100644 index 0000000..85558bc --- /dev/null +++ b/core-proxy/authelia/users_database.yml @@ -0,0 +1,11 @@ +# To generate a password: +# docker run --rm authelia/authelia:latest authelia hash-password 'yourpassword' + +users: + admin: + displayname: "Admin User" + email: "admin@example.com" + groups: + - admins + # Replace this with an argon2id hash generated by Authelia + password: "$argon2id$v=19$m=65536,t=3,p=2$BASE64_SALT$BASE64_HASH" diff --git a/core-proxy/docker-compose.yml b/core-proxy/docker-compose.yml new file mode 100644 index 0000000..7fb14e1 --- /dev/null +++ b/core-proxy/docker-compose.yml @@ -0,0 +1,58 @@ +version: "3.9" + +networks: + traefik_proxy: + external: true + +services: + traefik: + image: traefik:v3.1 + container_name: traefik + restart: unless-stopped + command: + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.file.directory=/dynamic" + - "--providers.file.watch=true" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + ports: + - "80:80" + - "443:443" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "./traefik/traefik.yml:/traefik.yml:ro" + - "./traefik/acme.json:/letsencrypt/acme.json" + - "./traefik/dynamic:/dynamic:ro" + networks: + - traefik_proxy + labels: + - "traefik.enable=true" + + # HTTP -> HTTPS redirect for traefik.example.com + - "traefik.http.routers.traefik-http.rule=Host(`traefik.example.com`)" + - "traefik.http.routers.traefik-http.entrypoints=web" + - "traefik.http.routers.traefik-http.middlewares=traefik-https-redirect" + - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https" + + # HTTPS router for Traefik dashboard, protected by Authelia + - "traefik.http.routers.traefik-https.rule=Host(`traefik.example.com`)" + - "traefik.http.routers.traefik-https.entrypoints=websecure" + - "traefik.http.routers.traefik-https.tls.certresolver=letsencrypt" + - "traefik.http.routers.traefik-https.service=api@internal" + - "traefik.http.routers.traefik-https.middlewares=authelia-auth@file" + + authelia: + image: authelia/authelia:latest + container_name: authelia + restart: unless-stopped + volumes: + - "./authelia/configuration.yml:/config/configuration.yml:ro" + - "./authelia/users_database.yml:/config/users_database.yml:ro" + # runtime DB + logs live in /config; optional to bind for backup + networks: + - traefik_proxy + diff --git a/core-proxy/traefik/dynamic/authelia.yml b/core-proxy/traefik/dynamic/authelia.yml new file mode 100644 index 0000000..7f7e98e --- /dev/null +++ b/core-proxy/traefik/dynamic/authelia.yml @@ -0,0 +1,27 @@ +http: + middlewares: + authelia-auth: + forwardAuth: + address: "http://authelia:9091/api/authz/forward-auth" + trustForwardHeader: true + authResponseHeaders: + - "Remote-User" + - "Remote-Name" + - "Remote-Email" + - "Remote-Groups" + + routers: + authelia: + rule: "Host(`auth.example.com`)" + entryPoints: + - websecure + service: authelia + tls: + certResolver: letsencrypt + + services: + authelia: + loadBalancer: + servers: + - url: "http://authelia:9091" + diff --git a/core-proxy/traefik/traefik.yml b/core-proxy/traefik/traefik.yml new file mode 100644 index 0000000..c125d97 --- /dev/null +++ b/core-proxy/traefik/traefik.yml @@ -0,0 +1,7 @@ +log: + level: INFO + +api: + dashboard: true + insecure: false + diff --git a/firewall/nftables.conf.example b/firewall/nftables.conf.example new file mode 100644 index 0000000..78c5646 --- /dev/null +++ b/firewall/nftables.conf.example @@ -0,0 +1,37 @@ +#!/usr/sbin/nft -f + +flush ruleset + +table inet filter { + chain input { + type filter hook input priority 0; + + iif lo accept + ct state established,related accept + + ip protocol icmp accept + ip6 nexthdr icmpv6 accept + + # SSH via ZeroTier only (interfaces starting with zt) + iifname "zt+" tcp dport 22 accept + + # Public web via Traefik + tcp dport { 80, 443 } accept + + # Example: AI services only accessible via ZeroTier + iifname "zt+" tcp dport { 7860,8080,11434,8000,8501 } accept + + counter drop + } + + chain forward { + type filter hook forward priority 0; + drop + } + + chain output { + type filter hook output priority 0; + accept + } +} + diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..7c59c42 --- /dev/null +++ b/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +docker network create traefik_proxy +cd core-proxy +touch traefik/acme.json +chmod 600 traefik/acme.json + diff --git a/sites/forgejo/docker-compose.yml b/sites/forgejo/docker-compose.yml new file mode 100644 index 0000000..9e5ef4b --- /dev/null +++ b/sites/forgejo/docker-compose.yml @@ -0,0 +1,89 @@ +version: "3.9" + +networks: + traefik_proxy: + external: true + forgejo_net: + external: false + +services: + forgejo_db: + image: postgres:16 + container_name: forgejo_db + restart: unless-stopped + networks: + - forgejo_net + environment: + - POSTGRES_DB=forgejo + - POSTGRES_USER=forgejo + - POSTGRES_PASSWORD=change_db_password + volumes: + - ./db:/var/lib/postgresql/data + + forgejo_redis: + image: redis:7-alpine + container_name: forgejo_redis + restart: unless-stopped + networks: + - forgejo_net + volumes: + - ./redis:/data + + forgejo: + image: codeberg.org/forgejo/forgejo:latest + container_name: forgejo + restart: unless-stopped + networks: + - traefik_proxy + - forgejo_net + depends_on: + - forgejo_db + - forgejo_redis + environment: + - USER_UID=1000 + - USER_GID=1000 + + - FORGEJO__database__DB_TYPE=postgres + - FORGEJO__database__HOST=forgejo_db:5432 + - FORGEJO__database__NAME=forgejo + - FORGEJO__database__USER=forgejo + - FORGEJO__database__PASSWD=change_db_password + + - FORGEJO__cache__ADAPTER=redis + - FORGEJO__cache__HOST=network=tcp,addr=forgejo_redis:6379,db=0,pool_size=100,idle_timeout=180 + + - FORGEJO__server__ROOT_URL=https://git.example.com + - FORGEJO__server__DOMAIN=git.example.com + volumes: + - ./data:/var/lib/gitea + labels: + - "traefik.enable=true" + + # HTTP -> HTTPS + - "traefik.http.routers.forgejo-http.rule=Host(`git.example.com`)" + - "traefik.http.routers.forgejo-http.entrypoints=web" + - "traefik.http.routers.forgejo-http.middlewares=forgejo-https-redirect" + - "traefik.http.middlewares.forgejo-https-redirect.redirectscheme.scheme=https" + + # HTTPS + Authelia + - "traefik.http.routers.forgejo-https.rule=Host(`git.example.com`)" + - "traefik.http.routers.forgejo-https.entrypoints=websecure" + - "traefik.http.routers.forgejo-https.tls.certresolver=letsencrypt" + - "traefik.http.routers.forgejo-https.middlewares=authelia-auth@file" + + forgejo_runner: + image: codeberg.org/forgejo/runner:latest + container_name: forgejo_runner + restart: unless-stopped + depends_on: + - forgejo + networks: + - forgejo_net + volumes: + - ./runner:/data + environment: + - FORGEJO_INSTANCE_URL=https://git.example.com + - FORGEJO_RUNNER_REGISTRATION_TOKEN=CHANGE_ME + - FORGEJO_RUNNER_NAME=server-runner + - FORGEJO_RUNNER_LABELS=ubuntu,server + diff --git a/sites/static-site/docker-compose.yml b/sites/static-site/docker-compose.yml new file mode 100644 index 0000000..86c815d --- /dev/null +++ b/sites/static-site/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.9" + +networks: + traefik_proxy: + external: true + +services: + static_site: + image: nginx:alpine + container_name: static_site + restart: unless-stopped + networks: + - traefik_proxy + volumes: + - ./html:/usr/share/nginx/html:ro + labels: + - "traefik.enable=true" + + # HTTP -> HTTPS + - "traefik.http.routers.static-http.rule=Host(`example.com`)" + - "traefik.http.routers.static-http.entrypoints=web" + - "traefik.http.routers.static-http.middlewares=static-https-redirect" + - "traefik.http.middlewares.static-https-redirect.redirectscheme.scheme=https" + + # HTTPS + - "traefik.http.routers.static-https.rule=Host(`example.com`)" + - "traefik.http.routers.static-https.entrypoints=websecure" + - "traefik.http.routers.static-https.tls.certresolver=letsencrypt" + # Uncomment to protect with Authelia + # - "traefik.http.routers.static-https.middlewares=authelia-auth@file" + diff --git a/sites/wordpress-site/docker-compose.yml b/sites/wordpress-site/docker-compose.yml new file mode 100644 index 0000000..a19c442 --- /dev/null +++ b/sites/wordpress-site/docker-compose.yml @@ -0,0 +1,51 @@ +version: "3.9" + +networks: + traefik_proxy: + external: true + wp_net: + external: false + +services: + wp_db: + image: mariadb:11 + container_name: wordpress_db + restart: unless-stopped + networks: + - wp_net + environment: + - MYSQL_ROOT_PASSWORD=change_root_password + - MYSQL_DATABASE=wordpress + - MYSQL_USER=wpuser + - MYSQL_PASSWORD=change_wp_password + volumes: + - ./db:/var/lib/mysql + + wordpress: + image: wordpress:latest + container_name: wordpress_app + restart: unless-stopped + networks: + - traefik_proxy + - wp_net + environment: + - WORDPRESS_DB_HOST=wp_db:3306 + - WORDPRESS_DB_NAME=wordpress + - WORDPRESS_DB_USER=wpuser + - WORDPRESS_DB_PASSWORD=change_wp_password + + - WORDPRESS_HOME=https://example.com/wp + - WORDPRESS_SITEURL=https://example.com/wp + volumes: + - ./wp:/var/www/html + labels: + - "traefik.enable=true" + + # HTTPS router for /wp + - "traefik.http.routers.wp-https.rule=Host(`example.com`) && PathPrefix(`/wp`)" + - "traefik.http.routers.wp-https.entrypoints=websecure" + - "traefik.http.routers.wp-https.tls.certresolver=letsencrypt" + - "traefik.http.routers.wp-https.priority=10" + # Uncomment to require Authelia before WP + # - "traefik.http.routers.wp-https.middlewares=authelia-auth@file" +