added ansible script

This commit is contained in:
liph
2026-02-16 23:40:30 +01:00
parent e6e444fff7
commit 8925d9677e
86 changed files with 15476 additions and 1911 deletions

42
ansible/.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# Ansible
*.retry
.vault_pass*
/tmp/
.ansible/
# Generated inventory (regenerate with setup.sh)
inventory/hosts.yml
inventory/group_vars/all/vars.yml
# Keep vault.yml encrypted (DO NOT ignore it - it should be committed encrypted)
# inventory/group_vars/all/vault.yml
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Virtual environments
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Temporary files
*.tmp
*.bak

259
ansible/AGENTS.md Normal file
View File

@@ -0,0 +1,259 @@
# Agent Guidelines for Ansible Infrastructure Repository
This repository contains Docker Compose infrastructure configurations for deploying self-hosted productivity services. This document provides guidelines for AI coding agents working in this repository.
## Project Overview
**Type:** Docker Compose Infrastructure Project
**Primary Technology:** Docker, Docker Compose
**Purpose:** Deployment configurations for self-hosted productivity stack including Nextcloud, OnlyOffice, Excalidraw, and Obsidian
## Repository Structure
```
/home/liph/programming/ansible/
└── nextcloud/
├── docker-compose.yml # Main orchestration file (5 services)
└── .env # Environment variables
```
## Commands
### Docker Compose Operations
```bash
# Navigate to service directory
cd nextcloud/
# Start all services
docker compose up -d
# Start specific service
docker compose up -d <service-name>
# Stop all services
docker compose down
# Stop and remove volumes (DESTRUCTIVE)
docker compose down -v
# View logs for all services
docker compose logs -f
# View logs for specific service
docker compose logs -f <service-name>
# Restart a service
docker compose restart <service-name>
# Rebuild and restart service
docker compose up -d --build <service-name>
# Validate docker-compose.yml syntax
docker compose config
# List running services
docker compose ps
```
### Service Names
- `excalidraw` - Drawing/whiteboard tool (port 3009)
- `next-db` - PostgreSQL 18 database
- `next` - Nextcloud main application (port 8808)
- `onlyoffice` - Document server (port 8000)
- `obsidian` - Note-taking app (ports 3004-3005)
### Testing
No automated tests exist. Manual testing workflow:
1. Validate syntax: `docker compose config`
2. Start services: `docker compose up -d`
3. Check health: `docker compose ps`
4. Review logs: `docker compose logs`
5. Test endpoints: `curl localhost:8808` (Nextcloud), etc.
## Code Style Guidelines
### YAML Formatting (docker-compose.yml)
**Indentation:**
- Use 2 spaces for indentation (NO tabs)
- Maintain consistent indentation levels
**Structure:**
```yaml
services:
service-name:
image: docker.io/image:tag
container_name: container-name
depends_on:
- dependency
ports:
- "host:container"
environment:
- KEY=value
- KEY=${ENV_VAR}
volumes:
- volume_name:/container/path
- ./host/path:/container/path
restart: unless-stopped
networks:
- network_name
```
**Naming Conventions:**
- Container names: Use kebab-case (e.g., `next-db`, `next-redis`)
- Service names: Use snake_case or kebab-case consistently
- Volume names: Use snake_case (e.g., `nextcloud_data`, `pg_data`)
- Network names: Use snake_case with project prefix (e.g., `nextcloud_network`)
**Environment Variables:**
- Reference .env variables using `${VAR_NAME}` syntax
- Never hardcode sensitive data (passwords, secrets) in docker-compose.yml
- Use explicit environment variable declarations with `-` prefix
**Image Specifications:**
- Always use fully qualified image names: `docker.io/image:tag`
- Pin specific versions for production (avoid `:latest` in production)
- Current exception: development environment uses `:latest` tags
**Volumes:**
- Named volumes for persistent data (defined in volumes section)
- Bind mounts for configuration files using `./relative/path`
- Always specify volume mount destination path
- CRITICAL: Never leave empty volume definitions (e.g., `- :/path`)
**Comments:**
- Use `#` for comments
- Add comments above service definitions to describe purpose
- Comment out optional services with `# ` prefix on each line
### Environment Files (.env)
**Format:**
```bash
# Database Configuration
DB_PASSWORD=secure_password_here
DB_USERNAME=nextcloud
DB_DATABASE_NAME=nextcloud
DB_HOST=next-db
# User/Group IDs
PUID=33
PGID=1000
```
**Rules:**
- One variable per line: `KEY=value`
- No spaces around `=`
- No quotes needed for values
- Group related variables with comment headers
- Never commit sensitive values (use placeholders or .env.example)
- All variables must have values (no empty assignments)
### Security Guidelines
1. **Secrets Management:**
- Never hardcode passwords in docker-compose.yml
- Use .env file for credentials (ensure .env is in .gitignore)
- Consider Docker secrets for sensitive production data
- Rotate default passwords immediately
2. **Current Security Issues to Fix:**
- Line 53 in docker-compose.yml: Remove hardcoded admin password
- .env file: All database credentials are empty
- Consider enabling JWT for OnlyOffice (currently disabled)
3. **Network Security:**
- Use isolated Docker networks for service communication
- Only expose necessary ports to host
- Consider reverse proxy (nginx/traefik) for HTTPS termination
### Error Handling
**Validation Steps Before Committing:**
1. Run `docker compose config` to validate YAML syntax
2. Ensure all volume mount paths are complete
3. Verify all environment variables are defined in .env
4. Check for hardcoded secrets
5. Confirm port conflicts with `docker compose ps`
**Common Issues:**
- Empty volume definitions: Always specify source and destination
- Missing environment variables: Define all referenced vars in .env
- Port conflicts: Use `docker ps` to check existing port bindings
- Network errors: Ensure services on same network can communicate
### Configuration Management
**Volume Mapping Patterns:**
```yaml
# Named volume (recommended for data)
volumes:
- nextcloud_data:/var/www/html
# Bind mount with SELinux label (for config)
volumes:
- ./config:/var/www/html/config:Z
# Bind mount with shared label
volumes:
- ./vault:/vault:z
```
**Restart Policies:**
- Use `restart: unless-stopped` for production services
- Avoid `restart: always` (harder to stop deliberately)
- Use `restart: on-failure` for development/debugging
### Database Configuration
**PostgreSQL Environment Variables:**
- `POSTGRES_DB` - Database name
- `POSTGRES_USER` - Database user
- `POSTGRES_PASSWORD` - Database password
**Nextcloud Database Connection:**
- `POSTGRES_HOST` - Hostname (service name: `next-db`)
- Must match database service configuration
### Best Practices
1. **Service Dependencies:**
- Use `depends_on` to define startup order
- Note: `depends_on` doesn't wait for "ready" state
2. **Resource Limits:**
- Consider adding memory/CPU limits for production
- Current config uses `mem_swappiness: -1` for Nextcloud
3. **Timezone Configuration:**
- Set `TZ` environment variable for correct timestamps
- Current: `TZ=Europe/Zurich` (Excalidraw), `TZ=Etc/UTC` (Obsidian)
4. **Data Persistence:**
- Always use named volumes for important data
- Define volumes in top-level `volumes:` section
- Backup volume data regularly
5. **Making Changes:**
- Test changes in development before production
- Use `docker compose config` to validate
- Review diff carefully before applying
- Document breaking changes
## Development Workflow
1. Make changes to docker-compose.yml or .env
2. Validate: `docker compose config`
3. Apply changes: `docker compose up -d`
4. Verify: `docker compose ps && docker compose logs`
5. Test affected services manually
6. Document changes in commit message
## Critical Fixes Needed
- [ ] Complete empty volume mount paths (lines 21, 55-57, 74, 89-90)
- [ ] Remove hardcoded admin password (line 53)
- [ ] Populate database credentials in .env file
- [ ] Add .gitignore to exclude .env from version control
- [ ] Consider adding docker-compose.override.yml for local development

426
ansible/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,426 @@
# Network Architecture
Visual overview of how all services connect and communicate.
## 🌐 Network Flow Diagram
```
INTERNET
│ DNS A Records
│ (cloud.yourdomain.com → SERVER_IP)
┌───────────────┐
│ Firewall (UFW)│
│ Ports: 80 │
│ 443 │
│ 41641 │
└───────┬───────┘
┌────────────────┼────────────────┐
│ │ │
Port 80/443 Port 41641 Port 22
│ │ │
↓ ↓ ↓
┌──────────────┐ ┌─────────────┐ ┌──────────┐
│ CADDY │ │ TAILSCALE │ │ SSH │
│ Reverse Proxy│ │ VPN │ │ Access │
└──────┬───────┘ └─────┬───────┘ └──────────┘
│ │
│ │
┌──────────┴────────┐ │
│ │ │
↓ ↓ ↓
┌─────────┐ ┌─────────────────┐
│ PUBLIC │ │ TAILSCALE-ONLY │
│SERVICES │ │ SERVICES │
└─────────┘ └─────────────────┘
```
---
## 🔐 Access Matrix
| Service | Public URL | Tailscale URL | Direct Access |
|---------|-----------|---------------|---------------|
| **Nextcloud** | ✅ https://cloud.domain.com | ✅ Via Tailscale IP | ❌ No |
| **OnlyOffice** | ✅ https://office.domain.com | ✅ Via Tailscale IP | ❌ No |
| **Excalidraw** | ✅ https://draw.domain.com | ✅ Via Tailscale IP | ❌ No |
| **Obsidian** | ✅ https://notes.domain.com | ✅ Via Tailscale IP | ❌ No |
| **Homarr** | ❌ 403 Forbidden | ✅ https://home.domain.com | ❌ No |
| **Dockhand** | ❌ 403 Forbidden | ✅ https://manage.domain.com | ❌ No |
| **Uptime Kuma** | ❌ 403 Forbidden | ✅ https://uptime.domain.com | ❌ No |
| **PostgreSQL** | ❌ No | ❌ No | ✅ Docker network only |
| **Redis** | ❌ No | ❌ No | ✅ Docker network only |
| **Caddy** | ❌ No (serves others) | ❌ No | ✅ Docker network only |
| **Watchtower** | ❌ No | ❌ No | ✅ Docker network only |
---
## 🐳 Docker Network Topology
```
┌────────────────────────────────────────────────────────────────┐
│ nextcloud_network (bridge) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │PostgreSQL│ │ Redis │ │Nextcloud │ │OnlyOffice│ │
│ │ :5432 │ │ :6379 │ │ :80 │ │ :80 │ │
│ └──────────┘ └──────────┘ └────┬─────┘ └──────────┘ │
│ ↑ ↑ │ │
│ │ │ │ │
│ └──────────────┴─────────────┘ │
│ │ │
│ ┌──────────┐ ┌────┴─────┐ ┌──────────┐ ┌──────────┐ │
│ │Excalidraw│ │ Caddy │ │ Obsidian │ │ Homarr │ │
│ │ :80 │ │80→:80 │ │ :3000 │ │ :7575 │ │
│ └──────────┘ │443→:443 │ └──────────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ ↓ │
│ │ Dockhand │ │ │ │ Uptime │ /var/run/ │
│ │ :3000 │ │ │ │ Kuma │ docker.sock │
│ └──────────┘ └──────────┘ │ :3001 │ (read-only) │
│ └──────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Watchtower (monitors all) │ │
│ │ Accesses: /var/run/docker.sock │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
│ │
│ Exposed Ports │ Internal
│ │ Communication
↓ ↓
Host: 80, 443 Container-to-Container
(Caddy only) via Docker DNS
```
---
## 🔄 Request Flow Examples
### Example 1: User Accesses Nextcloud
```
User Browser
│ HTTPS GET https://cloud.yourdomain.com
Internet
Firewall (Port 443)
Caddy Container (:443)
│ Validates SSL certificate
│ Checks Caddyfile rules
│ Proxies to backend
Nextcloud Container (:80)
│ Checks trusted domains
│ Queries PostgreSQL
│ Checks Redis cache
PostgreSQL + Redis
│ Returns data
Nextcloud → Caddy → User
```
### Example 2: User Accesses Homarr (via Tailscale)
```
User Browser (on Tailscale)
│ HTTPS GET https://home.yourdomain.com
Internet → DNS Resolution
Firewall (Port 443)
Caddy Container
│ Checks client IP: 100.64.x.x (Tailscale range)
│ ✅ Match! Proxy to backend
Homarr Container (:7575)
│ Reads Docker socket
│ Shows container status
Returns dashboard → Caddy → User
```
### Example 3: User Accesses Homarr (NOT on Tailscale)
```
User Browser (public internet)
│ HTTPS GET https://home.yourdomain.com
Internet → DNS Resolution
Firewall (Port 443)
Caddy Container
│ Checks client IP: NOT in 100.64.0.0/10
│ ❌ No match! Return 403 Forbidden
403 Access Denied
```
### Example 4: Nextcloud Connects to Database
```
Nextcloud Container
│ Docker DNS lookup: next-db
│ Resolves to: 172.20.0.x
PostgreSQL Container (next-db:5432)
│ Internal Docker network
│ No external exposure
Returns query results
```
---
## 📊 Data Flow Diagram
### File Upload to Nextcloud
```
User → Caddy → Nextcloud → Storage Volume
├→ PostgreSQL (metadata)
└→ Redis (cache)
```
### Document Editing with OnlyOffice
```
User → Caddy → Nextcloud → Caddy → OnlyOffice
│ │
↓ ↓
PostgreSQL Document
(metadata) Processing
│ │
↓ ↓
Nextcloud ←───────── OnlyOffice
Storage Volume
```
### Watchtower Auto-Update
```
Watchtower (cron: every 24h)
│ Check Docker Hub for new images
New image available?
├─ NO → Sleep
├─ YES → Check container labels
├─ "enable=false" → Skip
├─ "enable=true" → Pull & Update
└─ "monitor-only=true" → Notify
```
---
## 🔐 Security Boundaries
```
┌─────────────────────────────────────────────────────────────┐
│ INTERNET (Untrusted) │
└────────────────────────┬────────────────────────────────────┘
┌────────▼────────┐
│ UFW Firewall │ ← Layer 1: Host Firewall
│ 22, 80, 443 │
└────────┬────────┘
┌────────▼────────┐
│ fail2ban │ ← Layer 2: Intrusion Detection
│ SSH Protection │
└────────┬────────┘
┌────────▼────────┐
│ Caddy (HTTPS) │ ← Layer 3: TLS Termination
│ SSL Validation │
└────────┬────────┘
┌────────────────┼────────────────┐
│ │ │
┌───▼────┐ ┌────▼────┐ ┌─────▼─────┐
│ Public │ │Tailscale│ │ Docker │ ← Layer 4: Network
│Services│ │ Check │ │ Network │ Isolation
└────────┘ └─────────┘ └───────────┘
┌────▼────┐
│ Admin │ ← Layer 5: Admin Access
│ Services│ (Tailscale VPN only)
└─────────┘
```
---
## 🗄️ Data Persistence
### Docker Volumes
```
Host Filesystem
├── /var/lib/docker/volumes/
│ ├── nextcloud_pg_data/ ← PostgreSQL database
│ ├── nextcloud_redis_data/ ← Redis cache
│ ├── nextcloud_nextcloud_data/ ← Nextcloud files
│ ├── nextcloud_nextcloud_apps/ ← Nextcloud apps
│ ├── nextcloud_onlyoffice_data/ ← OnlyOffice docs
│ ├── nextcloud_caddy_data/ ← SSL certificates
│ ├── nextcloud_caddy_config/ ← Caddy config
│ ├── nextcloud_dockhand_data/ ← Dockhand settings
│ └── nextcloud_uptime_kuma_data/ ← Uptime Kuma data
└── /opt/nextcloud-stack/
├── configs/
│ ├── caddy/Caddyfile
│ └── nextcloud/
├── data/
│ ├── homarr/
│ └── obsidian/
└── backups/
├── database/
└── volumes/
```
---
## 🔄 Update Mechanism
### Watchtower Label-Based Control
```yaml
# Auto-update allowed
labels:
- "com.centurylinklabs.watchtower.enable=true"
→ Caddy, Excalidraw, Obsidian, Homarr, Dockhand, Uptime Kuma
# Monitor only (notify, no update)
labels:
- "com.centurylinklabs.watchtower.monitor-only=true"
→ OnlyOffice
# Never auto-update (manual only)
labels:
- "com.centurylinklabs.watchtower.enable=false"
→ Nextcloud, PostgreSQL, Redis
```
---
## 📡 Monitoring Architecture
```
┌────────────────────────────────────────────────────────┐
│ Uptime Kuma │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ HTTP Monitors (every 60s) │ │
│ │ • https://cloud.yourdomain.com/status.php │ │
│ │ • https://office.yourdomain.com/health │ │
│ │ • https://draw.yourdomain.com │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ TCP Port Monitors │ │
│ │ • next-db:5432 (PostgreSQL) │ │
│ │ • next-redis:6379 (Redis) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ SSL Certificate Monitor │ │
│ │ • Expiry check (30-day warning) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Notifications │ │
│ │ • Email alerts │ │
│ │ • Slack/Discord webhooks (optional) │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
```
---
## 🎯 Service Dependencies
```
Nextcloud
├─ requires → PostgreSQL (healthy)
├─ requires → Redis (healthy)
└─ requires → Caddy (for external access)
OnlyOffice
└─ requires → Caddy (for external access)
Homarr, Dockhand
├─ requires → Docker Socket (read-only)
└─ requires → Caddy (for Tailscale access)
Watchtower
└─ requires → Docker Socket (read-write)
All Services
└─ network → nextcloud_network
```
---
## 🌟 Architecture Benefits
### High Availability Features
- **Health checks**: Containers auto-restart on failure
- **Database connection pooling**: Redis caching reduces DB load
- **SSL auto-renewal**: Caddy handles certificate lifecycle
### Security Layers
1. **Network**: UFW firewall + fail2ban
2. **Transport**: TLS/SSL via Caddy
3. **Access**: Tailscale VPN for admin interfaces
4. **Secrets**: Ansible Vault encryption
5. **Isolation**: Docker network segmentation
### Scalability Points
- **Database**: Can migrate to external PostgreSQL
- **Cache**: Redis can be scaled/clustered
- **Storage**: Volumes can be moved to network storage
- **Reverse Proxy**: Caddy can be load-balanced
---
**This architecture provides a secure, maintainable, and production-ready self-hosted cloud platform.**

