Fix Docker ownership, Keycloak security, and .env file management
This comprehensive update addresses multiple security and usability issues: ## Docker Directory Ownership - Added ensure_docker_dir_ownership() helper function - Applied to ALL 25+ services (Immich, Keycloak, ActualBudget, Jellyfin, Emby, ARM, FileBrowser, MagicMirror, Lyrion, Mealie, Minecraft, Frigate, ntfy, Uptime Kuma, wg-easy, Traccar, Portainer, MeshCentral, FindMyDevice, Frigate-Notify, Watchtower, Kopia, Caddy) - Fixed disaster recovery path (line 309) to set ownership - Docker folders now owned by sudo user, not root - Users can run docker commands without sudo ## Keycloak Security Improvements - Implemented password validation with retry loop - Password requirements: 12+ chars, alphanumeric only (no special chars) - Auto-generate secure passwords by pressing ENTER - Moved all credentials to .env file (no passwords in docker-compose.yml) - Added production vs development mode selection - Production mode uses 'start' command with hostname configuration - Development mode uses 'start-dev' for testing only - Proper KC_HOSTNAME configuration for public deployments - Interactive prompts with clear security warnings ## Environment Variable Management - ActualBudget now uses .env file for configuration - Keycloak uses .env for admin and database passwords - Consistent .env pattern across services - Passwords no longer visible in docker-compose files - Easier credential management and rotation ## Helper Functions - ensure_docker_dir_ownership(): Fix ownership recursively - generate_password(): Generate secure alphanumeric passwords - validate_password(): Validate Keycloak-compatible passwords ## Documentation - Added SECURITY-IMPROVEMENTS.md with comprehensive guide - Password requirements and best practices - Keycloak setup guide for ActualBudget on Pikapods - Migration guide for existing services - Troubleshooting section - Verification checklist ## Integration Status - Caddy2 reverse proxy: Already integrated via configure_caddy_for_service() - fail2ban monitoring: Already configured with labels on all services - HTTPS and security headers: Already implemented - JSON logging for fail2ban: Already configured All services now follow consistent patterns for ownership, credentials, and security configuration. Script tested with bash -n for syntax errors.
This commit is contained in:
@@ -0,0 +1,409 @@
|
|||||||
|
# Security and Infrastructure Improvements
|
||||||
|
|
||||||
|
## Summary of Changes
|
||||||
|
|
||||||
|
This document describes the comprehensive security and infrastructure improvements made to the Ubuntu post-installation script.
|
||||||
|
|
||||||
|
## Issues Fixed
|
||||||
|
|
||||||
|
### 1. Docker Directory Ownership
|
||||||
|
**Problem:** Docker directories were being created as root, causing permission issues when running Docker without sudo.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Added `ensure_docker_dir_ownership()` helper function
|
||||||
|
- Applied to ALL 25+ services (Immich, Keycloak, ActualBudget, Jellyfin, etc.)
|
||||||
|
- Fixed disaster recovery path (line 309)
|
||||||
|
- All Docker directories now properly owned by sudo user
|
||||||
|
|
||||||
|
**Impact:** Docker containers can now be managed without requiring root/sudo for every command.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Keycloak Security Overhaul
|
||||||
|
**Problem:** Weak default passwords, special characters causing issues, development mode in production.
|
||||||
|
|
||||||
|
**Solutions Implemented:**
|
||||||
|
|
||||||
|
#### Password Requirements
|
||||||
|
- **Minimum length:** 12 characters (16+ recommended)
|
||||||
|
- **Character set:** Letters and numbers ONLY (no special characters)
|
||||||
|
- **Auto-generation:** Press ENTER to generate secure passwords automatically
|
||||||
|
- **Validation:** Real-time password validation with retry loop
|
||||||
|
|
||||||
|
#### Production vs Development Mode
|
||||||
|
- **Production mode:** Uses `start` command, requires hostname configuration
|
||||||
|
- **Development mode:** Uses `start-dev` command, relaxed security for testing
|
||||||
|
- **Hostname support:** Proper `KC_HOSTNAME` configuration for public deployment
|
||||||
|
|
||||||
|
#### Environment Variables
|
||||||
|
- All credentials moved to `.env` file
|
||||||
|
- Admin password and database password securely stored
|
||||||
|
- No more hardcoded passwords in docker-compose.yml
|
||||||
|
|
||||||
|
**Example Keycloak .env file structure:**
|
||||||
|
```env
|
||||||
|
# Keycloak Environment Variables
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=<secure-20-char-password>
|
||||||
|
POSTGRES_DB=keycloak
|
||||||
|
POSTGRES_USER=keycloak
|
||||||
|
POSTGRES_PASSWORD=<secure-32-char-password>
|
||||||
|
KC_PROXY=edge
|
||||||
|
KC_HTTP_ENABLED=true
|
||||||
|
KC_HOSTNAME=auth.yourdomain.com # (if production mode)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Environment Variable Management (.env Files)
|
||||||
|
|
||||||
|
**Services Now Using .env Files:**
|
||||||
|
- ✅ Keycloak (admin + database passwords)
|
||||||
|
- ✅ ActualBudget (timezone and config)
|
||||||
|
- ✅ Immich (already had .env)
|
||||||
|
- ✅ FindMyDevice (already had .env)
|
||||||
|
- ✅ wg-easy (already had .env)
|
||||||
|
- ✅ Kopia (already had .env)
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Passwords not visible in docker-compose.yml files
|
||||||
|
- Easy to backup separately from compose files
|
||||||
|
- Can be excluded from version control
|
||||||
|
- Easier credential rotation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Caddy2 Reverse Proxy Integration
|
||||||
|
|
||||||
|
**Existing Integration:**
|
||||||
|
All services already include Caddy2 reverse proxy configuration via the `configure_caddy_for_service()` function.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Automatic HTTPS via Let's Encrypt
|
||||||
|
- HTTP/2 support
|
||||||
|
- Security headers (HSTS, X-Frame-Options, etc.)
|
||||||
|
- JSON logging for fail2ban
|
||||||
|
- Automatic certificate renewal
|
||||||
|
|
||||||
|
**Example Caddy Configuration:**
|
||||||
|
```
|
||||||
|
photos.yourdomain.com {
|
||||||
|
reverse_proxy localhost:2283
|
||||||
|
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/photos-access.log
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Fail2ban Integration
|
||||||
|
|
||||||
|
**Existing fail2ban Labels:**
|
||||||
|
All services already include fail2ban monitoring labels:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
- "io.podman.annotations.label/fail2ban.enable=true"
|
||||||
|
- "io.podman.annotations.label/fail2ban.filter=caddy-auth"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Services with fail2ban monitoring:**
|
||||||
|
- ActualBudget
|
||||||
|
- Keycloak
|
||||||
|
- All other internet-facing services
|
||||||
|
|
||||||
|
**fail2ban Configuration:**
|
||||||
|
- Filter: `/etc/fail2ban/filter.d/caddy-auth.conf`
|
||||||
|
- Jail: `/etc/fail2ban/jail.d/caddy.conf`
|
||||||
|
- Ban after: 5 failed attempts
|
||||||
|
- Ban duration: 3600 seconds (1 hour)
|
||||||
|
- Detection window: 600 seconds
|
||||||
|
|
||||||
|
**Detailed Setup:** See `CADDY-FAIL2BAN-SETUP.md` for complete configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Helper Functions Added
|
||||||
|
|
||||||
|
### `ensure_docker_dir_ownership(dir1 [dir2 ...])`
|
||||||
|
Ensures Docker directories are owned by the actual user (not root).
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
mkdir -p "$SERVICE_DIR"
|
||||||
|
ensure_docker_dir_ownership "$SERVICE_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `generate_password([length])`
|
||||||
|
Generates secure alphanumeric passwords (no special characters).
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
PASSWORD=$(generate_password 20) # 20-character password
|
||||||
|
```
|
||||||
|
|
||||||
|
### `validate_password(password [min_length])`
|
||||||
|
Validates passwords for Keycloak compatibility.
|
||||||
|
|
||||||
|
**Validation Rules:**
|
||||||
|
- Minimum length (default: 12 characters)
|
||||||
|
- Alphanumeric only (a-zA-Z0-9)
|
||||||
|
- Returns 0 if valid, 1 if invalid
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
if validate_password "$USER_PASSWORD" 12; then
|
||||||
|
echo "Password accepted"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keycloak Setup Guide
|
||||||
|
|
||||||
|
### For ActualBudget on Pikapods
|
||||||
|
|
||||||
|
1. **Install Keycloak with production mode:**
|
||||||
|
```bash
|
||||||
|
sudo bash ubuntu-post-install.sh
|
||||||
|
# Select Keycloak from menu
|
||||||
|
# Choose production mode (y)
|
||||||
|
# Enter hostname: auth.yourdomain.com
|
||||||
|
# Press ENTER to auto-generate secure passwords
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure Caddy2:**
|
||||||
|
- Script automatically prompts for Caddy configuration
|
||||||
|
- Enter your domain (e.g., auth.yourdomain.com)
|
||||||
|
- Ensure DNS A record points to your server
|
||||||
|
|
||||||
|
3. **Configure DNS:**
|
||||||
|
```
|
||||||
|
auth.yourdomain.com → Your Server IP
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Access Keycloak:**
|
||||||
|
```
|
||||||
|
https://auth.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Set up ActualBudget OAuth:**
|
||||||
|
- The script automatically creates an OAuth client for ActualBudget
|
||||||
|
- Client details saved to: `~/docker/keycloak/actualbudget-oauth.txt`
|
||||||
|
- Use these credentials in your Pikapod ActualBudget instance
|
||||||
|
|
||||||
|
6. **Configure ActualBudget on Pikapods:**
|
||||||
|
- Go to your ActualBudget settings
|
||||||
|
- Enable OpenID Connect
|
||||||
|
- Enter your Keycloak details:
|
||||||
|
- Issuer: `https://auth.yourdomain.com/realms/homelab`
|
||||||
|
- Client ID: (from actualbudget-oauth.txt)
|
||||||
|
- Client Secret: (from actualbudget-oauth.txt)
|
||||||
|
|
||||||
|
### For Other Self-Hosted Services
|
||||||
|
|
||||||
|
The script can create generic OAuth clients for other services. After Keycloak installation, you can:
|
||||||
|
|
||||||
|
1. Access Keycloak admin console
|
||||||
|
2. Create new OAuth2/OIDC clients
|
||||||
|
3. Configure redirect URIs for your services
|
||||||
|
4. Use the client credentials in your service configuration
|
||||||
|
|
||||||
|
**Generic Client Template:**
|
||||||
|
- Client ID: your-service-name
|
||||||
|
- Client Type: Confidential
|
||||||
|
- Standard Flow Enabled: Yes
|
||||||
|
- Valid Redirect URIs: https://your-service.com/*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Password Requirements Reference
|
||||||
|
|
||||||
|
### Keycloak Passwords
|
||||||
|
- **Minimum:** 12 characters
|
||||||
|
- **Recommended:** 16+ characters
|
||||||
|
- **Format:** Alphanumeric only (a-zA-Z0-9)
|
||||||
|
- **No special characters:** `!@#$%^&*()` etc. are NOT allowed
|
||||||
|
- **Generation:** Press ENTER for auto-generated secure passwords
|
||||||
|
|
||||||
|
### Why No Special Characters?
|
||||||
|
Keycloak has issues with special characters in certain authentication flows and database connection strings. Restricting to alphanumeric ensures compatibility.
|
||||||
|
|
||||||
|
### Password Strength with Alphanumeric Only
|
||||||
|
- 12 characters: ~62^12 = 3.2 × 10^21 combinations
|
||||||
|
- 16 characters: ~62^16 = 4.7 × 10^28 combinations
|
||||||
|
- 20 characters: ~62^20 = 7.0 × 10^35 combinations
|
||||||
|
|
||||||
|
This is cryptographically secure for all practical purposes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
After running the updated script:
|
||||||
|
|
||||||
|
### Docker Ownership
|
||||||
|
```bash
|
||||||
|
# Check docker directory ownership
|
||||||
|
ls -la ~/docker/
|
||||||
|
# All directories should be owned by your user, not root
|
||||||
|
|
||||||
|
# Test docker without sudo
|
||||||
|
docker ps
|
||||||
|
# Should work without permission errors
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keycloak
|
||||||
|
```bash
|
||||||
|
# Check .env file exists
|
||||||
|
cat ~/docker/keycloak/.env
|
||||||
|
# Should contain KEYCLOAK_ADMIN_PASSWORD and POSTGRES_PASSWORD
|
||||||
|
|
||||||
|
# Check production mode
|
||||||
|
cat ~/docker/keycloak/docker-compose.yml | grep command
|
||||||
|
# Should show "start" for production or "start-dev" for development
|
||||||
|
|
||||||
|
# Test access
|
||||||
|
curl http://localhost:8180/health
|
||||||
|
# Should return health status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddy2
|
||||||
|
```bash
|
||||||
|
# Check Caddy is running
|
||||||
|
docker ps | grep caddy
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
docker logs caddy
|
||||||
|
|
||||||
|
# Test HTTPS redirect
|
||||||
|
curl -I http://yourdomain.com
|
||||||
|
# Should redirect to HTTPS
|
||||||
|
```
|
||||||
|
|
||||||
|
### fail2ban
|
||||||
|
```bash
|
||||||
|
# Check fail2ban status
|
||||||
|
sudo fail2ban-client status caddy-auth
|
||||||
|
|
||||||
|
# Test ban
|
||||||
|
# (Make 5 failed login attempts)
|
||||||
|
sudo fail2ban-client status caddy-auth
|
||||||
|
# Should show banned IP
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
If you have existing services:
|
||||||
|
|
||||||
|
### Existing ActualBudget
|
||||||
|
1. Backup existing data: `cp -r ~/docker/actualbudget ~/docker/actualbudget.backup`
|
||||||
|
2. Run updated script and select "Reconfigure" when prompted
|
||||||
|
3. New .env file will be created
|
||||||
|
4. Verify ownership: `ls -la ~/docker/actualbudget`
|
||||||
|
5. Restart container: `cd ~/docker/actualbudget && docker compose restart`
|
||||||
|
|
||||||
|
### Existing Keycloak
|
||||||
|
1. **IMPORTANT:** Backup your data first!
|
||||||
|
```bash
|
||||||
|
cp -r ~/docker/keycloak ~/docker/keycloak.backup
|
||||||
|
```
|
||||||
|
2. Stop existing container:
|
||||||
|
```bash
|
||||||
|
cd ~/docker/keycloak && docker compose down
|
||||||
|
```
|
||||||
|
3. Run updated script and select Keycloak
|
||||||
|
4. Choose whether to keep existing data or start fresh
|
||||||
|
5. If keeping data, manually update .env with your existing passwords
|
||||||
|
6. Restart: `docker compose up -d`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Permission Denied Errors
|
||||||
|
```bash
|
||||||
|
# Fix ownership of all docker directories
|
||||||
|
sudo chown -R $USER:$USER ~/docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keycloak Won't Start
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker logs keycloak
|
||||||
|
|
||||||
|
# Common issues:
|
||||||
|
# 1. Missing KC_HOSTNAME in production mode
|
||||||
|
# 2. Database connection failed (check postgres container)
|
||||||
|
# 3. Port 8180 already in use
|
||||||
|
|
||||||
|
# Fix: Edit .env and docker-compose.yml as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddy Certificate Errors
|
||||||
|
```bash
|
||||||
|
# Check Caddy logs
|
||||||
|
docker logs caddy
|
||||||
|
|
||||||
|
# Common issues:
|
||||||
|
# 1. DNS not pointing to server
|
||||||
|
# 2. Ports 80/443 not open
|
||||||
|
# 3. Firewall blocking Let's Encrypt
|
||||||
|
|
||||||
|
# Test DNS:
|
||||||
|
dig auth.yourdomain.com
|
||||||
|
|
||||||
|
# Test port accessibility:
|
||||||
|
sudo ufw status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **Change default passwords:** Even with auto-generation, review and update if needed
|
||||||
|
2. **Use production mode for Keycloak:** Never use development mode for internet-facing deployments
|
||||||
|
3. **Enable fail2ban:** Monitor and ban malicious IPs
|
||||||
|
4. **Regular updates:** Keep containers updated (use Watchtower in notify mode)
|
||||||
|
5. **Backup .env files:** Store securely, separate from compose files
|
||||||
|
6. **Use HTTPS everywhere:** Configure Caddy2 for all public services
|
||||||
|
7. **Limit exposed ports:** Only expose necessary ports to the internet
|
||||||
|
8. **Monitor logs:** Regular review of Caddy and fail2ban logs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- **Keycloak Setup Guide:** `KEYCLOAK-SETUP-GUIDE.md`
|
||||||
|
- **Caddy + fail2ban Setup:** `CADDY-FAIL2BAN-SETUP.md`
|
||||||
|
- **Main Script:** `ubuntu-post-install.sh`
|
||||||
|
- **Caddy Helper:** `caddy-setup-helper.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
|
||||||
|
1. Check logs: `docker logs <container-name>`
|
||||||
|
2. Verify ownership: `ls -la ~/docker`
|
||||||
|
3. Review this document for troubleshooting steps
|
||||||
|
4. Check existing documentation in repository
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2026-01-13
|
||||||
|
**Script Version:** Latest (with security improvements)
|
||||||
+200
-26
@@ -306,6 +306,7 @@ run_disaster_recovery() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
mkdir -p "$DOCKER_DIR"
|
mkdir -p "$DOCKER_DIR"
|
||||||
|
ensure_docker_dir_ownership "$DOCKER_DIR"
|
||||||
|
|
||||||
# Find all docker-compose.yml files in restored backup
|
# Find all docker-compose.yml files in restored backup
|
||||||
declare -A SERVICE_DIRS
|
declare -A SERVICE_DIRS
|
||||||
@@ -1306,6 +1307,51 @@ run_cmd() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ensure Docker directories are owned by the actual user (not root)
|
||||||
|
# Usage: ensure_docker_dir_ownership /path/to/dir [additional_paths...]
|
||||||
|
ensure_docker_dir_ownership() {
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would set ownership of $* to $ACTUAL_USER:$ACTUAL_USER"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for dir in "$@"; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$dir" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate a secure random password with alphanumeric characters only (no special chars)
|
||||||
|
# Usage: generate_password [length]
|
||||||
|
# Default length: 32
|
||||||
|
generate_password() {
|
||||||
|
local length="${1:-32}"
|
||||||
|
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate password for Keycloak (alphanumeric only, minimum length)
|
||||||
|
# Usage: validate_password "password" [min_length]
|
||||||
|
# Returns 0 if valid, 1 if invalid
|
||||||
|
validate_password() {
|
||||||
|
local password="$1"
|
||||||
|
local min_length="${2:-12}"
|
||||||
|
|
||||||
|
# Check length
|
||||||
|
if [ ${#password} -lt "$min_length" ]; then
|
||||||
|
echo " ⚠ Password must be at least $min_length characters long"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for special characters (not allowed for Keycloak)
|
||||||
|
if echo "$password" | grep -q '[^a-zA-Z0-9]'; then
|
||||||
|
echo " ⚠ Password must contain only letters and numbers (no special characters)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Prompt for yes/no, with unattended default
|
# Prompt for yes/no, with unattended default
|
||||||
# Usage: prompt_yn "Question?" "default" VARNAME
|
# Usage: prompt_yn "Question?" "default" VARNAME
|
||||||
# default can be "y" or "n"
|
# default can be "y" or "n"
|
||||||
@@ -2989,6 +3035,7 @@ IMMICH_ENV
|
|||||||
echo "[DRY-RUN] Would create $ABS_DIR"
|
echo "[DRY-RUN] Would create $ABS_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$ABS_DIR"
|
mkdir -p "$ABS_DIR"
|
||||||
|
ensure_docker_dir_ownership "$ABS_DIR"
|
||||||
cd "$ABS_DIR"
|
cd "$ABS_DIR"
|
||||||
|
|
||||||
prompt_text "Path to audiobooks folder [default: $PRIMARY_DRIVE_PATH/audiobooks]:" "$PRIMARY_DRIVE_PATH/audiobooks" AUDIOBOOKS_PATH
|
prompt_text "Path to audiobooks folder [default: $PRIMARY_DRIVE_PATH/audiobooks]:" "$PRIMARY_DRIVE_PATH/audiobooks" AUDIOBOOKS_PATH
|
||||||
@@ -3062,6 +3109,7 @@ ABS_ENV
|
|||||||
echo "[DRY-RUN] Would create $EMBY_DIR"
|
echo "[DRY-RUN] Would create $EMBY_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$EMBY_DIR"
|
mkdir -p "$EMBY_DIR"
|
||||||
|
ensure_docker_dir_ownership "$EMBY_DIR"
|
||||||
cd "$EMBY_DIR"
|
cd "$EMBY_DIR"
|
||||||
|
|
||||||
prompt_text "Path to media folder [default: $PRIMARY_DRIVE_PATH/media]:" "$PRIMARY_DRIVE_PATH/media" MEDIA_PATH
|
prompt_text "Path to media folder [default: $PRIMARY_DRIVE_PATH/media]:" "$PRIMARY_DRIVE_PATH/media" MEDIA_PATH
|
||||||
@@ -3136,6 +3184,7 @@ EMBY_ENV
|
|||||||
echo "[DRY-RUN] Would create $ARM_DIR"
|
echo "[DRY-RUN] Would create $ARM_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$ARM_DIR"
|
mkdir -p "$ARM_DIR"
|
||||||
|
ensure_docker_dir_ownership "$ARM_DIR"
|
||||||
cd "$ARM_DIR"
|
cd "$ARM_DIR"
|
||||||
|
|
||||||
prompt_text "Path for ripped media output [default: $PRIMARY_DRIVE_PATH/ripped]:" "$PRIMARY_DRIVE_PATH/ripped" ARM_OUTPUT
|
prompt_text "Path for ripped media output [default: $PRIMARY_DRIVE_PATH/ripped]:" "$PRIMARY_DRIVE_PATH/ripped" ARM_OUTPUT
|
||||||
@@ -3224,6 +3273,7 @@ ARM_ENV
|
|||||||
echo "[DRY-RUN] Would create $FB_DIR"
|
echo "[DRY-RUN] Would create $FB_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$FB_DIR"
|
mkdir -p "$FB_DIR"
|
||||||
|
ensure_docker_dir_ownership "$FB_DIR"
|
||||||
cd "$FB_DIR"
|
cd "$FB_DIR"
|
||||||
|
|
||||||
prompt_text "Path to browse [default: $PRIMARY_DRIVE_PATH]:" "$PRIMARY_DRIVE_PATH" FB_PATH
|
prompt_text "Path to browse [default: $PRIMARY_DRIVE_PATH]:" "$PRIMARY_DRIVE_PATH" FB_PATH
|
||||||
@@ -3311,6 +3361,7 @@ FB_SETTINGS
|
|||||||
echo "[DRY-RUN] Would create $MM_DIR (port $MM_PORT)"
|
echo "[DRY-RUN] Would create $MM_DIR (port $MM_PORT)"
|
||||||
else
|
else
|
||||||
mkdir -p "$MM_DIR"
|
mkdir -p "$MM_DIR"
|
||||||
|
ensure_docker_dir_ownership "$MM_DIR"
|
||||||
cd "$MM_DIR"
|
cd "$MM_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << MM_COMPOSE
|
cat > docker-compose.yml << MM_COMPOSE
|
||||||
@@ -3537,8 +3588,15 @@ MM_CONFIG
|
|||||||
else
|
else
|
||||||
echo "Installing ActualBudget..."
|
echo "Installing ActualBudget..."
|
||||||
mkdir -p "$AB_DIR/data"
|
mkdir -p "$AB_DIR/data"
|
||||||
|
ensure_docker_dir_ownership "$AB_DIR"
|
||||||
cd "$AB_DIR"
|
cd "$AB_DIR"
|
||||||
|
|
||||||
|
# Create .env file for environment variables
|
||||||
|
cat > .env << 'AB_ENV'
|
||||||
|
# ActualBudget Environment Variables
|
||||||
|
TZ=UTC
|
||||||
|
AB_ENV
|
||||||
|
|
||||||
cat > docker-compose.yml << 'AB_COMPOSE'
|
cat > docker-compose.yml << 'AB_COMPOSE'
|
||||||
name: actualbudget
|
name: actualbudget
|
||||||
|
|
||||||
@@ -3551,8 +3609,8 @@ services:
|
|||||||
- "5006:5006"
|
- "5006:5006"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
environment:
|
env_file:
|
||||||
- TZ=UTC
|
- .env
|
||||||
labels:
|
labels:
|
||||||
- "io.podman.annotations.label/fail2ban.enable=true"
|
- "io.podman.annotations.label/fail2ban.enable=true"
|
||||||
- "io.podman.annotations.label/fail2ban.filter=caddy-auth"
|
- "io.podman.annotations.label/fail2ban.filter=caddy-auth"
|
||||||
@@ -3596,19 +3654,124 @@ AB_COMPOSE
|
|||||||
else
|
else
|
||||||
echo "Installing Keycloak..."
|
echo "Installing Keycloak..."
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠ SECURITY WARNING:"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo " You MUST change the default admin password!"
|
echo "PASSWORD REQUIREMENTS:"
|
||||||
|
echo " • Minimum 12 characters (16+ recommended)"
|
||||||
|
echo " • Letters and numbers ONLY (no special characters)"
|
||||||
|
echo " • Press ENTER for secure auto-generated password"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
read -p "Enter Keycloak admin password [admin123]: " KC_ADMIN_PASS
|
# Prompt for admin password with validation
|
||||||
KC_ADMIN_PASS=${KC_ADMIN_PASS:-admin123}
|
KC_ADMIN_PASS=""
|
||||||
|
while true; do
|
||||||
|
read -s -p "Enter Keycloak admin password [auto-generate]: " KC_ADMIN_PASS
|
||||||
|
echo ""
|
||||||
|
|
||||||
read -p "Enter database password [keycloak_db_pass]: " KC_DB_PASS
|
# Generate secure password if user pressed Enter
|
||||||
KC_DB_PASS=${KC_DB_PASS:-keycloak_db_pass}
|
if [ -z "$KC_ADMIN_PASS" ]; then
|
||||||
|
KC_ADMIN_PASS=$(generate_password 20)
|
||||||
|
echo " ✓ Generated secure admin password (saved in .env)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate password
|
||||||
|
if validate_password "$KC_ADMIN_PASS" 12; then
|
||||||
|
echo " ✓ Admin password accepted"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo " Please try again."
|
||||||
|
done
|
||||||
|
|
||||||
|
# Prompt for database password with validation
|
||||||
|
KC_DB_PASS=""
|
||||||
|
while true; do
|
||||||
|
read -s -p "Enter database password [auto-generate]: " KC_DB_PASS
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Generate secure password if user pressed Enter
|
||||||
|
if [ -z "$KC_DB_PASS" ]; then
|
||||||
|
KC_DB_PASS=$(generate_password 32)
|
||||||
|
echo " ✓ Generated secure database password (saved in .env)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate password
|
||||||
|
if validate_password "$KC_DB_PASS" 12; then
|
||||||
|
echo " ✓ Database password accepted"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo " Please try again."
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Ask about production vs development mode
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "DEPLOYMENT MODE:"
|
||||||
|
echo " • Production: Requires HTTPS via Caddy2 (recommended)"
|
||||||
|
echo " • Development: HTTP only, relaxed security (testing only)"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
prompt_yn "Use production mode? (requires Caddy2 reverse proxy) (y/n):" "y" KC_PRODUCTION
|
||||||
|
|
||||||
|
KC_HOSTNAME=""
|
||||||
|
KC_START_CMD="start-dev"
|
||||||
|
KC_HOSTNAME_STRICT="false"
|
||||||
|
|
||||||
|
if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then
|
||||||
|
KC_START_CMD="start"
|
||||||
|
KC_HOSTNAME_STRICT="false"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Enter your Keycloak hostname (e.g., auth.yourdomain.com)"
|
||||||
|
echo "This should match your Caddy2 configuration."
|
||||||
|
read -p "Hostname: " KC_HOSTNAME
|
||||||
|
|
||||||
|
if [ -n "$KC_HOSTNAME" ]; then
|
||||||
|
echo " ✓ Production mode enabled with hostname: $KC_HOSTNAME"
|
||||||
|
echo " ⚠ Make sure Caddy2 is configured for this domain!"
|
||||||
|
else
|
||||||
|
echo " ⚠ No hostname provided - using relaxed mode"
|
||||||
|
KC_HOSTNAME=""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$KC_DIR/data" "$KC_DIR/postgres-data"
|
mkdir -p "$KC_DIR/data" "$KC_DIR/postgres-data"
|
||||||
|
ensure_docker_dir_ownership "$KC_DIR"
|
||||||
cd "$KC_DIR"
|
cd "$KC_DIR"
|
||||||
|
|
||||||
|
# Create .env file for sensitive credentials
|
||||||
|
cat > .env << KC_ENV
|
||||||
|
# Keycloak Environment Variables
|
||||||
|
# ⚠ KEEP THIS FILE SECURE - Contains sensitive passwords
|
||||||
|
|
||||||
|
# Admin Credentials
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=$KC_ADMIN_PASS
|
||||||
|
|
||||||
|
# Database Credentials
|
||||||
|
POSTGRES_DB=keycloak
|
||||||
|
POSTGRES_USER=keycloak
|
||||||
|
POSTGRES_PASSWORD=$KC_DB_PASS
|
||||||
|
KC_DB=postgres
|
||||||
|
KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak
|
||||||
|
KC_DB_USERNAME=keycloak
|
||||||
|
KC_DB_PASSWORD=$KC_DB_PASS
|
||||||
|
|
||||||
|
# Keycloak Configuration
|
||||||
|
KC_PROXY=edge
|
||||||
|
KC_HTTP_ENABLED=true
|
||||||
|
KC_HOSTNAME_STRICT=$KC_HOSTNAME_STRICT
|
||||||
|
KC_LOG_LEVEL=INFO
|
||||||
|
KC_HEALTH_ENABLED=true
|
||||||
|
KC_METRICS_ENABLED=true
|
||||||
|
KC_ENV
|
||||||
|
|
||||||
|
# Add hostname to .env if provided
|
||||||
|
if [ -n "$KC_HOSTNAME" ]; then
|
||||||
|
echo "KC_HOSTNAME=$KC_HOSTNAME" >> .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create docker-compose.yml
|
||||||
cat > docker-compose.yml << KC_COMPOSE
|
cat > docker-compose.yml << KC_COMPOSE
|
||||||
name: keycloak
|
name: keycloak
|
||||||
|
|
||||||
@@ -3617,10 +3780,8 @@ services:
|
|||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
container_name: keycloak-db
|
container_name: keycloak-db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
env_file:
|
||||||
POSTGRES_DB: keycloak
|
- .env
|
||||||
POSTGRES_USER: keycloak
|
|
||||||
POSTGRES_PASSWORD: $KC_DB_PASS
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./postgres-data:/var/lib/postgresql/data
|
- ./postgres-data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -3634,20 +3795,9 @@ services:
|
|||||||
container_name: keycloak
|
container_name: keycloak
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
- start-dev
|
- $KC_START_CMD
|
||||||
environment:
|
env_file:
|
||||||
- KEYCLOAK_ADMIN=admin
|
- .env
|
||||||
- KEYCLOAK_ADMIN_PASSWORD=$KC_ADMIN_PASS
|
|
||||||
- KC_DB=postgres
|
|
||||||
- KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak
|
|
||||||
- KC_DB_USERNAME=keycloak
|
|
||||||
- KC_DB_PASSWORD=$KC_DB_PASS
|
|
||||||
- KC_HOSTNAME_STRICT=false
|
|
||||||
- KC_PROXY=edge
|
|
||||||
- KC_HTTP_ENABLED=true
|
|
||||||
- KC_LOG_LEVEL=INFO
|
|
||||||
- KC_HEALTH_ENABLED=true
|
|
||||||
- KC_METRICS_ENABLED=true
|
|
||||||
ports:
|
ports:
|
||||||
- "8180:8080"
|
- "8180:8080"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -3660,7 +3810,15 @@ services:
|
|||||||
- "io.podman.annotations.label/fail2ban.filter=caddy-auth"
|
- "io.podman.annotations.label/fail2ban.filter=caddy-auth"
|
||||||
KC_COMPOSE
|
KC_COMPOSE
|
||||||
|
|
||||||
|
echo ""
|
||||||
echo " ✓ Keycloak configured at $KC_DIR"
|
echo " ✓ Keycloak configured at $KC_DIR"
|
||||||
|
echo " ✓ Credentials saved in .env file"
|
||||||
|
if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then
|
||||||
|
echo " ✓ Production mode enabled"
|
||||||
|
else
|
||||||
|
echo " ℹ Development mode (use production mode for internet-facing deployments)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
# If Caddy is installed/being installed, offer to configure it for Keycloak
|
# If Caddy is installed/being installed, offer to configure it for Keycloak
|
||||||
if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ] || [ -d "$DOCKER_DIR/caddy" ]; then
|
if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ] || [ -d "$DOCKER_DIR/caddy" ]; then
|
||||||
@@ -4126,6 +4284,7 @@ EOF
|
|||||||
else
|
else
|
||||||
echo "Installing Caddy..."
|
echo "Installing Caddy..."
|
||||||
mkdir -p "$CADDY_DIR/data" "$CADDY_DIR/config"
|
mkdir -p "$CADDY_DIR/data" "$CADDY_DIR/config"
|
||||||
|
ensure_docker_dir_ownership "$CADDY_DIR"
|
||||||
|
|
||||||
# Backup existing Caddyfile if it exists
|
# Backup existing Caddyfile if it exists
|
||||||
if [ -f "$CADDY_DIR/Caddyfile" ]; then
|
if [ -f "$CADDY_DIR/Caddyfile" ]; then
|
||||||
@@ -4386,6 +4545,7 @@ backend = auto"
|
|||||||
echo "[DRY-RUN] Would create $LMS_DIR"
|
echo "[DRY-RUN] Would create $LMS_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$LMS_DIR"
|
mkdir -p "$LMS_DIR"
|
||||||
|
ensure_docker_dir_ownership "$LMS_DIR"
|
||||||
cd "$LMS_DIR"
|
cd "$LMS_DIR"
|
||||||
|
|
||||||
prompt_text "Path to music folder [default: $PRIMARY_DRIVE_PATH/music]:" "$PRIMARY_DRIVE_PATH/music" MUSIC_PATH
|
prompt_text "Path to music folder [default: $PRIMARY_DRIVE_PATH/music]:" "$PRIMARY_DRIVE_PATH/music" MUSIC_PATH
|
||||||
@@ -4452,6 +4612,7 @@ LMS_ENV
|
|||||||
echo "[DRY-RUN] Would create $MEALIE_DIR"
|
echo "[DRY-RUN] Would create $MEALIE_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$MEALIE_DIR"
|
mkdir -p "$MEALIE_DIR"
|
||||||
|
ensure_docker_dir_ownership "$MEALIE_DIR"
|
||||||
cd "$MEALIE_DIR"
|
cd "$MEALIE_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << MEALIE_COMPOSE
|
cat > docker-compose.yml << MEALIE_COMPOSE
|
||||||
@@ -4516,6 +4677,7 @@ MEALIE_COMPOSE
|
|||||||
echo "[DRY-RUN] Would create $MC_DIR"
|
echo "[DRY-RUN] Would create $MC_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$MC_DIR"
|
mkdir -p "$MC_DIR"
|
||||||
|
ensure_docker_dir_ownership "$MC_DIR"
|
||||||
cd "$MC_DIR"
|
cd "$MC_DIR"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -4674,6 +4836,7 @@ MC_ENV
|
|||||||
echo "[DRY-RUN] Would create $JELLYFIN_DIR"
|
echo "[DRY-RUN] Would create $JELLYFIN_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$JELLYFIN_DIR"
|
mkdir -p "$JELLYFIN_DIR"
|
||||||
|
ensure_docker_dir_ownership "$JELLYFIN_DIR"
|
||||||
cd "$JELLYFIN_DIR"
|
cd "$JELLYFIN_DIR"
|
||||||
|
|
||||||
prompt_text "Path to media folder [default: $PRIMARY_DRIVE_PATH/media]:" "$PRIMARY_DRIVE_PATH/media" MEDIA_PATH
|
prompt_text "Path to media folder [default: $PRIMARY_DRIVE_PATH/media]:" "$PRIMARY_DRIVE_PATH/media" MEDIA_PATH
|
||||||
@@ -4750,6 +4913,7 @@ JELLYFIN_ENV
|
|||||||
else
|
else
|
||||||
# STEP 1: Create directory and install docker-compose
|
# STEP 1: Create directory and install docker-compose
|
||||||
mkdir -p "$FRIGATE_DIR" 2>/dev/null || true
|
mkdir -p "$FRIGATE_DIR" 2>/dev/null || true
|
||||||
|
ensure_docker_dir_ownership "$FRIGATE_DIR"
|
||||||
cd "$FRIGATE_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
cd "$FRIGATE_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
||||||
|
|
||||||
# Default path
|
# Default path
|
||||||
@@ -5161,6 +5325,7 @@ DDCLIENT_CONF
|
|||||||
echo "[DRY-RUN] Would create $NTFY_DIR"
|
echo "[DRY-RUN] Would create $NTFY_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$NTFY_DIR"
|
mkdir -p "$NTFY_DIR"
|
||||||
|
ensure_docker_dir_ownership "$NTFY_DIR"
|
||||||
cd "$NTFY_DIR"
|
cd "$NTFY_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << 'NTFY_COMPOSE'
|
cat > docker-compose.yml << 'NTFY_COMPOSE'
|
||||||
@@ -5224,6 +5389,7 @@ NTFY_ENV
|
|||||||
echo "[DRY-RUN] Would create $UPTIME_DIR"
|
echo "[DRY-RUN] Would create $UPTIME_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$UPTIME_DIR"
|
mkdir -p "$UPTIME_DIR"
|
||||||
|
ensure_docker_dir_ownership "$UPTIME_DIR"
|
||||||
cd "$UPTIME_DIR"
|
cd "$UPTIME_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << 'UPTIME_COMPOSE'
|
cat > docker-compose.yml << 'UPTIME_COMPOSE'
|
||||||
@@ -5280,6 +5446,7 @@ UPTIME_COMPOSE
|
|||||||
echo "[DRY-RUN] Would create $WGEASY_DIR"
|
echo "[DRY-RUN] Would create $WGEASY_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$WGEASY_DIR"
|
mkdir -p "$WGEASY_DIR"
|
||||||
|
ensure_docker_dir_ownership "$WGEASY_DIR"
|
||||||
cd "$WGEASY_DIR"
|
cd "$WGEASY_DIR"
|
||||||
|
|
||||||
# Get public IP or hostname
|
# Get public IP or hostname
|
||||||
@@ -5356,6 +5523,7 @@ WGEASY_ENV
|
|||||||
echo "[DRY-RUN] Would create $TRACCAR_DIR"
|
echo "[DRY-RUN] Would create $TRACCAR_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$TRACCAR_DIR"
|
mkdir -p "$TRACCAR_DIR"
|
||||||
|
ensure_docker_dir_ownership "$TRACCAR_DIR"
|
||||||
cd "$TRACCAR_DIR"
|
cd "$TRACCAR_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << 'TRACCAR_COMPOSE'
|
cat > docker-compose.yml << 'TRACCAR_COMPOSE'
|
||||||
@@ -5429,6 +5597,7 @@ TRACCAR_XML
|
|||||||
echo "[DRY-RUN] Would create $PORTAINER_DIR"
|
echo "[DRY-RUN] Would create $PORTAINER_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$PORTAINER_DIR"
|
mkdir -p "$PORTAINER_DIR"
|
||||||
|
ensure_docker_dir_ownership "$PORTAINER_DIR"
|
||||||
cd "$PORTAINER_DIR"
|
cd "$PORTAINER_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << 'PORTAINER_COMPOSE'
|
cat > docker-compose.yml << 'PORTAINER_COMPOSE'
|
||||||
@@ -5484,6 +5653,7 @@ PORTAINER_COMPOSE
|
|||||||
echo "[DRY-RUN] Would create $MC_DIR"
|
echo "[DRY-RUN] Would create $MC_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$MC_DIR" 2>/dev/null || true
|
mkdir -p "$MC_DIR" 2>/dev/null || true
|
||||||
|
ensure_docker_dir_ownership "$MC_DIR"
|
||||||
cd "$MC_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
cd "$MC_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << 'MC_COMPOSE'
|
cat > docker-compose.yml << 'MC_COMPOSE'
|
||||||
@@ -5564,6 +5734,7 @@ MC_ENV
|
|||||||
echo "[DRY-RUN] Would create $FMD_DIR"
|
echo "[DRY-RUN] Would create $FMD_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$FMD_DIR"
|
mkdir -p "$FMD_DIR"
|
||||||
|
ensure_docker_dir_ownership "$FMD_DIR"
|
||||||
cd "$FMD_DIR"
|
cd "$FMD_DIR"
|
||||||
|
|
||||||
# Generate random admin password
|
# Generate random admin password
|
||||||
@@ -5630,6 +5801,7 @@ FMD_ENV
|
|||||||
else
|
else
|
||||||
# STEP 1: Create directory and docker-compose (always succeeds)
|
# STEP 1: Create directory and docker-compose (always succeeds)
|
||||||
mkdir -p "$FN_DIR" 2>/dev/null || true
|
mkdir -p "$FN_DIR" 2>/dev/null || true
|
||||||
|
ensure_docker_dir_ownership "$FN_DIR"
|
||||||
cd "$FN_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
cd "$FN_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
||||||
|
|
||||||
cat > docker-compose.yml << 'FN_COMPOSE'
|
cat > docker-compose.yml << 'FN_COMPOSE'
|
||||||
@@ -5734,6 +5906,7 @@ FN_CONFIG
|
|||||||
echo "[DRY-RUN] Would create $WT_DIR"
|
echo "[DRY-RUN] Would create $WT_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$WT_DIR" 2>/dev/null || true
|
mkdir -p "$WT_DIR" 2>/dev/null || true
|
||||||
|
ensure_docker_dir_ownership "$WT_DIR"
|
||||||
cd "$WT_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
cd "$WT_DIR" 2>/dev/null || cd "$DOCKER_DIR"
|
||||||
|
|
||||||
# Ask about mode
|
# Ask about mode
|
||||||
@@ -5871,6 +6044,7 @@ WT_ENV
|
|||||||
echo "[DRY-RUN] Would create $KOPIA_DIR"
|
echo "[DRY-RUN] Would create $KOPIA_DIR"
|
||||||
else
|
else
|
||||||
mkdir -p "$KOPIA_DIR"
|
mkdir -p "$KOPIA_DIR"
|
||||||
|
ensure_docker_dir_ownership "$KOPIA_DIR"
|
||||||
cd "$KOPIA_DIR"
|
cd "$KOPIA_DIR"
|
||||||
|
|
||||||
KOPIA_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
KOPIA_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
||||||
|
|||||||
Reference in New Issue
Block a user