non-laravel support

This commit is contained in:
marito 2025-06-15 09:24:28 +08:00
parent 423a000d03
commit be3095e000
8 changed files with 138 additions and 98 deletions

View File

@ -3,25 +3,31 @@
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# 1. Set the global database engine. # 1. Set the global database engine.
# Choose one: "mariadb", "mysql", or "postgres"
db_engine: "mariadb" db_engine: "mariadb"
# 2. Define all your Laravel sites below. # 2. Define the global database credentials.
# Add, edit, or remove sites as needed. db_credentials:
user: "Lenvi"
password: "password"
# 3. Define all your web sites below.
sites: sites:
# Example for a standard Laravel project
- domain: myapp.local - 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" php_version: "8.2"
database: "myapp_db"
- domain: legacy-app.local # Example for a legacy project or a non-Laravel PHP project
project_root: /home/mar/projects/legacy-app - 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" php_version: "8.0"
database: "legacy_db"
- domain: another-project.local
project_root: /home/mar/projects/another-project
php_version: "8.2"
# Example of a new project you might add later: # Example for a simple static HTML site (no PHP or DB)
# - domain: new-api.local - domain: portfolio.local
# project_root: /home/mar/projects/new-api project_root: /home/mar/projects/portfolio # Directory for management
# php_version: "8.3" 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

View File

