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