Files
org-stack/deploy.sh
Stefano Manfredi 2866bff217 first commit
2025-12-01 14:58:40 +00:00

347 lines
12 KiB
Bash

#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log() { echo -e "${BLUE}${NC} $1"; }
success() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; }
error() { echo -e "${RED}${NC} $1"; exit 1; }
echo "======================================="
echo " Organization Stack Deployment"
echo "======================================="
echo
#=============================================================================
# 1. Initialize .env if missing
#=============================================================================
if [ ! -f .env ]; then
log "Creating .env from template..."
cp .env.example .env
success ".env created"
warn "Please edit .env with your configuration, then run deploy.sh again"
exit 0
else
log ".env exists, checking configuration..."
fi
# Source the .env file
set -a
source .env
set +a
# Validate required variables
if [ -z "$BASE_DOMAIN" ] || [ -z "$REMOTE_USER" ] || [ -z "$REMOTE_HOST" ]; then
error "Required variables missing in .env: BASE_DOMAIN, REMOTE_USER, REMOTE_HOST"
fi
# Handle REMOTE_PATH (supports both absolute and relative paths)
if [ -z "$REMOTE_PATH" ]; then
# Backwards compatibility: use old REMOTE_DIR if REMOTE_PATH not set
REMOTE_PATH="${REMOTE_DIR:-org-stack}"
fi
# Determine if path is absolute or relative
if [[ "$REMOTE_PATH" = /* ]]; then
DEPLOY_PATH="$REMOTE_PATH"
log "Deploying to absolute path: $DEPLOY_PATH"
else
DEPLOY_PATH="~/$REMOTE_PATH"
log "Deploying to path relative to home: $DEPLOY_PATH"
fi
success "Configuration validated"
#=============================================================================
# 2. Sync files to remote server
#=============================================================================
log "Syncing files to remote server ${REMOTE_USER}@${REMOTE_HOST}..."
# Create remote directory with proper permissions
ssh -p ${REMOTE_PORT:-22} ${REMOTE_USER}@${REMOTE_HOST} "mkdir -p $DEPLOY_PATH" || \
error "Failed to create remote directory"
# Sync all necessary files (excluding secrets - they'll be generated remotely)
rsync -avz --delete \
--exclude='secrets/' \
--exclude='.git/' \
--exclude='*.tar.gz' \
--exclude='backups/' \
--exclude='data/' \
./ ${REMOTE_USER}@${REMOTE_HOST}:${DEPLOY_PATH}/ || \
error "Failed to sync files"
success "Files synced to remote server"
#=============================================================================
# 3. Run remote deployment script
#=============================================================================
log "Running deployment on remote server..."
# Export variables for remote script
ssh -p ${REMOTE_PORT:-22} ${REMOTE_USER}@${REMOTE_HOST} \
"export DEPLOY_PATH='${DEPLOY_PATH}' ADMIN_GROUP='${ADMIN_GROUP}'; bash -s" <<'REMOTE_SCRIPT'
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}➜${NC} $1"; }
success() { echo -e "${GREEN}✓${NC} $1"; }
error() { echo -e "${RED}✗${NC} $1"; exit 1; }
# Change to deployment directory (expand tilde if present)
eval cd "$DEPLOY_PATH"
# Source environment
set -a
source .env
set +a
#=============================================================================
# Generate secrets
#=============================================================================
log "Checking secrets..."
mkdir -p secrets/lldap secrets/authelia
# Function to generate random secret
generate_secret_file() {
local file_path=$1
local length=${2:-32}
if [ ! -f "$file_path" ]; then
openssl rand -hex $length > "$file_path"
chmod 600 "$file_path"
log "Generated: $file_path"
fi
}
# Function to generate random password (alphanumeric + special chars)
generate_password_file() {
local file_path=$1
local length=${2:-32}
if [ ! -f "$file_path" ]; then
# Generate password with letters, numbers, and some special chars
< /dev/urandom tr -dc 'A-Za-z0-9!@#%^&*' | head -c${length} > "$file_path"
chmod 600 "$file_path"
log "Generated: $file_path"
fi
}
# Generate all secret files
generate_secret_file "secrets/lldap/JWT_SECRET" 32
generate_password_file "secrets/lldap/LDAP_USER_PASS" 32
generate_secret_file "secrets/authelia/JWT_SECRET" 32
generate_secret_file "secrets/authelia/SESSION_SECRET" 32
generate_secret_file "secrets/authelia/STORAGE_ENCRYPTION_KEY" 32
generate_secret_file "secrets/authelia/OIDC_HMAC_SECRET" 32
generate_secret_file "secrets/authelia/RESET_PASSWORD_JWT_SECRET" 32
# Generate Gitea OIDC secret in .env if missing
if ! grep -q "^GITEA_OIDC_CLIENT_SECRET=" .env || [ -z "$GITEA_OIDC_CLIENT_SECRET" ]; then
GITEA_OIDC_CLIENT_SECRET=$(openssl rand -hex 36)
echo "GITEA_OIDC_CLIENT_SECRET=${GITEA_OIDC_CLIENT_SECRET}" >> .env
log "Generated GITEA_OIDC_CLIENT_SECRET"
export GITEA_OIDC_CLIENT_SECRET
fi
# Generate RSA key using Docker
if [ ! -f "secrets/authelia/OIDC_PRIVATE_KEY" ]; then
log "Generating RSA private key using Docker..."
TEMP_DIR=$(mktemp -d)
docker run --rm --user "$(id -u):$(id -g)" -v "$TEMP_DIR:/keys" authelia/authelia:latest \
authelia crypto pair rsa generate --bits 2048 --directory /keys >/dev/null 2>&1 || \
error "Failed to generate RSA key"
if [ -f "$TEMP_DIR/private.pem" ]; then
mv "$TEMP_DIR/private.pem" "secrets/authelia/OIDC_PRIVATE_KEY"
chmod 600 "secrets/authelia/OIDC_PRIVATE_KEY"
rm -rf "$TEMP_DIR"
log "Generated: OIDC_PRIVATE_KEY"
else
rm -rf "$TEMP_DIR"
error "RSA key generation failed"
fi
fi
success "All secrets ready"
#=============================================================================
# Set up permissions for multi-admin access
#=============================================================================
if [ -n "$ADMIN_GROUP" ]; then
log "Configuring multi-admin permissions for group: $ADMIN_GROUP"
# Verify group exists
if ! getent group "$ADMIN_GROUP" >/dev/null 2>&1; then
error "Group '$ADMIN_GROUP' does not exist. Create it with: sudo groupadd $ADMIN_GROUP"
fi
# Set group ownership on all files (preserve user owner)
chgrp -R "$ADMIN_GROUP" . 2>/dev/null || \
error "Failed to set group ownership. Ensure user is in group: sudo usermod -aG $ADMIN_GROUP $USER"
# Set permissions:
# - Directories: 750 (rwxr-x---) - owner full, group read+execute, others none
# - Regular files: 640 (rw-r-----) - owner read+write, group read, others none
# - Secrets: 600 (rw-------) - owner only (Docker will read as owner)
# - Executables: 750 (rwxr-x---) - owner execute, group execute
find . -type d -exec chmod 750 {} \;
find . -type f -exec chmod 640 {} \;
find . -type f -name '*.sh' -exec chmod 750 {} \;
chmod 750 manage.sh deploy.sh 2>/dev/null || true
# Secrets should be more restrictive (owner only)
if [ -d "secrets" ]; then
chmod 750 secrets
find secrets -type d -exec chmod 750 {} \;
find secrets -type f -exec chmod 600 {} \;
fi
success "Permissions configured for multi-admin access"
log "Group members can read configs and manage with: cd $DEPLOY_PATH && ./manage.sh"
else
log "Single-user mode (ADMIN_GROUP not set)"
fi
#=============================================================================
# Derive LDAP_BASE_DN
#=============================================================================
if [ "$LDAP_BASE_DN" = "AUTO" ]; then
log "Deriving LDAP_BASE_DN from BASE_DOMAIN..."
DERIVED_DN=$(echo "$BASE_DOMAIN" | sed 's/\./,dc=/g' | sed 's/^/dc=/')
sed -i "s|^LDAP_BASE_DN=.*|LDAP_BASE_DN=${DERIVED_DN}|" .env
export LDAP_BASE_DN="$DERIVED_DN"
success "LDAP_BASE_DN set to: $DERIVED_DN"
fi
#=============================================================================
# Hash Gitea OIDC client secret
#=============================================================================
log "Hashing Gitea OIDC client secret..."
# Re-source .env to get GITEA_OIDC_CLIENT_SECRET
source .env
GITEA_OIDC_CLIENT_SECRET_HASH=$(docker run --rm authelia/authelia:latest \
authelia crypto hash generate pbkdf2 --variant sha512 --password "$GITEA_OIDC_CLIENT_SECRET" 2>&1 | \
grep 'Digest:' | awk '{print $2}')
if [ -z "$GITEA_OIDC_CLIENT_SECRET_HASH" ]; then
error "Failed to hash Gitea client secret"
fi
export GITEA_OIDC_CLIENT_SECRET_HASH
success "Gitea client secret hashed"
#=============================================================================
# Set authentication policy
#=============================================================================
if [ "$REQUIRE_2FA" = "true" ]; then
export AUTH_POLICY="two_factor"
else
export AUTH_POLICY="one_factor"
fi
#=============================================================================
# Generate Caddyfile from template
#=============================================================================
log "Generating Caddyfile..."
if [ "$USE_SELF_SIGNED_CERTS" = "true" ]; then
envsubst < Caddyfile.test.template > Caddyfile
success "Generated Caddyfile (self-signed mode)"
else
envsubst < Caddyfile.production.template > Caddyfile
success "Generated Caddyfile (Let's Encrypt mode)"
fi
#=============================================================================
# Generate Authelia configuration
#=============================================================================
log "Generating Authelia configuration..."
# Use appropriate template based on SMTP_ENABLED
if [ "${SMTP_ENABLED:-false}" = "true" ]; then
log "Using SMTP email notifications..."
envsubst < authelia/configuration.yml.smtp.template > authelia/configuration.yml
else
log "Using filesystem notifier..."
envsubst < authelia/configuration.yml.filesystem.template > authelia/configuration.yml
fi
success "Generated authelia/configuration.yml"
#=============================================================================
# Start services
#=============================================================================
log "Starting services..."
docker compose up -d || error "Failed to start services"
success "Services started successfully!"
#=============================================================================
# Display service information
#=============================================================================
echo
echo "======================================="
echo " Deployment Complete!"
echo "======================================="
echo
echo "Services available at:"
echo " • Gitea: https://${GITEA_SUBDOMAIN}.${BASE_DOMAIN}"
echo " • Wiki: https://${WIKI_SUBDOMAIN}.${BASE_DOMAIN}"
echo " • Authelia: https://${AUTHELIA_SUBDOMAIN}.${BASE_DOMAIN}"
echo " • lldap: https://${LLDAP_SUBDOMAIN}.${BASE_DOMAIN}"
echo " • Registration: https://${REGISTRATION_SUBDOMAIN}.${BASE_DOMAIN}"
echo
# Read and display credentials
if [ -f "secrets/lldap/LDAP_USER_PASS" ]; then
LLDAP_PASS=$(cat secrets/lldap/LDAP_USER_PASS)
echo "lldap admin credentials:"
echo " • Username: admin"
echo " • Password: ${LLDAP_PASS}"
echo
fi
if [ -n "$GITEA_OIDC_CLIENT_SECRET" ]; then
echo "Gitea OIDC credentials:"
echo " • Client ID: gitea"
echo " • Client Secret: ${GITEA_OIDC_CLIENT_SECRET}"
echo
fi
echo -e "${YELLOW}⚠${NC} Save these credentials securely!"
echo
REMOTE_SCRIPT
if [ $? -eq 0 ]; then
success "Deployment completed successfully on ${REMOTE_HOST}!"
echo
echo "Next steps:"
echo " 1. Access lldap at https://${LLDAP_SUBDOMAIN}.${BASE_DOMAIN}"
echo " 2. Create user accounts"
echo " 3. Configure Gitea OIDC (see README.md)"
echo
else
error "Deployment failed"
fi