commit 23b08fb56bb01346ed5b17fdd24473dd183fa016 Author: marito Date: Sun Jun 15 08:14:58 2025 +0800 v1 diff --git a/Lenvi.yaml b/Lenvi.yaml new file mode 100644 index 0000000..d414098 --- /dev/null +++ b/Lenvi.yaml @@ -0,0 +1,27 @@ +# ------------------------------------------------------------------ +# Lenvi: Central Configuration for Your Development Environment +# ------------------------------------------------------------------ + +# 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. +sites: + - domain: myapp.local + project_root: /home/mar/projects/myapp + php_version: "8.2" + + - domain: legacy-app.local + project_root: /home/mar/projects/legacy-app + php_version: "8.0" + + - domain: another-project.local + project_root: /home/mar/projects/another-project + php_version: "8.2" + +# 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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f18629 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Lenvi Ansible Provisioner + +Lenvi is a lightweight, Ansible-powered tool for managing local Laravel development environments. It provides a Homestead-like experience without the overhead of a virtual machine, running natively on Linux or on Windows via WSL2. + +## Features + +- **Centralized Configuration:** Manage all your projects from a single `lenvi.yaml` file. +- **Multi-PHP:** Assign a specific PHP version (e.g., 8.0, 8.2, 8.3) to each site. +- **Database Support:** Automatically creates databases using a shared MariaDB, MySQL, or PostgreSQL server. +- **Idempotent:** Safely run the provisioner at any time to update your environment. + +## Prerequisites + +1. **Ansible:** You must have Ansible installed. + ```bash + # On Debian/Ubuntu + sudo apt update + sudo apt install python3-pip -y + pip3 install ansible + ``` +2. **WSL2 (for Windows users):** Install WSL2 and a Linux distribution like **Ubuntu 22.04** from the Microsoft Store. All subsequent commands must be run from the WSL2 terminal. + +## How to Use + +1. **Clone this repository:** + ```bash + git clone ~/tools/lenvi_ansible + ``` + +2. **Configure `lenvi.yaml`:** + Open `~/tools/lenvi_ansible/lenvi.yaml` and configure it for your projects. Set your `db_engine` and list all your sites under the `sites` key. + +3. **Run the Playbook:** + Navigate to the `lenvi_ansible` directory and run the main playbook. + ```bash + cd ~/tools/lenvi_ansible + ansible-playbook playbook.yml --ask-become-pass + ``` + Ansible will ask for your `sudo` password to install software and configure services. + +### Important Note for Windows (WSL2) Users + +For your Windows browser (Chrome, Firefox, etc.) to access a site like `myapp.test`, you must manually edit the **Windows hosts file**. + +1. Open **Notepad** as an **Administrator**. +2. Open the file: `C:\Windows\System32\drivers\etc\hosts` +3. For each site in your `lenvi.yaml`, add a new line: + ``` + 127.0.0.1 myapp.test + 127.0.0.1 another-app.test + ``` +4. Save the file. You only need to do this once per new domain. + +You can now access your sites in your browser! \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..0626e63 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +inventory = inventory +host_key_checking = False \ No newline at end of file diff --git a/inventory b/inventory new file mode 100644 index 0000000..93fb760 --- /dev/null +++ b/inventory @@ -0,0 +1,2 @@ +[lenvi_dev] +localhost ansible_connection=local \ No newline at end of file diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..9825647 --- /dev/null +++ b/playbook.yml @@ -0,0 +1,19 @@ +--- +- name: "Provision Lenvi Development Environment" + hosts: lenvi_dev + become: yes + + vars_files: + - Lenvi.yaml + + pre_tasks: + - name: "Gather a unique list of required PHP versions from sites" + ansible.builtin.set_fact: + php_versions_to_install: "{{ sites | map(attribute='php_version') | list | unique }}" + + roles: + - role: common + - role: database + - role: php + - role: nginx + - role: projects \ No newline at end of file diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml new file mode 100644 index 0000000..492101b --- /dev/null +++ b/roles/common/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: "Install common dependencies" + ansible.builtin.apt: + name: + - software-properties-common + - ca-certificates + - apt-transport-https + state: present + update_cache: yes + +- name: "Add Ondřej PPA for PHP" + ansible.builtin.apt_repository: + repo: "ppa:ondrej/php" + state: present \ No newline at end of file diff --git a/roles/database/tasks/main.yml b/roles/database/tasks/main.yml new file mode 100644 index 0000000..98e949b --- /dev/null +++ b/roles/database/tasks/main.yml @@ -0,0 +1,30 @@ +--- +- name: "Install MariaDB/MySQL server" + ansible.builtin.apt: + name: mariadb-server + state: present + update_cache: yes + when: db_engine == 'mariadb' or db_engine == 'mysql' + +- name: "Install PostgreSQL server" + ansible.builtin.apt: + name: + - postgresql + - postgresql-contrib + state: present + update_cache: yes + when: db_engine == 'postgres' + +- name: "Ensure MariaDB/MySQL service is running and enabled" + ansible.builtin.service: + name: mariadb + state: started + enabled: yes + when: db_engine == 'mariadb' or db_engine == 'mysql' + +- name: "Ensure PostgreSQL service is running and enabled" + ansible.builtin.service: + name: postgresql + state: started + enabled: yes + when: db_engine == 'postgres' \ No newline at end of file diff --git a/roles/nginx/handlers/main.yml b/roles/nginx/handlers/main.yml new file mode 100644 index 0000000..0d623e8 --- /dev/null +++ b/roles/nginx/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: "Reload Nginx" + ansible.builtin.service: + name: nginx + state: reloaded \ No newline at end of file diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml new file mode 100644 index 0000000..a9e763b --- /dev/null +++ b/roles/nginx/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: "Install Nginx" + ansible.builtin.apt: + name: nginx + state: present + +- name: "Ensure Nginx is enabled and started" + ansible.builtin.service: + name: nginx + state: started + enabled: yes + +- name: "Remove the default Nginx site to prevent conflicts" + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: Reload Nginx \ No newline at end of file diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml new file mode 100644 index 0000000..4e76389 --- /dev/null +++ b/roles/php/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- 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" + - "php{{ item }}-xml" + - "php{{ item }}-zip" + - "php{{ item }}-mbstring" + - "php{{ item }}-curl" + - "php{{ item }}-bcmath" + state: present + loop: "{{ php_versions_to_install }}" + when: php_versions_to_install is defined and php_versions_to_install | length > 0 + +- name: "Ensure all PHP-FPM services are enabled and started" + ansible.builtin.service: + name: "php{{ item }}-fpm" + enabled: yes + state: started + loop: "{{ php_versions_to_install }}" + when: php_versions_to_install is defined and php_versions_to_install | length > 0 \ No newline at end of file diff --git a/roles/projects/handlers/main.yml b/roles/projects/handlers/main.yml new file mode 100644 index 0000000..5895159 --- /dev/null +++ b/roles/projects/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: "Reload PHP-FPM Services" + ansible.builtin.service: + name: "php{{ item }}-fpm" + state: reloaded + loop: "{{ php_versions_to_install }}" + when: php_versions_to_install is defined and php_versions_to_install | length > 0 \ No newline at end of file diff --git a/roles/projects/tasks/configure_site.yml b/roles/projects/tasks/configure_site.yml new file mode 100644 index 0000000..3367114 --- /dev/null +++ b/roles/projects/tasks/configure_site.yml @@ -0,0 +1,8 @@ +--- +- 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 diff --git a/roles/projects/tasks/main.yml b/roles/projects/tasks/main.yml new file mode 100644 index 0000000..07624db --- /dev/null +++ b/roles/projects/tasks/main.yml @@ -0,0 +1,11 @@ +--- +# 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" + 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 diff --git a/roles/projects/templates/nginx-site.conf.j2 b/roles/projects/templates/nginx-site.conf.j2 new file mode 100644 index 0000000..8c61ab1 --- /dev/null +++ b/roles/projects/templates/nginx-site.conf.j2 @@ -0,0 +1,92 @@ +server { + listen 80; + server_name {{ project.domain }}; + + root {{ project.project_root }}/public; + index index.php; + + access_log /var/log/nginx/{{ project.domain }}-access.log; + error_log /var/log/nginx/{{ project.domain }}-error.log; + + # General performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 30s; + types_hash_max_size 2048; + server_tokens off; + + client_max_body_size 100M; + client_body_buffer_size 128k; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + + # Gzip compression + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/x-javascript + application/xml + application/xml+rss + font/ttf + font/otf + image/svg+xml; + + # Laravel-friendly routing + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP-FPM handling + location ~ \.php$ { + include fastcgi_params; + fastcgi_pass unix:/run/php/php{{ project.php_version }}-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; + fastcgi_busy_buffers_size 64k; + fastcgi_temp_file_write_size 64k; + fastcgi_intercept_errors on; + } + + # Block hidden files + location ~ /\.(?!well-known).* { + deny all; + } + + # Static file caching + location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?|ttf|svg|eot)$ { + expires 30d; + access_log off; + 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; + allow 127.0.0.1; + deny all; + } +} \ No newline at end of file