From 67996ade869a91d62fc7ddd06746ef7b9aefd9cb Mon Sep 17 00:00:00 2001 From: Jonathan Agmon Date: Wed, 22 Oct 2025 19:59:09 +0000 Subject: [PATCH] Initial commit --- README.md | 35 ++++++++ backend/postgres/.env.example | 11 +++ backend/postgres/compose.yml | 34 +++++++ backend/postgres/scripts/create-db.sh | 28 ++++++ backend/postgres/scripts/create-user.sh | 46 ++++++++++ backend/postgres/scripts/drop-user.sh | 110 +++++++++++++++++++++++ backend/postgres/scripts/grant-prvlgs.sh | 50 +++++++++++ backend/redis/compose.yml | 26 ++++++ frontend/cloudflared/.env.example | 2 + frontend/cloudflared/compose.yml | 16 ++++ frontend/traefik/.env.example | 5 ++ frontend/traefik/compose.yml | 65 ++++++++++++++ local/adguard/.env.example | 1 + local/adguard/README.md | 10 +++ local/adguard/compose.yml | 17 ++++ mgmt/adminer/compose.yml | 35 ++++++++ mgmt/authentik/.env.example | 31 +++++++ mgmt/authentik/compose.yml | 83 +++++++++++++++++ mgmt/gitea/.env.example | 8 ++ mgmt/gitea/compose.yml | 49 ++++++++++ mgmt/portainer/.env.example | 2 + mgmt/portainer/docker-compose.yml | 35 ++++++++ mgmt/redis-commander/compose.yml | 17 ++++ mgmt/vaultwarden/.env.example | 4 + mgmt/vaultwarden/compose.yml | 35 ++++++++ templates/compose.yml | 0 webapp/it-tools/.env.example | 2 + webapp/it-tools/compose.yml | 26 ++++++ webapp/n8n/.env.example | 18 ++++ webapp/n8n/compose.yml | 46 ++++++++++ webapp/navidrome/.env.example | 3 + webapp/navidrome/compose.yml | 36 ++++++++ webapp/qbittorrent/.env.example | 5 ++ webapp/qbittorrent/compose.yml | 14 +++ 34 files changed, 905 insertions(+) create mode 100644 README.md create mode 100644 backend/postgres/.env.example create mode 100644 backend/postgres/compose.yml create mode 100755 backend/postgres/scripts/create-db.sh create mode 100755 backend/postgres/scripts/create-user.sh create mode 100755 backend/postgres/scripts/drop-user.sh create mode 100755 backend/postgres/scripts/grant-prvlgs.sh create mode 100644 backend/redis/compose.yml create mode 100644 frontend/cloudflared/.env.example create mode 100644 frontend/cloudflared/compose.yml create mode 100644 frontend/traefik/.env.example create mode 100644 frontend/traefik/compose.yml create mode 100644 local/adguard/.env.example create mode 100644 local/adguard/README.md create mode 100644 local/adguard/compose.yml create mode 100644 mgmt/adminer/compose.yml create mode 100644 mgmt/authentik/.env.example create mode 100644 mgmt/authentik/compose.yml create mode 100644 mgmt/gitea/.env.example create mode 100644 mgmt/gitea/compose.yml create mode 100644 mgmt/portainer/.env.example create mode 100644 mgmt/portainer/docker-compose.yml create mode 100644 mgmt/redis-commander/compose.yml create mode 100644 mgmt/vaultwarden/.env.example create mode 100644 mgmt/vaultwarden/compose.yml create mode 100644 templates/compose.yml create mode 100644 webapp/it-tools/.env.example create mode 100644 webapp/it-tools/compose.yml create mode 100644 webapp/n8n/.env.example create mode 100644 webapp/n8n/compose.yml create mode 100644 webapp/navidrome/.env.example create mode 100644 webapp/navidrome/compose.yml create mode 100644 webapp/qbittorrent/.env.example create mode 100644 webapp/qbittorrent/compose.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..57ce21a --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# My Docker Compose Projects Repo +Welcome to my humble repo of all the infrastructure I have assenbled thourtght the last months. +I have gone through A LOT with these projects and I have finally estibalished a position that I'm comfortable and (most important) happy with :) +In this README file I will go through all of the infrastructure I have +## Frontend +This section responsible to the exposure of the deployed services to the (wild) internet. +It relies on Cloudflare's Zero-Trust Tunnles feature to do so. +This is the only network "hole" I have made in my infrastructure and no port forwarding in my home-router was needed. +### Cloudflared +WIP +### Traefik +WIP +## Backend +WIP +### Postgres +WIP +### Redis +WIP +## Management +WIP +### Portainer +WIP +### Adminer +WIP +## Web-Apps +WIP +### Navidrome +WIP +### n8n +WIP +### +## Honorable Mentions +WIP +### Adguard +WIP \ No newline at end of file diff --git a/backend/postgres/.env.example b/backend/postgres/.env.example new file mode 100644 index 0000000..36f269d --- /dev/null +++ b/backend/postgres/.env.example @@ -0,0 +1,11 @@ +# PostgreSQL root user +POSTGRES_USER= +POSTGRES_PASSWORD_FILE=/run/secrets/YOUR_SECRET_PASS + +# Timezone settings +TZ= +PGTZ= + +# PostgreSQL settings +POSTGRES_INITDB_ARGS=--data-checksums +PGDATA=/var/lib/postgresql/18/docker \ No newline at end of file diff --git a/backend/postgres/compose.yml b/backend/postgres/compose.yml new file mode 100644 index 0000000..d40cd59 --- /dev/null +++ b/backend/postgres/compose.yml @@ -0,0 +1,34 @@ +services: + postgres: + image: postgres:18 + container_name: postgres + restart: always + healthcheck: + interval: 30s + retries: 5 + start_period: 20s + test: + - CMD-SHELL + - pg_isready -U $${POSTGRES_USER} + timeout: 5s + env_file: + - .env + secrets: + - PGROOT_PASS + # - PSUSER_PASS + volumes: + - pgdata:/var/lib/postgresql/18/docker + networks: + - db +volumes: + pgdata: + name: pgdata +secrets: + PGROOT_PASS: + file: .secrets/PGROOT_PASS + # PSUSER_PASS: + # file: .secrets/PSUSER_PASS +networks: + db: + external: + true \ No newline at end of file diff --git a/backend/postgres/scripts/create-db.sh b/backend/postgres/scripts/create-db.sh new file mode 100755 index 0000000..fe8e5ec --- /dev/null +++ b/backend/postgres/scripts/create-db.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Prompt for container name +read -p "Enter PostgreSQL container name: " CONTAINER_NAME + +# Check if container is running +if [ "$(docker ps -q -f name=$CONTAINER_NAME)" = "" ]; then + echo "Container $CONTAINER_NAME is not running!" + exit 1 +fi +echo "Using container: $CONTAINER_NAME" + +# Prompt for PostgreSQL root credentials +read -p "Enter PostgreSQL root username: " PGROOT_USER + +# Prompt for database name +read -p "Enter new PostgreSQL database name: " DB_NAME + +# Check if database exists +DB_EXISTS=$(docker exec $CONTAINER_NAME psql -U $PGROOT_USER -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'") +if [ "$DB_EXISTS" = "1" ]; then + echo "Database $DB_NAME already exists!" + exit 1 +fi + +# Create the database using docker exec and psql +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -c "CREATE DATABASE $DB_NAME;" +echo "Database $DB_NAME has been created successfully." diff --git a/backend/postgres/scripts/create-user.sh b/backend/postgres/scripts/create-user.sh new file mode 100755 index 0000000..edc6f8d --- /dev/null +++ b/backend/postgres/scripts/create-user.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Prompt for container name +read -p "Enter PostgreSQL container name: " CONTAINER_NAME + +# Check if container is running +if [ "$(docker ps -q -f name=$CONTAINER_NAME)" = "" ]; then + echo "Container $CONTAINER_NAME is not running!" + exit 1 +fi +echo "Using container: $CONTAINER_NAME" + +# Prompt for PostgreSQL root credentials +read -p "Enter PostgreSQL root username: " PGROOT_USER + +# Prompt for username and password +read -p "Enter new PostgreSQL username: " DB_USER + +# Check if username is provided +if [[ -z "$DB_USER" ]]; then + echo "Error: Username cannot be empty!" >&2 + exit 1 +fi + +# Check if user already exists in PostgreSQL +if docker exec $CONTAINER_NAME psql -U $PGROOT_USER -tAc "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname='$DB_USER'" | grep -q 1; then + echo "Error: User '$DB_USER' already exists in PostgreSQL!" >&2 + exit 1 +fi + +echo "User '$DB_USER' does not exist. Proceeding..." +read -s -p "Enter password for $DB_USER: " DB_PASS +echo +read -s -p "Confirm password for $DB_USER: " DB_PASS_CONFIRM +echo + +# Check if passwords match +if [ "$DB_PASS" != "$DB_PASS_CONFIRM" ]; then + echo "Passwords do not match!" + exit 1 +fi + +# Create the user using docker exec and psql +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';" + +echo "User $DB_USER has been created successfully." diff --git a/backend/postgres/scripts/drop-user.sh b/backend/postgres/scripts/drop-user.sh new file mode 100755 index 0000000..5cfec2f --- /dev/null +++ b/backend/postgres/scripts/drop-user.sh @@ -0,0 +1,110 @@ +#!/bin/bash +set -e + +# Prompt for container name +read -p "Enter PostgreSQL container name: " CONTAINER_NAME + +# Check if username is provided +if [[ -z "$CONTAINER_NAME" ]]; then + echo "Error: Container cannot be empty!" >&2 + exit 1 +fi + +# Check if container is running +if [ "$(docker ps -q -f name=$CONTAINER_NAME)" = "" ]; then + echo "Container $CONTAINER_NAME is not running!" + exit 1 +fi + +echo "Using container: $CONTAINER_NAME" + +# Prompt for PostgreSQL root credentials +read -p "Enter PostgreSQL root username: " PGROOT_USER + +# Prompt for username to drop +read -p "Enter PostgreSQL username to drop: " DB_USER + +# Check if username is provided +if [[ -z "$DB_USER" ]]; then + echo "Error: Username cannot be empty!" >&2 + exit 1 +fi +# Check if user exists in PostgreSQL +USER_EXISTS=$(docker exec "$CONTAINER_NAME" psql -U "$PGROOT_USER" -tAc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER';") +if [[ "$USER_EXISTS" != "1" ]]; then + echo "Error: User '$DB_USER' does not exist in PostgreSQL!" >&2 + exit 1 +fi + +# # Check if user exists in PostgreSQL +# if ! docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $CONTAINER_NAME -tAc "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname='$DB_USER'" | grep -q 1; then +# echo "Error: User '$DB_USER' does not exist in PostgreSQL!" >&2 +# exit 1 +# fi + +echo "User '$DB_USER' exists." + +# Get all databases (not just those with CONNECT privilege) +echo "Checking databases..." +ALL_DATABASES=$(docker exec $CONTAINER_NAME psql -U "$PGROOT_USER" -tAc " +SELECT datname +FROM pg_database +WHERE datname NOT IN ('template0', 'template1') + AND datallowconn = true; +") + +# Check for database privileges +echo "Checking privileges for user '$DB_USER'..." +DATABASES=$(docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $CONTAINER_NAME -tAc " +SELECT datname +FROM pg_database d +WHERE has_database_privilege('$DB_USER', d.oid, 'CONNECT') + AND datname NOT IN ('template0', 'template1'); +") + +if [[ -n "$DATABASES" && "$DATABASES" != "" ]]; then + echo "WARNING: User '$DB_USER' has privileges on the following database(s):" + echo "$DATABASES" + echo "" + read -p "Do you want to proceed with removing this user? (yes/no): " CONFIRM + + if [[ "$CONFIRM" != "yes" ]]; then + echo "Operation cancelled." + exit 0 + fi + + # Revoke privileges from each database + echo "Revoking privileges from databases..." + while IFS= read -r DB_NAME; do + if [[ -n "$DB_NAME" ]]; then + echo " - Revoking privileges on database: $DB_NAME" + docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $CONTAINER_NAME -c "REVOKE ALL PRIVILEGES ON DATABASE \"$DB_NAME\" FROM \"$DB_USER\";" + + # Revoke privileges on all tables, sequences, and functions in public schema + docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d "$DB_NAME" -c "REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"$DB_USER\";" + docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d "$DB_NAME" -c "REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM \"$DB_USER\";" + docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d "$DB_NAME" -c "REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM \"$DB_USER\";" + docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d "$DB_NAME" -c "REVOKE ALL PRIVILEGES ON SCHEMA public FROM \"$DB_USER\";" + fi + done <<< "$DATABASES" + + echo "Privileges revoked successfully." +else + echo "User '$DB_USER' has no database privileges." +fi + +# Reassign and drop owned objects in each database +echo "Reassigning and dropping owned objects in all databases..." +while IFS= read -r DB_NAME; do + if [[ -n "$DB_NAME" ]]; then + echo " - Processing database: $DB_NAME" + docker exec $CONTAINER_NAME psql -U "$PGROOT_USER" -d "$DB_NAME" -c "REASSIGN OWNED BY \"$DB_USER\" TO \"$PGROOT_USER\";" 2>/dev/null + docker exec $CONTAINER_NAME psql -U "$PGROOT_USER" -d "$DB_NAME" -c "DROP OWNED BY \"$DB_USER\";" 2>/dev/null + fi +done <<< "$ALL_DATABASES" + +# Drop the user using docker exec and psql +echo "Dropping user '$DB_USER'..." +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $CONTAINER_NAME -c "DROP USER \"$DB_USER\";" + +echo "User $DB_USER has been dropped successfully." \ No newline at end of file diff --git a/backend/postgres/scripts/grant-prvlgs.sh b/backend/postgres/scripts/grant-prvlgs.sh new file mode 100755 index 0000000..2d179ff --- /dev/null +++ b/backend/postgres/scripts/grant-prvlgs.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Prompt for container name +read -p "Enter PostgreSQL container name: " CONTAINER_NAME + +# Check if container is running +if [ "$(docker ps -q -f name=$CONTAINER_NAME)" = "" ]; then + echo "Container $CONTAINER_NAME is not running!" + exit 1 +fi +echo "Using container: $CONTAINER_NAME" + +# Prompt for PostgreSQL root credentials +read -p "Enter PostgreSQL root username: " PGROOT_USER + +# Prompt for username and database name +read -p "Enter PostgreSQL username to grant privileges: " USERNAME +read -p "Enter PostgreSQL database name will be granted on: " DB_NAME + +# Check if inputs are not empty +if [ -z "$USERNAME" ] || [ -z "$DB_NAME" ]; then + echo "Error: Username and database name cannot be empty" + exit 1 +fi + +# Grant all privileges +echo "Granting all privileges on $DB_NAME to $USERNAME..." + +# Grant database privileges +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -c "GRANT CONNECT ON DATABASE $DB_NAME TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -c "ALTER DATABASE $DB_NAME OWNER TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -c "ALTER USER $USERNAME CREATEDB;" + +# Connect to the specific database and grant schema privileges +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "GRANT ALL PRIVILEGES ON SCHEMA public TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "GRANT CREATE ON SCHEMA public TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "GRANT CREATE ON DATABASE $DB_NAME TO $USERNAME;" + +# Grant default privileges for future tables and sequences +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO $USERNAME;" +docker exec $CONTAINER_NAME psql -U $PGROOT_USER -d $DB_NAME -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO $USERNAME;" + +if [ $? -eq 0 ]; then + echo "Successfully granted all privileges" +else + echo "Error granting privileges" + exit 1 +fi \ No newline at end of file diff --git a/backend/redis/compose.yml b/backend/redis/compose.yml new file mode 100644 index 0000000..ca541ff --- /dev/null +++ b/backend/redis/compose.yml @@ -0,0 +1,26 @@ +services: + redis: + command: --save 60 1 --loglevel warning + healthcheck: + interval: 30s + retries: 5 + start_period: 20s + test: + - CMD-SHELL + - redis-cli ping | grep PONG + timeout: 3s + image: docker.io/library/redis:alpine + container_name: redis + restart: unless-stopped + env_file: + - .env + networks: + - db + volumes: + - redis_data:/data +volumes: + redis_data: + name: redis_data +networks: + db: + external: true \ No newline at end of file diff --git a/frontend/cloudflared/.env.example b/frontend/cloudflared/.env.example new file mode 100644 index 0000000..fec8fae --- /dev/null +++ b/frontend/cloudflared/.env.example @@ -0,0 +1,2 @@ +TUNNEL_TOKEN= +TZ= \ No newline at end of file diff --git a/frontend/cloudflared/compose.yml b/frontend/cloudflared/compose.yml new file mode 100644 index 0000000..514f760 --- /dev/null +++ b/frontend/cloudflared/compose.yml @@ -0,0 +1,16 @@ +services: + cloudflared: + image: cloudflare/cloudflared:latest + container_name: cloudflared + restart: always + command: tunnel --no-autoupdate run + labels: + - traefik.enable=true + env_file: + - .env + networks: + - frontend +networks: + frontend: + external: + true \ No newline at end of file diff --git a/frontend/traefik/.env.example b/frontend/traefik/.env.example new file mode 100644 index 0000000..fbd4bd4 --- /dev/null +++ b/frontend/traefik/.env.example @@ -0,0 +1,5 @@ +TRAEFIK_USER= +SSL_EMAIL= +CF_API_EMAIL= +CF_API_KEY= +TZ= \ No newline at end of file diff --git a/frontend/traefik/compose.yml b/frontend/traefik/compose.yml new file mode 100644 index 0000000..ef8052c --- /dev/null +++ b/frontend/traefik/compose.yml @@ -0,0 +1,65 @@ +services: + traefik: + image: "traefik:v3.5.3" + container_name: ${SUBDOMAIN} + restart: always + command: + - "--log.level=DEBUG" + - "--api.insecure=false" + - "--api.dashboard=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + - "--entrypoints.web.http.redirections.entrypoint.permanent=true" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http.tls=true" + - "--entrypoints.websecure.http.tls.certresolver=cloudflare" + - "--entrypoints.websecure.http.tls.domains[0].main=${DOMAIN_NAME}" + - "--entrypoints.websecure.http.tls.domains[0].sans=*.${DOMAIN_NAME}" + - "--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" + - "--certificatesresolvers.cloudflare.acme.dnschallenge=true" + - "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare" + - "--certificatesresolvers.cloudflare.acme.email=${CF_API_EMAIL}" + - "--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json" + labels: + - traefik.enable=true + - traefik.http.routers.traefik_dashboard.rule=Host(`traefik.jojops.com`) + # - traefik.http.routers.traefik_dashboard.rule=Host(`traefik.jojops.com`) && PathPrefix(`/outpost.goauthentik.io/`) + - traefik.http.routers.traefik_dashboard.entrypoints=websecure + - traefik.http.routers.traefik_dashboard.service=api@internal + - traefik.http.routers.traefik_dashboard.tls=true + - traefik.http.middlewares.myauth.basicauth.users=${TRAEFIK_USER} + # - traefik.http.middlewares.myauth.basicauth.users=test:$$apr1$$46.RmdYB$$Rx33ChqUskl4PF1ZqSXYV1 + # - traefik.http.routers.traefik_dashboard.middlewares=myauth@docker + - traefik.http.routers.traefik_dashboard.middlewares=authentik-forwardauth@docker + - traefik.http.routers.traefik_dashboard.tls.certresolver=cloudflare + # - traefik.http.routers.traefik-secure.tls.domains[0].main=jojops.com + # - traefik.http.routers.traefik-secure.tls.domains[0].sans=*.jojops.com + # - traefik.http.middlewares.myauth.redirectscheme.scheme=https + - traefik.http.services.traefik_dashboard.loadbalancer.server.port=80 + # - "traefik.http.middlewares.cloudflare-ips.ipallowlist.sourcerange=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,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32" + env_file: + - .env + volumes: + - ./traefik_data:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - "80:80" + - "443:443" + networks: + - frontend + - webapp + - mgmt +networks: + frontend: + external: + true + webapp: + external: + true + mgmt: + external: + true diff --git a/local/adguard/.env.example b/local/adguard/.env.example new file mode 100644 index 0000000..745da6a --- /dev/null +++ b/local/adguard/.env.example @@ -0,0 +1 @@ +IP_ADDRESS= \ No newline at end of file diff --git a/local/adguard/README.md b/local/adguard/README.md new file mode 100644 index 0000000..79898fd --- /dev/null +++ b/local/adguard/README.md @@ -0,0 +1,10 @@ +## WIP +### Command for creating docker macvlan network +### Change all CAPITAL arguments to your config +docker network create -d macvlan -o parent=HOST_NIC --gateway DG --subnet SUB --ip-range SUB NAME + +### Service for +WIP + +### DNS Rewrites +Disable IPv6 at your LAN or implement it :) diff --git a/local/adguard/compose.yml b/local/adguard/compose.yml new file mode 100644 index 0000000..7f69c69 --- /dev/null +++ b/local/adguard/compose.yml @@ -0,0 +1,17 @@ +services: + adguard: + image: adguard/adguardhome + container_name: adguard + hostname: $HOSTNAME + restart: always + env_file: + - .env + volumes: + - ./work:/opt/adguardhome/work + - ./conf:/opt/adguardhome/conf + networks: + lan: + ipv4_address: $IP_ADDRESS +networks: + lan: + external: true diff --git a/mgmt/adminer/compose.yml b/mgmt/adminer/compose.yml new file mode 100644 index 0000000..56c03ca --- /dev/null +++ b/mgmt/adminer/compose.yml @@ -0,0 +1,35 @@ +services: + adminer: + image: adminer:5.4.0 + container_name: $SUBDOMAIN + restart: always + labels: + - traefik.enable=true + - traefik.http.routers.$SUBDOMAIN.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.$SUBDOMAIN.tls=true + - traefik.http.routers.$SUBDOMAIN.entrypoints=web,websecure + - traefik.http.routers.$SUBDOMAIN.tls.certresolver=cloudflare + - traefik.http.middlewares.$SUBDOMAIN.headers.SSLRedirect=true + - traefik.http.middlewares.$SUBDOMAIN.headers.STSSeconds=315360000 + - traefik.http.middlewares.$SUBDOMAIN.headers.browserXSSFilter=true + - traefik.http.middlewares.$SUBDOMAIN.headers.contentTypeNosniff=true + - traefik.http.middlewares.$SUBDOMAIN.headers.forceSTSHeader=true + - traefik.http.middlewares.$SUBDOMAIN.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.$SUBDOMAIN.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.$SUBDOMAIN.headers.STSPreload=true + - traefik.http.middlewares.$SUBDOMAIN.headers.frameDeny=true + - traefik.http.routers.$SUBDOMAIN.middlewares=$SUBDOMAIN@docker + - traefik.http.services.$SUBDOMAIN.loadbalancer.server.port=8080 + - traefik.docker.network=webapp + env_file: + - .env + networks: + - webapp + - db +networks: + webapp: + external: + true + db: + external: + true \ No newline at end of file diff --git a/mgmt/authentik/.env.example b/mgmt/authentik/.env.example new file mode 100644 index 0000000..61fc00a --- /dev/null +++ b/mgmt/authentik/.env.example @@ -0,0 +1,31 @@ +DOMAIN_NAME= +SUBDOMAIN= + +# Authentik Configuration +AUTHENTIK_SECRET_KEY=file:///run/secrets/SECRET_KEY +AUTHENTIK_TAG= + +# PostgreSQL Configuration +PG_HOST=postgres +PG_PORT=5432 +PG_USER= +PG_PASS=file:///run/secrets/DB_PASS +PG_DB= + +# Redis Configuration +REDIS_HOST=redis +REDIS_PORT=6379 + +# Optional: Custom ports +COMPOSE_PORT_HTTP=9000 +COMPOSE_PORT_HTTPS=9443 + +# Environment Variables for Docker Compose +AUTHENTIK_REDIS__HOST=$REDIS_HOST +AUTHENTIK_REDIS__PORT=$REDIS_PORT +AUTHENTIK_POSTGRESQL__HOST=$PG_HOST +AUTHENTIK_POSTGRESQL__PORT=$PG_PORT +AUTHENTIK_POSTGRESQL__USER=$PG_USER +AUTHENTIK_POSTGRESQL__NAME=$PG_DB +AUTHENTIK_POSTGRESQL__PASSWORD=$PG_PASS +AUTHENTIK_HOST_BROWSER="https://$SUBDOMAIN.$DOMAIN_NAME" \ No newline at end of file diff --git a/mgmt/authentik/compose.yml b/mgmt/authentik/compose.yml new file mode 100644 index 0000000..b042e59 --- /dev/null +++ b/mgmt/authentik/compose.yml @@ -0,0 +1,83 @@ +services: + server: + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG} + restart: unless-stopped + command: server + env_file: + - .env + secrets: + - SECRET_KEY + - DB_PASS + volumes: + - ./media:/media + - ./custom-templates:/templates + networks: + - webapp + - db + # ports: + # - "${COMPOSE_PORT_HTTP:-9000}:9000" + # - "${COMPOSE_PORT_HTTPS:-9443}:9443" + labels: + - traefik.enable=true + - traefik.http.routers.$SUBDOMAIN.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.$SUBDOMAIN.tls=true + - traefik.http.routers.$SUBDOMAIN.entrypoints=web,websecure + - traefik.http.routers.$SUBDOMAIN.tls.certresolver=cloudflare + - traefik.http.middlewares.$SUBDOMAIN.headers.SSLRedirect=true + - traefik.http.middlewares.$SUBDOMAIN.headers.STSSeconds=315360000 + - traefik.http.middlewares.$SUBDOMAIN.headers.browserXSSFilter=true + - traefik.http.middlewares.$SUBDOMAIN.headers.contentTypeNosniff=true + - traefik.http.middlewares.$SUBDOMAIN.headers.forceSTSHeader=true + - traefik.http.middlewares.$SUBDOMAIN.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.$SUBDOMAIN.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.$SUBDOMAIN.headers.STSPreload=true + - traefik.http.middlewares.$SUBDOMAIN.headers.frameDeny=true + # - traefik.http.routers.$SUBDOMAIN.middlewares=$SUBDOMAIN@docker + - traefik.http.routers.$SUBDOMAIN.service=$SUBDOMAIN + - traefik.http.services.$SUBDOMAIN.loadbalancer.server.port=9000 + - traefik.docker.network=webapp + - "traefik.http.routers.authentik-output-rtr.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.${DOMAIN_NAME}`) && PathPrefix(`/outpost.goauthentik.io/`)" + + # ForwardAuth middleware definition + - "traefik.http.middlewares.authentik-forwardauth.forwardauth.address=http://authentik-server-1:9000/outpost.goauthentik.io/auth/traefik" + - "traefik.http.middlewares.authentik-forwardauth.forwardauth.trustForwardHeader=true" + - "traefik.http.middlewares.authentik-forwardauth.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version" + # Outpost router for /outpost.goauthentik.io paths + - "traefik.http.routers.$SUBDOMAIN-outpost.rule=Host(`authentik.jojops.com`) && PathPrefix(`/outpost.goauthentik.io/`)" + # - "traefik.http.routers.authentik-outpost.entrypoints=websecure" + # - "traefik.http.routers.authentik-outpost.tls=true" + - traefik.http.routers.$SUBDOMAIN.priority=15 + # - "traefik.http.routers.authentik-outpost.service=authentik-svc" + - "traefik.http.routers.authentik-outpost.rule=HostRegexp(`{$SUBDOMAIN:[a-z0-9-]+}.$DOMAIN_NAME`) && PathPrefix(`/outpost.goauthentik.io/`)" + - "traefik.http.routers.authentik-outpost.entrypoints=websecure" + - "traefik.http.routers.authentik-outpost.tls=true" + - "traefik.http.routers.authentik-outpost.priority=15" + - "traefik.http.routers.authentik-outpost.service=authentik" + + worker: + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG} + restart: unless-stopped + command: worker + env_file: + - .env + secrets: + - SECRET_KEY + - DB_PASS + user: root + networks: + - db + volumes: + # - /var/run/docker.sock:/var/run/docker.sock + - ./media:/media + - ./certs:/certs + - ./custom-templates:/templates +networks: + webapp: + external: true + db: + external: true +secrets: + SECRET_KEY: + file: .secrets/SECRET_KEY + DB_PASS: + file: .secrets/DB_PASS \ No newline at end of file diff --git a/mgmt/gitea/.env.example b/mgmt/gitea/.env.example new file mode 100644 index 0000000..f947aeb --- /dev/null +++ b/mgmt/gitea/.env.example @@ -0,0 +1,8 @@ +DOMAIN_NAME= +SUBDOMAIN= + +GITEA__database__DB_TYPE= +GITEA__database__HOST= +GITEA__database__NAME= +GITEA__database__USER= +GITEA__database__PASSWD__FILE=/run/secrets/DB_PASS \ No newline at end of file diff --git a/mgmt/gitea/compose.yml b/mgmt/gitea/compose.yml new file mode 100644 index 0000000..8eb10a1 --- /dev/null +++ b/mgmt/gitea/compose.yml @@ -0,0 +1,49 @@ +services: + gitea: + image: docker.gitea.com/gitea:1.24.6-rootless + container_name: gitea + restart: always + labels: + - traefik.enable=true + - traefik.http.routers.gitea.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.gitea.tls=true + - traefik.http.routers.gitea.entrypoints=web,websecure + - traefik.http.routers.gitea.tls.certresolver=cloudflare + - traefik.http.middlewares.gitea.headers.SSLRedirect=true + - traefik.http.middlewares.gitea.headers.STSSeconds=315360000 + - traefik.http.middlewares.gitea.headers.browserXSSFilter=true + - traefik.http.middlewares.gitea.headers.contentTypeNosniff=true + - traefik.http.middlewares.gitea.headers.forceSTSHeader=true + - traefik.http.middlewares.gitea.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.gitea.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.gitea.headers.STSPreload=true + - traefik.http.middlewares.gitea.headers.frameDeny=true + - traefik.http.routers.gitea.middlewares=gitea@docker + - traefik.http.services.gitea.loadbalancer.server.port=3000 + - traefik.docker.network=webapp + env_file: + - .env + secrets: + - DB_PASS + volumes: + - gitea-data:/var/lib/gitea + - ./config:/etc/gitea + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + # ports: + # - "3000:3000" + # - "2222:2222" + networks: + - webapp + - db +volumes: + gitea-data: + name: gitea-data +networks: + webapp: + external: true + db: + external: true +secrets: + DB_PASS: + file: .secrets/DB_PASS \ No newline at end of file diff --git a/mgmt/portainer/.env.example b/mgmt/portainer/.env.example new file mode 100644 index 0000000..5e8ed2c --- /dev/null +++ b/mgmt/portainer/.env.example @@ -0,0 +1,2 @@ +DOMAIN_NAME= +SUBDOMAIN= \ No newline at end of file diff --git a/mgmt/portainer/docker-compose.yml b/mgmt/portainer/docker-compose.yml new file mode 100644 index 0000000..0daab8e --- /dev/null +++ b/mgmt/portainer/docker-compose.yml @@ -0,0 +1,35 @@ +services: + portainer: + image: portainer/portainer-ce:lts + container_name: portainer + restart: always + labels: + - traefik.enable=true + - traefik.http.routers.portainer.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.docker.network=webapp + - traefik.http.services.portainer.loadbalancer.server.port=9000 + - traefik.http.routers.portainer.tls=true + - traefik.http.routers.portainer.entrypoints=web,websecure + - traefik.http.routers.portainer.tls.certresolver=cloudflare + - traefik.http.middlewares.portainer.headers.SSLRedirect=true + - traefik.http.middlewares.portainer.headers.STSSeconds=315360000 + - traefik.http.middlewares.portainer.headers.browserXSSFilter=true + - traefik.http.middlewares.portainer.headers.contentTypeNosniff=true + - traefik.http.middlewares.portainer.headers.forceSTSHeader=true + - traefik.http.middlewares.portainer.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.portainer.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.portainer.headers.STSPreload=true + - traefik.http.routers.portainer.middlewares=portainer@docker + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./data:/data + networks: + # - webapp + - mgmt +networks: + # webapp: + # external: + # true + mgmt: + external: + true \ No newline at end of file diff --git a/mgmt/redis-commander/compose.yml b/mgmt/redis-commander/compose.yml new file mode 100644 index 0000000..23987c4 --- /dev/null +++ b/mgmt/redis-commander/compose.yml @@ -0,0 +1,17 @@ +services: + redis-commander: + container_name: redis-commander + # hostname: redis-commander + image: ghcr.io/joeferner/redis-commander:latest + # build: . + restart: always + environment: + - REDIS_HOSTS=local:redis:6379 + # ports: + # - "8081:8081" + user: redis +networks: + db: + external: true + web: + external: true \ No newline at end of file diff --git a/mgmt/vaultwarden/.env.example b/mgmt/vaultwarden/.env.example new file mode 100644 index 0000000..a003433 --- /dev/null +++ b/mgmt/vaultwarden/.env.example @@ -0,0 +1,4 @@ +DOMAIN_NAME= +SUBDOMAIN= +DOMAIN=https://${SUBDOMAIN}.${DOMAIN_NAME} +# SIGNUPS_ALLOWED=false # Uncomment to disable signups \ No newline at end of file diff --git a/mgmt/vaultwarden/compose.yml b/mgmt/vaultwarden/compose.yml new file mode 100644 index 0000000..12f60df --- /dev/null +++ b/mgmt/vaultwarden/compose.yml @@ -0,0 +1,35 @@ +services: + vaultwarden: + image: vaultwarden/server:latest + container_name: vaultwarden + restart: always + env_file: + - .env + labels: + - traefik.enable=true + - traefik.http.routers.vw.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.vw.tls=true + - traefik.http.routers.vw.entrypoints=web,websecure + - traefik.http.routers.vw.tls.certresolver=cloudflare + - traefik.http.middlewares.vw.headers.SSLRedirect=true + - traefik.http.middlewares.vw.headers.STSSeconds=315360000 + - traefik.http.middlewares.vw.headers.browserXSSFilter=true + - traefik.http.middlewares.vw.headers.contentTypeNosniff=true + - traefik.http.middlewares.vw.headers.forceSTSHeader=true + - traefik.http.middlewares.vw.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.vw.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.vw.headers.STSPreload=true + - traefik.http.middlewares.vw.headers.frameDeny=true + - traefik.http.routers.vw.middlewares=vw@docker + - traefik.http.services.vw.loadbalancer.server.port=80 + volumes: + - vw-data:/data/ + networks: + - mgmt +volumes: + vw-data: + name: vw-data +networks: + mgmt: + external: + true \ No newline at end of file diff --git a/templates/compose.yml b/templates/compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/webapp/it-tools/.env.example b/webapp/it-tools/.env.example new file mode 100644 index 0000000..5e8ed2c --- /dev/null +++ b/webapp/it-tools/.env.example @@ -0,0 +1,2 @@ +DOMAIN_NAME= +SUBDOMAIN= \ No newline at end of file diff --git a/webapp/it-tools/compose.yml b/webapp/it-tools/compose.yml new file mode 100644 index 0000000..112406b --- /dev/null +++ b/webapp/it-tools/compose.yml @@ -0,0 +1,26 @@ +services: + it-tools: + image: corentinth/it-tools + container_name: it-tools + labels: + - traefik.enable=true + - traefik.http.routers.$SUBDOMAIN.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.$SUBDOMAIN.tls=true + - traefik.http.routers.$SUBDOMAIN.entrypoints=web,websecure + - traefik.http.routers.$SUBDOMAIN.tls.certresolver=cloudflare + - traefik.http.middlewares.$SUBDOMAIN.headers.SSLRedirect=true + - traefik.http.middlewares.$SUBDOMAIN.headers.STSSeconds=315360000 + - traefik.http.middlewares.$SUBDOMAIN.headers.browserXSSFilter=true + - traefik.http.middlewares.$SUBDOMAIN.headers.contentTypeNosniff=true + - traefik.http.middlewares.$SUBDOMAIN.headers.forceSTSHeader=true + - traefik.http.middlewares.$SUBDOMAIN.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.$SUBDOMAIN.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.$SUBDOMAIN.headers.STSPreload=true + - traefik.http.routers.$SUBDOMAIN.middlewares=$SUBDOMAIN@docker + - traefik.http.services.$SUBDOMAIN.loadbalancer.server.port=433 + networks: + - webapp +networks: + webapp: + external: + true \ No newline at end of file diff --git a/webapp/n8n/.env.example b/webapp/n8n/.env.example new file mode 100644 index 0000000..f2ba974 --- /dev/null +++ b/webapp/n8n/.env.example @@ -0,0 +1,18 @@ +DOMAIN_NAME= +SUBDOMAIN= +GENERIC_TIMEZONE= +SSL_EMAIL= + +N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true +N8N_RUNNERS_ENABLED=true +N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME} + +DB_TYPE=postgresdb +DB_POSTGRESDB_HOST= +DB_POSTGRESDB_PORT=5432 +DB_POSTGRESDB_DATABASE= +DB_POSTGRESDB_USER= +DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/DB_PASS + +WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME} +N8N_GIT_NODE_DISABLE_BARE_REPOS=true \ No newline at end of file diff --git a/webapp/n8n/compose.yml b/webapp/n8n/compose.yml new file mode 100644 index 0000000..f3e049a --- /dev/null +++ b/webapp/n8n/compose.yml @@ -0,0 +1,46 @@ +services: + n8n: + image: docker.n8n.io/n8nio/n8n + container_name: n8n + restart: always + labels: + - traefik.enable=true + - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.n8n.tls=true + - traefik.http.routers.n8n.entrypoints=web,websecure + - traefik.http.routers.n8n.tls.certresolver=cloudflare + - traefik.http.middlewares.n8n.headers.SSLRedirect=true + - traefik.http.middlewares.n8n.headers.STSSeconds=315360000 + - traefik.http.middlewares.n8n.headers.browserXSSFilter=true + - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true + - traefik.http.middlewares.n8n.headers.forceSTSHeader=true + - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.n8n.headers.STSPreload=true + - traefik.http.middlewares.n8n.headers.frameDeny=true + - traefik.http.routers.n8n.middlewares=n8n@docker + - traefik.http.services.n8n.loadbalancer.server.port=5678 + - traefik.docker.network=webapp + env_file: + - .env + secrets: + - DB_PASS + volumes: + - n8n_data:/home/node/.n8n + - ./local-files:/files + networks: + - webapp + - db +secrets: + DB_PASS: + file: .secrets/DB_PASS +networks: + webapp: + external: + true + db: + external: + true +volumes: + n8n_data: + name: n8n_data \ No newline at end of file diff --git a/webapp/navidrome/.env.example b/webapp/navidrome/.env.example new file mode 100644 index 0000000..0048d1a --- /dev/null +++ b/webapp/navidrome/.env.example @@ -0,0 +1,3 @@ +DOMAIN_NAME= +SUBDOMAIN= +# GENERIC_TIMEZONE= \ No newline at end of file diff --git a/webapp/navidrome/compose.yml b/webapp/navidrome/compose.yml new file mode 100644 index 0000000..d106cdb --- /dev/null +++ b/webapp/navidrome/compose.yml @@ -0,0 +1,36 @@ +services: + navidrome: + image: deluan/navidrome:latest + container_name: navidrome + hostname: ${SUBDOMAIN}.${DOMAIN_NAME} + user: 1000:1000 # should be owner of volumes + labels: + - traefik.enable=true + - traefik.http.routers.nd.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) + - traefik.http.routers.nd.tls=true + - traefik.http.routers.nd.entrypoints=web,websecure + - traefik.http.routers.nd.tls.certresolver=cloudflare + - traefik.http.middlewares.nd.headers.SSLRedirect=true + - traefik.http.middlewares.nd.headers.STSSeconds=315360000 + - traefik.http.middlewares.nd.headers.browserXSSFilter=true + - traefik.http.middlewares.nd.headers.contentTypeNosniff=true + - traefik.http.middlewares.nd.headers.forceSTSHeader=true + - traefik.http.middlewares.nd.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.nd.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.nd.headers.STSPreload=true + - traefik.http.middlewares.nd.headers.frameDeny=true + - traefik.http.routers.nd.middlewares=nd@docker + - traefik.http.services.nd.loadbalancer.server.port=4533 + restart: always + env_file: + - .env + # Optional: put your config options customization here. Examples: + # ND_LOGLEVEL: debug + volumes: + - ./data:/data + - ./music:/music:ro + networks: + webapp: +networks: + webapp: + external: true \ No newline at end of file diff --git a/webapp/qbittorrent/.env.example b/webapp/qbittorrent/.env.example new file mode 100644 index 0000000..4ca3ace --- /dev/null +++ b/webapp/qbittorrent/.env.example @@ -0,0 +1,5 @@ +PUID= +PGID= +TZ= +WEBUI_PORT= +TORRENTING_PORT= \ No newline at end of file diff --git a/webapp/qbittorrent/compose.yml b/webapp/qbittorrent/compose.yml new file mode 100644 index 0000000..c272f44 --- /dev/null +++ b/webapp/qbittorrent/compose.yml @@ -0,0 +1,14 @@ +services: + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + hostname: qbittorrent.jojo + volumes: + - ./config:/config + # - /mnt/c/Users/$USER/Downloads/:/downloads #optional + - ~/Downloads/:/downloads + ports: + - 5555:8080 + - 6881:6881 + - 6881:6881/udp + restart: unless-stopped