Compare commits
25 Commits
12ff0e15bd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c842b4a398 | |||
| 14cc6aa50d | |||
| ee0c7035d3 | |||
| ea1c71f0f0 | |||
| 059daa77aa | |||
| 9aa881d895 | |||
| ab4a4cfc9c | |||
| 646d3e59bc | |||
| ad7270aa74 | |||
| 7e64557f0b | |||
| acc30e7c3d | |||
| 3d57327146 | |||
| d0727bd8a7 | |||
| 7eaf16f53e | |||
| bab97f7166 | |||
| 6894c5f175 | |||
| 6761c8903d | |||
| a8bbf5ea4a | |||
| fefdc14398 | |||
| 802d275d6e | |||
| dc16d0302d | |||
| 64a43c8a96 | |||
| 43cfd25798 | |||
| 00f4fcbc1c | |||
| 31a9568fd4 |
52
.claude/skills/deploy-config/SKILL.md
Normal file
52
.claude/skills/deploy-config/SKILL.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: deploy-config
|
||||
description: Set up symlinks from system config locations to this repo. Use when setting up on a new machine, or when a config file is found to be a real file instead of a symlink (config drift). Not for routine config changes — just edit the repo file directly.
|
||||
disable-model-invocation: true
|
||||
allowed-tools: Write, Bash(chmod *), Bash(diff *), Bash(ls *)
|
||||
---
|
||||
|
||||
All system configs should be symlinked to this repo. Editing the repo file IS editing the live config.
|
||||
|
||||
## Symlink Map
|
||||
|
||||
| System location | Repo source |
|
||||
|---|---|
|
||||
| `/etc/nginx/sites-available/homelab` | `config/nginx/homelab.conf` |
|
||||
| `/var/www/homelab/index.html` | `config/www/index.html` |
|
||||
| `/etc/systemd/system/copyparty.service` | `config/systemd/copyparty.service` |
|
||||
| `/etc/systemd/system/glances-web.service` | `config/systemd/glances-web.service` |
|
||||
| `/etc/systemd/system/ssh-honeypot.service` | `config/systemd/ssh-honeypot.service` |
|
||||
| `/opt/docker/<service>/docker-compose.yml` | `config/docker/<service>/docker-compose.yml` |
|
||||
|
||||
## When to use this skill
|
||||
|
||||
- **New machine setup**: Create all symlinks from scratch
|
||||
- **Drift detected**: A system file is a real file instead of a symlink — replace it with a symlink
|
||||
- **New config added to repo**: Create the initial symlink for it
|
||||
|
||||
## How to create/fix a symlink
|
||||
|
||||
Generate a script in `scripts/tmp/symlink-<name>.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
REPO="/home/hoborg/homelab/<repo-path>"
|
||||
SYSTEM="<system-path>"
|
||||
|
||||
# Back up if it's a real file (not already a symlink)
|
||||
[ ! -L "$SYSTEM" ] && cp "$SYSTEM" "${SYSTEM}.backup.$(date +%Y%m%d)" && echo "Backup created"
|
||||
|
||||
ln -sf "$REPO" "$SYSTEM" && echo "Symlink created"
|
||||
# Add post-link steps here (e.g. nginx -t && systemctl reload nginx, systemctl daemon-reload)
|
||||
```
|
||||
|
||||
Tell the user to run: `sudo bash ~/homelab/scripts/tmp/symlink-<name>.sh`
|
||||
|
||||
## Routine config changes
|
||||
|
||||
Just edit `config/<path>` in the repo. The symlink means it's already live. Then:
|
||||
- Nginx: `sudo nginx -t && sudo systemctl reload nginx`
|
||||
- Systemd unit: `sudo systemctl daemon-reload && sudo systemctl restart <service>`
|
||||
- Docker: `cd /opt/docker/<service> && docker compose restart`
|
||||
- www/index.html: no action needed (served directly)
|
||||
98
.claude/skills/homelab-context/SKILL.md
Normal file
98
.claude/skills/homelab-context/SKILL.md
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: homelab-context
|
||||
description: Background reference for the homelab repo. Auto-loads key facts about services, ports, paths, and operational rules when working in this project.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Domain**: ak-homelab.duckdns.org (DuckDNS)
|
||||
- **Static IP**: 192.168.0.100 (interface: enp4s0)
|
||||
- **SSH**: port 2222 (system), port 2223 (Gitea git)
|
||||
- **NAS**: 192.168.0.101, mounted at `/mnt/nas/`
|
||||
|
||||
## Services & Ports
|
||||
|
||||
| Service | Port | Type | Path |
|
||||
|-------------|-------|----------|-----------------|
|
||||
| Nginx | 80/443| systemd | reverse proxy |
|
||||
| Gitea | 3000 | Docker | /gitea/ |
|
||||
| Jellyfin | 8096 | Docker | /media/ |
|
||||
| Copyparty | 8082 | systemd | /files/ |
|
||||
| Netdata | 19999 | Docker | /netdata/ |
|
||||
| Portainer | 9000 | Docker | /portainer/ |
|
||||
| qBittorrent | 8080 | Docker | /qbt/ |
|
||||
|
||||
## Key Paths
|
||||
|
||||
All configs are **symlinked** from system locations to this repo — editing the repo file is editing the live config:
|
||||
|
||||
| System location | Repo source |
|
||||
|---|---|
|
||||
| `/opt/docker/<service>/docker-compose.yml` | `config/docker/<service>/docker-compose.yml` |
|
||||
| `/etc/nginx/sites-available/homelab` | `config/nginx/homelab.conf` |
|
||||
| `/var/www/homelab/index.html` | `config/www/index.html` |
|
||||
| `/etc/systemd/system/copyparty.service` | `config/systemd/copyparty.service` |
|
||||
| `/etc/systemd/system/glances-web.service` | `config/systemd/glances-web.service` |
|
||||
| `/etc/systemd/system/ssh-honeypot.service` | `config/systemd/ssh-honeypot.service` |
|
||||
|
||||
- NAS docker data: `/mnt/nas/docker-data/<service>/`
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### Docker UID for NAS-mounted volumes
|
||||
Services with data on NAS (`/mnt/nas/`) must use `USER_UID=1024 USER_GID=100` to match NAS file ownership. Services with local storage use `1000:1000`.
|
||||
|
||||
### Docker config changes
|
||||
Edit `config/docker/<service>/docker-compose.yml` in repo → restart container. No copy needed (symlinked).
|
||||
|
||||
### After editing a config in repo
|
||||
- **Docker**: `cd /opt/docker/<service> && docker compose restart`
|
||||
- **Nginx**: `sudo nginx -t && sudo systemctl reload nginx`
|
||||
- **Systemd unit**: `sudo systemctl daemon-reload && sudo systemctl restart <service>`
|
||||
- **www/index.html**: no action needed
|
||||
|
||||
### Sudo scripts
|
||||
Never run sudo directly. Generate a script in `scripts/tmp/` and run `sudo bash ~/homelab/scripts/tmp/<script>.sh`.
|
||||
The sudoers rule `/etc/sudoers.d/homelab-scripts` grants NOPASSWD for `scripts/tmp/*` — no password needed.
|
||||
|
||||
### s6-overlay containers (Gitea)
|
||||
Do NOT set `user:` directive in docker-compose. Use `USER_UID`/`USER_GID` env vars instead — s6-overlay needs root to start, then drops privileges.
|
||||
|
||||
### Never copy any config files
|
||||
All configs are symlinked. Copying a file breaks the link and causes drift from the repo.
|
||||
|
||||
## NAS Services Currently Using 1024:100
|
||||
- Gitea (`/mnt/nas/docker-data/gitea/data`)
|
||||
- Nextcloud app + db (`/mnt/nas/docker-data/nextcloud/`)
|
||||
|
||||
## Management Quick Reference
|
||||
|
||||
```bash
|
||||
# Docker service (from anywhere)
|
||||
cd /opt/docker/<service> && docker compose logs -f
|
||||
cd /opt/docker/<service> && docker compose restart
|
||||
cd /opt/docker/<service> && docker compose down && docker compose up -d
|
||||
|
||||
# Systemd services
|
||||
sudo systemctl status nginx copyparty netdata
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# Nginx
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
# NAS mount check
|
||||
mountpoint /mnt/nas && echo mounted
|
||||
```
|
||||
|
||||
## Keeping Skills Up To Date
|
||||
|
||||
After any change to service configuration, ports, paths, Docker UIDs, or operational rules:
|
||||
- Update this skill file (`homelab-context/SKILL.md`) to reflect the new state
|
||||
- Update `homelab-status/SKILL.md` if services are added or removed
|
||||
- This ensures future sessions always have accurate context without re-reading files
|
||||
|
||||
## Docs Reference
|
||||
- Full service install steps (for migration): `docs/services.md`
|
||||
- Network & security: `docs/network-security.md`
|
||||
- Troubleshooting: `docs/troubleshooting/`
|
||||
15
.claude/skills/homelab-status/SKILL.md
Normal file
15
.claude/skills/homelab-status/SKILL.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: homelab-status
|
||||
description: Check status of all homelab services at once.
|
||||
disable-model-invocation: true
|
||||
allowed-tools: Bash(docker *), Bash(systemctl *), Bash(mountpoint *), Bash(nginx *)
|
||||
---
|
||||
|
||||
Check and report status of all homelab services:
|
||||
|
||||
1. Run `docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"` to show all containers
|
||||
2. Run `systemctl is-active nginx copyparty netdata` to show systemd service states
|
||||
3. Run `mountpoint /mnt/nas` to confirm NAS is mounted
|
||||
4. Run `sudo nginx -t` to verify nginx config is valid
|
||||
5. Report any services that are stopped, failed, or unhealthy
|
||||
6. Note any containers not running that should be (gitea, jellyfin, portainer, qbittorrent, nextcloud)
|
||||
76
AGENTS.md
76
AGENTS.md
@@ -1,76 +0,0 @@
|
||||
# AI Agent Instructions and Restrictions
|
||||
|
||||
## CRITICAL SECURITY RESTRICTIONS
|
||||
|
||||
### ❌ SUDO COMMAND PROHIBITION
|
||||
|
||||
**NEVER, UNDER ANY CIRCUMSTANCES, RUN SUDO COMMANDS**
|
||||
|
||||
The AI agent MUST NOT execute any commands that require elevated privileges:
|
||||
- ❌ `sudo` commands
|
||||
- ❌ `su` commands
|
||||
- ❌ Commands that modify system files directly
|
||||
- ❌ Commands that require root privileges
|
||||
|
||||
### ✅ APPROVED ALTERNATIVES
|
||||
|
||||
Instead of running sudo commands, the AI should:
|
||||
1. **Create scripts** that the user can run with `sudo -A`
|
||||
2. **Document commands** for the user to execute manually
|
||||
3. **Explain what needs to be done** and why
|
||||
4. **Provide step-by-step instructions** for the user
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**❌ WRONG:**
|
||||
```bash
|
||||
sudo systemctl restart nginx
|
||||
sudo chmod 755 /etc/nginx/conf.d/
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
```bash
|
||||
# Create a script for the user to run
|
||||
echo "systemctl restart nginx" > /tmp/restart-nginx.sh
|
||||
chmod +x /tmp/restart-nginx.sh
|
||||
|
||||
# Then tell the user:
|
||||
# "Run: sudo -A /tmp/restart-nginx.sh"
|
||||
```
|
||||
|
||||
### RATIONALE
|
||||
|
||||
- User explicitly denied AI access to sudo
|
||||
- Security best practice: AI should not have root privileges
|
||||
- User prefers manual control over system changes
|
||||
- Prevents accidental system modifications
|
||||
|
||||
### VERIFICATION
|
||||
|
||||
This restriction has been tested and verified:
|
||||
- ✅ AI cannot run sudo commands via bash tool
|
||||
- ✅ AI will create scripts instead
|
||||
- ✅ User maintains full control over privileged operations
|
||||
|
||||
## OTHER OPERATIONAL GUIDELINES
|
||||
|
||||
### File Operations
|
||||
- ✅ Read files in user directories
|
||||
- ✅ Write files in user directories
|
||||
- ✅ Create scripts in `/tmp` or user directories
|
||||
- ❌ Modify system configuration files directly
|
||||
|
||||
### Network Operations
|
||||
- ✅ Check network status with unprivileged commands
|
||||
- ✅ Test connectivity
|
||||
- ❌ Modify firewall rules directly
|
||||
- ❌ Bind to privileged ports (< 1024)
|
||||
|
||||
### Service Management
|
||||
- ❌ Start/stop/restart system services directly
|
||||
- ✅ Check service status with unprivileged commands
|
||||
- ✅ Create systemd service files for user to deploy
|
||||
|
||||
---
|
||||
|
||||
**REMEMBER: When in doubt, create a script and let the user run it with sudo.**
|
||||
230
CLAUDE.md
230
CLAUDE.md
@@ -4,192 +4,106 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a personal homelab setup repository documenting the migration from cloud services to self-hosted solutions on a ThinkPad running Arch Linux. The project uses Docker for containerized services behind an Nginx reverse proxy.
|
||||
Personal homelab on a ThinkPad running Arch Linux. Docker services behind an Nginx reverse proxy. Full context is in `.claude/skills/homelab-context/SKILL.md`.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Network Infrastructure
|
||||
- **Domain**: ak-homelab.duckdns.org (DuckDNS dynamic DNS)
|
||||
- **Static IP**: 192.168.0.100 (dual interface: ethernet enp4s0, WiFi wlp1s0)
|
||||
- **SSH**: Custom port 2222 for system access
|
||||
- **Reverse Proxy**: Nginx for path-based routing to services
|
||||
- **Domain**: ak-homelab.duckdns.org (DuckDNS)
|
||||
- **Static IP**: 192.168.0.100 (interface: enp4s0)
|
||||
- **NAS**: 192.168.0.101, mounted at `/mnt/nas/`
|
||||
- **Reverse Proxy**: Nginx → path-based routing to services
|
||||
|
||||
## Configuration Management
|
||||
|
||||
All configs are version-controlled in `config/`. Docker compose files are **always symlinked** — never copied:
|
||||
|
||||
### Service Architecture
|
||||
```
|
||||
Internet → Router → Nginx (80/443) → Services
|
||||
→ SSH (2222) → System
|
||||
→ Gitea SSH (2223) → Git operations
|
||||
/opt/docker/<service>/docker-compose.yml → ~/homelab/config/docker/<service>/docker-compose.yml
|
||||
```
|
||||
|
||||
**Current Services:**
|
||||
- Gitea Git server: Docker container on port 3000, accessible via `/gitea/` path
|
||||
- Copyparty file server: Systemd service on port 8082, accessible via `/files/` path with WebDAV support
|
||||
- Jellyfin media server: Docker container on port 8096, accessible via `/media/` path
|
||||
- Nginx: Reverse proxy routing to `ak-homelab.duckdns.org/servicename/`
|
||||
Editing the repo file is editing the live config. Restart the container to apply changes.
|
||||
|
||||
### Configuration Management
|
||||
All configs — Docker, nginx, systemd units, www — are symlinked to this repo. Editing a file in `config/` is editing the live config. Use `/deploy-config` only on a new machine to create symlinks from scratch, or if drift is detected (a real file exists instead of a symlink).
|
||||
|
||||
All configurations are version controlled in the `config/` directory:
|
||||
- `config/docker/gitea/`: Gitea container setup and deployment scripts
|
||||
- `config/docker/jellyfin/`: Jellyfin media server configuration
|
||||
- `config/nginx/`: Reverse proxy configurations with SSL and WebDAV support
|
||||
- `config/scripts/`: Utility scripts for system maintenance
|
||||
- `~/.config/copyparty/`: Copyparty file server configuration
|
||||
## Sudo Access
|
||||
|
||||
Configuration files include deployment instructions in comments showing target locations (e.g., `/opt/docker/gitea/`, `/etc/nginx/sites-available/`).
|
||||
A sudoers rule at `/etc/sudoers.d/homelab-scripts` grants NOPASSWD for scripts in `scripts/tmp/`:
|
||||
```
|
||||
hoborg ALL=(ALL) NOPASSWD: /bin/bash /home/hoborg/homelab/scripts/tmp/*
|
||||
```
|
||||
Scripts generated there can be run with `sudo bash ~/homelab/scripts/tmp/<script>.sh` — no password needed.
|
||||
|
||||
Never run sudo commands directly. Always create a script in `scripts/tmp/`.
|
||||
|
||||
## Docker Guidelines
|
||||
|
||||
- **NAS-mounted volumes** (`/mnt/nas/`): use `USER_UID=1024 USER_GID=100` to match NAS file ownership
|
||||
- **Local volumes**: use `USER_UID=1000 USER_GID=1000`
|
||||
- **s6-overlay containers** (Gitea): do NOT set `user:` directive — use `USER_UID`/`USER_GID` env vars only
|
||||
- **Never copy docker-compose files** — they are symlinked; copying breaks the link and causes config drift
|
||||
|
||||
## Key Commands
|
||||
|
||||
### Docker Service Management
|
||||
### Docker
|
||||
```bash
|
||||
# Gitea operations (from /opt/docker/gitea/)
|
||||
docker-compose logs gitea # View logs
|
||||
docker-compose down # Stop
|
||||
docker-compose up -d # Start
|
||||
docker-compose pull && docker-compose up -d # Update
|
||||
|
||||
# Jellyfin operations (from /opt/docker/jellyfin/)
|
||||
docker-compose logs jellyfin # View logs
|
||||
docker-compose down # Stop
|
||||
docker-compose up -d # Start
|
||||
docker-compose pull && docker-compose up -d # Update
|
||||
|
||||
# Deploy from repo
|
||||
sudo cp config/docker/gitea/docker-compose.yml /opt/docker/gitea/
|
||||
sudo cp config/docker/jellyfin/docker-compose.yml /opt/docker/jellyfin/
|
||||
cd /opt/docker/<service>
|
||||
docker compose logs -f # View logs
|
||||
docker compose restart # Restart
|
||||
docker compose down && docker compose up -d # Full restart
|
||||
```
|
||||
|
||||
### Copyparty File Server Management
|
||||
### Nginx
|
||||
```bash
|
||||
# Service operations
|
||||
sudo systemctl status copyparty # Check status
|
||||
sudo systemctl start copyparty # Start service
|
||||
sudo systemctl stop copyparty # Stop service
|
||||
sudo systemctl restart copyparty # Restart (reload config)
|
||||
|
||||
# Configuration
|
||||
sudo cp ~/.config/copyparty/copyparty.conf ~/.config/copyparty/copyparty.conf.backup
|
||||
# Edit config and restart service to reload
|
||||
```
|
||||
|
||||
### Nginx Operations
|
||||
```bash
|
||||
# Deploy configuration
|
||||
sudo cp config/nginx/homelab.conf /etc/nginx/sites-available/homelab
|
||||
sudo ln -s /etc/nginx/sites-available/homelab /etc/nginx/sites-enabled/homelab
|
||||
|
||||
# Management
|
||||
sudo nginx -t # Test config
|
||||
sudo systemctl reload nginx # Reload
|
||||
sudo systemctl status nginx # Status
|
||||
sudo systemctl reload nginx # Reload (use restart for major changes)
|
||||
```
|
||||
|
||||
### SSL Certificate Management
|
||||
### Systemd Services
|
||||
```bash
|
||||
sudo certbot --nginx -d ak-homelab.duckdns.org
|
||||
sudo systemctl enable certbot.timer # Auto-renewal
|
||||
sudo systemctl status nginx copyparty
|
||||
sudo systemctl restart <service>
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Task Tracking
|
||||
Use `TODO.md` for centralized task management organized by category (Network & Security, Git & Development, System Configuration, etc.). Mark completed items and update status in documentation.
|
||||
1. Edit files in `config/` (these are the live configs for Docker via symlink)
|
||||
2. For non-Docker configs: use `/deploy-config` skill or write a `scripts/tmp/` script
|
||||
3. Update `docs/` if the change affects setup or architecture
|
||||
4. Update `.claude/skills/homelab-context/SKILL.md` if ports, paths, or rules changed
|
||||
5. Commit with a clear message
|
||||
|
||||
### Configuration Changes
|
||||
1. Edit files in `config/` directory
|
||||
2. Test locally when possible
|
||||
3. Deploy to target locations with sudo commands included in file headers
|
||||
4. Update documentation status in `docs/services.md`
|
||||
5. Commit changes with logical separation
|
||||
## Documentation Structure
|
||||
|
||||
### Documentation Structure
|
||||
- `docs/`: Technical documentation for setup procedures
|
||||
- `README.md`: Overview and current status
|
||||
- `TODO.md`: Active task tracking
|
||||
- Configuration files self-document deployment locations in headers
|
||||
- `docs/` — Human-readable reference. Keep for migration and troubleshooting.
|
||||
- `README.md` — Overview and current status
|
||||
- `TODO.md` — Active task tracking
|
||||
- `.claude/skills/homelab-context/SKILL.md` — AI-optimized operational facts (keep in sync with reality)
|
||||
|
||||
### Network Configuration Notes
|
||||
- System uses dual ethernet ports (enp3s0f0, enp4s0) - stick to enp4s0 consistently
|
||||
- Port conflicts: SSH (2222), Gitea SSH (2223), Gitea Web (3000)
|
||||
- Router forwarding: HTTP (80), HTTPS (443), SSH (2222), Git SSH (2223)
|
||||
|
||||
### Service URLs
|
||||
- **Local access**: http://192.168.0.100/servicename/
|
||||
- **External access**: https://ak-homelab.duckdns.org/servicename/
|
||||
- **Gitea SSH**: ssh://git@ak-homelab.duckdns.org:2223
|
||||
- **Voice Assistant**: http://127.0.0.1:8880 (local TTS server)
|
||||
- **Copyparty WebDAV**: https://ak-homelab.duckdns.org/files/ (for X-plore, rclone, etc.)
|
||||
|
||||
### Voice Assistant Commands
|
||||
```bash
|
||||
# Enable voice mode (starts server and configures voice-mode)
|
||||
./scripts/enable-voice.sh
|
||||
|
||||
# Disable voice mode (stops server)
|
||||
./scripts/disable-voice.sh
|
||||
|
||||
# Manual voice server management
|
||||
cd voice-server
|
||||
poetry run voice-server # Start server
|
||||
poetry install # Install dependencies
|
||||
poetry run pytest # Run tests
|
||||
|
||||
# Test TTS directly
|
||||
curl -X POST "http://127.0.0.1:8880/v1/audio/speech" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"input": "Hello world!", "voice": "ryan"}' \
|
||||
--output test.wav
|
||||
```
|
||||
|
||||
### Disk Usage Analysis Tools
|
||||
```bash
|
||||
# System-level disk space overview (recommended for regular checks)
|
||||
duf # Modern df with colors and progress bars
|
||||
|
||||
# Directory size analysis
|
||||
dust /home/hoborg # Quick visual summary with tree view
|
||||
dust /home/hoborg --depth 3 # Limit depth for large directories
|
||||
|
||||
# Interactive cleanup and detailed analysis
|
||||
ncdu /home/hoborg # Navigate with arrow keys, delete with 'd'
|
||||
ncdu /opt/docker # Analyze Docker data usage
|
||||
ncdu ~ # Scan home directory for large files
|
||||
|
||||
# Usage patterns:
|
||||
# 1. duf - Quick system health check (like htop for disk space)
|
||||
# 2. dust - Fast overview of directory sizes (like tree with sizes)
|
||||
# 3. ncdu - Deep dive cleanup and file management (like htop for files)
|
||||
```
|
||||
|
||||
### WebDAV Client Setup
|
||||
```bash
|
||||
# X-plore File Manager (Android)
|
||||
# Server: ak-homelab.duckdns.org
|
||||
# Path: /files/
|
||||
# Port: 443 (HTTPS)
|
||||
# Username: hoborg
|
||||
# Password: AdminPass2024!
|
||||
|
||||
# rclone configuration
|
||||
rclone config create homelab-webdav webdav \
|
||||
url=https://ak-homelab.duckdns.org/files/ \
|
||||
vendor=other \
|
||||
user=hoborg \
|
||||
pass=$(rclone obscure "AdminPass2024!")
|
||||
|
||||
# Mount with rclone
|
||||
rclone mount homelab-webdav: ~/homelab-files --daemon
|
||||
|
||||
# Test WebDAV
|
||||
curl -X PROPFIND https://hoborg:AdminPass2024!@ak-homelab.duckdns.org/files/ \
|
||||
-H "Depth: 1" -H "Content-Type: text/xml"
|
||||
```
|
||||
- Always edit the local configs before when possible, and then copy them to the proper location. Instead of editing system files directly (and then losing the config and it won't be in this repo)
|
||||
- Never run sudo commands, instead create a script in scripts/tmp and ask the user to run it.
|
||||
For detailed install steps per service, see `docs/services.md`.
|
||||
|
||||
## Security Hardening Guidelines
|
||||
- When working on security hardening, make sure you **understand the service needs** first, to ensure the security doesn't interfre with normal operations (e.g. some services need read/write filesystem access, not just read)
|
||||
- Some containers (like Gitea with s6-overlay) need root start then privilege drop via USER_UID/USER_GID environment variables
|
||||
|
||||
- Understand service needs before applying restrictions (some need rw filesystem access)
|
||||
- Test each security change individually, not in batches
|
||||
- Network access patterns matter: SSH Git needs direct access, HTTP can be proxied through localhost
|
||||
- DO NOT set Docker user: directive for services using s6-overlay init systems (breaks initialization)
|
||||
- Network access patterns matter: SSH Git needs direct access, HTTP can go through localhost proxy
|
||||
|
||||
## Claude Development Guidelines
|
||||
|
||||
### CRITICAL: Anti-Pattern Reminders
|
||||
|
||||
1. **RESEARCH FIRST** — Check existing solutions in order: pacman → AUR → GitHub
|
||||
2. **VERIFY SYNTAX BEFORE WRITING** — Use `--help`, test in parts, never assume argument formats
|
||||
3. **STICK TO THE CHOSEN SOLUTION** — Don't drift mid-implementation
|
||||
4. **PREFER SIMPLE OVER COMPLEX** — Existing tools > custom scripts; bash > python for simple tasks
|
||||
5. **CHECK PACKAGE REPOS FIRST** — `pacman -Ss` before any manual installation
|
||||
|
||||
### Failure Patterns to Avoid
|
||||
- ❌ Broken syntax without testing commands first
|
||||
- ❌ Switching solutions mid-implementation
|
||||
- ❌ Overcomplicating when simple solutions exist
|
||||
- ❌ Installing random scripts before checking packages
|
||||
|
||||
### Success Pattern
|
||||
- ✅ Research → Choose → Verify syntax → Implement → Test
|
||||
- ✅ pacman → AUR → GitHub (preference order)
|
||||
- ✅ Use proven, maintained tools over custom scripts
|
||||
|
||||
@@ -13,6 +13,7 @@ Setting up a personal homelab using a ThinkPad laptop running Arch Linux to move
|
||||
- [x] Self-hosted media server (Jellyfin)
|
||||
- [x] AI voice assistant (local TTS with Piper)
|
||||
- [x] Reverse proxy with nginx for multiple services
|
||||
- [x] Geographic IP blocking (geoip-shell whitelist)
|
||||
- [ ] Gradual migration from commercial cloud services
|
||||
|
||||
## Hardware
|
||||
@@ -35,6 +36,7 @@ Setting up a personal homelab using a ThinkPad laptop running Arch Linux to move
|
||||
- ✅ Dotfiles management with yadm configured and merged
|
||||
- ✅ Development environment setup completed
|
||||
- ✅ Network domain setup (DuckDNS + Nginx reverse proxy + SSL)
|
||||
- ✅ Geographic IP blocking (geoip-shell whitelist for European countries)
|
||||
- ✅ Gitea Git server running (Docker container)
|
||||
- ✅ Copyparty file server with working WebDAV support
|
||||
- ✅ Jellyfin media server (Docker container)
|
||||
@@ -60,6 +62,7 @@ homelab/
|
||||
├── docs/ # Detailed documentation
|
||||
│ ├── system-setup.md # Arch Linux installation & config
|
||||
│ ├── network-security.md # SSH, DNS, VPN, firewall
|
||||
│ ├── geoip-blocking.md # Geographic IP blocking setup
|
||||
│ ├── services.md # Self-hosted services
|
||||
│ ├── voice-assistant.md # AI voice setup with Piper TTS
|
||||
│ └── troubleshooting/ # Solutions & troubleshooting guides
|
||||
@@ -83,6 +86,7 @@ homelab/
|
||||
### Documentation Files
|
||||
- **[docs/system-setup.md](docs/system-setup.md)** - Complete Arch Linux installation, TTY config, desktop setup
|
||||
- **[docs/network-security.md](docs/network-security.md)** - SSH hardening, DuckDNS, WireGuard VPN, firewall setup
|
||||
- **[docs/geoip-blocking.md](docs/geoip-blocking.md)** - Geographic IP blocking with geoip-shell whitelist
|
||||
- **[docs/services.md](docs/services.md)** - Self-hosted services: Git hosting, cloud storage, media server
|
||||
- **[docs/voice-assistant.md](docs/voice-assistant.md)** - AI voice assistant setup with Piper TTS and FastAPI
|
||||
- **[TODO.md](TODO.md)** - Centralized task list with progress tracking by category
|
||||
@@ -93,5 +97,5 @@ homelab/
|
||||
- **System**: Arch Linux with XFCE desktop, ter-124b TTY font, Colemak layout
|
||||
- **Network**: Static IP (192.168.0.100), SSH port 2222, DuckDNS (ak-homelab.duckdns.org)
|
||||
- **Services**: Nginx reverse proxy, Gitea Git server, Copyparty file server with WebDAV, Jellyfin media server, AI voice assistant
|
||||
- **Security**: SSH hardened, SSL certificates active, WebDAV authentication enabled
|
||||
- **Security**: SSH hardened, SSL certificates active, WebDAV authentication enabled, geographic IP blocking (geoip-shell whitelist)
|
||||
- **Development**: yadm dotfiles, tmux with temperature monitoring, zsh with proper history
|
||||
|
||||
43
TODO.md
43
TODO.md
@@ -1,13 +1,21 @@
|
||||
# Homelab TODO List
|
||||
|
||||
## HIGH PRIO
|
||||
|
||||
- [ ] Move all bigger storages to NAS if it isn't there already
|
||||
- Gitea
|
||||
- Nextcloud
|
||||
- [ ] Unify YADM configs across different systems
|
||||
- use YADM alternatives method if needed for conflicts
|
||||
|
||||
## Network & Security
|
||||
- [x] DuckDNS dynamic DNS setup *(completed - ak-homelab.duckdns.org)*
|
||||
- [x] SSH security hardening *(documented in network-security.md)*
|
||||
- [x] Figure out why laptop IP changes: Different eth ports have different MAC?
|
||||
- [x] Router port forwarding configuration
|
||||
- [ ] !!! Set up geoblocking for SSH. Rest of SSH hardening already done.
|
||||
- [x] !!! Set up geoblocking for SSH. Rest of SSH hardening already done.
|
||||
- [ ] !!! Modify syncthing to sync the NAS folders where appropriate (e.g. Logseq)
|
||||
- [ ] Dockerize everything and use symlinks for dockerfiles (tired of constantly copying stuff over)
|
||||
- [x] Dockerize everything and use symlinks for dockerfiles — all docker-compose files symlinked from `/opt/docker/` to repo `config/docker/`
|
||||
- [ ] !!! IMPORTANT: Run setup scripts made by security reviewer agent
|
||||
- [ ] Ran out of AI quota mid-security review so continue where we left off. Some scripts created but it's not
|
||||
complete yet
|
||||
@@ -23,7 +31,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 +99,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 +108,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
|
||||
@@ -116,6 +125,18 @@ Lower priority - mostly using SSH or TTY anyways
|
||||
- [x] Figure out drag and drop window tiling solution -> workaround with keyboard shortcuts
|
||||
- [ ] Install multimedia codecs and applications
|
||||
|
||||
## Docker Storage Migration
|
||||
- [x] Move Gitea storage to NAS — migrated to `/mnt/nas/docker-data/gitea/data` (USER_UID=1024:100 to match NAS ownership)
|
||||
- [x] Move Nextcloud storage to NAS — migrated to `/mnt/nas/docker-data/nextcloud/`
|
||||
- [ ] Persist copyparty index database to NAS — currently rebuilt from scratch on every restart (slow, scans entire NAS); mount `/mnt/nas/.copyparty-db` into the container so the index survives restarts
|
||||
- [ ] Clean up old leftover data: `/opt/docker/gitea/data/`, `/opt/docker/gitea/data.old/`, Docker volumes `gitea_gitea`, `nextcloud_nextcloud_data`, `nextcloud_nextcloud_db`
|
||||
|
||||
## Docker Image Upgrades
|
||||
See `docs/docker-upgrade-plan.md` for full plan. Key warnings:
|
||||
- **Jellyfin**: v10.11+ does a major EF Core DB migration (`library.db` → `jellyfin.db`). Known to hang at "Saving BaseItem entries". **Back up jellyfin data volume before upgrading.**
|
||||
- **Redis**: Stay on `7-alpine` — Redis 8 has ACL breaking changes, not worth upgrading for a cache role
|
||||
- All containers are behind on updates as of 2026-02-27 (see plan doc for details)
|
||||
|
||||
## Security & Maintenance
|
||||
- [ ] Configure automatic security updates
|
||||
- [ ] Set up system monitoring and alerting
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -14,11 +14,11 @@ networks:
|
||||
|
||||
services:
|
||||
server:
|
||||
image: gitea/gitea:latest
|
||||
image: gitea/gitea:1.24
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- USER_UID=1024
|
||||
- USER_GID=100
|
||||
- GITEA__server__ROOT_URL=https://ak-homelab.duckdns.org/gitea/
|
||||
- GITEA__server__SSH_PORT=2223
|
||||
restart: unless-stopped
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- /mnt/nas/docker-data/gitea/data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
services:
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
image: jellyfin/jellyfin:10.11.6
|
||||
container_name: jellyfin
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -10,13 +10,18 @@ services:
|
||||
image: mariadb:10.11
|
||||
restart: unless-stopped
|
||||
container_name: nextcloud-db
|
||||
user: "1024:100" # Match NAS file ownership (uid 1024, gid 100) to bypass chown on NFS
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_root_password
|
||||
- MYSQL_PASSWORD_FILE=/run/secrets/mysql_password
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
- MARIADB_INITDB_SKIP_TZINFO=1 # Skip timezone loading on NFS
|
||||
volumes:
|
||||
- nextcloud_db:/var/lib/mysql
|
||||
- /mnt/nas/docker-data/nextcloud/db-data:/var/lib/mysql
|
||||
command:
|
||||
- --innodb_use_native_aio=0 # Required for NFS
|
||||
- --innodb_flush_method=O_DIRECT # Better NFS performance
|
||||
networks:
|
||||
- nextcloud
|
||||
secrets:
|
||||
@@ -31,7 +36,7 @@ services:
|
||||
- nextcloud
|
||||
|
||||
nextcloud-app:
|
||||
image: nextcloud:29
|
||||
image: nextcloud:32
|
||||
restart: unless-stopped
|
||||
container_name: nextcloud-app
|
||||
ports:
|
||||
@@ -49,7 +54,7 @@ services:
|
||||
- OVERWRITEHOST=ak-homelab.duckdns.org
|
||||
- OVERWRITEWEBROOT=/cloud
|
||||
volumes:
|
||||
- nextcloud_data:/var/www/html
|
||||
- /mnt/nas/docker-data/nextcloud/app-data:/var/www/html
|
||||
# External storage mounts for existing folders
|
||||
- /home/hoborg/shared:/external_storage/shared:rw
|
||||
- /home/hoborg/Documents:/external_storage/documents:rw
|
||||
@@ -74,10 +79,6 @@ secrets:
|
||||
nextcloud_admin_password:
|
||||
file: /home/hoborg/creds/nextcloud_admin_password.txt
|
||||
|
||||
volumes:
|
||||
nextcloud_db:
|
||||
nextcloud_data:
|
||||
|
||||
networks:
|
||||
nextcloud:
|
||||
driver: bridge
|
||||
|
||||
44
config/docker/portainer/docker-compose.yml
Normal file
44
config/docker/portainer/docker-compose.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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:2.39.0
|
||||
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
|
||||
|
||||
# No healthcheck — Portainer is distroless (no shell available)
|
||||
|
||||
volumes:
|
||||
portainer_data:
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
services:
|
||||
qbittorrent:
|
||||
image: linuxserver/qbittorrent:latest
|
||||
image: linuxserver/qbittorrent:5.1.4
|
||||
container_name: qbittorrent
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
- WEBUI_PORT=8080
|
||||
|
||||
# Security hardening
|
||||
user: "1000:1000"
|
||||
# NOTE: No user: directive — linuxserver uses s6-overlay (starts as root, drops to PUID/PGID)
|
||||
read_only: false # qBittorrent needs write access for downloads
|
||||
tmpfs:
|
||||
- /tmp:noexec,nosuid,size=100m
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
# SSH Honeypot Service
|
||||
# Deploy to: /etc/systemd/system/ssh-honeypot.service
|
||||
#
|
||||
# Setup commands:
|
||||
# sudo cp config/systemd/ssh-honeypot.service /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable ssh-honeypot.service
|
||||
# sudo systemctl start ssh-honeypot.service
|
||||
|
||||
[Unit]
|
||||
Description=SSH Honeypot (Port 22)
|
||||
After=network.target
|
||||
|
||||
@@ -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>
|
||||
|
||||
181
docs/geoip-blocking.md
Normal file
181
docs/geoip-blocking.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# GeoIP Blocking with geoip-shell
|
||||
|
||||
## Overview
|
||||
|
||||
**Tool:** geoip-shell v0.7.5
|
||||
**Repository:** https://github.com/friendly-bits/geoip-shell
|
||||
**Installation Method:** Manual from GitHub repository
|
||||
**Purpose:** Geographic IP blocking for enhanced security
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/friendly-bits/geoip-shell.git
|
||||
cd geoip-shell
|
||||
|
||||
# Install the tool
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Current Configuration
|
||||
|
||||
### Core Settings
|
||||
- **Firewall Backend:** iptables
|
||||
- **IP Lists Source:** RIPE (Réseaux IP Européens)
|
||||
- **Network Interfaces:** All interfaces protected
|
||||
- **LAN Detection:** Automatic subnet detection enabled
|
||||
|
||||
### Update Mechanism
|
||||
- **Cron Service:** ✅ Enabled
|
||||
- **Update Schedule:** Daily at 4:08 AM (`8 4 * * *`)
|
||||
- **Last Update:** September 17, 2025 at 00:57:41
|
||||
- **Persistence:** ✅ Enabled (survives reboots)
|
||||
- **Backup:** ✅ Automatic IP lists backup enabled
|
||||
|
||||
### Persistence Cron Job
|
||||
- **Schedule:** On reboot (`@reboot`)
|
||||
- **Command:** `/usr/bin/geoip-shell-run.sh restore -a`
|
||||
- **Purpose:** Restores geoip-shell firewall rules after system reboot
|
||||
|
||||
## Inbound Geoblocking Configuration
|
||||
|
||||
### Blocking Mode
|
||||
- **Type:** Whitelist (only specified countries allowed)
|
||||
- **IP Families:** IPv4 and IPv6 supported
|
||||
|
||||
### Whitelisted Countries
|
||||
```
|
||||
AL (Albania), AD (Andorra), AM (Armenia), AT (Austria), 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)
|
||||
GE (Georgia), DE (Germany), GI (Gibraltar), GR (Greece)
|
||||
GG (Guernsey), HU (Hungary), IS (Iceland), IE (Ireland)
|
||||
IM (Isle of Man), IT (Italy), JE (Jersey), KZ (Kazakhstan)
|
||||
LV (Latvia), LI (Liechtenstein), LT (Lithuania), LU (Luxembourg)
|
||||
MT (Malta), MD (Moldova), MC (Monaco), ME (Montenegro)
|
||||
NL (Netherlands), MK (North Macedonia), NO (Norway), PL (Poland)
|
||||
PT (Portugal), RO (Romania), RU (Russia), SM (San Marino)
|
||||
RS (Serbia), SK (Slovakia), SI (Slovenia), ES (Spain)
|
||||
SE (Sweden), CH (Switzerland), TR (Turkey), UA (Ukraine)
|
||||
GB (United Kingdom), VA (Vatican City)
|
||||
```
|
||||
|
||||
### Network Exceptions (Always Allowed)
|
||||
**IPv4 Networks:**
|
||||
- `172.18.0.0/16` - Docker network
|
||||
- `172.17.0.0/16` - Docker network
|
||||
- `169.254.0.0/16` - Link-local addresses
|
||||
- `192.168.0.0/24` - Local LAN
|
||||
- `172.20.0.0/16` - Docker network
|
||||
- `172.19.0.0/16` - Docker network
|
||||
|
||||
**IPv6 Networks:**
|
||||
- `fdaa:bbcc:ddee::/64` - Custom network
|
||||
- `fe80::/10` - Link-local addresses
|
||||
|
||||
### Protocol Coverage
|
||||
- **TCP:** All destination ports blocked for non-whitelisted countries
|
||||
- **UDP:** All destination ports blocked for non-whitelisted countries
|
||||
|
||||
### Firewall Status
|
||||
- **IPv4 Chain:** ✅ Enabled and active
|
||||
- **IPv6 Chain:** ✅ Enabled and active
|
||||
- **Whitelist Rules:** ✅ Properly configured
|
||||
|
||||
## Outbound Geoblocking
|
||||
- **Status:** Disabled (no outbound restrictions)
|
||||
|
||||
## System Health
|
||||
- **Overall Status:** ✅ No problems detected
|
||||
- **Firewall Integration:** ✅ Working correctly
|
||||
- **Update Process:** ✅ Functioning properly
|
||||
|
||||
## Security Impact
|
||||
|
||||
### Protection Provided
|
||||
1. **Geographic Blocking:** Blocks all traffic from countries not in whitelist
|
||||
2. **Comprehensive Coverage:** Both IPv4 and IPv6 protection
|
||||
3. **Full Protocol Coverage:** TCP and UDP traffic controlled
|
||||
4. **Network Awareness:** Automatically detects and allows local networks
|
||||
5. **Persistence:** Rules survive system reboots
|
||||
6. **Automatic Updates:** IP lists updated daily
|
||||
|
||||
### Integration with Existing Security
|
||||
- **Complements fail2ban:** Provides geographic layer above intrusion detection
|
||||
- **Works with UFW:** Uses iptables backend compatible with UFW
|
||||
- **Docker Compatible:** Automatically allows Docker networks
|
||||
- **LAN Friendly:** Preserves local network access
|
||||
|
||||
## Monitoring and Maintenance
|
||||
|
||||
### Status Checking
|
||||
```bash
|
||||
geoip-shell status
|
||||
```
|
||||
|
||||
### Log Locations
|
||||
- Main logs: `/var/log/geoip-shell.log`
|
||||
- System logs: `journalctl -u geoip-shell`
|
||||
|
||||
### Update Verification
|
||||
- Automatic daily updates at 4:18 AM
|
||||
- Last update timestamp visible in status
|
||||
- Backup of IP lists maintained
|
||||
|
||||
## Configuration Philosophy
|
||||
|
||||
This setup follows a **whitelist approach** focusing on:
|
||||
- **Trusted Regions:** European countries plus select others
|
||||
- **Local Access:** Full LAN and Docker network access preserved
|
||||
- **Minimal Disruption:** Automatic detection of local networks
|
||||
- **Comprehensive Protection:** Both inbound directions covered
|
||||
|
||||
## Manual Setup Process
|
||||
|
||||
The tool was installed manually with interactive prompts rather than scripted installation due to security considerations. Key decisions made during setup:
|
||||
|
||||
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
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Service Not Starting:** Check `systemctl status geoip-shell`
|
||||
- **Rules Not Applied:** Verify iptables chains with `iptables -L`
|
||||
- **Update Failures:** Check `/var/log/geoip-shell.log`
|
||||
|
||||
### Recovery Commands
|
||||
```bash
|
||||
# Restart service
|
||||
sudo systemctl restart geoip-shell
|
||||
|
||||
# Reload configuration
|
||||
sudo geoip-shell reload
|
||||
|
||||
# Check for issues
|
||||
sudo geoip-shell status
|
||||
```
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Potential Enhancements
|
||||
- **Custom Country Lists:** Add/remove countries based on threat intelligence
|
||||
- **Time-based Rules:** Different rules for different times of day
|
||||
- **Integration with Monitoring:** Alert on blocked country attempts
|
||||
- **Backup Configurations:** Document alternative configurations
|
||||
|
||||
### Monitoring Improvements
|
||||
- **Log Analysis:** Regular review of blocked attempts by country
|
||||
- **Performance Impact:** Monitor system resource usage
|
||||
- **False Positives:** Track legitimate traffic being blocked
|
||||
|
||||
## References
|
||||
|
||||
- **Official Repository:** https://github.com/friendly-bits/geoip-shell
|
||||
- **Documentation:** Available in repository wiki
|
||||
- **Issue Tracking:** GitHub issues for bug reports and feature requests
|
||||
@@ -366,6 +366,245 @@ sudo fail2ban-client status sshd
|
||||
sudo fail2ban-client get sshd banned
|
||||
```
|
||||
|
||||
## Geographic IP Blocking with geoip-shell
|
||||
|
||||
### Overview
|
||||
**Status:** ✅ **Implemented** - Whitelist mode protecting all interfaces
|
||||
|
||||
**Tool:** geoip-shell v0.7.5
|
||||
**Repository:** https://github.com/friendly-bits/geoip-shell
|
||||
**Purpose:** Geographic IP blocking for enhanced security
|
||||
|
||||
### Current Configuration
|
||||
- **Firewall Backend:** iptables
|
||||
- **IP Lists Source:** RIPE (Réseaux IP Européens)
|
||||
- **Network Interfaces:** All interfaces protected
|
||||
- **LAN Detection:** Automatic subnet detection enabled
|
||||
- **Mode:** Whitelist (only specified countries allowed)
|
||||
- **IP Families:** IPv4 and IPv6 supported
|
||||
- **Update Schedule:** Daily at 4:18 AM
|
||||
- **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), 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)
|
||||
GE (Georgia), DE (Germany), GI (Gibraltar), GR (Greece)
|
||||
GG (Guernsey), HU (Hungary), IS (Iceland), IE (Ireland)
|
||||
IM (Isle of Man), IT (Italy), JE (Jersey), KZ (Kazakhstan)
|
||||
LV (Latvia), LI (Liechtenstein), LT (Lithuania), LU (Luxembourg)
|
||||
MT (Malta), MD (Moldova), MC (Monaco), ME (Montenegro)
|
||||
NL (Netherlands), MK (North Macedonia), NO (Norway), PL (Poland)
|
||||
PT (Portugal), RO (Romania), RU (Russia), SM (San Marino)
|
||||
RS (Serbia), SK (Slovakia), SI (Slovenia), ES (Spain)
|
||||
SE (Sweden), CH (Switzerland), TR (Turkey), UA (Ukraine)
|
||||
GB (United Kingdom), VA (Vatican City)
|
||||
```
|
||||
|
||||
### Network Exceptions (Always Allowed)
|
||||
**IPv4 Networks:**
|
||||
- `172.18.0.0/16` - Docker network
|
||||
- `172.17.0.0/16` - Docker network
|
||||
- `169.254.0.0/16` - Link-local addresses
|
||||
- `192.168.0.0/24` - Local LAN
|
||||
- `172.20.0.0/16` - Docker network
|
||||
- `172.19.0.0/16` - Docker network
|
||||
|
||||
**IPv6 Networks:**
|
||||
- `fdaa:bbcc:ddee::/64` - Custom network
|
||||
- `fe80::/10` - Link-local addresses
|
||||
|
||||
### Security Impact
|
||||
1. **Geographic Blocking:** Blocks all traffic from countries not in whitelist
|
||||
2. **Comprehensive Coverage:** Both IPv4 and IPv6 protection
|
||||
3. **Full Protocol Coverage:** TCP and UDP traffic controlled
|
||||
4. **Network Awareness:** Automatically detects and allows local networks
|
||||
5. **Persistence:** Rules survive system reboots
|
||||
6. **Automatic Updates:** IP lists updated daily
|
||||
|
||||
### Integration with Existing Security
|
||||
- **Complements fail2ban:** Provides geographic layer above intrusion detection
|
||||
- **Works with UFW:** Uses iptables backend compatible with UFW
|
||||
- **Docker Compatible:** Automatically allows Docker networks
|
||||
- **LAN Friendly:** Preserves local network access
|
||||
|
||||
### Monitoring
|
||||
```bash
|
||||
# Check geoip-shell status
|
||||
geoip-shell status
|
||||
|
||||
# View logs
|
||||
journalctl -u geoip-shell
|
||||
tail -f /var/log/geoip-shell.log
|
||||
```
|
||||
|
||||
### 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:** 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)**
|
||||
|
||||
## Permanent Ban System for Repeat Offenders
|
||||
|
||||
### Overview
|
||||
**Status:** ✅ **Implemented** - Automated permanent banning of persistent attackers
|
||||
|
||||
**Script:** `scripts/permanent-ban-repeat-offenders.sh`
|
||||
**Purpose:** Automatically identify and permanently ban IPs that have been banned by fail2ban more than a threshold number of times
|
||||
|
||||
### How It Works
|
||||
|
||||
#### Detection Logic
|
||||
1. **Log Analysis:** Scans `/var/log/fail2ban.log*` for ban entries
|
||||
2. **IP Extraction:** Extracts IP addresses from ban log entries
|
||||
3. **Frequency Counting:** Counts how many times each IP has been banned
|
||||
4. **Threshold Check:** Identifies IPs banned more than the threshold (4 times)
|
||||
|
||||
#### Permanent Banning Process
|
||||
For each repeat offender:
|
||||
1. **Country Lookup:** Uses `whois` to determine the country of origin
|
||||
2. **Banlist Update:** Adds IP to `/etc/fail2ban/permanent-banlist.conf`
|
||||
3. **Firewall Rule:** Creates permanent iptables DROP rule
|
||||
4. **Persistence:** Saves iptables rules to `/etc/iptables/iptables.rules`
|
||||
5. **Service Reload:** Reloads fail2ban to recognize the updated banlist
|
||||
|
||||
### Configuration
|
||||
|
||||
#### Threshold Settings
|
||||
```bash
|
||||
THRESHOLD=4 # Ban after 4 fail2ban bans
|
||||
```
|
||||
|
||||
#### File Locations
|
||||
- **Log File:** `/var/log/permanent-ban.log`
|
||||
- **Banlist:** `/etc/fail2ban/permanent-banlist.conf`
|
||||
- **Iptables Rules:** `/etc/iptables/iptables.rules`
|
||||
|
||||
#### Cron Schedule
|
||||
- **Frequency:** Daily at 2:00 AM (`0 2 * * *`)
|
||||
- **User:** root
|
||||
- **Command:** `/home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh`
|
||||
|
||||
### Security Benefits
|
||||
|
||||
#### Multi-Layer Protection
|
||||
1. **fail2ban:** Temporary bans for suspicious activity
|
||||
2. **Permanent Bans:** Long-term blocking of persistent attackers
|
||||
3. **Geographic Blocking:** Country-level filtering via geoip-shell
|
||||
4. **Network-Level:** iptables rules at the firewall level
|
||||
|
||||
#### Attack Prevention
|
||||
- **Brute Force:** Blocks IPs that repeatedly attempt attacks
|
||||
- **Botnets:** Prevents automated attacks from compromised hosts
|
||||
- **Persistence:** Maintains bans across system reboots
|
||||
- **Resource Protection:** Reduces server load from repeat offenders
|
||||
|
||||
### Monitoring and Maintenance
|
||||
|
||||
#### Log Analysis
|
||||
```bash
|
||||
# View permanent ban activity
|
||||
tail -f /var/log/permanent-ban.log
|
||||
|
||||
# Check current permanent bans
|
||||
cat /etc/fail2ban/permanent-banlist.conf
|
||||
|
||||
# View iptables permanent rules
|
||||
iptables -L | grep DROP
|
||||
```
|
||||
|
||||
#### Manual Execution
|
||||
```bash
|
||||
# Run the script manually (requires root)
|
||||
sudo /home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh
|
||||
```
|
||||
|
||||
#### Unban Procedure
|
||||
To manually unban a permanently banned IP:
|
||||
```bash
|
||||
# Remove from banlist
|
||||
sudo sed -i "/^192\.168\.1\.100/d" /etc/fail2ban/permanent-banlist.conf
|
||||
|
||||
# Remove iptables rule (find the rule number first)
|
||||
sudo iptables -L --line-numbers | grep "192.168.1.100"
|
||||
sudo iptables -D INPUT <rule_number>
|
||||
|
||||
# Save iptables rules
|
||||
sudo iptables-save > /etc/iptables/iptables.rules
|
||||
|
||||
# Reload fail2ban
|
||||
sudo systemctl reload fail2ban
|
||||
```
|
||||
|
||||
### Integration with Security Stack
|
||||
|
||||
#### Complementary Tools
|
||||
- **fail2ban:** Provides temporary bans that feed into permanent ban detection
|
||||
- **geoip-shell:** Geographic blocking at the network level
|
||||
- **UFW:** Additional firewall layer
|
||||
- **SSH Hardening:** Reduces initial attack surface
|
||||
|
||||
#### Workflow
|
||||
```
|
||||
Attack Attempt → fail2ban Detection → Temporary Ban → Repeat Offense → Permanent Ban → Geographic Block
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Common Issues
|
||||
- **Script Not Running:** Check cron job configuration
|
||||
- **Permission Errors:** Ensure script is executable and paths are correct
|
||||
- **whois Failures:** Some IPs may not return country information
|
||||
- **iptables-save Issues:** Check if iptables-persistent is installed
|
||||
|
||||
#### Diagnostic Commands
|
||||
```bash
|
||||
# Check cron service
|
||||
sudo systemctl status cron
|
||||
|
||||
# Test script manually
|
||||
sudo bash -x /home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh
|
||||
|
||||
# Verify iptables rules
|
||||
sudo iptables -L -n | grep DROP
|
||||
|
||||
# Check fail2ban integration
|
||||
sudo fail2ban-client status
|
||||
```
|
||||
|
||||
## Router Configuration
|
||||
|
||||
### Port Forwarding
|
||||
|
||||
@@ -45,11 +45,19 @@ This document catalogs all security-related configuration files in the homelab r
|
||||
|
||||
### Gitea Security
|
||||
- **Config File**: `config/docker/gitea/docker-compose.yml`
|
||||
- **Deploy To**: `/opt/docker/gitea/docker-compose.yml`
|
||||
- **Security Features**:
|
||||
- Custom SSH port (2223)
|
||||
- Database isolation
|
||||
- Volume permissions
|
||||
- Network restrictions
|
||||
- Custom SSH port (2223) for Git operations
|
||||
- Web UI bound to localhost only (127.0.0.1:3000) for reverse proxy
|
||||
- Non-root user operation via USER_UID/USER_GID environment variables
|
||||
- Resource limits (1G memory)
|
||||
- Health checking for service monitoring
|
||||
- s6-overlay compatible configuration (no read-only filesystem)
|
||||
- **Security Notes**:
|
||||
- SSH port 2223 bound to all interfaces for external Git access
|
||||
- Web port 3000 bound to localhost only for nginx proxy security
|
||||
- Avoid Docker `user:` directive (breaks s6-overlay init system)
|
||||
- Use environment variables for privilege dropping instead
|
||||
|
||||
### Jellyfin Security
|
||||
- **Config File**: `config/docker/jellyfin/docker-compose.yml`
|
||||
|
||||
993
docs/services.md
993
docs/services.md
File diff suppressed because it is too large
Load Diff
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)"
|
||||
64
scripts/permanent-ban-repeat-offenders.sh
Executable file
64
scripts/permanent-ban-repeat-offenders.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Permanent ban script for IPs that have been banned more than 4 times by fail2ban
|
||||
# Run via cron: 0 */6 * * * /home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh
|
||||
|
||||
LOGFILE="/var/log/permanent-ban.log"
|
||||
BANLIST_FILE="/etc/fail2ban/permanent-banlist.conf"
|
||||
THRESHOLD=4
|
||||
|
||||
# Function to log with timestamp
|
||||
log_message() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
# Ensure permanent banlist file exists
|
||||
if [ ! -f "$BANLIST_FILE" ]; then
|
||||
touch "$BANLIST_FILE"
|
||||
chmod 644 "$BANLIST_FILE"
|
||||
fi
|
||||
|
||||
log_message "Starting repeat offender analysis (threshold: $THRESHOLD bans)"
|
||||
|
||||
# Get list of IPs with ban counts from fail2ban logs
|
||||
REPEAT_OFFENDERS=$(grep "Ban " /var/log/fail2ban.log* | \
|
||||
grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | \
|
||||
sort | uniq -c | sort -nr | \
|
||||
awk -v threshold="$THRESHOLD" '$1 > threshold {print $2 " " $1}')
|
||||
|
||||
if [ -z "$REPEAT_OFFENDERS" ]; then
|
||||
log_message "No repeat offenders found above threshold"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Process each repeat offender
|
||||
echo "$REPEAT_OFFENDERS" | while read ip ban_count; do
|
||||
# Skip if already permanently banned
|
||||
if grep -q "^$ip" "$BANLIST_FILE"; then
|
||||
log_message "IP $ip already permanently banned (skip)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get country information
|
||||
COUNTRY=$(whois "$ip" 2>/dev/null | grep -i "country:" | head -1 | awk '{print $2}')
|
||||
|
||||
# Add to permanent banlist
|
||||
echo "$ip # Banned $(date '+%Y-%m-%d') - $ban_count offenses - Country: $COUNTRY" >> "$BANLIST_FILE"
|
||||
|
||||
# Add permanent iptables rule
|
||||
iptables -I INPUT -s "$ip" -j DROP
|
||||
|
||||
# Make iptables rule persistent (systemd approach)
|
||||
if command -v iptables-save >/dev/null 2>&1; then
|
||||
iptables-save > /etc/iptables/iptables.rules 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log_message "PERMANENTLY BANNED: $ip ($ban_count offenses, Country: $COUNTRY)"
|
||||
done
|
||||
|
||||
# Reload fail2ban to ensure it sees the updated banlist
|
||||
if systemctl is-active --quiet fail2ban; then
|
||||
log_message "Reloading fail2ban service"
|
||||
systemctl reload fail2ban
|
||||
fi
|
||||
|
||||
log_message "Repeat offender analysis completed"
|
||||
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