From 2ffcbb5f6027d0f042122a161b8f4e85fa820152 Mon Sep 17 00:00:00 2001 From: Alexander Doerflinger Date: Fri, 20 Feb 2026 09:10:27 +0100 Subject: [PATCH] Initial commit --- .env.example | 37 ++ .gitignore | 19 + README.md | 682 +++++++++++++++++++++++++++++++ docker-compose.yml | 121 ++++++ homeassistant/automations.yaml | 1 + homeassistant/configuration.yaml | 36 ++ homeassistant/scenes.yaml | 1 + homeassistant/scripts.yaml | 1 + scripts/backup.sh | 88 ++++ scripts/restore.sh | 82 ++++ touch | 0 11 files changed, 1068 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 homeassistant/automations.yaml create mode 100644 homeassistant/configuration.yaml create mode 100644 homeassistant/scenes.yaml create mode 100644 homeassistant/scripts.yaml create mode 100644 scripts/backup.sh create mode 100644 scripts/restore.sh create mode 100644 touch diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0318e98 --- /dev/null +++ b/.env.example @@ -0,0 +1,37 @@ +# ============================================================ +# Home Server Environment Configuration +# Copy this file to .env and fill in your values: +# cp .env.example .env +# ============================================================ + +# --- General --- +TZ=Europe/Berlin +PUID=1000 +PGID=1000 + +# --- Domain Names --- +DOMAIN_HA=ha.doerflingers.com +DOMAIN_GITEA=git.doerflingers.com +DOMAIN_HOME=home.doerflingers.com + +# --- WireGuard VPN --- +# Your public domain or DynDNS hostname for VPN connections +WG_SERVERURL=home.doerflingers.com +# Comma-separated list of client configs to generate (names or count) +WG_PEERS=phone,laptop,tablet +# Internal subnet for VPN clients +WG_INTERNAL_SUBNET=10.13.13.0 +# UDP port for WireGuard (must match FritzBox port forwarding) +WG_PORT=51820 + +# --- Gitea --- +GITEA_SSH_PORT=2222 +# Secret key for Gitea (generate with: openssl rand -hex 32) +GITEA_SECRET_KEY=CHANGE_ME_GENERATE_WITH_openssl_rand_hex_32 +# Internal token (generate with: openssl rand -hex 32) +GITEA_INTERNAL_TOKEN=CHANGE_ME_GENERATE_WITH_openssl_rand_hex_32 + +# --- Nginx Proxy Manager --- +# Default admin login (change after first login!) +# Email: admin@example.com +# Password: changeme diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e85603d --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Gitea data +gitea/data/ + +# Nginx Proxy Manager data +nginx-proxy-manager/data/ +nginx-proxy-manager/letsencrypt/ + +# WireGuard config (contains private keys!) +wireguard/config/ + +# Environment file (contains secrets) +.env + +# Backups +backups/ + +# OS files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..81ba2f8 --- /dev/null +++ b/README.md @@ -0,0 +1,682 @@ +# 🏠 Home Server β€” Docker Compose Stack + +Self-hosted infrastructure running **Home Assistant**, **Gitea**, and **WireGuard VPN** behind **Nginx Proxy Manager** with **Let's Encrypt SSL** and **IONOS DynDNS**. + +| Service | Domain | Internal Port | Purpose | +|---------|--------|--------------|---------| +| **Nginx Proxy Manager** | β€” (admin: `:81`) | 80, 443, 81 | Reverse proxy + SSL | +| **Home Assistant** | `ha.doerflingers.com` | 8123 | Smart home | +| **Gitea** | `git.doerflingers.com` | 3000 (web), 22 (SSH) | Git hosting | +| **WireGuard** | `home.doerflingers.com` | 51820/UDP | VPN | + +--- + +## Table of Contents + +1. [Hardware Requirements](#1-hardware-requirements) +2. [Prerequisites β€” Ubuntu 24.04 Setup](#2-prerequisites--ubuntu-2404-setup) +3. [IONOS DynDNS Setup](#3-ionos-dyndns-setup) +4. [FritzBox Port Forwarding](#4-fritzbox-port-forwarding) +5. [Project Setup](#5-project-setup) +6. [Launch the Stack](#6-launch-the-stack) +7. [Nginx Proxy Manager Configuration](#7-nginx-proxy-manager-configuration) +8. [Service-Specific Setup](#8-service-specific-setup) +9. [WireGuard Client Setup](#9-wireguard-client-setup) +10. [Backup & Restore](#10-backup--restore) +11. [Migration to New Hardware](#11-migration-to-new-hardware) +12. [Troubleshooting](#12-troubleshooting) +13. [Architecture Overview](#13-architecture-overview) + +--- + +## 1. Hardware Requirements + +This stack is designed to run on modest hardware. Here's the assessment for the target laptop: + +| Resource | Available | Used (est.) | Status | +|----------|-----------|-------------|--------| +| CPU | i5-3210M (2C/4T, 2.5 GHz) | ~5-15% idle | βœ… | +| RAM | 3.7 GB + 3.7 GB Swap | ~1.3 GB | βœ… | +| Disk | 500 GB HDD | ~5 GB base + data | βœ… | + +> **All services coexist without interference.** HTTP traffic is multiplexed by hostname through the reverse proxy, WireGuard uses a separate UDP port. + +--- + +## 2. Prerequisites β€” Ubuntu 24.04 Setup + +Since this is a fresh Ubuntu 24.04 install, we need to install all required packages from scratch. + +### 2.1 System Update + +```bash +sudo apt update && sudo apt upgrade -y +``` + +### 2.2 Install Essential Packages + +```bash +sudo apt install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release \ + python3 \ + python3-pip \ + python3-venv \ + git \ + cron +``` + +### 2.3 Install Docker Engine + +Follow the official Docker installation for Ubuntu. Do **not** use the `docker.io` snap package. + +```bash +# Add Docker's official GPG key +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc + +# Add the repository +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "${VERSION_CODENAME}") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +# Install Docker Engine + Compose plugin +sudo apt update +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` + +### 2.4 Post-Installation β€” Run Docker Without `sudo` + +```bash +sudo usermod -aG docker $USER +newgrp docker +``` + +Verify the installation: + +```bash +docker --version +docker compose version +``` + +### 2.5 Prevent Laptop Suspend on Lid Close + +Since this is a laptop acting as a server, you want it to stay on when the lid is closed: + +```bash +sudo sed -i 's/#HandleLidSwitch=suspend/HandleLidSwitch=ignore/' /etc/systemd/logind.conf +sudo sed -i 's/#HandleLidSwitchExternalPower=suspend/HandleLidSwitchExternalPower=ignore/' /etc/systemd/logind.conf +sudo systemctl restart systemd-logind +``` + +### 2.6 (Optional) Enable WireGuard Kernel Module + +WireGuard often needs the kernel module pre-loaded: + +```bash +sudo apt install -y wireguard-tools +sudo modprobe wireguard +# Make it persistent across reboots +echo "wireguard" | sudo tee /etc/modules-load.d/wireguard.conf +``` + +--- + +## 3. IONOS DynDNS Setup + +Your domains are registered at IONOS. Since your home internet has a dynamic IP address, you need DynDNS to keep the DNS records updated automatically. + +### 3.1 Install the IONOS DynDNS Client + +```bash +# Create a virtual environment (required on Ubuntu 24.04 which uses PEP 668) +sudo python3 -m venv /opt/dyndns-venv +sudo /opt/dyndns-venv/bin/pip install domain-connect-dyndns +``` + +### 3.2 Register Each Domain + +You need to run the setup command **once per domain/subdomain**. Each will give you a URL to open in your browser to authorize IONOS. + +```bash +# Domain for Home Assistant +sudo /opt/dyndns-venv/bin/domain-connect-dyndns setup --domain ha.doerflingers.com +# β†’ A URL is printed. Open it in your browser. +# β†’ Log in to IONOS, click "Erlauben" (Allow). +# β†’ Copy the code shown and paste it back into the terminal. + +# Domain for Gitea +sudo /opt/dyndns-venv/bin/domain-connect-dyndns setup --domain git.doerflingers.com +# β†’ Same process: open URL, authorize, paste code. + +# Domain for Home / VPN +sudo /opt/dyndns-venv/bin/domain-connect-dyndns setup --domain home.doerflingers.com +# β†’ Same process: open URL, authorize, paste code. +``` + +### 3.3 Test the Update + +```bash +sudo /opt/dyndns-venv/bin/domain-connect-dyndns update --all +``` + +You should see output like: + +``` +Read ha.doerflingers.com config. +IP x.x.x.x found in A record +New IP: x.x.x.x +A record up to date. +``` + +### 3.4 Set Up Automatic Updates via Cron + +The cron job runs every 5 minutes to check for IP changes and update DNS if needed. + +```bash +# Edit the root crontab +sudo crontab -e +``` + +Add this line at the bottom: + +```cron +*/5 * * * * /usr/bin/flock -n /tmp/dyndns-update.lck /opt/dyndns-venv/bin/domain-connect-dyndns update --all >> /var/log/dyndns-update.log 2>&1 +``` + +> **Note:** We use every 5 minutes instead of every 1 minute β€” it's unlikely your IP changes more frequently, and it reduces system load. The `flock` prevents overlapping runs. + +### 3.5 Verify Cron is Running + +```bash +sudo systemctl enable cron +sudo systemctl start cron +# Check it's scheduled +sudo crontab -l +``` + +--- + +## 4. FritzBox Port Forwarding + +You need to forward specific ports from your FritzBox router to the laptop. + +### 4.1 Find the Laptop's Local IP Address + +```bash +ip addr show | grep "inet " | grep -v 127.0.0.1 +``` + +Note the IP address (e.g., `192.168.178.XX`). + +> **Tip:** In FritzBox, go to **Heimnetz β†’ Netzwerk β†’ [Your Laptop]** and enable **"Diesem NetzwerkgerΓ€t immer die gleiche IPv4-Adresse zuweisen"** (always assign the same IP) to prevent the IP from changing. + +### 4.2 Configure Port Forwarding + +In the FritzBox admin UI (`http://fritz.box`), go to **Internet β†’ Freigaben β†’ Portfreigaben** and add the following rules: + +| Service | Protocol | External Port | Internal Port | Internal IP | Required | +|---------|----------|---------------|---------------|-------------|----------| +| HTTP | TCP | 80 | 80 | 192.168.178.XX | βœ… Yes (Let's Encrypt HTTP-01 challenge) | +| HTTPS | TCP | 443 | 443 | 192.168.178.XX | βœ… Yes (all web services) | +| WireGuard | UDP | 51820 | 51820 | 192.168.178.XX | βœ… Yes (VPN) | +| Gitea SSH | TCP | 2222 | 2222 | 192.168.178.XX | ⚑ Optional (for `git clone` via SSH) | + +> **⚠️ Security Note:** Do **NOT** forward port 81. The Nginx Proxy Manager admin UI should only be accessible from your local network (or via WireGuard VPN). + +--- + +## 5. Project Setup + +### 5.1 Clone or Copy This Repository + +```bash +# If using git +git clone ~/home-server +cd ~/home-server + +# Or simply copy the files to your laptop +# and navigate to the directory +cd ~/home-server +``` + +### 5.2 Create Your Environment File + +```bash +cp .env.example .env +``` + +Edit `.env` with your preferred editor: + +```bash +nano .env +``` + +**Required changes:** + +| Variable | What to set | +|----------|-------------| +| `TZ` | Your timezone (default: `Europe/Berlin`) | +| `WG_SERVERURL` | `home.doerflingers.com` (already set) | +| `WG_PEERS` | Comma-separated list of your VPN client names | +| `GITEA_SECRET_KEY` | Run `openssl rand -hex 32` and paste result | +| `GITEA_INTERNAL_TOKEN` | Run `openssl rand -hex 32` and paste result | + +### 5.3 Set Script Permissions + +```bash +chmod +x scripts/backup.sh scripts/restore.sh +``` + +--- + +## 6. Launch the Stack + +### 6.1 Start All Services + +```bash +docker compose up -d +``` + +This will pull all images (first run may take 5-10 minutes on a slow connection) and start the containers. + +### 6.2 Verify Everything is Running + +```bash +docker compose ps +``` + +Expected output: + +``` +NAME STATUS PORTS +gitea Up 0.0.0.0:2222->22/tcp, 3000/tcp +homeassistant Up 8123/tcp +nginx-proxy-manager Up (healthy) 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:81->81/tcp +wireguard Up 0.0.0.0:51820->51820/udp +``` + +### 6.3 Check Logs (if something goes wrong) + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f homeassistant +docker compose logs -f gitea +docker compose logs -f wireguard +docker compose logs -f nginx-proxy-manager +``` + +--- + +## 7. Nginx Proxy Manager Configuration + +### 7.1 Access the Admin UI + +Open your browser and go to: + +``` +http://:81 +``` + +Default credentials: + +| Field | Value | +|-------|-------| +| Email | `admin@example.com` | +| Password | `changeme` | + +> **You will be prompted to change these on first login. Do it immediately.** + +### 7.2 Add Proxy Host for Home Assistant + +1. Go to **Hosts β†’ Proxy Hosts β†’ Add Proxy Host** +2. Fill in the **Details** tab: + + | Field | Value | + |-------|-------| + | Domain Names | `ha.doerflingers.com` | + | Scheme | `http` | + | Forward Hostname / IP | `homeassistant` | + | Forward Port | `8123` | + | Websockets Support | βœ… **Enable** (required for HA) | + +3. Go to the **SSL** tab: + + | Field | Value | + |-------|-------| + | SSL Certificate | Request a new SSL Certificate | + | Force SSL | βœ… Enable | + | HTTP/2 Support | βœ… Enable | + | Email for Let's Encrypt | your-email@example.com | + | Agree to ToS | βœ… Yes | + +4. Click **Save**. + +### 7.3 Add Proxy Host for Gitea + +Same process, with these values: + +| Field | Value | +|-------|-------| +| Domain Names | `git.doerflingers.com` | +| Scheme | `http` | +| Forward Hostname / IP | `gitea` | +| Forward Port | `3000` | +| Websockets Support | βœ… Enable | + +SSL tab: same as above (request new cert, force SSL, HTTP/2). + +### 7.4 Add Proxy Host for Home / Landing Page (Optional) + +If you want `home.doerflingers.com` to point somewhere (e.g., a landing page or redirect to Home Assistant): + +| Field | Value | +|-------|-------| +| Domain Names | `home.doerflingers.com` | +| Scheme | `http` | +| Forward Hostname / IP | `homeassistant` | +| Forward Port | `8123` | + +Or use a **Redirection Host** to redirect `home.doerflingers.com` β†’ `ha.doerflingers.com`. + +> **Note:** `home.doerflingers.com` is primarily used as the WireGuard server URL. It doesn't need a proxy host unless you want it to serve a web page too. + +--- + +## 8. Service-Specific Setup + +### 8.1 Home Assistant β€” First Launch + +1. Navigate to `https://ha.doerflingers.com` (or `http://:8123` from LAN). +2. The onboarding wizard will guide you through: + - Creating your admin account + - Setting your home location + - Discovering devices on your network +3. Home Assistant is ready to use! + +> **Note on device discovery:** In container mode (non-host network), some discovery protocols (mDNS, SSDP) may not work. You can add integrations manually via **Settings β†’ Devices & Services β†’ Add Integration**. + +### 8.2 Gitea β€” First Launch + +1. Navigate to `https://git.doerflingers.com`. +2. The initial configuration page will appear. Most settings are pre-configured via environment variables. Verify: + + | Setting | Value | + |---------|-------| + | Site Title | Your choice | + | Repository Root Path | `/data/git/repositories` | + | SQLite Database Path | `/data/gitea/gitea.db` | + | SSH Server Domain | `git.doerflingers.com` | + | SSH Port | `2222` | + | Gitea Base URL | `https://git.doerflingers.com/` | + +3. Create your admin account in the **Administrator Account Settings** section. +4. Click **Install Gitea**. + +--- + +## 9. WireGuard Client Setup + +### 9.1 Locate Client Configurations + +After the WireGuard container starts, it automatically generates config files for each peer defined in `WG_PEERS`: + +```bash +# List generated peer configs +ls wireguard/config/peer_*/ + +# View a specific peer's config +cat wireguard/config/peer_phone/peer_phone.conf +``` + +Each peer folder also contains a **QR code** image for easy mobile setup: + +```bash +# Display QR code in the terminal (for mobile scanning) +docker exec wireguard /app/show-peer phone +``` + +### 9.2 Install WireGuard on Your Client + +| Platform | App | +|----------|-----| +| **Android** | [WireGuard](https://play.google.com/store/apps/details?id=com.wireguard.android) on Play Store | +| **iOS** | [WireGuard](https://apps.apple.com/app/wireguard/id1441195209) on App Store | +| **Windows** | [WireGuard](https://www.wireguard.com/install/) official installer | +| **macOS** | [WireGuard](https://apps.apple.com/app/wireguard/id1451685025) on App Store or `brew install wireguard-tools` | +| **Linux** | `sudo apt install wireguard-tools` | + +### 9.3 Import the Configuration + +- **Mobile:** Scan the QR code shown by `docker exec wireguard /app/show-peer ` +- **Desktop:** Copy the `peer_.conf` file to the client and import it in the WireGuard app + +### 9.4 Test the VPN Connection + +1. Activate the WireGuard tunnel on your client. +2. Try accessing `https://ha.doerflingers.com` β€” it should work even from a mobile network. +3. You can also access the Nginx Proxy Manager admin UI at `http://192.168.178.XX:81` through the VPN. + +--- + +## 10. Backup & Restore + +### 10.1 Create a Backup + +```bash +# Backup all service data to ./backups/ +./scripts/backup.sh + +# Or specify a custom backup directory +./scripts/backup.sh /mnt/usb-drive/backups +``` + +The script creates a timestamped archive like `home-server-backup_20260220_080000.tar.gz`. + +> **Tip:** For automated backups, add a cron job: +> ```cron +> # Daily backup at 3 AM +> 0 3 * * * /home/YOUR_USER/home-server/scripts/backup.sh /mnt/backup-drive/ >> /var/log/home-server-backup.log 2>&1 +> ``` + +### 10.2 Restore from Backup + +```bash +# Stop all services first! +docker compose down + +# Restore +./scripts/restore.sh backups/home-server-backup_20260220_080000.tar.gz + +# Restart services +docker compose up -d +``` + +--- + +## 11. Migration to New Hardware + +Moving everything to a new server is straightforward thanks to Docker: + +1. **On the old server:** + ```bash + docker compose down + ./scripts/backup.sh + ``` + +2. **Copy to new server:** + ```bash + scp backups/home-server-backup_*.tar.gz user@new-server:/tmp/ + scp -r . user@new-server:~/home-server/ # or just clone the repo + ``` + +3. **On the new server:** + ```bash + # Install prerequisites (Section 2 of this README) + cd ~/home-server + ./scripts/restore.sh /tmp/home-server-backup_*.tar.gz + # Review .env and adjust if needed (e.g., new timezone) + docker compose up -d + ``` + +4. **Update DynDNS** β€” Run the DynDNS setup again on the new server (Section 3). + +5. **Update FritzBox** β€” If the new server has a different local IP, update the port forwarding rules (Section 4). + +--- + +## 12. Troubleshooting + +### Container won't start + +```bash +docker compose logs +``` + +### Home Assistant shows `400 Bad Request` + +This means the reverse proxy is not trusted. Verify that `homeassistant/configuration.yaml` contains the correct `trusted_proxies` subnet (`172.20.0.0/24`) matching the Docker network in `docker-compose.yml`. + +```bash +# Find the actual Docker network subnet +docker network inspect proxy-network | grep Subnet +``` + +### Let's Encrypt certificate fails + +- **Port 80 must be forwarded** in FritzBox (HTTP-01 challenge requires it). +- DNS for the domain must already point to your IP. Check: `dig +short ha.doerflingers.com` +- Wait a few minutes after setting up DynDNS for DNS propagation. + +### WireGuard clients can't connect + +- Verify port `51820/UDP` is forwarded in FritzBox. +- Check if the WireGuard kernel module is loaded: `lsmod | grep wireguard` +- View WireGuard logs: `docker compose logs wireguard` + +### Gitea SSH clone doesn't work + +- Verify port `2222/TCP` is forwarded in FritzBox. +- Clone syntax: `git clone ssh://git@git.doerflingers.com:2222/user/repo.git` + +### High memory usage + +```bash +# Check per-container memory usage +docker stats --no-stream +``` + +If running low on RAM, consider: +- Reducing `WG_PEERS` count +- Moving Gitea to a lighter database (SQLite is already the lightest) +- Adding more swap: `sudo fallocate -l 4G /swapfile2 && sudo chmod 600 /swapfile2 && sudo mkswap /swapfile2 && sudo swapon /swapfile2` + +### Laptop suspends when lid is closed + +See Section 2.5 β€” make sure `HandleLidSwitch=ignore` is set. + +--- + +## 13. Architecture Overview + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ INTERNET β”‚ +β”‚ β”‚ +β”‚ ha.doerflingers.com ──┐ β”‚ +β”‚ git.doerflingers.com ────→ Your Public IP (DynDNS-updated) β”‚ +β”‚ home.doerflingers.com β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ FritzBox β”‚ + β”‚ Port Forwarding: β”‚ + β”‚ 80 β†’ Laptop:80 β”‚ + β”‚ 443 β†’ Laptop:443 β”‚ + β”‚ 51820/UDP β†’ :51820 β”‚ + β”‚ 2222 β†’ Laptop:2222 β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Ubuntu 24.04 Laptop β”‚ + β”‚ (Docker Host) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Docker Network β”‚ + β”‚ (proxy-network) β”‚ + β”‚ 172.20.0.0/24 β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Nginx Proxy Manager β”‚ β”‚ + β”‚ β”‚ :80, :443, :81 β”‚ β”‚ + β”‚ β”‚ SSL termination β”‚ β”‚ + β”‚ β”‚ Let's Encrypt certs β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”˜ └────┐ β”‚ + β”‚ β–Ό β–Ό β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Home β”‚ β”‚ Gitea β”‚ β”‚ + β”‚ β”‚ Assistantβ”‚ β”‚ :3000 β”‚ β”‚ + β”‚ β”‚ :8123 β”‚ β”‚ :22β†’2222β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ WireGuard VPN β”‚ β”‚ + β”‚ β”‚ :51820/UDP β”‚ β”‚ + β”‚ β”‚ Subnet: 10.13.13.0/24 β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Data Persistence + +All service data is stored in local directories (bind mounts), making backups and migration simple: + +| Service | Data Directory | Contains | +|---------|---------------|----------| +| Nginx Proxy Manager | `./nginx-proxy-manager/data/` + `./nginx-proxy-manager/letsencrypt/` | Proxy configs, SSL certs | +| Home Assistant | `./homeassistant/` | Configuration, automations, database | +| Gitea | `./gitea/data/` | Repositories, database, config | +| WireGuard | `./wireguard/config/` | Server + peer keys, configs | + +--- + +## Quick Reference + +```bash +# Start all services +docker compose up -d + +# Stop all services +docker compose down + +# Restart a single service +docker compose restart homeassistant + +# View logs +docker compose logs -f + +# Check resource usage +docker stats + +# Update all images to latest +docker compose pull +docker compose up -d + +# Backup +./scripts/backup.sh + +# Restore +docker compose down +./scripts/restore.sh backups/.tar.gz +docker compose up -d +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2af94c5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,121 @@ +############################################################################### +# Home Server Docker Compose Stack +# Services: Nginx Proxy Manager, Home Assistant, Gitea, WireGuard +# +# Usage: +# cp .env.example .env # then edit .env with your values +# docker compose up -d +############################################################################### + +networks: + proxy-network: + name: proxy-network + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/24 + +services: + # =========================================================================== + # Nginx Proxy Manager β€” Reverse proxy with built-in Let's Encrypt + # Admin UI: http://:81 + # Default login: admin@example.com / changeme + # =========================================================================== + nginx-proxy-manager: + image: jc21/nginx-proxy-manager:latest + container_name: nginx-proxy-manager + restart: unless-stopped + ports: + - "80:80" # HTTP + - "443:443" # HTTPS + - "81:81" # Admin UI + volumes: + - ./nginx-proxy-manager/data:/data + - ./nginx-proxy-manager/letsencrypt:/etc/letsencrypt + networks: + - proxy-network + healthcheck: + test: [ "CMD", "/usr/bin/check-health" ] + interval: 30s + timeout: 10s + retries: 3 + + # =========================================================================== + # Home Assistant β€” Smart home platform + # Internal URL: http://homeassistant:8123 + # =========================================================================== + homeassistant: + image: ghcr.io/home-assistant/home-assistant:stable + container_name: homeassistant + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + volumes: + - ./homeassistant:/config + networks: + - proxy-network + depends_on: + - nginx-proxy-manager + + # =========================================================================== + # Gitea β€” Lightweight Git hosting + # Internal URL: http://gitea:3000 + # SSH: Port 2222 (mapped from container 22) + # =========================================================================== + gitea: + image: gitea/gitea:latest + container_name: gitea + restart: unless-stopped + environment: + - USER_UID=${PUID:-1000} + - USER_GID=${PGID:-1000} + - GITEA__database__DB_TYPE=sqlite3 + - GITEA__database__PATH=/data/gitea/gitea.db + - GITEA__server__DOMAIN=${DOMAIN_GITEA:-git.doerflingers.com} + - GITEA__server__SSH_DOMAIN=${DOMAIN_GITEA:-git.doerflingers.com} + - GITEA__server__SSH_PORT=${GITEA_SSH_PORT:-2222} + - GITEA__server__ROOT_URL=https://${DOMAIN_GITEA:-git.doerflingers.com}/ + - GITEA__server__LFS_START_SERVER=true + - GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY:-} + - GITEA__security__INTERNAL_TOKEN=${GITEA_INTERNAL_TOKEN:-} + volumes: + - ./gitea/data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "${GITEA_SSH_PORT:-2222}:22" + networks: + - proxy-network + depends_on: + - nginx-proxy-manager + + # =========================================================================== + # WireGuard β€” Modern VPN server + # External: UDP port 51820 (must be forwarded in FritzBox) + # =========================================================================== + wireguard: + image: lscr.io/linuxserver/wireguard:latest + container_name: wireguard + restart: unless-stopped + cap_add: + - NET_ADMIN + - SYS_MODULE + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - SERVERURL=${WG_SERVERURL:-home.doerflingers.com} + - SERVERPORT=${WG_PORT:-51820} + - PEERS=${WG_PEERS:-phone,laptop,tablet} + - PEERDNS=auto + - INTERNAL_SUBNET=${WG_INTERNAL_SUBNET:-10.13.13.0} + - ALLOWEDIPS=0.0.0.0/0 + volumes: + - ./wireguard/config:/config + - /lib/modules:/lib/modules:ro + ports: + - "${WG_PORT:-51820}:51820/udp" + networks: + - proxy-network + sysctls: + - net.ipv4.conf.all.src_valid_mark=1 diff --git a/homeassistant/automations.yaml b/homeassistant/automations.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/homeassistant/automations.yaml @@ -0,0 +1 @@ +[] diff --git a/homeassistant/configuration.yaml b/homeassistant/configuration.yaml new file mode 100644 index 0000000..a3f17ad --- /dev/null +++ b/homeassistant/configuration.yaml @@ -0,0 +1,36 @@ +# Home Assistant Configuration +# https://www.home-assistant.io/docs/configuration/ + +homeassistant: + name: Home + unit_system: metric + time_zone: Europe/Berlin + currency: EUR + +# Enable the frontend +frontend: + +# Enable configuration UI +config: + +# HTTP configuration β€” required for reverse proxy +http: + use_x_forwarded_for: true + trusted_proxies: + # Docker proxy-network subnet + - 172.20.0.0/24 + # WireGuard VPN subnet + - 10.13.13.0/24 + +# Discover devices on the network +# Note: discovery may be limited in container mode +# For full discovery, consider using host network mode +# (but that conflicts with reverse proxy setup) + +# Enable the API +api: + +# Automations, scripts, scenes +automation: !include automations.yaml +script: !include scripts.yaml +scene: !include scenes.yaml diff --git a/homeassistant/scenes.yaml b/homeassistant/scenes.yaml new file mode 100644 index 0000000..8a9130d --- /dev/null +++ b/homeassistant/scenes.yaml @@ -0,0 +1 @@ +# Scenes diff --git a/homeassistant/scripts.yaml b/homeassistant/scripts.yaml new file mode 100644 index 0000000..59d9cb4 --- /dev/null +++ b/homeassistant/scripts.yaml @@ -0,0 +1 @@ +# Scripts diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..ba1b459 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +############################################################################### +# backup.sh β€” Backup all persistent data for the home-server stack +# +# Usage: +# ./scripts/backup.sh [backup-directory] +# +# Default backup directory: ./backups/ +# Creates a timestamped tar.gz archive of all service data. +############################################################################### +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +BACKUP_DIR="${1:-$PROJECT_DIR/backups}" +TIMESTAMP="$(date +%Y%m%d_%H%M%S)" +BACKUP_FILE="$BACKUP_DIR/home-server-backup_${TIMESTAMP}.tar.gz" + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}=== Home Server Backup ===${NC}" +echo "Timestamp: $TIMESTAMP" +echo "Project: $PROJECT_DIR" +echo "Backup to: $BACKUP_FILE" +echo "" + +# Create backup directory if it doesn't exist +mkdir -p "$BACKUP_DIR" + +# Check if services are running and warn +if command -v docker &> /dev/null; then + RUNNING=$(docker compose -f "$PROJECT_DIR/docker-compose.yml" ps --status running -q 2>/dev/null | wc -l) + if [ "$RUNNING" -gt 0 ]; then + echo -e "${YELLOW}WARNING: $RUNNING container(s) are running.${NC}" + echo "For a consistent backup, consider stopping services first:" + echo " docker compose down" + echo "" + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Backup cancelled." + exit 0 + fi + fi +fi + +# Directories to back up +DIRS_TO_BACKUP=( + "nginx-proxy-manager" + "homeassistant" + "gitea" + "wireguard" + ".env" +) + +# Build tar arguments β€” only include directories/files that exist +TAR_ARGS=() +for item in "${DIRS_TO_BACKUP[@]}"; do + if [ -e "$PROJECT_DIR/$item" ]; then + TAR_ARGS+=("$item") + echo -e " ${GREEN}βœ“${NC} Including: $item" + else + echo -e " ${YELLOW}⊘${NC} Skipping (not found): $item" + fi +done + +if [ ${#TAR_ARGS[@]} -eq 0 ]; then + echo -e "${RED}ERROR: No data directories found to back up.${NC}" + exit 1 +fi + +echo "" +echo "Creating archive..." + +tar -czf "$BACKUP_FILE" -C "$PROJECT_DIR" "${TAR_ARGS[@]}" + +BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) +echo "" +echo -e "${GREEN}=== Backup complete ===${NC}" +echo "File: $BACKUP_FILE" +echo "Size: $BACKUP_SIZE" +echo "" +echo "To restore, run:" +echo " ./scripts/restore.sh $BACKUP_FILE" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100644 index 0000000..1b54f0f --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +############################################################################### +# restore.sh β€” Restore home-server stack data from a backup archive +# +# Usage: +# ./scripts/restore.sh +# +# This will extract the backup into the project directory, overwriting +# existing service data. +############################################################################### +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +if [ $# -lt 1 ]; then + echo -e "${RED}Usage: $0 ${NC}" + echo "" + echo "Available backups:" + if [ -d "$PROJECT_DIR/backups" ]; then + ls -lh "$PROJECT_DIR/backups/"*.tar.gz 2>/dev/null || echo " (none found)" + else + echo " (no backups directory)" + fi + exit 1 +fi + +BACKUP_FILE="$1" + +if [ ! -f "$BACKUP_FILE" ]; then + echo -e "${RED}ERROR: Backup file not found: $BACKUP_FILE${NC}" + exit 1 +fi + +echo -e "${YELLOW}=== Home Server Restore ===${NC}" +echo "Backup: $BACKUP_FILE" +echo "Target: $PROJECT_DIR" +echo "" + +# Check if services are running +if command -v docker &> /dev/null; then + RUNNING=$(docker compose -f "$PROJECT_DIR/docker-compose.yml" ps --status running -q 2>/dev/null | wc -l) + if [ "$RUNNING" -gt 0 ]; then + echo -e "${RED}ERROR: $RUNNING container(s) are still running.${NC}" + echo "Please stop all services before restoring:" + echo " docker compose down" + exit 1 + fi +fi + +echo -e "${YELLOW}WARNING: This will overwrite existing data in:${NC}" +echo " - nginx-proxy-manager/" +echo " - homeassistant/" +echo " - gitea/" +echo " - wireguard/" +echo " - .env" +echo "" +read -p "Are you sure? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Restore cancelled." + exit 0 +fi + +echo "" +echo "Extracting backup..." + +tar -xzf "$BACKUP_FILE" -C "$PROJECT_DIR" + +echo "" +echo -e "${GREEN}=== Restore complete ===${NC}" +echo "" +echo "Next steps:" +echo " 1. Review your .env file and adjust if needed" +echo " 2. Start services: docker compose up -d" +echo " 3. Verify all services: docker compose ps" diff --git a/touch b/touch new file mode 100644 index 0000000..e69de29