@ -1,78 +1,55 @@
# Lenvi - Ansible-Powered Laravel Development Environment # 🧰 Lenvi - Ansible-Powered 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 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 ## Prerequisites
- **Ansible & Git:** Must be installed on the machine where you are running the playbook. - **Ansible & Git:** Must be installed. `sudo apt update && sudo apt install ansible git -y`
```bash - **Sudo Access:** Your user must have `sudo` privileges.
sudo apt update && sudo apt install ansible git -y - **Debian-based OS:** Required for `apt` (includes Ubuntu, WSL distributions, etc.).
```
- **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.
## How to Run ## How to Run
### 1. Get the Code ### 1. Get the Code
Clone the repository and enter the project directory.
```bash ```bash
git clone https://git.marmattheo.com/marito/Lenvi.git lenvi-ansible && cd lenvi-ansible git clone https://git.marmattheo.com/marito/Lenvi.git lenvi-ansible && cd lenvi-ansible
``` ```
### 2. Configure Lenvi ### 2. Configure Lenvi
This is the most important step. Open the **`Lenvi.yaml`** file and customize it for your needs: Open **`Lenvi.yaml`** and define your sites. This is the key to Lenvi's flexibility:
- Set your global `db_engine`. - **`project_root`**: The base directory of your project. This is where you would run `git` or `composer` commands.
- Define all your projects under the `sites` list with their correct `domain`, `project_root`, and `php_version`. - **`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`:** **Example `Lenvi.yaml`:**
```yaml ```yaml
# Set the global database engine.
db_engine: "mariadb" db_engine: "mariadb"
# Define all your Laravel sites. db_credentials: { user: "lenvi", password: "password" }
sites: sites:
- domain: myapp.local - domain: my-laravel-app.local
project_root: /home/mar/projects/myapp project_root: /home/user/projects/my-laravel-app
document_root: /home/user/projects/my-laravel-app/public
php_version: "8.2" php_version: "8.2"
- domain: legacy-app.local database: "laravel_db"
project_root: /home/mar/projects/legacy-app - domain: my-static-site.local
php_version: "8.0" 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 ### 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 ```bash
ansible-playbook playbook.yml -i inventory --ask-become-pass 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`). ## 🚀 Post-Installation
## 🚀 Final Setup ### Database Access
After the playbook completes successfully, there is one final manual step. For each project, use the global user credentials with the project-specific database name from `Lenvi.yaml`.
### Update Your Hosts File ### 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. Map your domains to `127.0.0.1`.
#### On Linux (Desktop) #### On Linux
Edit the `/etc/hosts` file directly in your terminal: `sudo nano /etc/hosts`
```bash
sudo nano /etc/hosts
```
#### On Windows (for WSL Users) #### On Windows (for WSL Users)
If you are using WSL, you **must** edit the hosts file on Windows itself, not inside the Linux environment. Open Notepad as Administrator and edit `C:\Windows\System32\drivers\etc\hosts`.
1. Open **Notepad** as an **Administrator**. **Example entries:**
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:**
``` ```
127.0.0.1 myapp.local 127.0.0.1 my-laravel-app.local
127.0.0.1 legacy-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. **You're all set!** Your flexible, multi-project environment is ready.
## 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.

View File

@ -1,38 +1,38 @@
#!/bin/bash #!/bin/bash
#
# Lenvi Smart Composer Wrapper # Lenvi Smart Composer Wrapper
#
# This script automatically detects the required PHP version for a project # This script automatically detects the required PHP version for a project
# by looking for a `Lenvi.yaml` file and uses the correct PHP binary. # by looking for a `Lenvi.yaml` file and uses the correct PHP binary.
# The real composer binary
COMPOSER_PHAR="/usr/local/bin/composer.phar" COMPOSER_PHAR="/usr/local/bin/composer.phar"
DEFAULT_PHP_BINARY="/usr/bin/php" DEFAULT_PHP_BINARY="/usr/bin/php"
PHP_BINARY="" 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) SEARCH_DIR=$(pwd)
while [[ "$SEARCH_DIR" != "" && ! -f "$SEARCH_DIR/Lenvi.yaml" ]]; do while [[ "$SEARCH_DIR" != "" && ! -f "$SEARCH_DIR/Lenvi.yaml" ]]; do
SEARCH_DIR=${SEARCH_DIR%/*} SEARCH_DIR=${SEARCH_DIR%/*}
done done
if [[ -f "$SEARCH_DIR/Lenvi.yaml" ]]; then 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) 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 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" 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
fi fi
# If no specific PHP binary was found, use the system default # If no specific PHP binary was found, use the system default
if [[ -z "$PHP_BINARY" ]]; then 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" PHP_BINARY="$DEFAULT_PHP_BINARY"
fi fi
# Execute the real Composer with the determined PHP version and pass all arguments
exec "$PHP_BINARY" "$COMPOSER_PHAR" "$@" exec "$PHP_BINARY" "$COMPOSER_PHAR" "$@"

View File

@ -27,4 +27,20 @@
name: postgresql name: postgresql
state: started state: started
enabled: yes enabled: yes
when: db_engine == 'postgres' 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

View File

@ -2,11 +2,9 @@
- name: "Install required PHP versions and extensions" - name: "Install required PHP versions and extensions"
ansible.builtin.apt: ansible.builtin.apt:
name: name:
# Core PHP
- "php{{ item }}" - "php{{ item }}"
- "php{{ item }}-fpm" - "php{{ item }}-fpm"
- "php{{ item }}-cli" - "php{{ item }}-cli"
# Common Laravel Extensions
- "php{{ item }}-common" - "php{{ item }}-common"
- "php{{ item }}-mysql" - "php{{ item }}-mysql"
- "php{{ item }}-pgsql" - "php{{ item }}-pgsql"

View File

@ -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" - name: "Create Nginx config for {{ project.domain }} in conf.d"
ansible.builtin.template: ansible.builtin.template:
src: nginx-site.conf.j2 src: nginx-site.conf.j2
dest: "/etc/nginx/conf.d/{{ project.domain }}.conf" dest: "/etc/nginx/conf.d/{{ project.domain }}.conf"
owner: root owner: root
group: root group: root
mode: '0644' 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')

View File

@ -1,11 +1,6 @@
--- ---
# This loop will run the 'configure_site.yml' tasks for each site - name: Configure Nginx site and database for each project
# defined in Lenvi.yaml. Handlers are notified only once at the end.
- name: "Configure Nginx site for each project"
ansible.builtin.include_tasks: configure_site.yml ansible.builtin.include_tasks: configure_site.yml
loop: "{{ sites }}" loop: "{{ sites }}"
loop_control: loop_control:
loop_var: project loop_var: project
notify:
- Reload Nginx
- Reload PHP-FPM Services

View File

@ -2,7 +2,7 @@ server {
listen 80; listen 80;
server_name {{ project.domain }}; server_name {{ project.domain }};
root {{ project.project_root }}/public; root {{ project.project_root }};
index index.php; index index.php;
access_log /var/log/nginx/{{ project.domain }}-access.log; access_log /var/log/nginx/{{ project.domain }}-access.log;
@ -78,11 +78,6 @@ server {
add_header Cache-Control "public"; 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) # Optional: Nginx status endpoint (local only)
location /nginx_status { location /nginx_status {
stub_status; stub_status;