Created
March 7, 2025 06:42
-
-
Save vlados/b8e871791940b4ae66e335459c474eae to your computer and use it in GitHub Desktop.
New project deployment with Caddy server, php, nodejs, redis and postgresql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Enhanced deployment script with error handling and external templates | |
| # Define color codes for better readability | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[0;33m' | |
| NC='\033[0m' # No Color | |
| # Set global environment variables | |
| export COMPOSER_ALLOW_SUPERUSER=1 | |
| # Function for logging | |
| log() { | |
| echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | |
| } | |
| # Function for error logging and exit | |
| error() { | |
| echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" | |
| exit 1 | |
| } | |
| # Function for warnings | |
| warning() { | |
| echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" | |
| } | |
| # Generate a random secure password | |
| generate_password() { | |
| openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | |
| } | |
| # Function to uncomment and update a line in .env | |
| update_env_var() { | |
| local key="$1" | |
| local value="$2" | |
| # Check if the key exists, commented or not | |
| if grep -q "^${key}=" .env; then | |
| # Key exists and is not commented, just update it | |
| sed -i "s|^${key}=.*|${key}=${value}|" .env | |
| elif grep -q "^# ${key}=" .env; then | |
| # Key exists but is commented, uncomment and update it | |
| sed -i "s|^# ${key}=.*|${key}=${value}|" .env | |
| else | |
| # Key doesn't exist, add it | |
| echo "${key}=${value}" >> .env | |
| fi | |
| } | |
| # Check command line arguments | |
| if [ $# -lt 3 ]; then | |
| error "Usage: ./deploy.sh project_name git_repo domain_name [php_version]" | |
| fi | |
| # Set variables from arguments | |
| PROJECT_NAME=$1 | |
| GIT_REPO=$2 | |
| DOMAIN_NAME=$3 | |
| PHP_VERSION=${4:-8.3} # Default to PHP 8.3 if not provided | |
| CURRENT_USER=$(whoami) | |
| DB_USER="${PROJECT_NAME}_user" | |
| DB_PASSWORD=$(generate_password) | |
| DB_NAME="${PROJECT_NAME}" | |
| # Ensure necessary directories exist | |
| log "Creating project directories..." | |
| mkdir -p /var/www/$PROJECT_NAME || error "Failed to create project directory" | |
| mkdir -p /var/log/caddy || error "Failed to create Caddy log directory" | |
| mkdir -p /var/log/supervisor || error "Failed to create Supervisor log directory" | |
| mkdir -p /etc/caddy/conf.d || error "Failed to create Caddy config directory" | |
| # Enhanced PostgreSQL database creation with schema isolation | |
| log "Creating PostgreSQL database and user..." | |
| if sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -qw $DB_NAME; then | |
| log "Database $DB_NAME already exists." | |
| else | |
| # Create database with optimal settings | |
| sudo -u postgres psql -c "CREATE DATABASE $DB_NAME WITH ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0;" || warning "Failed to create database $DB_NAME" | |
| # Create user with limited permissions and connection limits | |
| sudo -u postgres psql -c "CREATE USER $DB_USER WITH ENCRYPTED PASSWORD '$DB_PASSWORD' CONNECTION LIMIT 30;" || warning "Failed to create user $DB_USER" | |
| # Grant specific privileges (not all) | |
| sudo -u postgres psql -c "GRANT CONNECT ON DATABASE $DB_NAME TO $DB_USER;" || warning "Failed to grant CONNECT privilege" | |
| sudo -u postgres psql -c "ALTER DATABASE $DB_NAME OWNER TO $DB_USER;" || warning "Failed to change database owner" | |
| # Connect to the database and create a schema for better isolation | |
| sudo -u postgres psql -d $DB_NAME -c "CREATE SCHEMA IF NOT EXISTS app AUTHORIZATION $DB_USER;" | |
| sudo -u postgres psql -d $DB_NAME -c "ALTER USER $DB_USER SET search_path TO app,public;" | |
| # Set optimal statement timeout for this database | |
| sudo -u postgres psql -d $DB_NAME -c "ALTER DATABASE $DB_NAME SET statement_timeout = '30s';" | |
| sudo -u postgres psql -d $DB_NAME -c "ALTER DATABASE $DB_NAME SET idle_in_transaction_session_timeout = '1min';" | |
| fi | |
| # Clone or update repository with error handling | |
| log "Deploying code from $GIT_REPO..." | |
| if [ -d "/var/www/$PROJECT_NAME/.git" ]; then | |
| log "Repository exists. Updating..." | |
| cd /var/www/$PROJECT_NAME || error "Failed to navigate to project directory" | |
| git fetch || error "Git fetch failed" | |
| git reset --hard origin/master || error "Git reset failed" | |
| else | |
| log "Fresh clone of repository..." | |
| git clone $GIT_REPO /var/www/$PROJECT_NAME || error "Git clone failed" | |
| cd /var/www/$PROJECT_NAME || error "Failed to navigate to project directory" | |
| fi | |
| # Check if .env file exists, if not, create from example | |
| if [ ! -f ".env" ]; then | |
| log "Creating .env file from example..." | |
| if [ -f ".env.example" ]; then | |
| cp .env.example .env || error "Failed to create .env file" | |
| else | |
| warning "No .env.example file found. Creating a new .env file." | |
| touch .env | |
| fi | |
| fi | |
| # Update database credentials in .env file | |
| log "Updating database credentials in .env file..." | |
| update_env_var "DB_CONNECTION" "pgsql" | |
| update_env_var "DB_HOST" "127.0.0.1" | |
| update_env_var "DB_PORT" "5432" | |
| update_env_var "DB_DATABASE" "$DB_NAME" | |
| update_env_var "DB_USERNAME" "$DB_USER" | |
| update_env_var "DB_PASSWORD" "$DB_PASSWORD" | |
| # Add additional optimization settings to .env | |
| log "Configuring optimal environment settings..." | |
| update_env_var "APP_DEBUG" "false" | |
| update_env_var "APP_ENV" "production" | |
| update_env_var "REDIS_PREFIX" "${PROJECT_NAME}_" | |
| update_env_var "QUEUE_TIMEOUT" "60" | |
| update_env_var "QUEUE_RETRY_AFTER" "90" | |
| update_env_var "TRUSTED_PROXIES" "127.0.0.1" | |
| # Update cache and queue settings for production | |
| log "Updating cache and queue settings in .env file..." | |
| update_env_var "CACHE_DRIVER" "redis" | |
| update_env_var "SESSION_DRIVER" "redis" | |
| update_env_var "QUEUE_CONNECTION" "redis" | |
| # Update APP_URL in .env | |
| update_env_var "APP_URL" "https://$DOMAIN_NAME" | |
| # Generate Laravel application key if not already set | |
| if grep -q "^APP_KEY=base64:" .env; then | |
| log "APP_KEY already set" | |
| else | |
| log "Generating Laravel application key..." | |
| php artisan key:generate || warning "Could not generate app key" | |
| fi | |
| # Install dependencies | |
| log "Installing Composer dependencies..." | |
| composer install --no-interaction --prefer-dist --optimize-autoloader || error "Composer install failed" | |
| # Add specialized caching configuration | |
| log "Setting up advanced caching..." | |
| # Create Redis cache configuration | |
| if [ -f "config/cache.php" ]; then | |
| # Update Laravel Redis cache to use serialized values | |
| sed -i "s/'client' => env('REDIS_CLIENT', 'phpredis'),/'client' => env('REDIS_CLIENT', 'phpredis'),\n 'options' => ['serializer' => \Redis::SERIALIZER_IGBINARY],/" config/cache.php 2>/dev/null || true | |
| fi | |
| # Create opcache preload file for PHP 8.3+ | |
| if [ ! -f "preload.php" ]; then | |
| cat > preload.php << 'EOL' | |
| <?php | |
| require_once __DIR__ . '/vendor/autoload.php'; | |
| if (file_exists(__DIR__ . '/bootstrap/cache/compiled.php')) { | |
| $files = require __DIR__ . '/bootstrap/cache/compiled.php'; | |
| foreach ($files as $file) { | |
| if (file_exists($file)) { | |
| opcache_compile_file($file); | |
| } | |
| } | |
| } | |
| EOL | |
| log "Created opcache preload file for improved performance" | |
| fi | |
| # Check if package.json exists before running npm | |
| if [ -f "package.json" ]; then | |
| log "Installing NPM dependencies..." | |
| npm ci || warning "NPM install failed. Continuing deployment..." | |
| log "Building frontend assets..." | |
| npm run build || warning "NPM build failed. Continuing deployment..." | |
| else | |
| log "No package.json found. Skipping NPM steps." | |
| fi | |
| # Set proper permissions | |
| log "Setting file permissions..." | |
| find /var/www/$PROJECT_NAME -type f -exec chmod 644 {} \; | |
| find /var/www/$PROJECT_NAME -type d -exec chmod 755 {} \; | |
| sudo chown -R $CURRENT_USER:www-data /var/www/$PROJECT_NAME | |
| chmod -R 775 /var/www/$PROJECT_NAME/storage /var/www/$PROJECT_NAME/bootstrap/cache | |
| # Optimize Laravel for production | |
| log "Optimizing Laravel for production..." | |
| php artisan config:clear || warning "Failed to clear config" | |
| php artisan config:cache || warning "Failed to cache config" | |
| php artisan route:clear || warning "Failed to clear routes" | |
| php artisan route:cache || warning "Failed to cache routes" | |
| php artisan view:clear || warning "Failed to clear views" | |
| php artisan view:cache || warning "Failed to cache views" | |
| php artisan event:clear || warning "Failed to clear events" | |
| php artisan event:cache || warning "Failed to cache events" | |
| # Use templates to create config files | |
| log "Creating Caddy configuration from template..." | |
| # Replace variables in Caddy template | |
| cat /etc/caddy/templates/laravel.Caddyfile.template | \ | |
| sed -e "s|{\$DOMAIN_NAME}|$DOMAIN_NAME|g" \ | |
| -e "s|{\$PROJECT_NAME}|$PROJECT_NAME|g" \ | |
| -e "s|{\$PHP_VERSION}|$PHP_VERSION|g" | \ | |
| sudo tee /etc/caddy/conf.d/$DOMAIN_NAME.Caddyfile > /dev/null || \ | |
| error "Failed to create Caddy configuration" | |
| log "Creating Supervisor configuration from template..." | |
| # Replace variables in Supervisor template | |
| cat /etc/supervisor/templates/laravel-worker.conf.template | \ | |
| sed -e "s|{\$PROJECT_NAME}|$PROJECT_NAME|g" \ | |
| -e "s|{\$PHP_VERSION}|$PHP_VERSION|g" \ | |
| -e "s|{\$USER}|$CURRENT_USER|g" | \ | |
| sudo tee /etc/supervisor/conf.d/$PROJECT_NAME-worker.conf > /dev/null || \ | |
| error "Failed to create Supervisor configuration" | |
| # Run migrations if database is configured | |
| log "Running database migrations..." | |
| if php artisan migrate --force; then | |
| log "Database migrations completed successfully" | |
| else | |
| warning "Database migrations failed. Check your database connection." | |
| fi | |
| # Reload configurations | |
| log "Reloading service configurations..." | |
| sudo supervisorctl reread || warning "Failed to reread supervisor configuration" | |
| sudo supervisorctl update || warning "Failed to update supervisor" | |
| sudo systemctl reload caddy || warning "Failed to reload Caddy" | |
| # Verify deployment | |
| log "Verifying deployment..." | |
| if sudo systemctl is-active --quiet caddy; then | |
| log "Caddy is running" | |
| else | |
| warning "Caddy is not running. Check the service status." | |
| fi | |
| log "Deployment complete! Your site is available at https://$DOMAIN_NAME" | |
| log "Database information:" | |
| log " - Database name: $DB_NAME" | |
| log " - Database user: $DB_USER" | |
| log " - Database password: $DB_PASSWORD (saved in .env)" | |
| log "If you encounter any issues, check the logs in /var/log/caddy/" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment