Initial commit
This commit is contained in:
37
.env.example
Normal file
37
.env.example
Normal file
@@ -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
|
||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -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
|
||||
682
README.md
Normal file
682
README.md
Normal file
@@ -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 <your-repo-url> ~/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://<laptop-local-ip>: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://<laptop-ip>: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 <peername>`
|
||||
- **Desktop:** Copy the `peer_<name>.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 <service-name>
|
||||
```
|
||||
|
||||
### 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/<backup-file>.tar.gz
|
||||
docker compose up -d
|
||||
```
|
||||
121
docker-compose.yml
Normal file
121
docker-compose.yml
Normal file
@@ -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://<server-ip>: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
|
||||
1
homeassistant/automations.yaml
Normal file
1
homeassistant/automations.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
36
homeassistant/configuration.yaml
Normal file
36
homeassistant/configuration.yaml
Normal file
@@ -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
|
||||
1
homeassistant/scenes.yaml
Normal file
1
homeassistant/scenes.yaml
Normal file
@@ -0,0 +1 @@
|
||||
# Scenes
|
||||
1
homeassistant/scripts.yaml
Normal file
1
homeassistant/scripts.yaml
Normal file
@@ -0,0 +1 @@
|
||||
# Scripts
|
||||
88
scripts/backup.sh
Normal file
88
scripts/backup.sh
Normal file
@@ -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"
|
||||
82
scripts/restore.sh
Normal file
82
scripts/restore.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
###############################################################################
|
||||
# restore.sh — Restore home-server stack data from a backup archive
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/restore.sh <backup-file.tar.gz>
|
||||
#
|
||||
# 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 <backup-file.tar.gz>${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"
|
||||
Reference in New Issue
Block a user