Skip to content

Instantly share code, notes, and snippets.

@vlados
Created March 7, 2025 06:42
Show Gist options
  • Select an option

  • Save vlados/b8e871791940b4ae66e335459c474eae to your computer and use it in GitHub Desktop.

Select an option

Save vlados/b8e871791940b4ae66e335459c474eae to your computer and use it in GitHub Desktop.
New project deployment with Caddy server, php, nodejs, redis and postgresql
#!/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