Initial commit: homelab configuration and documentation

This commit is contained in:
2025-11-29 19:03:14 +00:00
commit 0769ca6888
72 changed files with 7806 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
{
# Global options
admin off
}
# Main fallback server
:80 {
root * /srv/maintenance
file_server
# Serve maintenance page for all requests
handle {
rewrite * /maintenance.html
file_server
}
# Log all requests
log {
output file /var/log/caddy/access.log
}
}
# Optional: HTTPS fallback (if you have certificates)
:443 {
root * /srv/maintenance
file_server
handle {
rewrite * /maintenance.html
file_server
}
log {
output file /var/log/caddy/access.log
}
}

View File

@@ -0,0 +1,27 @@
version: '3.8'
services:
caddy:
image: caddy:latest
container_name: caddy_fallback
restart: unless-stopped
ports:
- "8080:80"
- "8443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./maintenance.html:/srv/maintenance/maintenance.html
- caddy_data:/data
- caddy_config:/config
- caddy_logs:/var/log/caddy
networks:
- caddy_net
volumes:
caddy_data:
caddy_config:
caddy_logs:
networks:
caddy_net:
driver: bridge

View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Service Maintenance</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.container {
text-align: center;
padding: 3rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
max-width: 600px;
}
h1 {
font-size: 3rem;
margin-bottom: 1rem;
animation: pulse 2s infinite;
}
p {
font-size: 1.25rem;
line-height: 1.6;
margin-bottom: 2rem;
}
.status {
display: inline-block;
padding: 0.75rem 2rem;
background: rgba(255, 255, 255, 0.2);
border-radius: 50px;
font-weight: 600;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
</style>
</head>
<body>
<div class="container">
<h1>🔧 Maintenance Mode</h1>
<p>Our services are temporarily unavailable due to maintenance or system updates.</p>
<p>We'll be back online shortly. Thank you for your patience.</p>
<div class="status">⏳ Please check back soon</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,34 @@
# https://github.com/dockur/macos
services:
macos:
image: dockurr/macos
container_name: macos
environment:
VERSION: "15"
DISK_SIZE: "50G"
RAM_SIZE: "6G"
CPU_CORES: "4"
# DHCP: "Y" # if enabled you must create a macvlan
devices:
- /dev/kvm
- /dev/net/tun
cap_add:
- NET_ADMIN
ports:
- 8006:8006
- 5900:5900/tcp
- 5900:5900/udp
volumes:
- ./macos:/storage
restart: always
stop_grace_period: 2m
networks:
macos:
ipv4_address: 172.70.20.3
networks:
macos:
ipam:
config:
- subnet: 172.70.20.0/29
name: macos

View File

@@ -0,0 +1,107 @@
# Place this at ~/docker/docker-compose.yml (overwrite existing if ready)
# NOTE: the top-level "version" key is optional in modern Compose v2/v3 usage.
services:
tsdproxy:
image: almeidapaulopt/tsdproxy:1
container_name: tsdproxy
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- tsd_data:/data
- ./tsdproxy/config:/config
ports:
- "8080:8080"
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
# You may optionally set an auth key here, or add it to /config/tsdproxy.yaml later
TAILSCALE_AUTHKEY: "tskey-auth-kUFWCyDau321CNTRL-Vdt9PFUDUqAb7iQYLvCjqAkhcnq3aTTtg" # (optional — recommended to use config file)
TS_EXTRA_ARGS: "--accept-routes"
db:
image: mariadb:11
container_name: nextcloud-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: supersecurepassword
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: nextcloudpassword
volumes:
- db_data:/var/lib/mysql
nextcloud:
image: nextcloud:29
container_name: nextcloud-app
restart: unless-stopped
depends_on:
- db
environment:
MYSQL_HOST: db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: nextcloudpassword
volumes:
- /mnt/nextcloud-data:/var/www/html/data
- /mnt/nextcloud-config:/var/www/html/config
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.sj98.duckdns.org`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "tsdproxy.enable=true"
- "tsdproxy.name=nextcloud"
plex:
image: lscr.io/linuxserver/plex:latest
container_name: plex
restart: unless-stopped
network_mode: "host"
environment:
PLEX_CLAIM: claim-your-plex-claim
PUID: 1000
PGID: 1000
TZ: America/Chicago
volumes:
- /mnt/media:/media
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.plex.rule=HostSNI(`plex.sj98.duckdns.org`)"
- "traefik.tcp.routers.plex.entrypoints=websecure"
- "traefik.tcp.services.plex.loadbalancer.server.port=32400"
- "tsdproxy.enable=true"
- "tsdproxy.name=plex"
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
network_mode: "host"
environment:
PUID: 1000
PGID: 1000
TZ: America/Chicago
volumes:
- /mnt/media:/media
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.jellyfin.rule=HostSNI(`jellyfin.sj98.duckdns.org`)"
- "traefik.tcp.routers.jellyfin.entrypoints=websecure"
- "traefik.tcp.services.jellyfin.loadbalancer.server.port=8096"
- "tsdproxy.enable=true"
- "tsdproxy.name=jellyfin"
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 3600
volumes:
db_data:
tsd_data:

View File

@@ -0,0 +1,87 @@
version: "3.9"
services:
broker:
image: docker.io/library/redis:7
restart: unless-stopped
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
networks:
- web
db:
image: docker.io/library/postgres:15
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} || exit 1"]
interval: 10s
timeout: 5s
retries: 5
networks:
- web
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped
depends_on:
- db
- broker
ports:
- "8000:8000"
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
- ./export:/usr/src/paperless/export
- ./consume:/usr/src/paperless/consume
environment:
PAPERLESS_DBHOST: db
PAPERLESS_DBNAME: paperless
PAPERLESS_DBUSER: paperless
PAPERLESS_DBPASS: paperless
PAPERLESS_REDIS: redis://broker:6379/0
PAPERLESS_TIME_ZONE: "America/Chicago"
PAPERLESS_SECRET_KEY: "replace-with-a-64-char-random-string"
PAPERLESS_ADMIN_USER: admin@example.local
PAPERLESS_ADMIN_PASSWORD: changeme
PAPERLESS_ALLOWED_HOSTS: '["paperless.sj98.duckdns.org"]'
PAPERLESS_CSRF_TRUSTED_ORIGINS: '["https://paperless.sj98.duckdns.org"]'
# Add / adjust these for running behind Traefik:
PAPERLESS_URL: "https://paperless.sj98.duckdns.org" # required/preferred
PAPERLESS_PROXY_SSL_HEADER: '["HTTP_X_FORWARDED_PROTO","https"]' # tells Django to treat X-Forwarded-Proto=https as TLS
PAPERLESS_USE_X_FORWARD_HOST: "true" # optional, can help URL generation
PAPERLESS_USE_X_FORWARD_PORT: "true" # optional
# Optional: restrict trusted proxies to your docker network or Traefik IP
# PAPERLESS_TRUSTED_PROXIES: "172.18.0.0/16" # <-- replace with your web network subnet or Traefik IP if you want to lock down
networks:
- web
labels:
- "traefik.enable=true"
- "traefik.http.routers.paperless.rule=Host(`paperless.sj98.duckdns.org`)"
- "traefik.http.routers.paperless.entrypoints=websecure"
- "traefik.http.routers.paperless.tls=true"
- "traefik.http.routers.paperless.tls.certresolver=duckdns"
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
- "tsdproxy.enable=true"
- "tsdproxy.name=paperless"
volumes:
data:
media:
pgdata:
redisdata:
networks:
web:
external: true

View File

@@ -0,0 +1,14 @@
version: '3.8'
services:
portainer-agent:
image: portainer/agent:latest
container_name: portainer-agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
environment:
AGENT_CLUSTER_ADDR: 192.168.1.81 # Replace with the actual IP address
AGENT_PORT: 9001
ports:
- "9001:9001" # Port for agent communication
restart: always

View File

@@ -0,0 +1,39 @@
version: '3.8'
services:
rustdesk-hbbs:
image: rustdesk/rustdesk-server:latest
container_name: rustdesk-hbbs
restart: unless-stopped
platform: linux/arm64
command: ["hbbs", "--relay-servers", "192.168.1.245:21117"]
volumes:
- rustdesk_data:/root
ports:
- "21115:21115/tcp"
- "21115:21115/udp"
- "21116:21116/tcp"
- "21116:21116/udp"
rustdesk-hbbr:
image: rustdesk/rustdesk-server:latest
container_name: rustdesk-hbbr
restart: unless-stopped
platform: linux/arm64
command: ["hbbr"]
volumes:
- rustdesk_data:/root
ports:
- "21117:21117/tcp"
- "21118:21118/udp"
- "21119:21119/tcp"
- "21119:21119/udp"
environment:
- TOTAL_BANDWIDTH=20480
- SINGLE_BANDWIDTH=128
- LIMIT_SPEED=100Mb/s
- DOWNGRADE_START_CHECK=600
- DOWNGRADE_THRESHOLD=0.9
volumes:
rustdesk_data:

View File

@@ -0,0 +1,53 @@
version: "3.9"
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
environment:
# Replace this placeholder with your DuckDNS token
- DUCKDNS_TOKEN=03a4d8f7-695a-4f51-b66c-cc2fac555fc1
networks:
- web
ports:
- "80:80" # http
- "443:443" # https
- "8089:8089" # traefik dashboard (secure it if exposed)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt # <-- keep this directory inside WSL filesystem
- ./traefik_dynamic.yml:/etc/traefik/traefik_dynamic.yml:ro
command:
- --api.insecure=false
- --api.dashboard=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.dashboard.address=:8089
- --providers.docker=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker.exposedbydefault=false
- --providers.file.filename=/etc/traefik/traefik_dynamic.yml
- --providers.file.watch=true
- --certificatesresolvers.duckdns.acme.email=sterlenjohnson6@gmail.com
- --certificatesresolvers.duckdns.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.duckdns.acme.dnschallenge.provider=duckdns
- --certificatesresolvers.duckdns.acme.dnschallenge.disablepropagationcheck=true
whoami:
image: containous/whoami:latest
container_name: whoami
restart: unless-stopped
networks:
- web
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.sj98.duckdns.org`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.routers.whoami.tls.certresolver=duckdns"
networks:
web:
external: true

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
# traefik_dynamic.yml
http:
routers:
traefik-dashboard:
entryPoints:
- dashboard
rule: "Host(`localhost`) && (PathPrefix(`/dashboard`) || PathPrefix(`/`))"
service: "api@internal"
middlewares:
- dashboard-auth
middlewares:
dashboard-auth:
basicAuth:
# replace the example hash below with a hash you generate (see step 3)
users:
- "admin:$2y$05$8CZrANjYoKRm5VG6QO8kseVpumnDXnLDU2vREgfMm9F/JdsTpq.iy"
- "Sterl:$2y$05$t8LnSDA190LOs2Wpmbt/p.7dFHzZKDT4BMLjSjqsxg0i6re5I9wlm"