first commit
This commit is contained in:
346
deploy.sh
Normal file
346
deploy.sh
Normal file
@@ -0,0 +1,346 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user