diff --git a/Lenvi.yaml b/Lenvi.yaml index d414098..4b5c3e6 100644 --- a/Lenvi.yaml +++ b/Lenvi.yaml @@ -3,25 +3,31 @@ # ------------------------------------------------------------------ # 1. Set the global database engine. -# Choose one: "mariadb", "mysql", or "postgres" db_engine: "mariadb" -# 2. Define all your Laravel sites below. -# Add, edit, or remove sites as needed. +# 2. Define the global database credentials. +db_credentials: + user: "Lenvi" + password: "password" + +# 3. Define all your web sites below. sites: + # Example for a standard Laravel project - domain: myapp.local - project_root: /home/mar/projects/myapp + project_root: /home/mar/projects/myapp # For Composer + document_root: /home/mar/projects/myapp/public # For Nginx php_version: "8.2" + database: "myapp_db" - - domain: legacy-app.local - project_root: /home/mar/projects/legacy-app + # Example for a legacy project or a non-Laravel PHP project + - domain: legacy-site.local + project_root: /home/mar/projects/legacy-site # For Composer + document_root: /home/mar/projects/legacy-site # Nginx serves from the root php_version: "8.0" - - - domain: another-project.local - project_root: /home/mar/projects/another-project - php_version: "8.2" + database: "legacy_db" -# Example of a new project you might add later: -# - domain: new-api.local -# project_root: /home/mar/projects/new-api -# php_version: "8.3" \ No newline at end of file + # Example for a simple static HTML site (no PHP or DB) + - domain: portfolio.local + project_root: /home/mar/projects/portfolio # Directory for management + document_root: /home/mar/projects/portfolio # Nginx serves from the root + php_version: "8.2" # Still needed for Nginx config, but can be any installed version \ No newline at end of file diff --git a/README.md b/README.md index a0b2eb0..579cc12 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,55 @@ -# Lenvi - Ansible-Powered Laravel Development Environment -This Ansible playbook automates the setup of a complete Laravel development environment directly on your local machine (Debian/Ubuntu/WSL2). It installs and configures Nginx, multiple PHP versions, and your choice of database based on a simple `Lenvi.yaml` configuration file. +# 🧰 Lenvi - Ansible-Powered Development Environment +Lenvi is a framework-agnostic development environment powered by Ansible. It installs and configures Nginx, multiple PHP versions, your choice of database, **per-project databases**, a global database user, and a smart Composer wrapper that automatically uses the correct PHP version for each project. +## Features +- **Framework Agnostic**: By specifying a `document_root` for Nginx and a `project_root` for Composer, you can support Laravel, WordPress, legacy PHP apps, or even static HTML sites. +- **Robust Validation**: Automatically checks that your configured directories and index files exist before attempting setup, preventing common Nginx errors. +- **Per-Project Databases**: Each site defined in `Lenvi.yaml` can have its own isolated database, created automatically. +- **Seamless Composer Workflow**: `cd` into your project directory and run `composer install`. Lenvi automatically uses the correct PHP version for that project. +- **Centralized Configuration**: Manage your entire environment from a single, clean `Lenvi.yaml` file. ## Prerequisites -- **Ansible & Git:** Must be installed on the machine where you are running the playbook. - ```bash - sudo apt update && sudo apt install ansible git -y - ``` -- **Sudo Access:** Your user must have `sudo` privileges to run the playbook. -- **Debian-based OS:** A distribution like Ubuntu or Debian is required for `apt` and the OndΕ™ej PPA for PHP. This includes WSL distributions. +- **Ansible & Git:** Must be installed. `sudo apt update && sudo apt install ansible git -y` +- **Sudo Access:** Your user must have `sudo` privileges. +- **Debian-based OS:** Required for `apt` (includes Ubuntu, WSL distributions, etc.). ## How to Run ### 1. Get the Code -Clone the repository and enter the project directory. ```bash git clone https://git.marmattheo.com/marito/Lenvi.git lenvi-ansible && cd lenvi-ansible ``` ### 2. Configure Lenvi -This is the most important step. Open the **`Lenvi.yaml`** file and customize it for your needs: -- Set your global `db_engine`. -- Define all your projects under the `sites` list with their correct `domain`, `project_root`, and `php_version`. +Open **`Lenvi.yaml`** and define your sites. This is the key to Lenvi's flexibility: +- **`project_root`**: The base directory of your project. This is where you would run `git` or `composer` commands. +- **`document_root`**: The directory that Nginx will serve files from. For Laravel, this is the `public` sub-directory. For many other projects, it might be the same as the `project_root`. **Example `Lenvi.yaml`:** ```yaml -# Set the global database engine. db_engine: "mariadb" -# Define all your Laravel sites. +db_credentials: { user: "lenvi", password: "password" } sites: - - domain: myapp.local - project_root: /home/mar/projects/myapp + - domain: my-laravel-app.local + project_root: /home/user/projects/my-laravel-app + document_root: /home/user/projects/my-laravel-app/public php_version: "8.2" - - domain: legacy-app.local - project_root: /home/mar/projects/legacy-app - php_version: "8.0" + database: "laravel_db" + - domain: my-static-site.local + project_root: /home/user/projects/my-static-site + document_root: /home/user/projects/my-static-site + php_version: "8.2" # Required for Nginx config, can be any installed version ``` ### 3. Execute the Playbook -Run the following command from the `lenvi-ansible` directory. It will prompt you for your `sudo` password to perform the administrative tasks. ```bash ansible-playbook playbook.yml -i inventory --ask-become-pass ``` -> **--ask-become-pass:** This flag tells Ansible to prompt for the password needed for privilege escalation (`sudo`). -## πŸš€ Final Setup -After the playbook completes successfully, there is one final manual step. +## πŸš€ Post-Installation +### Database Access +For each project, use the global user credentials with the project-specific database name from `Lenvi.yaml`. ### Update Your Hosts File -For your browser to resolve local domains like `myapp.local`, you must map them to your machine's local IP address (`127.0.0.1`). The location of this file depends on your operating system. -#### On Linux (Desktop) -Edit the `/etc/hosts` file directly in your terminal: -```bash -sudo nano /etc/hosts -``` +Map your domains to `127.0.0.1`. +#### On Linux +`sudo nano /etc/hosts` #### On Windows (for WSL Users) -If you are using WSL, you **must** edit the hosts file on Windows itself, not inside the Linux environment. -1. Open **Notepad** as an **Administrator**. -2. Click `File -> Open` and navigate to this path: - `C:\Windows\System32\drivers\etc\hosts` -3. Add the entries to this file. ---- -**Example entries to add:** +Open Notepad as Administrator and edit `C:\Windows\System32\drivers\etc\hosts`. +**Example entries:** ``` -127.0.0.1 myapp.local -127.0.0.1 legacy-app.local +127.0.0.1 my-laravel-app.local +127.0.0.1 my-static-site.local ``` -βœ… **You're all set!** You can now access your sites, like `http://myapp.local`, in your browser. -## Daily Workflow -Managing your environment is as simple as editing the `Lenvi.yaml` file and re-running the playbook. -### Adding a New Site -1. Add a new project block to the `sites` list in `Lenvi.yaml`. -2. Add the new domain to your `/etc/hosts` file (or the Windows hosts file for WSL). -3. Re-run the playbook: `ansible-playbook playbook.yml -i inventory --ask-become-pass` -### Changing a Site's PHP Version -1. In `Lenvi.yaml`, change the `php_version` for the desired site. -2. Re-run the playbook. Ansible will automatically install the new PHP version (if not already present) and update the Nginx configuration. -### Removing a Site -1. Delete the project's block from the `sites` list in `Lenvi.yaml`. -2. Manually delete the site's Nginx configuration file: - ```bash - # Example for myapp.local - sudo rm /etc/nginx/conf.d/myapp.local.conf - ``` -3. Re-run the playbook to apply the changes and reload Nginx. -4. Remove the entry from your hosts file. \ No newline at end of file +βœ… **You're all set!** Your flexible, multi-project environment is ready. \ No newline at end of file diff --git a/roles/composer/templates/composer-wrapper.sh.j2 b/roles/composer/templates/composer-wrapper.sh.j2 index 814e4bc..79d0a66 100644 --- a/roles/composer/templates/composer-wrapper.sh.j2 +++ b/roles/composer/templates/composer-wrapper.sh.j2 @@ -1,38 +1,38 @@ #!/bin/bash -# # Lenvi Smart Composer Wrapper -# # This script automatically detects the required PHP version for a project # by looking for a `Lenvi.yaml` file and uses the correct PHP binary. -# The real composer binary COMPOSER_PHAR="/usr/local/bin/composer.phar" DEFAULT_PHP_BINARY="/usr/bin/php" PHP_BINARY="" -# Find the project root by looking for a Lenvi.yaml file in parent directories +# Find the Lenvi.yaml file by looking in parent directories +LENVI_CONFIG_FILE="" SEARCH_DIR=$(pwd) while [[ "$SEARCH_DIR" != "" && ! -f "$SEARCH_DIR/Lenvi.yaml" ]]; do SEARCH_DIR=${SEARCH_DIR%/*} done if [[ -f "$SEARCH_DIR/Lenvi.yaml" ]]; then - # We found a config file, let's find the PHP version for the current directory + LENVI_CONFIG_FILE="$SEARCH_DIR/Lenvi.yaml" +fi + +if [[ -n "$LENVI_CONFIG_FILE" ]]; then + # We found a config file, now find the PHP version for the current project directory CURRENT_PROJECT_ROOT=$(pwd) - PHP_VERSION=$(yq eval '.sites[] | select(.project_root == "'"$CURRENT_PROJECT_ROOT"'") | .php_version' "$SEARCH_DIR/Lenvi.yaml") + PHP_VERSION=$(yq eval '.sites[] | select(.project_root == "'"$CURRENT_PROJECT_ROOT"'") | .php_version' "$LENVI_CONFIG_FILE") if [[ -n "$PHP_VERSION" && -x "/usr/bin/php$PHP_VERSION" ]]; then - # Version found and the corresponding PHP binary exists PHP_BINARY="/usr/bin/php$PHP_VERSION" - echo "Lenvi: Found project configuration. Using PHP $PHP_VERSION..." >&2 + echo "Lenvi: Project found. Using PHP $PHP_VERSION..." >&2 fi fi # If no specific PHP binary was found, use the system default if [[ -z "$PHP_BINARY" ]]; then - echo "Lenvi: No project configuration found. Using default system PHP." >&2 + echo "Lenvi: No project configuration found for this directory. Using default system PHP." >&2 PHP_BINARY="$DEFAULT_PHP_BINARY" fi -# Execute the real Composer with the determined PHP version and pass all arguments exec "$PHP_BINARY" "$COMPOSER_PHAR" "$@" \ No newline at end of file diff --git a/roles/database/tasks/main.yml b/roles/database/tasks/main.yml index 98e949b..baa134e 100644 --- a/roles/database/tasks/main.yml +++ b/roles/database/tasks/main.yml @@ -27,4 +27,20 @@ name: postgresql state: started enabled: yes - when: db_engine == 'postgres' \ No newline at end of file + when: db_engine == 'postgres' + +- name: "Install Python dependencies for MariaDB/MySQL management" + ansible.builtin.apt: + name: python3-pymysql + state: present + when: db_engine == 'mariadb' or db_engine == 'mysql' + +- name: "Create the global Lenvi database user" + community.mysql.mysql_user: + name: "{{ db_credentials.user }}" + password: "{{ db_credentials.password }}" + priv: "*.*:ALL,GRANT" + host: "%" + state: present + login_unix_socket: /var/run/mysqld/mysqld.sock + when: (db_engine == 'mariadb' or db_engine == 'mysql') and db_credentials is defined \ No newline at end of file diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml index 4e76389..8bcaf5f 100644 --- a/roles/php/tasks/main.yml +++ b/roles/php/tasks/main.yml @@ -2,11 +2,9 @@ - name: "Install required PHP versions and extensions" ansible.builtin.apt: name: - # Core PHP - "php{{ item }}" - "php{{ item }}-fpm" - "php{{ item }}-cli" - # Common Laravel Extensions - "php{{ item }}-common" - "php{{ item }}-mysql" - "php{{ item }}-pgsql" diff --git a/roles/projects/tasks/configure_site.yml b/roles/projects/tasks/configure_site.yml index 3367114..e8d2e7e 100644 --- a/roles/projects/tasks/configure_site.yml +++ b/roles/projects/tasks/configure_site.yml @@ -1,8 +1,61 @@ --- +# ------------------ VALIDATION TASKS ------------------ +- name: "Validate that project root '{{ project.project_root }}' exists" + ansible.builtin.stat: + path: "{{ project.project_root }}" + register: project_root_stat + +- name: "Fail if project root directory does not exist" + ansible.builtin.fail: + msg: | + VALIDATION FAILED for site '{{ project.domain }}': + The project_root directory '{{ project.project_root }}' does not exist. + This path is required for Composer. Please create it or correct the path in Lenvi.yaml. + when: not project_root_stat.stat.exists or not project_root_stat.stat.isdir + +- name: "Validate that document root '{{ project.document_root }}' exists" + ansible.builtin.stat: + path: "{{ project.document_root }}" + register: document_root_stat + +- name: "Fail if document root directory does not exist" + ansible.builtin.fail: + msg: | + VALIDATION FAILED for site '{{ project.domain }}': + The document_root directory '{{ project.document_root }}' does not exist. + This path is required for Nginx. Please create it or correct the path in Lenvi.yaml. + when: not document_root_stat.stat.exists or not document_root_stat.stat.isdir + +- name: "Check for an index file (index.php or index.html) in the document root" + ansible.builtin.find: + paths: "{{ project.document_root }}" + patterns: "index.php,index.html" + file_type: file + register: index_file_find + +- name: "Fail if no index file is found" + ansible.builtin.fail: + msg: | + VALIDATION FAILED for site '{{ project.domain }}': + No 'index.php' or 'index.html' was found in the document_root '{{ project.document_root }}'. + Nginx requires an entry point file to serve the site. + when: index_file_find.matched == 0 + +# ------------------ CONFIGURATION TASKS ------------------ - name: "Create Nginx config for {{ project.domain }} in conf.d" ansible.builtin.template: src: nginx-site.conf.j2 dest: "/etc/nginx/conf.d/{{ project.domain }}.conf" owner: root group: root - mode: '0644' \ No newline at end of file + mode: '0644' + notify: + - Reload Nginx + - Reload PHP-FPM Services + +- name: "Create project-specific database '{{ project.database }}'" + community.mysql.mysql_db: + name: "{{ project.database }}" + state: present + login_unix_socket: /var/run/mysqld/mysqld.sock + when: project.database is defined and (db_engine == 'mariadb' or db_engine == 'mysql') \ No newline at end of file diff --git a/roles/projects/tasks/main.yml b/roles/projects/tasks/main.yml index 07624db..6ff3bea 100644 --- a/roles/projects/tasks/main.yml +++ b/roles/projects/tasks/main.yml @@ -1,11 +1,6 @@ --- -# This loop will run the 'configure_site.yml' tasks for each site -# defined in Lenvi.yaml. Handlers are notified only once at the end. -- name: "Configure Nginx site for each project" +- name: Configure Nginx site and database for each project ansible.builtin.include_tasks: configure_site.yml loop: "{{ sites }}" loop_control: - loop_var: project - notify: - - Reload Nginx - - Reload PHP-FPM Services \ No newline at end of file + loop_var: project \ No newline at end of file diff --git a/roles/projects/templates/nginx-site.conf.j2 b/roles/projects/templates/nginx-site.conf.j2 index 8c61ab1..ef12322 100644 --- a/roles/projects/templates/nginx-site.conf.j2 +++ b/roles/projects/templates/nginx-site.conf.j2 @@ -2,7 +2,7 @@ server { listen 80; server_name {{ project.domain }}; - root {{ project.project_root }}/public; + root {{ project.project_root }}; index index.php; access_log /var/log/nginx/{{ project.domain }}-access.log; @@ -78,11 +78,6 @@ server { add_header Cache-Control "public"; } - # Maintenance mode redirect (Laravel down file) - if (-f $document_root/storage/framework/down) { - return 503; - } - # Optional: Nginx status endpoint (local only) location /nginx_status { stub_status;