Initial LemonSec deployment
Features: - Traefik reverse proxy with automatic SSL (Let's Encrypt) - Authelia SSO and 2FA authentication - CrowdSec intrusion detection/prevention - AdGuard Home DNS - TrueNAS Nextcloud routing configured - Portainer Git Repository deployment ready Security: - Cloudflare integration with strict SSL - Rate limiting and security headers - Network segmentation (external/internal) - Automatic threat blocking
This commit is contained in:
commit
d0a2273b71
|
|
@ -0,0 +1,59 @@
|
|||
# ============================================================================
|
||||
# LEMONSEC - Environment Configuration
|
||||
# Copy this file to .env and fill in your values
|
||||
# ============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Cloudflare DNS Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# Your Cloudflare account email
|
||||
CF_API_EMAIL=your-email@example.com
|
||||
|
||||
# Cloudflare Global API Key (from https://dash.cloudflare.com/profile/api-tokens)
|
||||
# OR use DNS API Token (more secure)
|
||||
CF_API_KEY=your-cloudflare-global-api-key
|
||||
# CF_DNS_API_TOKEN=your-cloudflare-dns-api-token
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Timezone
|
||||
# -----------------------------------------------------------------------------
|
||||
TZ=Europe/Stockholm
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CrowdSec API Key
|
||||
# Generate after first CrowdSec start: docker exec crowdsec cscli bouncers add traefik-bouncer
|
||||
CROWDSEC_API_KEY=your-crowdsec-api-key-here
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Authelia Secrets
|
||||
# Generate these with: openssl rand -hex 32
|
||||
# -----------------------------------------------------------------------------
|
||||
AUTHELIA_JWT_SECRET=change-me-32-characters-min
|
||||
AUTHELIA_SESSION_SECRET=change-me-32-characters-min
|
||||
AUTHELIA_STORAGE_KEY=change-me-32-characters-min
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Grafana
|
||||
# -----------------------------------------------------------------------------
|
||||
GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=change-me-strong-password
|
||||
GRAFANA_OAUTH_SECRET=generate-with-authelia
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tailscale Configuration
|
||||
# Your Tailscale IP for internal routing
|
||||
# Find with: tailscale ip -4
|
||||
# -----------------------------------------------------------------------------
|
||||
TAILSCALE_IP=100.x.x.x
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# TrueNAS / External Services
|
||||
# -----------------------------------------------------------------------------
|
||||
TRUENAS_IP=192.168.x.x # Your TrueNAS VM IP
|
||||
TRUENAS_NEXTCLOUD_PORT=9001 # Port where TrueNAS Nextcloud is exposed
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# External Services (add your own)
|
||||
# -----------------------------------------------------------------------------
|
||||
# NEXTCLOUD_MYSQL_PASSWORD=...
|
||||
# VAULTWARDEN_ADMIN_TOKEN=...
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# Environment files (local development only)
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# Keep stack.env as template for Portainer
|
||||
# stack.env is intentionally NOT ignored
|
||||
|
||||
# Secrets
|
||||
secrets/*.txt
|
||||
secrets/*.key
|
||||
secrets/*.pem
|
||||
|
||||
# Generated certificates
|
||||
letsencrypt/
|
||||
*.crt
|
||||
*.key
|
||||
*.pem
|
||||
|
||||
# Data directories
|
||||
*/data/
|
||||
*/db/
|
||||
*.sqlite3
|
||||
|
||||
# Backups
|
||||
*.tar.gz
|
||||
*.zip
|
||||
backup/
|
||||
backups/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Runtime files
|
||||
.pid
|
||||
.sock
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker volumes that might be mounted
|
||||
adguard-work/
|
||||
adguard-conf/
|
||||
uptime-kuma-data/
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
# Git Repository Setup for Portainer
|
||||
|
||||
This guide shows how to push LemonSec to your Git server for Portainer deployment.
|
||||
|
||||
## Files to Commit
|
||||
|
||||
These files should be in your Git repository:
|
||||
|
||||
```
|
||||
LemonSec/
|
||||
├── docker-compose.yml ✅ Main stack configuration
|
||||
├── stack.env ✅ Environment variable template
|
||||
├── .gitignore ✅ Git ignore rules
|
||||
├── README.md ✅ Main documentation
|
||||
├── SUMMARY.md ✅ Quick reference
|
||||
├── PORTAINER-DEPLOY.md ✅ Portainer deployment guide
|
||||
├── SETUP-TRUENAS-NEXTCLOUD.md ✅ TrueNAS specific guide
|
||||
├── MIGRATE-FROM-NPM.md ✅ NPM migration guide
|
||||
├── SETUP.md ✅ General setup guide
|
||||
│
|
||||
├── traefik/ ✅ Traefik configuration
|
||||
│ ├── traefik.yml
|
||||
│ └── dynamic/
|
||||
│ ├── middlewares.yml
|
||||
│ └── tls.yml
|
||||
│
|
||||
├── authelia/ ✅ Authelia configuration
|
||||
│ ├── configuration.yml
|
||||
│ └── users_database.yml ✅ (Add family users here)
|
||||
│
|
||||
├── crowdsec/ ✅ CrowdSec configuration
|
||||
│ └── acquis.yaml
|
||||
│
|
||||
├── docs/ ✅ Documentation
|
||||
│ ├── CLOUDFLARE.md
|
||||
│ ├── TAILSCALE.md
|
||||
│ └── SECURITY.md
|
||||
│
|
||||
├── examples/ ✅ Service examples
|
||||
│ ├── nextcloud-compose.yml
|
||||
│ ├── vaultwarden-compose.yml
|
||||
│ └── internal-service-compose.yml
|
||||
│
|
||||
└── monitoring/ ✅ Monitoring configs
|
||||
├── prometheus.yml
|
||||
├── loki-config.yml
|
||||
└── promtail-config.yml
|
||||
```
|
||||
|
||||
## Files NOT to Commit
|
||||
|
||||
These are in `.gitignore`:
|
||||
|
||||
- `.env` - Contains your actual secrets
|
||||
- `secrets/` directory - Secret files
|
||||
- `*.log` - Log files
|
||||
- `traefik/logs/` - Traefik logs
|
||||
- `*.tar.gz` - Backup files
|
||||
|
||||
## Step-by-Step Git Setup
|
||||
|
||||
### 1. Initialize Repository
|
||||
|
||||
```bash
|
||||
cd LemonSec
|
||||
|
||||
git init
|
||||
```
|
||||
|
||||
### 2. Add Your Git Server
|
||||
|
||||
```bash
|
||||
git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
```
|
||||
|
||||
### 3. Configure Git (if needed)
|
||||
|
||||
```bash
|
||||
git config user.name "Your Name"
|
||||
git config user.email "your.email@example.com"
|
||||
```
|
||||
|
||||
### 4. Add and Commit Files
|
||||
|
||||
```bash
|
||||
# Add all files
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "Initial LemonSec deployment
|
||||
|
||||
- Traefik reverse proxy with Cloudflare SSL
|
||||
- Authelia SSO and 2FA
|
||||
- CrowdSec intrusion detection
|
||||
- AdGuard Home DNS
|
||||
- TrueNAS Nextcloud routing
|
||||
"
|
||||
```
|
||||
|
||||
### 5. Push to Server
|
||||
|
||||
```bash
|
||||
# For main branch
|
||||
git push -u origin main
|
||||
|
||||
# Or if your default is master
|
||||
git push -u origin master
|
||||
```
|
||||
|
||||
### 6. Verify
|
||||
|
||||
```bash
|
||||
# Check remote
|
||||
git remote -v
|
||||
|
||||
# Should show:
|
||||
# origin https://git.lemonlink.eu/impulsivefps/LemonSec.git (fetch)
|
||||
# origin https://git.lemonlink.eu/impulsivefps/LemonSec.git (push)
|
||||
|
||||
# Check status
|
||||
git status
|
||||
# Should show: "nothing to commit, working tree clean"
|
||||
```
|
||||
|
||||
## Updating the Repository
|
||||
|
||||
After making changes:
|
||||
|
||||
```bash
|
||||
# Make changes to files
|
||||
nano authelia/users_database.yml # Add family member
|
||||
|
||||
# Commit
|
||||
git add authelia/users_database.yml
|
||||
git commit -m "Add family member to Authelia"
|
||||
|
||||
# Push
|
||||
git push
|
||||
|
||||
# In Portainer: Pull and redeploy stack
|
||||
```
|
||||
|
||||
## Repository URL for Portainer
|
||||
|
||||
Use this URL in Portainer:
|
||||
|
||||
```
|
||||
https://git.lemonlink.eu/impulsivefps/LemonSec
|
||||
```
|
||||
|
||||
### If Repository is Private
|
||||
|
||||
If your Git server requires authentication:
|
||||
|
||||
1. **Option A: Deploy Key** (Recommended)
|
||||
- Generate SSH key pair
|
||||
- Add public key to Git repo as deploy key
|
||||
- Use SSH URL: `git@git.lemonlink.eu:impulsivefps/LemonSec.git`
|
||||
|
||||
2. **Option B: Personal Access Token**
|
||||
- Create token in Git settings
|
||||
- Use HTTPS with token: `https://token@git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
|
||||
3. **Option C: Portainer Git Credentials**
|
||||
- In Portainer stack settings
|
||||
- Enable authentication
|
||||
- Enter username/password
|
||||
|
||||
## Testing Git Access
|
||||
|
||||
From your Proxmox VM:
|
||||
|
||||
```bash
|
||||
# Test HTTPS access
|
||||
git ls-remote https://git.lemonlink.eu/impulsivefps/LemonSec
|
||||
|
||||
# Should show refs without errors
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "repository not found"
|
||||
- Verify URL is correct
|
||||
- Check repository exists on Git server
|
||||
- Confirm permissions
|
||||
|
||||
### "Authentication failed"
|
||||
- Check credentials
|
||||
- Verify deploy key is added (if using SSH)
|
||||
- Try accessing in browser first
|
||||
|
||||
### "Updates were rejected"
|
||||
```bash
|
||||
# Pull first
|
||||
git pull origin main
|
||||
|
||||
# Then push
|
||||
git push
|
||||
```
|
||||
|
||||
### Large files / Binary files
|
||||
If you accidentally committed secrets:
|
||||
```bash
|
||||
# Remove from history (be careful!)
|
||||
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch .env' HEAD
|
||||
|
||||
# Force push
|
||||
git push --force
|
||||
```
|
||||
|
||||
## Branch Strategy
|
||||
|
||||
For simple deployments, use `main` or `master`:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch
|
||||
|
||||
# Create and switch to main if needed
|
||||
git checkout -b main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
For advanced setups, you might want:
|
||||
- `main` - Production
|
||||
- `develop` - Testing
|
||||
- `feature/*` - New services
|
||||
|
||||
## Automated Updates
|
||||
|
||||
Set up webhook (if your Git server supports it):
|
||||
1. Go to Git repo settings
|
||||
2. Add webhook URL: `http://portainer:9000/api/stacks/{stack_id}/git/redeploy`
|
||||
3. On push, Portainer auto-redeploys
|
||||
|
||||
Or use Portainer's polling:
|
||||
- Stacks → lemonsec → Git settings
|
||||
- Enable automatic updates
|
||||
- Set interval (e.g., 5 minutes)
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
# Migrating from Nginx Proxy Manager (NPM)
|
||||
|
||||
## Concept Mapping
|
||||
|
||||
| NPM Feature | Traefik Equivalent |
|
||||
|-------------|-------------------|
|
||||
| Proxy Hosts | Routers + Services |
|
||||
| SSL Certificates | certresolver (automatic) |
|
||||
| Custom Locations | Middlewares |
|
||||
| Access Lists | Authelia / Basic Auth |
|
||||
| 404 Page | Custom error middleware |
|
||||
| Redirection Hosts | Redirect middleware |
|
||||
| Streams | TCP Routers |
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### 1. Document Current NPM Setup
|
||||
|
||||
In NPM UI, go through each Proxy Host and note:
|
||||
- Domain names
|
||||
- Forward hostname/IP and port
|
||||
- Scheme (http/https)
|
||||
- Custom locations
|
||||
- Access lists
|
||||
- SSL settings
|
||||
- Advanced configuration
|
||||
|
||||
### 2. Export NPM Config (Optional Backup)
|
||||
|
||||
NPM → **Settings** → **Backup** → **Download**
|
||||
|
||||
This creates a JSON backup of all hosts.
|
||||
|
||||
### 3. Create docker-compose.override.yml
|
||||
|
||||
This is where your migrated hosts go:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
services:
|
||||
external: true
|
||||
|
||||
services:
|
||||
# ========================================
|
||||
# MIGRATED FROM NPM
|
||||
# ========================================
|
||||
|
||||
# Example: Migrating "nextcloud.lemonlink.eu"
|
||||
# NPM Settings:
|
||||
# - Domain: nextcloud.lemonlink.eu
|
||||
# - Forward: 192.168.1.100:9001
|
||||
# - Scheme: http
|
||||
# - Block Common Exploits: yes
|
||||
# - SSL: yes (LE)
|
||||
|
||||
nextcloud-migration:
|
||||
image: alpine:latest
|
||||
container_name: npm-migration-nextcloud
|
||||
restart: "no"
|
||||
command: "echo 'Migrated from NPM'"
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
# Enable Traefik
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Router (like Proxy Host)
|
||||
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.lemonlink.eu`)"
|
||||
- "traefik.http.routers.nextcloud.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
||||
|
||||
# Service (Forward to)
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.url=http://192.168.1.100:9001"
|
||||
|
||||
# Middlewares (Access Lists, Security)
|
||||
- "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file"
|
||||
|
||||
# Add more migrations here...
|
||||
# service2-migration:
|
||||
# ...
|
||||
```
|
||||
|
||||
### 4. Common NPM → Traefik Patterns
|
||||
|
||||
#### Pattern 1: Basic Proxy Host
|
||||
```
|
||||
NPM:
|
||||
Domain: app.lemonlink.eu
|
||||
Forward: 192.168.1.10:8080
|
||||
SSL: yes
|
||||
|
||||
Traefik:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.app.rule=Host(`app.lemonlink.eu`)"
|
||||
- "traefik.http.routers.app.entrypoints=websecure"
|
||||
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.app.loadbalancer.server.url=http://192.168.1.10:8080"
|
||||
- "traefik.http.routers.app.middlewares=security-headers@file,rate-limit@file"
|
||||
```
|
||||
|
||||
#### Pattern 2: Access List (Basic Auth)
|
||||
```
|
||||
NPM:
|
||||
Access List: Family
|
||||
Users: user1 / pass1, user2 / pass2
|
||||
|
||||
Traefik:
|
||||
# In docker-compose.override.yml middlewares section
|
||||
# Or use Authelia (recommended)
|
||||
|
||||
# Option A: Basic Auth (simple)
|
||||
labels:
|
||||
- "traefik.http.routers.app.middlewares=app-basicauth"
|
||||
- "traefik.http.middlewares.app-basicauth.basicauth.users=user1:$$apr1$$..."
|
||||
|
||||
# Option B: Authelia (SSO, recommended)
|
||||
labels:
|
||||
- "traefik.http.routers.app.middlewares=authelia@docker"
|
||||
```
|
||||
|
||||
#### Pattern 3: Custom Location
|
||||
```
|
||||
NPM:
|
||||
Location: /api
|
||||
Forward: 192.168.1.10:8080/api
|
||||
|
||||
Traefik:
|
||||
# Use PathPrefix rule
|
||||
labels:
|
||||
- "traefik.http.routers.app-api.rule=Host(`app.lemonlink.eu`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.app-api.service=api-service"
|
||||
- "traefik.http.services.api-service.loadbalancer.server.url=http://192.168.1.10:8080"
|
||||
```
|
||||
|
||||
#### Pattern 4: Custom Nginx Config
|
||||
```
|
||||
NPM Advanced:
|
||||
proxy_set_header X-Custom-Header value;
|
||||
client_max_body_size 10G;
|
||||
|
||||
Traefik:
|
||||
# Add to dynamic/middlewares.yml or inline
|
||||
labels:
|
||||
- "traefik.http.middlewares.custom-headers.headers.customRequestHeaders.X-Custom-Header=value"
|
||||
- "traefik.http.routers.app.middlewares=custom-headers"
|
||||
|
||||
# For body size, use buffering:
|
||||
- "traefik.http.services.app.loadbalancer.responseforwarding.flushinterval=100ms"
|
||||
```
|
||||
|
||||
### 5. Gradual Migration Strategy
|
||||
|
||||
Don't migrate everything at once:
|
||||
|
||||
**Week 1: Setup LemonSec**
|
||||
- Deploy Traefik + Authelia + CrowdSec
|
||||
- Test with one non-critical service
|
||||
- Keep NPM running
|
||||
|
||||
**Week 2: Migrate Low-Risk Services**
|
||||
- Move internal services first
|
||||
- Test thoroughly
|
||||
- Keep Nextcloud on NPM for now
|
||||
|
||||
**Week 3: Migrate Nextcloud**
|
||||
- Schedule downtime
|
||||
- Update TrueNAS Nextcloud config
|
||||
- Test with family
|
||||
- Keep NPM as backup
|
||||
|
||||
**Week 4: Decommission NPM**
|
||||
- After 1 week stable
|
||||
- Stop NPM container
|
||||
- Remove port forwards to NPM
|
||||
- Keep config backup
|
||||
|
||||
### 6. Parallel Running
|
||||
|
||||
You can run both during migration:
|
||||
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
Router (port 443)
|
||||
↓
|
||||
├─► NPM (port 444) ──► Some services
|
||||
└─► Traefik (port 443) ──► New services
|
||||
```
|
||||
|
||||
Change router port forward to test:
|
||||
- Port 443 → Traefik
|
||||
- Port 444 → NPM (backup access)
|
||||
|
||||
### 7. Testing Checklist
|
||||
|
||||
For each migrated service:
|
||||
- [ ] HTTP redirects to HTTPS
|
||||
- [ ] SSL certificate valid
|
||||
- [ ] Can login
|
||||
- [ ] All pages load
|
||||
- [ ] File uploads work
|
||||
- [ ] Mobile apps connect
|
||||
- [ ] No console errors
|
||||
|
||||
### 8. Rollback Plan
|
||||
|
||||
If something breaks:
|
||||
|
||||
```bash
|
||||
# Stop Traefik
|
||||
docker-compose -f docker-compose.yml -f docker-compose.override.yml down
|
||||
|
||||
# Restart NPM
|
||||
docker start npm
|
||||
|
||||
# Revert router port forward to NPM
|
||||
# Access via: https://old-domain:444 (if configured)
|
||||
```
|
||||
|
||||
Keep NPM container stopped but available:
|
||||
```bash
|
||||
docker stop npm
|
||||
docker rename npm npm-backup
|
||||
# Don't delete until migration complete
|
||||
```
|
||||
|
||||
## NPM Features Not in Traefik
|
||||
|
||||
| NPM Feature | Traefik Alternative |
|
||||
|-------------|---------------------|
|
||||
| Nice Web UI | Traefik Dashboard + Portainer |
|
||||
| One-click SSL | Automatic via certresolver |
|
||||
| Built-in auth | Authelia (more powerful) |
|
||||
| Redirection UI | YAML config |
|
||||
| Access Logs | JSON logs in traefik/logs/ |
|
||||
|
||||
## Benefits of Migrating
|
||||
|
||||
1. **Declarative**: All config in Git-trackable files
|
||||
2. **Automatic**: SSL certificates without clicks
|
||||
3. **Integrated**: Built-in CrowdSec, Authelia
|
||||
4. **Fast**: Better performance than NPM
|
||||
5. **Flexible**: More routing options
|
||||
6. **Modern**: Active development, Traefik 3.x
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check `examples/` for common patterns
|
||||
- See `docs/TRUENAS-NEXTCLOUD.md` for Nextcloud specifically
|
||||
- Review `QUICKSTART.md` for fresh setup
|
||||
- Traefik docs: https://doc.traefik.io/
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
# Deploy LemonSec via Portainer Git Repository
|
||||
|
||||
## Overview
|
||||
|
||||
This guide shows how to deploy LemonSec using Portainer's **Git Repository** feature. This is the cleanest deployment method - no manual file copying, just point Portainer to your Git repo!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [ ] Portainer installed on Proxmox VM
|
||||
- [ ] Git repository at `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
- [ ] Cloudflare account with `lemonlink.eu` domain
|
||||
- [ ] TrueNAS Scale running Nextcloud
|
||||
|
||||
## Step 1: Prepare Git Repository
|
||||
|
||||
### On your local machine (where you created the files):
|
||||
|
||||
```bash
|
||||
# Initialize git repo (if not already)
|
||||
cd LemonSec
|
||||
git init
|
||||
git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
|
||||
# Add all files
|
||||
git add .
|
||||
git commit -m "Initial LemonSec deployment"
|
||||
|
||||
# Push to your Git server
|
||||
git push -u origin main
|
||||
# or git push -u origin master
|
||||
```
|
||||
|
||||
### Files that should be in the repo:
|
||||
```
|
||||
LemonSec/
|
||||
├── docker-compose.yml ✅ Main stack
|
||||
├── stack.env ✅ Environment template
|
||||
├── traefik/
|
||||
│ ├── traefik.yml ✅ Static config
|
||||
│ └── dynamic/
|
||||
│ ├── middlewares.yml ✅ Security middlewares
|
||||
│ └── tls.yml ✅ TLS settings
|
||||
├── authelia/
|
||||
│ ├── configuration.yml ✅ Authelia config
|
||||
│ └── users_database.yml ✅ Family users
|
||||
├── crowdsec/
|
||||
│ └── acquis.yaml ✅ Log sources
|
||||
└── docs/ ✅ Documentation
|
||||
```
|
||||
|
||||
### Files to NOT commit (already in .gitignore):
|
||||
- `.env` - Contains secrets
|
||||
- `secrets/` directory
|
||||
- Any `*.log` files
|
||||
- Backup files
|
||||
|
||||
## Step 2: Generate Secrets
|
||||
|
||||
Before deploying, generate the Authelia secrets:
|
||||
|
||||
### Option A: On Windows (PowerShell)
|
||||
```powershell
|
||||
$jwt = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
|
||||
Write-Host "AUTHELIA_JWT_SECRET: $jwt"
|
||||
|
||||
$session = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
|
||||
Write-Host "AUTHELIA_SESSION_SECRET: $session"
|
||||
|
||||
$storage = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
|
||||
Write-Host "AUTHELIA_STORAGE_KEY: $storage"
|
||||
```
|
||||
|
||||
### Option B: On Linux/macOS
|
||||
```bash
|
||||
openssl rand -hex 32 # Run 3 times for each secret
|
||||
echo "AUTHELIA_JWT_SECRET: $(openssl rand -hex 32)"
|
||||
echo "AUTHELIA_SESSION_SECRET: $(openssl rand -hex 32)"
|
||||
echo "AUTHELIA_STORAGE_KEY: $(openssl rand -hex 32)"
|
||||
```
|
||||
|
||||
### Option C: In Portainer (after first failed deploy)
|
||||
The stack will fail to start without secrets. You can generate them inside the Portainer host:
|
||||
```bash
|
||||
docker run --rm authelia/authelia:latest authelia crypto rand --length 64 --hex
|
||||
docker run --rm alpine openssl rand -hex 32
|
||||
```
|
||||
|
||||
**Save these 3 secrets!** You'll need them in Step 4.
|
||||
|
||||
## Step 3: Deploy in Portainer
|
||||
|
||||
### 1. Open Portainer
|
||||
Navigate to your Portainer UI: `http://your-proxmox-vm:9000`
|
||||
|
||||
### 2. Create Stack from Git
|
||||
- Click **Stacks** → **Add Stack**
|
||||
- Select **Repository** (not Web editor)
|
||||
- Fill in:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Name** | `lemonsec` |
|
||||
| **Repository URL** | `https://git.lemonlink.eu/impulsivefps/LemonSec` |
|
||||
| **Repository Reference** | `refs/heads/main` (or `refs/heads/master`) |
|
||||
| **Compose Path** | `docker-compose.yml` |
|
||||
| **Authentication** | (only if repo is private) |
|
||||
|
||||
### 3. Environment Variables
|
||||
Click **Load variables from .env file** or enter manually:
|
||||
|
||||
**Required Variables:**
|
||||
|
||||
| Variable | Example Value | Description |
|
||||
|----------|---------------|-------------|
|
||||
| `CF_API_EMAIL` | `youremail@example.com` | Cloudflare account email |
|
||||
| `CF_API_KEY` | `your-cloudflare-global-api-key` | From Cloudflare profile |
|
||||
| `TRUENAS_IP` | `192.168.1.100` | TrueNAS Scale VM IP |
|
||||
| `TRUENAS_NEXTCLOUD_PORT` | `9001` | Nextcloud port on TrueNAS |
|
||||
| `AUTHELIA_JWT_SECRET` | `a1b2c3d4...` (64 hex chars) | Generated secret |
|
||||
| `AUTHELIA_SESSION_SECRET` | `e5f6g7h8...` (64 hex chars) | Generated secret |
|
||||
| `AUTHELIA_STORAGE_KEY` | `i9j0k1l2...` (64 hex chars) | Generated secret |
|
||||
|
||||
**Optional Variables:**
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `TZ` | `Europe/Stockholm` | Timezone |
|
||||
| `TAILSCALE_IP` | (empty) | For internal access |
|
||||
| `CROWDSEC_API_KEY` | (empty) | Add after first deploy |
|
||||
|
||||
### 4. Deploy the Stack
|
||||
- Click **Deploy the stack**
|
||||
- Portainer will pull from Git and start containers
|
||||
|
||||
## Step 4: Initial Configuration
|
||||
|
||||
### 1. Check Deployment Status
|
||||
In Portainer:
|
||||
- **Stacks** → **lemonsec** → Check all containers are running
|
||||
- Click on individual containers to view logs
|
||||
|
||||
### 2. Generate CrowdSec API Key
|
||||
Once CrowdSec is running:
|
||||
```bash
|
||||
# In Portainer → Containers → crowdsec → Console
|
||||
# Or SSH to host:
|
||||
docker exec crowdsec cscli bouncers add traefik-bouncer
|
||||
|
||||
# Copy the API key output
|
||||
```
|
||||
|
||||
### 3. Update Environment Variables
|
||||
- **Stacks** → **lemonsec** → **Editor** tab
|
||||
- Add `CROWDSEC_API_KEY=your-key-here`
|
||||
- Click **Update the stack**
|
||||
|
||||
### 4. Configure TrueNAS Nextcloud
|
||||
In TrueNAS Scale:
|
||||
1. **Apps** → **Installed** → **Nextcloud** → **Edit**
|
||||
2. **Environment Variables**:
|
||||
```
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
OVERWRITEPROTOCOL=https
|
||||
OVERWRITEHOST=cloud.lemonlink.eu
|
||||
OVERWRITECLIURL=https://cloud.lemonlink.eu
|
||||
TRUSTED_PROXIES=192.168.1.50
|
||||
```
|
||||
(Replace 192.168.1.50 with your Proxmox VM IP)
|
||||
3. **Save**
|
||||
|
||||
### 5. Configure Cloudflare DNS
|
||||
1. Login to [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||
2. Add DNS records:
|
||||
|
||||
| Type | Name | Target | Proxy |
|
||||
|------|------|--------|-------|
|
||||
| A | cloud | YOUR_PUBLIC_IP | 🟠 Proxied |
|
||||
| A | auth | YOUR_PUBLIC_IP | 🟠 Proxied |
|
||||
| A | *.local | YOUR_PUBLIC_IP | 🟠 Proxied |
|
||||
|
||||
3. **SSL/TLS** → **Overview**:
|
||||
- Set to **Full (strict)**
|
||||
- Enable **Always Use HTTPS**
|
||||
|
||||
## Step 5: Verify Everything Works
|
||||
|
||||
### Test Commands
|
||||
```bash
|
||||
# From any machine:
|
||||
curl -I https://cloud.lemonlink.eu
|
||||
# Expected: HTTP/2 200 or redirect to login
|
||||
|
||||
# Check Traefik dashboard (via Tailscale):
|
||||
curl -k -I https://traefik.local.lemonlink.eu:8443
|
||||
|
||||
# Verify SSL certificate:
|
||||
echo | openssl s_client -servername cloud.lemonlink.eu -connect cloud.lemonlink.eu:443 2>/dev/null | openssl x509 -noout -dates
|
||||
```
|
||||
|
||||
### Web Access
|
||||
- **Nextcloud**: `https://cloud.lemonlink.eu` ✅
|
||||
- **Authelia**: `https://auth.lemonlink.eu` ✅
|
||||
|
||||
## Step 6: Add Family to Authelia (Optional)
|
||||
|
||||
If you enabled Authelia on Nextcloud:
|
||||
|
||||
1. Generate password hash:
|
||||
```bash
|
||||
docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'FamilyPassword123!'
|
||||
```
|
||||
|
||||
2. Edit `authelia/users_database.yml` in your Git repo
|
||||
3. Push changes
|
||||
4. In Portainer: **Stacks** → **lemonsec** → **Pull and redeploy**
|
||||
|
||||
## Updating the Stack
|
||||
|
||||
### Update from Git (new commits)
|
||||
1. **Stacks** → **lemonsec**
|
||||
2. Click **Pull and redeploy**
|
||||
3. Portainer pulls latest changes and restarts
|
||||
|
||||
### Update container images
|
||||
1. **Stacks** → **lemonsec** → **Editor**
|
||||
2. Click **Update the stack** (this pulls new images)
|
||||
|
||||
Or manually:
|
||||
```bash
|
||||
# SSH to host
|
||||
docker-compose -f /var/lib/docker/volumes/portainer_data/_data/compose/lemonsec/docker-compose.yml pull
|
||||
docker-compose -f /var/lib/docker/volumes/portainer_data/_data/compose/lemonsec/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "CF_API_EMAIL not set" error
|
||||
- Check all required environment variables are filled in Portainer
|
||||
- Redeploy the stack
|
||||
|
||||
### "Bad Gateway" for Nextcloud
|
||||
- Verify `TRUENAS_IP` and `TRUENAS_NEXTCLOUD_PORT` are correct
|
||||
- Test: `curl http://TRUENAS_IP:TRUENAS_NEXTCLOUD_PORT` from Proxmox VM
|
||||
|
||||
### SSL Certificate issues
|
||||
- Check Cloudflare API credentials
|
||||
- Verify DNS records exist
|
||||
- Check Traefik logs in Portainer
|
||||
|
||||
### CrowdSec not blocking
|
||||
- Verify `CROWDSEC_API_KEY` is set and correct
|
||||
- Check CrowdSec logs for connection errors
|
||||
|
||||
### Can't pull from Git
|
||||
- Verify Git URL is accessible from Portainer host
|
||||
- Check if repository requires authentication
|
||||
- Try: `git ls-remote https://git.lemonlink.eu/impulsivefps/LemonSec` from host
|
||||
|
||||
## Portainer Stack vs Docker Compose
|
||||
|
||||
| Feature | Portainer Stacks | Docker Compose CLI |
|
||||
|---------|------------------|-------------------|
|
||||
| Deployment | Git/Web UI | Command line |
|
||||
| Updates | One-click pull | Manual git pull |
|
||||
| Environment | UI variables | .env file |
|
||||
| Logs | Built-in UI | docker logs |
|
||||
| Access control | Portainer RBAC | OS level |
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Portainer Backup
|
||||
Portainer automatically stores stack files in:
|
||||
```
|
||||
/var/lib/docker/volumes/portainer_data/_data/compose/
|
||||
```
|
||||
|
||||
### Manual Backup
|
||||
```bash
|
||||
# Backup Portainer data
|
||||
docker run --rm -v portainer_data:/data -v $(pwd):/backup alpine tar czf /backup/portainer-backup.tar.gz -C /data .
|
||||
|
||||
# Backup LemonSec volumes
|
||||
docker volume ls | grep lemonsec
|
||||
docker run --rm -v lemonsec_traefik-certs:/certs -v $(pwd):/backup alpine tar czf /backup/traefik-certs.tar.gz -C /certs .
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Verify Nextcloud is accessible
|
||||
2. ✅ Test family member logins
|
||||
3. ✅ Set up mobile apps
|
||||
4. ✅ Enable monitoring (optional)
|
||||
5. ✅ Add more services (Vaultwarden, etc.)
|
||||
|
||||
## Support
|
||||
|
||||
- **Traefik issues**: Check logs in Portainer → Containers → traefik → Logs
|
||||
- **Authelia issues**: Check logs in Portainer → Containers → authelia → Logs
|
||||
- **TrueNAS issues**: Check TrueNAS → Apps → Nextcloud → Logs
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
# LemonSec Quick Start Guide
|
||||
|
||||
## 🚀 Deployment Steps
|
||||
|
||||
### Step 1: Prepare Your Server
|
||||
|
||||
```bash
|
||||
# On Proxmox (Debian/Ubuntu)
|
||||
sudo apt update && sudo apt install -y docker.io docker-compose
|
||||
sudo usermod -aG docker $USER
|
||||
# Log out and back in
|
||||
```
|
||||
|
||||
### Step 2: Configure Environment
|
||||
|
||||
```bash
|
||||
cd LemonSec
|
||||
|
||||
# 1. Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# 2. Edit with your details
|
||||
nano .env
|
||||
# Required:
|
||||
# - CF_API_EMAIL (your Cloudflare email)
|
||||
# - CF_API_KEY (from https://dash.cloudflare.com/profile/api-tokens)
|
||||
# - TAILSCALE_IP (from `tailscale ip -4`)
|
||||
```
|
||||
|
||||
### Step 3: Setup Authelia
|
||||
|
||||
```bash
|
||||
# Generate password hash for admin user
|
||||
docker run --rm authelia/authelia:latest \
|
||||
authelia crypto hash generate argon2 \
|
||||
--password 'YourSecurePassword123!'
|
||||
|
||||
# Edit users database
|
||||
nano authelia/users_database.yml
|
||||
# Replace the password hash with the one you generated
|
||||
```
|
||||
|
||||
### Step 4: Run Setup Script
|
||||
|
||||
```powershell
|
||||
# On Windows
|
||||
.\setup.ps1
|
||||
|
||||
# On Linux (create first)
|
||||
# bash setup.sh
|
||||
```
|
||||
|
||||
### Step 5: Start Core Services
|
||||
|
||||
```bash
|
||||
# Start everything
|
||||
docker-compose up -d
|
||||
|
||||
# Check Traefik is working
|
||||
docker-compose logs -f traefik
|
||||
|
||||
# You should see "Configuration loaded from files..." and no errors
|
||||
```
|
||||
|
||||
### Step 6: Configure CrowdSec
|
||||
|
||||
```bash
|
||||
# Generate API key for Traefik bouncer
|
||||
docker-compose exec crowdsec cscli bouncers add traefik-bouncer
|
||||
|
||||
# Copy the key and add to .env:
|
||||
# CROWDSEC_API_KEY=your-key-here
|
||||
|
||||
# Restart to apply
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Step 7: Verify DNS
|
||||
|
||||
In Cloudflare DNS, ensure you have:
|
||||
|
||||
| Type | Name | Target | Proxy |
|
||||
|------|------|--------|-------|
|
||||
| A | @ | YOUR_IP | 🟠 |
|
||||
| A | * | YOUR_IP | 🟠 |
|
||||
| A | auth | YOUR_IP | 🟠 |
|
||||
|
||||
### Step 8: Test Access
|
||||
|
||||
```bash
|
||||
# Test external (after DNS propagates)
|
||||
curl -I https://auth.lemonlink.eu
|
||||
|
||||
# Test internal (via Tailscale)
|
||||
curl -k -I https://traefik.local.lemonlink.eu:8443
|
||||
```
|
||||
|
||||
## 📝 Adding Services
|
||||
|
||||
### External Service (e.g., Nextcloud)
|
||||
|
||||
```yaml
|
||||
# In docker-compose.override.yml
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:latest
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)"
|
||||
- "traefik.http.routers.nextcloud.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.nextcloud.middlewares=authelia@docker"
|
||||
```
|
||||
|
||||
### Internal Service (e.g., Portainer)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
portainer:
|
||||
image: portainer/portainer-ce:latest
|
||||
networks:
|
||||
- services
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.portainer.rule=Host(`docker.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=internal"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.portainer.middlewares=authelia@docker"
|
||||
```
|
||||
|
||||
## 🔍 Common Commands
|
||||
|
||||
```bash
|
||||
# View all logs
|
||||
docker-compose logs -f
|
||||
|
||||
# View specific service
|
||||
docker-compose logs -f authelia
|
||||
|
||||
# Restart service
|
||||
docker-compose restart traefik
|
||||
|
||||
# Check CrowdSec bans
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# Unban an IP
|
||||
docker-compose exec crowdsec cscli decisions delete --ip 1.2.3.4
|
||||
|
||||
# Update everything
|
||||
docker-compose pull && docker-compose up -d
|
||||
|
||||
# Full reset (keeps data)
|
||||
docker-compose down && docker-compose up -d
|
||||
|
||||
# Complete wipe (⚠️ destroys data)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### "Bad Gateway" Error
|
||||
- Check service is running: `docker-compose ps`
|
||||
- Check service logs: `docker-compose logs [service]`
|
||||
- Verify port in labels matches container port
|
||||
|
||||
### Certificate Issues
|
||||
- Check Cloudflare API credentials
|
||||
- Verify DNS records
|
||||
- Check Traefik logs for ACME errors
|
||||
- Use staging first: change `certresolver` to `letsencrypt-staging`
|
||||
|
||||
### Authelia Redirect Loop
|
||||
- Check `session.domain` in authelia/configuration.yml
|
||||
- Verify time sync: `timedatectl status`
|
||||
- Clear browser cookies
|
||||
|
||||
### Can't Access Internal Services
|
||||
- Verify Tailscale is connected: `tailscale status`
|
||||
- Check if port 8443 is bound to Tailscale IP
|
||||
- Test locally: `curl -k https://localhost:8443`
|
||||
|
||||
### CrowdSec Blocking Legitimate Traffic
|
||||
```bash
|
||||
# Check what's blocked
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# Remove false positive
|
||||
docker-compose exec crowdsec cscli decisions delete --ip YOUR_IP
|
||||
|
||||
# Add whitelist
|
||||
docker-compose exec crowdsec cscli parsers install crowdsecurity/whitelists
|
||||
```
|
||||
|
||||
## 📊 Monitoring Stack (Optional)
|
||||
|
||||
```bash
|
||||
# Start monitoring
|
||||
docker-compose --profile monitoring up -d
|
||||
|
||||
# Access (via Tailscale)
|
||||
# - Grafana: https://grafana.local.lemonlink.eu:8443
|
||||
# - Prometheus: https://prometheus.local.lemonlink.eu:8443
|
||||
```
|
||||
|
||||
## 🔄 Backup Strategy
|
||||
|
||||
```bash
|
||||
# Backup script (run weekly)
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d)
|
||||
tar czf backup-$DATE.tar.gz \
|
||||
traefik/ authelia/ crowdsec/ .env
|
||||
|
||||
# Backup volumes
|
||||
docker run --rm \
|
||||
-v lemonsec_authelia-data:/data \
|
||||
-v $(pwd):/backup \
|
||||
alpine tar czf /backup/authelia-$DATE.tar.gz -C /data .
|
||||
```
|
||||
|
||||
## 🛡️ Security Checklist
|
||||
|
||||
- [ ] Changed default Authelia password
|
||||
- [ ] Enabled 2FA in Authelia
|
||||
- [ ] Set up email notifications
|
||||
- [ ] Configured CrowdSec notifications
|
||||
- [ ] Enabled Cloudflare "Under Attack" mode for DDoS
|
||||
- [ ] Set up regular backups
|
||||
- [ ] Reviewed access logs weekly
|
||||
- [ ] Updated images monthly
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
1. **Add your services** - See `examples/` directory
|
||||
2. **Configure monitoring** - Enable `--profile monitoring`
|
||||
3. **Set up notifications** - Email/Discord alerts
|
||||
4. **Review security** - Follow `docs/SECURITY.md`
|
||||
5. **Customize** - Edit `docker-compose.override.yml`
|
||||
|
||||
## 💬 Getting Help
|
||||
|
||||
- **Traefik docs**: https://doc.traefik.io/traefik/
|
||||
- **Authelia docs**: https://www.authelia.com/
|
||||
- **CrowdSec docs**: https://docs.crowdsec.net/
|
||||
- **Logs**: Always check `docker-compose logs [service]` first
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
# 🚀 Deploy LemonSec Now
|
||||
|
||||
This is the **fast path** to get LemonSec running on your Proxmox VM with Portainer.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [ ] Proxmox VM with Docker and Portainer installed
|
||||
- [ ] TrueNAS Scale VM with Nextcloud app installed
|
||||
- [ ] Cloudflare account managing `lemonlink.eu`
|
||||
- [ ] Ports 80/443 forwarded to Proxmox VM
|
||||
- [ ] Git repository at `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
|
||||
## Step 1: Prepare (2 minutes)
|
||||
|
||||
### Get your info:
|
||||
```bash
|
||||
# Proxmox VM IP (where Portainer runs)
|
||||
ip addr show | grep "inet " | head -2
|
||||
# e.g., 192.168.1.50
|
||||
|
||||
# TrueNAS IP
|
||||
cat /etc/motd # or check TrueNAS UI
|
||||
# e.g., 192.168.1.100
|
||||
|
||||
# Nextcloud Port (TrueNAS → Apps → Nextcloud)
|
||||
# e.g., 9001
|
||||
|
||||
# Cloudflare API Key
|
||||
# https://dash.cloudflare.com/profile/api-tokens → Global API Key
|
||||
```
|
||||
|
||||
### Generate Secrets:
|
||||
```bash
|
||||
# Run this 3 times, save each output:
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
## Step 2: Push to Git (1 minute)
|
||||
|
||||
```bash
|
||||
cd LemonSec
|
||||
git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
git add .
|
||||
git commit -m "Initial LemonSec deployment"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Step 3: Deploy in Portainer (3 minutes)
|
||||
|
||||
### 3.1 Create Stack
|
||||
1. Open Portainer: `http://your-proxmox-vm:9000`
|
||||
2. **Stacks** → **Add Stack**
|
||||
3. Select **Repository**
|
||||
4. Fill in:
|
||||
- **Name**: `lemonsec`
|
||||
- **Repository URL**: `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
- **Compose path**: `docker-compose.yml`
|
||||
|
||||
### 3.2 Add Environment Variables
|
||||
|
||||
Copy/paste from `stack.env` and fill in your values:
|
||||
|
||||
```
|
||||
CF_API_EMAIL=your-email@example.com
|
||||
CF_API_KEY=your-cloudflare-global-api-key
|
||||
TRUENAS_IP=192.168.1.100
|
||||
TRUENAS_NEXTCLOUD_PORT=9001
|
||||
AUTHELIA_JWT_SECRET=aaaa... (64 hex chars)
|
||||
AUTHELIA_SESSION_SECRET=bbbb... (64 hex chars)
|
||||
AUTHELIA_STORAGE_KEY=cccc... (64 hex chars)
|
||||
TZ=Europe/Stockholm
|
||||
```
|
||||
|
||||
### 3.3 Deploy
|
||||
Click **Deploy the stack**
|
||||
|
||||
Wait for containers to start (about 30 seconds).
|
||||
|
||||
## Step 4: Configure CrowdSec (2 minutes)
|
||||
|
||||
After deployment, get the API key:
|
||||
|
||||
```bash
|
||||
# SSH to Proxmox VM or use Portainer Console
|
||||
docker exec crowdsec cscli bouncers add traefik-bouncer
|
||||
|
||||
# Copy the key
|
||||
```
|
||||
|
||||
Back in Portainer:
|
||||
1. **Stacks** → **lemonsec** → **Editor**
|
||||
2. Add environment variable: `CROWDSEC_API_KEY=the-key-you-copied`
|
||||
3. Click **Update the stack**
|
||||
|
||||
## Step 5: Configure TrueNAS (2 minutes)
|
||||
|
||||
In TrueNAS Scale UI:
|
||||
1. **Apps** → **Installed** → **Nextcloud** → **Edit**
|
||||
2. Add Environment Variables:
|
||||
```
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
OVERWRITEPROTOCOL=https
|
||||
OVERWRITEHOST=cloud.lemonlink.eu
|
||||
OVERWRITECLIURL=https://cloud.lemonlink.eu
|
||||
TRUSTED_PROXIES=192.168.1.50 # Your Proxmox VM IP
|
||||
```
|
||||
3. **Save**
|
||||
|
||||
## Step 6: Cloudflare DNS (1 minute)
|
||||
|
||||
1. Login to [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||
2. **DNS** → Add records:
|
||||
|
||||
| Type | Name | Target | Proxy |
|
||||
|------|------|--------|-------|
|
||||
| A | cloud | YOUR_PUBLIC_IP | 🟠 Orange |
|
||||
| A | auth | YOUR_PUBLIC_IP | 🟠 Orange |
|
||||
|
||||
3. **SSL/TLS** → Set to **Full (strict)**
|
||||
|
||||
## Step 7: Test (30 seconds)
|
||||
|
||||
Open in browser:
|
||||
```
|
||||
https://cloud.lemonlink.eu
|
||||
```
|
||||
|
||||
You should see the Nextcloud login page! 🎉
|
||||
|
||||
## What You Got
|
||||
|
||||
| Service | URL | Purpose |
|
||||
|---------|-----|---------|
|
||||
| Nextcloud | `https://cloud.lemonlink.eu` | Family file sharing |
|
||||
| Authelia | `https://auth.lemonlink.eu` | SSO login portal |
|
||||
| Traefik | `https://traefik.local.lemonlink.eu` | Reverse proxy dashboard |
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Add family to Authelia**: Edit `authelia/users_database.yml` → push → "Pull and redeploy"
|
||||
2. **Add more services**: Edit `docker-compose.yml` → add routers → push → redeploy
|
||||
3. **Enable monitoring**: Uncomment monitoring profile in compose
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Fix |
|
||||
|---------|-----|
|
||||
| "CF_API_EMAIL not set" | Check environment variables in Portainer |
|
||||
| "502 Bad Gateway" | Verify TRUENAS_IP and PORT are correct |
|
||||
| "Untrusted domain" | Check TrueNAS Nextcloud env vars |
|
||||
| No SSL certificate | Check Cloudflare API key and DNS records |
|
||||
|
||||
## Full Documentation
|
||||
|
||||
- **[PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)** - Detailed Portainer guide
|
||||
- **[SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md)** - TrueNAS specifics
|
||||
- **[GIT-REPO-SETUP.md](GIT-REPO-SETUP.md)** - Git workflow
|
||||
- **[MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md)** - If migrating from NPM
|
||||
|
||||
---
|
||||
|
||||
**You're done!** Family can now access Nextcloud at `https://cloud.lemonlink.eu`
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
# LemonSec 🍋
|
||||
|
||||
A comprehensive, security-focused reverse proxy and authentication stack for homelab and small business deployments.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet → Cloudflare → Traefik → {Authelia → Your Services}
|
||||
↓
|
||||
CrowdSec (threat detection)
|
||||
↓
|
||||
AdGuard Home (internal DNS)
|
||||
```
|
||||
|
||||
### For TrueNAS Scale Users
|
||||
If you run Nextcloud on TrueNAS Scale (separate VM), LemonSec acts as the secure gateway:
|
||||
|
||||
```
|
||||
Internet → Cloudflare → Traefik (Proxmox VM) → TrueNAS Nextcloud
|
||||
↓
|
||||
Authelia (family SSO)
|
||||
```
|
||||
|
||||
See [SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md) for step-by-step guide.
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Purpose | External | Internal |
|
||||
|-----------|---------|----------|----------|
|
||||
| **Traefik** | Reverse Proxy & Load Balancer | ✅ 443/80 | ✅ 8443 |
|
||||
| **Authelia** | SSO & 2FA Authentication | ✅ auth.lemonlink.eu | ✅ |
|
||||
| **CrowdSec** | Intrusion Detection/Prevention | ✅ | ✅ |
|
||||
| **AdGuard Home** | DNS + Ad Blocking | ❌ | ✅ DNS |
|
||||
| **Redis** | Session Storage | ❌ | ✅ |
|
||||
|
||||
## Network Segmentation
|
||||
|
||||
### External Network (`traefik-external`)
|
||||
- **Entry**: Cloudflare (orange cloud)
|
||||
- **Security**: CrowdSec + Authelia 2FA
|
||||
- **Examples**: nextcloud.lemonlink.eu, vault.lemonlink.eu
|
||||
|
||||
### Internal Network (`traefik-internal`)
|
||||
- **Entry**: Tailscale/VPN only
|
||||
- **Security**: Authelia (one_factor on trusted networks)
|
||||
- **Examples**: traefik.local.lemonlink.eu, portainer.local.lemonlink.eu
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 🚀 Deploy via Portainer (Recommended for Proxmox)
|
||||
|
||||
The easiest way to deploy on Proxmox with Portainer:
|
||||
|
||||
1. **Push to Git**:
|
||||
```bash
|
||||
git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
2. **In Portainer**:
|
||||
- **Stacks** → **Add Stack** → **Repository**
|
||||
- URL: `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
- Add environment variables from `stack.env`
|
||||
- **Deploy**
|
||||
|
||||
**Full Portainer guide**: [PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)
|
||||
|
||||
### 🚀 TrueNAS + Nextcloud Quick Start
|
||||
|
||||
If your main goal is securing TrueNAS Nextcloud for family:
|
||||
|
||||
```bash
|
||||
# 1. Clone and configure
|
||||
cd LemonSec
|
||||
cp .env.example .env
|
||||
# Edit: Add Cloudflare API, TrueNAS_IP, Nextcloud port
|
||||
|
||||
# 2. Setup
|
||||
./setup.ps1 # or create secrets manually
|
||||
|
||||
# 3. Deploy
|
||||
docker-compose up -d
|
||||
docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d
|
||||
|
||||
# 4. Configure TrueNAS Nextcloud
|
||||
# Apps → Nextcloud → Add environment variables
|
||||
# NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
# OVERWRITEPROTOCOL=https
|
||||
# TRUSTED_PROXIES=YOUR_PROXMOX_VM_IP
|
||||
```
|
||||
|
||||
**Full guide**: [SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md)
|
||||
|
||||
### Migrating from Nginx Proxy Manager?
|
||||
|
||||
See [MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md) for side-by-side migration guide.
|
||||
|
||||
---
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- Docker & Docker Compose installed
|
||||
- Domain managed by Cloudflare
|
||||
- Tailscale installed on server
|
||||
- Ports 80/443 forwarded to Proxmox server
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
```bash
|
||||
# Clone and setup
|
||||
cd LemonSec
|
||||
|
||||
# Copy and edit environment
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
|
||||
# Run setup script (generates secrets)
|
||||
./setup.ps1 # On Windows
|
||||
# or
|
||||
bash setup.sh # On Linux
|
||||
```
|
||||
|
||||
### 3. Cloudflare Setup
|
||||
|
||||
See [CLOUDFLARE.md](docs/CLOUDFLARE.md) for detailed DNS configuration.
|
||||
|
||||
Quick checklist:
|
||||
- [ ] Create API token with DNS:Edit permissions
|
||||
- [ ] Add A record pointing to your server IP
|
||||
- [ ] Enable orange cloud (proxy)
|
||||
- [ ] Set SSL/TLS mode to "Full (strict)"
|
||||
- [ ] Enable "Always Use HTTPS"
|
||||
|
||||
### 4. Deploy
|
||||
|
||||
```bash
|
||||
# Start core services
|
||||
docker-compose up -d
|
||||
|
||||
# Check Traefik is working
|
||||
docker-compose logs -f traefik
|
||||
|
||||
# Generate CrowdSec API key
|
||||
docker-compose exec crowdsec cscli bouncers add traefik-bouncer
|
||||
# Add key to .env, then restart
|
||||
docker-compose up -d
|
||||
|
||||
# Setup Authelia users
|
||||
docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD'
|
||||
# Add hash to authelia/users_database.yml
|
||||
```
|
||||
|
||||
### 5. Add Your Services
|
||||
|
||||
See [examples/](examples/) for service templates.
|
||||
|
||||
```yaml
|
||||
# docker-compose.override.yml or separate file
|
||||
services:
|
||||
myapp:
|
||||
image: myapp:latest
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.myapp.rule=Host(`myapp.lemonlink.eu`)"
|
||||
- "traefik.http.routers.myapp.entrypoints=websecure"
|
||||
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.myapp.middlewares=authelia@docker"
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
### 1. Multi-Factor Authentication
|
||||
- TOTP (Google Authenticator, Authy, etc.)
|
||||
- WebAuthn/FIDO2 (YubiKey, Windows Hello, etc.)
|
||||
- Duo Push (with configuration)
|
||||
|
||||
### 2. Threat Detection (CrowdSec)
|
||||
- Real-time log analysis
|
||||
- Community blocklists
|
||||
- Automatic IP banning
|
||||
- Metrics and alerts
|
||||
|
||||
### 3. Network Isolation
|
||||
- Internal services inaccessible from internet
|
||||
- External services protected by auth
|
||||
- Docker network segmentation
|
||||
|
||||
### 4. TLS/SSL
|
||||
- Automatic Let's Encrypt certificates
|
||||
- Wildcard certificates for subdomains
|
||||
- Modern TLS 1.3 configuration
|
||||
|
||||
### 5. Security Headers
|
||||
- HSTS (1 year)
|
||||
- CSP (Content Security Policy)
|
||||
- X-Frame-Options, X-Content-Type-Options
|
||||
- Referrer-Policy
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
LemonSec/
|
||||
├── docker-compose.yml # Main stack
|
||||
├── .env # Your secrets (gitignored)
|
||||
├── .env.example # Template
|
||||
├── setup.ps1 # Windows setup
|
||||
├── traefik/
|
||||
│ ├── traefik.yml # Static config
|
||||
│ ├── dynamic/ # Dynamic config
|
||||
│ │ ├── middlewares.yml # Security middlewares
|
||||
│ │ └── tls.yml # TLS options
|
||||
│ ├── crowdsec/ # CrowdSec bouncer config
|
||||
│ └── logs/ # Access logs
|
||||
├── authelia/
|
||||
│ ├── configuration.yml # Authelia settings
|
||||
│ └── users_database.yml # User accounts
|
||||
├── crowdsec/
|
||||
│ └── acquis.yaml # Log sources
|
||||
├── adguard/ # AdGuard data (volumes)
|
||||
├── monitoring/ # Prometheus/Grafana configs
|
||||
├── examples/ # Service examples
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Common Operations
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# All services
|
||||
docker-compose logs -f
|
||||
|
||||
# Specific service
|
||||
docker-compose logs -f traefik
|
||||
|
||||
# CrowdSec decisions
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# CrowdSec metrics
|
||||
docker-compose exec crowdsec cscli metrics
|
||||
```
|
||||
|
||||
### Update
|
||||
```bash
|
||||
# Pull latest images
|
||||
docker-compose pull
|
||||
|
||||
# Recreate containers
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Backup
|
||||
```bash
|
||||
# Backup volumes
|
||||
docker run --rm -v lemonsec_authelia-data:/data -v $(pwd):/backup alpine tar czf /backup/authelia-backup.tar.gz -C /data .
|
||||
docker run --rm -v lemonsec_crowdsec-data:/data -v $(pwd):/backup alpine tar czf /backup/crowdsec-backup.tar.gz -C /data .
|
||||
|
||||
# Backup configs
|
||||
tar czf lemonsec-config-backup.tar.gz traefik/ authelia/ crowdsec/ .env
|
||||
```
|
||||
|
||||
### Add User to Authelia
|
||||
```bash
|
||||
# Generate password hash
|
||||
docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'NEWPASSWORD'
|
||||
|
||||
# Edit authelia/users_database.yml
|
||||
# Add user entry with hash
|
||||
# Restart authelia
|
||||
docker-compose restart authelia
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Traefik not getting certificates
|
||||
1. Check Cloudflare API credentials in `.env`
|
||||
2. Verify DNS records point to your IP
|
||||
3. Check Traefik logs: `docker-compose logs traefik`
|
||||
4. Test with staging first: change `certresolver` to `letsencrypt-staging`
|
||||
|
||||
### Authelia redirect loops
|
||||
1. Check `session.domain` matches your domain
|
||||
2. Verify cookies aren't blocked
|
||||
3. Check time sync on server: `ntpdate -s time.cloudflare.com`
|
||||
|
||||
### CrowdSec blocking legitimate traffic
|
||||
```bash
|
||||
# Check decisions
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# Remove false positive
|
||||
docker-compose exec crowdsec cscli decisions delete --ip YOUR_IP
|
||||
|
||||
# Add whitelist
|
||||
docker-compose exec crowdsec cscli parsers install crowdsecurity/whitelists
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] Change all default passwords
|
||||
- [ ] Enable 2FA for all users
|
||||
- [ ] Set up email notifications for Authelia
|
||||
- [ ] Configure CrowdSec notifications
|
||||
- [ ] Review Cloudflare security settings
|
||||
- [ ] Enable fail2ban on host (optional)
|
||||
- [ ] Regular backups
|
||||
- [ ] Update images monthly
|
||||
|
||||
## License
|
||||
|
||||
MIT - Use at your own risk. This is a homelab security stack, not enterprise-grade.
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
# Quick Setup: TrueNAS Nextcloud + LemonSec
|
||||
|
||||
## Your Setup
|
||||
- ✅ Proxmox VM running Docker/Portainer
|
||||
- ✅ TrueNAS Scale VM running Nextcloud
|
||||
- ✅ Need: Family access to Nextcloud via secure domain
|
||||
|
||||
## Timeline: 15 minutes to working Nextcloud
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Prepare (5 min)
|
||||
|
||||
### 1. Get Your IPs
|
||||
```bash
|
||||
# On Proxmox VM (where LemonSec will run)
|
||||
ip addr show | grep "inet " | head -3
|
||||
# Note: e.g., 192.168.1.50
|
||||
|
||||
# On TrueNAS Scale VM
|
||||
# Check TrueNAS UI or: ip addr
|
||||
# Note: e.g., 192.168.1.100
|
||||
|
||||
# Get Nextcloud port in TrueNAS
|
||||
# Apps → Nextcloud → Note the Node Port (e.g., 9001)
|
||||
```
|
||||
|
||||
### 2. Configure Environment
|
||||
```bash
|
||||
cd LemonSec
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
Fill in:
|
||||
```
|
||||
CF_API_EMAIL=youremail@example.com
|
||||
CF_API_KEY=your-cloudflare-global-api-key
|
||||
TZ=Europe/Stockholm
|
||||
TRUENAS_IP=192.168.1.100
|
||||
TRUENAS_NEXTCLOUD_PORT=9001
|
||||
```
|
||||
|
||||
### 3. Generate Secrets
|
||||
```bash
|
||||
# PowerShell (on Windows)
|
||||
$jwt = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
|
||||
$jwt | Set-Content secrets/authelia_jwt_secret.txt -NoNewline
|
||||
|
||||
$session = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
|
||||
$session | Set-Content secrets/authelia_session_secret.txt -NoNewline
|
||||
|
||||
$storage = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
|
||||
$storage | Set-Content secrets/authelia_storage_key.txt -NoNewline
|
||||
|
||||
# Or on Linux:
|
||||
# openssl rand -hex 32 > secrets/authelia_jwt_secret.txt
|
||||
# openssl rand -hex 32 > secrets/authelia_session_secret.txt
|
||||
# openssl rand -hex 32 > secrets/authelia_storage_key.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Deploy Core (5 min)
|
||||
|
||||
### 1. Start LemonSec
|
||||
```bash
|
||||
docker-compose up -d
|
||||
|
||||
# Check logs
|
||||
docker-compose logs -f traefik
|
||||
# Wait for: "Configuration loaded from files..."
|
||||
# Press Ctrl+C when stable
|
||||
```
|
||||
|
||||
### 2. Setup CrowdSec
|
||||
```bash
|
||||
docker-compose exec crowdsec cscli bouncers add traefik-bouncer
|
||||
# Copy the API key
|
||||
|
||||
# Edit .env, add:
|
||||
# CROWDSEC_API_KEY=paste-key-here
|
||||
|
||||
# Restart
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 3. Start External Routing
|
||||
```bash
|
||||
docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Cloudflare DNS (3 min)
|
||||
|
||||
Login to [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||
|
||||
### Add DNS Records
|
||||
| Type | Name | Target | Proxy |
|
||||
|------|------|--------|-------|
|
||||
| A | cloud | YOUR_PROXMOX_PUBLIC_IP | 🟠 |
|
||||
| A | auth | YOUR_PROXMOX_PUBLIC_IP | 🟠 |
|
||||
| A | * | YOUR_PROXMOX_PUBLIC_IP | 🟠 |
|
||||
|
||||
### SSL/TLS Settings
|
||||
- **SSL/TLS encryption**: Full (strict)
|
||||
- **Always Use HTTPS**: ON
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: TrueNAS Nextcloud Config (2 min)
|
||||
|
||||
### In TrueNAS Scale:
|
||||
|
||||
1. **Apps** → **Installed Applications** → **Nextcloud** → **Edit**
|
||||
2. **Add Environment Variables**:
|
||||
```
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
OVERWRITEPROTOCOL=https
|
||||
OVERWRITEHOST=cloud.lemonlink.eu
|
||||
OVERWRITECLIURL=https://cloud.lemonlink.eu
|
||||
TRUSTED_PROXIES=192.168.1.50
|
||||
```
|
||||
(Replace 192.168.1.50 with your Proxmox VM IP)
|
||||
|
||||
3. **Save** and wait for app to update
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Test (Immediately)
|
||||
|
||||
### 1. Test Nextcloud
|
||||
```bash
|
||||
# From anywhere
|
||||
curl -I https://cloud.lemonlink.eu
|
||||
# Should return 200 or redirect to login
|
||||
```
|
||||
|
||||
### 2. Access Web UI
|
||||
Open: `https://cloud.lemonlink.eu`
|
||||
|
||||
You should see Nextcloud login page!
|
||||
|
||||
### 3. Create Family Accounts
|
||||
Login as admin → **Users** → **Create** for each family member
|
||||
|
||||
---
|
||||
|
||||
## Optional: Add Authelia Protection
|
||||
|
||||
If you want extra login security before Nextcloud:
|
||||
|
||||
### Edit docker-compose.external.yml
|
||||
```yaml
|
||||
# Change this line:
|
||||
- "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file"
|
||||
# From: (no authelia)
|
||||
# - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file"
|
||||
```
|
||||
|
||||
### Restart
|
||||
```bash
|
||||
docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d
|
||||
```
|
||||
|
||||
### Setup Authelia User
|
||||
```bash
|
||||
# Generate password hash
|
||||
docker run --rm authelia/authelia:latest \
|
||||
authelia crypto hash generate argon2 \
|
||||
--password 'FamilyPassword123!'
|
||||
|
||||
# Edit authelia/users_database.yml
|
||||
# Add family members with the hash
|
||||
|
||||
# Restart authelia
|
||||
docker-compose restart authelia
|
||||
```
|
||||
|
||||
Now family logs in to Authelia first, then Nextcloud.
|
||||
|
||||
---
|
||||
|
||||
## For Family Members
|
||||
|
||||
Send them this info:
|
||||
|
||||
```
|
||||
🍋 Your Nextcloud Access
|
||||
|
||||
URL: https://cloud.lemonlink.eu
|
||||
|
||||
Login with your credentials (created by admin)
|
||||
|
||||
Mobile Apps:
|
||||
- iOS: App Store → "Nextcloud"
|
||||
- Android: Play Store → "Nextcloud"
|
||||
- Desktop: nextcloud.com/install
|
||||
|
||||
Server address in apps: https://cloud.lemonlink.eu
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Access through untrusted domain"
|
||||
```bash
|
||||
# Shell into TrueNAS
|
||||
k3s kubectl exec -it -n ix-nextcloud deployment/ix-nextcloud -- /bin/sh
|
||||
|
||||
# Check config
|
||||
cat /var/www/html/config/config.php | grep trusted
|
||||
|
||||
# Should include 'cloud.lemonlink.eu'
|
||||
```
|
||||
|
||||
### "502 Bad Gateway"
|
||||
- Check TrueNAS IP and port in .env
|
||||
- Verify Nextcloud app is running in TrueNAS
|
||||
- Test direct access: `curl http://TRUENAS_IP:PORT`
|
||||
|
||||
### "Too Many Redirects"
|
||||
- Ensure OVERWRITEPROTOCOL=https is set
|
||||
- Check Cloudflare SSL mode is "Full (strict)"
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (After Nextcloud Works)
|
||||
|
||||
1. ✅ **Backup** - Set up automatic backups
|
||||
2. ✅ **Monitoring** - Enable `--profile monitoring`
|
||||
3. ✅ **More Services** - Add Portainer, etc.
|
||||
4. ✅ **Security** - Review `docs/SECURITY.md`
|
||||
|
||||
---
|
||||
|
||||
## Files You Modified
|
||||
|
||||
Keep backups of these:
|
||||
- `.env` - Your secrets and IPs
|
||||
- `authelia/users_database.yml` - Family logins
|
||||
- `docker-compose.external.yml` - Service routing
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
# LemonSec Setup Guide
|
||||
|
||||
Complete setup instructions for different deployment methods.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Portainer Git Deployment](#portainer-git-deployment-recommended) (Recommended)
|
||||
2. [Docker Compose Manual](#docker-compose-manual)
|
||||
3. [TrueNAS Nextcloud Specifics](#truenas-nextcloud-setup)
|
||||
4. [Migration from NPM](#migration-from-nginx-proxy-manager)
|
||||
|
||||
---
|
||||
|
||||
## Portainer Git Deployment (Recommended)
|
||||
|
||||
Best for Proxmox users with Portainer already installed.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Portainer running on Proxmox VM
|
||||
- Git repository at `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
- Cloudflare account
|
||||
- TrueNAS Scale with Nextcloud
|
||||
|
||||
### Quick Steps
|
||||
|
||||
1. **Push code to Git**:
|
||||
```bash
|
||||
cd LemonSec
|
||||
git init
|
||||
git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
2. **Generate Secrets**:
|
||||
```bash
|
||||
openssl rand -hex 32 # Run 3 times
|
||||
```
|
||||
Save the 3 outputs for AUTHELIA_*_SECRET variables.
|
||||
|
||||
3. **Deploy in Portainer**:
|
||||
- Stacks → Add Stack → Repository
|
||||
- URL: `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
- Add environment variables (see below)
|
||||
- Deploy
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `CF_API_EMAIL` | Cloudflare email | `you@example.com` |
|
||||
| `CF_API_KEY` | Cloudflare API key | `global-api-key` |
|
||||
| `TRUENAS_IP` | TrueNAS VM IP | `192.168.1.100` |
|
||||
| `TRUENAS_NEXTCLOUD_PORT` | Nextcloud port | `9001` |
|
||||
| `AUTHELIA_JWT_SECRET` | Random hex string | `openssl rand -hex 32` |
|
||||
| `AUTHELIA_SESSION_SECRET` | Random hex string | `openssl rand -hex 32` |
|
||||
| `AUTHELIA_STORAGE_KEY` | Random hex string | `openssl rand -hex 32` |
|
||||
|
||||
**Full detailed guide**: [PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)
|
||||
|
||||
---
|
||||
|
||||
## Docker Compose Manual
|
||||
|
||||
For systems without Portainer or for development.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- Git
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Clone repository**:
|
||||
```bash
|
||||
git clone https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
cd LemonSec
|
||||
```
|
||||
|
||||
2. **Configure environment**:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
3. **Generate secrets**:
|
||||
```bash
|
||||
mkdir -p secrets
|
||||
openssl rand -hex 32 > secrets/authelia_jwt_secret.txt
|
||||
openssl rand -hex 32 > secrets/authelia_session_secret.txt
|
||||
openssl rand -hex 32 > secrets/authelia_storage_key.txt
|
||||
```
|
||||
|
||||
4. **Deploy**:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
5. **Setup CrowdSec**:
|
||||
```bash
|
||||
docker-compose exec crowdsec cscli bouncers add traefik-bouncer
|
||||
# Add key to .env and restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TrueNAS Nextcloud Setup
|
||||
|
||||
If your Nextcloud runs on TrueNAS Scale (separate VM).
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
Internet → Cloudflare → Traefik (Proxmox) → TrueNAS (Nextcloud)
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
1. **Get TrueNAS Info**:
|
||||
- TrueNAS IP: `192.168.1.100`
|
||||
- Nextcloud Port: Check Apps → Nextcloud (e.g., `9001`)
|
||||
|
||||
2. **Set environment variables**:
|
||||
```
|
||||
TRUENAS_IP=192.168.1.100
|
||||
TRUENAS_NEXTCLOUD_PORT=9001
|
||||
```
|
||||
|
||||
3. **Configure TrueNAS Nextcloud**:
|
||||
In TrueNAS → Apps → Nextcloud → Edit, add:
|
||||
```
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
OVERWRITEPROTOCOL=https
|
||||
OVERWRITEHOST=cloud.lemonlink.eu
|
||||
TRUSTED_PROXIES=192.168.1.50 # Your Proxmox VM IP
|
||||
```
|
||||
|
||||
4. **DNS**:
|
||||
Cloudflare → DNS:
|
||||
- A record: `cloud` → YOUR_PUBLIC_IP (orange cloud)
|
||||
|
||||
5. **Test**:
|
||||
Visit `https://cloud.lemonlink.eu`
|
||||
|
||||
**Detailed guide**: [SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md)
|
||||
|
||||
---
|
||||
|
||||
## Migration from Nginx Proxy Manager
|
||||
|
||||
If you're currently using NPM and want to migrate to LemonSec.
|
||||
|
||||
### Strategy
|
||||
|
||||
1. **Parallel running** (recommended):
|
||||
- Deploy LemonSec alongside NPM
|
||||
- Migrate services one by one
|
||||
- Test thoroughly
|
||||
- Only then remove NPM
|
||||
|
||||
2. **Direct migration**:
|
||||
- Export NPM config
|
||||
- Convert to Traefik format
|
||||
- Deploy and verify
|
||||
|
||||
### Migration Mapping
|
||||
|
||||
| NPM | Traefik |
|
||||
|-----|---------|
|
||||
| Proxy Hosts | Labels on router containers |
|
||||
| SSL Certificates | Automatic via certresolver |
|
||||
| Access Lists | Authelia middleware |
|
||||
| Custom Locations | Path rules |
|
||||
| Redirection Hosts | Redirect middleware |
|
||||
|
||||
**Full migration guide**: [MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md)
|
||||
|
||||
---
|
||||
|
||||
## Post-Deployment Checklist
|
||||
|
||||
- [ ] Nextcloud accessible at `https://cloud.lemonlink.eu`
|
||||
- [ ] SSL certificate valid (green lock)
|
||||
- [ ] Family can login with Nextcloud credentials
|
||||
- [ ] Mobile apps sync successfully
|
||||
- [ ] CrowdSec showing decisions (`docker exec crowdsec cscli decisions list`)
|
||||
- [ ] Authelia accessible at `https://auth.lemonlink.eu`
|
||||
- [ ] Backups configured
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "CF_API_EMAIL not set"
|
||||
→ Check environment variables in Portainer UI
|
||||
|
||||
### "502 Bad Gateway" for Nextcloud
|
||||
→ Verify TRUENAS_IP and TRUENAS_NEXTCLOUD_PORT
|
||||
→ Test: `curl http://TRUENAS_IP:PORT` from Proxmox VM
|
||||
|
||||
### "Access through untrusted domain"
|
||||
→ Add domain to TrueNAS Nextcloud environment variables
|
||||
|
||||
### SSL Certificate issues
|
||||
→ Check Cloudflare API credentials
|
||||
→ Verify DNS records exist in Cloudflare
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Add family to Authelia** (if using SSO)
|
||||
2. **Enable monitoring** (Prometheus/Grafana)
|
||||
3. **Add more services** (Vaultwarden, etc.)
|
||||
4. **Configure backups**
|
||||
5. **Review security settings**
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# LemonSec - Quick Reference
|
||||
|
||||
## 🎯 Your Setup
|
||||
- **Proxmox VM**: Docker/Portainer + LemonSec stack
|
||||
- **TrueNAS Scale VM**: Nextcloud app
|
||||
- **Goal**: Secure family access to Nextcloud via `cloud.lemonlink.eu`
|
||||
- **Deploy Method**: Portainer Git Repository
|
||||
|
||||
## 🚀 Deploy via Portainer (5 min)
|
||||
|
||||
### 1. Push to Git
|
||||
```bash
|
||||
cd LemonSec
|
||||
git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git
|
||||
git add .
|
||||
git commit -m "Initial deployment"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### 2. Portainer UI
|
||||
- **Stacks** → **Add Stack** → **Repository**
|
||||
- **URL**: `https://git.lemonlink.eu/impulsivefps/LemonSec`
|
||||
- **Compose Path**: `docker-compose.yml`
|
||||
|
||||
### 3. Environment Variables
|
||||
Copy from `stack.env` and fill in:
|
||||
|
||||
| Variable | Value |
|
||||
|----------|-------|
|
||||
| `CF_API_EMAIL` | your@email.com |
|
||||
| `CF_API_KEY` | Cloudflare API key |
|
||||
| `TRUENAS_IP` | 192.168.1.100 |
|
||||
| `TRUENAS_NEXTCLOUD_PORT` | 9001 |
|
||||
| `AUTHELIA_JWT_SECRET` | `openssl rand -hex 32` |
|
||||
| `AUTHELIA_SESSION_SECRET` | `openssl rand -hex 32` |
|
||||
| `AUTHELIA_STORAGE_KEY` | `openssl rand -hex 32` |
|
||||
|
||||
### 4. Deploy
|
||||
Click **Deploy the stack**
|
||||
|
||||
### 5. Setup CrowdSec
|
||||
```bash
|
||||
docker exec crowdsec cscli bouncers add traefik-bouncer
|
||||
# Copy key, add to Portainer env vars, redeploy
|
||||
```
|
||||
|
||||
### 6. TrueNAS Config
|
||||
TrueNAS → Apps → Nextcloud → Edit, add env:
|
||||
```
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
OVERWRITEPROTOCOL=https
|
||||
TRUSTED_PROXIES=192.168.1.50 # Proxmox VM IP
|
||||
```
|
||||
|
||||
### 7. Cloudflare DNS
|
||||
- A record: `cloud` → YOUR_PUBLIC_IP (orange cloud)
|
||||
|
||||
### Done!
|
||||
Visit: `https://cloud.lemonlink.eu` ✅
|
||||
|
||||
**Full guide**: [PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Repository Structure
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `docker-compose.yml` | Main stack - Traefik, Authelia, CrowdSec, Nextcloud router |
|
||||
| `stack.env` | Environment variable template for Portainer |
|
||||
| `traefik/` | Traefik configuration files |
|
||||
| `authelia/` | Authelia config and user database |
|
||||
| `crowdsec/` | CrowdSec acquisition config |
|
||||
|
||||
## 🔧 Customization
|
||||
|
||||
### Add Family to Authelia
|
||||
Edit `authelia/users_database.yml` → push → Portainer "Pull and redeploy"
|
||||
|
||||
### Add More Services
|
||||
Edit `docker-compose.yml` → add router container → push → redeploy
|
||||
|
||||
### Update Stack
|
||||
1. Edit files locally
|
||||
2. `git commit -am "Update" && git push`
|
||||
3. Portainer → Stacks → lemonsec → "Pull and redeploy"
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **[PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)** - Detailed Portainer deployment
|
||||
- **[SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md)** - TrueNAS specific setup
|
||||
- **[MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md)** - NPM migration guide
|
||||
- **[docs/CLOUDFLARE.md](docs/CLOUDFLARE.md)** - DNS/SSL configuration
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| "CF_API_EMAIL not set" | Check env vars in Portainer UI |
|
||||
| "502 Bad Gateway" | Verify TRUENAS_IP and PORT |
|
||||
| "Untrusted domain" | Add domain to TrueNAS Nextcloud env |
|
||||
| SSL errors | Check Cloudflare API credentials |
|
||||
|
||||
## ✅ Success Checklist
|
||||
|
||||
- [ ] `https://cloud.lemonlink.eu` loads Nextcloud
|
||||
- [ ] Family can login with Nextcloud accounts
|
||||
- [ ] Mobile apps work
|
||||
- [ ] SSL certificate valid
|
||||
- [ ] CrowdSec shows decisions
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
# Authelia Configuration
|
||||
# Full reference: https://www.authelia.com/configuration/
|
||||
|
||||
server:
|
||||
address: tcp://0.0.0.0:9091
|
||||
endpoints:
|
||||
enable_pprof: false
|
||||
enable_expvars: false
|
||||
disable_healthcheck: false
|
||||
tls:
|
||||
key: ''
|
||||
certificate: ''
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: text
|
||||
file_path: /config/authelia.log
|
||||
keep_stdout: true
|
||||
|
||||
telemetry:
|
||||
metrics:
|
||||
enabled: false
|
||||
|
||||
totp:
|
||||
issuer: lemonlink.eu
|
||||
algorithm: sha1
|
||||
digits: 6
|
||||
period: 30
|
||||
skew: 1
|
||||
secret_size: 32
|
||||
|
||||
webauthn:
|
||||
disable: false
|
||||
display_name: LemonLink
|
||||
attestation_conveyance_preference: indirect
|
||||
user_verification: preferred
|
||||
timeout: 60s
|
||||
|
||||
ntp:
|
||||
address: "time.cloudflare.com:123"
|
||||
version: 4
|
||||
max_desync: 3s
|
||||
disable_startup_check: false
|
||||
disable_failure: false
|
||||
|
||||
authentication_backend:
|
||||
password_reset:
|
||||
disable: false
|
||||
custom_url: ''
|
||||
|
||||
# File-based user database (for small deployments)
|
||||
# For LDAP/Active Directory, see: https://www.authelia.com/configuration/authentication/ldap/
|
||||
file:
|
||||
path: /users_database.yml
|
||||
watch: true
|
||||
search:
|
||||
email: true
|
||||
case_insensitive: false
|
||||
password:
|
||||
algorithm: argon2id
|
||||
argon2id:
|
||||
variant: argon2id
|
||||
iterations: 3
|
||||
memory: 65536
|
||||
parallelism: 4
|
||||
key_length: 32
|
||||
salt_length: 16
|
||||
|
||||
password_policy:
|
||||
standard:
|
||||
enabled: false
|
||||
min_length: 8
|
||||
max_length: 0
|
||||
require_uppercase: true
|
||||
require_lowercase: true
|
||||
require_number: true
|
||||
require_special: true
|
||||
zxcvbn:
|
||||
enabled: true
|
||||
min_score: 3
|
||||
|
||||
access_control:
|
||||
default_policy: two_factor
|
||||
networks:
|
||||
- name: internal
|
||||
networks:
|
||||
- 100.64.0.0/10 # Tailscale
|
||||
- 10.0.0.0/8 # Private
|
||||
- 172.16.0.0/12 # Private
|
||||
- 192.168.0.0/16 # Private
|
||||
- fc00::/7 # IPv6 ULA
|
||||
|
||||
rules:
|
||||
# Public endpoints (no auth)
|
||||
- domain:
|
||||
- "auth.lemonlink.eu"
|
||||
policy: bypass
|
||||
|
||||
# External services - strict 2FA
|
||||
- domain:
|
||||
- "*.lemonlink.eu"
|
||||
policy: two_factor
|
||||
|
||||
# Internal services - can use one_factor on trusted networks
|
||||
- domain:
|
||||
- "*.local.lemonlink.eu"
|
||||
policy: one_factor
|
||||
networks:
|
||||
- internal
|
||||
|
||||
# Internal services from external - require 2FA
|
||||
- domain:
|
||||
- "*.local.lemonlink.eu"
|
||||
policy: two_factor
|
||||
|
||||
session:
|
||||
name: authelia_session
|
||||
domain: lemonlink.eu
|
||||
same_site: lax
|
||||
secret: ${AUTHELIA_SESSION_SECRET} # Set via environment variable
|
||||
expiration: 1h
|
||||
inactivity: 5m
|
||||
remember_me_duration: 1M
|
||||
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
database_index: 0
|
||||
maximum_active_connections: 8
|
||||
minimum_idle_connections: 0
|
||||
tls:
|
||||
enabled: false
|
||||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
find_time: 2m
|
||||
ban_time: 5m
|
||||
|
||||
# CrowdSec integration
|
||||
# crowdsec:
|
||||
# enabled: true
|
||||
# host: crowdsec
|
||||
# port: 8080
|
||||
# key: ${CROWDSEC_API_KEY}
|
||||
|
||||
storage:
|
||||
encryption_key: ${AUTHELIA_STORAGE_KEY} # Set via environment variable
|
||||
local:
|
||||
path: /config/db.sqlite3
|
||||
|
||||
notifier:
|
||||
disable_startup_check: false
|
||||
|
||||
# SMTP configuration for email notifications
|
||||
# smtp:
|
||||
# address: smtp.gmail.com:587
|
||||
# timeout: 5s
|
||||
# username: your-email@gmail.com
|
||||
# password: your-app-password
|
||||
# sender: "Authelia <auth@lemonlink.eu>"
|
||||
# identifier: lemonlink.eu
|
||||
# subject: "[Authelia] {title}"
|
||||
# startup_check_address: test@authelia.com
|
||||
# disable_require_tls: false
|
||||
# disable_html_emails: false
|
||||
# tls:
|
||||
# skip_verify: false
|
||||
# minimum_version: TLS1.2
|
||||
|
||||
identity_providers:
|
||||
oidc:
|
||||
cors:
|
||||
endpoints:
|
||||
- authorization
|
||||
- token
|
||||
- revocation
|
||||
- introspection
|
||||
allowed_origins: []
|
||||
allowed_origins_from_client_redirect_uris: false
|
||||
|
||||
# Clients configuration
|
||||
clients:
|
||||
# Grafana OAuth
|
||||
- client_id: grafana
|
||||
client_name: Grafana
|
||||
client_secret: ${GRAFANA_OAUTH_SECRET}
|
||||
public: false
|
||||
authorization_policy: two_factor
|
||||
require_pkce: true
|
||||
pkce_challenge_method: S256
|
||||
redirect_uris:
|
||||
- https://grafana.local.lemonlink.eu/login/generic_oauth
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- groups
|
||||
- email
|
||||
userinfo_signed_response_alg: none
|
||||
|
||||
# Add more clients as needed
|
||||
# - client_id: nextcloud
|
||||
# client_name: Nextcloud
|
||||
# ...
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Authelia Users Database
|
||||
# Generate passwords with: docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD'
|
||||
|
||||
users:
|
||||
admin:
|
||||
displayname: "Administrator"
|
||||
password: "$argon2id$v=19$m=65536,t=3,p=4$VEVNUExBQ0VIRUxMT1dPVUJE$EXAMPLEHASHCHANGE" # Change this!
|
||||
email: admin@lemonlink.eu
|
||||
groups:
|
||||
- admins
|
||||
- users
|
||||
|
||||
# Add more users here
|
||||
# username:
|
||||
# displayname: "Full Name"
|
||||
# password: "hashed_password"
|
||||
# email: user@example.com
|
||||
# groups:
|
||||
# - users
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# CrowdSec acquisition configuration
|
||||
# This tells CrowdSec where to find logs to analyze
|
||||
|
||||
filenames:
|
||||
# Traefik access logs
|
||||
- /var/log/traefik/access.log
|
||||
# Traefik application logs
|
||||
- /var/log/traefik/traefik.log
|
||||
labels:
|
||||
type: traefik
|
||||
|
||||
---
|
||||
# System authentication logs (if available)
|
||||
filenames:
|
||||
- /var/log/auth.log
|
||||
labels:
|
||||
type: syslog
|
||||
|
||||
---
|
||||
# Journald (if running on host)
|
||||
journald_filter:
|
||||
- "SYSLOG_IDENTIFIER=sshd"
|
||||
labels:
|
||||
type: syslog
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# External Services Router
|
||||
# For services running on other VMs (TrueNAS, etc.)
|
||||
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
services:
|
||||
external: true # Connect to main LemonSec network
|
||||
|
||||
services:
|
||||
# ============================================================================
|
||||
# EXTERNAL NEXTCLOUD (on TrueNAS Scale)
|
||||
# ============================================================================
|
||||
# This is just a "dummy" container that tells Traefik how to route to TrueNAS
|
||||
# No actual container runs - it's just configuration
|
||||
|
||||
nextcloud-router:
|
||||
image: alpine:latest
|
||||
container_name: nextcloud-router
|
||||
restart: "no"
|
||||
command: "echo 'This is a routing configuration container'"
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
|
||||
# EXTERNAL ACCESS - Public URL for family
|
||||
- "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)"
|
||||
- "traefik.http.routers.nextcloud.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
||||
|
||||
# Service points to TrueNAS Nextcloud
|
||||
# Replace TRUENAS_IP with your TrueNAS VM IP
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.url=http://${TRUENAS_IP}:PORT"
|
||||
# If TrueNAS Nextcloud uses HTTPS internally:
|
||||
# - "traefik.http.services.nextcloud.loadbalancer.server.url=https://${TRUENAS_IP}:PORT"
|
||||
# - "traefik.http.services.nextcloud.loadbalancer.server.scheme=https"
|
||||
# - "traefik.http.services.nextcloud.loadbalancer.serversTransport=insecureTransport@file"
|
||||
|
||||
# AUTHENTICATION OPTIONS (choose one):
|
||||
|
||||
# Option A: No Authelia (Nextcloud handles auth itself)
|
||||
# Good for: Family already has Nextcloud accounts
|
||||
# - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file"
|
||||
|
||||
# Option B: Authelia one_factor (password only, no 2FA)
|
||||
# Good for: Family-friendly, still protected from internet
|
||||
- "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file"
|
||||
|
||||
# Option C: Authelia two_factor (password + 2FA)
|
||||
# Good for: Maximum security
|
||||
# - "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file"
|
||||
|
||||
# WebDAV/CalDAV support (important for Nextcloud apps)
|
||||
- "traefik.http.routers.nextcloud-dav.rule=Host(`cloud.lemonlink.eu`) && PathPrefix(`/.well-known/carddav`,`/.well-known/caldav`,`/remote.php`)"
|
||||
- "traefik.http.routers.nextcloud-dav.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud-dav.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.nextcloud-dav.service=nextcloud"
|
||||
- "traefik.http.routers.nextcloud-dav.middlewares=authelia@docker"
|
||||
|
||||
# Upload size (important for Nextcloud)
|
||||
- "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
|
||||
- "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Frame-Options=SAMEORIGIN"
|
||||
- "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file,nextcloud-headers"
|
||||
|
||||
# ============================================================================
|
||||
# OTHER EXTERNAL SERVICES
|
||||
# ============================================================================
|
||||
|
||||
# TrueNAS Web UI (internal only)
|
||||
truenas-router:
|
||||
image: alpine:latest
|
||||
container_name: truenas-router
|
||||
restart: "no"
|
||||
command: "echo 'TrueNAS routing'"
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.truenas.rule=Host(`nas.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.truenas.entrypoints=internal"
|
||||
- "traefik.http.routers.truenas.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.truenas.middlewares=authelia@docker"
|
||||
- "traefik.http.services.truenas.loadbalancer.server.url=https://${TRUENAS_IP}:443"
|
||||
- "traefik.http.services.truenas.loadbalancer.serversTransport=insecureTransport@file"
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# Docker Compose Override
|
||||
# Copy this file to docker-compose.override.yml and customize
|
||||
# This file is automatically loaded by docker-compose
|
||||
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
# ============================================================================
|
||||
# Traefik Customization
|
||||
# ============================================================================
|
||||
traefik:
|
||||
# Bind internal entrypoint to Tailscale IP only
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# Uncomment and set TAILSCALE_IP in .env
|
||||
# - "${TAILSCALE_IP}:8443:8443"
|
||||
|
||||
# Additional volumes for custom certs
|
||||
# volumes:
|
||||
# - ./custom-certs:/certs:ro
|
||||
|
||||
# ============================================================================
|
||||
# Add Your Services Below
|
||||
# ============================================================================
|
||||
|
||||
# Example: Static website
|
||||
# website:
|
||||
# image: nginx:alpine
|
||||
# container_name: website
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - traefik-external
|
||||
# volumes:
|
||||
# - ./website:/usr/share/nginx/html:ro
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.http.routers.website.rule=Host(`lemonlink.eu`) || Host(`www.lemonlink.eu`)"
|
||||
# - "traefik.http.routers.website.entrypoints=websecure"
|
||||
# - "traefik.http.routers.website.tls.certresolver=letsencrypt"
|
||||
# # No Authelia for public website
|
||||
# - "traefik.http.services.website.loadbalancer.server.port=80"
|
||||
|
||||
# Example: Bookmarks service
|
||||
# linkding:
|
||||
# image: sissbruecker/linkding:latest
|
||||
# container_name: linkding
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - services
|
||||
# environment:
|
||||
# - LD_SUPERUSER_NAME=admin
|
||||
# - LD_SUPERUSER_PASSWORD=${LINKDING_ADMIN_PASSWORD}
|
||||
# volumes:
|
||||
# - linkding-data:/etc/linkding/data
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.http.routers.linkding.rule=Host(`bookmarks.lemonlink.eu`)"
|
||||
# - "traefik.http.routers.linkding.entrypoints=websecure"
|
||||
# - "traefik.http.routers.linkding.tls.certresolver=letsencrypt"
|
||||
# - "traefik.http.routers.linkding.middlewares=authelia@docker"
|
||||
|
||||
# Example: File browser (internal only)
|
||||
# filebrowser:
|
||||
# image: filebrowser/filebrowser:latest
|
||||
# container_name: filebrowser
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - services
|
||||
# volumes:
|
||||
# - /path/to/your/files:/srv
|
||||
# - filebrowser-data:/database
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.http.routers.filebrowser.rule=Host(`files.local.lemonlink.eu`)"
|
||||
# - "traefik.http.routers.filebrowser.entrypoints=internal"
|
||||
# - "traefik.http.routers.filebrowser.tls.certresolver=letsencrypt"
|
||||
# - "traefik.http.routers.filebrowser.middlewares=authelia@docker"
|
||||
|
||||
# Example: Media server (Jellyfin)
|
||||
# jellyfin:
|
||||
# image: jellyfin/jellyfin:latest
|
||||
# container_name: jellyfin
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - services
|
||||
# environment:
|
||||
# - PUID=1000
|
||||
# - PGID=1000
|
||||
# volumes:
|
||||
# - jellyfin-config:/config
|
||||
# - /path/to/media:/media:ro
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# # External access with auth
|
||||
# - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.lemonlink.eu`)"
|
||||
# - "traefik.http.routers.jellyfin.entrypoints=websecure"
|
||||
# - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
|
||||
# - "traefik.http.routers.jellyfin.middlewares=authelia@docker"
|
||||
# # Internal access (direct)
|
||||
# - "traefik.http.routers.jellyfin-internal.rule=Host(`jellyfin.local.lemonlink.eu`)"
|
||||
# - "traefik.http.routers.jellyfin-internal.entrypoints=internal"
|
||||
# - "traefik.http.routers.jellyfin-internal.tls.certresolver=letsencrypt"
|
||||
# # Jellyfin uses its own auth, so skip Authelia for internal
|
||||
|
||||
# Additional volumes for your services
|
||||
# volumes:
|
||||
# linkding-data:
|
||||
# filebrowser-data:
|
||||
# jellyfin-config:
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
version: "3.8"
|
||||
|
||||
# ============================================================================
|
||||
# LEMONSEC - Security Stack for LemonLink
|
||||
# Deploy via Portainer: Git Repository
|
||||
# URL: https://git.lemonlink.eu/impulsivefps/LemonSec
|
||||
# ============================================================================
|
||||
#
|
||||
# ENVIRONMENT VARIABLES (set in Portainer UI):
|
||||
# Required:
|
||||
# - CF_API_EMAIL Cloudflare account email
|
||||
# - CF_API_KEY Cloudflare Global API Key
|
||||
# - TRUENAS_IP TrueNAS Scale VM IP (e.g., 192.168.1.100)
|
||||
# - TRUENAS_NEXTCLOUD_PORT TrueNAS Nextcloud port (e.g., 9001)
|
||||
#
|
||||
# Optional:
|
||||
# - TZ Timezone (default: Europe/Stockholm)
|
||||
# - CROWDSEC_API_KEY Generated after first CrowdSec start
|
||||
# - TAILSCALE_IP For internal entrypoint binding
|
||||
#
|
||||
# Secrets (generate once, store securely):
|
||||
# - AUTHELIA_JWT_SECRET
|
||||
# - AUTHELIA_SESSION_SECRET
|
||||
# - AUTHELIA_STORAGE_KEY
|
||||
#
|
||||
# ============================================================================
|
||||
|
||||
networks:
|
||||
# External-facing network (Cloudflare → Traefik)
|
||||
traefik-external:
|
||||
driver: bridge
|
||||
|
||||
# Internal network (Tailscale/VPN → Traefik)
|
||||
traefik-internal:
|
||||
driver: bridge
|
||||
internal: true
|
||||
|
||||
# Services network (isolated)
|
||||
services:
|
||||
driver: bridge
|
||||
|
||||
# CrowdSec network
|
||||
crowdsec:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
traefik-certs:
|
||||
authelia-data:
|
||||
crowdsec-data:
|
||||
crowdsec-config:
|
||||
adguard-work:
|
||||
adguard-conf:
|
||||
redis-data:
|
||||
|
||||
services:
|
||||
# ============================================================================
|
||||
# REVERSE PROXY - Traefik
|
||||
# ============================================================================
|
||||
traefik:
|
||||
image: traefik:v3.1
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
networks:
|
||||
- traefik-external
|
||||
- traefik-internal
|
||||
- services
|
||||
- crowdsec
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# Internal entrypoint - uncomment and set TAILSCALE_IP for Tailscale-only access
|
||||
# - "${TAILSCALE_IP}:8443:8443"
|
||||
environment:
|
||||
- CF_API_EMAIL=${CF_API_EMAIL:?CF_API_EMAIL not set}
|
||||
- CF_API_KEY=${CF_API_KEY:?CF_API_KEY not set}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik/traefik.yml:/traefik.yml:ro
|
||||
- ./traefik/dynamic:/dynamic:ro
|
||||
- traefik-certs:/letsencrypt
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# Dashboard - INTERNAL ONLY
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.traefik.middlewares=authelia@docker"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8082/ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# ============================================================================
|
||||
# SSO & AUTHENTICATION - Authelia
|
||||
# ============================================================================
|
||||
authelia:
|
||||
image: authelia/authelia:4.38
|
||||
container_name: authelia
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
- traefik-external
|
||||
- traefik-internal
|
||||
expose:
|
||||
- 9091
|
||||
volumes:
|
||||
- authelia-data:/config
|
||||
- ./authelia/configuration.yml:/configuration.yml:ro
|
||||
- ./authelia/users_database.yml:/users_database.yml:ro
|
||||
environment:
|
||||
- TZ=${TZ:-Europe/Stockholm}
|
||||
# Secrets - passed via environment variables for Portainer
|
||||
- AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET:?AUTHELIA_JWT_SECRET not set}
|
||||
- AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET:?AUTHELIA_SESSION_SECRET not set}
|
||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_KEY:?AUTHELIA_STORAGE_KEY not set}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.authelia.rule=Host(`auth.lemonlink.eu`)"
|
||||
- "traefik.http.routers.authelia.entrypoints=websecure"
|
||||
- "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.lemonlink.eu/"
|
||||
- "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true"
|
||||
- "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9091/api/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# ============================================================================
|
||||
# THREAT DETECTION - CrowdSec
|
||||
# ============================================================================
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:v1.6.0
|
||||
container_name: crowdsec
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- crowdsec
|
||||
- services
|
||||
environment:
|
||||
- TZ=${TZ:-Europe/Stockholm}
|
||||
- COLLECTIONS=crowdsecurity/linux crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors
|
||||
- GID=1000
|
||||
volumes:
|
||||
- crowdsec-data:/var/lib/crowdsec/data
|
||||
- crowdsec-config:/etc/crowdsec
|
||||
- ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
healthcheck:
|
||||
test: ["CMD", "cscli", "machines", "list"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# CrowdSec bouncer for Traefik
|
||||
crowdsec-bouncer-traefik:
|
||||
image: crowdsecurity/traefik-bouncer:v0.1.0
|
||||
container_name: crowdsec-bouncer-traefik
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- crowdsec
|
||||
environment:
|
||||
- CROWDSEC_BOUNCER_API_KEY=${CROWDSEC_API_KEY:-}
|
||||
- CROWDSEC_AGENT_HOST=crowdsec:8080
|
||||
- CROWDSEC_BOUNCER_LOG_LEVEL=1
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
depends_on:
|
||||
- crowdsec
|
||||
|
||||
# ============================================================================
|
||||
# INTERNAL DNS - AdGuard Home
|
||||
# ============================================================================
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.52
|
||||
container_name: adguard
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
- traefik-internal
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
expose:
|
||||
- 3000
|
||||
- 80
|
||||
volumes:
|
||||
- adguard-work:/opt/adguardhome/work
|
||||
- adguard-conf:/opt/adguardhome/conf
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.adguard.rule=Host(`dns.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.adguard.entrypoints=websecure"
|
||||
- "traefik.http.routers.adguard.service=adguard"
|
||||
- "traefik.http.routers.adguard.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.adguard.middlewares=authelia@docker"
|
||||
- "traefik.http.services.adguard.loadbalancer.server.port=80"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:80"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# ============================================================================
|
||||
# CACHE - Redis (for Authelia sessions)
|
||||
# ============================================================================
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
|
||||
# ============================================================================
|
||||
# EXTERNAL SERVICES ROUTING
|
||||
# ============================================================================
|
||||
|
||||
# TrueNAS Nextcloud Router
|
||||
# This container doesn't run anything - it's just configuration for Traefik
|
||||
nextcloud-router:
|
||||
image: alpine:latest
|
||||
container_name: nextcloud-router
|
||||
restart: "no"
|
||||
entrypoint: ["echo", "Nextcloud routing configured"]
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Main Nextcloud route - EXTERNAL ACCESS
|
||||
- "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)"
|
||||
- "traefik.http.routers.nextcloud.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.nextcloud.service=nextcloud"
|
||||
|
||||
# Point to TrueNAS Nextcloud
|
||||
# Uses environment variables: TRUENAS_IP and TRUENAS_NEXTCLOUD_PORT
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.url=http://${TRUENAS_IP:?TRUENAS_IP not set}:${TRUENAS_NEXTCLOUD_PORT:?TRUENAS_NEXTCLOUD_PORT not set}"
|
||||
|
||||
# Security middlewares (NO Authelia - Nextcloud handles auth)
|
||||
# This allows family to login directly to Nextcloud
|
||||
- "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file,nextcloud-headers"
|
||||
|
||||
# Nextcloud-specific headers
|
||||
- "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
|
||||
- "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Frame-Options=SAMEORIGIN"
|
||||
|
||||
# WebDAV/CalDAV routes (needed for mobile apps)
|
||||
- "traefik.http.routers.nextcloud-dav.rule=Host(`cloud.lemonlink.eu`) && PathPrefix(`/.well-known/carddav`,`/.well-known/caldav`,`/remote.php`)"
|
||||
- "traefik.http.routers.nextcloud-dav.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud-dav.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.nextcloud-dav.service=nextcloud"
|
||||
|
||||
# TrueNAS Web UI Router (internal access only)
|
||||
truenas-router:
|
||||
image: alpine:latest
|
||||
container_name: truenas-router
|
||||
restart: "no"
|
||||
entrypoint: ["echo", "TrueNAS routing configured"]
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.truenas.rule=Host(`nas.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.truenas.entrypoints=websecure"
|
||||
- "traefik.http.routers.truenas.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.truenas.middlewares=authelia@docker"
|
||||
- "traefik.http.services.truenas.loadbalancer.server.url=https://${TRUENAS_IP:?TRUENAS_IP not set}:443"
|
||||
- "traefik.http.services.truenas.loadbalancer.serversTransport=insecureTransport@file"
|
||||
|
||||
# ============================================================================
|
||||
# PORTAINER ROUTER (if Portainer is on host, not in this stack)
|
||||
# ============================================================================
|
||||
portainer-router:
|
||||
image: alpine:latest
|
||||
container_name: portainer-router
|
||||
restart: "no"
|
||||
entrypoint: ["echo", "Portainer routing configured"]
|
||||
networks:
|
||||
- services
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.portainer.rule=Host(`docker.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.portainer.middlewares=authelia@docker"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.url=http://portainer:9000"
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# Cloudflare Configuration Guide
|
||||
|
||||
## DNS Setup
|
||||
|
||||
### 1. Create API Token (Recommended)
|
||||
|
||||
1. Go to [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens)
|
||||
2. Click **Create Token**
|
||||
3. Use **Custom Token**
|
||||
4. Configure permissions:
|
||||
- **Zone:Read** - All zones
|
||||
- **DNS:Edit** - All zones
|
||||
5. Zone Resources:
|
||||
- Include: Specific zone - lemonlink.eu
|
||||
6. Click **Continue to summary** → **Create Token**
|
||||
7. Copy the token to your `.env` file as `CF_DNS_API_TOKEN`
|
||||
|
||||
### 2. DNS Records
|
||||
|
||||
Create these A records in Cloudflare DNS:
|
||||
|
||||
| Type | Name | Target | Proxy Status | TTL |
|
||||
|------|------|--------|--------------|-----|
|
||||
| A | @ | YOUR_SERVER_IP | 🟠 Proxied | Auto |
|
||||
| A | * | YOUR_SERVER_IP | 🟠 Proxied | Auto |
|
||||
| A | auth | YOUR_SERVER_IP | 🟠 Proxied | Auto |
|
||||
|
||||
The wildcard (`*`) record is crucial for automatic subdomain routing.
|
||||
|
||||
### 3. SSL/TLS Settings
|
||||
|
||||
Navigate to **SSL/TLS** → **Overview**:
|
||||
|
||||
| Setting | Value | Why |
|
||||
|---------|-------|-----|
|
||||
| SSL/TLS encryption mode | **Full (strict)** | Encrypts end-to-end with origin cert validation |
|
||||
| Always Use HTTPS | **ON** | Redirects HTTP to HTTPS |
|
||||
| Automatic HTTPS Rewrites | **ON** | Fixes mixed content |
|
||||
| Minimum TLS Version | **1.2** | Security best practice |
|
||||
|
||||
### 4. Edge Certificates
|
||||
|
||||
**SSL/TLS** → **Edge Certificates**:
|
||||
|
||||
- Enable **Always Use HTTPS**
|
||||
- Set **Minimum TLS Version** to 1.2
|
||||
- Enable **Automatic HTTPS Rewrites**
|
||||
- Enable **Opportunistic Encryption**
|
||||
- Consider enabling **TLS 1.3** (fastest)
|
||||
|
||||
### 5. Security Settings
|
||||
|
||||
**Security** → **Settings**:
|
||||
|
||||
| Setting | Recommended Value |
|
||||
|---------|-------------------|
|
||||
| Security Level | Medium or High |
|
||||
| Challenge Passage | 30 minutes |
|
||||
| Browser Integrity Check | ON |
|
||||
|
||||
### 6. DDoS Protection
|
||||
|
||||
**Security** → **DDoS** → **Configure**:
|
||||
|
||||
- HTTP DDoS attack protection: **ON**
|
||||
- Sensitivity: **High**
|
||||
- Action: **Managed Challenge**
|
||||
|
||||
### 7. Firewall Rules (Optional but Recommended)
|
||||
|
||||
**Security** → **WAF** → **Firewall rules**:
|
||||
|
||||
Create rules to block threats before they reach your server:
|
||||
|
||||
**Block Known Bad Bots:**
|
||||
```
|
||||
(cf.client.bot) or (http.user_agent contains "bot" and not cf.client.bot)
|
||||
```
|
||||
Action: Block
|
||||
|
||||
**Rate Limit Login Attempts:**
|
||||
```
|
||||
(http.request.uri.path contains "/api/verify") or (http.request.uri.path contains "/login")
|
||||
```
|
||||
Action: Challenge (Rate limit: 5 requests per 10 seconds)
|
||||
|
||||
**Block Countries (Optional):**
|
||||
```
|
||||
(not ip.geoip.country in {"SE" "NO" "DK" "FI" "DE"})
|
||||
```
|
||||
Action: Block or Challenge
|
||||
|
||||
### 8. Origin Server Certificates (Optional)
|
||||
|
||||
For extra security between Cloudflare and your server:
|
||||
|
||||
1. **SSL/TLS** → **Origin Server** → **Create Certificate**
|
||||
2. Let Cloudflare generate a certificate
|
||||
3. Choose RSA (2048) or ECC
|
||||
4. Download certificate and private key
|
||||
5. Place in `traefik/certs/` and reference in config
|
||||
|
||||
For most homelab setups, the auto-generated Let's Encrypt certificates are sufficient.
|
||||
|
||||
### 9. Page Rules (Optional)
|
||||
|
||||
**Rules** → **Page Rules**:
|
||||
|
||||
Create rules for specific caching/security behaviors:
|
||||
|
||||
**Cache Static Assets:**
|
||||
```
|
||||
*lemonlink.eu/static/*
|
||||
```
|
||||
Settings:
|
||||
- Cache Level: Cache Everything
|
||||
- Edge Cache TTL: 1 month
|
||||
|
||||
**Secure Auth Endpoint:**
|
||||
```
|
||||
auth.lemonlink.eu/*
|
||||
```
|
||||
Settings:
|
||||
- Security Level: High
|
||||
- Browser Integrity Check: On
|
||||
|
||||
### 10. Analytics & Monitoring
|
||||
|
||||
Enable **Security Events** notifications:
|
||||
|
||||
1. **Notifications** → **Add**
|
||||
2. Choose **Security Events**
|
||||
3. Set threshold (e.g., notify on 100+ events)
|
||||
|
||||
## Verification
|
||||
|
||||
Test your setup:
|
||||
|
||||
```bash
|
||||
# Check DNS resolution
|
||||
dig +short lemonlink.eu
|
||||
dig +short auth.lemonlink.eu
|
||||
dig +short test.lemonlink.eu # Should resolve to CF IP
|
||||
|
||||
# Check SSL
|
||||
curl -I https://lemonlink.eu
|
||||
# Should show Cloudflare headers
|
||||
|
||||
# Check certificate
|
||||
echo | openssl s_client -servername lemonlink.eu -connect lemonlink.eu:443 2>/dev/null | openssl x509 -noout -text | grep -A2 "Subject Alternative Name"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Too Many Redirects" Error
|
||||
- Check SSL/TLS mode is **Full (strict)**, not Flexible
|
||||
- Verify no redirect loops in Traefik config
|
||||
|
||||
### Certificate Errors
|
||||
- Ensure `CF_API_EMAIL` and `CF_API_KEY` are correct in `.env`
|
||||
- Check Traefik logs for ACME errors
|
||||
- Verify DNS propagation: `dig @1.1.1.1 lemonlink.eu`
|
||||
|
||||
### Cloudflare IP in Logs (Not Client IP)
|
||||
- Ensure `forwardedHeaders.trustedIPs` includes all Cloudflare IPs in `traefik.yml`
|
||||
- Check `X-Forwarded-For` header is being passed
|
||||
|
||||
## Cloudflare IPs (Keep Updated!)
|
||||
|
||||
Traefik config includes Cloudflare IPs, but verify they're current:
|
||||
https://www.cloudflare.com/ips/
|
||||
|
||||
Update `traefik/traefik.yml` if IPs change.
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
# Security Hardening Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This stack implements defense in depth with multiple security layers:
|
||||
|
||||
1. **Network**: Cloudflare → Traefik → Service
|
||||
2. **Authentication**: Authelia (SSO + 2FA)
|
||||
3. **Authorization**: Access control rules
|
||||
4. **Detection**: CrowdSec (IPS/IDS)
|
||||
5. **Encryption**: TLS 1.3, strong ciphers
|
||||
6. **Monitoring**: Logs, metrics, alerts
|
||||
|
||||
## Pre-Deployment Checklist
|
||||
|
||||
### 1. Server Hardening
|
||||
|
||||
```bash
|
||||
# Update system
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Install fail2ban
|
||||
sudo apt install fail2ban
|
||||
|
||||
# Configure fail2ban for SSH
|
||||
cat <<EOF | sudo tee /etc/fail2ban/jail.local
|
||||
[DEFAULT]
|
||||
bantime = 1h
|
||||
findtime = 10m
|
||||
maxretry = 3
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = 3
|
||||
EOF
|
||||
|
||||
sudo systemctl restart fail2ban
|
||||
|
||||
# Disable password SSH (use keys only)
|
||||
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
|
||||
sudo systemctl restart sshd
|
||||
|
||||
# Configure firewall (UFW)
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow from 100.64.0.0/10 to any port 22 # Tailscale SSH only
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### 2. Docker Security
|
||||
|
||||
```bash
|
||||
# Install Docker rootless (optional but recommended)
|
||||
dockerd-rootless-setuptool.sh install
|
||||
|
||||
# Or use userns-remap
|
||||
echo '{"userns-remap": "default"}' | sudo tee /etc/docker/daemon.json
|
||||
sudo systemctl restart docker
|
||||
|
||||
# Enable Docker Content Trust
|
||||
export DOCKER_CONTENT_TRUST=1
|
||||
```
|
||||
|
||||
### 3. Secret Generation
|
||||
|
||||
```bash
|
||||
# Generate all secrets
|
||||
cd LemonSec
|
||||
|
||||
# JWT Secret (32+ chars)
|
||||
openssl rand -hex 32 > secrets/authelia_jwt_secret.txt
|
||||
|
||||
# Session Secret (32+ chars)
|
||||
openssl rand -hex 32 > secrets/authelia_session_secret.txt
|
||||
|
||||
# Storage Encryption Key (32+ chars)
|
||||
openssl rand -hex 32 > secrets/authelia_storage_key.txt
|
||||
|
||||
# Authelia user password
|
||||
docker run --rm authelia/authelia:latest \
|
||||
authelia crypto hash generate argon2 \
|
||||
--password 'YourStrongPassword123!'
|
||||
```
|
||||
|
||||
### 4. Cloudflare Security
|
||||
|
||||
- [ ] SSL/TLS mode: Full (strict)
|
||||
- [ ] Always Use HTTPS: ON
|
||||
- [ ] Security Level: High
|
||||
- [ ] Browser Integrity Check: ON
|
||||
- [ ] Challenge Passage: 30 minutes
|
||||
- [ ] Minimum TLS Version: 1.2
|
||||
|
||||
## Post-Deployment Security
|
||||
|
||||
### 1. Verify CrowdSec is Working
|
||||
|
||||
```bash
|
||||
# Check CrowdSec is detecting attacks
|
||||
docker-compose exec crowdsec cscli metrics
|
||||
|
||||
# View active decisions (bans)
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# Simulate an attack to test
|
||||
curl -I http://your-ip/.env
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
# Should show your IP banned
|
||||
```
|
||||
|
||||
### 2. Configure Authelia Notifications
|
||||
|
||||
Edit `authelia/configuration.yml`:
|
||||
|
||||
```yaml
|
||||
notifier:
|
||||
smtp:
|
||||
address: smtp.gmail.com:587
|
||||
username: your-email@gmail.com
|
||||
password: ${SMTP_PASSWORD}
|
||||
sender: "Authelia <security@lemonlink.eu>"
|
||||
```
|
||||
|
||||
Test: Try logging in with wrong password 3 times → should get email.
|
||||
|
||||
### 3. Enable CrowdSec Notifications
|
||||
|
||||
```bash
|
||||
# Install Discord/Slack plugin
|
||||
docker-compose exec crowdsec cscli notifications add slack
|
||||
|
||||
# Configure in CrowdSec container
|
||||
docker-compose exec crowdsec sh -c "cat > /etc/crowdsec/notifications/slack.yaml" << 'EOF'
|
||||
type: slack
|
||||
name: slack_default
|
||||
log_level: info
|
||||
format: |
|
||||
{{range . -}}
|
||||
{{$alert := . -}}
|
||||
{{range .Decisions -}}
|
||||
:warning: *CrowdSec Alert*
|
||||
*IP:* {{.Value}}
|
||||
*Reason:* {{.Scenario}}
|
||||
*Duration:* {{.Duration}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
webhook: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
|
||||
EOF
|
||||
```
|
||||
|
||||
### 4. Regular Security Tasks
|
||||
|
||||
**Weekly:**
|
||||
```bash
|
||||
# Check for new decisions
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# Review failed logins
|
||||
docker-compose logs authelia | grep "unsuccessful"
|
||||
|
||||
# Update images
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**Monthly:**
|
||||
```bash
|
||||
# Rotate secrets
|
||||
./scripts/rotate-secrets.sh # You need to create this
|
||||
|
||||
# Review access logs
|
||||
docker-compose logs traefik | grep -E "(401|403|429)"
|
||||
|
||||
# Backup
|
||||
tar czf backup-$(date +%Y%m%d).tar.gz traefik/ authelia/ crowdsec/ .env
|
||||
```
|
||||
|
||||
## Threat Model
|
||||
|
||||
### What This Stack Protects Against
|
||||
|
||||
| Threat | Mitigation |
|
||||
|--------|-----------|
|
||||
| Credential stuffing | Authelia rate limiting, fail2ban |
|
||||
| Brute force attacks | CrowdSec detection, progressive delays |
|
||||
| DDoS | Cloudflare, rate limiting |
|
||||
| MITM | TLS 1.3, HSTS, certificate pinning |
|
||||
| Session hijacking | Secure cookies, short expiration |
|
||||
| XSS/CSRF | Security headers, SameSite cookies |
|
||||
| SQL injection | Parameterized queries (app responsibility) |
|
||||
|
||||
### What It Doesn't Protect Against
|
||||
|
||||
- Zero-day vulnerabilities in applications
|
||||
- Social engineering
|
||||
- Compromised client devices
|
||||
- Insider threats
|
||||
- Application-level logic bugs
|
||||
|
||||
## Incident Response
|
||||
|
||||
### If You Suspect a Breach
|
||||
|
||||
1. **Isolate**
|
||||
```bash
|
||||
# Stop all services
|
||||
docker-compose down
|
||||
|
||||
# Block all traffic temporarily
|
||||
sudo ufw default deny incoming
|
||||
```
|
||||
|
||||
2. **Investigate**
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs > incident-$(date +%Y%m%d).log
|
||||
|
||||
# Check CrowdSec decisions
|
||||
docker-compose exec crowdsec cscli decisions list
|
||||
|
||||
# Check for unknown containers
|
||||
docker ps -a
|
||||
```
|
||||
|
||||
3. **Recover**
|
||||
```bash
|
||||
# Rotate all secrets
|
||||
# Change all passwords
|
||||
# Re-deploy from clean backup
|
||||
```
|
||||
|
||||
## Security Testing
|
||||
|
||||
### Automated Scans
|
||||
|
||||
```bash
|
||||
# Test TLS configuration
|
||||
docker run --rm drwetter/testssl.sh https://lemonlink.eu
|
||||
|
||||
# Test headers
|
||||
curl -I https://lemonlink.eu | grep -E "(strict-transport-security|content-security-policy|x-frame-options)"
|
||||
|
||||
# Test rate limiting
|
||||
for i in {1..20}; do curl -s -o /dev/null -w "%{http_code}" https://auth.lemonlink.eu/; done
|
||||
```
|
||||
|
||||
### Manual Penetration Testing
|
||||
|
||||
1. Try accessing internal services from external IP
|
||||
2. Attempt SQL injection in login forms
|
||||
3. Test XSS payloads
|
||||
4. Verify 2FA can't be bypassed
|
||||
5. Check for information disclosure in headers
|
||||
|
||||
## Compliance Notes
|
||||
|
||||
This setup helps with:
|
||||
- **GDPR**: Encryption, access logs, data retention
|
||||
- **ISO 27001**: Defense in depth, monitoring
|
||||
- **SOC 2**: Audit trails, access controls
|
||||
|
||||
But does NOT make you compliant by itself. You must:
|
||||
- Document all data flows
|
||||
- Implement data retention policies
|
||||
- Conduct regular audits
|
||||
- Train users on security
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
|
||||
- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/)
|
||||
- [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker)
|
||||
- [Cloudflare Security Center](https://dash.cloudflare.com/?to=/:account/:zone/security)
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# Tailscale Integration
|
||||
|
||||
Tailscale provides secure, zero-config VPN access to your internal network without exposing ports.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install Tailscale on Server
|
||||
|
||||
```bash
|
||||
# On Proxmox (Debian/Ubuntu)
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
|
||||
# Start Tailscale
|
||||
sudo tailscale up
|
||||
|
||||
# Get your Tailscale IP
|
||||
sudo tailscale ip -4
|
||||
```
|
||||
|
||||
### 2. Configure DNS in Tailscale Admin
|
||||
|
||||
1. Go to [Tailscale Admin Console](https://login.tailscale.com/admin/dns)
|
||||
2. Add **Nameservers**:
|
||||
- Your Raspberry Pi Tailscale IP (for AdGuard)
|
||||
- Or: `100.100.100.100` (Tailscale's MagicDNS)
|
||||
3. Enable **Override local DNS** (optional)
|
||||
4. Add **Search domain**: `local.lemonlink.eu`
|
||||
|
||||
### 3. DNS Split Horizon
|
||||
|
||||
Configure AdGuard to handle `local.lemonlink.eu`:
|
||||
|
||||
```
|
||||
# In AdGuard Home (dns.local.lemonlink.eu)
|
||||
# Filters → DNS rewrites
|
||||
|
||||
*.local.lemonlink.eu → YOUR_TAILSCALE_IP
|
||||
```
|
||||
|
||||
### 4. Traefik Internal Entrypoint
|
||||
|
||||
The internal entrypoint (port 8443) is configured to only listen on Tailscale:
|
||||
|
||||
```yaml
|
||||
# In docker-compose.yml, under traefik service:
|
||||
ports:
|
||||
- "${TAILSCALE_IP}:8443:8443" # Only accessible via Tailscale
|
||||
```
|
||||
|
||||
Update `.env`:
|
||||
```
|
||||
TAILSCALE_IP=100.x.x.x # Your server's Tailscale IP
|
||||
```
|
||||
|
||||
### 5. ACLs (Access Control Lists)
|
||||
|
||||
For extra security, configure Tailscale ACLs:
|
||||
|
||||
```json
|
||||
// In Tailscale Admin → Access Controls
|
||||
{
|
||||
"acls": [
|
||||
// Allow users to access specific ports
|
||||
{
|
||||
"action": "accept",
|
||||
"src": ["group:family"],
|
||||
"dst": ["100.x.x.x:443,8443"] // Your server
|
||||
},
|
||||
// Deny everything else
|
||||
{
|
||||
"action": "deny",
|
||||
"src": ["*"],
|
||||
"dst": ["100.x.x.x:*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# From your phone/computer with Tailscale
|
||||
|
||||
# Test internal DNS
|
||||
ping traefik.local.lemonlink.eu
|
||||
|
||||
# Access internal services
|
||||
curl https://traefik.local.lemonlink.eu:8443
|
||||
|
||||
# Verify you're going through Tailscale
|
||||
# Should show 100.x.x.x IPs, not public IPs
|
||||
traceroute traefik.local.lemonlink.eu
|
||||
```
|
||||
|
||||
## Raspberry Pi 5 Setup
|
||||
|
||||
Your Raspberry Pi can run additional monitoring services:
|
||||
|
||||
```yaml
|
||||
# On Raspberry Pi - docker-compose.yml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.52
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
- "3000:3000" # Initial setup only
|
||||
volumes:
|
||||
- ./adguard-work:/opt/adguardhome/work
|
||||
- ./adguard-conf:/opt/adguardhome/conf
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Configure AdGuard:
|
||||
1. Bootstrap DNS: `1.1.1.1`, `8.8.8.8`
|
||||
2. Upstream DNS: `https://dns.cloudflare.com/dns-query`
|
||||
3. DNS rewrites for local domains
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Disable key expiry** for servers:
|
||||
```bash
|
||||
sudo tailscale up --reset --operator=$USER
|
||||
```
|
||||
|
||||
2. **Enable device approval** for new devices
|
||||
|
||||
3. **Use ACLs** to limit access between devices
|
||||
|
||||
4. **Enable HTTPS** (Beta feature):
|
||||
```bash
|
||||
sudo tailscale up --accept-routes
|
||||
sudo tailscale cert your-host.local.lemonlink.eu
|
||||
```
|
||||
|
||||
5. **Disable subnet routing** if not needed:
|
||||
```bash
|
||||
sudo tailscale up --accept-routes=false
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Can't resolve local.lemonlink.eu
|
||||
- Check AdGuard is running on Raspberry Pi
|
||||
- Verify Tailscale DNS settings
|
||||
- Test: `dig @100.x.x.x traefik.local.lemonlink.eu` (Raspberry Pi IP)
|
||||
|
||||
### Connection refused on :8443
|
||||
- Verify Traefik is bound to Tailscale IP
|
||||
- Check firewall: `sudo ufw allow from 100.64.0.0/10 to any port 8443`
|
||||
- Test locally: `curl -k https://localhost:8443`
|
||||
|
||||
### Slow performance
|
||||
- Enable NAT optimization:
|
||||
```bash
|
||||
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
|
||||
sudo sysctl -p
|
||||
```
|
||||
- Use `--netfilter-mode=off` if issues persist
|
||||
|
||||
## Comparison: External vs Internal Access
|
||||
|
||||
| Service | External URL | Internal URL | Auth |
|
||||
|---------|-------------|--------------|------|
|
||||
| Nextcloud | cloud.lemonlink.eu | cloud.local.lemonlink.eu:8443 | Authelia 2FA |
|
||||
| Vaultwarden | vault.lemonlink.eu | vault.local.lemonlink.eu:8443 | Authelia 2FA |
|
||||
| Portainer | - | docker.local.lemonlink.eu:8443 | Authelia 1FA |
|
||||
| Traefik Dashboard | - | traefik.local.lemonlink.eu:8443 | Authelia 1FA |
|
||||
| AdGuard | - | dns.local.lemonlink.eu:8443 | Authelia 1FA |
|
||||
|
||||
*1FA = Username/Password, 2FA = + TOTP/WebAuthn*
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
# TrueNAS Scale + Nextcloud Integration
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐
|
||||
│ Proxmox VM │ │ TrueNAS Scale │
|
||||
│ (LemonSec) │◄───────►│ VM │
|
||||
│ │ │ │
|
||||
│ ┌─────────────┐ │ │ ┌──────────────┐ │
|
||||
│ │ Traefik │ │ HTTP │ │ Nextcloud │ │
|
||||
│ │ (443) │◄├─────────┤►│ App │ │
|
||||
│ └─────────────┘ │ │ └──────────────┘ │
|
||||
│ │ │ └──────────────────┘
|
||||
│ ▼ │
|
||||
│ ┌─────────────┐ │
|
||||
│ │ Authelia │ │ (Optional SSO)
|
||||
│ └─────────────┘ │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Step 1: Configure TrueNAS Nextcloud
|
||||
|
||||
### In TrueNAS Scale Web UI:
|
||||
|
||||
1. **Apps** → **Available Applications** → **Nextcloud**
|
||||
2. **Install** with these settings:
|
||||
- **Application Name**: `nextcloud`
|
||||
- **Nextcloud Config**:
|
||||
- **Nextcloud URL**: `https://cloud.lemonlink.eu` (your domain)
|
||||
- **Username**: `admin`
|
||||
- **Password**: Generate strong password
|
||||
- **Networking**:
|
||||
- **Web Port**: `9001` (or any free port)
|
||||
- **Storage**:
|
||||
- Set up your data pool
|
||||
- **Enable**: True
|
||||
|
||||
3. **Wait** for installation to complete
|
||||
|
||||
4. **Note the Node Port**: TrueNAS will show the port (e.g., `9001`)
|
||||
|
||||
## Step 2: Configure LemonSec
|
||||
|
||||
### Update .env
|
||||
|
||||
```bash
|
||||
# Edit .env
|
||||
TRUENAS_IP=192.168.1.100 # Your TrueNAS IP
|
||||
TRUENAS_NEXTCLOUD_PORT=9001 # Nextcloud port on TrueNAS
|
||||
```
|
||||
|
||||
### Choose Authentication Mode
|
||||
|
||||
Edit `docker-compose.external.yml` and pick your authentication:
|
||||
|
||||
#### Option A: Nextcloud Native Auth (Recommended for Families)
|
||||
- Family members use Nextcloud accounts directly
|
||||
- No Authelia barrier
|
||||
- Nextcloud handles its own security
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# No authelia middleware
|
||||
- "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file"
|
||||
```
|
||||
|
||||
#### Option B: Authelia + Nextcloud (Double Security)
|
||||
- Authelia login first, then Nextcloud login
|
||||
- Good for admin accounts
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file"
|
||||
```
|
||||
|
||||
#### Option C: Authelia SSO (One login for both)
|
||||
- Authelia handles auth, passes to Nextcloud
|
||||
- Requires OpenID Connect setup in Nextcloud
|
||||
|
||||
See "Advanced: SSO Integration" below.
|
||||
|
||||
## Step 3: Deploy
|
||||
|
||||
```bash
|
||||
# Start core LemonSec
|
||||
docker-compose up -d
|
||||
|
||||
# Start external routing
|
||||
docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d
|
||||
```
|
||||
|
||||
## Step 4: Configure Nextcloud
|
||||
|
||||
### First Login
|
||||
|
||||
1. Go to `https://cloud.lemonlink.eu`
|
||||
2. Login with TrueNAS Nextcloud admin credentials
|
||||
|
||||
### Required Settings
|
||||
|
||||
Add to TrueNAS Nextcloud **config.php**:
|
||||
|
||||
```php
|
||||
'trusted_domains' =>
|
||||
array (
|
||||
0 => 'localhost',
|
||||
1 => '192.168.1.100', # TrueNAS IP
|
||||
2 => 'cloud.lemonlink.eu', # Your domain
|
||||
),
|
||||
'overwriteprotocol' => 'https',
|
||||
'overwritehost' => 'cloud.lemonlink.eu',
|
||||
'overwrite.cli.url' => 'https://cloud.lemonlink.eu',
|
||||
'trusted_proxies' =>
|
||||
array (
|
||||
0 => '192.168.1.50', # Proxmox VM IP (Traefik)
|
||||
),
|
||||
```
|
||||
|
||||
In TrueNAS:
|
||||
1. **Apps** → **Nextcloud** → **Edit** → **Nextcloud Config**
|
||||
2. Add to **Additional Environments**:
|
||||
```
|
||||
OVERWRITEPROTOCOL=https
|
||||
OVERWRITEHOST=cloud.lemonlink.eu
|
||||
TRUSTED_PROXIES=192.168.1.50
|
||||
```
|
||||
|
||||
### Fix WebDAV/Calendar Sync
|
||||
|
||||
Create file `fix-wellknown.yml` in Traefik dynamic config:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
nextcloud-wellknown:
|
||||
redirectRegex:
|
||||
regex: "^https://(.*)/.well-known/(card|cal)dav"
|
||||
replacement: "https://cloud.lemonlink.eu/remote.php/dav/"
|
||||
permanent: true
|
||||
```
|
||||
|
||||
Add middleware to Nextcloud router:
|
||||
```yaml
|
||||
- "traefik.http.routers.nextcloud.middlewares=...,nextcloud-wellknown"
|
||||
```
|
||||
|
||||
## Step 5: Family Access
|
||||
|
||||
### Create Family Accounts
|
||||
|
||||
1. Login as Nextcloud admin
|
||||
2. **Settings** → **Users** → **Create user**
|
||||
3. Create accounts for each family member
|
||||
|
||||
### Share the URL
|
||||
|
||||
Tell your family:
|
||||
- **URL**: `https://cloud.lemonlink.eu`
|
||||
- **Login**: Their individual username/password
|
||||
- **Apps**: Web, Desktop, Mobile apps available
|
||||
|
||||
### Mobile/Desktop Setup
|
||||
|
||||
**iOS/Android:**
|
||||
1. Download Nextcloud app
|
||||
2. Enter server: `https://cloud.lemonlink.eu`
|
||||
3. Login with credentials
|
||||
|
||||
**Desktop:**
|
||||
1. Download from nextcloud.com/install
|
||||
2. Same server URL
|
||||
3. Enable auto-sync
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Access through untrusted domain"
|
||||
|
||||
Add your domain to TrueNAS Nextcloud config:
|
||||
```bash
|
||||
# Shell into TrueNAS Nextcloud pod
|
||||
k3s kubectl exec -it -n ix-nextcloud nextcloud-xxx -- /bin/sh
|
||||
|
||||
# Edit config
|
||||
cd /var/www/html/config
|
||||
vi config.php
|
||||
# Add 'cloud.lemonlink.eu' to trusted_domains
|
||||
```
|
||||
|
||||
### Slow Uploads
|
||||
|
||||
Increase timeouts in Traefik:
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.services.nextcloud.loadbalancer.responseforwarding.flushinterval=100ms"
|
||||
```
|
||||
|
||||
### Large File Uploads (Nextcloud default limit)
|
||||
|
||||
In TrueNAS Nextcloud:
|
||||
1. **Apps** → **Nextcloud** → **Edit**
|
||||
2. **PHP Configuration**:
|
||||
- **Upload Max Size**: `10G` (or your limit)
|
||||
- **Memory Limit**: `1G`
|
||||
|
||||
### HTTPS Redirect Loop
|
||||
|
||||
Ensure these are set in Nextcloud:
|
||||
```php
|
||||
'overwriteprotocol' => 'https',
|
||||
'trusted_proxies' => ['192.168.1.50'],
|
||||
```
|
||||
|
||||
## Advanced: Authelia SSO Integration
|
||||
|
||||
If you want single sign-on (login once, access Nextcloud without second login):
|
||||
|
||||
### 1. Configure Authelia OpenID Connect
|
||||
|
||||
Add to `authelia/configuration.yml`:
|
||||
|
||||
```yaml
|
||||
identity_providers:
|
||||
oidc:
|
||||
clients:
|
||||
- client_id: nextcloud
|
||||
client_name: Nextcloud
|
||||
client_secret: ${NEXTCLOUD_OAUTH_SECRET} # Generate with openssl rand -hex 32
|
||||
public: false
|
||||
authorization_policy: one_factor
|
||||
require_pkce: true
|
||||
pkce_challenge_method: S256
|
||||
redirect_uris:
|
||||
- https://cloud.lemonlink.eu/apps/user_oidc/code
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
```
|
||||
|
||||
### 2. Install OIDC App in Nextcloud
|
||||
|
||||
1. Nextcloud → **Apps** → **Search**: "OpenID Connect user backend"
|
||||
2. **Enable**
|
||||
3. **Settings** → **Administration** → **OpenID Connect**
|
||||
4. Configure:
|
||||
- **Provider name**: Authelia
|
||||
- **Client ID**: nextcloud
|
||||
- **Client Secret**: (from above)
|
||||
- **Discovery endpoint**: `https://auth.lemonlink.eu/.well-known/openid-configuration`
|
||||
- **Scope**: `openid profile email groups`
|
||||
- **User ID mapping**: `preferred_username`
|
||||
|
||||
### 3. Optional: Disable Authelia on Nextcloud route
|
||||
|
||||
Since Nextcloud now handles auth via Authelia OIDC:
|
||||
```yaml
|
||||
# Remove authelia@docker middleware
|
||||
- "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file"
|
||||
```
|
||||
|
||||
## Migration from NPM
|
||||
|
||||
If you have existing NPM configuration:
|
||||
|
||||
1. **Export NPM config**: Settings → Export
|
||||
2. **Note custom locations**: Proxy Hosts → Edit → Advanced
|
||||
3. **Recreate in Traefik**:
|
||||
- Each NPM Proxy Host = One Traefik router
|
||||
- NPM Advanced config = Traefik middlewares
|
||||
4. **Test** one service at a time
|
||||
5. **Disable NPM** only after everything works
|
||||
|
||||
## Security Notes
|
||||
|
||||
- TrueNAS Nextcloud should not be exposed directly (no port forward to TrueNAS)
|
||||
- All traffic goes through Traefik (single entry point)
|
||||
- Consider fail2ban on TrueNAS for extra protection
|
||||
- Regular Nextcloud updates via TrueNAS UI
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Example: Internal-only service (Portainer)
|
||||
# Accessible only via Tailscale/VPN
|
||||
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
services:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
portainer-data:
|
||||
|
||||
services:
|
||||
portainer:
|
||||
image: portainer/portainer-ce:latest
|
||||
container_name: portainer
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- portainer-data:/data
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# Internal entrypoint only - NOT exposed to internet
|
||||
- "traefik.http.routers.portainer.rule=Host(`docker.local.lemonlink.eu`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=internal"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
# Optional: Skip Authelia for Portainer if it has its own auth
|
||||
# Or keep it for extra security
|
||||
- "traefik.http.routers.portainer.middlewares=authelia@docker"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Example: Nextcloud with LemonSec
|
||||
# Add this to your docker-compose.yml or run separately
|
||||
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
services:
|
||||
external: true # Use the existing network from main compose
|
||||
|
||||
volumes:
|
||||
nextcloud-data:
|
||||
nextcloud-db:
|
||||
|
||||
services:
|
||||
nextcloud-db:
|
||||
image: mariadb:10.11
|
||||
container_name: nextcloud-db
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${NEXTCLOUD_MYSQL_ROOT_PASSWORD}
|
||||
- MYSQL_PASSWORD=${NEXTCLOUD_MYSQL_PASSWORD}
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
volumes:
|
||||
- nextcloud-db:/var/lib/mysql
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
|
||||
nextcloud:
|
||||
image: nextcloud:29-apache
|
||||
container_name: nextcloud
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
environment:
|
||||
- MYSQL_PASSWORD=${NEXTCLOUD_MYSQL_PASSWORD}
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
- MYSQL_HOST=nextcloud-db
|
||||
- NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu
|
||||
- OVERWRITEPROTOCOL=https
|
||||
- OVERWRITEHOST=cloud.lemonlink.eu
|
||||
- OVERWRITECLIURL=https://cloud.lemonlink.eu
|
||||
volumes:
|
||||
- nextcloud-data:/var/www/html
|
||||
depends_on:
|
||||
- nextcloud-db
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# External access
|
||||
- "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)"
|
||||
- "traefik.http.routers.nextcloud.entrypoints=websecure"
|
||||
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.nextcloud.middlewares=authelia@docker,nextcloud-dav"
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
|
||||
# Nextcloud DAV fix
|
||||
- "traefik.http.middlewares.nextcloud-dav.redirectregex.permanent=true"
|
||||
- "traefik.http.middlewares.nextcloud-dav.redirectregex.regex=^https://(.*)/.well-known/(card|cal)dav"
|
||||
- "traefik.http.middlewares.nextcloud-dav.redirectregex.replacement=https://$${1}/remote.php/dav/"
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# Example: Vaultwarden (Bitwarden RS) with LemonSec
|
||||
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
services:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
vaultwarden-data:
|
||||
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:latest
|
||||
container_name: vaultwarden
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- services
|
||||
environment:
|
||||
- WEBSOCKET_ENABLED=true
|
||||
- SIGNUPS_ALLOWED=false # Disable after creating your account
|
||||
- ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN}
|
||||
- DOMAIN=https://vault.lemonlink.eu
|
||||
- SMTP_HOST=${SMTP_HOST}
|
||||
- SMTP_FROM=${SMTP_FROM}
|
||||
- SMTP_PORT=587
|
||||
- SMTP_SECURITY=starttls
|
||||
- SMTP_USERNAME=${SMTP_USERNAME}
|
||||
- SMTP_PASSWORD=${SMTP_PASSWORD}
|
||||
volumes:
|
||||
- vaultwarden-data:/data
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Main Vaultwarden interface
|
||||
- "traefik.http.routers.vaultwarden.rule=Host(`vault.lemonlink.eu`)"
|
||||
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
|
||||
- "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.vaultwarden.service=vaultwarden"
|
||||
- "traefik.http.routers.vaultwarden.middlewares=authelia@docker"
|
||||
|
||||
# WebSocket for real-time sync
|
||||
- "traefik.http.routers.vaultwarden-ws.rule=Host(`vault.lemonlink.eu`) && Path(`/notifications/hub`)"
|
||||
- "traefik.http.routers.vaultwarden-ws.entrypoints=websecure"
|
||||
- "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.vaultwarden-ws.service=vaultwarden-ws"
|
||||
|
||||
# Admin panel (separate router for different middleware)
|
||||
- "traefik.http.routers.vaultwarden-admin.rule=Host(`vault.lemonlink.eu`) && PathPrefix(`/admin`)"
|
||||
- "traefik.http.routers.vaultwarden-admin.entrypoints=websecure"
|
||||
- "traefik.http.routers.vaultwarden-admin.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.vaultwarden-admin.service=vaultwarden"
|
||||
- "traefik.http.routers.vaultwarden-admin.middlewares=authelia@docker,rate-limit-strict@file"
|
||||
|
||||
# Services
|
||||
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
|
||||
- "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012"
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
auth_enabled: false
|
||||
|
||||
server:
|
||||
http_listen_port: 3100
|
||||
grpc_listen_port: 9096
|
||||
|
||||
common:
|
||||
path_prefix: /loki
|
||||
storage:
|
||||
filesystem:
|
||||
chunks_directory: /loki/chunks
|
||||
rules_directory: /loki/rules
|
||||
replication_factor: 1
|
||||
ring:
|
||||
instance_addr: 127.0.0.1
|
||||
kvstore:
|
||||
store: inmemory
|
||||
|
||||
query_range:
|
||||
results_cache:
|
||||
cache:
|
||||
embedded_cache:
|
||||
enabled: true
|
||||
max_size_mb: 100
|
||||
|
||||
schema_config:
|
||||
configs:
|
||||
- from: 2020-10-24
|
||||
store: boltdb-shipper
|
||||
object_store: filesystem
|
||||
schema: v11
|
||||
index:
|
||||
prefix: index_
|
||||
period: 24h
|
||||
|
||||
ruler:
|
||||
alertmanager_url: http://localhost:9093
|
||||
|
||||
analytics:
|
||||
reporting_enabled: false
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
alerting:
|
||||
alertmanagers: []
|
||||
|
||||
rule_files: []
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
- job_name: 'traefik'
|
||||
static_configs:
|
||||
- targets: ['traefik:8080']
|
||||
metrics_path: /metrics
|
||||
|
||||
- job_name: 'docker'
|
||||
static_configs:
|
||||
- targets: ['cadvisor:8080']
|
||||
|
||||
- job_name: 'node-exporter'
|
||||
static_configs:
|
||||
- targets: ['node-exporter:9100']
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
server:
|
||||
http_listen_port: 9080
|
||||
grpc_listen_port: 0
|
||||
|
||||
positions:
|
||||
filename: /tmp/positions.yaml
|
||||
|
||||
clients:
|
||||
- url: http://loki:3100/loki/api/v1/push
|
||||
|
||||
scrape_configs:
|
||||
- job_name: traefik
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: traefik
|
||||
__path__: /var/log/traefik/*.log
|
||||
pipeline_stages:
|
||||
- json:
|
||||
expressions:
|
||||
timestamp: time
|
||||
level: level
|
||||
msg: msg
|
||||
- timestamp:
|
||||
source: timestamp
|
||||
format: RFC3339
|
||||
|
||||
- job_name: syslog
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: syslog
|
||||
__path__: /var/log/auth.log
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env pwsh
|
||||
# LemonSec Setup Script
|
||||
# Run this script to initialize the security stack
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " LemonSec Security Stack Setup" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Check if running as administrator (not required for Docker Desktop)
|
||||
# if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||
# Write-Warning "This script should be run as Administrator for some features."
|
||||
# }
|
||||
|
||||
# Create necessary directories
|
||||
Write-Host "[1/7] Creating directories..." -ForegroundColor Green
|
||||
$dirs = @(
|
||||
"traefik/logs",
|
||||
"secrets",
|
||||
"crowdsec-data",
|
||||
"uptime-kuma-data"
|
||||
)
|
||||
foreach ($dir in $dirs) {
|
||||
if (!(Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Generate secrets
|
||||
Write-Host "[2/7] Generating secrets..." -ForegroundColor Green
|
||||
|
||||
function Generate-Secret {
|
||||
$bytes = New-Object byte[] 32
|
||||
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
||||
$rng.GetBytes($bytes)
|
||||
return [BitConverter]::ToString($bytes).Replace("-", "").ToLower()
|
||||
}
|
||||
|
||||
if (!(Test-Path "secrets/authelia_jwt_secret.txt")) {
|
||||
Generate-Secret | Set-Content -Path "secrets/authelia_jwt_secret.txt" -NoNewline
|
||||
Write-Host " ✓ Created authelia_jwt_secret.txt" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
if (!(Test-Path "secrets/authelia_session_secret.txt")) {
|
||||
Generate-Secret | Set-Content -Path "secrets/authelia_session_secret.txt" -NoNewline
|
||||
Write-Host " ✓ Created authelia_session_secret.txt" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
if (!(Test-Path "secrets/authelia_storage_key.txt")) {
|
||||
Generate-Secret | Set-Content -Path "secrets/authelia_storage_key.txt" -NoNewline
|
||||
Write-Host " ✓ Created authelia_storage_key.txt" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Set permissions (Windows doesn't have the same permission model, but we can set ACLs)
|
||||
Write-Host "[3/7] Setting permissions..." -ForegroundColor Green
|
||||
# Note: On Windows, Docker Desktop handles permissions differently
|
||||
|
||||
# Check if .env exists
|
||||
Write-Host "[4/7] Checking configuration..." -ForegroundColor Green
|
||||
if (!(Test-Path ".env")) {
|
||||
Write-Host " ⚠ .env file not found!" -ForegroundColor Yellow
|
||||
Write-Host " Copy .env.example to .env and fill in your values:" -ForegroundColor Yellow
|
||||
Write-Host " cp .env.example .env" -ForegroundColor Yellow
|
||||
Write-Host " nano .env # or your preferred editor" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Pull images
|
||||
Write-Host "[5/7] Pulling Docker images..." -ForegroundColor Green
|
||||
docker-compose pull
|
||||
|
||||
# Create external network if it doesn't exist
|
||||
Write-Host "[6/7] Setting up Docker networks..." -ForegroundColor Green
|
||||
$networks = docker network ls --format "{{.Name}}"
|
||||
if ($networks -notcontains "traefik-external") {
|
||||
docker network create traefik-external
|
||||
}
|
||||
|
||||
Write-Host "[7/7] Setup complete!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Ensure .env is configured with your Cloudflare credentials" -ForegroundColor White
|
||||
Write-Host " 2. Update authelia/users_database.yml with your users" -ForegroundColor White
|
||||
Write-Host " 3. Start the stack: docker-compose up -d" -ForegroundColor White
|
||||
Write-Host " 4. Check logs: docker-compose logs -f traefik" -ForegroundColor White
|
||||
Write-Host " 5. Generate CrowdSec API key: docker-compose exec crowdsec cscli bouncers add traefik-bouncer" -ForegroundColor White
|
||||
Write-Host " 6. Add the key to .env and restart: docker-compose up -d" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Access points:" -ForegroundColor Cyan
|
||||
Write-Host " - External: https://auth.lemonlink.eu (after DNS setup)" -ForegroundColor White
|
||||
Write-Host " - Internal: https://traefik.local.lemonlink.eu:8443 (via Tailscale)" -ForegroundColor White
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# LemonSec Environment Variables for Portainer
|
||||
# Copy these into Portainer's Environment Variables section when deploying
|
||||
|
||||
# ============================================================================
|
||||
# REQUIRED - Cloudflare DNS
|
||||
# ============================================================================
|
||||
# Your Cloudflare account email
|
||||
CF_API_EMAIL=your-email@example.com
|
||||
|
||||
# Cloudflare Global API Key (from https://dash.cloudflare.com/profile/api-tokens)
|
||||
# Note: DNS API Token can also be used as CF_DNS_API_TOKEN
|
||||
CF_API_KEY=your-cloudflare-global-api-key
|
||||
|
||||
# ============================================================================
|
||||
# REQUIRED - TrueNAS Configuration
|
||||
# ============================================================================
|
||||
# IP address of your TrueNAS Scale VM
|
||||
TRUENAS_IP=192.168.1.100
|
||||
|
||||
# Port where Nextcloud is exposed on TrueNAS
|
||||
# Check: TrueNAS → Apps → Nextcloud → Node Port
|
||||
TRUENAS_NEXTCLOUD_PORT=9001
|
||||
|
||||
# ============================================================================
|
||||
# REQUIRED - Authelia Secrets
|
||||
# Generate these with: openssl rand -hex 32
|
||||
# Or use the setup script: ./setup.ps1 (on Windows)
|
||||
# ============================================================================
|
||||
AUTHELIA_JWT_SECRET=change-me-generate-with-openssl-rand-hex-32
|
||||
AUTHELIA_SESSION_SECRET=change-me-generate-with-openssl-rand-hex-32
|
||||
AUTHELIA_STORAGE_KEY=change-me-generate-with-openssl-rand-hex-32
|
||||
|
||||
# ============================================================================
|
||||
# OPTIONAL - CrowdSec
|
||||
# Generate after first deployment:
|
||||
# docker exec crowdsec cscli bouncers add traefik-bouncer
|
||||
# Then add the key here and redeploy
|
||||
# ============================================================================
|
||||
CROWDSEC_API_KEY=
|
||||
|
||||
# ============================================================================
|
||||
# OPTIONAL - General Settings
|
||||
# ============================================================================
|
||||
# Timezone
|
||||
TZ=Europe/Stockholm
|
||||
|
||||
# Tailscale IP (for internal entrypoint binding)
|
||||
# Get with: tailscale ip -4
|
||||
TAILSCALE_IP=
|
||||
|
||||
# ============================================================================
|
||||
# OPTIONAL - Grafana (if using monitoring profile)
|
||||
# ============================================================================
|
||||
# GRAFANA_ADMIN_USER=admin
|
||||
# GRAFANA_ADMIN_PASSWORD=change-me
|
||||
# GRAFANA_OAUTH_SECRET=generate-with-authelia
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
http:
|
||||
middlewares:
|
||||
# ============================================================================
|
||||
# SECURITY MIDDLEWARES
|
||||
# ============================================================================
|
||||
|
||||
# Security Headers - Comprehensive hardening
|
||||
security-headers:
|
||||
headers:
|
||||
browserXssFilter: true
|
||||
contentTypeNosniff: true
|
||||
forceSTSHeader: true
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
stsSeconds: 31536000 # 1 year
|
||||
customFrameOptionsValue: "SAMEORIGIN"
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
|
||||
customResponseHeaders:
|
||||
X-Robots-Tag: "noindex, nofollow"
|
||||
X-Download-Options: "noopen"
|
||||
X-Permitted-Cross-Domain-Policies: "none"
|
||||
Permissions-Policy: "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
|
||||
Referrer-Policy: "strict-origin-when-cross-origin"
|
||||
sslProxyHeaders:
|
||||
X-Forwarded-Proto: https
|
||||
|
||||
# Rate Limiting - External
|
||||
rate-limit:
|
||||
rateLimit:
|
||||
average: 100
|
||||
burst: 200
|
||||
period: 1m
|
||||
|
||||
# Rate Limiting - Internal (more lenient)
|
||||
rate-limit-internal:
|
||||
rateLimit:
|
||||
average: 300
|
||||
burst: 500
|
||||
period: 1m
|
||||
|
||||
# Rate Limiting - Strict (for auth endpoints)
|
||||
rate-limit-strict:
|
||||
rateLimit:
|
||||
average: 10
|
||||
burst: 20
|
||||
period: 1m
|
||||
|
||||
# Geo-blocking (optional - configure countries as needed)
|
||||
geo-block:
|
||||
plugin:
|
||||
geo-block:
|
||||
allowLocalRequests: true
|
||||
logLocalRequests: false
|
||||
logAllowedRequests: false
|
||||
logApiRequests: true
|
||||
api: "https://get.geojs.io/v1/ip/country/{ip}"
|
||||
apiTimeoutMs: 150
|
||||
cacheSize: 1000
|
||||
forceMonthlyUpdate: true
|
||||
allowUnknownCountries: false
|
||||
unknownCountryApiResponse: "nil"
|
||||
blackListMode: false
|
||||
# Countries allowed (ISO 3166-1 alpha-2)
|
||||
countries:
|
||||
- SE # Sweden
|
||||
- NO # Norway
|
||||
- DK # Denmark
|
||||
- FI # Finland
|
||||
# Add more as needed
|
||||
|
||||
# Compress responses
|
||||
compress:
|
||||
compress:
|
||||
excludedContentTypes:
|
||||
- text/event-stream
|
||||
|
||||
# Redirect to HTTPS (fallback)
|
||||
redirect-https:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
|
||||
# Add X-Forwarded headers
|
||||
forwarded-headers:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
X-Forwarded-Port: "443"
|
||||
sslProxyHeaders:
|
||||
X-Forwarded-Proto: https
|
||||
|
||||
# Real IP from Cloudflare
|
||||
cloudflare-real-ip:
|
||||
plugin:
|
||||
cloudflarewarp:
|
||||
disableDefault: false
|
||||
|
||||
# ============================================================================
|
||||
# AUTHENTICATION MIDDLEWARES
|
||||
# ============================================================================
|
||||
|
||||
# Authelia forward auth (from Docker labels)
|
||||
authelia:
|
||||
forwardAuth:
|
||||
address: "http://authelia:9091/api/verify?rd=https://auth.lemonlink.eu/"
|
||||
trustForwardHeader: true
|
||||
authResponseHeaders:
|
||||
- Remote-User
|
||||
- Remote-Groups
|
||||
- Remote-Name
|
||||
- Remote-Email
|
||||
|
||||
# Basic Auth fallback (for emergencies)
|
||||
basic-auth:
|
||||
basicAuth:
|
||||
users:
|
||||
# Generate with: htpasswd -nb admin yourpassword | sed -e s/\$/\$\$/g
|
||||
# Replace $ with $$ in the hash for YAML
|
||||
- "admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/" # admin:admin - CHANGE THIS!
|
||||
realm: "LemonLink Secured"
|
||||
|
||||
# ============================================================================
|
||||
# CROWDSEC MIDDLEWARE
|
||||
# ============================================================================
|
||||
|
||||
crowdsec-bouncer:
|
||||
forwardAuth:
|
||||
address: http://crowdsec-bouncer-traefik:8080/api/v1/forwardAuth
|
||||
trustForwardHeader: true
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
tls:
|
||||
options:
|
||||
# Modern TLS configuration
|
||||
modern:
|
||||
minVersion: VersionTLS13
|
||||
cipherSuites: []
|
||||
curvePreferences:
|
||||
- X25519
|
||||
- P-256
|
||||
- P-384
|
||||
|
||||
# Intermediate TLS configuration (better compatibility)
|
||||
intermediate:
|
||||
minVersion: VersionTLS12
|
||||
cipherSuites:
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
|
||||
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
|
||||
curvePreferences:
|
||||
- X25519
|
||||
- P-256
|
||||
- P-384
|
||||
sniStrict: true
|
||||
|
||||
# Default options
|
||||
default:
|
||||
minVersion: VersionTLS12
|
||||
sniStrict: false
|
||||
|
||||
certificates:
|
||||
# Wildcard certificate for local domains
|
||||
- certFile: /letsencrypt/local.lemonlink.eu.crt
|
||||
keyFile: /letsencrypt/local.lemonlink.eu.key
|
||||
stores:
|
||||
- default
|
||||
|
||||
stores:
|
||||
default:
|
||||
defaultCertificate:
|
||||
certFile: /letsencrypt/local.lemonlink.eu.crt
|
||||
keyFile: /letsencrypt/local.lemonlink.eu.key
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
global:
|
||||
checkNewVersion: false
|
||||
sendAnonymousUsage: false
|
||||
|
||||
api:
|
||||
dashboard: true
|
||||
debug: false
|
||||
# Secure the API
|
||||
insecure: false
|
||||
|
||||
ping:
|
||||
entryPoint: "ping"
|
||||
|
||||
log:
|
||||
level: INFO
|
||||
filePath: /var/log/traefik/traefik.log
|
||||
format: json
|
||||
|
||||
accessLog:
|
||||
filePath: /var/log/traefik/access.log
|
||||
format: json
|
||||
bufferingSize: 100
|
||||
filters:
|
||||
statusCodes:
|
||||
- "200-299" # Success
|
||||
- "300-399" # Redirects
|
||||
- "400-599" # Errors
|
||||
retryAttempts: true
|
||||
minDuration: 10ms
|
||||
fields:
|
||||
headers:
|
||||
defaultMode: keep
|
||||
names:
|
||||
User-Agent: keep
|
||||
Authorization: drop
|
||||
Cookie: drop
|
||||
X-Forwarded-For: keep
|
||||
X-Real-Ip: keep
|
||||
|
||||
entryPoints:
|
||||
# HTTP - Redirect to HTTPS
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
permanent: true
|
||||
priority: 100
|
||||
|
||||
# HTTPS External (Cloudflare)
|
||||
websecure:
|
||||
address: ":443"
|
||||
http:
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
domains:
|
||||
- main: "lemonlink.eu"
|
||||
sans:
|
||||
- "*.lemonlink.eu"
|
||||
middlewares:
|
||||
- crowdsec-bouncer@file
|
||||
- security-headers@file
|
||||
- rate-limit@file
|
||||
forwardedHeaders:
|
||||
trustedIPs:
|
||||
# Cloudflare IPs - Keep updated!
|
||||
- "173.245.48.0/20"
|
||||
- "103.21.244.0/22"
|
||||
- "103.22.200.0/22"
|
||||
- "103.31.4.0/22"
|
||||
- "141.101.64.0/18"
|
||||
- "108.162.192.0/18"
|
||||
- "190.93.240.0/20"
|
||||
- "188.114.96.0/20"
|
||||
- "197.234.240.0/22"
|
||||
- "198.41.128.0/17"
|
||||
- "162.158.0.0/15"
|
||||
- "104.16.0.0/13"
|
||||
- "104.24.0.0/14"
|
||||
- "172.64.0.0/13"
|
||||
- "131.0.72.0/22"
|
||||
# IPv6
|
||||
- "2400:cb00::/32"
|
||||
- "2606:4700::/32"
|
||||
- "2803:f800::/32"
|
||||
- "2405:b500::/32"
|
||||
- "2405:8100::/32"
|
||||
- "2a06:98c0::/29"
|
||||
- "2c0f:f248::/32"
|
||||
proxyProtocol:
|
||||
trustedIPs:
|
||||
- "173.245.48.0/20"
|
||||
- "103.21.244.0/22"
|
||||
- "103.22.200.0/22"
|
||||
- "103.31.4.0/22"
|
||||
- "141.101.64.0/18"
|
||||
- "108.162.192.0/18"
|
||||
- "190.93.240.0/20"
|
||||
- "188.114.96.0/20"
|
||||
- "197.234.240.0/22"
|
||||
- "198.41.128.0/17"
|
||||
- "162.158.0.0/15"
|
||||
- "104.16.0.0/13"
|
||||
- "104.24.0.0/14"
|
||||
- "172.64.0.0/13"
|
||||
- "131.0.72.0/22"
|
||||
|
||||
# Internal EntryPoint (Tailscale/VPN only)
|
||||
internal:
|
||||
address: ":8443" # Use non-standard port, map via Tailscale
|
||||
http:
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
domains:
|
||||
- main: "local.lemonlink.eu"
|
||||
sans:
|
||||
- "*.local.lemonlink.eu"
|
||||
middlewares:
|
||||
- security-headers@file
|
||||
- rate-limit-internal@file
|
||||
|
||||
# Health check
|
||||
ping:
|
||||
address: ":8082"
|
||||
|
||||
providers:
|
||||
docker:
|
||||
exposedByDefault: false
|
||||
network: services
|
||||
watch: true
|
||||
file:
|
||||
directory: /dynamic
|
||||
watch: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: ${CF_API_EMAIL}
|
||||
storage: /letsencrypt/acme.json
|
||||
tlsChallenge: {}
|
||||
# Use DNS challenge for wildcard certificates
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
delayBeforeCheck: 10
|
||||
resolvers:
|
||||
- "1.1.1.1:53"
|
||||
- "8.8.8.8:53"
|
||||
|
||||
# Staging resolver for testing
|
||||
letsencrypt-staging:
|
||||
acme:
|
||||
email: ${CF_API_EMAIL}
|
||||
storage: /letsencrypt/acme-staging.json
|
||||
caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
tlsChallenge: {}
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
delayBeforeCheck: 10
|
||||
|
||||
serversTransport:
|
||||
insecureSkipVerify: false
|
||||
Loading…
Reference in New Issue