Compare commits
17 Commits
64a43c8a96
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 059daa77aa | |||
| 9aa881d895 | |||
| ab4a4cfc9c | |||
| 646d3e59bc | |||
| ad7270aa74 | |||
| 7e64557f0b | |||
| acc30e7c3d | |||
| 3d57327146 | |||
| d0727bd8a7 | |||
| 7eaf16f53e | |||
| bab97f7166 | |||
| 6894c5f175 | |||
| 6761c8903d | |||
| a8bbf5ea4a | |||
| fefdc14398 | |||
| 802d275d6e | |||
| dc16d0302d |
19
TODO.md
19
TODO.md
@@ -23,7 +23,7 @@
|
||||
- [ ] WireGuard VPN server configuration
|
||||
- [ ] UFW firewall setup and rules
|
||||
- [ ] fail2ban for intrusion prevention
|
||||
- [ ] Security enhancement for VNC connections (in the meantime: only run the vnc service for short time while we are using it)
|
||||
- [x] VNC setup for remote desktop access *(TigerVNC installed, enabled temporarily via SSH when needed)*
|
||||
|
||||
## Git & Development
|
||||
- [x] Gitea Docker container setup *(completed - running on port 3000)*
|
||||
@@ -91,9 +91,8 @@ Lower priority - mostly using SSH or TTY anyways
|
||||
- [x] Shared media folders with Copyparty (Music, Videos, shared)
|
||||
- [ ] Set up self-hosted chat server (Matrix or Mattermost)
|
||||
- [ ] Install monitoring and management tools *(in progress)*
|
||||
- [ ] Portainer (Docker management with built-in auth)
|
||||
- [ ] Glances (system monitoring with nginx basic auth)
|
||||
- [ ] Cockpit (system administration with PAM auth)
|
||||
- [x] Portainer (Docker management with built-in auth)
|
||||
- [x] Glances (system monitoring with nginx basic auth)
|
||||
- [ ] lazydocker (terminal Docker management)
|
||||
- [ ] Configure nginx basic auth for Glances endpoint
|
||||
- [ ] Update nginx reverse proxy config for new admin services
|
||||
@@ -101,14 +100,16 @@ Lower priority - mostly using SSH or TTY anyways
|
||||
- [ ] Set up Nextcloud for advanced file synchronization features
|
||||
- Copyparty covers basic file sharing needs
|
||||
- [x] Set up reverse proxy with SSL certificates *(completed - HTTPS working with auto-renewal)*
|
||||
- [ ] Make sure all services are dockerized unless we have a good reason not to
|
||||
- [x] Make sure all services are dockerized unless we have a good reason not to
|
||||
- Gitea: ✅ Docker
|
||||
- Jellyfin: ✅ Docker
|
||||
- Copyparty: ❌ systemd service (consider dockerizing)
|
||||
- Nginx: ❌ system package (fine as-is for reverse proxy)
|
||||
- qBittorrent: ✅ Docker
|
||||
- Portainer: ✅ Docker
|
||||
- Glances: ❌ system package (web server mode)
|
||||
- Cockpit: ❌ system package (system integration required)
|
||||
- Copyparty: ✅ Docker
|
||||
- Glances: ❌ systemd (needs full system access for accurate monitoring)
|
||||
- Netdata: ❌ systemd (needs full system access for accurate monitoring)
|
||||
- Syncthing: ❌ systemd (Docker permission issues with config directory)
|
||||
- Nginx: ❌ system package (reverse proxy, system integration required)
|
||||
|
||||
## Hardware & Troubleshooting
|
||||
- [ ] Fix bluetooth audio connectivity issues
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# Copyparty Configuration for Homelab
|
||||
# DEPLOYMENT LOCATION: /home/hoborg/.config/copyparty/copyparty.conf
|
||||
|
||||
[global]
|
||||
# Network settings
|
||||
i: 127.0.0.1
|
||||
p: 8082
|
||||
rp-loc: /files
|
||||
|
||||
# Reverse proxy configuration
|
||||
rproxy: -1
|
||||
|
||||
# Security and features
|
||||
usernames
|
||||
chpw
|
||||
chpw-db: /home/hoborg/.config/copyparty/passwords.json
|
||||
e2dsa
|
||||
dedup
|
||||
dotpart
|
||||
|
||||
# Upload settings
|
||||
u2ts: c
|
||||
chmod-f: 644
|
||||
chmod-d: 755
|
||||
|
||||
# Server name
|
||||
name: homelab-files
|
||||
|
||||
[accounts]
|
||||
guest: SecurePass2024!
|
||||
hoborg: AdminPass2024!
|
||||
|
||||
[/shared]
|
||||
/mnt/nas/shared
|
||||
accs:
|
||||
rw: guest
|
||||
rwmd: hoborg
|
||||
|
||||
[/documents]
|
||||
/mnt/nas/documents
|
||||
accs:
|
||||
rwmd: hoborg
|
||||
|
||||
[/music]
|
||||
/mnt/nas/music
|
||||
accs:
|
||||
rw: guest
|
||||
rwmd: hoborg
|
||||
|
||||
[/videos]
|
||||
/mnt/nas/videos
|
||||
accs:
|
||||
rw: guest
|
||||
rwmd: hoborg
|
||||
|
||||
[/private]
|
||||
/mnt/nas/private
|
||||
accs:
|
||||
rwmd: hoborg
|
||||
|
||||
[/pictures]
|
||||
/mnt/nas/pictures
|
||||
accs:
|
||||
rw: guest
|
||||
rwmd: hoborg
|
||||
|
||||
[/torrent]
|
||||
/mnt/nas/torrent
|
||||
accs:
|
||||
rw: guest
|
||||
rwmd: hoborg
|
||||
90
config/docker/copyparty/docker-compose.yml
Normal file
90
config/docker/copyparty/docker-compose.yml
Normal file
@@ -0,0 +1,90 @@
|
||||
# Copyparty Docker Compose Configuration
|
||||
# Deploy with: sudo mkdir -p /opt/docker/copyparty && sudo cp config/docker/copyparty/docker-compose.yml /opt/docker/copyparty/
|
||||
# Start with: cd /opt/docker/copyparty && sudo docker-compose up -d
|
||||
|
||||
# COPYPARTY CONFIGURATION
|
||||
# - File server with upload/download capabilities
|
||||
# - WebDAV support for mobile and desktop clients
|
||||
# - Bound to localhost only (reverse proxy required)
|
||||
# - Configuration stored in repo at config/copyparty/copyparty.conf
|
||||
|
||||
services:
|
||||
copyparty:
|
||||
image: copyparty/ac:latest
|
||||
container_name: copyparty
|
||||
restart: unless-stopped
|
||||
|
||||
# Environment
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Budapest
|
||||
|
||||
# Network - bind to localhost for security
|
||||
ports:
|
||||
- "127.0.0.1:8082:3923" # Web UI (reverse proxy only)
|
||||
|
||||
# Volume mounts
|
||||
volumes:
|
||||
# Password database
|
||||
- /home/hoborg/.config/copyparty/passwords.json:/home/hoborg/.config/copyparty/passwords.json
|
||||
# NAS storage volumes (mapped to container paths)
|
||||
- /mnt/nas/shared:/w/shared:rw
|
||||
- /mnt/nas/documents:/w/documents:rw
|
||||
- /mnt/nas/music:/w/music:rw
|
||||
- /mnt/nas/videos:/w/videos:rw
|
||||
- /mnt/nas/private:/w/private:rw
|
||||
- /mnt/nas/pictures:/w/pictures:rw
|
||||
- /mnt/nas/installers:/w/installers:rw
|
||||
- /mnt/nas/torrent:/w/torrent:rw
|
||||
|
||||
# Command with inline volume configuration using -v flag
|
||||
command:
|
||||
- --rp-loc=/files
|
||||
- --name=homelab-files
|
||||
- --usernames
|
||||
- --chpw
|
||||
- --chpw-db=/home/hoborg/.config/copyparty/passwords.json
|
||||
- --xff-src=172.0.0.0/8
|
||||
- --rproxy=-1
|
||||
- -e2dsa
|
||||
- --dedup
|
||||
- --dotpart
|
||||
- -a
|
||||
- hoborg:AdminPass2024!
|
||||
- -a
|
||||
- guest:SecurePass2024!
|
||||
- -v
|
||||
- /w/shared:shared:r:rw,guest:rwmd,hoborg
|
||||
- -v
|
||||
- /w/documents:documents:rwmd,hoborg
|
||||
- -v
|
||||
- /w/music:music:r:rw,guest:rwmd,hoborg
|
||||
- -v
|
||||
- /w/videos:videos:r:rw,guest:rwmd,hoborg
|
||||
- -v
|
||||
- /w/private:private:rwmd,hoborg
|
||||
- -v
|
||||
- /w/pictures:pictures:r:rw,guest:rwmd,hoborg
|
||||
- -v
|
||||
- /w/installers:installers:r:rw,guest:rwmd,hoborg
|
||||
- -v
|
||||
- /w/torrent:torrent:r:rw,guest:rwmd,hoborg
|
||||
|
||||
# Resource limits
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 1G
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 256M
|
||||
|
||||
# Health check
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3923/files/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
@@ -1,13 +1,3 @@
|
||||
# Docker Daemon Configuration (Minimal Working Version)
|
||||
# Deploy to: /etc/docker/daemon.json
|
||||
#
|
||||
# Setup commands:
|
||||
# sudo cp config/docker/daemon.json /etc/docker/
|
||||
# sudo systemctl restart docker
|
||||
#
|
||||
# Note: Removed problematic security settings that caused read-only filesystem issues.
|
||||
# Only keeping essential logging configuration.
|
||||
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
|
||||
50
config/docker/portainer/docker-compose.yml
Normal file
50
config/docker/portainer/docker-compose.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
# Portainer Docker Compose Configuration
|
||||
# Deploy with: sudo mkdir -p /opt/docker/portainer && sudo cp config/docker/portainer/docker-compose.yml /opt/docker/portainer/
|
||||
# Start with: cd /opt/docker/portainer && sudo docker-compose up -d
|
||||
|
||||
# PORTAINER CONFIGURATION
|
||||
# - Web UI for Docker container management
|
||||
# - Bound to localhost only (reverse proxy recommended)
|
||||
# - Persistent data storage in named volume
|
||||
|
||||
services:
|
||||
portainer:
|
||||
image: portainer/portainer-ce:latest
|
||||
container_name: portainer
|
||||
restart: unless-stopped
|
||||
|
||||
# Environment
|
||||
environment:
|
||||
- TZ=Europe/Budapest
|
||||
|
||||
# Network - bind to localhost for security
|
||||
ports:
|
||||
- "127.0.0.1:9000:9000" # Web UI (reverse proxy only)
|
||||
|
||||
# Volume mounts
|
||||
volumes:
|
||||
# Docker socket for container management
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Portainer data
|
||||
- portainer_data:/data
|
||||
|
||||
# Resource limits
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 128M
|
||||
|
||||
# Health check
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9000/api/status"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
portainer_data:
|
||||
@@ -5,8 +5,15 @@ server {
|
||||
listen 80 default_server;
|
||||
server_name ak-homelab.duckdns.org _;
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
# Allow Let's Encrypt ACME challenges
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Redirect all other HTTP to HTTPS
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
@@ -176,6 +183,32 @@ server {
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Nextcloud cloud storage
|
||||
location /cloud/ {
|
||||
proxy_pass http://127.0.0.1:8083/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Nextcloud specific headers
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Server $host;
|
||||
|
||||
# Large file upload support
|
||||
client_max_body_size 10G;
|
||||
client_body_buffer_size 128k;
|
||||
|
||||
# Upload timeout settings
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
# Buffering settings for uploads
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/ak-homelab.duckdns.org/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/ak-homelab.duckdns.org/privkey.pem; # managed by Certbot
|
||||
|
||||
@@ -140,10 +140,10 @@
|
||||
<h3>Copyparty</h3>
|
||||
<p>File Server & WebDAV</p>
|
||||
</a>
|
||||
<a href="#" class="service disabled" onclick="return false;">
|
||||
<a href="/cloud/" class="service">
|
||||
<i class="fas fa-cloud"></i>
|
||||
<h3>Nextcloud</h3>
|
||||
<p>Temporarily Disabled</p>
|
||||
<p>Cloud Storage & Collaboration</p>
|
||||
</a>
|
||||
<a href="/media/" class="service">
|
||||
<i class="fas fa-play-circle"></i>
|
||||
|
||||
@@ -386,8 +386,11 @@ sudo fail2ban-client get sshd banned
|
||||
- **Last Update:** September 17, 2025 at 00:57:41
|
||||
|
||||
### Whitelisted Countries
|
||||
|
||||
**Configuration:** All EU member states plus European territories (full list accepted by geoip-shell)
|
||||
|
||||
```
|
||||
AL (Albania), AD (Andorra), AM (Armenia), AT (Austria), AZ (Azerbaijan)
|
||||
AL (Albania), AD (Andorra), AM (Armenia), AT (Austria), AX (Åland Islands), AZ (Azerbaijan)
|
||||
BY (Belarus), BE (Belgium), BA (Bosnia and Herzegovina), BG (Bulgaria)
|
||||
HR (Croatia), CY (Cyprus), CZ (Czech Republic), DK (Denmark)
|
||||
EE (Estonia), FO (Faroe Islands), FI (Finland), FR (France)
|
||||
@@ -440,14 +443,36 @@ journalctl -u geoip-shell
|
||||
tail -f /var/log/geoip-shell.log
|
||||
```
|
||||
|
||||
### Manual Setup Process
|
||||
The tool was installed manually with interactive prompts rather than scripted installation due to security considerations. Key decisions made during setup:
|
||||
### Installation and Setup
|
||||
|
||||
**Installation Method:** Interactive CLI setup using geoip-shell installer
|
||||
|
||||
The tool was configured using its interactive setup wizard, which prompted for configuration choices and automatically applied all necessary changes to the firewall. This approach ensures proper integration with the existing firewall backend.
|
||||
|
||||
**Key Configuration Decisions:**
|
||||
|
||||
1. **Whitelist Mode:** Chosen over blacklist for better control
|
||||
2. **European Focus:** Primary whitelist consists of European countries
|
||||
3. **Network Exceptions:** Docker and LAN networks automatically detected
|
||||
4. **Dual Stack:** Both IPv4 and IPv6 protection enabled
|
||||
5. **Full Protocol Coverage:** TCP and UDP both protected
|
||||
2. **European Focus:** All EU member states plus European territories
|
||||
3. **Country List:** Complete list of countries accepted by geoip-shell
|
||||
4. **Network Exceptions:** Docker and LAN networks automatically detected
|
||||
5. **Dual Stack:** Both IPv4 and IPv6 protection enabled
|
||||
6. **Full Protocol Coverage:** TCP and UDP both protected
|
||||
|
||||
**Setup Command:**
|
||||
```bash
|
||||
# Download and run installer
|
||||
curl -fsSL https://raw.githubusercontent.com/friendly-bits/geoip-shell/main/install.sh | bash
|
||||
|
||||
# Run configuration wizard (prompted automatically after install)
|
||||
geoip-shell configure
|
||||
```
|
||||
|
||||
During the interactive setup, the wizard prompted for:
|
||||
- Firewall backend selection (iptables chosen)
|
||||
- IP list source (RIPE selected)
|
||||
- Mode selection (whitelist chosen)
|
||||
- Country selection (all EU countries entered)
|
||||
- Network interface configuration (all interfaces selected)
|
||||
|
||||
### Detailed Documentation
|
||||
For complete setup details, see **[docs/geoip-blocking.md](docs/geoip-blocking.md)**
|
||||
|
||||
233
docs/system-backups.md
Normal file
233
docs/system-backups.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# System Backups
|
||||
|
||||
Complete backup strategy for the homelab using Restic for encrypted, deduplicated backups to NAS.
|
||||
|
||||
## Overview
|
||||
|
||||
**Backup Tool**: Restic (modern, encrypted, deduplicated)
|
||||
**Backup Destination**: NAS at `/mnt/nas/backups/homelab-restic`
|
||||
**Backup Frequency**: Daily (can be automated with systemd timer)
|
||||
**Encryption**: AES-256, password stored in `/home/hoborg/creds/restic-password.txt`
|
||||
|
||||
## What Gets Backed Up
|
||||
|
||||
### Full System Backup Includes:
|
||||
1. **Package Lists**
|
||||
- All explicitly installed packages (pacman)
|
||||
- All installed packages (including dependencies)
|
||||
- AUR/foreign packages list
|
||||
- Allows exact system recreation after reinstall
|
||||
|
||||
2. **System Configuration** (`/etc/`)
|
||||
- Nginx configuration
|
||||
- Systemd services
|
||||
- Network configuration
|
||||
- All system-wide configs
|
||||
- Excludes: shadow/gshadow password files
|
||||
|
||||
3. **Docker Volumes**
|
||||
- Gitea (Git repositories and database)
|
||||
- Jellyfin (media library configuration)
|
||||
- Nextcloud (cloud storage data and database)
|
||||
- Portainer (container management data)
|
||||
|
||||
### What's NOT Backed Up (Intentionally):
|
||||
- **User data in `/home`**: Separate partition, safe from system reinstalls
|
||||
- **Media files**: Already stored on NAS (Music, Videos, Pictures)
|
||||
- **User documents**: Synced via Nextcloud/Copyparty or on separate partition
|
||||
|
||||
## Setup
|
||||
|
||||
### Initial Setup (One-Time)
|
||||
|
||||
1. **Initialize the backup repository:**
|
||||
```bash
|
||||
sudo /home/hoborg/homelab/scripts/backup-init.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
- Install Restic if needed
|
||||
- Create backup directory on NAS (`/mnt/nas/backups/homelab-restic`)
|
||||
- Generate encryption password (saved to `/home/hoborg/creds/restic-password.txt`)
|
||||
- Initialize the Restic repository
|
||||
|
||||
⚠️ **IMPORTANT**: Back up the password file! Without it, backups cannot be restored.
|
||||
|
||||
### Running Backups
|
||||
|
||||
**Manual backup:**
|
||||
```bash
|
||||
sudo /home/hoborg/homelab/scripts/backup-system-full.sh
|
||||
```
|
||||
|
||||
**Automated backups** (optional - setup systemd timer later):
|
||||
- Create systemd service and timer
|
||||
- Schedule daily backups (e.g., 3 AM)
|
||||
- Logs written to `/var/log/homelab-backup.log`
|
||||
|
||||
## Backup Scripts
|
||||
|
||||
### `backup-init.sh`
|
||||
Initializes the Restic repository. Run once during setup.
|
||||
|
||||
Location: `/home/hoborg/homelab/scripts/backup-init.sh`
|
||||
|
||||
### `backup-system-full.sh`
|
||||
Performs full system backup including packages, configs, and Docker volumes.
|
||||
|
||||
Location: `/home/hoborg/homelab/scripts/backup-system-full.sh`
|
||||
|
||||
What it does:
|
||||
1. Exports package lists (pacman explicit, all packages, AUR packages)
|
||||
2. Exports Docker volumes to compressed tarballs
|
||||
3. Backs up entire `/etc/` directory
|
||||
4. Prunes old backups (keeps: 7 daily, 4 weekly, 6 monthly, 2 yearly)
|
||||
5. Shows backup statistics
|
||||
|
||||
## Restore Procedures
|
||||
|
||||
### List Available Backups
|
||||
|
||||
```bash
|
||||
export RESTIC_PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
restic -r /mnt/nas/backups/homelab-restic snapshots
|
||||
```
|
||||
|
||||
### Restore After System Reinstall
|
||||
|
||||
1. **Restore backup to temporary location:**
|
||||
```bash
|
||||
export RESTIC_PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
restic -r /mnt/nas/backups/homelab-restic restore latest --target /tmp/restore
|
||||
```
|
||||
|
||||
2. **Reinstall packages:**
|
||||
```bash
|
||||
# Reinstall all explicitly installed packages
|
||||
sudo pacman -S --needed $(cat /tmp/restore/tmp/system-backup-*/pacman-explicit.txt)
|
||||
|
||||
# Reinstall AUR packages
|
||||
yay -S --needed $(cat /tmp/restore/tmp/system-backup-*/aur-packages.txt)
|
||||
```
|
||||
|
||||
3. **Restore system configs:**
|
||||
```bash
|
||||
sudo cp -r /tmp/restore/etc/* /etc/
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
4. **Restore Docker volumes:**
|
||||
```bash
|
||||
# Example: Restore Gitea
|
||||
docker-compose -f /opt/docker/gitea/docker-compose.yml down
|
||||
|
||||
sudo tar xzf /tmp/restore/tmp/docker-backup-*/gitea-data.tar.gz -C /tmp/gitea-restore
|
||||
|
||||
docker run --rm -v gitea_gitea:/data -v /tmp/gitea-restore:/backup alpine \
|
||||
sh -c "rm -rf /data/* && cp -a /backup/* /data/"
|
||||
|
||||
docker-compose -f /opt/docker/gitea/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
### Rollback After Broken Update
|
||||
|
||||
If a system update breaks something:
|
||||
|
||||
1. **Restore specific config:**
|
||||
```bash
|
||||
export RESTIC_PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
|
||||
# Restore nginx config
|
||||
restic -r /mnt/nas/backups/homelab-restic restore latest \
|
||||
--target /tmp/restore --include /etc/nginx/
|
||||
|
||||
sudo cp -r /tmp/restore/etc/nginx/* /etc/nginx/
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
2. **Downgrade packages:**
|
||||
```bash
|
||||
# Check which packages were updated
|
||||
grep -A5 "upgraded" /var/log/pacman.log | tail -20
|
||||
|
||||
# Downgrade specific package to cached version
|
||||
sudo pacman -U /var/cache/pacman/pkg/package-old-version.pkg.tar.zst
|
||||
```
|
||||
|
||||
### Restore Specific Docker Volume
|
||||
|
||||
```bash
|
||||
export RESTIC_PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
|
||||
# List files in backup
|
||||
restic -r /mnt/nas/backups/homelab-restic ls latest | grep docker-backup
|
||||
|
||||
# Restore specific volume tarball
|
||||
restic -r /mnt/nas/backups/homelab-restic restore latest \
|
||||
--target /tmp/restore \
|
||||
--include /tmp/docker-backup-*/nextcloud-data.tar.gz
|
||||
|
||||
# Extract and restore to Docker volume
|
||||
mkdir /tmp/nextcloud-data
|
||||
tar xzf /tmp/restore/tmp/docker-backup-*/nextcloud-data.tar.gz -C /tmp/nextcloud-data
|
||||
|
||||
docker run --rm -v nextcloud_nextcloud_data:/data -v /tmp/nextcloud-data:/backup alpine \
|
||||
sh -c "rm -rf /data/* && cp -a /backup/* /data/"
|
||||
```
|
||||
|
||||
## Backup Retention Policy
|
||||
|
||||
The backup script automatically prunes old backups:
|
||||
- **Daily**: Keep last 7 days
|
||||
- **Weekly**: Keep last 4 weeks
|
||||
- **Monthly**: Keep last 6 months
|
||||
- **Yearly**: Keep last 2 years
|
||||
|
||||
This balances storage space with recovery options.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Repository not initialized" Error
|
||||
|
||||
Run the initialization script:
|
||||
```bash
|
||||
sudo /home/hoborg/homelab/scripts/backup-init.sh
|
||||
```
|
||||
|
||||
### "Password incorrect" Error
|
||||
|
||||
Check that password file exists and is readable:
|
||||
```bash
|
||||
ls -l /home/hoborg/creds/restic-password.txt
|
||||
```
|
||||
|
||||
### Check Backup Size
|
||||
|
||||
```bash
|
||||
export RESTIC_PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
restic -r /mnt/nas/backups/homelab-restic stats --mode restore-size
|
||||
```
|
||||
|
||||
### Verify Backup Integrity
|
||||
|
||||
```bash
|
||||
export RESTIC_PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
restic -r /mnt/nas/backups/homelab-restic check
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Backups are encrypted with AES-256
|
||||
- Password file has `600` permissions (owner read/write only)
|
||||
- Shadow/gshadow files are excluded from backups
|
||||
- NAS should have access restrictions
|
||||
- Consider offsite backup copy of password file
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Automated systemd timer setup
|
||||
- [ ] Pre-backup notifications
|
||||
- [ ] Post-backup success/failure notifications
|
||||
- [ ] Backup verification script
|
||||
- [ ] Web dashboard for backup status
|
||||
- [ ] Offsite backup replication (optional)
|
||||
@@ -1,5 +1,7 @@
|
||||
# Voice Assistant Setup
|
||||
|
||||
⚠️ **STATUS: DISABLED** - onnxruntime package was removed to free disk space (1.2GB). Voice functionality is currently unavailable.
|
||||
|
||||
This document describes how to set up AI voice capabilities for Claude Code using local TTS (Text-to-Speech) services.
|
||||
|
||||
## Overview
|
||||
@@ -8,6 +10,7 @@ The voice assistant setup uses:
|
||||
- **Piper TTS**: Local neural text-to-speech engine for generating natural-sounding speech
|
||||
- **FastAPI**: HTTP server wrapper to make Piper compatible with voice-mode
|
||||
- **Ryan voice model**: Professional male US English voice for AI assistant personality
|
||||
- **onnxruntime**: ML inference library (removed - required for TTS)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
95
scripts/backup-homelab.sh
Executable file
95
scripts/backup-homelab.sh
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
# Backup Docker volumes and system configs to NAS using Restic
|
||||
# Can be run manually or via systemd timer
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BACKUP_REPO="/mnt/nas/backups/homelab-restic"
|
||||
PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
LOG_FILE="/var/log/homelab-backup.log"
|
||||
DOCKER_BACKUP_DIR="/tmp/docker-backup-$$"
|
||||
|
||||
# Export password
|
||||
export RESTIC_PASSWORD_FILE="$PASSWORD_FILE"
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "========================================"
|
||||
log "Starting Homelab Backup"
|
||||
log "========================================"
|
||||
|
||||
# Check if repository exists
|
||||
if ! restic -r "$BACKUP_REPO" snapshots &>/dev/null; then
|
||||
log "ERROR: Backup repository not initialized. Run backup-init.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temporary directory for Docker volume exports
|
||||
mkdir -p "$DOCKER_BACKUP_DIR"
|
||||
log "Created temporary backup directory: $DOCKER_BACKUP_DIR"
|
||||
|
||||
# Export Docker volumes
|
||||
log "Exporting Docker volumes..."
|
||||
|
||||
# Gitea
|
||||
log " - Exporting Gitea data..."
|
||||
docker run --rm -v gitea_gitea:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/gitea-data.tar.gz -C /data .
|
||||
|
||||
# Jellyfin
|
||||
log " - Exporting Jellyfin config..."
|
||||
docker run --rm -v jellyfin_jellyfin_config:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/jellyfin-config.tar.gz -C /data .
|
||||
|
||||
# Nextcloud data
|
||||
log " - Exporting Nextcloud data..."
|
||||
docker run --rm -v nextcloud_nextcloud_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/nextcloud-data.tar.gz -C /data .
|
||||
|
||||
# Nextcloud database
|
||||
log " - Exporting Nextcloud database..."
|
||||
docker run --rm -v nextcloud_nextcloud_db:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/nextcloud-db.tar.gz -C /data .
|
||||
|
||||
# Portainer
|
||||
log " - Exporting Portainer data..."
|
||||
docker run --rm -v portainer_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/portainer-data.tar.gz -C /data .
|
||||
|
||||
log "✓ Docker volumes exported"
|
||||
|
||||
# Run backup
|
||||
log "Running Restic backup..."
|
||||
restic -r "$BACKUP_REPO" backup \
|
||||
--verbose \
|
||||
--tag homelab-docker \
|
||||
--tag homelab-configs \
|
||||
/etc/nginx/sites-available/homelab \
|
||||
/etc/systemd/system/copyparty.service \
|
||||
/home/hoborg/.config/copyparty/ \
|
||||
"$DOCKER_BACKUP_DIR"
|
||||
|
||||
# Cleanup temporary files
|
||||
log "Cleaning up temporary files..."
|
||||
rm -rf "$DOCKER_BACKUP_DIR"
|
||||
|
||||
# Prune old backups (keep: 7 daily, 4 weekly, 6 monthly, 2 yearly)
|
||||
log "Pruning old backups..."
|
||||
restic -r "$BACKUP_REPO" forget \
|
||||
--keep-daily 7 \
|
||||
--keep-weekly 4 \
|
||||
--keep-monthly 6 \
|
||||
--keep-yearly 2 \
|
||||
--prune
|
||||
|
||||
# Show backup stats
|
||||
log "Backup statistics:"
|
||||
restic -r "$BACKUP_REPO" stats --mode restore-size
|
||||
|
||||
log "========================================"
|
||||
log "Backup completed successfully!"
|
||||
log "========================================"
|
||||
74
scripts/backup-init.sh
Executable file
74
scripts/backup-init.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
# Initialize Restic backup repository on NAS
|
||||
# Run once to set up the backup system
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BACKUP_REPO="/mnt/nas/backups/homelab-restic"
|
||||
PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
|
||||
echo "========================================"
|
||||
echo "Restic Backup Repository Initialization"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "This will:"
|
||||
echo " 1. Install restic if needed"
|
||||
echo " 2. Create backup repository at: $BACKUP_REPO"
|
||||
echo " 3. Generate and save encryption password"
|
||||
echo ""
|
||||
|
||||
# Check if restic is installed
|
||||
if ! command -v restic &> /dev/null; then
|
||||
echo "Installing restic..."
|
||||
pacman -S --needed --noconfirm restic
|
||||
echo "✓ Restic installed"
|
||||
else
|
||||
echo "✓ Restic already installed ($(restic version))"
|
||||
fi
|
||||
|
||||
# Create backup directory on NAS
|
||||
echo ""
|
||||
echo "Creating backup directory on NAS..."
|
||||
mkdir -p "$BACKUP_REPO"
|
||||
echo "✓ Directory created: $BACKUP_REPO"
|
||||
|
||||
# Generate random password if doesn't exist
|
||||
echo ""
|
||||
if [ -f "$PASSWORD_FILE" ]; then
|
||||
echo "✓ Password file already exists: $PASSWORD_FILE"
|
||||
else
|
||||
echo "Generating encryption password..."
|
||||
mkdir -p /home/hoborg/creds
|
||||
openssl rand -base64 32 > "$PASSWORD_FILE"
|
||||
chmod 600 "$PASSWORD_FILE"
|
||||
echo "✓ Password saved to: $PASSWORD_FILE"
|
||||
echo "⚠️ IMPORTANT: Back up this password file! Without it, backups cannot be restored."
|
||||
fi
|
||||
|
||||
# Export password for restic
|
||||
export RESTIC_PASSWORD_FILE="$PASSWORD_FILE"
|
||||
|
||||
# Initialize repository
|
||||
echo ""
|
||||
echo "Initializing Restic repository..."
|
||||
if restic -r "$BACKUP_REPO" snapshots &>/dev/null; then
|
||||
echo "✓ Repository already initialized"
|
||||
else
|
||||
restic -r "$BACKUP_REPO" init
|
||||
echo "✓ Repository initialized"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Backup Repository Ready!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Repository location: $BACKUP_REPO"
|
||||
echo "Password file: $PASSWORD_FILE"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Run backup-homelab.sh to create first backup"
|
||||
echo " 2. Test restore with backup-restore.sh"
|
||||
echo " 3. Enable automated backups with systemd timer"
|
||||
echo ""
|
||||
128
scripts/backup-system-full.sh
Executable file
128
scripts/backup-system-full.sh
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
# Full system backup - packages, configs, and Docker volumes
|
||||
# Can be run manually or via systemd timer
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BACKUP_REPO="/mnt/nas/backups/homelab-restic"
|
||||
PASSWORD_FILE="/home/hoborg/creds/restic-password.txt"
|
||||
LOG_FILE="/var/log/homelab-backup.log"
|
||||
DOCKER_BACKUP_DIR="/tmp/docker-backup-$$"
|
||||
SYSTEM_BACKUP_DIR="/tmp/system-backup-$$"
|
||||
|
||||
# Export password
|
||||
export RESTIC_PASSWORD_FILE="$PASSWORD_FILE"
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "========================================"
|
||||
log "Starting Full System Backup"
|
||||
log "========================================"
|
||||
|
||||
# Check if repository exists
|
||||
if ! restic -r "$BACKUP_REPO" snapshots &>/dev/null; then
|
||||
log "ERROR: Backup repository not initialized. Run backup-init.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temporary directories
|
||||
mkdir -p "$DOCKER_BACKUP_DIR"
|
||||
mkdir -p "$SYSTEM_BACKUP_DIR"
|
||||
log "Created temporary directories"
|
||||
|
||||
# ============================================
|
||||
# 1. Export Package Lists
|
||||
# ============================================
|
||||
log "Exporting package lists..."
|
||||
pacman -Qqe > "$SYSTEM_BACKUP_DIR/pacman-explicit.txt" # Explicitly installed packages
|
||||
pacman -Qq > "$SYSTEM_BACKUP_DIR/pacman-all.txt" # All installed packages
|
||||
pacman -Qqm > "$SYSTEM_BACKUP_DIR/aur-packages.txt" # AUR/foreign packages only
|
||||
log "✓ Package lists exported ($(wc -l < $SYSTEM_BACKUP_DIR/pacman-explicit.txt) explicit, $(wc -l < $SYSTEM_BACKUP_DIR/aur-packages.txt) AUR)"
|
||||
|
||||
# ============================================
|
||||
# 2. Export Docker Volumes
|
||||
# ============================================
|
||||
log "Exporting Docker volumes..."
|
||||
|
||||
# Gitea
|
||||
log " - Exporting Gitea data..."
|
||||
docker run --rm -v gitea_gitea:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/gitea-data.tar.gz -C /data .
|
||||
|
||||
# Jellyfin
|
||||
log " - Exporting Jellyfin config..."
|
||||
docker run --rm -v jellyfin_jellyfin_config:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/jellyfin-config.tar.gz -C /data .
|
||||
|
||||
# Nextcloud data
|
||||
log " - Exporting Nextcloud data..."
|
||||
docker run --rm -v nextcloud_nextcloud_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/nextcloud-data.tar.gz -C /data .
|
||||
|
||||
# Nextcloud database
|
||||
log " - Exporting Nextcloud database..."
|
||||
docker run --rm -v nextcloud_nextcloud_db:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/nextcloud-db.tar.gz -C /data .
|
||||
|
||||
# Portainer
|
||||
log " - Exporting Portainer data..."
|
||||
docker run --rm -v portainer_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \
|
||||
tar czf /backup/portainer-data.tar.gz -C /data .
|
||||
|
||||
log "✓ Docker volumes exported"
|
||||
|
||||
# ============================================
|
||||
# 3. Run Backup
|
||||
# ============================================
|
||||
log "Running Restic backup..."
|
||||
restic -r "$BACKUP_REPO" backup \
|
||||
--verbose \
|
||||
--tag full-system \
|
||||
--exclude '/etc/shadow*' \
|
||||
--exclude '/etc/gshadow*' \
|
||||
--exclude '/etc/passwd-' \
|
||||
--exclude '/etc/group-' \
|
||||
/etc/ \
|
||||
"$DOCKER_BACKUP_DIR" \
|
||||
"$SYSTEM_BACKUP_DIR"
|
||||
|
||||
# ============================================
|
||||
# 4. Cleanup
|
||||
# ============================================
|
||||
log "Cleaning up temporary files..."
|
||||
rm -rf "$DOCKER_BACKUP_DIR"
|
||||
rm -rf "$SYSTEM_BACKUP_DIR"
|
||||
|
||||
# ============================================
|
||||
# 5. Prune Old Backups
|
||||
# ============================================
|
||||
log "Pruning old backups..."
|
||||
restic -r "$BACKUP_REPO" forget \
|
||||
--keep-daily 7 \
|
||||
--keep-weekly 4 \
|
||||
--keep-monthly 3 \
|
||||
--keep-yearly 1 \
|
||||
--prune
|
||||
|
||||
# ============================================
|
||||
# 6. Show Stats
|
||||
# ============================================
|
||||
log "Backup statistics:"
|
||||
restic -r "$BACKUP_REPO" stats --mode restore-size
|
||||
|
||||
log "========================================"
|
||||
log "Full system backup completed!"
|
||||
log "========================================"
|
||||
log ""
|
||||
log "Backed up:"
|
||||
log " - /etc/ (all system configs)"
|
||||
log " - Docker volumes (Gitea, Jellyfin, Nextcloud, Portainer)"
|
||||
log " - Package lists (pacman + AUR)"
|
||||
log ""
|
||||
log "To restore packages after reinstall:"
|
||||
log " pacman -S --needed \$(cat pacman-explicit.txt)"
|
||||
log " yay -S --needed \$(cat aur-packages.txt)"
|
||||
130
scripts/setup-docker-symlinks.sh
Executable file
130
scripts/setup-docker-symlinks.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# Set up symlinks for Docker configurations to avoid manual copying
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "==================================================================="
|
||||
echo "Docker Configuration Symlink Setup"
|
||||
echo "==================================================================="
|
||||
echo ""
|
||||
|
||||
REPO_ROOT="/home/hoborg/homelab"
|
||||
|
||||
# Function to create symlink with backup
|
||||
create_symlink() {
|
||||
local service=$1
|
||||
local target=$2
|
||||
local link=$3
|
||||
|
||||
echo "-------------------------------------------------------------------"
|
||||
echo "Setting up: $service"
|
||||
echo "-------------------------------------------------------------------"
|
||||
echo "Target: $target"
|
||||
echo "Link: $link"
|
||||
echo ""
|
||||
|
||||
# Check if target exists
|
||||
if [ ! -f "$target" ]; then
|
||||
echo "ERROR: Target file does not exist: $target"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create parent directory if it doesn't exist
|
||||
local link_dir=$(dirname "$link")
|
||||
if [ ! -d "$link_dir" ]; then
|
||||
echo "Creating directory: $link_dir"
|
||||
mkdir -p "$link_dir"
|
||||
fi
|
||||
|
||||
# Backup existing file if it's not already a symlink
|
||||
if [ -f "$link" ] && [ ! -L "$link" ]; then
|
||||
backup="${link}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backing up existing file to: $backup"
|
||||
cp "$link" "$backup"
|
||||
rm "$link"
|
||||
elif [ -L "$link" ]; then
|
||||
echo "Removing existing symlink"
|
||||
rm "$link"
|
||||
fi
|
||||
|
||||
# Create symlink
|
||||
ln -s "$target" "$link"
|
||||
|
||||
if [ -L "$link" ]; then
|
||||
echo "✓ Symlink created successfully"
|
||||
else
|
||||
echo "✗ Failed to create symlink"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Gitea docker-compose.yml
|
||||
create_symlink "Gitea docker-compose" \
|
||||
"$REPO_ROOT/config/docker/gitea/docker-compose.yml" \
|
||||
"/opt/docker/gitea/docker-compose.yml"
|
||||
|
||||
# Jellyfin docker-compose.yml
|
||||
create_symlink "Jellyfin docker-compose" \
|
||||
"$REPO_ROOT/config/docker/jellyfin/docker-compose.yml" \
|
||||
"/opt/docker/jellyfin/docker-compose.yml"
|
||||
|
||||
# qBittorrent docker-compose.yml
|
||||
create_symlink "qBittorrent docker-compose" \
|
||||
"$REPO_ROOT/config/docker/qbittorrent/docker-compose.yml" \
|
||||
"/opt/docker/qbittorrent/docker-compose.yml"
|
||||
|
||||
# Portainer docker-compose.yml
|
||||
create_symlink "Portainer docker-compose" \
|
||||
"$REPO_ROOT/config/docker/portainer/docker-compose.yml" \
|
||||
"/opt/docker/portainer/docker-compose.yml"
|
||||
|
||||
# Copyparty docker-compose.yml
|
||||
create_symlink "Copyparty docker-compose" \
|
||||
"$REPO_ROOT/config/docker/copyparty/docker-compose.yml" \
|
||||
"/opt/docker/copyparty/docker-compose.yml"
|
||||
|
||||
# Glances docker-compose.yml
|
||||
create_symlink "Glances docker-compose" \
|
||||
"$REPO_ROOT/config/docker/glances/docker-compose.yml" \
|
||||
"/opt/docker/glances/docker-compose.yml"
|
||||
|
||||
# Syncthing docker-compose.yml
|
||||
create_symlink "Syncthing docker-compose" \
|
||||
"$REPO_ROOT/config/docker/syncthing/docker-compose.yml" \
|
||||
"/opt/docker/syncthing/docker-compose.yml"
|
||||
|
||||
# Docker daemon config
|
||||
create_symlink "Docker daemon config" \
|
||||
"$REPO_ROOT/config/docker/daemon.json" \
|
||||
"/etc/docker/daemon.json"
|
||||
|
||||
# Landing page
|
||||
create_symlink "Landing page" \
|
||||
"$REPO_ROOT/config/www/index.html" \
|
||||
"/var/www/homelab/index.html"
|
||||
|
||||
echo "==================================================================="
|
||||
echo "Summary"
|
||||
echo "==================================================================="
|
||||
echo ""
|
||||
echo "✓ All symlinks created successfully!"
|
||||
echo ""
|
||||
echo "Notes:"
|
||||
echo " - Original files backed up with timestamp"
|
||||
echo " - Edit files in $REPO_ROOT/config/docker/"
|
||||
echo " - Changes take effect immediately (no copying needed)"
|
||||
echo " - For daemon.json: restart docker after changes"
|
||||
echo " - For docker-compose: restart containers after changes"
|
||||
echo ""
|
||||
echo "Verify symlinks:"
|
||||
echo " ls -l /opt/docker/gitea/docker-compose.yml"
|
||||
echo " ls -l /opt/docker/jellyfin/docker-compose.yml"
|
||||
echo " ls -l /opt/docker/qbittorrent/docker-compose.yml"
|
||||
echo " ls -l /opt/docker/portainer/docker-compose.yml"
|
||||
echo " ls -l /opt/docker/copyparty/docker-compose.yml"
|
||||
echo " ls -l /opt/docker/glances/docker-compose.yml"
|
||||
echo " ls -l /opt/docker/syncthing/docker-compose.yml"
|
||||
echo " ls -l /etc/docker/daemon.json"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user