diff --git a/cloud/.env.example b/cloud/.env.example new file mode 100644 index 0000000..1d5d924 --- /dev/null +++ b/cloud/.env.example @@ -0,0 +1,63 @@ +# ============================================================================= +# NEXTCLOUD HOMELAB CLOUD ENVIRONMENT CONFIGURATION +# ============================================================================= +# Copy this file to .env and customize the values for your setup + +# ============================================================================= +# GENERAL SETTINGS +# ============================================================================= +TZ=Europe/Warsaw +PUID=1000 +PGID=1000 +DOMAIN_NAME=fajnachata.club + +# ============================================================================= +# DATABASE CONFIGURATION (PostgreSQL) +# ============================================================================= +POSTGRES_DB=nextcloud +POSTGRES_USER=nextcloud_user +POSTGRES_PASSWORD=your_secure_postgres_password_here + +# Additional databases for other services +PAPERLESS_DB_NAME=paperless +BOOKSTACK_DB_NAME=bookstack + +# ============================================================================= +# REDIS CONFIGURATION +# ============================================================================= +REDIS_PASSWORD=your_secure_redis_password_here + +# ============================================================================= +# NEXTCLOUD CONFIGURATION +# ============================================================================= +NEXTCLOUD_ADMIN_USER=admin +NEXTCLOUD_ADMIN_PASSWORD=your_secure_nextcloud_admin_password_here +NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.fajnachata.club,100.92.119.54 + +# ============================================================================= +# PAPERLESS-NGX CONFIGURATION +# ============================================================================= +PAPERLESS_SECRET_KEY=your_very_long_secret_key_for_paperless_here_at_least_50_chars +PAPERLESS_ADMIN_USER=admin +PAPERLESS_ADMIN_PASSWORD=your_secure_paperless_admin_password_here + +# ============================================================================= +# SECURITY NOTES +# ============================================================================= +# 1. Generate strong passwords for all services +# 2. Use a password manager to store these credentials +# 3. The PAPERLESS_SECRET_KEY should be at least 50 characters long +# 4. Consider using environment-specific secrets management +# 5. Never commit the actual .env file to version control + +# ============================================================================= +# GENERATION COMMANDS +# ============================================================================= +# Generate secure passwords: +# openssl rand -base64 32 +# +# Generate Paperless secret key: +# openssl rand -base64 64 +# +# Generate random string: +# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 50 diff --git a/cloud/README.md b/cloud/README.md new file mode 100644 index 0000000..7871736 --- /dev/null +++ b/cloud/README.md @@ -0,0 +1,338 @@ +# Nextcloud Homelab Cloud Setup + +A production-ready Docker Compose configuration for a personal homelab cloud setup centered around Nextcloud, with integrated document management and knowledge base systems. + +## 🏗️ Architecture Overview + +### Core Services +- **Nextcloud 29** (PHP-FPM) - Personal cloud storage and collaboration +- **PostgreSQL 16** - Primary database for all services +- **Redis 7** - Caching and session storage +- **Nginx** - Web server and reverse proxy for Nextcloud + +### Additional Services +- **Paperless-ngx** - Document management with OCR capabilities +- **BookStack** - Wiki-style knowledge base for university materials + +### Integration Features +- ✅ Traefik integration with automatic SSL certificates +- ✅ Tailscale network compatibility +- ✅ Security best practices implemented +- ✅ Health checks and auto-restart policies +- ✅ Resource limits for stability +- ✅ Optimized for performance + +## 🚀 Quick Start + +### Prerequisites +- **Unraid OS** with Docker enabled +- **Compose Manager** plugin installed (from Community Applications) +- **User Scripts** plugin (optional, for automated backups) +- Existing Traefik setup (from your infra directory) +- Domain configured (*.fajnachata.club) +- Sufficient storage space (recommend 100GB+ for `/mnt/user/appdata/`) + +### 🎯 Unraid-Optimized Setup + +This configuration is specifically optimized for Unraid OS with: +- Direct volume mounts (no complex named volumes) +- PUID/PGID environment variables for proper permissions +- Unraid User Scripts integration for backups +- Simplified directory structure + +### 1. Automated Setup (Recommended) + +```bash +# Navigate to the cloud directory +cd /mnt/user/appdata/docker/cloud + +# Run the Unraid-optimized setup script +./scripts/setup.sh +``` + +### 2. Manual Setup (Alternative) + +```bash +# Navigate to the cloud directory +cd /mnt/user/appdata/docker/cloud + +# Create required directories (Unraid will handle permissions) +mkdir -p /mnt/user/appdata/nextcloud/{data,postgres,redis} +mkdir -p /mnt/user/appdata/paperless/{data,media,consume} +mkdir -p /mnt/user/appdata/bookstack/data +mkdir -p /mnt/user/backups/cloud + +# Make scripts executable +chmod +x scripts/*.sh +chmod +x postgres/init-multiple-databases.sh +``` + +### 2. Environment Configuration + +```bash +# Copy the example environment file +cp .env.example .env + +# Edit the environment file with your secure passwords +nano .env +``` + +**Important**: Generate secure passwords for all services: +```bash +# Generate secure passwords +openssl rand -base64 32 + +# Generate Paperless secret key (50+ characters) +openssl rand -base64 64 +``` + +### 3. Deploy Services + +```bash +# Start the services +docker-compose up -d + +# Check service status +docker-compose ps + +# View logs +docker-compose logs -f +``` + +### 4. Initial Configuration + +#### Nextcloud Setup +1. Access Nextcloud at `https://nextcloud.fajnachata.club` +2. Complete the initial setup wizard +3. Install recommended apps (Calendar, Contacts, Tasks) +4. Configure additional settings in Admin panel + +#### Paperless-ngx Setup +1. Access Paperless at `https://paperless.fajnachata.club` +2. Login with admin credentials from .env file +3. Configure OCR languages and document processing +4. Set up consumption folders + +#### BookStack Setup +1. Access BookStack at `https://bookstack.fajnachata.club` +2. Default login: `admin@admin.com` / `password` +3. Change default credentials immediately +4. Create your first book for university materials + +## 🔧 Configuration Details + +### Database Choice: PostgreSQL +- **Why PostgreSQL over MySQL/MariaDB?** + - Better performance with complex queries + - Superior concurrent access handling + - More robust for production environments + - Excellent JSON support for modern applications + - Better compliance with SQL standards + +### Security Features +- All services run with non-root users where possible +- Secrets managed through environment variables +- Security headers configured in Nginx +- Database access restricted to internal network +- Regular security updates through Alpine-based images + +### Performance Optimizations +- Redis caching for Nextcloud sessions and data +- PHP OPcache enabled with optimized settings +- Nginx configured with gzip compression +- Resource limits prevent memory exhaustion +- Health checks ensure service reliability + +## 📊 Monitoring and Maintenance + +### Health Checks +All services include health checks that monitor: +- Database connectivity +- Application responsiveness +- Service-specific endpoints + +### Log Management +```bash +# View service logs +docker-compose logs [service_name] + +# Follow logs in real-time +docker-compose logs -f [service_name] + +# View last 100 lines +docker-compose logs --tail=100 [service_name] +``` + +### Updates +```bash +# Update all services +docker-compose pull +docker-compose up -d + +# Update specific service +docker-compose pull [service_name] +docker-compose up -d [service_name] +``` + +## 💾 Backup Strategy + +### Critical Data Locations +- **Nextcloud Data**: `/mnt/user/appdata/nextcloud/data` +- **PostgreSQL Database**: `/mnt/user/appdata/nextcloud/postgres` +- **Paperless Documents**: `/mnt/user/appdata/paperless/{data,media}` +- **BookStack Data**: `/mnt/user/appdata/bookstack/data` + +### Automated Backup Script +Create a backup script for regular data protection: + +```bash +#!/bin/bash +# backup-cloud.sh - Automated backup script + +BACKUP_DIR="/mnt/user/backups/cloud" +DATE=$(date +%Y%m%d_%H%M%S) + +# Create backup directory +mkdir -p "$BACKUP_DIR/$DATE" + +# Stop services for consistent backup +cd /Volumes/appdata/docker/cloud +docker-compose stop + +# Backup databases +docker-compose exec postgres pg_dumpall -U nextcloud_user > "$BACKUP_DIR/$DATE/databases.sql" + +# Backup data directories +tar -czf "$BACKUP_DIR/$DATE/nextcloud_data.tar.gz" /mnt/user/appdata/nextcloud/data +tar -czf "$BACKUP_DIR/$DATE/paperless_data.tar.gz" /mnt/user/appdata/paperless +tar -czf "$BACKUP_DIR/$DATE/bookstack_data.tar.gz" /mnt/user/appdata/bookstack + +# Restart services +docker-compose start + +# Cleanup old backups (keep last 7 days) +find "$BACKUP_DIR" -type d -mtime +7 -exec rm -rf {} \; + +echo "Backup completed: $BACKUP_DIR/$DATE" +``` + +### Backup Schedule + +#### Unraid User Scripts (Recommended) +1. Install **User Scripts** plugin from Community Applications +2. Go to **Settings > User Scripts** +3. Add new script: **cloud-backup** (created automatically by setup script) +4. Set schedule: **Daily at 2 AM** (`0 2 * * *`) + +#### Manual Cron (Alternative) +```bash +# Edit crontab +crontab -e + +# Add daily backup at 2 AM +0 2 * * * /mnt/user/appdata/docker/cloud/scripts/backup-cloud.sh +``` + +## 🔍 Troubleshooting + +### Common Issues + +#### Nextcloud Performance +- Check PHP memory limits in `nextcloud/php.ini` +- Verify Redis connection: `docker-compose exec redis redis-cli ping` +- Monitor database performance: `docker-compose exec postgres pg_stat_activity` + +#### Database Connection Issues +- Verify PostgreSQL is healthy: `docker-compose ps postgres` +- Check database logs: `docker-compose logs postgres` +- Ensure environment variables are correct + +#### SSL/TLS Issues +- Verify Traefik is running and healthy +- Check domain DNS resolution +- Ensure Cloudflare API tokens are valid + +### Service-Specific Debugging + +#### Nextcloud +```bash +# Access Nextcloud container +docker-compose exec nextcloud bash + +# Run Nextcloud OCC commands +docker-compose exec nextcloud php occ status +docker-compose exec nextcloud php occ config:list +``` + +#### Paperless-ngx +```bash +# Check document processing +docker-compose logs paperless + +# Access Paperless management +docker-compose exec paperless python manage.py shell +``` + +## 🔐 Security Recommendations + +1. **Regular Updates**: Keep all services updated +2. **Strong Passwords**: Use unique, complex passwords for all services +3. **Network Segmentation**: Services communicate only through internal networks +4. **Access Control**: Limit access through Tailscale and proper firewall rules +5. **Monitoring**: Regularly check logs for suspicious activity +6. **Backups**: Maintain regular, tested backups +7. **SSL/TLS**: Ensure all connections use HTTPS + +## 📚 Additional Resources + +- [Nextcloud Admin Manual](https://docs.nextcloud.com/server/latest/admin_manual/) +- [Paperless-ngx Documentation](https://paperless-ngx.readthedocs.io/) +- [BookStack Documentation](https://www.bookstackapp.com/docs/) +- [PostgreSQL Documentation](https://www.postgresql.org/docs/) +- [Traefik Documentation](https://doc.traefik.io/traefik/) + +## 🤝 Integration with Existing Services + +This setup is designed to work alongside your existing infrastructure: +- **Traefik**: Uses existing `traefik_proxy` network +- **Domain Pattern**: Follows `*.fajnachata.club` convention +- **Tailscale**: Compatible with your Tailscale network setup +- **Storage**: Uses standard `/mnt/user/appdata/` pattern +- **Environment**: Consistent with your existing `.env` patterns + +## 🖥️ Unraid-Specific Features + +### Container Management +- **Docker Tab**: Monitor all containers from Unraid's Docker tab +- **Resource Usage**: View CPU/RAM usage in Unraid Dashboard +- **Auto-Start**: Containers automatically start with Unraid +- **Updates**: Use Unraid's built-in update notifications + +### Storage Integration +- **Cache Drive**: Containers run from cache drive for better performance +- **Array Storage**: Data stored on protected array storage +- **Mover**: Automatic data movement between cache and array +- **Snapshots**: Use Unraid's snapshot features for additional protection + +### Backup Integration +- **User Scripts**: Automated backups through User Scripts plugin +- **CA Backup**: Compatible with Community Applications backup plugin +- **Manual Backups**: Easy manual backup execution from Unraid terminal + +### Monitoring +- **Unraid Dashboard**: Resource usage and container status +- **Notifications**: Unraid can send alerts for container issues +- **Logs**: Access container logs through Unraid Docker tab +- **Health Checks**: Container health visible in Unraid interface + +### Network Configuration +- **Bridge Mode**: Uses Unraid's default Docker bridge network +- **Traefik Integration**: Seamless integration with existing Traefik setup +- **Port Management**: No port conflicts with Unraid services +- **Tailscale**: Full compatibility with Tailscale exit nodes + +### Performance Optimization +- **PUID/PGID**: Proper user mapping for Unraid permissions +- **Direct Mounts**: No performance overhead from named volumes +- **Resource Limits**: Prevents containers from overwhelming system +- **SSD Cache**: Fast access to frequently used data diff --git a/cloud/SETUP_SUMMARY.md b/cloud/SETUP_SUMMARY.md new file mode 100644 index 0000000..af4516a --- /dev/null +++ b/cloud/SETUP_SUMMARY.md @@ -0,0 +1,209 @@ +# 🏠 Nextcloud Homelab Cloud - Setup Summary + +## 📦 What's Been Created + +Your complete Nextcloud homelab cloud setup is now ready! Here's what has been delivered: + +### 🗂️ File Structure +``` +cloud/ +├── docker-compose.yml # Main Docker Compose configuration +├── .env.example # Environment variables template +├── README.md # Comprehensive documentation +├── SETUP_SUMMARY.md # This summary file +├── nginx/ +│ └── nextcloud.conf # Optimized Nginx configuration +├── nextcloud/ +│ └── php.ini # PHP optimization settings +├── postgres/ +│ └── init-multiple-databases.sh # Database initialization +└── scripts/ + ├── setup.sh # Automated setup script + ├── backup-cloud.sh # Backup automation + └── restore-cloud.sh # Restoration script +``` + +## 🚀 Quick Start (3 Steps) + +### 1. Run the Setup Script +```bash +cd /Volumes/appdata/docker/cloud +sudo ./scripts/setup.sh +``` + +### 2. Configure Environment +Edit the `.env` file with your secure passwords: +```bash +nano .env +``` + +### 3. Access Your Services +- **Nextcloud**: https://nextcloud.fajnachata.club +- **Paperless**: https://paperless.fajnachata.club +- **BookStack**: https://bookstack.fajnachata.club + +## 🏗️ Architecture Highlights + +### **Database Choice: PostgreSQL** ✅ +- **Why PostgreSQL over MySQL/MariaDB?** + - Superior performance with complex queries + - Better concurrent access handling + - More robust for production environments + - Excellent JSON support for modern apps + - Single database instance serves all services + +### **Core Services** +- **Nextcloud 29** (PHP-FPM) - Personal cloud storage +- **PostgreSQL 16** - Shared database for all services +- **Redis 7** - Caching and session storage +- **Nginx** - Optimized web server for Nextcloud + +### **Document Management** +- **Paperless-ngx** - OCR document management with automatic processing +- **BookStack** - Wiki-style knowledge base perfect for university materials + +## 🔧 Integration Features + +### ✅ **Seamless Integration with Your Existing Setup** +- Uses your existing `traefik_proxy` network +- Follows your `*.fajnachata.club` domain pattern +- Compatible with your Tailscale network (100.92.119.54) +- Uses standard `/mnt/user/appdata/` storage pattern +- Consistent with your existing `.env` patterns + +### ✅ **Production-Ready Security** +- All services run with non-root users +- Security headers configured +- Secrets managed through environment variables +- Database access restricted to internal network +- Regular security updates through Alpine images + +### ✅ **Performance Optimizations** +- Redis caching for Nextcloud +- PHP OPcache enabled +- Nginx with gzip compression +- Resource limits prevent memory exhaustion +- Health checks ensure reliability + +## 📊 Resource Requirements + +### **Memory Allocation** +- PostgreSQL: 256MB-512MB +- Redis: 128MB-256MB +- Nextcloud: 512MB-1GB +- Nginx: 128MB-256MB +- Paperless: 512MB-1GB +- BookStack: 256MB-512MB +- **Total**: ~2-4GB RAM + +### **Storage Requirements** +- Application data: `/mnt/user/appdata/` (grows with usage) +- Backups: `/mnt/user/backups/cloud/` (recommend 2x data size) +- **Minimum**: 50GB, **Recommended**: 200GB+ + +## 🔐 Security Best Practices Implemented + +1. **Network Segmentation**: Internal network for service communication +2. **Least Privilege**: Services run with minimal required permissions +3. **Secrets Management**: All sensitive data in environment variables +4. **SSL/TLS**: Automatic HTTPS through Traefik integration +5. **Security Headers**: Comprehensive security headers in Nginx +6. **Regular Updates**: Alpine-based images for security patches + +## 💾 Backup Strategy + +### **Automated Backups** +- **Schedule**: Daily at 2:00 AM (configurable) +- **Retention**: 7 days (configurable) +- **Location**: `/mnt/user/backups/cloud/` +- **Contents**: Databases, application data, configuration + +### **Manual Operations** +```bash +# Create backup +sudo ./scripts/backup-cloud.sh + +# Restore from latest backup +sudo ./scripts/restore-cloud.sh latest + +# Restore from specific backup +sudo ./scripts/restore-cloud.sh /mnt/user/backups/cloud/20240101_120000 +``` + +## 🎯 Recommended Next Steps + +### **Immediate (First Hour)** +1. ✅ Run setup script +2. ✅ Configure `.env` with secure passwords +3. ✅ Complete Nextcloud setup wizard +4. ✅ Change BookStack default password +5. ✅ Test all service access + +### **First Day** +1. Configure Nextcloud apps (Calendar, Contacts, Tasks) +2. Set up Paperless document processing +3. Create your first BookStack book for university materials +4. Test backup and restore procedures +5. Configure mobile apps + +### **First Week** +1. Migrate existing documents to Paperless +2. Organize university materials in BookStack +3. Set up Nextcloud desktop sync clients +4. Configure additional Nextcloud apps as needed +5. Monitor resource usage and adjust if needed + +## 🔍 Monitoring and Maintenance + +### **Health Monitoring** +```bash +# Check service status +docker-compose ps + +# View service logs +docker-compose logs -f [service_name] + +# Check resource usage +docker stats +``` + +### **Regular Maintenance** +- **Weekly**: Check service health and logs +- **Monthly**: Update Docker images +- **Quarterly**: Review and test backup procedures +- **As needed**: Scale resources based on usage + +## 🆘 Troubleshooting Quick Reference + +### **Common Issues** +- **Service won't start**: Check logs with `docker-compose logs [service]` +- **Database connection**: Verify PostgreSQL health and credentials +- **SSL issues**: Ensure Traefik is running and domain DNS is correct +- **Performance**: Check resource usage and adjust limits + +### **Emergency Procedures** +- **Complete failure**: Restore from backup using `restore-cloud.sh` +- **Data corruption**: Stop services, restore data only with `--data-only` +- **Config issues**: Restore config only with `--config-only` + +## 📚 Documentation + +- **Full Documentation**: `README.md` +- **Environment Config**: `.env.example` +- **Backup Procedures**: `scripts/backup-cloud.sh` +- **Restoration Guide**: `scripts/restore-cloud.sh` + +## 🎉 Success Criteria + +Your setup is successful when: +- ✅ All services show "healthy" status +- ✅ Web interfaces are accessible via HTTPS +- ✅ File upload/download works in Nextcloud +- ✅ Document OCR works in Paperless +- ✅ Wiki editing works in BookStack +- ✅ Automated backups are running +- ✅ Services survive container restarts + +--- + +**🏆 Congratulations!** You now have a production-ready, secure, and maintainable personal cloud infrastructure that follows the "intelligence is compression" principle - simple, elegant, and powerful. diff --git a/cloud/docker-compose.yml b/cloud/docker-compose.yml new file mode 100644 index 0000000..76d0cdf --- /dev/null +++ b/cloud/docker-compose.yml @@ -0,0 +1,231 @@ +version: '3.8' + +networks: + traefik_proxy: + external: true + cloud_internal: + driver: bridge + internal: false + +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: nextcloud-postgres + restart: unless-stopped + env_file: + - ./.env + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_MULTIPLE_DATABASES: ${PAPERLESS_DB_NAME},${BOOKSTACK_DB_NAME} + TZ: ${TZ:-Europe/Warsaw} + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + volumes: + - /mnt/user/appdata/nextcloud/postgres:/var/lib/postgresql/data + - ./postgres/init-multiple-databases.sh:/docker-entrypoint-initdb.d/init-multiple-databases.sh:ro + networks: + - cloud_internal + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + + # Redis Cache + redis: + image: redis:7-alpine + container_name: nextcloud-redis + restart: unless-stopped + env_file: + - ./.env + environment: + TZ: ${TZ:-Europe/Warsaw} + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + volumes: + - /mnt/user/appdata/nextcloud/redis:/data + networks: + - cloud_internal + command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 10s + + # Nextcloud Application + nextcloud: + image: nextcloud:29-fpm-alpine + container_name: nextcloud-app + restart: unless-stopped + env_file: + - ./.env + environment: + POSTGRES_HOST: postgres + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + REDIS_HOST: redis + REDIS_HOST_PASSWORD: ${REDIS_PASSWORD} + NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER} + NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD} + NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS} + OVERWRITEPROTOCOL: https + OVERWRITECLIURL: https://nextcloud.${DOMAIN_NAME} + OVERWRITEHOST: nextcloud.${DOMAIN_NAME} + TZ: ${TZ:-Europe/Warsaw} + PHP_MEMORY_LIMIT: 1G + PHP_UPLOAD_LIMIT: 10G + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + volumes: + - /mnt/user/appdata/nextcloud/data:/var/www/html + - ./nextcloud/php.ini:/usr/local/etc/php/conf.d/nextcloud.ini:ro + networks: + - cloud_internal + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "php", "-f", "/var/www/html/occ", "status"] + interval: 60s + timeout: 30s + retries: 3 + start_period: 120s + + # Nginx Web Server + nginx: + image: nginx:alpine + container_name: nextcloud-nginx + restart: unless-stopped + env_file: + - ./.env + environment: + TZ: ${TZ:-Europe/Warsaw} + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + volumes: + - /mnt/user/appdata/nextcloud/data:/var/www/html:ro + - ./nginx/nextcloud.conf:/etc/nginx/nginx.conf:ro + networks: + - cloud_internal + - traefik_proxy + depends_on: + nextcloud: + condition: service_healthy + labels: + - "traefik.enable=true" + - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.${DOMAIN_NAME}`)" + - "traefik.http.routers.nextcloud.entrypoints=websecure" + - "traefik.http.routers.nextcloud.tls=true" + - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" + - "traefik.http.services.nextcloud.loadbalancer.server.port=80" + - "traefik.http.routers.nextcloud.middlewares=nextcloud-headers@docker" + - "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https" + - "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Host=nextcloud.${DOMAIN_NAME}" + - "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000" + - "traefik.http.middlewares.nextcloud-headers.headers.stsIncludeSubdomains=true" + - "traefik.http.middlewares.nextcloud-headers.headers.stsPreload=true" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/status.php"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + # Paperless-ngx Document Management + paperless: + image: ghcr.io/paperless-ngx/paperless-ngx:latest + container_name: paperless-ngx + restart: unless-stopped + env_file: + - ./.env + environment: + PAPERLESS_REDIS: redis://redis:6379 + PAPERLESS_DBHOST: postgres + PAPERLESS_DBNAME: ${PAPERLESS_DB_NAME} + PAPERLESS_DBUSER: ${POSTGRES_USER} + PAPERLESS_DBPASS: ${POSTGRES_PASSWORD} + PAPERLESS_DBPORT: 5432 + PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY} + PAPERLESS_URL: https://paperless.${DOMAIN_NAME} + PAPERLESS_ADMIN_USER: ${PAPERLESS_ADMIN_USER} + PAPERLESS_ADMIN_PASSWORD: ${PAPERLESS_ADMIN_PASSWORD} + PAPERLESS_TIME_ZONE: ${TZ:-Europe/Warsaw} + PAPERLESS_OCR_LANGUAGE: eng+pol + PAPERLESS_CONSUMER_POLLING: 30 + PAPERLESS_CONSUMER_RECURSIVE: true + PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS: true + USERMAP_UID: ${PUID:-1000} + USERMAP_GID: ${PGID:-1000} + volumes: + - /mnt/user/appdata/paperless/data:/usr/src/paperless/data + - /mnt/user/appdata/paperless/media:/usr/src/paperless/media + - /mnt/user/appdata/paperless/consume:/usr/src/paperless/consume + networks: + - cloud_internal + - traefik_proxy + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + labels: + - "traefik.enable=true" + - "traefik.http.routers.paperless.rule=Host(`paperless.${DOMAIN_NAME}`)" + - "traefik.http.routers.paperless.entrypoints=websecure" + - "traefik.http.routers.paperless.tls=true" + - "traefik.http.routers.paperless.tls.certresolver=letsencrypt" + - "traefik.http.services.paperless.loadbalancer.server.port=8000" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/"] + interval: 60s + timeout: 30s + retries: 3 + start_period: 120s + + # BookStack Knowledge Base + bookstack: + image: lscr.io/linuxserver/bookstack:latest + container_name: bookstack + restart: unless-stopped + env_file: + - ./.env + environment: + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + TZ: ${TZ:-Europe/Warsaw} + APP_URL: https://bookstack.${DOMAIN_NAME} + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${POSTGRES_USER} + DB_PASS: ${POSTGRES_PASSWORD} + DB_DATABASE: ${BOOKSTACK_DB_NAME} + volumes: + - /mnt/user/appdata/bookstack/data:/config + networks: + - cloud_internal + - traefik_proxy + depends_on: + postgres: + condition: service_healthy + labels: + - "traefik.enable=true" + - "traefik.http.routers.bookstack.rule=Host(`bookstack.${DOMAIN_NAME}`)" + - "traefik.http.routers.bookstack.entrypoints=websecure" + - "traefik.http.routers.bookstack.tls=true" + - "traefik.http.routers.bookstack.tls.certresolver=letsencrypt" + - "traefik.http.services.bookstack.loadbalancer.server.port=80" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/login"] + interval: 60s + timeout: 30s + retries: 3 + start_period: 120s diff --git a/cloud/nextcloud/php.ini b/cloud/nextcloud/php.ini new file mode 100644 index 0000000..8b46421 --- /dev/null +++ b/cloud/nextcloud/php.ini @@ -0,0 +1,45 @@ +# Nextcloud PHP Configuration +# Optimized for production use with proper security and performance settings + +# Memory and execution limits +memory_limit = 1G +max_execution_time = 3600 +max_input_time = 3600 + +# File upload settings +upload_max_filesize = 10G +post_max_size = 10G +max_file_uploads = 100 + +# Session settings +session.save_handler = redis +session.save_path = "tcp://redis:6379?auth=${REDIS_PASSWORD}" +session.gc_maxlifetime = 1440 + +# OPcache settings for better performance +opcache.enable = 1 +opcache.enable_cli = 1 +opcache.memory_consumption = 256 +opcache.interned_strings_buffer = 64 +opcache.max_accelerated_files = 10000 +opcache.revalidate_freq = 1 +opcache.save_comments = 1 + +# Security settings +expose_php = Off +allow_url_fopen = Off +allow_url_include = Off + +# Error reporting (disable in production) +display_errors = Off +display_startup_errors = Off +log_errors = On +error_log = /var/log/php_errors.log + +# Date and timezone +date.timezone = Europe/Warsaw + +# Other optimizations +output_buffering = 4096 +realpath_cache_size = 4096K +realpath_cache_ttl = 600 diff --git a/cloud/nginx/nextcloud.conf b/cloud/nginx/nextcloud.conf new file mode 100644 index 0000000..c98c0eb --- /dev/null +++ b/cloud/nginx/nextcloud.conf @@ -0,0 +1,184 @@ +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + + # Performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 10G; + client_body_buffer_size 400M; + client_body_timeout 120s; + + # Gzip + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types + application/atom+xml + application/javascript + application/json + application/ld+json + application/manifest+json + application/rss+xml + application/vnd.geo+json + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/bmp + image/svg+xml + image/x-icon + text/cache-manifest + text/css + text/plain + text/vcard + text/vnd.rim.location.xloc + text/vtt + text/x-component + text/x-cross-domain-policy; + + # Security headers + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; + + upstream php-handler { + server nextcloud:9000; + } + + # Set the `immutable` cache control options only for assets with a cache busting `v` argument + map $arg_v $asset_immutable { + "" ""; + default "immutable"; + } + + server { + listen 80; + listen [::]:80; + server_name _; + + # Path to the root of your installation + root /var/www/html; + index index.php index.html /index.php$request_uri; + + # Rule borrowed from `.htaccess` to handle Microsoft DAV clients + location = / { + if ( $http_user_agent ~ ^DavClnt ) { + return 302 /remote.php/webdav/$is_args$args; + } + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # Make a regex exception for `/.well-known` so that clients can still + # access it despite the existence of the regex rule + # `location ~ /(\.|autotest|...)` which would otherwise handle requests + # for `/.well-known`. + location ^~ /.well-known { + # The rules in this block are an adaptation of the rules + # in `.htaccess` that concern `/.well-known`. + + location = /.well-known/carddav { return 301 /remote.php/dav/; } + location = /.well-known/caldav { return 301 /remote.php/dav/; } + + location /.well-known/acme-challenge { try_files $uri $uri/ =404; } + location /.well-known/pki-validation { try_files $uri $uri/ =404; } + + # Let Nextcloud's API for `/.well-known` URIs handle all other + # requests by passing them to the front-end controller. + return 301 /index.php$request_uri; + } + + # Rules borrowed from `.htaccess` to hide certain paths from clients + location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } + location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } + + # Ensure this block, which passes PHP files to the PHP process, is above the blocks + # which handle static assets (as seen below). If this block is not declared first, + # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php` + # to the URI, resulting in a HTTP 500 error response. + location ~ \.php(?:$|/) { + # Required for legacy support + rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; + + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + set $path_info $fastcgi_path_info; + + try_files $fastcgi_script_name =404; + + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + fastcgi_param HTTPS on; + + fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice + fastcgi_param front_controller_active true; # Enable pretty urls + fastcgi_pass php-handler; + + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + + fastcgi_max_temp_file_size 0; + fastcgi_buffering off; + } + + location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ { + try_files $uri /index.php$request_uri; + add_header Cache-Control "public, max-age=15778463, $asset_immutable"; + access_log off; # Optional: Don't log access to assets + + location ~ \.wasm$ { + default_type application/wasm; + } + } + + location ~ \.woff2?$ { + try_files $uri /index.php$request_uri; + expires 7d; # Cache the font files for 7 days + access_log off; # Optional: Don't log access to assets + } + + # Rule borrowed from `.htaccess` + location /remote { + return 301 /remote.php$request_uri; + } + + location / { + try_files $uri $uri/ /index.php$request_uri; + } + } +} diff --git a/cloud/postgres/init-multiple-databases.sh b/cloud/postgres/init-multiple-databases.sh new file mode 100644 index 0000000..7c3e2fc --- /dev/null +++ b/cloud/postgres/init-multiple-databases.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Script to create multiple databases for different services +# This script is executed during PostgreSQL container initialization + +set -e +set -u + +function create_user_and_database() { + local database=$1 + echo "Creating user and database '$database'" + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE DATABASE $database; + GRANT ALL PRIVILEGES ON DATABASE $database TO $POSTGRES_USER; +EOSQL +} + +if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then + echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES" + for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do + create_user_and_database $db + done + echo "Multiple databases created" +fi diff --git a/cloud/scripts/backup-cloud.sh b/cloud/scripts/backup-cloud.sh new file mode 100644 index 0000000..a699dc0 --- /dev/null +++ b/cloud/scripts/backup-cloud.sh @@ -0,0 +1,226 @@ +#!/bin/bash +# ============================================================================= +# Nextcloud Homelab Cloud Backup Script +# ============================================================================= +# This script creates comprehensive backups of all cloud services including +# databases, application data, and configuration files. + +set -euo pipefail + +# ============================================================================= +# CONFIGURATION +# ============================================================================= +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLOUD_DIR="$(dirname "$SCRIPT_DIR")" +BACKUP_BASE_DIR="/mnt/user/backups/cloud" +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_DIR="$BACKUP_BASE_DIR/$DATE" +RETENTION_DAYS=7 +LOG_FILE="/var/log/cloud-backup.log" + +# ============================================================================= +# LOGGING FUNCTIONS +# ============================================================================= +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +error() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 +} + +# ============================================================================= +# BACKUP FUNCTIONS +# ============================================================================= +create_backup_directory() { + log "Creating backup directory: $BACKUP_DIR" + mkdir -p "$BACKUP_DIR" + chmod 750 "$BACKUP_DIR" +} + +backup_databases() { + log "Starting database backup..." + + cd "$CLOUD_DIR" + + # Check if PostgreSQL container is running + if ! docker-compose ps postgres | grep -q "Up"; then + error "PostgreSQL container is not running" + return 1 + fi + + # Backup all databases + log "Backing up PostgreSQL databases..." + docker-compose exec -T postgres pg_dumpall -U nextcloud_user > "$BACKUP_DIR/databases.sql" + + # Backup individual databases for easier restoration + docker-compose exec -T postgres pg_dump -U nextcloud_user nextcloud > "$BACKUP_DIR/nextcloud_db.sql" + docker-compose exec -T postgres pg_dump -U nextcloud_user paperless > "$BACKUP_DIR/paperless_db.sql" + docker-compose exec -T postgres pg_dump -U nextcloud_user bookstack > "$BACKUP_DIR/bookstack_db.sql" + + log "Database backup completed" +} + +backup_application_data() { + log "Starting application data backup..." + + # Nextcloud data + log "Backing up Nextcloud data..." + tar -czf "$BACKUP_DIR/nextcloud_data.tar.gz" -C /mnt/user/appdata/nextcloud data + + # Paperless data + log "Backing up Paperless data..." + tar -czf "$BACKUP_DIR/paperless_data.tar.gz" -C /mnt/user/appdata paperless + + # BookStack data + log "Backing up BookStack data..." + tar -czf "$BACKUP_DIR/bookstack_data.tar.gz" -C /mnt/user/appdata bookstack + + log "Application data backup completed" +} + +backup_configuration() { + log "Backing up configuration files..." + + # Copy Docker Compose configuration + cp "$CLOUD_DIR/docker-compose.yml" "$BACKUP_DIR/" + cp "$CLOUD_DIR/.env" "$BACKUP_DIR/env_backup" + + # Copy custom configuration files + cp -r "$CLOUD_DIR/nginx" "$BACKUP_DIR/" + cp -r "$CLOUD_DIR/nextcloud" "$BACKUP_DIR/" + cp -r "$CLOUD_DIR/postgres" "$BACKUP_DIR/" + + log "Configuration backup completed" +} + +stop_services() { + log "Stopping services for consistent backup..." + cd "$CLOUD_DIR" + docker-compose stop +} + +start_services() { + log "Starting services..." + cd "$CLOUD_DIR" + docker-compose start + + # Wait for services to be healthy + log "Waiting for services to become healthy..." + sleep 30 + + # Check service health + if docker-compose ps | grep -q "unhealthy"; then + error "Some services are unhealthy after restart" + docker-compose ps + else + log "All services are running normally" + fi +} + +cleanup_old_backups() { + log "Cleaning up backups older than $RETENTION_DAYS days..." + find "$BACKUP_BASE_DIR" -type d -name "20*" -mtime +$RETENTION_DAYS -exec rm -rf {} \; 2>/dev/null || true + log "Cleanup completed" +} + +create_backup_manifest() { + log "Creating backup manifest..." + + cat > "$BACKUP_DIR/MANIFEST.txt" << EOF +Nextcloud Homelab Cloud Backup +============================== +Backup Date: $(date) +Backup Directory: $BACKUP_DIR +Script Version: 1.0 + +Contents: +- databases.sql: Complete PostgreSQL dump +- nextcloud_db.sql: Nextcloud database only +- paperless_db.sql: Paperless database only +- bookstack_db.sql: BookStack database only +- nextcloud_data.tar.gz: Nextcloud application data +- paperless_data.tar.gz: Paperless documents and data +- bookstack_data.tar.gz: BookStack wiki data +- docker-compose.yml: Docker Compose configuration +- env_backup: Environment variables (sensitive) +- nginx/: Nginx configuration +- nextcloud/: Nextcloud PHP configuration +- postgres/: PostgreSQL initialization scripts + +Restoration Notes: +1. Stop all services: docker-compose stop +2. Restore databases: psql < databases.sql +3. Extract data archives to /mnt/user/appdata/ +4. Restore configuration files +5. Start services: docker-compose up -d + +Total Backup Size: $(du -sh "$BACKUP_DIR" | cut -f1) +EOF + + log "Backup manifest created" +} + +verify_backup() { + log "Verifying backup integrity..." + + # Check if all expected files exist + local expected_files=( + "databases.sql" + "nextcloud_data.tar.gz" + "paperless_data.tar.gz" + "bookstack_data.tar.gz" + "docker-compose.yml" + "env_backup" + ) + + for file in "${expected_files[@]}"; do + if [[ ! -f "$BACKUP_DIR/$file" ]]; then + error "Missing backup file: $file" + return 1 + fi + done + + # Check file sizes (should not be empty) + for file in "${expected_files[@]}"; do + if [[ ! -s "$BACKUP_DIR/$file" ]]; then + error "Empty backup file: $file" + return 1 + fi + done + + log "Backup verification completed successfully" +} + +# ============================================================================= +# MAIN EXECUTION +# ============================================================================= +main() { + log "Starting Nextcloud Homelab Cloud backup..." + + # Create log directory if it doesn't exist + mkdir -p "$(dirname "$LOG_FILE")" + + # Trap to ensure services are restarted even if script fails + trap 'start_services' EXIT + + create_backup_directory + stop_services + backup_databases + backup_application_data + start_services + backup_configuration + create_backup_manifest + verify_backup + cleanup_old_backups + + log "Backup completed successfully: $BACKUP_DIR" + log "Total backup size: $(du -sh "$BACKUP_DIR" | cut -f1)" +} + +# ============================================================================= +# SCRIPT EXECUTION +# ============================================================================= +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/cloud/scripts/restore-cloud.sh b/cloud/scripts/restore-cloud.sh new file mode 100644 index 0000000..ba05e0a --- /dev/null +++ b/cloud/scripts/restore-cloud.sh @@ -0,0 +1,345 @@ +#!/bin/bash +# ============================================================================= +# Nextcloud Homelab Cloud Restoration Script +# ============================================================================= +# This script restores cloud services from backups created by backup-cloud.sh + +set -euo pipefail + +# ============================================================================= +# CONFIGURATION +# ============================================================================= +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLOUD_DIR="$(dirname "$SCRIPT_DIR")" +BACKUP_BASE_DIR="/mnt/user/backups/cloud" +LOG_FILE="/var/log/cloud-restore.log" + +# ============================================================================= +# LOGGING FUNCTIONS +# ============================================================================= +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +error() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 +} + +# ============================================================================= +# UTILITY FUNCTIONS +# ============================================================================= +usage() { + cat << EOF +Usage: $0 [OPTIONS] BACKUP_DIR + +Restore Nextcloud Homelab Cloud from backup + +OPTIONS: + -h, --help Show this help message + -f, --force Force restoration without confirmation + -d, --data-only Restore only data, skip configuration + -c, --config-only Restore only configuration, skip data + +BACKUP_DIR: + Path to backup directory (e.g., /mnt/user/backups/cloud/20240101_120000) + Or use 'latest' to restore from the most recent backup + +Examples: + $0 /mnt/user/backups/cloud/20240101_120000 + $0 latest + $0 --data-only latest +EOF +} + +list_available_backups() { + log "Available backups:" + find "$BACKUP_BASE_DIR" -type d -name "20*" | sort -r | head -10 | while read -r backup; do + local size=$(du -sh "$backup" 2>/dev/null | cut -f1 || echo "Unknown") + local date=$(basename "$backup") + echo " $date (Size: $size)" + done +} + +get_latest_backup() { + find "$BACKUP_BASE_DIR" -type d -name "20*" | sort -r | head -1 +} + +confirm_restoration() { + local backup_dir="$1" + + echo + echo "WARNING: This will restore from backup and OVERWRITE existing data!" + echo "Backup directory: $backup_dir" + echo "Target directory: /mnt/user/appdata/" + echo + + if [[ "${FORCE:-false}" == "true" ]]; then + log "Force mode enabled, skipping confirmation" + return 0 + fi + + read -p "Are you sure you want to continue? (yes/no): " -r + if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then + log "Restoration cancelled by user" + exit 0 + fi +} + +# ============================================================================= +# RESTORATION FUNCTIONS +# ============================================================================= +verify_backup_directory() { + local backup_dir="$1" + + log "Verifying backup directory: $backup_dir" + + if [[ ! -d "$backup_dir" ]]; then + error "Backup directory does not exist: $backup_dir" + return 1 + fi + + # Check for required files + local required_files=( + "databases.sql" + "nextcloud_data.tar.gz" + "paperless_data.tar.gz" + "bookstack_data.tar.gz" + ) + + for file in "${required_files[@]}"; do + if [[ ! -f "$backup_dir/$file" ]]; then + error "Missing required backup file: $file" + return 1 + fi + done + + log "Backup directory verification completed" +} + +stop_services() { + log "Stopping all cloud services..." + cd "$CLOUD_DIR" + docker-compose down +} + +restore_databases() { + local backup_dir="$1" + + log "Starting database restoration..." + + cd "$CLOUD_DIR" + + # Start only PostgreSQL for restoration + log "Starting PostgreSQL container..." + docker-compose up -d postgres + + # Wait for PostgreSQL to be ready + log "Waiting for PostgreSQL to be ready..." + sleep 30 + + # Drop existing databases (except template databases) + log "Dropping existing databases..." + docker-compose exec -T postgres psql -U nextcloud_user -d postgres -c "DROP DATABASE IF EXISTS nextcloud;" + docker-compose exec -T postgres psql -U nextcloud_user -d postgres -c "DROP DATABASE IF EXISTS paperless;" + docker-compose exec -T postgres psql -U nextcloud_user -d postgres -c "DROP DATABASE IF EXISTS bookstack;" + + # Restore databases + log "Restoring databases from backup..." + docker-compose exec -T postgres psql -U nextcloud_user -d postgres < "$backup_dir/databases.sql" + + log "Database restoration completed" +} + +restore_application_data() { + local backup_dir="$1" + + log "Starting application data restoration..." + + # Create backup of existing data + local timestamp=$(date +%Y%m%d_%H%M%S) + local backup_existing="/tmp/cloud_data_backup_$timestamp" + + log "Creating backup of existing data to: $backup_existing" + mkdir -p "$backup_existing" + + if [[ -d "/mnt/user/appdata/nextcloud/data" ]]; then + mv "/mnt/user/appdata/nextcloud/data" "$backup_existing/nextcloud_data_old" + fi + + if [[ -d "/mnt/user/appdata/paperless" ]]; then + mv "/mnt/user/appdata/paperless" "$backup_existing/paperless_old" + fi + + if [[ -d "/mnt/user/appdata/bookstack" ]]; then + mv "/mnt/user/appdata/bookstack" "$backup_existing/bookstack_old" + fi + + # Restore data from backup + log "Restoring Nextcloud data..." + mkdir -p "/mnt/user/appdata/nextcloud" + tar -xzf "$backup_dir/nextcloud_data.tar.gz" -C "/mnt/user/appdata/nextcloud" + + log "Restoring Paperless data..." + tar -xzf "$backup_dir/paperless_data.tar.gz" -C "/mnt/user/appdata" + + log "Restoring BookStack data..." + tar -xzf "$backup_dir/bookstack_data.tar.gz" -C "/mnt/user/appdata" + + # Set proper permissions + log "Setting proper permissions..." + chown -R 1000:1000 /mnt/user/appdata/nextcloud + chown -R 1000:1000 /mnt/user/appdata/paperless + chown -R 1000:1000 /mnt/user/appdata/bookstack + + log "Application data restoration completed" + log "Previous data backed up to: $backup_existing" +} + +restore_configuration() { + local backup_dir="$1" + + log "Starting configuration restoration..." + + # Backup existing configuration + local timestamp=$(date +%Y%m%d_%H%M%S) + local config_backup="/tmp/cloud_config_backup_$timestamp" + mkdir -p "$config_backup" + + # Backup current configuration + if [[ -f "$CLOUD_DIR/.env" ]]; then + cp "$CLOUD_DIR/.env" "$config_backup/" + fi + + if [[ -d "$CLOUD_DIR/nginx" ]]; then + cp -r "$CLOUD_DIR/nginx" "$config_backup/" + fi + + # Restore configuration from backup + if [[ -f "$backup_dir/env_backup" ]]; then + log "Restoring environment configuration..." + cp "$backup_dir/env_backup" "$CLOUD_DIR/.env" + fi + + if [[ -d "$backup_dir/nginx" ]]; then + log "Restoring Nginx configuration..." + cp -r "$backup_dir/nginx" "$CLOUD_DIR/" + fi + + if [[ -d "$backup_dir/nextcloud" ]]; then + log "Restoring Nextcloud configuration..." + cp -r "$backup_dir/nextcloud" "$CLOUD_DIR/" + fi + + if [[ -d "$backup_dir/postgres" ]]; then + log "Restoring PostgreSQL configuration..." + cp -r "$backup_dir/postgres" "$CLOUD_DIR/" + fi + + log "Configuration restoration completed" + log "Previous configuration backed up to: $config_backup" +} + +start_services() { + log "Starting all cloud services..." + cd "$CLOUD_DIR" + docker-compose up -d + + # Wait for services to be healthy + log "Waiting for services to become healthy..." + sleep 60 + + # Check service health + local unhealthy_services=$(docker-compose ps | grep -c "unhealthy" || true) + if [[ $unhealthy_services -gt 0 ]]; then + error "Some services are unhealthy after restoration" + docker-compose ps + return 1 + else + log "All services are running normally" + fi +} + +# ============================================================================= +# MAIN EXECUTION +# ============================================================================= +main() { + local backup_dir="" + local data_only=false + local config_only=false + + # Parse command line arguments + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + usage + exit 0 + ;; + -f|--force) + FORCE=true + shift + ;; + -d|--data-only) + data_only=true + shift + ;; + -c|--config-only) + config_only=true + shift + ;; + *) + backup_dir="$1" + shift + ;; + esac + done + + # Validate arguments + if [[ -z "$backup_dir" ]]; then + error "Backup directory not specified" + usage + exit 1 + fi + + # Handle 'latest' keyword + if [[ "$backup_dir" == "latest" ]]; then + backup_dir=$(get_latest_backup) + if [[ -z "$backup_dir" ]]; then + error "No backups found in $BACKUP_BASE_DIR" + list_available_backups + exit 1 + fi + log "Using latest backup: $backup_dir" + fi + + # Create log directory if it doesn't exist + mkdir -p "$(dirname "$LOG_FILE")" + + log "Starting Nextcloud Homelab Cloud restoration..." + log "Backup source: $backup_dir" + + verify_backup_directory "$backup_dir" + confirm_restoration "$backup_dir" + + stop_services + + if [[ "$config_only" == "false" ]]; then + restore_databases "$backup_dir" + restore_application_data "$backup_dir" + fi + + if [[ "$data_only" == "false" ]]; then + restore_configuration "$backup_dir" + fi + + start_services + + log "Restoration completed successfully!" + log "Please verify that all services are working correctly" +} + +# ============================================================================= +# SCRIPT EXECUTION +# ============================================================================= +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/cloud/scripts/setup.sh b/cloud/scripts/setup.sh new file mode 100644 index 0000000..4ae5f0d --- /dev/null +++ b/cloud/scripts/setup.sh @@ -0,0 +1,298 @@ +#!/bin/bash +# ============================================================================= +# Nextcloud Homelab Cloud Setup Script - Unraid Optimized +# ============================================================================= +# This script automates the initial setup of the Nextcloud homelab cloud +# Specifically designed for Unraid OS + +set -euo pipefail + +# ============================================================================= +# CONFIGURATION +# ============================================================================= +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLOUD_DIR="$(dirname "$SCRIPT_DIR")" +LOG_FILE="/mnt/user/appdata/cloud-setup.log" + +# ============================================================================= +# LOGGING FUNCTIONS +# ============================================================================= +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +error() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 +} + +success() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" | tee -a "$LOG_FILE" +} + +# ============================================================================= +# UTILITY FUNCTIONS +# ============================================================================= +check_prerequisites() { + log "Checking prerequisites for Unraid..." + + # Check if we're on Unraid + if [[ ! -f "/etc/unraid-version" ]]; then + log "WARNING: This script is optimized for Unraid OS" + fi + + # Check if Docker is available + if ! command -v docker &> /dev/null; then + error "Docker is not available. Please enable Docker in Unraid settings." + exit 1 + fi + + # Check if Docker Compose is available + if ! docker-compose --version &> /dev/null; then + error "Docker Compose is not available. Please install the Compose Manager plugin." + exit 1 + fi + + # Check if appdata directory exists + if [[ ! -d "/mnt/user/appdata" ]]; then + error "Unraid appdata directory not found at /mnt/user/appdata" + exit 1 + fi + + success "Prerequisites check passed" +} + +create_directories() { + log "Creating required directories for Unraid..." + + local directories=( + "/mnt/user/appdata/nextcloud/data" + "/mnt/user/appdata/nextcloud/postgres" + "/mnt/user/appdata/nextcloud/redis" + "/mnt/user/appdata/paperless/data" + "/mnt/user/appdata/paperless/media" + "/mnt/user/appdata/paperless/consume" + "/mnt/user/appdata/bookstack/data" + "/mnt/user/backups/cloud" + ) + + for dir in "${directories[@]}"; do + if [[ ! -d "$dir" ]]; then + log "Creating directory: $dir" + mkdir -p "$dir" + else + log "Directory already exists: $dir" + fi + done + + success "Directories created" +} + +set_permissions() { + log "Setting Unraid-compatible permissions..." + + # Unraid typically handles permissions through PUID/PGID + # We'll set basic permissions but rely on container PUID/PGID + + # Ensure directories are accessible + chmod -R 755 /mnt/user/appdata/nextcloud + chmod -R 755 /mnt/user/appdata/paperless + chmod -R 755 /mnt/user/appdata/bookstack + chmod -R 755 /mnt/user/backups/cloud + + # Make scripts executable + chmod +x "$SCRIPT_DIR"/*.sh + chmod +x "$CLOUD_DIR/postgres/init-multiple-databases.sh" + + success "Permissions set for Unraid" +} + +setup_environment() { + log "Setting up environment configuration..." + + if [[ ! -f "$CLOUD_DIR/.env" ]]; then + if [[ -f "$CLOUD_DIR/.env.example" ]]; then + log "Copying .env.example to .env" + cp "$CLOUD_DIR/.env.example" "$CLOUD_DIR/.env" + + echo + echo "==============================================" + echo "IMPORTANT: Environment Configuration Required" + echo "==============================================" + echo "Please edit $CLOUD_DIR/.env and set:" + echo "1. Strong passwords for all services" + echo "2. Your domain name" + echo "3. Paperless secret key (50+ characters)" + echo + echo "Generate secure passwords with:" + echo " openssl rand -base64 32" + echo + echo "Generate Paperless secret key with:" + echo " openssl rand -base64 64" + echo + read -p "Press Enter after you've configured the .env file..." + else + error ".env.example file not found" + exit 1 + fi + else + log ".env file already exists" + fi + + success "Environment configuration ready" +} + +check_traefik_network() { + log "Checking Traefik network..." + + if ! docker network ls | grep -q "traefik_proxy"; then + log "Creating traefik_proxy network..." + docker network create traefik_proxy + else + log "traefik_proxy network already exists" + fi + + success "Traefik network ready" +} + +deploy_services() { + log "Deploying cloud services..." + + cd "$CLOUD_DIR" + + # Pull latest images + log "Pulling latest Docker images..." + docker-compose pull + + # Start services + log "Starting services..." + docker-compose up -d + + # Wait for services to be ready + log "Waiting for services to initialize (this may take a few minutes)..." + sleep 120 + + # Check service status + log "Checking service status..." + docker-compose ps + + success "Services deployed" +} + +setup_unraid_backup() { + log "Setting up Unraid User Scripts backup..." + + # Create User Scripts directory if it doesn't exist + local user_scripts_dir="/boot/config/plugins/user.scripts/scripts" + + if [[ -d "$user_scripts_dir" ]]; then + local backup_script_dir="$user_scripts_dir/cloud-backup" + + if [[ ! -d "$backup_script_dir" ]]; then + log "Creating User Scripts backup entry..." + mkdir -p "$backup_script_dir" + + # Create the User Scripts wrapper + cat > "$backup_script_dir/script" << 'EOF' +#!/bin/bash +# Nextcloud Cloud Backup - User Scripts Integration +# This script is called by Unraid User Scripts plugin + +SCRIPT_DIR="/mnt/user/appdata/docker/cloud/scripts" +if [[ -f "$SCRIPT_DIR/backup-cloud.sh" ]]; then + echo "Starting Nextcloud Cloud backup..." + bash "$SCRIPT_DIR/backup-cloud.sh" +else + echo "ERROR: Backup script not found at $SCRIPT_DIR/backup-cloud.sh" + exit 1 +fi +EOF + chmod +x "$backup_script_dir/script" + + # Create description file + echo "Automated backup for Nextcloud Cloud services" > "$backup_script_dir/description" + + success "User Scripts backup entry created" + log "Configure schedule in Unraid User Scripts plugin: Settings > User Scripts" + else + log "User Scripts backup entry already exists" + fi + else + log "User Scripts plugin not detected. Install from Community Applications for automated backups." + log "Manual backup available: $SCRIPT_DIR/backup-cloud.sh" + fi +} + +display_access_info() { + local domain_name=$(grep "DOMAIN_NAME=" "$CLOUD_DIR/.env" | cut -d'=' -f2) + + echo + echo "==============================================" + echo "🎉 Nextcloud Homelab Cloud Setup Complete!" + echo "==============================================" + echo + echo "Your services are now available at:" + echo "📁 Nextcloud: https://nextcloud.$domain_name" + echo "📄 Paperless: https://paperless.$domain_name" + echo "📚 BookStack: https://bookstack.$domain_name" + echo + echo "Initial Setup Required:" + echo "1. Complete Nextcloud setup wizard" + echo "2. Login to Paperless with admin credentials" + echo "3. Change BookStack default password" + echo + echo "Default BookStack Login:" + echo " Email: admin@admin.com" + echo " Password: password" + echo " ⚠️ CHANGE THIS IMMEDIATELY!" + echo + echo "Unraid Integration:" + echo "🖥️ Monitor containers: Unraid Docker tab" + echo "📊 Resource usage: Unraid Dashboard" + echo "⚙️ Backup scheduling: Settings > User Scripts" + echo "🔧 Manual backup: $SCRIPT_DIR/backup-cloud.sh" + echo "🔄 Restore backup: $SCRIPT_DIR/restore-cloud.sh" + echo + echo "Storage Locations:" + echo "💾 App data: /mnt/user/appdata/nextcloud/" + echo "💾 Documents: /mnt/user/appdata/paperless/" + echo "💾 Wiki data: /mnt/user/appdata/bookstack/" + echo "💾 Backups: /mnt/user/backups/cloud/" + echo + echo "Logs and Monitoring:" + echo "📊 Setup log: $LOG_FILE" + echo "🔍 Service logs: docker-compose logs -f [service_name]" + echo "💡 Service status: docker-compose ps" + echo + echo "Documentation:" + echo "📖 Full documentation: $CLOUD_DIR/README.md" + echo + echo "==============================================" +} + +# ============================================================================= +# MAIN EXECUTION +# ============================================================================= +main() { + log "Starting Nextcloud Homelab Cloud setup for Unraid..." + + # Create log directory if it doesn't exist + mkdir -p "$(dirname "$LOG_FILE")" + + check_prerequisites + create_directories + set_permissions + setup_environment + check_traefik_network + deploy_services + setup_unraid_backup + + success "Setup completed successfully!" + display_access_info +} + +# ============================================================================= +# SCRIPT EXECUTION +# ============================================================================= +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi