diff --git a/.docker/development/README.md b/.docker/development/README.md new file mode 100644 index 0000000..d271186 --- /dev/null +++ b/.docker/development/README.md @@ -0,0 +1,109 @@ +# Development Docker Setup + +This directory contains Docker Compose files for local development using Laravel Sail. + +## Files + +- **docker-compose.yml** - Base Sail configuration for backend (Laravel) and MySQL +- **docker-compose.override.yml** - Development overrides: + - Podman/SELinux compatibility (`:Z` volume flags) + - Standard MySQL image (instead of mysql-server) + - Frontend container (Node.js development server) + +## Quick Start + +Use the automated setup script from the project root: + +```bash +./bin/start-dev +``` + +This script will: +1. Create `.env` if it doesn't exist +2. Install backend dependencies (composer) +3. Start all containers (backend, MySQL, frontend) +4. Run database migrations +5. Optionally seed the database + +## Manual Usage + +### Start all services + +```bash +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + up -d +``` + +### View logs + +```bash +# All services +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + logs -f + +# Specific service +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + logs -f frontend +``` + +### Run backend commands + +```bash +# Run migrations +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + exec backend php artisan migrate + +# Run tinker +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + exec backend php artisan tinker + +# Run tests +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + exec backend php artisan test +``` + +### Stop all services + +```bash +docker compose -f .docker/development/docker-compose.yml \ + -f .docker/development/docker-compose.override.yml \ + down +``` + +## Services + +| Service | Port | Description | +|---------|------|-------------| +| **backend** | 8000 | Laravel API (PHP 8.4 + Sail) | +| **mysql** | 3306 | MySQL 8.0 database | +| **frontend** | 5173 | Vite dev server (React Router) | + +## Environment Variables + +The backend reads from `backend/.env`. Key variables: + +- `APP_PORT` - Backend port (default: 80, set to 8000 for Podman) +- `DB_*` - Database connection settings +- `FORWARD_DB_PORT` - Exposed MySQL port (default: 3306) + +## Volume Mounts + +- `backend/` → `/var/www/html` (backend container) +- `frontend/` → `/app` (frontend container) +- MySQL data persists in named volume `sail-mysql` + +## Podman Compatibility + +The `:Z` flag on volumes enables SELinux compatibility for Podman users. This is automatically included in the override file. + +## Notes + +- The frontend container automatically runs `npm install` and `npm run dev` on startup +- Laravel Sail handles PHP-FPM, supervisor, and other backend services +- Hot reload is enabled for both frontend (Vite HMR) and backend (file watchers) diff --git a/.docker/development/docker-compose.override.yml b/.docker/development/docker-compose.override.yml new file mode 100644 index 0000000..6457d10 --- /dev/null +++ b/.docker/development/docker-compose.override.yml @@ -0,0 +1,25 @@ +services: + backend: + volumes: + - '../../backend:/var/www/html:Z' + + mysql: + image: 'docker.io/library/mysql:8.0' + environment: + MYSQL_USER: '${DB_USERNAME}' + MYSQL_PASSWORD: '${DB_PASSWORD}' + volumes: + - 'sail-mysql:/var/lib/mysql:Z' + + frontend: + image: 'docker.io/library/node:22-alpine' + working_dir: /app + command: sh -c "npm install && npm run dev -- --host" + volumes: + - '../../frontend:/app:Z' + ports: + - '5173:5173' + networks: + - sail + depends_on: + - backend diff --git a/.docker/development/docker-compose.yml b/.docker/development/docker-compose.yml new file mode 100644 index 0000000..b6f93b5 --- /dev/null +++ b/.docker/development/docker-compose.yml @@ -0,0 +1,54 @@ +services: + backend: + build: + context: '../../backend/vendor/laravel/sail/runtimes/8.4' + dockerfile: Dockerfile + args: + WWWGROUP: '${WWWGROUP}' + image: 'sail-8.4/app' + extra_hosts: + - 'host.docker.internal:host-gateway' + ports: + - '${APP_PORT:-80}:80' + - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' + environment: + WWWUSER: '${WWWUSER}' + LARAVEL_SAIL: 1 + XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' + XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' + IGNITION_LOCAL_SITES_PATH: '${PWD}' + volumes: + - '../../backend:/var/www/html:Z' + networks: + - sail + depends_on: + - mysql + mysql: + image: 'docker.io/mysql/mysql-server:8.0' + ports: + - '${FORWARD_DB_PORT:-3306}:3306' + environment: + MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' + MYSQL_ROOT_HOST: '%' + MYSQL_DATABASE: '${DB_DATABASE}' + MYSQL_USER: '${DB_USERNAME}' + MYSQL_PASSWORD: '${DB_PASSWORD}' + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + volumes: + - 'sail-mysql:/var/lib/mysql:Z' + networks: + - sail + healthcheck: + test: + - CMD + - mysqladmin + - ping + - '-p${DB_PASSWORD}' + retries: 3 + timeout: 5s +networks: + sail: + driver: bridge +volumes: + sail-mysql: + driver: local diff --git a/.docker/production/.dockerignore b/.docker/production/.dockerignore new file mode 100644 index 0000000..b4144a4 --- /dev/null +++ b/.docker/production/.dockerignore @@ -0,0 +1,29 @@ +# Ignore .env files - they should be created at runtime +backend/.env +backend/.env.production + +# Ignore node_modules - we install dependencies during build +frontend/node_modules +backend/vendor + +# Ignore git +.git +.gitignore + +# Ignore build artifacts +frontend/build +backend/bootstrap/cache/* +backend/storage/logs/* +backend/storage/framework/cache/* +backend/storage/framework/sessions/* +backend/storage/framework/views/* + +# Ignore test files +backend/tests +frontend/tests + +# Ignore development files +.vscode +.idea +*.log +.DS_Store diff --git a/.docker/production/Dockerfile b/.docker/production/Dockerfile new file mode 100644 index 0000000..cf374d4 --- /dev/null +++ b/.docker/production/Dockerfile @@ -0,0 +1,151 @@ +# ============================================ +# Dish Planner - All-in-One Production Image +# ============================================ +# This Dockerfile creates a single container with: +# - MySQL 8.0 database +# - PHP 8.2-FPM (Laravel backend) +# - Node.js 20 (React Router frontend) +# - Nginx (reverse proxy) +# - Supervisor (process manager) +# +# Build: docker build -f .docker/production/Dockerfile -t codeberg.org/lvl0/dish-planner:latest . +# Deploy: See .docker/production/README.md for deployment instructions +# ============================================ + +FROM ubuntu:22.04 + +# Prevent interactive prompts during build +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC + +# ============================================ +# Install System Dependencies +# ============================================ +RUN apt-get update && apt-get install -y \ + # Basic utilities + curl \ + wget \ + git \ + unzip \ + ca-certificates \ + gnupg \ + supervisor \ + software-properties-common \ + # Nginx + nginx \ + # MySQL Server + mysql-server \ + && rm -rf /var/lib/apt/lists/* + +# ============================================ +# Install PHP 8.2 from ondrej/php PPA +# ============================================ +RUN add-apt-repository ppa:ondrej/php -y \ + && apt-get update \ + && apt-get install -y \ + php8.2-fpm \ + php8.2-cli \ + php8.2-mysql \ + php8.2-mbstring \ + php8.2-xml \ + php8.2-bcmath \ + php8.2-curl \ + php8.2-zip \ + php8.2-gd \ + php8.2-intl \ + && rm -rf /var/lib/apt/lists/* + +# ============================================ +# Install Node.js 20 +# ============================================ +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +# ============================================ +# Install Composer +# ============================================ +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# ============================================ +# Set up working directories +# ============================================ +WORKDIR /var/www + +# Create necessary directories +RUN mkdir -p /var/www/backend \ + /var/www/frontend \ + /var/www/storage/logs \ + /var/lib/mysql \ + /run/php + +# ============================================ +# Copy and Build Backend (Laravel) +# ============================================ +COPY backend /var/www/backend +WORKDIR /var/www/backend + +# Remove any existing .env files - they will be created at runtime +RUN rm -f .env .env.production + +# Install PHP dependencies (production) +RUN composer install --no-dev --optimize-autoloader --no-interaction + +# Set permissions for Laravel +RUN chown -R www-data:www-data /var/www/backend/storage /var/www/backend/bootstrap/cache \ + && chmod -R 775 /var/www/backend/storage /var/www/backend/bootstrap/cache + +# Add www-data to mysql group so it can access MySQL socket +RUN usermod -a -G mysql www-data + +# ============================================ +# Copy and Build Frontend (React Router) +# ============================================ +COPY frontend /var/www/frontend +WORKDIR /var/www/frontend + +# Install all dependencies (including dev for build), build, then remove dev deps +RUN npm ci \ + && npm run build \ + && npm prune --production + +# ============================================ +# Configure Nginx +# ============================================ +COPY .docker/production/nginx.conf /etc/nginx/sites-available/default +RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default \ + && rm -f /etc/nginx/sites-enabled/default.old + +# ============================================ +# Configure PHP-FPM +# ============================================ +RUN sed -i 's/listen = \/run\/php\/php8.2-fpm.sock/listen = 127.0.0.1:9000/' /etc/php/8.2/fpm/pool.d/www.conf + +# ============================================ +# Configure MySQL +# ============================================ +# Allow MySQL to bind to all interfaces (for easier debugging if needed) +RUN sed -i 's/bind-address.*/bind-address = 127.0.0.1/' /etc/mysql/mysql.conf.d/mysqld.cnf || true + +# ============================================ +# Copy Configuration Files +# ============================================ +COPY .docker/production/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY .docker/production/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# ============================================ +# Create volume mount points +# ============================================ +VOLUME ["/var/lib/mysql", "/var/www/backend/storage"] + +# ============================================ +# Expose HTTP port +# ============================================ +EXPOSE 80 + +# ============================================ +# Set entrypoint and default command +# ============================================ +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/.docker/production/README.md b/.docker/production/README.md new file mode 100644 index 0000000..860dd17 --- /dev/null +++ b/.docker/production/README.md @@ -0,0 +1,345 @@ +# Dish Planner - Self-Hosted Deployment Guide + +This directory contains everything you need to deploy Dish Planner as a self-hosted all-in-one container. + +> **Note**: This guide uses an embedded docker-compose configuration (no separate file to download). Simply copy the YAML configuration shown below and save it as `docker-compose.yml`. + +## Quick Start + +### For End Users (Using Pre-Built Image) + +> **Image Versioning**: This guide uses the `:latest` tag for simplicity. For production deployments, consider using a specific version tag (e.g., `:v0.3`) for stability and predictable updates. + +1. **Create a docker-compose.yml file:** + + Copy the following configuration and save it as `docker-compose.yml`: + + ```yaml + # ============================================ + # Dish Planner - Self-Hosted Deployment + # ============================================ + # + # QUICK START: + # 1. Save this file as docker-compose.yml + # 2. (Optional) Edit the environment variables below + # 3. Run: docker compose up -d + # 4. Access your app at http://localhost:3000 + # + # IMPORTANT SECURITY NOTES: + # - Database credentials are auto-generated on first run + # - Check the container logs to see generated credentials: + # docker logs dishplanner + # - For production, set a custom APP_URL environment variable + # + # CUSTOMIZATION: + # You can override any environment variable by uncommenting + # and editing the values below, or by creating a .env file. + # + # ============================================ + + services: + dishplanner: + # Pre-built all-in-one image from Codeberg Container Registry + image: codeberg.org/lvl0/dish-planner:latest + + container_name: dishplanner + restart: unless-stopped + + # ---------------------------------------- + # Port Configuration + # ---------------------------------------- + # The application will be accessible on port 3000 + # Change the left number to use a different host port + # Example: "8080:80" to access on port 8080 + ports: + - "3000:80" + + # ---------------------------------------- + # Environment Variables + # ---------------------------------------- + # Uncomment and customize as needed + environment: + # Application URL (set this to your domain) + - APP_URL=http://localhost:3000 + + # Application environment (production recommended) + - APP_ENV=production + + # Debug mode (set to false in production for security) + - APP_DEBUG=false + + # Database configuration + # Note: Credentials are auto-generated on first run if not set + # Check logs for generated credentials: docker logs dishplanner + # - DB_DATABASE=dishplanner + # - DB_USERNAME=dishuser + # - DB_PASSWORD=change-this-secure-password + + # Timezone (optional) + # - APP_TIMEZONE=UTC + + # ---------------------------------------- + # Persistent Data Volumes + # ---------------------------------------- + # These volumes ensure data persists across container restarts + volumes: + # MySQL database data + - dishplanner_mysql_data:/var/lib/mysql + + # Laravel storage (uploaded files, logs, cache) + - dishplanner_storage:/var/www/backend/storage + + # Supervisor logs + - dishplanner_logs:/var/log/supervisor + + # ---------------------------------------- + # Health Check (optional) + # ---------------------------------------- + # Uncomment to enable container health monitoring + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost/api/health"] + # interval: 30s + # timeout: 10s + # retries: 3 + # start_period: 60s + + # ---------------------------------------- + # Named Volumes + # ---------------------------------------- + # Docker manages these volumes - data persists even if container is removed + volumes: + dishplanner_mysql_data: + driver: local + dishplanner_storage: + driver: local + dishplanner_logs: + driver: local + + # ---------------------------------------- + # Network Configuration + # ---------------------------------------- + # Uses default bridge network (suitable for single-host deployment) + # For advanced setups, you can define custom networks here + ``` + +2. **Start the application:** + ```bash + docker compose up -d + ``` + +3. **View the initialization logs to get your database credentials:** + ```bash + docker logs dishplanner + ``` + + Save the auto-generated database credentials shown in the logs! + +4. **Access the application:** + Open your browser to `http://localhost:3000` + +That's it! The application is now running with: +- MySQL database +- Laravel backend API +- React Router frontend +- Nginx reverse proxy +- Background queue worker + +### For DockGE Users + +1. In the DockGE web UI, click "Add Stack" +2. Copy the entire YAML configuration from the code block above (starting from `services:` and including all volumes) +3. Paste it into the DockGE compose editor +4. Optionally edit the environment variables +5. Click "Start" +6. View logs to get auto-generated database credentials + +## Configuration + +### Environment Variables + +You can customize the deployment by setting these environment variables in the docker-compose file: + +| Variable | Default | Description | +|----------|---------|-------------| +| `APP_URL` | `http://localhost:3000` | The URL where your app is accessible | +| `APP_ENV` | `production` | Environment mode (production/local) | +| `APP_DEBUG` | `false` | Enable debug mode (never use in production!) | +| `DB_DATABASE` | `dishplanner` | Database name | +| `DB_USERNAME` | `dishuser` | Database username | +| `DB_PASSWORD` | Auto-generated | Database password (check logs for generated value) | + +### Changing the Port + +By default, the app runs on port 3000. To use a different port, edit the `ports` section: + +```yaml +ports: + - "8080:80" # This makes the app available on port 8080 +``` + +### Persistent Data + +The following data is persisted in Docker volumes: +- **MySQL database** - All your dishes, schedules, and users +- **Laravel storage** - Uploaded files, logs, and cache +- **Supervisor logs** - Application and service logs + +Even if you remove the container, this data remains intact. + +## Building the Image Yourself + +If you prefer to build the image yourself instead of using the pre-built one: + +1. **Clone the repository:** + ```bash + git clone https://codeberg.org/lvl0/dish-planner.git + cd dish-planner + ``` + +2. **Build the image:** + ```bash + docker build -f .docker/production/Dockerfile -t codeberg.org/lvl0/dish-planner:latest . + ``` + +3. **Create a docker-compose.yml file** using the configuration shown in the Quick Start section above (or save the embedded compose config to a file) + +4. **Run it:** + ```bash + docker compose up -d + ``` + +## Management + +### Viewing Logs + +```bash +# All logs +docker logs dishplanner + +# Follow logs in real-time +docker logs -f dishplanner + +# Last 100 lines +docker logs --tail 100 dishplanner +``` + +### Stopping the Application + +```bash +docker compose down +``` + +### Restarting the Application + +```bash +docker compose restart +``` + +### Updating to a New Version + +1. Pull the latest image: + ```bash + docker compose pull + ``` + +2. Recreate the container: + ```bash + docker compose up -d + ``` + +3. Your data persists in volumes automatically! + +## Troubleshooting + +### "Cannot connect to database" + +Check that MySQL started successfully: +```bash +docker logs dishplanner | grep mysql +``` + +### "502 Bad Gateway" + +One of the services may not have started. Check supervisor logs: +```bash +docker exec dishplanner supervisorctl status +``` + +### Reset Everything (CAUTION: Deletes all data!) + +```bash +docker compose down -v # The -v flag removes volumes +docker compose up -d +``` + +### Access the Container Shell + +```bash +docker exec -it dishplanner bash +``` + +### Run Laravel Commands + +```bash +# Run migrations +docker exec dishplanner php /var/www/backend/artisan migrate + +# Create a new user (if needed) +docker exec -it dishplanner php /var/www/backend/artisan tinker +``` + +## Architecture + +This all-in-one container includes: + +- **MySQL 8.0** - Database server +- **PHP 8.2-FPM** - Runs the Laravel backend +- **Node.js 20** - Runs the React Router frontend +- **Nginx** - Web server and reverse proxy +- **Supervisord** - Process manager that keeps everything running + +All services start automatically and are monitored by supervisord. If any service crashes, it will be automatically restarted. + +## Security Recommendations + +For production deployments: + +1. **Set a strong database password:** + ```yaml + environment: + - DB_PASSWORD=your-very-secure-password-here + ``` + +2. **Use HTTPS:** Put the container behind a reverse proxy (Nginx, Caddy, Traefik) with SSL/TLS + +3. **Set APP_DEBUG to false:** + ```yaml + environment: + - APP_DEBUG=false + ``` + +4. **Keep the image updated:** Regularly pull and deploy new versions + +5. **Backup your data:** + ```bash + # Backup volumes + docker run --rm -v dishplanner_mysql_data:/data -v $(pwd):/backup ubuntu tar czf /backup/mysql-backup.tar.gz /data + ``` + +## Support + +For issues and questions: +- Codeberg Issues: https://codeberg.org/lvl0/dish-planner/issues +- Documentation: https://codeberg.org/lvl0/dish-planner + +## What's Inside + +The container runs these processes (managed by supervisord): + +1. **MySQL** - Database (port 3306, internal only) +2. **PHP-FPM** - Laravel application (port 9000, internal only) +3. **Node.js** - React frontend (port 3000, internal only) +4. **Nginx** - Reverse proxy (port 80, exposed) +5. **Queue Worker** - Background job processor + +Only Nginx's port 80 is exposed to the host (mapped to 3000 by default). diff --git a/.docker/production/docker-compose.test.yml b/.docker/production/docker-compose.test.yml new file mode 100644 index 0000000..0af3b87 --- /dev/null +++ b/.docker/production/docker-compose.test.yml @@ -0,0 +1,105 @@ +# ============================================ +# Dish Planner - Self-Hosted Deployment +# ============================================ +# +# QUICK START: +# 1. Copy this file to your server +# 2. (Optional) Edit the environment variables below +# 3. Run: docker compose up -d +# 4. Access your app at http://localhost:3000 +# +# IMPORTANT SECURITY NOTES: +# - Database credentials are auto-generated on first run +# - Check the container logs to see generated credentials: +# docker logs dishplanner +# - For production, set a custom APP_URL environment variable +# +# CUSTOMIZATION: +# You can override any environment variable by uncommenting +# and editing the values below, or by creating a .env file. +# +# ============================================ + +services: + dishplanner: + # Pre-built all-in-one image from Docker Hub + image: localhost/dishplanner-allinone:test + + container_name: dishplanner + restart: unless-stopped + + # ---------------------------------------- + # Port Configuration + # ---------------------------------------- + # The application will be accessible on port 3000 + # Change the left number to use a different host port + # Example: "8080:80" to access on port 8080 + ports: + - "3000:80" + + # ---------------------------------------- + # Environment Variables + # ---------------------------------------- + # Uncomment and customize as needed + environment: + # Application URL (set this to your domain) + - APP_URL=http://localhost:3000 + + # Application environment (production recommended) + - APP_ENV=production + + # Debug mode (set to false in production for security) + - APP_DEBUG=false + + # Database configuration + # Note: Credentials are auto-generated on first run if not set + # Check logs for generated credentials: docker logs dishplanner + # - DB_DATABASE=dishplanner + # - DB_USERNAME=dishuser + # - DB_PASSWORD=change-this-secure-password + + # Timezone (optional) + # - APP_TIMEZONE=UTC + + # ---------------------------------------- + # Persistent Data Volumes + # ---------------------------------------- + # These volumes ensure data persists across container restarts + volumes: + # MySQL database data + - dishplanner_mysql_data:/var/lib/mysql + + # Laravel storage (uploaded files, logs, cache) + - dishplanner_storage:/var/www/backend/storage + + # Supervisor logs + - dishplanner_logs:/var/log/supervisor + + # ---------------------------------------- + # Health Check (optional) + # ---------------------------------------- + # Uncomment to enable container health monitoring + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost/api/health"] + # interval: 30s + # timeout: 10s + # retries: 3 + # start_period: 60s + +# ---------------------------------------- +# Named Volumes +# ---------------------------------------- +# Docker manages these volumes - data persists even if container is removed +volumes: + dishplanner_mysql_data: + driver: local + dishplanner_storage: + driver: local + dishplanner_logs: + driver: local + +# ---------------------------------------- +# Network Configuration +# ---------------------------------------- +# Uses default bridge network (suitable for single-host deployment) +# For advanced setups, you can define custom networks here diff --git a/.docker/production/entrypoint.sh b/.docker/production/entrypoint.sh new file mode 100644 index 0000000..5228fc8 --- /dev/null +++ b/.docker/production/entrypoint.sh @@ -0,0 +1,153 @@ +#!/bin/bash +set -e + +echo "==========================================" +echo "Dish Planner - Starting Initialization" +echo "==========================================" + +# ============================================ +# MySQL Initialization +# ============================================ +echo "[1/6] Initializing MySQL..." + +# Check if MySQL data directory is empty (first run) +if [ ! -d "/var/lib/mysql/mysql" ]; then + echo " → First run detected, initializing MySQL data directory..." + mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql +fi + +# Start MySQL temporarily in background for setup +echo " → Starting MySQL..." +mysqld --user=mysql --datadir=/var/lib/mysql & +MYSQL_PID=$! + +# Wait for MySQL to be ready +echo " → Waiting for MySQL to be ready..." +for i in {1..30}; do + if mysqladmin ping -h localhost --silent; then + echo " → MySQL is ready!" + break + fi + echo " → Waiting... ($i/30)" + sleep 2 +done + +# ============================================ +# Database Setup +# ============================================ +echo "[2/6] Setting up database..." + +# Set default values for database credentials +DB_DATABASE=${DB_DATABASE:-dishplanner} +DB_USERNAME=${DB_USERNAME:-dishuser} + +# Check if this is first run by looking for credentials file +CREDS_FILE="/var/www/backend/storage/.db_credentials" + +if [ -f "$CREDS_FILE" ]; then + # Not first run - load existing credentials + echo " → Loading existing database credentials..." + source "$CREDS_FILE" +else + # First run - generate new credentials and save them + echo " → First run detected, generating credentials..." + DB_PASSWORD=${DB_PASSWORD:-$(openssl rand -base64 32)} + MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-$(openssl rand -base64 32)} + + # Save credentials for future restarts + cat > "$CREDS_FILE" <