From 8f23ba583d78e33b107bfd6283d66991f882460f Mon Sep 17 00:00:00 2001 From: BenjiG70 Date: Sat, 27 Jun 2026 20:35:49 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Initial=20Commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 3 + dyndns/docker-compose.yml | 30 ++++ excalidraw/docker-compose.yml | 38 +++++ forgejo/docker-compose.yml | 45 ++++++ gantt_dockerfolder/docker-compose.yml | 36 +++++ jellyfin/docker-compose.yml | 41 ++++++ kitchenowl/docker-compose.yml | 59 ++++++++ matrix/docker-compose.yml | 60 ++++++++ n8n/docker-compose.yml | 49 +++++++ openProject/docker-compose.yml | 44 ++++++ passbolt/docker-compose.yml | 72 ++++++++++ readme.md | 192 ++++++++++++++++++++++++++ sparkyFitness/docker-compose.yml | 110 +++++++++++++++ sure/docker-compose.yml | 149 ++++++++++++++++++++ test/dns-check.sh | 19 +++ traefik/docker-compose.yml | 63 +++++++++ wartungsskripte/update_befehl.sh | 1 + wartungsskripte/update_docker.sh | 72 ++++++++++ xWiki/docker-compose.yml | 70 ++++++++++ 20 files changed, 1153 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 dyndns/docker-compose.yml create mode 100644 excalidraw/docker-compose.yml create mode 100644 forgejo/docker-compose.yml create mode 100644 gantt_dockerfolder/docker-compose.yml create mode 100644 jellyfin/docker-compose.yml create mode 100644 kitchenowl/docker-compose.yml create mode 100755 matrix/docker-compose.yml create mode 100644 n8n/docker-compose.yml create mode 100644 openProject/docker-compose.yml create mode 100644 passbolt/docker-compose.yml create mode 100644 readme.md create mode 100644 sparkyFitness/docker-compose.yml create mode 100644 sure/docker-compose.yml create mode 100755 test/dns-check.sh create mode 100644 traefik/docker-compose.yml create mode 100644 wartungsskripte/update_befehl.sh create mode 100755 wartungsskripte/update_docker.sh create mode 100644 xWiki/docker-compose.yml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..77e4b1659afcd763c32187714c698c65341e4a16 GIT binary patch literal 6148 zcmeHKJ5EC}5S)b+kq}Z!N?(B+SW(~tT!2qNLLh>8P|&@Kb8$3gKh4vFE;P}sv>toC zW6M*#eG9=?|7MI{~mDeDF(da25%Go zJ)^_JcDTFU?VdeNCIzH`6p#W^KnmLv7PX~u+ z0f-BR!#Iy#g4jGj?1f_@BQ#4YF{xH9h9#ZxR(ZW}OiVhgnh&d+tvVEo+j)MAbXZT+ zC}^3Wq4i k#3;vHcsV|cq|9qR=YB676NAop(24pPa9w0l;J+340UYxd(*OVf literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3272896 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +.env.local +*.env \ No newline at end of file diff --git a/dyndns/docker-compose.yml b/dyndns/docker-compose.yml new file mode 100644 index 0000000..10dcc07 --- /dev/null +++ b/dyndns/docker-compose.yml @@ -0,0 +1,30 @@ +name: dyndns +services: + ddns-updater: + container_name: ddns-updater + environment: + - PERIOD=${DDNS_PERIOD} + - UPDATE_COOLDOWN=${DDNS_COOLDOWN} + - TZ=${TZ} + image: qmcgaw/ddns-updater:latest + restart: ${RESTART_POLICY} + volumes: + - type: bind + source: ${DDNS_DATA_PATH} + target: /updater/data + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + ports: + - "8000:8000" + network_mode: host + hostname: ddns-updater + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M \ No newline at end of file diff --git a/excalidraw/docker-compose.yml b/excalidraw/docker-compose.yml new file mode 100644 index 0000000..5780b6d --- /dev/null +++ b/excalidraw/docker-compose.yml @@ -0,0 +1,38 @@ +name: ${CONTAINER_NAME} +services: + excalidraw: + cpu_shares: 90 + command: [] + container_name: ${CONTAINER_NAME} + deploy: + resources: + limits: + memory: 15841M + hostname: ${CONTAINER_NAME} + image: excalidraw/excalidraw:latest + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + restart: ${RESTART_POLICY} + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + ports: [] + volumes: [] + devices: [] + cap_add: [] + environment: [] + networks: + - traefik + privileged: false +networks: + traefik: + name: traefik + external: true diff --git a/forgejo/docker-compose.yml b/forgejo/docker-compose.yml new file mode 100644 index 0000000..a8a8535 --- /dev/null +++ b/forgejo/docker-compose.yml @@ -0,0 +1,45 @@ +name: ${CONTAINER_NAME} + +networks: + forgejo: + external: false + traefik: + external: true + +services: + server: + image: codeberg.org/forgejo/forgejo:9.0 + container_name: ${CONTAINER_NAME} + restart: ${RESTART_POLICY} + environment: + - USER_UID=1000 + - USER_GID=1000 + networks: + - forgejo + - traefik + volumes: + - type: bind + source: ${DATA_PATH} + target: /data + bind: + create_host_path: true + + - type: bind + source: /etc/localtime + target: /etc/localtime + read_only: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + ports: + - '222:22' + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" diff --git a/gantt_dockerfolder/docker-compose.yml b/gantt_dockerfolder/docker-compose.yml new file mode 100644 index 0000000..bb11786 --- /dev/null +++ b/gantt_dockerfolder/docker-compose.yml @@ -0,0 +1,36 @@ +name: gantt +services: + gantt-app: + image: nginx:stable-alpine + container_name: ${CONTAINER_NAME} + volumes: + - type: bind + source: ${DIST_PATH} + target: /usr/share/nginx/html:ro + bind: + create_host_path: true + - type: bind + source: ${CONFIG_PATH} + target: /etc/nginx/conf.d/default.conf:ro + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + restart: ${RESTART_POLICY} + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + +networks: + traefik: + external: true diff --git a/jellyfin/docker-compose.yml b/jellyfin/docker-compose.yml new file mode 100644 index 0000000..0cc6b28 --- /dev/null +++ b/jellyfin/docker-compose.yml @@ -0,0 +1,41 @@ +services: + jellyfin: + image: jellyfin/jellyfin + container_name: ${CONTAINER_NAME} + restart: ${RESTART_POLICY} + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + environment: + - JELLYFIN_PublishedServerUrl=https://${SUBDOMAIN}.${DOMAIN} + volumes: + - type: bind + source: ${CACHE_PATH} + target: /cache + bind: + create_host_path: true + - type: bind + source: ${CONFIG_PATH} + target: /config + bind: + create_host_path: true + - type: bind + source: ${MEDIA_PATH} + target: /media + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" +networks: + traefik: + external: true diff --git a/kitchenowl/docker-compose.yml b/kitchenowl/docker-compose.yml new file mode 100644 index 0000000..3b1b10e --- /dev/null +++ b/kitchenowl/docker-compose.yml @@ -0,0 +1,59 @@ +services: + kitchenowl: + image: tombursch/kitchenowl:latest + container_name: ${CONTAINER_NAME} + restart: ${RESTART_POLICY} + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + environment: + - DATABASE_URL=${DB_PROVIDER}://${DB_USER}:${DB_PASSWORD}@db:${DB_PORT}/${DB_DATABASE} + - BACKEND_PROVIDER=${DB_BACKEND_PROVIDER} + networks: + - traefik + - internal_net + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + + - "traefik.http.middlewares.${ROUTER_NAME}-stripprefix.headers.customrequestheaders.X-Forwarded-Proto=http" + - "traefik.http.routers.${ROUTER_NAME}.middlewares=kitchenowl-compress" + - "traefik.http.middlewares.${ROUTER_NAME}-compress.compress=true" + depends_on: + - db + + db: + image: postgres:15-alpine + container_name: ${CONTAINER_NAME}_db + restart: ${RESTART_POLICY} + environment: + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=${DB_DATABASE} + volumes: + - type: bind + source: ${DB_PATH} + target: /var/lib/postgresql/data + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - internal_net + +networks: + traefik: + external: true + internal_net: + driver: bridge + diff --git a/matrix/docker-compose.yml b/matrix/docker-compose.yml new file mode 100755 index 0000000..cbf39b6 --- /dev/null +++ b/matrix/docker-compose.yml @@ -0,0 +1,60 @@ +name: matrix +services: + synapse: + container_name: ${CONTAINER_NAME} + hostname: ${CONTAINER_NAME} + image: matrixdotorg/synapse:latest + restart: ${RESTART_POLICY} + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M + + entrypoint: + - /bin/sh + - -c + - > + set -e + if [ ! -f /data/homeserver.yaml ]; then + echo "Generating initial configuration..." + /start.py generate + echo "" >> /data/homeserver.yaml + echo "enable_registration: true" >> /data/homeserver.yaml + echo "enable_registration_without_verification: true" >> /data/homeserver.yaml + fi + + echo "Starting Synapse..." + exec /start.py + + environment: + - SYNAPSE_REPORT_STATS=${SYNAPSE_REPORT_STATS} + - SYNAPSE_SERVER_NAME=${SUBDOMAIN}.${DOMAIN} + + networks: + - traefik + + volumes: + - type: bind + source: ${DATA_PATH} + target: /data + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + +networks: + traefik: + name: traefik + external: true \ No newline at end of file diff --git a/n8n/docker-compose.yml b/n8n/docker-compose.yml new file mode 100644 index 0000000..2668667 --- /dev/null +++ b/n8n/docker-compose.yml @@ -0,0 +1,49 @@ +name: n8n +services: + n8n: + image: docker.n8n.io/n8nio/n8n:latest + container_name: ${CONTAINER_NAME} + restart: ${RESTART_POLICY} + networks: + - traefik + + environment: + - NODE_ENV=production + - N8N_PORT=${CONTAINER_PORT} + - N8N_PROTOCOL=https + - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true + - N8N_HOST=${SUBDOMAIN}.${DOMAIN} + - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN}/ + - GENERIC_TIMEZONE=${TZ} + - TZ=${TZ} + + volumes: + - type: bind + source: ${DATA_PATH} + target: /home/node/.n8n + bind: + create_host_path: true + + - type: bind + source: ${FILES_PATH} + target: /files + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + +networks: + traefik: + name: traefik + external: true diff --git a/openProject/docker-compose.yml b/openProject/docker-compose.yml new file mode 100644 index 0000000..c8b02ba --- /dev/null +++ b/openProject/docker-compose.yml @@ -0,0 +1,44 @@ +name: op-xwiki-stack +networks: + frontend: + name: op_xwiki_frontend + backend: + name: op_xwiki_backend + traefik: + name: traefik + external: true + +services: + openproject_web: + image: openproject/openproject:17 + container_name: ${CONTAINER_NAME} + restart: ${RESTART_POLICY} + + environment: + - OPENPROJECT_SECRET_KEY_BASE=${OP_SECRET_KEY_BASE} + - SECRET_KEY_BASE=${SECRET_KEY_BASE} + + volumes: + - type: bind + source: ${DATA_PATH} + target: /var/openproject + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - frontend + - backend + - traefik + + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" \ No newline at end of file diff --git a/passbolt/docker-compose.yml b/passbolt/docker-compose.yml new file mode 100644 index 0000000..2aac094 --- /dev/null +++ b/passbolt/docker-compose.yml @@ -0,0 +1,72 @@ +name: passbolt +services: + passbolt-db: + image: mariadb:10.11 + container_name: ${CONTAINER_NAME}-db + restart: ${RESTART_POLICY} + environment: + - MYSQL_ROOT_PASSWORD=${DB_MYSQL_ROOT_PASSWORD} + - MYSQL_DATABASE=${DB_DATABASE} + - MYSQL_USER=${DB_USER} + - MYSQL_PASSWORD=${DB_PASSWORD} + volumes: + - type: bind + source: ${DB_PATH} + target: /var/lib/mysql + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - internal_net + + passbolt: + image: passbolt/passbolt:latest-ce + container_name: ${CONTAINER_NAME} + restart: ${RESTART_POLICY} + depends_on: + - passbolt-db + environment: + - APP_FULL_BASE_URL=https://${SUBDOMAIN}.${DOMAIN} + - DB_HOST=passbolt-db + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_DATABASE=${DB_DATABASE} + - EMAIL_DEFAULT_TRANSPORT=${EMAIL_TRANSPORT} + volumes: + - type: bind + source: ${PGP_PATH} + target: /etc/passbolt/gpg + bind: + create_host_path: true + - type: bind + source: ${JWT_PATH} + target: /etc/passbolt/jwt + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - traefik + - internal_net + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + +networks: + traefik: + name: traefik + external: true + internal_net: + driver: bridge \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d4c9f71 --- /dev/null +++ b/readme.md @@ -0,0 +1,192 @@ +# 🏠 Homeserver Setup + +Zentrale Dokumentation der Infrastruktur, Docker-Container und persistenten Daten meines Debian-basierten Homeservers. + +--- + +## 🗺️ Projektstruktur + +Das Setup trennt strikt zwischen persistenten Anwendungsdaten (`data/`) und den Docker-Konfigurationsdateien (`homeserver/`). + +```text + +## Tree +├── data +│   ├── backup_emmc +│   │   └── sys_ssh +│   ├── dyndns +│   │   └── ddns-data +│   ├── excalidraw +│   ├── forgejo +│   │   └── forgejo +│   ├── gantt +│   │   ├── dist +│   │   ├── env.d.ts +│   │   ├── gantt_dockerfolder +│   │   ├── index.html +│   │   ├── node_modules +│   │   ├── package.json +│   │   ├── package-lock.json +│   │   ├── public +│   │   ├── README.md +│   │   ├── src +│   │   ├── tsconfig.app.json +│   │   ├── tsconfig.json +│   │   ├── tsconfig.node.json +│   │   └── vite.config.ts +│   ├── jellyfin +│   │   ├── cache +│   │   ├── config +│   │   └── media +│   ├── kitchenowl +│   │   └── db_data +│   ├── letsencrypt +│   ├── matrix +│   │   └── data +│   ├── n8n +│   │   └── local-files +│   ├── openProject +│   ├── passbolt +│   │   ├── db-data +│   │   ├── gpg-keys +│   │   └── jwt-keys +│   ├── sure +│   │   ├── db +│   │   ├── enable_banking.pem +│   │   └── storage +│   ├── test +│   │   └── dns-check.sh +│   ├── traefik +│   │   ├── acme.json +│   │   └── letsencrypt +│   ├── wartungsskripte +│   │   ├── update_befehl.sh +│   │   └── update_docker.sh +│   └── xWiki +│   ├── postgres_data +│   └── xwiki_data +└── homeserver + │ └── docker-compose.yml + ├── excalidraw + │ └── docker-compose.yml + ├── forgejo + │ └── docker-compose.yml + ├── gantt_dockerfolder + │ └── docker-compose.yml + ├── jellyfin + │ └── docker-compose.yml + ├── kitchenowl + │ └── docker-compose.yml + ├── letsencrypt + ├── matrix + │ └── docker-compose.yml + ├── n8n + │ └── docker-compose.yml + ├── openProject + │ └── docker-compose.yml + ├── passbolt + │ └── docker-compose.yml + ├── readme.md + ├── sure + │ └── docker-compose.yml + ├── test + │ └── dns-check.sh + ├── traefik + │ └── docker-compose.yml + ├── wartungsskripte + │ ├── update_befehl.sh + │ └── update_docker.sh + └── xWiki + └── docker-compose.yml +``` +## 🚀 Infrastruktur & Netzwerk +Als zentraler Reverse Proxy wird Traefik eingesetzt. Er fängt alle Anfragen auf Port 80/443 ab, verteilt sie an die jeweiligen Docker-Container und erstellt automatisch SSL-Zertifikate via Let's Encrypt. + +## Wichtige URLs im Netzwerk: +🌐 Git-Server (Forgejo): `https://git.bybenji.de` (SSH-Port: 222) +🌐 Passwortmanager: `https://passbolt.bybenji.de` +🌐 Streaming: `https://jellyfin.bybenji.de` + +## 🛠️ Betrieb & Wartung +Container starten / stoppen +Um einen bestimmten Dienst zu starten, in den entsprechenden Ordner unter homeserver/ wechseln und ausführen: +```Bash +cd homeserver/jellyfin +docker compose up -d +``` + +### Updates +Unter `homeserver/wartungsskripte/` befinden sich Skripte zur Serverpflege. +`update_docker.sh`: Aktualisiert alle laufenden Docker-Container auf das neueste Image und räumt alte Images auf (docker system prune). +Aktuell ist ein Manuelles Wartungsfenster jeden Samstag um 21 Uhr gesetzt. +> [!NOTE] Automatisierung +Einrichtung als Cronjob für automatische Updates jeden Sonntag um 03:00 Uhr: +>```Bash +>0 3 * * 0 /bin/bash /mnt/PB960/data/gantt/homeserver/wartungsskripte/update_docker.>sh +>``` +## 📦 Backup-Strategie + +Um maximale Datensicherheit zu gewährleisten, werden alle Produktivdaten (`data/`) nach dem 3-2-1-Prinzip gesichert. + +### 1. Datenbasis & Lokales Backup (Kopie 1 & 2 - Medium 1) +* **Produktivdaten:** Befinden sich live auf dem Produktivsystem. +* **Lokales Backup:** Täglich inkrementelle und monatlich vollständige Backups werden auf einem dedizierten **NAS** gesichert, das sich physisch im selben Serverrack befindet. + +### 2. Offsite & Standby-Server (Kopie 3 - Standort extern) +* **Backup-Server:** Eine vollständige Kopie wird an einen geografisch getrennten Standort gesichert. +* **Hot-Standby:** Da dieser Server denselben Techstack besitzt, kann er bei Wartungsarbeiten oder Ausfällen des Hauptservers direkt als temporäres Produktivsystem zugeschaltet werden. + + + +> [!NOTE] +> **Offene Baustelle: Das 2. Speichermedium** +> Für die vollständige Erfüllung des 3-2-1-Prinzips fehlt noch das zweite, alternative Speichermedium (z. B. eine rotierende externe USB-Festplatte (Air-Gapped) oder ein verschlüsselter S3-Cloud-Speicher wie Hetzner Storage Box). + +### 🖥️ System-Backup +* Unabhängig von den Anwendungsdaten werden Abbilder des eMMC-Speichers sowie die systemrelevanten SSH-Schlüssel unter `data/backup_emmc/sys_ssh/` vorgehalten. + + +### 💾 Speicher-Architektur & NAS + +Sowohl das lokale Produktiv-NAS als auch das Offsite-NAS sind speicherseitig identisch aufgebaut, um maximale Redundanz und volle Kompatibilität im Failover-Fall zu gewährleisten. + +### 📍 Phase 1: Start-Setup (Aktueller Stand) +* **Main-Server:** 1x 14 TB Seagate Exos (Single Drive – 14 TB Netto-Nutzdaten) +* **Offsite-Server:** 1x 14 TB Seagate Exos (Single Drive – 14 TB Backup-Ziel) +* **Sicherheit:** Die Datenkomponente wird per täglichem Skript/Replikation auf den Offsite-Server gesichert. Es besteht in dieser Phase noch keine lokale Festplatten-Redundanz (RAID) beim Ausfall einer Platte vor Ort. + +### 🚀 Phase 2: RAID 5 Erweiterung (Zukunft) +Sobald zusätzlicher Speicher oder lokale Ausfallsicherheit benötigt wird, wird jedes System um 2 weitere 14 TB Platten ergänzt: +* **Konfiguration:** Migration auf ein **3-Platten-RAID-5** pro Server. +* **Neuer Speicher:** **28 TB Netto-Nutzdaten** pro Server bei einer Fehlertoleranz von 1 ausfallenden Festplatte pro System. + +## 🖥️ Offsite-Server & Techstack + +Der Offsite-Server dient primär als geografisch getrenntes Backup-Ziel, ist jedoch hardware- und softwareseitig als **Hot-Standby** konzipiert. Bei einem Totalausfall oder Wartungsarbeiten am Hauptstandort kann er die primären Dienste temporär übernehmen. + +### 🌐 Virtualisierungs-Plattform (Proxmox VE) +Als Betriebssystem-Unterbau wird **Proxmox VE (Virtual Environment)** eingesetzt. Dies ermöglicht eine strikte Kapselung und einfache Verwaltung der Ressourcen: + +* **Hypervisor:** Typ-1-Bare-Metal-Hypervisor auf Debian-Basis. +* **LXC (Linux Containers):** Für ressourcensparende Anwendungen (wie den Docker-Host). +* **VMs (Virtuelle Maschinen):** Für isolierte Systeme, die einen eigenen Kernel oder ein anderes OS benötigen. + +### 📦 Container-Architektur (Der "Bauchtanz") +Um den exakt gleichen Techstack wie auf dem Main-Server zu spiegeln, läuft innerhalb von Proxmox ein dedizierter LXC-Container (oder eine VM), der als **Docker-Host** fungiert. + +* **Docker & Compose:** Innerhalb dieser Instanz wird die identische `homeserver/`-Struktur per Git synchronisiert gehalten. +* **Dienste-Status:** Die Docker-Container (Nextcloud, Immich, Jellyfin etc.) sind im Normalbetrieb gestoppt oder laufen im reinen Standby-Modus, um Ressourcen zu schonen, stehen aber für ein sofortiges Failover bereit. + +### 🔄 Replikation & Backup-Infrastruktur (Phase 1) +In der aktuellen Phase (Single-Drive) sieht das Zusammenspiel wie folgt aus: + +1. **ZFS / Local Storage:** Proxmox verwaltet die einzelne 14 TB Seagate Exos Festplatte als lokalen Speicherpool. +2. **Pull-/Push-Backups:** Der Hauptserver schiebt seine Daten (z. B. via `rsync`, `rclone` oder BorgBackup) verschlüsselt über das Netzwerk auf den Offsite-Proxmox-Speicher. + + +--- + +# Visualisierung +├── für einen Ordner oder eine Datei, wenn danach noch weitere Elemente auf derselben Ebene folgen. +└── für das letzte Element auf einer Ebene (das „Eckstück“). +│ für Linien, die an Unterordnern vorbeiführen (wichtig für die richtige Einrückung). \ No newline at end of file diff --git a/sparkyFitness/docker-compose.yml b/sparkyFitness/docker-compose.yml new file mode 100644 index 0000000..e1c5ed4 --- /dev/null +++ b/sparkyFitness/docker-compose.yml @@ -0,0 +1,110 @@ +services: + sparkyfitness-db: + image: postgres:18.3-alpine + container_name: ${CONTAINER_NAME}-db + restart: ${RESTART_POLICY} + environment: + POSTGRES_DB: ${SPARKY_FITNESS_DB_NAME} + POSTGRES_USER: ${SPARKY_FITNESS_DB_USER} + POSTGRES_PASSWORD: ${SPARKY_FITNESS_DB_PASSWORD} + PUID: 1000 + GUID: 1000 + volumes: + - type: bind + source: ${DB_PATH:-./postgresql} + target: /var/lib/postgresql + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - sparkyfitness-network + + sparkyfitness-server: + image: codewithcj/sparkyfitness_server:latest + environment: + SPARKY_FITNESS_LOG_LEVEL: ${SPARKY_FITNESS_LOG_LEVEL} + ALLOW_PRIVATE_NETWORK_CORS: ${ALLOW_PRIVATE_NETWORK_CORS:-false} + SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS: ${SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS} + SPARKY_FITNESS_PUBLIC_API_DOCS: ${SPARKY_FITNESS_PUBLIC_API_DOCS:-false} + SPARKY_FITNESS_DB_USER: ${SPARKY_FITNESS_DB_USER:-sparky} + SPARKY_FITNESS_DB_HOST: ${SPARKY_FITNESS_DB_HOST:-sparkyfitness-db} + SPARKY_FITNESS_DB_NAME: ${SPARKY_FITNESS_DB_NAME} + SPARKY_FITNESS_DB_PASSWORD: ${SPARKY_FITNESS_DB_PASSWORD} + SPARKY_FITNESS_APP_DB_USER: ${SPARKY_FITNESS_APP_DB_USER:-sparkyapp} + SPARKY_FITNESS_APP_DB_PASSWORD: ${SPARKY_FITNESS_APP_DB_PASSWORD} + SPARKY_FITNESS_DB_PORT: 5432 + SPARKY_FITNESS_API_ENCRYPTION_KEY: ${SPARKY_FITNESS_API_ENCRYPTION_KEY} + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + SPARKY_FITNESS_FRONTEND_URL: ${SPARKY_FITNESS_FRONTEND_URL:-http://0.0.0.0} + SPARKY_FITNESS_DISABLE_SIGNUP: ${SPARKY_FITNESS_DISABLE_SIGNUP} + SPARKY_FITNESS_ADMIN_EMAIL: ${SPARKY_FITNESS_ADMIN_EMAIL} + SPARKY_FITNESS_EMAIL_HOST: ${SPARKY_FITNESS_EMAIL_HOST} + SPARKY_FITNESS_EMAIL_PORT: ${SPARKY_FITNESS_EMAIL_PORT} + SPARKY_FITNESS_EMAIL_SECURE: ${SPARKY_FITNESS_EMAIL_SECURE} + SPARKY_FITNESS_EMAIL_USER: ${SPARKY_FITNESS_EMAIL_USER} + SPARKY_FITNESS_EMAIL_PASS: ${SPARKY_FITNESS_EMAIL_PASS} + SPARKY_FITNESS_EMAIL_FROM: ${SPARKY_FITNESS_EMAIL_FROM} + GARMIN_MICROSERVICE_URL: http://sparkyfitness-garmin:8000 + HTTP_PROXY: ${HTTP_PROXY:-} + HTTPS_PROXY: ${HTTPS_PROXY:-} + NO_PROXY: ${NO_PROXY:-} + PUID: 1000 + GUID: 1000 + networks: + - sparkyfitness-network + restart: always + depends_on: + - sparkyfitness-db + volumes: + - type: bind + source: ${SERVER_BACKUP_PATH:-./backup} + target: /app/SparkyFitnessServer/backup + bind: + create_host_path: true + - type: bind + source: ${SERVER_UPLOADS_PATH:-./uploads} + target: /app/SparkyFitnessServer/uploads + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + sparkyfitness-frontend: + image: codewithcj/sparkyfitness:latest + environment: + SPARKY_FITNESS_FRONTEND_URL: ${SPARKY_FITNESS_FRONTEND_URL} + SPARKY_FITNESS_SERVER_HOST: ${CONTAINER_NAME}-server + SPARKY_FITNESS_SERVER_PORT: 3010 + PUID: 1000 + GUID: 1000 + networks: + - sparkyfitness-network + - traefik + restart: ${RESTART_POLICY} + depends_on: + - sparkyfitness-server + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + + - "traefik.http.routers.${ROUTER_NAME}.middlewares=sparkyfitness-headers" + - "traefik.http.middlewares.${ROUTER_NAME}-headers.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.middlewares.${ROUTER_NAME}-headers.headers.customrequestheaders.X-Forwarded-Host=fitness.bybenji.de" + +networks: + sparkyfitness-network: + driver: bridge + traefik: + external: true diff --git a/sure/docker-compose.yml b/sure/docker-compose.yml new file mode 100644 index 0000000..efcf1fd --- /dev/null +++ b/sure/docker-compose.yml @@ -0,0 +1,149 @@ +name: sure +networks: + traefik: + name: traefik + external: true + internal_net: + name: sure_internal_net + driver: bridge + +services: + sure-db: + image: postgres:16-alpine + container_name: ${CONTAINER_NAME}-db + restart: ${RESTART_POLICY} + environment: + - POSTGRES_DB=${DB_DATABASE} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_USER=${DB_USER} + volumes: + - type: bind + source: ${DB_PATH} + target: /var/lib/postgresql/data + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - internal_net + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M + + sure-redis: + image: redis:7-alpine + container_name: ${CONTAINER_NAME}-redis + restart: ${RESTART_POLICY} + volumes: + - type: bind + source: ${REDIS_PATH} + target: /data + bind: + create_host_path: true + networks: + - internal_net + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M + + web: + image: ghcr.io/we-promise/sure:stable + container_name: ${CONTAINER_NAME}-web + restart: ${RESTART_POLICY} + depends_on: + sure-db: + condition: service_started + sure-redis: + condition: service_started + environment: + - SELF_HOSTED=${SELF_HOSTED} + - ALLOW_REGISTRATION=${ALLOW_REGISTRATION} + - SECRET_KEY_BASE=${SECRET_KEY_BASE} + - RAILS_ASSUME_SSL=${RAILS_ASSUME_SSL} + - RAILS_FORCE_SSL=${RAILS_FORCE_SSL} + - DB_HOST=sure-db + - POSTGRES_DB=${DB_DATABASE} + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - REDIS_URL=${REDIS_URL} + volumes: + - type: bind + source: ${STORAGE_PATH} + target: /rails/storage + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - traefik + - internal_net + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" + + worker: + image: ghcr.io/we-promise/sure:stable + container_name: ${CONTAINER_NAME}-worker + restart: ${RESTART_POLICY} + command: + - /rails/bin/docker-entrypoint + - ./bin/bundle + - exec + - sidekiq + depends_on: + sure-db: + condition: service_started + sure-redis: + condition: service_started + environment: + - APP_URL=https://${SUBDOMAIN}.${DOMAIN} + - DB_HOST=sure-db + - POSTGRES_DB=${DB_DATABASE} + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - REDIS_URL=${REDIS_URL} + - SECRET_KEY_BASE=${SECRET_KEY_BASE} + volumes: + - type: bind + source: ${STORAGE_PATH} + target: /rails/storage + bind: + create_host_path: true + - type: bind + source: ${BANKING_PATH} + target: /rails/config/enable_banking.pem + read_only: true + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - internal_net + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M \ No newline at end of file diff --git a/test/dns-check.sh b/test/dns-check.sh new file mode 100755 index 0000000..1eedf66 --- /dev/null +++ b/test/dns-check.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +while true; do + clear + echo "=== Nameserver-Überwachung für bybenji.de ===" + echo "Letzte Prüfung: $(date +%H:%M:%S)" + echo "----------------------------------------" + + NS_INFO=$(nslookup -type=ns budget.bybenji.de 8.8.8.8 2>&1) + echo "$NS_INFO" + + if echo "$NS_INFO" | grep -q "desec.io"; then + echo -e "\n\a🚀🚀🚀 ERFOLG! Die Nameserver wurden auf deSEC umgestellt! 🚀🚀🚀" + break + fi + + echo -e "\n[Warten auf deSEC...] Nächste Prüfung in 30 Sekunden. (Abbrechen mit STRG+C)" + sleep 30 +done diff --git a/traefik/docker-compose.yml b/traefik/docker-compose.yml new file mode 100644 index 0000000..7be4a72 --- /dev/null +++ b/traefik/docker-compose.yml @@ -0,0 +1,63 @@ +name: traefik + +networks: + traefik: + name: traefik + # external: true -> Entfernt, damit dieser Stack das Netzwerk global auf dem Server erstellt! + +services: + traefik: + image: traefik:latest + container_name: ${CONTAINER_NAME} + hostname: traefik + restart: ${RESTART_POLICY} + cpu_shares: 90 + deploy: + resources: + limits: + memory: 15841M + + command: + - --api.insecure=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.websecure.address=:443 + - --certificatesresolvers.myresolver.acme.dnschallenge=true + - --certificatesresolvers.myresolver.acme.dnschallenge.provider=desec + - --certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=10 + - --certificatesresolvers.myresolver.acme.email=${CERT_RESOLVER_EMAIL} + - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json + - --certificatesresolvers.myresolver.acme.dnschallenge.resolvers=${DNS_RESOLVERS} + + environment: + - DESEC_TOKEN=${DESEC_TOKEN} + - DESEC_TTL=${DESEC_TTL} + - DESEC_POLLING_INTERVAL=${DESEC_POLLING_INTERVAL} + - DESEC_PROPAGATION_TIMEOUT=${DESEC_PROPAGATION_TIMEOUT} + + ports: + - "${HTTP_PORT}:80" + - "${HTTPS_PORT}:443" + - "${DASHBOARD_PORT}:8080" + + volumes: + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + read_only: true + - type: bind + source: ${LETSENCRYPT_PATH} + target: /letsencrypt + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + networks: + - traefik \ No newline at end of file diff --git a/wartungsskripte/update_befehl.sh b/wartungsskripte/update_befehl.sh new file mode 100644 index 0000000..40af19d --- /dev/null +++ b/wartungsskripte/update_befehl.sh @@ -0,0 +1 @@ +sudo ./update_docker.sh diff --git a/wartungsskripte/update_docker.sh b/wartungsskripte/update_docker.sh new file mode 100755 index 0000000..44f1982 --- /dev/null +++ b/wartungsskripte/update_docker.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +PROJECTS=( + "/mnt/PB960/homeserver/dyndns" + "/mnt/PB960/homeserver/excalidraw" + "/mnt/PB960/homeserver/forgejo" + "/mnt/PB960/homeserver/gantt_dockerfolder" + "/mnt/PB960/homeserver/jellyfin" + "/mnt/PB960/homeserver/kitchenowl" + "/mnt/PB960/homeserver/matrix" + "/mnt/PB960/homeserver/n8n" + "/mnt/PB960/homeserver/openProject" + "/mnt/PB960/homeserver/passbolt" + "/mnt/PB960/homeserver/sparkyFitness" + "/mnt/PB960/homeserver/sure" + "/mnt/PB960/homeserver/traefik" + "/mnt/PB960/homeserver/xWiki" +) + +echo "==========================================" +echo " STARRTE AUTOMATISCHES DOCKER-UPDATE " +echo " Datum: $(date '+%Y-%m-%d %H:%M:%S')" +echo "==========================================" + +# Prüfen, ob das Skript als Root ausgeführt wird +if [ "$EUID" -ne 0 ]; then + echo "[FEHLER] Bitte starte das Skript mit 'sudo'!" + exit 1 +fi + +# Aktuellste Version des Repos holen +echo "[1/2] Aktualisiere Git-Repository (Homeserver)..." +cd /mnt/PB960/homeserver && git pull + +echo "[2/2] Aktualisiere Git-Repository (Gantt)..." +cd /mnt/PB960/data/gantt && git pull + +for TARGET_DIR in "${PROJECTS[@]}"; do + if [ -d "$TARGET_DIR" ]; then + echo "" + echo "------------------------------------------" + echo "[INFO] Verarbeite Projekt in: $TARGET_DIR" + echo "------------------------------------------" + + cd "$TARGET_DIR" || continue + + # 1. Container sauber stoppen + echo "[1/4] Stoppe laufende Container..." + docker compose down + + # 2. Neueste Versionen/Images aus dem Netz ziehen + echo "[2/4] Ziehe neueste Docker-Images (Pull)..." + docker compose pull + + # 3. Container im Hintergrund neu starten (erzwingt das Update) + echo "[3/4] Starte Container neu (erzwinge Update)..." + docker compose up -d --build --force-recreate + + # 4. Alte, ungenutzte Image-Reste löschen (spart Speicherplatz) + echo "[4/4] Bereinige alte Image-Leichen..." + docker image prune -f + + echo "[ERFOLG] Projekt $TARGET_DIR wurde aktualisiert!" + else + echo "[WARNUNG] Verzeichnis $TARGET_DIR existiert nicht. Überspringe..." + fi +done + +echo "" +echo "==========================================" +echo " ALLE UPDATES ERFOLGREICH DURCHGEFÜHRT! " +echo "==========================================" diff --git a/xWiki/docker-compose.yml b/xWiki/docker-compose.yml new file mode 100644 index 0000000..d15a391 --- /dev/null +++ b/xWiki/docker-compose.yml @@ -0,0 +1,70 @@ +name: xwiki + +networks: + frontend: + name: op_xwiki_frontend + backend: + name: op_xwiki_backend + traefik: + name: traefik + external: true + +services: + xwiki_db: + image: postgres:15-alpine + container_name: ${CONTAINER_NAME}_postgres + restart: ${RESTART_POLICY} + environment: + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=${DB_DATABASE} + volumes: + - type: bind + source: ${DB_PATH} + target: /var/lib/postgresql/data + bind: + create_host_path: true + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - backend + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + + xwiki_web: + image: xwiki:lts-postgres-tomcat + container_name: ${CONTAINER_NAME}_web + restart: ${RESTART_POLICY} + environment: + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=${DB_DATABASE} + - DB_HOST=xwiki_db + - JAVA_OPTS=-Xmx1024m + volumes: + - type: bind + source: ${DATA_PATH} + target: /usr/local/xwiki + bind: + create_host_path: true + networks: + - frontend + - backend + - traefik + depends_on: + xwiki_db: + condition: service_healthy + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" + - "traefik.http.routers.${ROUTER_NAME}.tls=true" + - "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=${CERTIFICATE_RESOLVER}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_PORT}" + - "traefik.docker.network=${TRAEFIK_NETWORK}" \ No newline at end of file