420
ansible/BACKUP_RESTORE.md Normal file
View File

@@ -0,0 +1,420 @@
# Backup and Restore Guide
Complete guide for backing up and restoring your Nextcloud Stack.
## Automated Backups
### What Gets Backed Up
- **Daily (3:00 AM)**:
- PostgreSQL database dump
- **Weekly (Sundays, 3:00 AM)**:
- Nextcloud data volume
- Configuration files
- **Retention**: 30 days
### Backup Locations
```
/opt/nextcloud-stack/backups/
├── database/
│ └── nextcloud_db_YYYYMMDD_HHMMSS.sql.gz
├── volumes/
│ ├── nextcloud_data_YYYYMMDD_HHMMSS.tar.gz
│ └── configs_YYYYMMDD_HHMMSS.tar.gz
└── backup.log
```
### Manual Backup
```bash
ssh user@server
/opt/nextcloud-stack/backup.sh
```
### Check Backup Status
```bash
# View backup log
tail -f /opt/nextcloud-stack/backups/backup.log
# List backups
ls -lh /opt/nextcloud-stack/backups/database/
ls -lh /opt/nextcloud-stack/backups/volumes/
# Check disk usage
du -sh /opt/nextcloud-stack/backups/
```
---
## Restore Procedures
### 1. Restore Database Only
**When to use:**
- Database corruption
- Accidental data deletion
- Need to revert to earlier state
**Steps:**
```bash
# 1. Stop Nextcloud (keep database running)
cd /opt/nextcloud-stack
docker compose stop next
# 2. List available backups
ls -lh backups/database/
# 3. Choose backup to restore
BACKUP_FILE="backups/database/nextcloud_db_20260216_030000.sql.gz"
# 4. Drop existing database (CAUTION!)
docker exec next-db psql -U nextcloud -c "DROP DATABASE nextcloud;"
docker exec next-db psql -U nextcloud -c "CREATE DATABASE nextcloud;"
# 5. Restore from backup
gunzip < $BACKUP_FILE | docker exec -i next-db psql -U nextcloud -d nextcloud
# 6. Restart Nextcloud
docker compose start next
# 7. Run Nextcloud upgrade (if needed)
docker exec -u www-data next php occ upgrade
# 8. Disable maintenance mode
docker exec -u www-data next php occ maintenance:mode --off
```
### 2. Restore Data Volumes
**When to use:**
- Lost files
- Corrupted data directory
- Complete system failure
**Steps:**
```bash
# 1. Stop all services
cd /opt/nextcloud-stack
docker compose down
# 2. List available backups
ls -lh backups/volumes/
# 3. Choose backup
BACKUP_FILE="backups/volumes/nextcloud_data_20260216_030000.tar.gz"
# 4. Restore volume
# Note: This requires accessing the Docker volume directory
sudo tar -xzf $BACKUP_FILE -C /var/lib/docker/volumes/nextcloud_nextcloud_data/_data/
# 5. Restore configs (optional)
CONFIG_BACKUP="backups/volumes/configs_20260216_030000.tar.gz"
sudo tar -xzf $CONFIG_BACKUP -C /opt/nextcloud-stack/configs/
# 6. Fix permissions
sudo chown -R www-data:www-data /var/lib/docker/volumes/nextcloud_nextcloud_data/_data/
# 7. Start services
docker compose up -d
# 8. Run Nextcloud file scan
docker exec -u www-data next php occ files:scan --all
```
### 3. Complete System Restore
**When to use:**
- Disaster recovery
- Migration to new server
- Complete system failure
**Steps:**
```bash
# 1. On new server, install base system
# Run Ansible playbooks 01-04 (up to Tailscale)
ansible-playbook playbooks/01-preflight-checks.yml --ask-vault-pass
ansible-playbook playbooks/02-system-setup.yml --ask-vault-pass
ansible-playbook playbooks/03-docker-setup.yml --ask-vault-pass
ansible-playbook playbooks/04-tailscale-setup.yml --ask-vault-pass
# 2. Create deployment directory structure
ssh user@new-server
sudo mkdir -p /opt/nextcloud-stack/backups/{database,volumes}
# 3. Copy backups from old server to new server
# On your local machine:
scp -r old-server:/opt/nextcloud-stack/backups/* user@new-server:/opt/nextcloud-stack/backups/
# 4. Deploy stack (creates volumes and containers)
ansible-playbook playbooks/05-deploy-stack.yml --ask-vault-pass
# 5. Stop services on new server
ssh user@new-server
cd /opt/nextcloud-stack
docker compose down
# 6. Restore database
BACKUP_FILE="backups/database/nextcloud_db_20260216_030000.sql.gz"
docker compose up -d next-db next-redis
sleep 10
gunzip < $BACKUP_FILE | docker exec -i next-db psql -U nextcloud -d nextcloud
# 7. Restore data volumes
VOLUME_BACKUP="backups/volumes/nextcloud_data_20260216_030000.tar.gz"
sudo tar -xzf $VOLUME_BACKUP -C /var/lib/docker/volumes/nextcloud_nextcloud_data/_data/
# 8. Restore configs
CONFIG_BACKUP="backups/volumes/configs_20260216_030000.tar.gz"
sudo tar -xzf $CONFIG_BACKUP -C /opt/nextcloud-stack/configs/
# 9. Fix permissions
sudo chown -R www-data:www-data /var/lib/docker/volumes/nextcloud_nextcloud_data/_data/
# 10. Start all services
docker compose up -d
# 11. Run Nextcloud maintenance
docker exec -u www-data next php occ maintenance:mode --off
docker exec -u www-data next php occ files:scan --all
docker exec -u www-data next php occ db:add-missing-indices
# 12. Continue with remaining playbooks
exit
ansible-playbook playbooks/06-configure-caddy.yml --ask-vault-pass
ansible-playbook playbooks/07-setup-backups.yml --ask-vault-pass
ansible-playbook playbooks/08-post-deployment.yml --ask-vault-pass
```
---
## Off-Site Backups with rclone
### Initial Setup
```bash
ssh user@server
# Configure rclone
rclone config
# Example: Setup Backblaze B2
# Follow prompts:
# - Choose: New remote
# - Name: b2backup
# - Storage: Backblaze B2
# - Enter account ID and application key
```
### Sync Backups to Remote
```bash
# Test sync (dry run)
rclone sync /opt/nextcloud-stack/backups/ b2backup:nextcloud-backups --dry-run
# Actual sync
rclone sync /opt/nextcloud-stack/backups/ b2backup:nextcloud-backups
```
### Automated Off-Site Backups
Add to `/opt/nextcloud-stack/backup.sh` (after the cleanup section):
```bash
# Off-site backup (if rclone configured)
if command -v rclone &> /dev/null && rclone listremotes | grep -q "b2backup"; then
log "Syncing backups to off-site storage..."
rclone sync $BACKUP_DIR b2backup:nextcloud-backups --log-file=$LOG_FILE
log "Off-site sync completed"
fi
```
### Restore from Off-Site Backup
```bash
# List remote backups
rclone ls b2backup:nextcloud-backups/database/
# Download specific backup
rclone copy b2backup:nextcloud-backups/database/nextcloud_db_20260216_030000.sql.gz /opt/nextcloud-stack/backups/database/
# Download all backups
rclone sync b2backup:nextcloud-backups /opt/nextcloud-stack/backups/
```
---
## Backup Verification
### Test Database Backup Integrity
```bash
# 1. Extract backup
gunzip -c backups/database/nextcloud_db_20260216_030000.sql.gz > /tmp/test_restore.sql
# 2. Check for errors
grep -i "error" /tmp/test_restore.sql
# 3. Verify size (should be reasonable)
ls -lh /tmp/test_restore.sql
# 4. Cleanup
rm /tmp/test_restore.sql
```
### Test Volume Backup Integrity
```bash
# Test tar archive
tar -tzf backups/volumes/nextcloud_data_20260216_030000.tar.gz | head -20
# Check for errors
tar -tzf backups/volumes/nextcloud_data_20260216_030000.tar.gz > /dev/null && echo "Archive OK"
```
---
## Migration to New Server
**Complete migration guide:**
1. **Prepare new server:**
- Same or newer Ubuntu version
- Run Ansible setup playbooks (01-04)
2. **Backup old server:**
```bash
/opt/nextcloud-stack/backup.sh
```
3. **Copy backups to new server:**
```bash
rsync -avz old-server:/opt/nextcloud-stack/backups/ new-server:/opt/nextcloud-stack/backups/
```
4. **Update DNS:**
- Point all domains to new server IP
- Wait for propagation
5. **Restore on new server:**
- Follow "Complete System Restore" steps above
6. **Verify:**
- Test all services
- Check Nextcloud functionality
- Verify SSL certificates
7. **Decommission old server:**
- Only after confirming new server works
- Keep final backup from old server
---
## Backup Best Practices
### DO:
- ✅ Test restores regularly (monthly)
- ✅ Keep backups off-site (rclone to cloud storage)
- ✅ Verify backup integrity
- ✅ Monitor backup log for errors
- ✅ Document restore procedures
- ✅ Keep multiple backup generations
### DON'T:
- ❌ Store backups only on same server
- ❌ Assume backups work without testing
- ❌ Delete old backups without verification
- ❌ Skip database backups
- ❌ Ignore backup failure notifications
---
## Backup Schedule Customization
Edit `/opt/nextcloud-stack/backup.sh` to change:
- Backup frequency
- Retention period
- What gets backed up
- Compression settings
Edit cron job:
```bash
sudo crontab -e
# Change from 3:00 AM to 2:00 AM
0 2 * * * /opt/nextcloud-stack/backup.sh >> /opt/nextcloud-stack/backups/backup.log 2>&1
```
---
## Troubleshooting Backups
### Backup script fails
**Check logs:**
```bash
tail -100 /opt/nextcloud-stack/backups/backup.log
```
**Common issues:**
- Disk space full
- Docker container not running
- Permission denied
**Solutions:**
```bash
# Check disk space
df -h
# Check containers
docker ps
# Fix permissions
sudo chown -R root:root /opt/nextcloud-stack/backup.sh
sudo chmod +x /opt/nextcloud-stack/backup.sh
```
### Database backup empty
**Verify database is running:**
```bash
docker exec next-db pg_isready -U nextcloud
```
**Test manual backup:**
```bash
docker exec next-db pg_dump -U nextcloud nextcloud > /tmp/test.sql
ls -lh /tmp/test.sql
```
---
## Emergency Recovery
If all backups are lost and you need to start fresh:
```bash
# 1. Remove everything
ansible-playbook playbooks/99-rollback.yml --ask-vault-pass
cd /opt/nextcloud-stack
docker compose down -v
# 2. Redeploy from scratch
ansible-playbook playbooks/site.yml --ask-vault-pass
# 3. Reconfigure Nextcloud manually
# Login and set up users, apps, etc.
```
---
**Remember:** Backups are worthless unless you've tested restoring from them!
**Last Updated:** 2026-02-16

