#!/bin/bash # --- Check if running with bash --- if [ -z "$BASH_VERSION" ]; then echo -e "\033[0;31m[ERROR]\033[0m This script must be run with bash, not sh." >&2 echo "Please use 'bash $0' to run this script." >&2 exit 1 fi # Set a standard locale to prevent perl warnings from PostgreSQL export LC_ALL=C.UTF-8 set -e # Exit immediately on error # --- Color definitions --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # --- Logging functions --- log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_step() { echo -e "\n${CYAN}${BOLD}=== $1 ===${NC}"; } # --- Welcome Banner --- echo -e "${BLUE}${BOLD}╔══════════════════════════════════════════════════════════════════╗" echo -e "║ MYCELIA FAMILY TREE - Minimal Deployment ║" echo -e "╚══════════════════════════════════════════════════════════════════╝${NC}" # --- Minimal User Input --- log_step "ESSENTIAL CONFIGURATION" # Auto-detect IP, User, and Domain DETECTED_IP=$(curl -4 -s --connect-timeout 10 ifconfig.me 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}' || echo "") DETECTED_USER=$(whoami) DETECTED_DOMAIN=$(hostname -f 2>/dev/null || echo "vps-9e0eed30.vps.ovh.net") # 1. Server IP read -p "Enter your server's public IP address [$DETECTED_IP]: " SERVER_IP SERVER_IP=${SERVER_IP:-$DETECTED_IP} # 2. Domain Name read -p "Enter your domain name [${DETECTED_DOMAIN}]: " DOMAIN DOMAIN=${DOMAIN:-$DETECTED_DOMAIN} # Validate the final domain while [[ ! "$DOMAIN" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]; do log_error "Invalid domain format. Please try again." read -p "Enter your domain name: " DOMAIN done # 3. Admin Email read -p "Enter your email (for SSL certificate) [admin@${DOMAIN}]: " EMAIL EMAIL=${EMAIL:-"admin@${DOMAIN}"} # 4. Django Admin Username read -p "Enter Django admin username [admin]: " DJANGO_SUPERUSER_USER DJANGO_SUPERUSER_USER=${DJANGO_SUPERUSER_USER:-"admin"} # 5. Django Admin Email read -p "Enter Django admin email [${EMAIL}]: " DJANGO_SUPERUSER_EMAIL DJANGO_SUPERUSER_EMAIL=${DJANGO_SUPERUSER_EMAIL:-"$EMAIL"} log_info "Configuration complete." # --- Hardcoded & Derived Defaults --- REMOTE_USER=$DETECTED_USER PROJECT_NAME="mycelia" PROJECT_PARENT_DIR="/var/www" PROJECT_DIR="${PROJECT_PARENT_DIR}/${DOMAIN}" VENV_PARENT_DIR="/home/${REMOTE_USER}/venvs" VENV_PATH="${VENV_PARENT_DIR}/${PROJECT_NAME}" COREAPPNAME="core" SETTINGS_FILENAME="settings_improved" DB_NAME="${PROJECT_NAME}_db" DB_HOST="localhost" DB_PORT="5432" REDIS_HOST="127.0.0.1" REDIS_PORT="6379" REDIS_DB="1" EMAIL_HOST="smtp.gmail.com" EMAIL_PORT="587" EMAIL_USE_TLS="True" EMAIL_HOST_USER="your-email@gmail.com" # Placeholder EMAIL_HOST_PASSWORD="your-app-password" # Placeholder DEFAULT_FROM_EMAIL="your-email@gmail.com" # Placeholder RECAPTCHA_PUBLIC_KEY="your-recaptcha-public-key" # Placeholder RECAPTCHA_PRIVATE_KEY="your-recaptcha-private-key" # Placeholder ENABLE_SSL_REDIRECT="True" ENABLE_SUBDOMAIN_REDIRECTS="True" FORCE_SUBDOMAIN_COOKIES="False" CPU_COUNT=$(nproc 2>/dev/null || echo "2") GUNICORN_WORKERS=$((CPU_COUNT * 2 + 1)) TIME_ZONE="Europe/Paris" ENABLE_NOTIFICATIONS="True" ENABLE_ELASTICSEARCH="False" ENABLE_CELERY="True" LATEST_ZIP_FILENAME="mycelia-latest.zip" FILES_BASE_URL="https://mycelia-project.com/files" ZIP_URL="${FILES_BASE_URL}/${LATEST_ZIP_FILENAME}" # --- AUTOMATIC CREDENTIAL GENERATION --- log_info "Generating secure, random credentials..." DB_USER="mycelia_$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)" DB_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24) DJANGO_SECRET_KEY=$(LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c 50) DJANGO_SUPERUSER_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16) DJANGO_WSGI_MODULE=${COREAPPNAME} # --- BEGIN INSTALLATION --- log_step "STARTING AUTOMATED INSTALLATION" SUDO="" if [ "$EUID" -ne 0 ]; then SUDO="sudo"; fi log "[1/8] Installing system packages..." export DEBIAN_FRONTEND=noninteractive $SUDO apt-get update -qq $SUDO apt-get install -y -qq nginx python3-pip python3-dev python3-venv postgresql postgresql-contrib curl unzip certbot python3-certbot-nginx ufw redis-server file gnupg apt-transport-https log "[2/8] Configuring PostgreSQL & Redis..." $SUDO systemctl -q enable --now postgresql redis-server PG_VERSION_DIR=$($SUDO find /etc/postgresql/ -maxdepth 1 -type d -name "[0-9]*" | sort -V | tail -n 1) PG_HBA_CONF="${PG_VERSION_DIR}/main/pg_hba.conf" $SUDO bash -c "cat > ${PG_HBA_CONF}" << EOF local all postgres peer local all all md5 host all all 127.0.0.1/32 md5 host all all ::1/128 md5 EOF $SUDO systemctl restart postgresql sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${DB_NAME};" &>/dev/null sudo -u postgres psql -c "DROP ROLE IF EXISTS ${DB_USER};" &>/dev/null sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME};" sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" sudo -u postgres psql -c "ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};" log "[3/8] Downloading and fixing project files..." $SUDO mkdir -p "${PROJECT_DIR}" log_info "Clearing project directory for a clean install..." $SUDO rm -rf "${PROJECT_DIR}"/* $SUDO rm -rf "${PROJECT_DIR}"/.[!.]* 2>/dev/null || true $SUDO chown "${REMOTE_USER}":"${REMOTE_USER}" "${PROJECT_DIR}" TEMP_EXTRACT_DIR=$(mktemp -d) curl -L --progress-bar -o /tmp/project.zip "${ZIP_URL}" unzip -q /tmp/project.zip -d "${TEMP_EXTRACT_DIR}" SOURCE_DIR=$(find "${TEMP_EXTRACT_DIR}" -name "manage.py" -printf '%h' -quit) if [ -z "${SOURCE_DIR}" ]; then log_error "Could not find 'manage.py' in the downloaded ZIP file." exit 1 fi log_info "Project root found, moving files..." shopt -s dotglob $SUDO mv "${SOURCE_DIR}"/* "${PROJECT_DIR}"/ shopt -u dotglob rm /tmp/project.zip && rm -rf "${TEMP_EXTRACT_DIR}" $SUDO chown -R "${REMOTE_USER}":"${REMOTE_USER}" "${PROJECT_DIR}" if [ -f "${PROJECT_DIR}/celery.py" ]; then log_info "Removing conflicting 'celery.py' file from project root." $SUDO rm "${PROJECT_DIR}/celery.py" fi log "[4/8] Setting up virtual environment..." $SUDO mkdir -p "${VENV_PARENT_DIR}" $SUDO chown -R "${REMOTE_USER}":"${REMOTE_USER}" "${VENV_PARENT_DIR}" sudo -u "${REMOTE_USER}" python3 -m venv "${VENV_PATH}" "${VENV_PATH}"/bin/pip install --upgrade pip -q "${VENV_PATH}"/bin/pip install -r "${PROJECT_DIR}"/requirements.txt -q "${VENV_PATH}"/bin/pip install gunicorn psycopg2-binary python-decouple redis -q log "[5/8] Configuring system services (Gunicorn & Nginx)..." $SUDO usermod -a -G "${REMOTE_USER}" www-data $SUDO bash -c "cat > /etc/systemd/system/gunicorn_${PROJECT_NAME}.service" << EOF [Unit] Description=gunicorn daemon for ${PROJECT_NAME} After=network.target [Service] User=${REMOTE_USER} Group=www-data WorkingDirectory=${PROJECT_DIR} EnvironmentFile=${PROJECT_DIR}/.env ExecStart=${VENV_PATH}/bin/gunicorn --workers ${GUNICORN_WORKERS} --bind unix:${PROJECT_DIR}/${PROJECT_NAME}.sock ${DJANGO_WSGI_MODULE}.wsgi:application [Install] WantedBy=multi-user.target EOF $SUDO systemctl daemon-reload $SUDO systemctl -q enable "gunicorn_${PROJECT_NAME}" $SUDO bash -c "cat > /etc/nginx/sites-available/${DOMAIN}" << EOF server { listen 80; server_name ${DOMAIN} www.${DOMAIN} *.${DOMAIN}; location /static/ { alias ${PROJECT_DIR}/staticfiles/; } location /media/ { root ${PROJECT_DIR}; } # Pass required headers to the backend application. location / { proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_pass http://unix:${PROJECT_DIR}/${PROJECT_NAME}.sock; } } EOF $SUDO ln -sfn "/etc/nginx/sites-available/${DOMAIN}" "/etc/nginx/sites-enabled/${DOMAIN}" $SUDO rm -f /etc/nginx/sites-enabled/default $SUDO nginx -t && $SUDO systemctl restart nginx log "[6/8] Creating .env configuration file..." REDIS_URL="redis://${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}" sudo -u "${REMOTE_USER}" bash -c "cat > ${PROJECT_DIR}/.env" << EOF DEBUG=False SECRET_KEY='${DJANGO_SECRET_KEY}' MAIN_DOMAIN=${DOMAIN} ALLOWED_HOSTS=${SERVER_IP},${DOMAIN},www.${DOMAIN},fr.${DOMAIN},tr.${DOMAIN},ku.${DOMAIN} SECURE_SSL_REDIRECT=${ENABLE_SSL_REDIRECT} ENABLE_SUBDOMAIN_REDIRECTS=${ENABLE_SUBDOMAIN_REDIRECTS} FORCE_SUBDOMAIN_COOKIES=${FORCE_SUBDOMAIN_COOKIES} DATABASE_NAME=${DB_NAME} DATABASE_USER=${DB_USER} DATABASE_PASSWORD='${DB_PASSWORD}' DATABASE_HOST=${DB_HOST} DATABASE_PORT=${DB_PORT} REDIS_URL=${REDIS_URL} EMAIL_HOST=${EMAIL_HOST} EMAIL_PORT=${EMAIL_PORT} EMAIL_USE_TLS=${EMAIL_USE_TLS} EMAIL_HOST_USER=${EMAIL_HOST_USER} EMAIL_HOST_PASSWORD='${EMAIL_HOST_PASSWORD}' DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL} FAMILY_ADMIN_EMAIL=${EMAIL} RECAPTCHA_PUBLIC_KEY=${RECAPTCHA_PUBLIC_KEY} RECAPTCHA_PRIVATE_KEY=${RECAPTCHA_PRIVATE_KEY} TIME_ZONE=${TIME_ZONE} ENABLE_NOTIFICATIONS=${ENABLE_NOTIFICATIONS} ENABLE_ELASTICSEARCH=${ENABLE_ELASTICSEARCH} ENABLE_CELERY=${ENABLE_CELERY} EOF $SUDO chmod 600 "${PROJECT_DIR}"/.env log "[7/8] Setting up Django application..." export DJANGO_SETTINGS_MODULE="${DJANGO_WSGI_MODULE}.${SETTINGS_FILENAME}" sudo -u "${REMOTE_USER}" "${VENV_PATH}"/bin/python "${PROJECT_DIR}"/manage.py migrate --noinput sudo -u "${REMOTE_USER}" "${VENV_PATH}"/bin/python "${PROJECT_DIR}"/manage.py collectstatic --noinput --clear export DJANGO_SUPERUSER_USERNAME=${DJANGO_SUPERUSER_USER} export DJANGO_SUPERUSER_PASSWORD=${DJANGO_SUPERUSER_PASSWORD} export DJANGO_SUPERUSER_EMAIL=${DJANGO_SUPERUSER_EMAIL} sudo -u "${REMOTE_USER}" "${VENV_PATH}"/bin/python "${PROJECT_DIR}"/manage.py createsuperuser --noinput 2>/dev/null || log_warning "Superuser may already exist." $SUDO systemctl start "gunicorn_${PROJECT_NAME}" log "[8/8] Configuring Firewall & SSL Certificate..." $SUDO ufw --force reset > /dev/null $SUDO ufw allow 'Nginx Full' > /dev/null $SUDO ufw allow 'OpenSSH' > /dev/null $SUDO ufw --force enable CERT_DOMAINS="-d ${DOMAIN} -d www.${DOMAIN} -d *.${DOMAIN}" # Try to obtain a wildcard SSL certificate for all subdomains using DNS challenge if ! $SUDO certbot certonly --manual --preferred-challenges dns --agree-tos --no-eff-email --manual-public-ip-logging-ok --email "${EMAIL}" ${CERT_DOMAINS} --non-interactive --manual-cleanup-hook "echo 'Cleanup DNS challenge'"; then log_warning "Wildcard SSL certificate could not be obtained. Site will be available via HTTP." $SUDO sed -i 's/SECURE_SSL_REDIRECT=True/SECURE_SSL_REDIRECT=False/' "${PROJECT_DIR}"/.env $SUDO systemctl restart "gunicorn_${PROJECT_NAME}" else log "Wildcard SSL certificate installed successfully." # Update Nginx config to use the wildcard certificate $SUDO sed -i "s|ssl_certificate .*|ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;|g" /etc/nginx/sites-available/${DOMAIN} $SUDO sed -i "s|ssl_certificate_key .*|ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;|g" /etc/nginx/sites-available/${DOMAIN} $SUDO nginx -t && $SUDO systemctl reload nginx fi # --- DEPLOYMENT COMPLETE --- log_step "DEPLOYMENT COMPLETED!" PROTOCOL="http" if $SUDO certbot certificates 2>/dev/null | grep -q "Domains:.* ${DOMAIN}"; then PROTOCOL="https" fi echo -e "\n${BLUE}${BOLD}=== YOUR MYCELIA FAMILY TREE IS READY ===${NC}\n" echo -e "${CYAN}🌐 Website Access:${NC}\n Main site: ${PROTOCOL}://${DOMAIN}\n Admin panel: ${PROTOCOL}://${DOMAIN}/admin/\n" echo -e "${CYAN}👤 Admin Credentials:${NC}\n Username: ${DJANGO_SUPERUSER_USER}\n Password: ${DJANGO_SUPERUSER_PASSWORD}\n" echo -e "${CYAN}🔧 Useful Commands:${NC}\n Restart application: $SUDO systemctl restart gunicorn_${PROJECT_NAME}\n View logs: journalctl -u gunicorn_${PROJECT_NAME} -f\n" echo -e "${YELLOW}⚠️ IMPORTANT ⚠️${NC}" echo "To enable sending emails and reCAPTCHA, you must edit the .env file:" echo " ${SUDO} nano ${PROJECT_DIR}/.env" echo "Update the placeholder values for EMAIL_ and RECAPTCHA_, then restart the app." echo echo -e "${RED}${BOLD}*** SAVE YOUR ADMIN PASSWORD NOW: ${DJANGO_SUPERUSER_PASSWORD} ***${NC}\n" log "Deployment finished successfully! 🎉"