470
ansible/DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,470 @@
# Quick Deployment Guide
Get your Nextcloud Stack running in under an hour!
## ⚡ Quick Start (3 Steps)
### Step 1: Configure (5-10 minutes)
```bash
./setup.sh
```
Answer the questions about your servers and preferences. This generates your encrypted configuration.
### Step 2: Deploy (30-45 minutes per server)
```bash
make deploy
```
Or manually:
```bash
ansible-playbook playbooks/site.yml --ask-vault-pass
```
### Step 3: Access Your Services
- **Nextcloud**: `https://cloud.yourdomain.com`
- **OnlyOffice**: `https://office.yourdomain.com`
- **Excalidraw**: `https://draw.yourdomain.com`
- **Obsidian**: `https://notes.yourdomain.com`
**Management (Tailscale only)**:
- **Homarr Dashboard**: `https://home.yourdomain.com`
- **Dockhand**: `https://manage.yourdomain.com`
- **Uptime Kuma**: `https://uptime.yourdomain.com`
---
## 📋 Prerequisites Checklist
### Before You Begin
- [ ] Ubuntu 20.04+ server (LXC or VPS)
- [ ] Root or sudo access
- [ ] Minimum 100GB disk space
- [ ] Ansible 2.14+ on your control machine
- [ ] Domain name with DNS access
### DNS Configuration (CRITICAL!)
Before deployment, create these A records pointing to your server IP:
```
cloud.yourdomain.com → YOUR_SERVER_IP
office.yourdomain.com → YOUR_SERVER_IP
draw.yourdomain.com → YOUR_SERVER_IP
notes.yourdomain.com → YOUR_SERVER_IP
home.yourdomain.com → YOUR_SERVER_IP
manage.yourdomain.com → YOUR_SERVER_IP
uptime.yourdomain.com → YOUR_SERVER_IP
```
**Verify DNS before deployment:**
```bash
dig +short cloud.yourdomain.com
# Should return: YOUR_SERVER_IP
```
---
## 🎯 What Gets Deployed
### 12 Services
| Service | Purpose | Auto-Update |
|---------|---------|-------------|
| **Nextcloud** | File sync & collaboration | ❌ Manual |
| **PostgreSQL 18** | Database | ❌ Manual |
| **Redis 7** | Cache | ❌ Manual |
| **OnlyOffice** | Document editor | 👁️ Monitor |
| **Excalidraw** | Whiteboard | ✅ Auto |
| **Obsidian** | Notes | ✅ Auto |
| **Caddy** | Reverse proxy + SSL | ✅ Auto |
| **Homarr** | Dashboard | ✅ Auto |
| **Dockhand** | Container manager | ✅ Auto |
| **Uptime Kuma** | Monitoring | ✅ Auto |
| **Watchtower** | Auto-updater | N/A |
### Security Features
- ✅ UFW firewall configured
- ✅ Fail2ban for SSH protection
- ✅ Automatic SSL certificates (Let's Encrypt)
- ✅ Ansible Vault encrypted secrets
- ✅ Tailscale-only admin interfaces
- ✅ Automatic security updates
### Backup System
- **Daily**: Database backups (3:00 AM)
- **Weekly**: Volume backups (Sundays)
- **Retention**: 30 days
- **Location**: `/opt/nextcloud-stack/backups/`
---
## 📝 Detailed Setup Process
### 1. Install Ansible (Control Machine)
**Ubuntu/Debian:**
```bash
sudo apt update
sudo apt install ansible
```
**macOS:**
```bash
brew install ansible
```
**Verify:**
```bash
ansible --version
# Should be 2.14 or higher
```
### 2. Clone Repository
```bash
git clone <your-repo-url>
cd ansible-nextcloud-deployment
```
### 3. Run Interactive Setup
```bash
chmod +x setup.sh
./setup.sh
```
**You'll be asked for:**
1. **Server Details** (for each server):
- IP address
- Hostname
- Domain (e.g., `example.com`)
- SSH user
- SSH key path
2. **User Information**:
- Your name
- Your email (for SSL certificates)
- Timezone
3. **Admin Account**:
- Nextcloud admin username
- Nextcloud admin password
4. **Subdomain Preferences** (defaults shown):
- Nextcloud: `cloud`
- OnlyOffice: `office`
- Excalidraw: `draw`
- Obsidian: `notes`
- Homarr: `home`
- Dockhand: `manage`
- Uptime Kuma: `uptime`
5. **Tailscale** (optional):
- Auth key (or skip for manual activation)
6. **Monitoring**:
- Alert email
- Public status page (yes/no)
7. **Backups**:
- Install rclone (yes/no)
8. **Ansible Vault Password**:
- Create a password to encrypt secrets
**Output:**
- `inventory/hosts.yml` - Server inventory
- `inventory/group_vars/all/vars.yml` - Public variables
- `inventory/group_vars/all/vault.yml` - Encrypted secrets
### 4. Test Connectivity
```bash
make ping
```
Or:
```bash
ansible all -m ping --ask-vault-pass
```
Expected output:
```
server-hostname | SUCCESS => {
"ping": "pong"
}
```
### 5. Run Deployment
```bash
make deploy
```
Or manually:
```bash
ansible-playbook playbooks/site.yml --ask-vault-pass
```
**What happens** (30-45 minutes):
1.**Preflight Checks** (2 min)
- Validates DNS, connectivity, disk space
2. ⚙️ **System Setup** (10-15 min)
- Installs packages
- Configures firewall (UFW)
- Sets up fail2ban
3. 🐳 **Docker Setup** (5 min)
- Removes old Docker
- Installs Docker CE + Compose v2
4. 🔐 **Tailscale** (2 min)
- Installs Tailscale
- Activates if auth key provided
5. 📦 **Deploy Stack** (10-15 min)
- Templates configuration files
- Pulls Docker images
- Starts all containers
- Initializes Nextcloud
6. 🌐 **Configure Caddy** (2-5 min)
- Validates configuration
- Obtains SSL certificates
7. 💾 **Setup Backups** (2 min)
- Creates backup script
- Sets up cron jobs
8.**Post-Deployment** (5 min)
- Verifies all services
- Installs Nextcloud apps
- Creates deployment report
### 6. Access Your Services
**Immediately after deployment:**
1. **Nextcloud**: `https://cloud.yourdomain.com`
- Login with admin credentials from setup
2. **Setup Uptime Kuma** (via Tailscale):
- `https://uptime.yourdomain.com`
- Create admin account on first visit
- Add monitors for your services
3. **Configure Homarr** (via Tailscale):
- `https://home.yourdomain.com`
- Add service tiles
- Customize dashboard
4. **OnlyOffice Integration**:
- Nextcloud → Settings → Administration → Office
- Document Server: `https://office.yourdomain.com`
---
## 🔧 Post-Deployment Tasks
### Activate Tailscale (if not done automatically)
```bash
ssh user@server
sudo tailscale up
```
### Test Backups
```bash
make backup
```
Or on the server:
```bash
ssh user@server
/opt/nextcloud-stack/backup.sh
```
### Check Service Status
```bash
make status
```
Or on the server:
```bash
ssh user@server
cd /opt/nextcloud-stack
docker compose ps
```
### View Logs
```bash
make logs
```
Or on the server:
```bash
ssh user@server
cd /opt/nextcloud-stack
docker compose logs -f
```
---
## 🎓 Learning Ansible Commands
### Useful Makefile Shortcuts
```bash
make help # Show all commands
make ping # Test connectivity
make check # Run preflight checks only
make deploy # Full deployment
make deploy-dry # Test without making changes
make status # Show container status
make logs # View all logs
make backup # Run manual backup
make restart # Restart all containers
make edit-vault # Edit encrypted secrets
```
### Direct Ansible Commands
```bash
# Run specific playbook
ansible-playbook playbooks/01-preflight-checks.yml --ask-vault-pass
# Run on specific server
ansible-playbook playbooks/site.yml --limit server1 --ask-vault-pass
# Dry run (no changes)
ansible-playbook playbooks/site.yml --ask-vault-pass --check
# View inventory
ansible-inventory --list
# Run ad-hoc command
ansible all -m shell -a "docker ps" --ask-vault-pass
```
---
## 🚨 Troubleshooting
### DNS Not Configured
**Symptom**: Let's Encrypt fails
**Solution**: Ensure all DNS A records point to server IP
```bash
dig +short cloud.yourdomain.com
```
### LXC Container Issues
**Symptom**: Docker won't start
**Solution** (on LXC host):
```bash
lxc config set CONTAINER_NAME security.nesting true
lxc restart CONTAINER_NAME
```
### Port Already in Use
**Symptom**: Caddy fails to start
**Solution**:
```bash
sudo systemctl stop apache2
sudo systemctl stop nginx
```
### Full troubleshooting guide:
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
---
## 📚 Additional Documentation
- [README.md](README.md) - Complete project overview
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Common issues and solutions
- [BACKUP_RESTORE.md](BACKUP_RESTORE.md) - Backup and restore procedures
- [AGENTS.md](AGENTS.md) - Development guidelines
---
## 🔒 Security Best Practices
### After Deployment
1. **Change default passwords** (if you used simple ones during testing)
2. **Enable 2FA** in Nextcloud
3. **Review firewall rules**: `sudo ufw status`
4. **Check fail2ban**: `sudo fail2ban-client status sshd`
5. **Verify SSL certificates**: Visit all your domains in browser
6. **Test backups**: Ensure they're working
7. **Setup monitoring alerts** in Uptime Kuma
### Ongoing Maintenance
- **Weekly**: Check Uptime Kuma for service health
- **Monthly**: Test backup restore
- **Quarterly**: Review security updates
- **Annually**: Rotate passwords
---
## 🆘 Getting Help
1. **Check logs:**
```bash
docker compose logs [service-name]
```
2. **Deployment report:**
```bash
cat /opt/nextcloud-stack/DEPLOYMENT.txt
```
3. **System status:**
```bash
docker compose ps
sudo systemctl status docker
```
---
## ✅ Success Checklist
After deployment, verify:
- [ ] All containers running: `docker compose ps`
- [ ] Nextcloud accessible via HTTPS
- [ ] SSL certificate valid (green padlock in browser)
- [ ] Can login to Nextcloud
- [ ] OnlyOffice integration working
- [ ] Uptime Kuma showing all services green
- [ ] Homarr dashboard accessible
- [ ] Backup script runs successfully
- [ ] Tailscale connected (if using)
---
**You're all set! Enjoy your self-hosted Nextcloud stack! 🎉**
For questions or issues, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
**Last Updated:** 2026-02-16

61
ansible/Makefile Normal file
View File

@@ -0,0 +1,61 @@
# Makefile for Nextcloud Stack Deployment
# Provides convenient shortcuts for common Ansible operations
.PHONY: help setup ping check deploy deploy-stack deploy-dry update backup logs status restart rollback clean edit-vault rekey-vault
help: ## Show this help message
@echo "Nextcloud Stack Deployment - Make Commands"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
setup: ## Run interactive setup script
@./setup.sh
ping: ## Test connectivity to all servers
@ansible all -m ping --ask-vault-pass
check: ## Run preflight checks only
@ansible-playbook playbooks/01-preflight-checks.yml --ask-vault-pass
deploy: ## Full deployment (all playbooks)
@ansible-playbook playbooks/site.yml --ask-vault-pass
deploy-stack: ## Deploy stack only (skip system setup)
@ansible-playbook playbooks/05-deploy-stack.yml --ask-vault-pass
deploy-dry: ## Dry run (check mode)
@ansible-playbook playbooks/site.yml --ask-vault-pass --check
update: ## Update Docker images (safe services only)
@ansible all -m shell -a "cd /opt/nextcloud-stack && docker compose pull && docker compose up -d" --ask-vault-pass
backup: ## Run manual backup on all servers
@ansible all -m shell -a "/opt/nextcloud-stack/backup.sh" --ask-vault-pass
logs: ## Tail logs from all containers
@ansible all -m shell -a "cd /opt/nextcloud-stack && docker compose logs -f --tail=100" --ask-vault-pass
status: ## Show container status
@ansible all -m shell -a "cd /opt/nextcloud-stack && docker compose ps" --ask-vault-pass
restart: ## Restart all containers
@ansible all -m shell -a "cd /opt/nextcloud-stack && docker compose restart" --ask-vault-pass
rollback: ## Emergency rollback
@ansible-playbook playbooks/99-rollback.yml --ask-vault-pass
clean: ## Remove generated inventory (keeps vault)
@rm -f inventory/hosts.yml inventory/group_vars/all/vars.yml
@echo "Cleaned generated files (vault.yml preserved)"
edit-vault: ## Edit encrypted vault
@ansible-vault edit inventory/group_vars/all/vault.yml
rekey-vault: ## Change vault password
@ansible-vault rekey inventory/group_vars/all/vault.yml
inventory: ## Show inventory configuration
@ansible-inventory --list
graph: ## Show inventory graph
@ansible-inventory --graph

560
ansible/PROJECT_SUMMARY.md Normal file
View File

@@ -0,0 +1,560 @@
# Nextcloud Stack - Ansible Deployment Project
## 🎉 Project Complete!
This Ansible automation project provides complete, one-command deployment of a self-hosted Nextcloud productivity stack to Ubuntu-based LXC/VPS servers.
---
## 📦 What's Included
### Core Files
```
ansible-nextcloud-deployment/
├── setup.sh # ⭐ Interactive configuration wizard
├── Makefile # Convenient command shortcuts
├── ansible.cfg # Ansible configuration
├── .gitignore # Git ignore rules
├── inventory/ # Generated by setup.sh
│ ├── hosts.yml # Server inventory (generated)
│ └── group_vars/all/
│ ├── vars.yml # Public variables (generated)
│ └── vault.yml # Encrypted secrets (generated)
├── playbooks/ # ⭐ Deployment playbooks
│ ├── site.yml # Main orchestrator
│ ├── 01-preflight-checks.yml # DNS, connectivity validation
│ ├── 02-system-setup.yml # Packages, firewall, security
│ ├── 03-docker-setup.yml # Docker CE installation
│ ├── 04-tailscale-setup.yml # VPN setup
│ ├── 05-deploy-stack.yml # Docker Compose deployment
│ ├── 06-configure-caddy.yml # Reverse proxy + SSL
│ ├── 07-setup-backups.yml # Backup automation
│ ├── 08-post-deployment.yml # Final verification
│ └── 99-rollback.yml # Emergency rollback
├── roles/ # ⭐ Ansible roles
│ ├── nextcloud_stack/
│ │ └── templates/
│ │ ├── docker-compose.yml.j2 # Complete stack definition
│ │ └── env.j2 # Environment variables
│ └── caddy/
│ └── templates/
│ └── Caddyfile.j2 # Reverse proxy config
└── docs/ # ⭐ Documentation
├── README.md # Project overview
├── DEPLOYMENT_GUIDE.md # Quick start guide
├── TROUBLESHOOTING.md # Common issues & solutions
└── BACKUP_RESTORE.md # Backup procedures
```
### Stack Components (12 Services)
**Public Services** (via HTTPS):
- Nextcloud (file sync & collaboration)
- OnlyOffice (document editor)
- Excalidraw (whiteboard)
- Obsidian (note-taking)
**Infrastructure**:
- PostgreSQL 18 (database)
- Redis 7 (cache)
- Caddy (reverse proxy + automatic SSL)
**Management** (Tailscale-only):
- Homarr (dashboard)
- Dockhand (container manager)
- Uptime Kuma (monitoring)
**Automation**:
- Watchtower (auto-updates with safety exclusions)
---
## 🚀 Quick Start
### 1. Configure
```bash
./setup.sh
```
Interactive wizard collects all configuration:
- Server details (IP, domain, SSH)
- User information
- Admin credentials
- Subdomain preferences
- Tailscale auth key (optional)
- Monitoring settings
**Generates:**
- `inventory/hosts.yml` - Server inventory
- `inventory/group_vars/all/vars.yml` - Public config
- `inventory/group_vars/all/vault.yml` - Encrypted secrets
### 2. Deploy
```bash
make deploy
```
Or:
```bash
ansible-playbook playbooks/site.yml --ask-vault-pass
```
**Duration:** 30-45 minutes per server
### 3. Access
- Nextcloud: `https://cloud.yourdomain.com`
- Homarr: `https://home.yourdomain.com` (Tailscale)
- Uptime Kuma: `https://uptime.yourdomain.com` (Tailscale)
---
## ✨ Key Features
### 🔒 Security
- **Firewall**: UFW configured (only SSH, HTTP, HTTPS, Tailscale open)
- **Fail2ban**: SSH brute-force protection (5 failures = 10 min ban)
- **Automatic Security Updates**: Unattended upgrades enabled
- **Secret Management**: Ansible Vault encryption for all credentials
- **Access Control**: Management UIs restricted to Tailscale network
- **SSL/TLS**: Automatic Let's Encrypt certificates via Caddy
- **Container Isolation**: Dedicated Docker network
### 💾 Backups
- **Daily**: PostgreSQL database dumps (3:00 AM)
- **Weekly**: Full volume backups (Sundays, 3:00 AM)
- **Retention**: 30 days
- **Location**: `/opt/nextcloud-stack/backups/`
- **Off-site Ready**: rclone pre-installed
- **Automated**: Cron jobs configured
- **Tested**: Documented restore procedures
### 🔄 Updates
**Automated (Watchtower)**:
- Caddy, Excalidraw, Obsidian, Homarr, Dockhand, Uptime Kuma
**Monitor Only**:
- OnlyOffice (notifications, manual update)
**Manual Required**:
- Nextcloud, PostgreSQL, Redis (too critical to auto-update)
### 🌐 Multi-Server Support
Deploy identical stacks to multiple servers:
- Each server gets unique domain
- Fully independent deployments
- Shared Tailscale network (optional)
### 📊 Monitoring
- **Uptime Kuma**: Service health monitoring
- **Email Alerts**: Configurable notifications
- **Status Page**: Optional public status page
- **Container Health**: Docker healthchecks
- **SSL Monitoring**: Certificate expiry tracking
---
## 📚 Documentation
| Document | Purpose |
|----------|---------|
| [README.md](README.md) | Complete project overview, features, usage |
| [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) | Step-by-step deployment instructions |
| [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | Common issues and solutions |
| [BACKUP_RESTORE.md](BACKUP_RESTORE.md) | Backup and restore procedures |
| [AGENTS.md](AGENTS.md) | Docker Compose & Ansible guidelines |
---
## 🛠️ Makefile Commands
```bash
make help # Show all commands
make setup # Run interactive setup
make ping # Test connectivity
make check # Run preflight checks
make deploy # Full deployment
make deploy-dry # Test run (no changes)
make status # Show container status
make logs # View all logs
make backup # Run manual backup
make restart # Restart all containers
make edit-vault # Edit encrypted secrets
make rekey-vault # Change vault password
```
---
## 🎯 Design Principles
### 1. **User-Friendly**
- Interactive setup script (no manual configuration editing)
- Automatic SSL certificates
- Clear documentation
- Helpful error messages
### 2. **Secure by Default**
- Minimal firewall rules
- Encrypted secrets (Ansible Vault)
- Tailscale-only admin interfaces
- Automatic security updates
- No hardcoded credentials
### 3. **Production-Ready**
- Automated backups
- Monitoring included
- Health checks configured
- Documented restore procedures
- Emergency rollback playbook
### 4. **Maintainable**
- Modular playbook structure
- Clear variable naming
- Comprehensive comments
- Role-based organization
- Git-friendly (.gitignore configured)
### 5. **Safe Updates**
- Critical services excluded from auto-update
- Watchtower uses label-based control
- Manual update procedures documented
- Rollback capability included
---
## 🔧 Deployment Flow
```
┌─────────────────────────────────────────────────────────────┐
│ 1. SETUP (./setup.sh) │
│ • Collect server details, domains, credentials │
│ • Generate encrypted inventory & variables │
│ • Create Ansible Vault with secrets │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 2. PREFLIGHT CHECKS (01-preflight-checks.yml) │
│ • Verify Ansible version, SSH connectivity │
│ • Check disk space, ports availability │
│ • Validate DNS records, detect LXC │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 3. SYSTEM SETUP (02-system-setup.yml) │
│ • Update packages, install essentials │
│ • Configure UFW firewall, fail2ban │
│ • Enable automatic security updates │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 4. DOCKER SETUP (03-docker-setup.yml) │
│ • Remove old Docker installations │
│ • Install Docker CE + Compose v2 │
│ • Configure Docker daemon │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 5. TAILSCALE SETUP (04-tailscale-setup.yml) │
│ • Install Tailscale │
│ • Activate if auth key provided │
│ • Enable systemd service │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 6. DEPLOY STACK (05-deploy-stack.yml) │
│ • Create directory structure │
│ • Template docker-compose.yml, .env, configs │
│ • Pull Docker images, start containers │
│ • Wait for services healthy │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 7. CONFIGURE CADDY (06-configure-caddy.yml) │
│ • Validate Caddyfile syntax │
│ • Reload Caddy configuration │
│ • Obtain Let's Encrypt SSL certificates │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 8. SETUP BACKUPS (07-setup-backups.yml) │
│ • Create backup script │
│ • Configure cron jobs (daily/weekly) │
│ • Run test backup │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 9. POST-DEPLOYMENT (08-post-deployment.yml) │
│ • Verify all containers running │
│ • Install Nextcloud apps │
│ • Configure background jobs │
│ • Create deployment report │
└─────────────────────────────────────────────────────────────┘
✅ DEPLOYMENT COMPLETE!
```
---
## 🧩 Template Files
### docker-compose.yml.j2
Complete Docker Compose stack with:
- All 12 services configured
- Health checks for critical services
- Proper dependencies (depends_on with conditions)
- Watchtower labels for update control
- Named volumes and networks
- Environment variables from .env
**Location**: `roles/nextcloud_stack/templates/docker-compose.yml.j2`
### Caddyfile.j2
Caddy reverse proxy configuration with:
- Automatic SSL for all domains
- Public services (Nextcloud, OnlyOffice, etc.)
- Tailscale-restricted services (Homarr, Dockhand, Uptime Kuma)
- Security headers
- Large file upload support (10GB)
- Optional public status page
**Location**: `roles/caddy/templates/Caddyfile.j2`
### env.j2
Environment variables template with:
- Database credentials
- Redis password
- Nextcloud admin credentials
- Application secrets (Homarr)
- Domain configuration
- Timezone settings
**Location**: `roles/nextcloud_stack/templates/env.j2`
---
## 🔐 Security Considerations
### Secrets Management
All sensitive data encrypted with Ansible Vault:
- Database passwords
- Admin credentials
- Application secrets
- Tailscale auth keys
**Never committed in plain text!**
### Network Security
- **Public**: Only Nextcloud and productivity apps
- **Tailscale-only**: All management interfaces
- **Internal**: Database and Redis (no external access)
### Firewall Rules (UFW)
```
22/tcp - SSH
80/tcp - HTTP (redirects to HTTPS)
443/tcp - HTTPS
443/udp - HTTP/3 (QUIC)
41641/udp - Tailscale
Default: DENY
```
### Update Strategy
**Why not auto-update everything?**
- **Nextcloud**: Database migrations may fail automatically
- **PostgreSQL**: Schema changes could break Nextcloud
- **Redis**: Unlikely issues, but excluded for consistency
**Safe to auto-update:**
- Stateless applications (Excalidraw, Obsidian)
- Infrastructure (Caddy)
- Management tools (Homarr, Dockhand)
---
## 📊 Resource Usage (Approximate)
### Disk Space
- Docker images: ~5GB
- Initial volumes: ~2GB
- Backups (30 days): ~50GB
- **Total recommended**: 100GB minimum
### Memory
- PostgreSQL: ~256MB
- Redis: ~256MB (configured limit)
- Nextcloud: ~512MB
- OnlyOffice: ~2GB
- Other services: ~1GB combined
- **Total**: 4-6GB RAM recommended
### Network
- **Inbound**: 80, 443 (HTTP/HTTPS), 41641 (Tailscale)
- **Outbound**: Internet access required for Let's Encrypt, Docker images, updates
---
## 🎓 Next Steps
### Immediate
1. **Test the deployment** on a test server first
2. **Verify DNS** is configured correctly
3. **Run setup.sh** and answer questions carefully
4. **Deploy** with `make deploy`
5. **Access Nextcloud** and complete initial setup
### After Deployment
1. **Configure Uptime Kuma** monitoring
2. **Customize Homarr** dashboard
3. **Test backups** with `make backup`
4. **Enable 2FA** in Nextcloud
5. **Install additional Nextcloud apps** as needed
### Ongoing
1. **Monitor** service health in Uptime Kuma
2. **Review backups** monthly
3. **Test restore** procedures quarterly
4. **Update Nextcloud** when new versions release (manually)
5. **Check logs** for any issues
---
## 💡 Tips & Best Practices
### For LXC Containers
**On LXC host, enable nested virtualization:**
```bash
lxc config set CONTAINER_NAME security.nesting true
lxc restart CONTAINER_NAME
```
### For Production Use
1. Use strong passwords (not example passwords)
2. Enable Nextcloud 2FA
3. Configure off-site backups with rclone
4. Set up Uptime Kuma email alerts
5. Review logs regularly
### For Multiple Servers
Each server can have:
- Different domain
- Different subdomain preferences
- Same or different Tailscale network
Just run `setup.sh` and add all servers when prompted.
---
## 🐛 Known Limitations
1. **Watchtower is archived** (December 2025)
- Still works, but no longer maintained
- Consider alternative update strategies long-term
2. **OnlyOffice needs 2GB+ RAM**
- May be slow on low-memory servers
- Consider disabling if not needed
3. **First-time deployment takes 30-45 min**
- Mostly Docker image downloads
- Subsequent deploys are faster
4. **Let's Encrypt rate limits**
- 50 certs/week per domain
- Use staging server for testing
---
## 📜 License
MIT License - Use freely, modify as needed
---
## 🙏 Acknowledgments
Built with these amazing open-source projects:
- **Nextcloud** - File sync and collaboration
- **Caddy** - Automatic HTTPS web server
- **Ansible** - IT automation
- **Docker** - Containerization
- **PostgreSQL** - Database
- **Tailscale** - Zero-config VPN
- **OnlyOffice**, **Excalidraw**, **Obsidian** - Productivity tools
- **Homarr**, **Dockhand**, **Uptime Kuma** - Management & monitoring
---
## 📞 Support
**Documentation:**
- [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) - Getting started
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Fix common issues
- [BACKUP_RESTORE.md](BACKUP_RESTORE.md) - Backup procedures
**Project Files:**
All source code, playbooks, and templates included in this repository.
---
## ✅ Checklist for New Users
Before deploying:
- [ ] Read DEPLOYMENT_GUIDE.md
- [ ] Install Ansible 2.14+ on control machine
- [ ] Have Ubuntu 20.04+ server ready
- [ ] Configure DNS A records
- [ ] Test SSH access to server
- [ ] Have domain name ready
- [ ] Decide on subdomain preferences
After deploying:
- [ ] Access Nextcloud web UI
- [ ] Complete Nextcloud setup wizard
- [ ] Setup Uptime Kuma monitoring
- [ ] Configure Homarr dashboard
- [ ] Test backup with `make backup`
- [ ] Enable Nextcloud 2FA
- [ ] Review deployment report
- [ ] Save Ansible Vault password securely
---
**Project Status**: ✅ Complete and Ready for Use
**Last Updated**: 2026-02-16
**Version**: 1.0
---
**Happy self-hosting! 🎉**

382
ansible/README.md Normal file
View File

@@ -0,0 +1,382 @@
# Nextcloud Stack - Automated Deployment with Ansible
Complete automation for deploying a self-hosted Nextcloud productivity stack on Ubuntu-based LXC/VPS servers.
## Features
- **12 Services**: Nextcloud, OnlyOffice, Excalidraw, Obsidian, Homarr, Dockhand, Uptime Kuma, PostgreSQL, Redis, Caddy, Watchtower
- **Automatic SSL**: Let's Encrypt certificates via Caddy
- **Secure Management**: Tailscale-only access for admin interfaces
- **Auto-updates**: Watchtower with safety exclusions for critical services
- **Automated Backups**: Daily database backups, weekly volume backups (30-day retention)
- **Monitoring**: Uptime Kuma with configurable alerts
- **Multi-server**: Deploy to multiple VPS servers with unique domains
## Quick Start
### Prerequisites
1. **Control Machine** (your laptop):
- Ansible 2.14+ installed
- SSH access to target servers
2. **Target Server(s)**:
- Ubuntu 20.04+ (LXC container or VPS)
- Root or sudo access
- Minimum 100GB disk space
- Ports 80, 443 available
3. **DNS Configuration** (BEFORE deployment):
```
cloud.yourdomain.com → YOUR_SERVER_IP
office.yourdomain.com → YOUR_SERVER_IP
draw.yourdomain.com → YOUR_SERVER_IP
notes.yourdomain.com → YOUR_SERVER_IP
home.yourdomain.com → YOUR_SERVER_IP
manage.yourdomain.com → YOUR_SERVER_IP
uptime.yourdomain.com → YOUR_SERVER_IP
```
### Installation
1. **Clone this repository**:
```bash
git clone <your-repo-url>
cd ansible-nextcloud-deployment
```
2. **Run interactive setup**:
```bash
./setup.sh
```
This will ask for:
- Server IP addresses and domains
- Your name and email
- Admin credentials
- Subdomain preferences
- Tailscale auth key (optional)
- Monitoring email
- Ansible Vault password
3. **Deploy the stack**:
```bash
make deploy
```
Or manually:
```bash
ansible-playbook playbooks/site.yml --ask-vault-pass
```
4. **Access your services**:
- Nextcloud: `https://cloud.yourdomain.com`
- OnlyOffice: `https://office.yourdomain.com`
- Homarr (Tailscale only): `https://home.yourdomain.com`
- Uptime Kuma (Tailscale only): `https://uptime.yourdomain.com`
## Project Structure
```
ansible-nextcloud-deployment/
├── setup.sh # Interactive configuration script
├── Makefile # Convenient command shortcuts
├── ansible.cfg # Ansible configuration
├── inventory/
│ ├── hosts.yml # Generated by setup.sh
│ └── group_vars/all/
│ ├── vars.yml # Public variables
│ └── vault.yml # Encrypted secrets
├── playbooks/
│ ├── site.yml # Main orchestrator
│ ├── 01-preflight-checks.yml # Pre-deployment validation
│ ├── 02-system-setup.yml # System packages & security
│ ├── 03-docker-setup.yml # Docker installation
│ ├── 04-tailscale-setup.yml # VPN setup
│ ├── 05-deploy-stack.yml # Docker Compose deployment
│ ├── 06-configure-caddy.yml # Reverse proxy configuration
│ ├── 07-setup-backups.yml # Backup automation
│ └── 08-post-deployment.yml # Final verification
└── roles/
├── nextcloud_stack/
│ └── templates/
│ ├── docker-compose.yml.j2
│ └── env.j2
└── caddy/
└── templates/
└── Caddyfile.j2
```
## Makefile Commands
```bash
make help # Show all commands
make setup # Run interactive setup
make ping # Test connectivity
make check # Run preflight checks
make deploy # Full deployment
make deploy-dry # Dry run (no changes)
make status # Show container status
make logs # View container logs
make backup # Run manual backup
make restart # Restart all containers
make edit-vault # Edit encrypted secrets
```
## Service Stack
| Service | Purpose | Port | Access | Auto-Update |
|---------|---------|------|--------|-------------|
| Caddy | Reverse proxy + SSL | 80, 443 | Public | ✅ |
| Nextcloud | File sync & collaboration | Internal | Public (via Caddy) | ❌ Manual |
| PostgreSQL 18 | Database | Internal | Internal only | ❌ Manual |
| Redis 7 | Cache layer | Internal | Internal only | ❌ Manual |
| OnlyOffice | Document editor | Internal | Public (via Caddy) | 👁️ Monitor |
| Excalidraw | Whiteboard tool | Internal | Public (via Caddy) | ✅ |
| Obsidian | Note-taking | Internal | Public (via Caddy) | ✅ |
| Homarr | Dashboard | Internal | Tailscale only | ✅ |
| Dockhand | Container manager | 3003 | Tailscale only | ✅ |
| Uptime Kuma | Monitoring | Internal | Tailscale only | ✅ |
| Watchtower | Auto-updater | N/A | N/A | N/A |
## Security Features
- **Firewall**: UFW configured with minimal open ports
- **Fail2ban**: SSH brute-force protection
- **Automatic Updates**: Unattended security updates enabled
- **Secret Management**: Ansible Vault encryption for all credentials
- **Access Control**: Management UIs restricted to Tailscale network
- **SSL/TLS**: Automatic Let's Encrypt certificates
- **Container Isolation**: Dedicated Docker network
## Backup System
- **Daily**: PostgreSQL database dumps (3:00 AM)
- **Weekly**: Full volume backups (Sundays)
- **Retention**: 30 days
- **Location**: `/opt/nextcloud-stack/backups/`
- **rclone**: Pre-installed for future remote backups
### Manual Backup
```bash
ssh user@server
cd /opt/nextcloud-stack
./backup.sh
```
### Restore from Backup
See [BACKUP_RESTORE.md](BACKUP_RESTORE.md) for detailed procedures.
## Tailscale Setup
If you didn't provide an auth key during setup:
```bash
ssh user@server
sudo tailscale up
```
Then access management interfaces via Tailscale IP or MagicDNS.
## Updating Services
### Safe to Auto-Update (Watchtower handles)
- Caddy, Excalidraw, Obsidian, Homarr, Dockhand, Uptime Kuma
### Monitor Only (notifications, no auto-update)
- OnlyOffice
### Manual Update Required
- Nextcloud
- PostgreSQL
- Redis
### Updating Nextcloud Manually
```bash
ssh user@server
cd /opt/nextcloud-stack
# 1. Backup
./backup.sh
# 2. Enable maintenance mode
docker exec -u www-data next php occ maintenance:mode --on
# 3. Update
docker compose pull next
docker compose up -d next
# 4. Run database migrations
docker exec -u www-data next php occ upgrade
# 5. Disable maintenance mode
docker exec -u www-data next php occ maintenance:mode --off
```
## Troubleshooting
### DNS Not Configured
**Error**: Let's Encrypt fails to issue certificates
**Solution**: Ensure all DNS A records point to your server IP. Check with:
```bash
dig +short cloud.yourdomain.com
```
### LXC Container Issues
**Error**: Docker fails to start
**Solution**: On LXC host, enable nested virtualization:
```bash
lxc config set CONTAINER_NAME security.nesting true
lxc restart CONTAINER_NAME
```
### Port Already in Use
**Error**: Port 80 or 443 already bound
**Solution**: Check what's using the ports:
```bash
sudo ss -tlnp | grep ':80\|:443'
sudo systemctl stop apache2 # or nginx
```
### Nextcloud Stuck in Maintenance Mode
```bash
docker exec -u www-data next php occ maintenance:mode --off
```
### View Logs
```bash
cd /opt/nextcloud-stack
docker compose logs -f [service-name]
```
## Post-Deployment Tasks
1. **Login to Nextcloud**:
- URL: `https://cloud.yourdomain.com`
- Username: (set during setup)
- Password: (stored in vault)
2. **Setup Uptime Kuma**:
- URL: `https://uptime.yourdomain.com` (via Tailscale)
- Create admin account on first visit
- Configure monitors for your services
3. **Configure Homarr Dashboard**:
- URL: `https://home.yourdomain.com` (via Tailscale)
- Add service tiles
- Customize layout
4. **Configure OnlyOffice in Nextcloud**:
- Nextcloud Settings → Administration → Office
- Document Editing Service: `https://office.yourdomain.com`
5. **Test Backups**:
```bash
make backup
```
## File Locations on Server
```
/opt/nextcloud-stack/
├── docker-compose.yml
├── .env
├── backup.sh
├── configs/
│ ├── caddy/Caddyfile
│ └── nextcloud/
├── data/
│ ├── homarr/
│ └── obsidian/
└── backups/
├── database/
└── volumes/
```
## Environment Variables
All secrets are stored in `inventory/group_vars/all/vault.yml` (encrypted).
To edit:
```bash
make edit-vault
```
## Multiple Server Deployment
The setup script supports multiple servers. Each can have its own domain:
```bash
Server 1: 192.168.1.100 → cloud1.example.com
Server 2: 192.168.1.101 → cloud2.anotherdomain.net
Server 3: 192.168.1.102 → personal.mydomain.org
```
Deploy to all:
```bash
make deploy
```
Deploy to specific server:
```bash
ansible-playbook playbooks/site.yml --limit server_hostname --ask-vault-pass
```
## Maintenance
### Check Container Status
```bash
make status
```
### Restart Services
```bash
make restart
```
### Update Images (safe services only)
```bash
make update
```
### View Logs
```bash
make logs
```
## Uninstallation
```bash
ssh user@server
cd /opt/nextcloud-stack
docker compose down -v # WARNING: Deletes all data!
cd /opt
rm -rf nextcloud-stack
```
## Support
- See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for common issues
- See [BACKUP_RESTORE.md](BACKUP_RESTORE.md) for backup procedures
- See [AGENTS.md](AGENTS.md) for development guidelines
## License
MIT
## Acknowledgments
- Nextcloud team
- Caddy web server
- Ansible community
- All open-source contributors
---
**Built with ❤️ for self-hosting enthusiasts**

192
ansible/START_HERE.md Normal file
View File

@@ -0,0 +1,192 @@
# 👋 Welcome to Nextcloud Stack Ansible Deployment!
## 🚀 Quick Start (Choose Your Path)
### 🏃 I Want to Deploy NOW
1. Read → [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) (10 min read)
2. Run → `./setup.sh` (5-10 min)
3. Deploy → `make deploy` (30-45 min)
### 📚 I Want to Learn More First
1. Read → [README.md](README.md) - Complete project overview
2. Read → [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) - Design & architecture
3. Then → Follow Quick Start above
### 🔧 I'm Having Issues
1. Check → [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
2. Check logs → `make logs`
3. Review → [BACKUP_RESTORE.md](BACKUP_RESTORE.md) if you need to restore
---
## 📖 Documentation Index
| Document | Purpose | Read Time |
|----------|---------|-----------|
| **[DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md)** | Step-by-step deployment instructions | 10 min |
| **[README.md](README.md)** | Complete project overview & features | 15 min |
| **[PROJECT_SUMMARY.md](PROJECT_SUMMARY.md)** | Technical details & design | 10 min |
| **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** | Common issues & solutions | Reference |
| **[BACKUP_RESTORE.md](BACKUP_RESTORE.md)** | Backup & restore procedures | Reference |
| **[AGENTS.md](AGENTS.md)** | Docker & Ansible development guidelines | Reference |
---
## ✅ Pre-Deployment Checklist
Before you begin, ensure you have:
- [ ] Ubuntu 20.04+ server (LXC or VPS)
- [ ] Root or sudo access to server
- [ ] Minimum 100GB disk space on server
- [ ] Ansible 2.14+ installed on your laptop/control machine
- [ ] Domain name with DNS control
- [ ] SSH access configured to server
**Critical:** DNS A records must point to your server BEFORE deployment!
---
## 🎯 What You'll Get
After deployment, you'll have:
### Public Services (HTTPS)
- **Nextcloud** - File sync & collaboration
- **OnlyOffice** - Document editor (Word, Excel, PowerPoint)
- **Excalidraw** - Whiteboard & diagrams
- **Obsidian** - Note-taking & knowledge management
### Management Tools (Tailscale-only, secure)
- **Homarr** - Beautiful dashboard for all services
- **Dockhand** - Container management interface
- **Uptime Kuma** - Monitoring & alerts
### Infrastructure (automatic, invisible)
- **Caddy** - Reverse proxy with automatic SSL
- **PostgreSQL** - Database
- **Redis** - Cache for better performance
- **Watchtower** - Automatic updates (safe services only)
---
## 💡 Key Features
-**One-command deployment** - `make deploy` does everything
-**Automatic SSL** - Let's Encrypt certificates via Caddy
-**Secure by default** - Firewall, fail2ban, Vault encryption
-**Auto backups** - Daily DB, weekly volumes (30-day retention)
-**Multi-server** - Deploy to multiple VPS with unique domains
-**Production-ready** - Monitoring, backups, documentation
---
## 🏗️ Project Structure
```
/home/liph/programming/ansible/
├── setup.sh ⭐ # Start here! Interactive setup
├── Makefile # Convenient commands (make deploy)
├── playbooks/ # 10 Ansible playbooks
│ ├── site.yml # Main orchestrator
│ ├── 01-preflight-checks.yml # Validates environment
│ ├── 02-system-setup.yml # Packages & security
│ ├── 03-docker-setup.yml # Docker installation
│ ├── 04-tailscale-setup.yml # VPN setup
│ ├── 05-deploy-stack.yml # Main deployment
│ ├── 06-configure-caddy.yml # SSL & reverse proxy
│ ├── 07-setup-backups.yml # Backup automation
│ ├── 08-post-deployment.yml # Verification
│ └── 99-rollback.yml # Emergency rollback
├── roles/ # Templates & configs
│ ├── nextcloud_stack/
│ │ └── templates/
│ │ ├── docker-compose.yml.j2 # 12-service stack
│ │ └── env.j2 # Environment vars
│ └── caddy/
│ └── templates/
│ └── Caddyfile.j2 # Reverse proxy config
└── docs/ # You are here!
```
---
## 🎓 Common Commands
After setup, you'll use these frequently:
```bash
make deploy # Deploy everything
make status # Check container status
make logs # View all logs
make backup # Run manual backup
make restart # Restart all containers
make edit-vault # Edit encrypted secrets
```
---
## 📞 Need Help?
1. **Common Issues**: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
2. **Backup/Restore**: [BACKUP_RESTORE.md](BACKUP_RESTORE.md)
3. **Full Documentation**: [README.md](README.md)
---
## ⚡ 30-Second Overview
This project automates deploying a complete self-hosted productivity stack:
1. **Configure once**: `./setup.sh` collects all info
2. **Deploy anywhere**: `make deploy` to any Ubuntu server
3. **Use immediately**: Access via `https://cloud.yourdomain.com`
**Time to deploy:** 30-45 minutes (mostly automatic)
---
## 🎯 Recommended First Steps
### For Complete Beginners
1. Read [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md)
2. Configure DNS for your domain
3. Run `./setup.sh` on your laptop
4. Run `make deploy` and wait
5. Access your new Nextcloud!
### For Experienced Users
1. Skim [README.md](README.md) to understand the stack
2. Review [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) for architecture
3. Run `./setup.sh` and `make deploy`
4. Review generated configs in `/opt/nextcloud-stack/`
### For Developers
1. Read [AGENTS.md](AGENTS.md) for development guidelines
2. Review playbooks in `playbooks/` directory
3. Check templates in `roles/*/templates/`
4. Customize as needed for your use case
---
## 🌟 What Makes This Special?
- **User-friendly**: Interactive setup, no manual config editing
- **Secure**: Ansible Vault, firewalls, Tailscale, automatic updates
- **Complete**: Monitoring, backups, SSL, documentation included
- **Production-ready**: Tested deployment procedures, rollback capability
- **Maintainable**: Clean code, comprehensive docs, modular design
---
## 🚦 Ready to Begin?
**New users**: Start with [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md)
**Quick reference**: See [README.md](README.md)
**Technical details**: Check [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md)
---
**Let's build your self-hosted cloud! 🚀**

509
ansible/TROUBLESHOOTING.md Normal file
View File

@@ -0,0 +1,509 @@
# Troubleshooting Guide
Common issues and solutions for Nextcloud Stack deployment.
## Table of Contents
- [DNS Issues](#dns-issues)
- [SSL Certificate Problems](#ssl-certificate-problems)
- [Docker Issues](#docker-issues)
- [LXC Container Issues](#lxc-container-issues)
- [Nextcloud Issues](#nextcloud-issues)
- [Database Connection Issues](#database-connection-issues)
- [Tailscale Issues](#tailscale-issues)
- [Port Conflicts](#port-conflicts)
- [Permission Issues](#permission-issues)
---
## DNS Issues
### Problem: DNS records not resolving
**Symptoms:**
- Let's Encrypt fails to issue certificates
- Caddy shows certificate errors
- Services inaccessible via domain
**Diagnosis:**
```bash
dig +short cloud.yourdomain.com @8.8.8.8
```
**Solution:**
1. Ensure all required A records point to your server IP
2. Wait for DNS propagation (up to 48 hours, usually minutes)
3. Use [DNSChecker.org](https://dnschecker.org) to verify global propagation
**Required DNS Records:**
```
cloud.yourdomain.com → YOUR_SERVER_IP
office.yourdomain.com → YOUR_SERVER_IP
draw.yourdomain.com → YOUR_SERVER_IP
notes.yourdomain.com → YOUR_SERVER_IP
home.yourdomain.com → YOUR_SERVER_IP
manage.yourdomain.com → YOUR_SERVER_IP
uptime.yourdomain.com → YOUR_SERVER_IP
```
**Temporary Workaround:**
Edit `/etc/hosts` on your local machine:
```
YOUR_SERVER_IP cloud.yourdomain.com
```
---
## SSL Certificate Problems
### Problem: Let's Encrypt rate limit exceeded
**Symptoms:**
- Error: "too many certificates already issued"
**Solution:**
1. Use Let's Encrypt staging server for testing
2. Edit Caddyfile (add to global options):
```caddy
{
email {{ user_email }}
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
```
3. Reload Caddy: `docker exec caddy caddy reload`
4. After testing, remove staging server line
**Rate Limits:**
- 50 certificates per domain per week
- 5 duplicate certificates per week
### Problem: Certificate validation failed
**Symptoms:**
- "Failed to verify" errors in Caddy logs
**Diagnosis:**
```bash
docker logs caddy
```
**Common Causes:**
1. DNS not pointing to server
2. Firewall blocking port 80/443
3. Another service using port 80/443
**Solution:**
```bash
# Check firewall
sudo ufw status
# Check port usage
sudo ss -tlnp | grep ':80\|:443'
# Check DNS
dig +short yourdomain.com
```
---
## Docker Issues
### Problem: Docker daemon won't start
**Symptoms:**
- `docker ps` fails
- Error: "Cannot connect to Docker daemon"
**Diagnosis:**
```bash
sudo systemctl status docker
sudo journalctl -xu docker
```
**Solution:**
```bash
sudo systemctl restart docker
```
### Problem: Containers keep restarting
**Diagnosis:**
```bash
cd /opt/nextcloud-stack
docker compose logs [service-name]
```
**Common Causes:**
1. Configuration errors
2. Port conflicts
3. Missing dependencies
**Solution:**
```bash
# Check specific container
docker logs next-db
docker logs next
docker logs caddy
# Restart specific service
docker compose restart next
```
---
## LXC Container Issues
### Problem: Docker fails to start in LXC
**Symptoms:**
- Error: "cgroups: cgroup mountpoint does not exist"
- Docker daemon fails to start
**Diagnosis:**
```bash
systemd-detect-virt # Should show "lxc"
```
**Solution on LXC Host:**
```bash
# Set security nesting
lxc config set CONTAINER_NAME security.nesting true
# May also need privileged mode
lxc config set CONTAINER_NAME security.privileged true
# Restart container
lxc restart CONTAINER_NAME
```
**Inside LXC Container:**
```bash
# Verify cgroups
mount | grep cgroup
# Check Docker status
sudo systemctl status docker
```
### Problem: AppArmor denials in LXC
**Solution on LXC Host:**
```bash
lxc config set CONTAINER_NAME raw.lxc "lxc.apparmor.profile=unconfined"
lxc restart CONTAINER_NAME
```
---
## Nextcloud Issues
### Problem: Nextcloud stuck in maintenance mode
**Symptoms:**
- Web interface shows "System in maintenance mode"
**Solution:**
```bash
docker exec -u www-data next php occ maintenance:mode --off
```
### Problem: Trusted domain error
**Symptoms:**
- "Access through untrusted domain" error
**Solution:**
```bash
docker exec -u www-data next php occ config:system:set trusted_domains 1 --value=cloud.yourdomain.com
```
### Problem: Redis connection failed
**Diagnosis:**
```bash
docker logs next-redis
docker exec next-redis redis-cli ping
```
**Solution:**
```bash
# Reconfigure Redis in Nextcloud
docker exec -u www-data next php occ config:system:set redis host --value=next-redis
docker exec -u www-data next php occ config:system:set redis port --value=6379
```
### Problem: File uploads fail
**Symptoms:**
- Large files won't upload
- Error 413 (Payload Too Large)
**Solution:**
Already configured in Caddyfile for 10GB uploads. Check:
```bash
docker exec -u www-data next php occ config:system:get max_upload
```
### Problem: OnlyOffice integration not working
**Solution:**
```bash
# Install OnlyOffice app
docker exec -u www-data next php occ app:install onlyoffice
# Configure document server URL
docker exec -u www-data next php occ config:app:set onlyoffice DocumentServerUrl --value="https://office.yourdomain.com/"
# Disable JWT (or configure if needed)
docker exec -u www-data next php occ config:app:set onlyoffice jwt_secret --value=""
```
---
## Database Connection Issues
### Problem: Nextcloud can't connect to database
**Symptoms:**
- Error: "SQLSTATE[08006]"
- Nextcloud shows database error
**Diagnosis:**
```bash
# Check if PostgreSQL is running
docker ps | grep next-db
# Check PostgreSQL logs
docker logs next-db
# Test connection
docker exec next-db pg_isready -U nextcloud
```
**Solution:**
```bash
# Restart database
docker compose restart next-db
# Wait for it to be healthy
docker exec next-db pg_isready -U nextcloud
# Restart Nextcloud
docker compose restart next
```
### Problem: Database initialization failed
**Symptoms:**
- PostgreSQL container keeps restarting
- Empty database
**Solution:**
```bash
# Remove volumes and recreate
cd /opt/nextcloud-stack
docker compose down -v
docker compose up -d
```
**⚠️ WARNING:** This deletes all data! Only use for fresh installations.
---
## Tailscale Issues
### Problem: Can't access Tailscale-only services
**Symptoms:**
- Homarr, Dockhand, Uptime Kuma return 403 Forbidden
**Diagnosis:**
```bash
# Check if Tailscale is running
sudo tailscale status
# Get Tailscale IP
tailscale ip -4
```
**Solution:**
```bash
# Activate Tailscale (if not done)
sudo tailscale up
# Verify connection
tailscale status
```
**Access via:**
- Tailscale IP: `https://100.64.x.x:PORT`
- MagicDNS: `https://hostname.tailnet-name.ts.net`
### Problem: Tailscale not installed
**Solution:**
```bash
# Re-run Tailscale playbook
ansible-playbook playbooks/04-tailscale-setup.yml --ask-vault-pass
```
---
## Port Conflicts
### Problem: Port 80 or 443 already in use
**Symptoms:**
- Error: "bind: address already in use"
- Caddy won't start
**Diagnosis:**
```bash
sudo ss -tlnp | grep ':80\|:443'
```
**Common Culprits:**
- Apache2
- Nginx
- Another Caddy instance
**Solution:**
```bash
# Stop conflicting service
sudo systemctl stop apache2
sudo systemctl disable apache2
# OR
sudo systemctl stop nginx
sudo systemctl disable nginx
# Restart Caddy
docker compose restart caddy
```
---
## Permission Issues
### Problem: Permission denied errors in Nextcloud
**Symptoms:**
- Can't upload files
- Can't install apps
**Diagnosis:**
```bash
# Check file permissions
docker exec next ls -la /var/www/html
```
**Solution:**
```bash
# Fix permissions (run inside container)
docker exec next chown -R www-data:www-data /var/www/html
```
### Problem: Docker socket permission denied
**Symptoms:**
- Homarr or Dockhand can't see containers
**Solution:**
Docker socket is mounted read-only by design for security.
This is normal and expected.
---
## Emergency Commands
### Completely restart the stack
```bash
cd /opt/nextcloud-stack
docker compose down
docker compose up -d
```
### View all logs in real-time
```bash
cd /opt/nextcloud-stack
docker compose logs -f
```
### Check container health
```bash
docker compose ps
docker inspect --format='{{.State.Health.Status}}' next
```
### Rebuild a specific container
```bash
docker compose up -d --force-recreate --no-deps next
```
### Emergency backup
```bash
/opt/nextcloud-stack/backup.sh
```
### Reset Nextcloud admin password
```bash
docker exec -u www-data next php occ user:resetpassword admin
```
---
## Getting Help
If none of these solutions work:
1. **Check logs:**
```bash
docker compose logs [service-name]
```
2. **Check system logs:**
```bash
sudo journalctl -xe
```
3. **Verify configuration:**
```bash
cat /opt/nextcloud-stack/docker-compose.yml
cat /opt/nextcloud-stack/.env
```
4. **Test connectivity:**
```bash
curl -I https://cloud.yourdomain.com
docker exec caddy caddy validate
```
5. **Deployment report:**
```bash
cat /opt/nextcloud-stack/DEPLOYMENT.txt
```
---
## Recovery Procedures
### Restore from backup
See [BACKUP_RESTORE.md](BACKUP_RESTORE.md)
### Complete reinstallation
```bash
# 1. Backup first!
/opt/nextcloud-stack/backup.sh
# 2. Remove deployment
ansible-playbook playbooks/99-rollback.yml --ask-vault-pass
# 3. Redeploy
ansible-playbook playbooks/site.yml --ask-vault-pass
```
---
**Last Updated:** 2026-02-16

35
ansible/ansible.cfg Normal file
View File

@@ -0,0 +1,35 @@
# Ansible Configuration File
# Generated for Nextcloud Stack Deployment
[defaults]
inventory = inventory/hosts.yml
roles_path = roles
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600
stdout_callback = yaml
callbacks_enabled = profile_tasks, timer
deprecation_warnings = False
# SSH settings
timeout = 30
forks = 5
# Privilege escalation
become = True
become_method = sudo
become_ask_pass = False
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

7
ansible/nextcloud/.env Executable file
View File

@@ -0,0 +1,7 @@
DB_PASSWORD=
DB_USERNAME=
DB_DATABASE_NAME=
DB_HOST=
PUID=33
PGID=1000

View File

@@ -0,0 +1,108 @@
services:
excalidraw:
container_name: excalidraw
image: docker.io/excalidraw/excalidraw:latest
restart: unless-stopped
ports:
- "3009:80"
environment:
- NODE_ENV=production
- TZ=Europe/Zurich
# Database (PostgreSQL)
next-db:
image: docker.io/postgres:18
container_name: next-db
environment:
- POSTGRES_DB=${DB_DATABASE_NAME}
- POSTGRES_USER=${DB_USERNAME}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
-
restart: unless-stopped
networks:
- nextcloud_network
# # Redis Cache
# next_redis:
# image: docker.io/redis:latest
# container_name: next-redis
# command: redis-server --save 60 1 --loglevel warning
# volumes:
# - ./data/redis:/data
# restart: unless-stopped
# networks:
# - nextcloud_network
# Nextcloud Main Application
next:
image: docker.io/nextcloud:latest
container_name: next
depends_on:
- next-db
mem_swappiness: -1
ports:
- "8808:80"
environment:
- POSTGRES_DB=${DB_DATABASE_NAME}
- POSTGRES_USER=${DB_USERNAME}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_HOST=${DB_HOST}
- NEXTCLOUD_TRUSTED_DOMAINS=next.liphlink.xyz
- NEXTCLOUD_ADMIN_USER=liph
- NEXTCLOUD_ADMIN_PASSWORD=1ChagenexT
volumes:
- :/var/www/html
- :/var/www/html/config:Z
- :/var/www/html/custom_apps
restart: unless-stopped
networks:
- nextcloud_network
# OnlyOffice (alternative to Collabora)
onlyoffice:
image: docker.io/onlyoffice/documentserver:latest
container_name: onlyoffice
ports:
- 8000:80
environment:
# - JWT_SECRET='S2jaRmOxSxnFoOMhzXotL1QTptDuqhom'
- JWT_ENABLED=false
- JWT_HEADER=Authorization
- JWT_IN_BODY=true
volumes:
- :/var/www/onlyoffice/Data
restart: unless-stopped
networks:
- nextcloud_network
obsidian:
image: lscr.io/linuxserver/obsidian:latest
container_name: obsidian
security_opt:
- seccomp:unconfined #optional
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
volumes:
- :/config:z
- :/vault:z
ports:
- 3005:3000
- 3004:3001
shm_size: "1gb"
restart: unless-stopped
volumes:
pg_data:
redis_data:
nextcloud_data:
nextcloud_config:
nextcloud_apps:
nextcloud_db_data:
onlyoffice_data:
networks:
nextcloud_network:
name: nextcloud_network

View File

@@ -0,0 +1,98 @@
---
# Playbook 01: Preflight Checks
# Validates environment before deployment
- name: Preflight Checks
hosts: all
gather_facts: yes
become: no
tasks:
- name: Check Ansible version
assert:
that:
- ansible_version.full is version('2.14', '>=')
fail_msg: "Ansible 2.14 or higher is required"
success_msg: "Ansible version OK ({{ ansible_version.full }})"
delegate_to: localhost
run_once: true
- name: Test SSH connectivity
ping:
- name: Check sudo access
command: sudo -n true
changed_when: false
- name: Check Python3 availability
command: python3 --version
register: python_version
changed_when: false
- name: Display Python version
debug:
msg: "Python version: {{ python_version.stdout }}"
- name: Check disk space
shell: df -h / | awk 'NR==2 {print $4}'
register: disk_space
changed_when: false
- name: Validate sufficient disk space
assert:
that:
- disk_space.stdout is regex('[0-9]+G')
fail_msg: "Insufficient disk space. At least 20GB recommended."
success_msg: "Disk space OK ({{ disk_space.stdout }} available)"
- name: Check if ports 80 and 443 are available
wait_for:
port: "{{ item }}"
state: stopped
timeout: 1
loop:
- 80
- 443
ignore_errors: yes
register: port_check
- name: Detect virtualization type
command: systemd-detect-virt
register: virt_type
changed_when: false
failed_when: false
- name: Warn if running in LXC
debug:
msg: |
⚠️ RUNNING IN LXC CONTAINER
Docker requires nested virtualization.
Ensure on LXC host: lxc config set {{ inventory_hostname }} security.nesting true
when: "'lxc' in virt_type.stdout"
- name: Validate DNS resolution for all subdomains
command: dig +short {{ item }}.{{ domain }} @8.8.8.8
register: dns_check
changed_when: false
failed_when: false
loop:
- "{{ subdomain_nextcloud }}"
- "{{ subdomain_office }}"
- "{{ subdomain_draw }}"
- "{{ subdomain_notes }}"
- "{{ subdomain_homarr }}"
- "{{ subdomain_dockhand }}"
- "{{ subdomain_uptime }}"
- name: Display DNS check results
debug:
msg: "{{ item.item }}.{{ domain }} → {{ item.stdout if item.stdout else 'NOT CONFIGURED' }}"
loop: "{{ dns_check.results }}"
loop_control:
label: "{{ item.item }}.{{ domain }}"
- name: Preflight checks complete
debug:
msg: |
✓ All preflight checks passed
✓ Ready to proceed with deployment

View File

@@ -0,0 +1,119 @@
---
# Playbook 02: System Setup
# Install packages, configure firewall and security
- name: System Setup
hosts: all
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Upgrade all packages
apt:
upgrade: dist
autoremove: yes
autoclean: yes
- name: Install essential packages
apt:
name:
- curl
- wget
- git
- vim
- htop
- net-tools
- dnsutils
- ufw
- fail2ban
- unattended-upgrades
- apt-transport-https
- ca-certificates
- gnupg
- lsb-release
- software-properties-common
- python3-pip
- python3-docker
- rsync
- "{% if install_rclone %}rclone{% endif %}"
state: present
- name: Configure UFW - Allow SSH
ufw:
rule: allow
port: '22'
proto: tcp
- name: Configure UFW - Allow HTTP
ufw:
rule: allow
port: '80'
proto: tcp
- name: Configure UFW - Allow HTTPS TCP
ufw:
rule: allow
port: '443'
proto: tcp
- name: Configure UFW - Allow HTTPS UDP (HTTP/3)
ufw:
rule: allow
port: '443'
proto: udp
- name: Configure UFW - Allow Tailscale
ufw:
rule: allow
port: '41641'
proto: udp
- name: Enable UFW
ufw:
state: enabled
policy: deny
- name: Configure fail2ban for SSH
copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 600
notify: restart fail2ban
- name: Enable unattended security updates
copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
- name: Set timezone
timezone:
name: "{{ timezone }}"
- name: Set kernel parameters for Docker
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: yes
loop:
- { key: 'net.ipv4.ip_forward', value: '1' }
- { key: 'fs.inotify.max_user_watches', value: '524288' }
handlers:
- name: restart fail2ban
service:
name: fail2ban
state: restarted

View File

@@ -0,0 +1,115 @@
---
# Playbook 03: Docker Setup
# Install Docker CE and Docker Compose v2
- name: Docker Installation
hosts: all
become: yes
tasks:
- name: Remove old Docker installations
apt:
name:
- docker
- docker-engine
- docker.io
- containerd
- runc
- docker-compose
state: absent
purge: yes
- name: Remove old Docker data directories
file:
path: "{{ item }}"
state: absent
loop:
- /var/lib/docker
- /var/lib/containerd
- /etc/docker
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Install Docker CE
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: yes
- name: Create Docker daemon configuration
copy:
dest: /etc/docker/daemon.json
content: |
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"userland-proxy": false,
"live-restore": true
}
notify: restart docker
- name: Create docker group
group:
name: docker
state: present
- name: Add user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
- name: Create deployment directory
file:
path: "{{ deployment_dir }}"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
- name: Start and enable Docker
service:
name: docker
state: started
enabled: yes
- name: Verify Docker installation
command: docker --version
register: docker_version
changed_when: false
- name: Display Docker version
debug:
msg: "Docker installed: {{ docker_version.stdout }}"
- name: Verify Docker Compose installation
command: docker compose version
register: compose_version
changed_when: false
- name: Display Docker Compose version
debug:
msg: "Docker Compose installed: {{ compose_version.stdout }}"
handlers:
- name: restart docker
service:
name: docker
state: restarted

View File

@@ -0,0 +1,60 @@
---
# Playbook 04: Tailscale Setup
# Install and optionally activate Tailscale VPN
- name: Tailscale Installation
hosts: all
become: yes
tasks:
- name: Add Tailscale GPG key
apt_key:
url: https://pkgs.tailscale.com/stable/ubuntu/{{ ansible_distribution_release }}.noarmor.gpg
state: present
- name: Add Tailscale repository
apt_repository:
repo: "deb https://pkgs.tailscale.com/stable/ubuntu {{ ansible_distribution_release }} main"
state: present
- name: Install Tailscale
apt:
name: tailscale
state: present
update_cache: yes
- name: Check if Tailscale auth key is provided
set_fact:
tailscale_auto_enable: "{{ tailscale_auth_key is defined and tailscale_auth_key != '' }}"
- name: Activate Tailscale (if auth key provided)
command: tailscale up --authkey={{ tailscale_auth_key }} --advertise-tags=tag:nextcloud
when: tailscale_auto_enable
register: tailscale_activation
- name: Get Tailscale IP (if activated)
command: tailscale ip -4
register: tailscale_ip
when: tailscale_auto_enable
changed_when: false
- name: Display Tailscale status (activated)
debug:
msg: |
✓ Tailscale activated
IP: {{ tailscale_ip.stdout }}
when: tailscale_auto_enable
- name: Display manual activation instructions (not activated)
debug:
msg: |
Tailscale installed but not activated.
To enable, run on the server:
sudo tailscale up
when: not tailscale_auto_enable
- name: Enable Tailscale service
service:
name: tailscaled
state: started
enabled: yes

View File

@@ -0,0 +1,109 @@
---
# Playbook 05: Deploy Stack
# Deploy Docker Compose stack with all services
- name: Deploy Nextcloud Stack
hosts: all
become: yes
tasks:
- name: Create directory structure
file:
path: "{{ item }}"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
loop:
- "{{ deployment_dir }}/configs"
- "{{ deployment_dir }}/configs/caddy"
- "{{ deployment_dir }}/configs/nextcloud"
- "{{ deployment_dir }}/data"
- "{{ deployment_dir }}/data/homarr"
- "{{ deployment_dir }}/data/obsidian/config"
- "{{ deployment_dir }}/data/obsidian/vault"
- "{{ deployment_dir }}/backups"
- "{{ deployment_dir }}/backups/database"
- "{{ deployment_dir }}/backups/volumes"
- name: Template docker-compose.yml
template:
src: ../roles/nextcloud_stack/templates/docker-compose.yml.j2
dest: "{{ deployment_dir }}/docker-compose.yml"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
- name: Template .env file
template:
src: ../roles/nextcloud_stack/templates/env.j2
dest: "{{ deployment_dir }}/.env"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0600'
no_log: true
- name: Template Caddyfile
template:
src: ../roles/caddy/templates/Caddyfile.j2
dest: "{{ deployment_dir }}/configs/caddy/Caddyfile"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
- name: Pull Docker images
command: docker compose pull
args:
chdir: "{{ deployment_dir }}"
become_user: "{{ ansible_user }}"
- name: Start Docker Compose stack
command: docker compose up -d
args:
chdir: "{{ deployment_dir }}"
become_user: "{{ ansible_user }}"
- name: Wait for PostgreSQL to be healthy
command: docker exec next-db pg_isready -U {{ db_user }}
register: pg_ready
until: pg_ready.rc == 0
retries: 30
delay: 10
changed_when: false
- name: Wait for Nextcloud to be accessible
uri:
url: http://localhost:8808/status.php
status_code: 200
register: nc_status
until: nc_status.status == 200
retries: 30
delay: 10
- name: Configure Nextcloud Redis cache
command: |
docker exec -u www-data next php occ config:system:set redis host --value=next-redis
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Configure Nextcloud Redis port
command: |
docker exec -u www-data next php occ config:system:set redis port --value=6379
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Configure Nextcloud memcache
command: |
docker exec -u www-data next php occ config:system:set memcache.locking --value='\\OC\\Memcache\\Redis'
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Display deployment success
debug:
msg: |
✓ Docker Compose stack deployed
✓ All containers started
✓ Nextcloud is accessible

View File

@@ -0,0 +1,48 @@
---
# Playbook 06: Configure Caddy
# Setup reverse proxy and obtain SSL certificates
- name: Configure Caddy Reverse Proxy
hosts: all
become: yes
tasks:
- name: Validate Caddyfile syntax
command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
args:
chdir: "{{ deployment_dir }}"
register: caddy_validate
failed_when: caddy_validate.rc != 0
changed_when: false
- name: Reload Caddy configuration
command: docker exec caddy caddy reload --config /etc/caddy/Caddyfile
args:
chdir: "{{ deployment_dir }}"
- name: Wait for SSL certificates (may take 1-2 minutes)
pause:
seconds: 30
prompt: "Waiting for Let's Encrypt to issue certificates..."
- name: Test HTTPS endpoint for Nextcloud
uri:
url: "https://{{ subdomain_nextcloud }}.{{ domain }}/status.php"
validate_certs: yes
status_code: 200
register: https_test
until: https_test.status == 200
retries: 10
delay: 10
ignore_errors: yes
- name: Display Caddy status
debug:
msg: |
✓ Caddyfile validated
✓ Caddy reloaded
{% if https_test.status == 200 %}
✓ HTTPS working: https://{{ subdomain_nextcloud }}.{{ domain }}
{% else %}
⚠ HTTPS check failed - verify DNS and firewall
{% endif %}

View File

@@ -0,0 +1,87 @@
---
# Playbook 07: Setup Backups
# Configure automated backup system
- name: Setup Backup System
hosts: all
become: yes
tasks:
- name: Create backup script
copy:
dest: "{{ deployment_dir }}/backup.sh"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
content: |
#!/bin/bash
# Nextcloud Stack Backup Script
set -euo pipefail
BACKUP_DIR="{{ deployment_dir }}/backups"
DB_BACKUP_DIR="$BACKUP_DIR/database"
VOLUME_BACKUP_DIR="$BACKUP_DIR/volumes"
RETENTION_DAYS={{ backup_retention_days }}
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="$BACKUP_DIR/backup.log"
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting backup process..."
# Database backup
log "Backing up PostgreSQL database..."
docker exec next-db pg_dump -U {{ db_user }} {{ db_name }} | \
gzip > "$DB_BACKUP_DIR/nextcloud_db_$DATE.sql.gz"
if [ $? -eq 0 ]; then
log "Database backup completed: nextcloud_db_$DATE.sql.gz"
else
log "ERROR: Database backup failed!"
exit 1
fi
# Weekly volume backup (Sundays only)
if [ $(date +%u) -eq 7 ]; then
log "Weekly volume backup (Sunday)..."
docker exec -u www-data next php occ maintenance:mode --on
tar -czf "$VOLUME_BACKUP_DIR/nextcloud_data_$DATE.tar.gz" \
-C /var/lib/docker/volumes/nextcloud_nextcloud_data/_data . 2>/dev/null || true
tar -czf "$VOLUME_BACKUP_DIR/configs_$DATE.tar.gz" \
-C {{ deployment_dir }}/configs . 2>/dev/null || true
docker exec -u www-data next php occ maintenance:mode --off
log "Volume backup completed"
fi
# Cleanup old backups
log "Cleaning up backups older than $RETENTION_DAYS days..."
find "$DB_BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
find "$VOLUME_BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "Backup process completed successfully!"
- name: Create cron job for daily backups
cron:
name: "Nextcloud Stack Backup"
minute: "0"
hour: "3"
job: "{{ deployment_dir }}/backup.sh >> {{ deployment_dir }}/backups/backup.log 2>&1"
user: root
- name: Run initial test backup
command: "{{ deployment_dir }}/backup.sh"
register: backup_test
ignore_errors: yes
- name: Display backup status
debug:
msg: |
✓ Backup script created
✓ Cron job configured (daily 3:00 AM)
✓ Test backup completed
Location: {{ deployment_dir }}/backups/

View File

@@ -0,0 +1,154 @@
---
# Playbook 08: Post-Deployment
# Final verification and configuration
- name: Post-Deployment Tasks
hosts: all
become: yes
tasks:
- name: Verify all containers are running
command: docker compose ps --format json
args:
chdir: "{{ deployment_dir }}"
register: container_status
changed_when: false
- name: Check Nextcloud status
command: docker exec -u www-data next php occ status
args:
chdir: "{{ deployment_dir }}"
register: nc_status
changed_when: false
ignore_errors: yes
- name: Install recommended Nextcloud apps
command: docker exec -u www-data next php occ app:install {{ item }}
args:
chdir: "{{ deployment_dir }}"
loop:
- calendar
- contacts
- tasks
- notes
ignore_errors: yes
- name: Configure Nextcloud background jobs
command: docker exec -u www-data next php occ background:cron
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Setup Nextcloud cron job
cron:
name: "Nextcloud Background Jobs"
minute: "*/5"
job: "docker exec -u www-data next php /var/www/html/cron.php"
user: root
- name: Get Tailscale IP (if activated)
command: tailscale ip -4
register: tailscale_ip_result
changed_when: false
failed_when: false
- name: Create deployment report
copy:
dest: "{{ deployment_dir }}/DEPLOYMENT.txt"
content: |
════════════════════════════════════════════════════════════
NEXTCLOUD STACK DEPLOYMENT REPORT
════════════════════════════════════════════════════════════
Server: {{ inventory_hostname }}
IP Address: {{ ansible_host }}
Domain: {{ domain }}
Deployment Date: {{ ansible_date_time.iso8601 }}
────────────────────────────────────────────────────────────
PUBLIC SERVICES (HTTPS)
────────────────────────────────────────────────────────────
• Nextcloud: https://{{ subdomain_nextcloud }}.{{ domain }}
• OnlyOffice: https://{{ subdomain_office }}.{{ domain }}
• Excalidraw: https://{{ subdomain_draw }}.{{ domain }}
• Obsidian: https://{{ subdomain_notes }}.{{ domain }}
────────────────────────────────────────────────────────────
MANAGEMENT SERVICES (Tailscale only)
────────────────────────────────────────────────────────────
• Homarr: https://{{ subdomain_homarr }}.{{ domain }}
• Dockhand: https://{{ subdomain_dockhand }}.{{ domain }}
• Uptime Kuma: https://{{ subdomain_uptime }}.{{ domain }}
────────────────────────────────────────────────────────────
CREDENTIALS
────────────────────────────────────────────────────────────
Nextcloud Admin User: {{ admin_user }}
Nextcloud Admin Password: [stored in Ansible vault]
────────────────────────────────────────────────────────────
TAILSCALE
────────────────────────────────────────────────────────────
{% if tailscale_ip_result.rc == 0 %}
Status: Connected
Tailscale IP: {{ tailscale_ip_result.stdout }}
{% else %}
Status: Not activated
Activate with: sudo tailscale up
{% endif %}
────────────────────────────────────────────────────────────
BACKUPS
────────────────────────────────────────────────────────────
Schedule: Daily at 3:00 AM
Location: {{ deployment_dir }}/backups/
Retention: {{ backup_retention_days }} days
────────────────────────────────────────────────────────────
USEFUL COMMANDS
────────────────────────────────────────────────────────────
View containers: cd {{ deployment_dir }} && docker compose ps
View logs: cd {{ deployment_dir }} && docker compose logs -f
Restart service: cd {{ deployment_dir }} && docker compose restart [service]
Manual backup: {{ deployment_dir }}/backup.sh
Nextcloud CLI: docker exec -u www-data next php occ [command]
════════════════════════════════════════════════════════════
- name: Display deployment summary
debug:
msg: |
╔════════════════════════════════════════════════════════════╗
║ DEPLOYMENT COMPLETED SUCCESSFULLY! ║
╚════════════════════════════════════════════════════════════╝
Server: {{ inventory_hostname }} ({{ ansible_host }})
Domain: {{ domain }}
📦 Public Services:
• Nextcloud: https://{{ subdomain_nextcloud }}.{{ domain }}
• OnlyOffice: https://{{ subdomain_office }}.{{ domain }}
• Excalidraw: https://{{ subdomain_draw }}.{{ domain }}
• Obsidian: https://{{ subdomain_notes }}.{{ domain }}
🔒 Management (Tailscale only):
• Homarr: https://{{ subdomain_homarr }}.{{ domain }}
• Dockhand: https://{{ subdomain_dockhand }}.{{ domain }}
• Uptime Kuma: https://{{ subdomain_uptime }}.{{ domain }}
👤 Nextcloud Admin:
Username: {{ admin_user }}
Password: [check vault]
💾 Backups:
Daily at 3:00 AM
Location: {{ deployment_dir }}/backups/
📝 Next Steps:
1. Login to Nextcloud and complete setup
2. Setup Uptime Kuma monitoring (via Tailscale)
3. Configure Homarr dashboard (via Tailscale)
4. Review deployment report: {{ deployment_dir }}/DEPLOYMENT.txt
Deployment report saved to:
{{ deployment_dir }}/DEPLOYMENT.txt

View File

@@ -0,0 +1,41 @@
---
# Playbook 99: Emergency Rollback
# Stop and remove the deployment (keeps backups)
- name: Emergency Rollback
hosts: all
become: yes
tasks:
- name: Confirm rollback
pause:
prompt: |
⚠️ WARNING: This will stop and remove all containers!
Backups in {{ deployment_dir }}/backups/ will be preserved.
Press ENTER to continue or Ctrl+C to cancel
- name: Stop Docker Compose stack
command: docker compose down
args:
chdir: "{{ deployment_dir }}"
become_user: "{{ ansible_user }}"
ignore_errors: yes
- name: Remove deployment directory (except backups)
shell: |
cd {{ deployment_dir }}
rm -rf configs data docker-compose.yml .env
ls -la
args:
warn: false
- name: Display rollback status
debug:
msg: |
✓ Containers stopped and removed
✓ Deployment files removed
✓ Backups preserved in {{ deployment_dir }}/backups/
✓ Docker volumes preserved (use 'docker volume ls' to see)
To completely remove volumes (DELETES ALL DATA):
cd {{ deployment_dir }} && docker compose down -v

View File

@@ -0,0 +1,26 @@
---
# Main Playbook - Nextcloud Stack Deployment
# This orchestrates the complete deployment process
- name: Nextcloud Stack - Complete Deployment
hosts: all
gather_facts: yes
pre_tasks:
- name: Display deployment banner
debug:
msg: |
╔════════════════════════════════════════════════════════════╗
║ Nextcloud Stack Deployment Starting ║
║ Target: {{ inventory_hostname }} ({{ ansible_host }}) ║
║ Domain: {{ domain }} ║
╚════════════════════════════════════════════════════════════╝
- import_playbook: 01-preflight-checks.yml
- import_playbook: 02-system-setup.yml
- import_playbook: 03-docker-setup.yml
- import_playbook: 04-tailscale-setup.yml
- import_playbook: 05-deploy-stack.yml
- import_playbook: 06-configure-caddy.yml
- import_playbook: 07-setup-backups.yml
- import_playbook: 08-post-deployment.yml

View File

@@ -0,0 +1,128 @@
# Caddyfile - Generated by Ansible
# Domain: {{ domain }}
# Global options
{
email {{ user_email }}
}
# ===== PUBLIC SERVICES =====
# Nextcloud
{{ subdomain_nextcloud }}.{{ domain }} {
reverse_proxy next:80
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy no-referrer
X-XSS-Protection "1; mode=block"
-Server
}
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
redir /.well-known/webfinger /index.php/.well-known/webfinger 301
redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 301
request_body {
max_size 10GB
}
}
# OnlyOffice Document Server
{{ subdomain_office }}.{{ domain }} {
reverse_proxy onlyoffice:80
request_body {
max_size 100MB
}
header {
Strict-Transport-Security "max-age=31536000"
-Server
}
}
# Excalidraw
{{ subdomain_draw }}.{{ domain }} {
reverse_proxy excalidraw:80
header {
Strict-Transport-Security "max-age=31536000"
-Server
}
}
# Obsidian
{{ subdomain_notes }}.{{ domain }} {
reverse_proxy obsidian:3000
header {
Strict-Transport-Security "max-age=31536000"
-Server
}
}
# ===== TAILSCALE-ONLY SERVICES =====
# Homarr Dashboard
{{ subdomain_homarr }}.{{ domain }} {
@tailscale {
remote_ip 100.64.0.0/10
}
handle @tailscale {
reverse_proxy homarr:7575
}
handle {
respond "Access Denied - Tailscale Required" 403
abort
}
}
# Dockhand Container Manager
{{ subdomain_dockhand }}.{{ domain }} {
@tailscale {
remote_ip 100.64.0.0/10
}
handle @tailscale {
reverse_proxy dockhand:3000
}
handle {
respond "Access Denied - Tailscale Required" 403
abort
}
}
# Uptime Kuma Monitoring
{{ subdomain_uptime }}.{{ domain }} {
@tailscale {
remote_ip 100.64.0.0/10
}
handle @tailscale {
reverse_proxy uptime-kuma:3001
}
handle {
respond "Access Denied - Tailscale Required" 403
abort
}
}
{% if enable_public_status %}
# Public Status Page
status.{{ domain }} {
reverse_proxy uptime-kuma:3001/status
header {
Strict-Transport-Security "max-age=31536000"
-Server
}
}
{% endif %}

View File

@@ -0,0 +1,229 @@
services:
# ===== DATABASE LAYER =====
next-db:
image: docker.io/postgres:18
container_name: next-db
restart: unless-stopped
environment:
- POSTGRES_DB={{ db_name }}
- POSTGRES_USER={{ db_user }}
- POSTGRES_PASSWORD={{ db_password }}
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=false"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{ db_user }}"]
interval: 10s
timeout: 5s
retries: 5
next-redis:
image: docker.io/redis:7-alpine
container_name: next-redis
restart: unless-stopped
command: redis-server --requirepass {{ redis_password }} --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=false"
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
# ===== APPLICATION LAYER =====
next:
image: docker.io/nextcloud:latest
container_name: next
restart: unless-stopped
depends_on:
next-db:
condition: service_healthy
next-redis:
condition: service_healthy
mem_swappiness: -1
environment:
- POSTGRES_DB={{ db_name }}
- POSTGRES_USER={{ db_user }}
- POSTGRES_PASSWORD={{ db_password }}
- POSTGRES_HOST=next-db
- REDIS_HOST=next-redis
- REDIS_HOST_PASSWORD={{ redis_password }}
- NEXTCLOUD_TRUSTED_DOMAINS={{ subdomain_nextcloud }}.{{ domain }}
- NEXTCLOUD_ADMIN_USER={{ admin_user }}
- NEXTCLOUD_ADMIN_PASSWORD={{ admin_password }}
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST={{ subdomain_nextcloud }}.{{ domain }}
- TRUSTED_PROXIES=caddy
- PHP_MEMORY_LIMIT=512M
- PHP_UPLOAD_LIMIT=10G
volumes:
- nextcloud_data:/var/www/html
- {{ deployment_dir }}/configs/nextcloud:/var/www/html/config:Z
- nextcloud_apps:/var/www/html/custom_apps
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=false"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/status.php"]
interval: 30s
timeout: 10s
retries: 3
onlyoffice:
image: docker.io/onlyoffice/documentserver:latest
container_name: onlyoffice
restart: unless-stopped
environment:
- JWT_ENABLED=false
- JWT_HEADER=Authorization
- JWT_IN_BODY=true
volumes:
- onlyoffice_data:/var/www/onlyoffice/Data
- onlyoffice_logs:/var/log/onlyoffice
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.monitor-only=true"
excalidraw:
image: docker.io/excalidraw/excalidraw:latest
container_name: excalidraw
restart: unless-stopped
environment:
- NODE_ENV=production
- TZ={{ timezone }}
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=true"
obsidian:
image: lscr.io/linuxserver/obsidian:latest
container_name: obsidian
restart: unless-stopped
security_opt:
- seccomp:unconfined
environment:
- PUID=1000
- PGID=1000
- TZ={{ timezone }}
volumes:
- {{ deployment_dir }}/data/obsidian/config:/config:z
- {{ deployment_dir }}/data/obsidian/vault:/vault:z
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=true"
shm_size: "1gb"
# ===== INFRASTRUCTURE LAYER =====
caddy:
image: docker.io/caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- {{ deployment_dir }}/configs/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
environment:
- DOMAIN={{ domain }}
- EMAIL={{ user_email }}
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=true"
homarr:
image: ghcr.io/homarr-labs/homarr:latest
container_name: homarr
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- {{ deployment_dir }}/data/homarr:/appdata
environment:
- SECRET_ENCRYPTION_KEY={{ homarr_secret }}
- TZ={{ timezone }}
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=true"
dockhand:
image: fnsys/dockhand:latest
container_name: dockhand
restart: unless-stopped
ports:
- "3003:3000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- dockhand_data:/app/data
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=true"
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
volumes:
- uptime_kuma_data:/app/data
environment:
- TZ={{ timezone }}
networks:
- nextcloud_network
labels:
- "com.centurylinklabs.watchtower.enable=true"
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_POLL_INTERVAL=86400
- WATCHTOWER_ROLLING_RESTART=true
- WATCHTOWER_INCLUDE_RESTARTING=true
- TZ={{ timezone }}
networks:
- nextcloud_network
# ===== PERSISTENT STORAGE =====
volumes:
pg_data:
redis_data:
nextcloud_data:
nextcloud_apps:
onlyoffice_data:
onlyoffice_logs:
caddy_data:
caddy_config:
dockhand_data:
uptime_kuma_data:
# ===== NETWORKING =====
networks:
nextcloud_network:
name: nextcloud_network
driver: bridge

View File

@@ -0,0 +1,31 @@
# Environment Variables for Nextcloud Stack
# Generated by Ansible - DO NOT EDIT MANUALLY
# Database Configuration
DB_NAME={{ db_name }}
DB_USER={{ db_user }}
DB_PASSWORD={{ db_password }}
DB_HOST=next-db
# Redis Configuration
REDIS_PASSWORD={{ redis_password }}
# Nextcloud Admin
NEXTCLOUD_ADMIN_USER={{ admin_user }}
NEXTCLOUD_ADMIN_PASSWORD={{ admin_password }}
# Application Secrets
HOMARR_SECRET_KEY={{ homarr_secret }}
# Domain Configuration
DOMAIN={{ domain }}
SUBDOMAIN_NEXTCLOUD={{ subdomain_nextcloud }}
SUBDOMAIN_OFFICE={{ subdomain_office }}
# User Configuration
USER_EMAIL={{ user_email }}
TIMEZONE={{ timezone }}
# UIDs/GIDs
PUID=1000
PGID=1000

463
ansible/setup.sh Executable file
View File

@@ -0,0 +1,463 @@
#!/bin/bash
#
# Nextcloud Stack - Interactive Setup Script
# This script collects all configuration variables and generates Ansible inventory
#
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Header
clear
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Nextcloud Stack - Ansible Deployment Setup ║${NC}"
echo -e "${CYAN}║ Version 1.0 ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "This script will guide you through configuring your Nextcloud"
echo "deployment. Information will be encrypted using Ansible Vault."
echo ""
# Arrays to store server data
declare -a SERVER_IPS
declare -a SERVER_HOSTNAMES
declare -a SERVER_DOMAINS
declare -a SERVER_SSH_USERS
declare -a SERVER_SSH_KEYS
# Global variables
ADMIN_USER=""
ADMIN_PASSWORD=""
USER_NAME=""
USER_EMAIL=""
TIMEZONE="Europe/Zurich"
INSTALL_RCLONE="y"
ENABLE_PUBLIC_STATUS="n"
ALERT_EMAIL=""
TAILSCALE_AUTH_KEY=""
# Subdomain defaults
SUBDOMAIN_NEXTCLOUD="cloud"
SUBDOMAIN_OFFICE="office"
SUBDOMAIN_DRAW="draw"
SUBDOMAIN_NOTES="notes"
SUBDOMAIN_HOMARR="home"
SUBDOMAIN_DOCKHAND="manage"
SUBDOMAIN_UPTIME="uptime"
# Database defaults
DB_NAME="nextcloud"
DB_USER="nextcloud"
# Generate random password
generate_password() {
openssl rand -base64 32 | tr -d "=+/" | cut -c1-32
}
# Validate IP address
validate_ip() {
local ip=$1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
return 0
else
return 1
fi
}
# Validate domain
validate_domain() {
local domain=$1
if [[ $domain =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$ ]]; then
return 0
else
return 1
fi
}
# Server Configuration
echo -e "${GREEN}=== Server Configuration ===${NC}"
echo "Enter server details (press Enter without input to finish):"
echo ""
server_count=0
while true; do
((server_count++))
echo -e "${BLUE}Server $server_count:${NC}"
# IP Address
while true; do
read -p " IP address: " ip
if [[ -z "$ip" ]]; then
((server_count--))
break 2
fi
if validate_ip "$ip"; then
SERVER_IPS+=("$ip")
break
else
echo -e "${RED} Invalid IP address. Please try again.${NC}"
fi
done
# Hostname
read -p " Hostname [cloud$(printf "%02d" $server_count)]: " hostname
hostname=${hostname:-cloud$(printf "%02d" $server_count)}
SERVER_HOSTNAMES+=("$hostname")
# Domain
while true; do
read -p " Domain (e.g., example.com): " domain
if [[ -z "$domain" ]]; then
echo -e "${RED} Domain is required. Please try again.${NC}"
elif validate_domain "$domain"; then
SERVER_DOMAINS+=("$domain")
break
else
echo -e "${RED} Invalid domain format. Please try again.${NC}"
fi
done
# SSH User
read -p " SSH user [root]: " ssh_user
ssh_user=${ssh_user:-root}
SERVER_SSH_USERS+=("$ssh_user")
# SSH Key
read -p " SSH key path [~/.ssh/id_rsa]: " ssh_key
ssh_key=${ssh_key:-~/.ssh/id_rsa}
# Expand tilde
ssh_key="${ssh_key/#\~/$HOME}"
SERVER_SSH_KEYS+=("$ssh_key")
echo ""
done
if [[ ${#SERVER_IPS[@]} -eq 0 ]]; then
echo -e "${RED}Error: At least one server is required.${NC}"
exit 1
fi
echo -e "${GREEN}✓ Configured ${#SERVER_IPS[@]} server(s)${NC}"
echo ""
# User Information
echo -e "${GREEN}=== User Information ===${NC}"
read -p "Your name: " USER_NAME
while [[ -z "$USER_NAME" ]]; do
echo -e "${RED}Name is required.${NC}"
read -p "Your name: " USER_NAME
done
read -p "Your email: " USER_EMAIL
while [[ -z "$USER_EMAIL" ]] || [[ ! "$USER_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; do
echo -e "${RED}Valid email is required.${NC}"
read -p "Your email: " USER_EMAIL
done
read -p "Timezone [Europe/Zurich]: " TIMEZONE
TIMEZONE=${TIMEZONE:-Europe/Zurich}
echo ""
# Nextcloud Admin Account
echo -e "${GREEN}=== Nextcloud Admin Account ===${NC}"
read -p "Admin username [admin]: " ADMIN_USER
ADMIN_USER=${ADMIN_USER:-admin}
while true; do
read -sp "Admin password: " ADMIN_PASSWORD
echo ""
read -sp "Confirm password: " ADMIN_PASSWORD_CONFIRM
echo ""
if [[ "$ADMIN_PASSWORD" == "$ADMIN_PASSWORD_CONFIRM" ]] && [[ ${#ADMIN_PASSWORD} -ge 8 ]]; then
break
elif [[ ${#ADMIN_PASSWORD} -lt 8 ]]; then
echo -e "${RED}Password must be at least 8 characters.${NC}"
else
echo -e "${RED}Passwords do not match. Please try again.${NC}"
fi
done
echo ""
# Subdomain Configuration
echo -e "${GREEN}=== Subdomain Configuration ===${NC}"
echo "Press Enter to use defaults shown in brackets"
read -p "Nextcloud subdomain [cloud]: " SUBDOMAIN_NEXTCLOUD
SUBDOMAIN_NEXTCLOUD=${SUBDOMAIN_NEXTCLOUD:-cloud}
read -p "OnlyOffice subdomain [office]: " SUBDOMAIN_OFFICE
SUBDOMAIN_OFFICE=${SUBDOMAIN_OFFICE:-office}
read -p "Excalidraw subdomain [draw]: " SUBDOMAIN_DRAW
SUBDOMAIN_DRAW=${SUBDOMAIN_DRAW:-draw}
read -p "Obsidian subdomain [notes]: " SUBDOMAIN_NOTES
SUBDOMAIN_NOTES=${SUBDOMAIN_NOTES:-notes}
read -p "Homarr subdomain [home]: " SUBDOMAIN_HOMARR
SUBDOMAIN_HOMARR=${SUBDOMAIN_HOMARR:-home}
read -p "Dockhand subdomain [manage]: " SUBDOMAIN_DOCKHAND
SUBDOMAIN_DOCKHAND=${SUBDOMAIN_DOCKHAND:-manage}
read -p "Uptime Kuma subdomain [uptime]: " SUBDOMAIN_UPTIME
SUBDOMAIN_UPTIME=${SUBDOMAIN_UPTIME:-uptime}
echo ""
# Database Configuration
echo -e "${GREEN}=== Database Configuration ===${NC}"
read -p "Database name [nextcloud]: " DB_NAME
DB_NAME=${DB_NAME:-nextcloud}
read -p "Database user [nextcloud]: " DB_USER
DB_USER=${DB_USER:-nextcloud}
echo "Generating secure passwords..."
DB_PASSWORD=$(generate_password)
REDIS_PASSWORD=$(generate_password)
HOMARR_SECRET=$(openssl rand -hex 32)
echo -e "${GREEN}✓ Passwords generated${NC}"
echo ""
# Tailscale Configuration
echo -e "${GREEN}=== Tailscale Configuration ===${NC}"
read -p "Install Tailscale? (y/n) [y]: " install_tailscale
install_tailscale=${install_tailscale:-y}
if [[ "$install_tailscale" == "y" ]]; then
read -p "Tailscale auth key (optional, press Enter to skip): " TAILSCALE_AUTH_KEY
if [[ -z "$TAILSCALE_AUTH_KEY" ]]; then
echo "Note: Tailscale will be installed but not activated."
echo " Activate manually with: sudo tailscale up"
fi
fi
echo ""
# Monitoring Configuration
echo -e "${GREEN}=== Monitoring Configuration ===${NC}"
read -p "Email for uptime alerts: " ALERT_EMAIL
while [[ -z "$ALERT_EMAIL" ]] || [[ ! "$ALERT_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; do
echo -e "${RED}Valid email is required.${NC}"
read -p "Email for uptime alerts: " ALERT_EMAIL
done
read -p "Enable public status page? (y/n) [n]: " ENABLE_PUBLIC_STATUS
ENABLE_PUBLIC_STATUS=${ENABLE_PUBLIC_STATUS:-n}
echo ""
# Backup Configuration
echo -e "${GREEN}=== Backup Configuration ===${NC}"
echo "Backup retention: 30 days (default)"
read -p "Install rclone for future remote backups? (y/n) [y]: " INSTALL_RCLONE
INSTALL_RCLONE=${INSTALL_RCLONE:-y}
if [[ "$INSTALL_RCLONE" == "y" ]]; then
echo "Note: rclone will be installed but not configured."
echo " Configure later with: rclone config"
fi
echo ""
# Ansible Vault Password
echo -e "${GREEN}=== Security ===${NC}"
echo "Create Ansible Vault password (will encrypt all secrets):"
while true; do
read -sp "Vault password: " VAULT_PASSWORD
echo ""
read -sp "Confirm: " VAULT_PASSWORD_CONFIRM
echo ""
if [[ "$VAULT_PASSWORD" == "$VAULT_PASSWORD_CONFIRM" ]] && [[ ${#VAULT_PASSWORD} -ge 8 ]]; then
break
elif [[ ${#VAULT_PASSWORD} -lt 8 ]]; then
echo -e "${RED}Password must be at least 8 characters.${NC}"
else
echo -e "${RED}Passwords do not match. Please try again.${NC}"
fi
done
echo ""
# Configuration Summary
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ CONFIGURATION SUMMARY ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "Servers: ${#SERVER_IPS[@]}"
for i in "${!SERVER_IPS[@]}"; do
echo "${SERVER_IPS[$i]} (${SERVER_DOMAINS[$i]})"
done
echo ""
echo "Services (per server):"
echo " • Nextcloud: https://$SUBDOMAIN_NEXTCLOUD.<domain>"
echo " • OnlyOffice: https://$SUBDOMAIN_OFFICE.<domain>"
echo " • Excalidraw: https://$SUBDOMAIN_DRAW.<domain>"
echo " • Obsidian: https://$SUBDOMAIN_NOTES.<domain>"
echo " • Homarr: https://$SUBDOMAIN_HOMARR.<domain> (Tailscale)"
echo " • Dockhand: https://$SUBDOMAIN_DOCKHAND.<domain> (Tailscale)"
echo " • Uptime Kuma: https://$SUBDOMAIN_UPTIME.<domain> (Tailscale)"
echo ""
read -p "Proceed with these settings? (y/n): " confirm
if [[ "$confirm" != "y" ]]; then
echo "Setup cancelled."
exit 0
fi
echo ""
# Generate Configuration Files
echo "Generating configuration files..."
# Create inventory directory
mkdir -p inventory/group_vars/all
# Generate hosts.yml
cat >inventory/hosts.yml <<EOF
# Ansible Inventory - Generated by setup.sh
# Date: $(date)
all:
children:
nextcloud_servers:
hosts:
EOF
for i in "${!SERVER_IPS[@]}"; do
cat >>inventory/hosts.yml <<EOF
${SERVER_HOSTNAMES[$i]}:
ansible_host: ${SERVER_IPS[$i]}
ansible_user: ${SERVER_SSH_USERS[$i]}
ansible_ssh_private_key_file: ${SERVER_SSH_KEYS[$i]}
domain: ${SERVER_DOMAINS[$i]}
EOF
done
echo -e "${GREEN}✓ Created: inventory/hosts.yml${NC}"
# Generate vars.yml (public variables)
cat >inventory/group_vars/all/vars.yml <<EOF
# Public Variables - Generated by setup.sh
# Date: $(date)
# User Information
user_name: "$USER_NAME"
user_email: "$USER_EMAIL"
timezone: "$TIMEZONE"
# Admin Account
admin_user: "$ADMIN_USER"
# Subdomain Configuration
subdomain_nextcloud: "$SUBDOMAIN_NEXTCLOUD"
subdomain_office: "$SUBDOMAIN_OFFICE"
subdomain_draw: "$SUBDOMAIN_DRAW"
subdomain_notes: "$SUBDOMAIN_NOTES"
subdomain_homarr: "$SUBDOMAIN_HOMARR"
subdomain_dockhand: "$SUBDOMAIN_DOCKHAND"
subdomain_uptime: "$SUBDOMAIN_UPTIME"
# Database Configuration
db_name: "$DB_NAME"
db_user: "$DB_USER"
# Backup Configuration
backup_retention_days: 30
install_rclone: $INSTALL_RCLONE
# Monitoring
alert_email: "$ALERT_EMAIL"
enable_public_status: $ENABLE_PUBLIC_STATUS
# Deployment Settings
deployment_dir: "/opt/nextcloud-stack"
stack_name: "nextcloud"
# Docker Configuration
docker_compose_version: "v2"
EOF
echo -e "${GREEN}✓ Created: inventory/group_vars/all/vars.yml${NC}"
# Generate vault.yml (encrypted secrets)
VAULT_CONTENT=$(
cat <<EOF
# Encrypted Secrets - Generated by setup.sh
# Date: $(date)
# Admin Password
admin_password: "$ADMIN_PASSWORD"
# Database Credentials
db_password: "$DB_PASSWORD"
redis_password: "$REDIS_PASSWORD"
# Application Secrets
homarr_secret: "$HOMARR_SECRET"
# Tailscale
tailscale_auth_key: "$TAILSCALE_AUTH_KEY"
EOF
)
# Save vault password to temporary file
echo "$VAULT_PASSWORD" >.vault_pass_temp
# Create encrypted vault
echo "$VAULT_CONTENT" | ansible-vault encrypt --vault-password-file=.vault_pass_temp --output=inventory/group_vars/all/vault.yml
# Clean up temp file
rm .vault_pass_temp
echo -e "${GREEN}✓ Created: inventory/group_vars/all/vault.yml (encrypted)${NC}"
echo ""
# Save vault password hint
echo "IMPORTANT: Save your vault password!"
echo "You will need it to run the playbook."
echo ""
echo "Vault password: **************** (hidden)"
echo ""
# Final Instructions
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ READY TO DEPLOY! ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "Next steps:"
echo ""
echo "1. Review configuration (optional):"
echo " ansible-inventory --list"
echo ""
echo "2. Test connectivity:"
echo " ansible all -m ping --ask-vault-pass"
echo ""
echo "3. Deploy stack:"
echo " ansible-playbook playbooks/site.yml --ask-vault-pass"
echo ""
echo " OR use Makefile:"
echo " make deploy"
echo ""
echo "Estimated deployment time: 30-45 minutes per server"
echo ""
echo -e "${YELLOW}WARNING: Ensure DNS records are configured before deploying!${NC}"
echo " Let's Encrypt will fail if DNS is not pointing correctly."
echo ""
echo "Required DNS records for each server:"
for i in "${!SERVER_DOMAINS[@]}"; do
echo " ${SERVER_DOMAINS[$i]}:"
echo " $SUBDOMAIN_NEXTCLOUD.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo " $SUBDOMAIN_OFFICE.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo " $SUBDOMAIN_DRAW.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo " $SUBDOMAIN_NOTES.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo " $SUBDOMAIN_HOMARR.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo " $SUBDOMAIN_DOCKHAND.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo " $SUBDOMAIN_UPTIME.${SERVER_DOMAINS[$i]}${SERVER_IPS[$i]}"
echo ""
done
echo ""
echo "Setup complete! 🎉"