aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel Dalberto Pedro <miguel.pedro@precisioninno.com>2026-10-29 12:48:06 -0300
committerMiguel Dalberto Pedro <miguel.pedro@precisioninno.com>2026-10-29 12:48:06 -0300
commit4b7379914a1865f07b2c54252234cc90158fc253 (patch)
treea476c7e08bb994034d7d04b5b1cdfce216f49c43
downloadmy-infra-4b7379914a1865f07b2c54252234cc90158fc253.tar.gz
my-infra-4b7379914a1865f07b2c54252234cc90158fc253.zip
feat(iac): establish Ansible baseline for Nginx, SearXNG, Valkey, and Certbot
- Reverse-engineer live Debian 13 (trixie) VPS state at miguelpedro.xyz.br - Add modular Ansible roles: common, certbot, valkey, searxng, and nginx - Encrypt SearXNG secret_key using ansible-vault in group_vars/all/vault.yml - Cleanly remove unused example.com Nginx stub symlink (sites-enabled/miguelpedro.xyz.br) - Scaffold cgit + fcgiwrap role (cgit_enabled: false) for upcoming deployment - Add DISCOVERY_REPORT.md, COMMAND_LOG.md, README.md, and AGENT_INSTRUCTIONS.md with Self-Updating Protocol
-rw-r--r--.gitignore33
-rw-r--r--AGENT_INSTRUCTIONS.md53
-rw-r--r--COMMAND_LOG.md595
-rw-r--r--DISCOVERY_REPORT.md132
-rw-r--r--README.md96
-rw-r--r--ansible.cfg12
-rw-r--r--group_vars/all/vars.yml44
-rw-r--r--group_vars/all/vault.yml11
-rw-r--r--inventory/hosts.yml7
-rw-r--r--roles/certbot/tasks/main.yml23
-rw-r--r--roles/cgit/tasks/main.yml47
-rw-r--r--roles/cgit/templates/cgitrc.j226
-rw-r--r--roles/cgit/templates/site-cgit.conf.j221
-rw-r--r--roles/common/tasks/main.yml26
-rw-r--r--roles/nginx/handlers/main.yml11
-rw-r--r--roles/nginx/tasks/main.yml87
-rw-r--r--roles/nginx/templates/default-app-searxng.conf.j223
-rw-r--r--roles/nginx/templates/nginx.conf.j284
-rw-r--r--roles/nginx/templates/site-default.conf.j2173
-rw-r--r--roles/nginx/templates/site-searxng.conf.j247
-rw-r--r--roles/searxng/handlers/main.yml5
-rw-r--r--roles/searxng/tasks/main.yml84
-rw-r--r--roles/searxng/templates/limiter.toml.j249
-rw-r--r--roles/searxng/templates/settings.yml.j225
-rw-r--r--roles/searxng/templates/uwsgi-searxng.ini.j278
-rw-r--r--roles/valkey/tasks/main.yml12
-rw-r--r--site.yml18
27 files changed, 1822 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..00f1747
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+# Local Python virtual environment
+.venv/
+__pycache__/
+*.py[cod]
+
+# Ansible Vault password file (NEVER COMMIT)
+.vault_pass
+*.vault_pass
+
+# Ansible artifacts & temporary files
+*.retry
+.ansible/
+ansible.log
+facts_cache/
+
+# Unencrypted secret overrides (if ever created temporarily)
+group_vars/**/secrets.yml
+host_vars/**/secrets.yml
+*.pem
+*.key
+.env
+.env.*
+
+# Editor, OS, and temporary files
+.DS_Store
+Thumbs.db
+*~
+*.swp
+*.swo
+*.bak
+*.tmp
+.idea/
+.vscode/
diff --git a/AGENT_INSTRUCTIONS.md b/AGENT_INSTRUCTIONS.md
new file mode 100644
index 0000000..45410d3
--- /dev/null
+++ b/AGENT_INSTRUCTIONS.md
@@ -0,0 +1,53 @@
+# AI Agent Instructions & Self-Updating Protocol
+
+This document defines the mandatory operating procedures and **Self-Updating Protocol** for any AI coding or DevOps agent working in this repository.
+
+---
+
+## 1. Core Principles
+
+1. **IaC is the Single Source of Truth:** Never make ad-hoc manual edits on `root@miguelpedro.xyz.br` without codifying them in the corresponding Ansible role under `roles/` or variables in `group_vars/all/`.
+2. **Protect Secrets:** Never write plaintext secrets to tracked files. Store all sensitive variables in `group_vars/all/vault.yml` (encrypted with `ansible-vault` using `.vault_pass`), and reference them in `group_vars/all/vars.yml` as `{{ vault_* }}`.
+3. **Check Before Apply:** Always run `.venv/bin/ansible-playbook site.yml --check --diff` and inspect the diff before applying changes to the live VPS.
+
+---
+
+## 2. Self-Updating Protocol (Mandatory for All Agent Sessions)
+
+Every AI agent session MUST execute this three-stage protocol:
+
+### Stage A: Pre-Flight State Verification (Start of Session)
+1. **Read Context:** Read [`README.md`](file:///home/mpedro/Repos/my-infra/README.md), [`DISCOVERY_REPORT.md`](file:///home/mpedro/Repos/my-infra/DISCOVERY_REPORT.md), and the latest entries in [` AGENT_INSTRUCTIONS.md` Changelog](#3-infrastructure-session-changelog) and [`COMMAND_LOG.md`](file:///home/mpedro/Repos/my-infra/COMMAND_LOG.md).
+2. **Detect Drift:** Run an Ansible dry-run against the VPS to verify whether the live state matches the repository:
+ ```bash
+ .venv/bin/ansible-playbook site.yml --check --diff
+ ```
+3. **Resolve Drift:** If unexpected drift is detected on the VPS (e.g., Certbot updated a file or a manual hotfix was applied), inspect the remote file via SSH, inform the user, and backport the remote change into the Ansible templates before proceeding.
+
+### Stage B: Implementation & Verification (During Session)
+1. **Modify IaC:** Implement requested changes in `group_vars/all/vars.yml`, `group_vars/all/vault.yml` (via `ansible-vault`), or the appropriate role in `roles/<role_name>/`.
+2. **Dry-Run & Apply:**
+ - Test with `.venv/bin/ansible-playbook site.yml --check --diff`.
+ - Apply with `.venv/bin/ansible-playbook site.yml --diff`.
+ - Re-run `.venv/bin/ansible-playbook site.yml --check --diff` to confirm idempotency (`changed=0`).
+3. **Health Check:** Verify service health (`systemctl status nginx uwsgi valkey-server` or `curl -I`) on the VPS.
+
+### Stage C: Documentation & Log Update (Before Closing Session)
+Before ending your session or creating a final commit, you **MUST** update the repository documentation:
+1. **Append to [`COMMAND_LOG.md`](file:///home/mpedro/Repos/my-infra/COMMAND_LOG.md):** Add a dated section logging the key SSH/Ansible commands executed during your session and their outputs.
+2. **Update [`DISCOVERY_REPORT.md`](file:///home/mpedro/Repos/my-infra/DISCOVERY_REPORT.md):** If any new services, ports, paths, certificates, or configuration files were added, modified, or removed on the VPS, update the architecture diagram and Critical State Map table.
+3. **Update [`README.md`](file:///home/mpedro/Repos/my-infra/README.md):** If new roles, tags, or domains were introduced, update the Architecture table and Repository Structure tree.
+4. **Append to the [Infrastructure Session Changelog](#3-infrastructure-session-changelog) below:** Record a concise summary of what changed, why, and the resulting state.
+
+---
+
+## 3. Infrastructure Session Changelog
+
+### [2026-10-29] Initial Reverse-Engineering & Ansible IaC Baseline
+- **Agent Action:**
+ - Reverse-engineered live VPS state (`root@miguelpedro.xyz.br`, Debian 13 `trixie`).
+ - Documented full discovery findings in `DISCOVERY_REPORT.md` and command outputs in `COMMAND_LOG.md`.
+ - Established Ansible IaC baseline (`site.yml`, `inventory/hosts.yml`, `group_vars/all/vars.yml`, and `ansible-vault`-encrypted `group_vars/all/vault.yml`).
+ - Created roles for `common`, `certbot`, `valkey`, `searxng`, `nginx`, and scaffolded `cgit` (`cgit_enabled: false`) for upcoming deployment.
+ - Cleaned up unused Nginx stub symlink (`/etc/nginx/sites-enabled/miguelpedro.xyz.br` which pointed to `example.com`) via `nginx_remove_unused_sites`.
+- **Verification:** Verified playbook against live VPS with `ansible-playbook site.yml --check --diff`.
diff --git a/COMMAND_LOG.md b/COMMAND_LOG.md
new file mode 100644
index 0000000..e386d19
--- /dev/null
+++ b/COMMAND_LOG.md
@@ -0,0 +1,595 @@
+# Phase 1: Command Execution Log
+
+**Session Date:** 2026-10-29
+**Target Host:** `root@miguelpedro.xyz.br`
+
+---
+
+## 1. Local Workspace Inspection
+**Command:**
+```bash
+ls -la /home/mpedro/Repos/my-infra
+```
+**Output:**
+```text
+total 8
+drwxr-xr-x 2 mpedro mpedro 4096 Oct 29 11:48 .
+drwxr-xr-x 18 mpedro mpedro 4096 Oct 29 11:48 ..
+```
+
+---
+
+## 2. VPS OS & Connectivity Check
+**Command:**
+```bash
+ssh -o BatchMode=yes -o ConnectTimeout=10 root@miguelpedro.xyz.br "uname -a; cat /etc/os-release; uptime"
+```
+**Output:**
+```text
+Linux dd.vps-kinghost.net 6.12.63+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.63-1 (2025-12-30) x86_64 GNU/Linux
+PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
+NAME="Debian GNU/Linux"
+VERSION_ID="13"
+VERSION="13 (trixie)"
+VERSION_CODENAME=trixie
+DEBIAN_VERSION_FULL=13.7
+ID=debian
+HOME_URL="https://www.debian.org/"
+SUPPORT_URL="https://www.debian.org/support"
+BUG_REPORT="https://bugs.debian.org/"
+ 11:49:36 up 53 days, 19:30, 2 users, load average: 0.01, 0.01, 0.00
+```
+
+---
+
+## 3. Nginx Configuration Inspection
+**Command:**
+```bash
+ssh root@miguelpedro.xyz.br '
+echo "=== NGINX FILES ==="
+find /etc/nginx -maxdepth 3 -ls
+echo "=== NGINX CONF ==="
+cat /etc/nginx/nginx.conf
+echo "=== NGINX SITES-ENABLED ==="
+head -n 200 /etc/nginx/sites-enabled/* /etc/nginx/conf.d/* 2>/dev/null
+'
+```
+**Output:**
+```text
+=== NGINX FILES ===
+ 397806 4 drwxr-xr-x 10 root root 4096 Sep 25 22:13 /etc/nginx
+ 397811 4 -rw-r--r-- 1 root root 2223 Jun 27 17:25 /etc/nginx/koi-win
+ 397820 4 drwxr-xr-x 2 root root 4096 Sep 25 22:12 /etc/nginx/sites-enabled
+ 398212 0 lrwxrwxrwx 1 root root 45 Sep 7 22:19 /etc/nginx/sites-enabled/miguelpedro.xyz.br -> /etc/nginx/sites-available/miguelpedro.xyz.br
+ 397993 0 lrwxrwxrwx 1 root root 34 Sep 7 17:30 /etc/nginx/sites-enabled/default -> /etc/nginx/sites-available/default
+ 413109 0 lrwxrwxrwx 1 root root 39 Sep 25 19:52 /etc/nginx/sites-enabled/searxng.conf -> /etc/nginx/sites-available/searxng.conf
+ 397808 4 -rw-r--r-- 1 root root 1125 Jun 27 17:25 /etc/nginx/fastcgi.conf
+ 397815 4 -rw-r--r-- 1 root root 1545 Jun 27 17:25 /etc/nginx/nginx.conf
+ 397814 4 drwxr-xr-x 2 root root 4096 Jun 27 17:33 /etc/nginx/modules-enabled
+ 397821 4 drwxr-xr-x 2 root root 4096 Sep 25 19:28 /etc/nginx/snippets
+ 397823 4 -rw-r--r-- 1 root root 217 Jun 27 17:25 /etc/nginx/snippets/snakeoil.conf
+ 397822 4 -rw-r--r-- 1 root root 423 Jun 27 17:25 /etc/nginx/snippets/fastcgi-php.conf
+ 397812 8 -rw-r--r-- 1 root root 5527 Jun 27 17:25 /etc/nginx/mime.types
+ 397809 4 -rw-r--r-- 1 root root 2460 Jun 27 17:25 /etc/nginx/fastcgi_params
+ 413451 4 drwxr-xr-x 2 root root 4096 Sep 25 22:00 /etc/nginx/default.apps-available
+ 413452 4 -rw-r--r-- 1 root root 647 Sep 25 22:00 /etc/nginx/default.apps-available/searxng.conf
+ 397810 4 -rw-r--r-- 1 root root 2837 Jun 27 17:25 /etc/nginx/koi-utf
+ 397813 4 drwxr-xr-x 2 root root 4096 Jun 27 17:33 /etc/nginx/modules-available
+ 397818 4 drwxr-xr-x 2 root root 4096 Sep 25 22:17 /etc/nginx/sites-available
+ 398211 4 -rw-r--r-- 1 root root 227 Sep 7 22:18 /etc/nginx/sites-available/miguelpedro.xyz.br
+ 397819 8 -rw-r--r-- 1 root root 4543 Sep 25 22:11 /etc/nginx/sites-available/default
+ 413455 4 -rw-r--r-- 1 root root 1570 Oct 27 21:21 /etc/nginx/sites-available/searxng.conf
+ 413447 8 -rw-r--r-- 1 root root 4432 Sep 25 22:00 /etc/nginx/sites-available/default.bak
+ 397825 4 -rw-r--r-- 1 root root 3071 Jun 27 17:25 /etc/nginx/win-utf
+ 397824 4 -rw-r--r-- 1 root root 2067 Jun 27 17:25 /etc/nginx/uwsgi_params
+ 397816 4 -rw-r--r-- 1 root root 1545 Jun 27 17:25 /etc/nginx/proxy_params
+ 413453 4 drwxr-xr-x 2 root root 4096 Sep 25 22:00 /etc/nginx/default.d
+ 413454 0 lrwxrwxrwx 1 root root 46 Sep 25 22:00 /etc/nginx/default.d/searxng.conf -> /etc/nginx/default.apps-available/searxng.conf
+ 397807 4 drwxr-xr-x 2 root root 4096 Jun 27 17:33 /etc/nginx/conf.d
+ 397817 4 -rw-r--r-- 1 root root 2038 Jun 27 17:25 /etc/nginx/scgi_params
+=== NGINX CONF ===
+user www-data;
+worker_processes auto;
+worker_cpu_affinity auto;
+pid /run/nginx.pid;
+error_log /var/log/nginx/error.log;
+include /etc/nginx/modules-enabled/*.conf;
+
+events {
+ worker_connections 768;
+ # multi_accept on;
+}
+
+http {
+ sendfile on;
+ tcp_nopush on;
+ types_hash_max_size 2048;
+ server_tokens off;
+
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_prefer_server_ciphers off;
+
+ access_log /var/log/nginx/access.log;
+
+ gzip on;
+
+ include /etc/nginx/conf.d/*.conf;
+ include /etc/nginx/sites-enabled/*;
+}
+=== NGINX SITES-ENABLED ===
+==> /etc/nginx/sites-enabled/default <==
+server {
+ # Load configuration files for the default server block.
+ include /etc/nginx/default.d/*.conf;
+
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ root /var/www/html;
+ index index.html index.htm index.nginx-debian.html;
+ server_name _;
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+}
+
+server {
+ root /var/www/html;
+ index index.html index.htm index.nginx-debian.html;
+ server_name www.miguelpedro.xyz.br miguelpedro.xyz.br; # managed by Certbot
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+
+ listen 443 ssl; # managed by Certbot
+ listen [::]:443 ssl ipv6only=on; # managed by Certbot
+ ssl_certificate /etc/letsencrypt/live/miguelpedro.xyz.br/fullchain.pem; # managed by Certbot
+ ssl_certificate_key /etc/letsencrypt/live/miguelpedro.xyz.br/privkey.pem; # managed by Certbot
+ include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+}
+server {
+ if ($host = www.miguelpedro.xyz.br) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+ if ($host = miguelpedro.xyz.br) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+ listen 80 ;
+ listen [::]:80 ;
+ server_name www.miguelpedro.xyz.br miguelpedro.xyz.br;
+ return 404; # managed by Certbot
+}
+
+==> /etc/nginx/sites-enabled/miguelpedro.xyz.br <==
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name example.com www.example.com;
+
+ root /var/www/miguelpedro.xyz.br/html;
+ index index.html index.htm;
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+}
+
+==> /etc/nginx/sites-enabled/searxng.conf <==
+server {
+ server_name searx.miguelpedro.xyz.br;
+
+ # Disable logging for user privacy
+ access_log /dev/null;
+ error_log /dev/null;
+
+ # Security headers
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Referrer-Policy "no-referrer" always;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+ location / {
+ uwsgi_pass unix:///usr/local/searxng/run/socket;
+
+ include uwsgi_params;
+
+ uwsgi_param HTTP_HOST $host;
+ uwsgi_param HTTP_CONNECTION $http_connection;
+
+ # see flaskfix.py
+ uwsgi_param HTTP_X_SCHEME $scheme;
+
+ # see limiter.py
+ uwsgi_param HTTP_X_REAL_IP $remote_addr;
+ uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
+ }
+
+ listen 443 ssl; # managed by Certbot
+ listen [::]:443 ssl; # managed by Certbot
+ ssl_certificate /etc/letsencrypt/live/searx.miguelpedro.xyz.br/fullchain.pem; # managed by Certbot
+ ssl_certificate_key /etc/letsencrypt/live/searx.miguelpedro.xyz.br/privkey.pem; # managed by Certbot
+ include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+}
+
+server {
+ if ($host = searx.miguelpedro.xyz.br) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+ listen 80;
+ listen [::]:80;
+ server_name searx.miguelpedro.xyz.br;
+ return 404; # managed by Certbot
+}
+```
+
+---
+
+## 4. Containers, Systemd Services, and SearXNG Processes
+**Command:**
+```bash
+ssh root@miguelpedro.xyz.br '
+echo "=== DOCKER / PODMAN ==="
+which docker podman 2>&1
+docker ps -a 2>/dev/null || true
+podman ps -a 2>/dev/null || true
+echo "=== SYSTEMD SERVICES (RUNNING) ==="
+systemctl list-units --type=service --state=running --no-pager
+echo "=== SEARXNG PROCESSES & USERS ==="
+ps aux | grep -iE "searx|uwsgi|gunicorn|valkey|redis|nginx"
+grep -i searx /etc/passwd /etc/group 2>/dev/null || true
+'
+```
+**Output:**
+```text
+=== DOCKER / PODMAN ===
+=== SYSTEMD SERVICES (RUNNING) ===
+ UNIT LOAD ACTIVE SUB DESCRIPTION
+ cron.service loaded active running Regular background program processing daemon
+ dbus.service loaded active running D-Bus System Message Bus
+ getty@tty1.service loaded active running Getty on tty1
+ nginx.service loaded active running A high performance web server and a reverse proxy server
+ serial-getty@hvc0.service loaded active running Serial Getty on hvc0
+ ssh.service loaded active running OpenBSD Secure Shell server
+ systemd-journald.service loaded active running Journal Service
+ systemd-logind.service loaded active running User Login Management
+ systemd-timesyncd.service loaded active running Network Time Synchronization
+ systemd-udevd.service loaded active running Rule-based Manager for Device Events and Files
+ user@0.service loaded active running User Manager for UID 0
+ uwsgi.service loaded active running LSB: Start/stop uWSGI server instance(s)
+ valkey-server.service loaded active running Advanced key-value store
+ xe-linux-distribution.service loaded active running LSB: Virtual Machine daemon providing host integration services
+
+14 loaded units listed.
+=== SEARXNG PROCESSES & USERS ===
+root 790020 0.0 0.0 7176 2684 ? Ss Sep25 0:00 SCREEN -S searx -- sudo -H -u searxng -i
+root 790021 0.0 0.2 22076 9852 pts/1 Ss+ Sep25 0:00 sudo -H -u searxng -i
+root 790023 0.0 0.0 22076 2872 pts/2 Ss Sep25 0:00 sudo -H -u searxng -i
+searxng 790024 0.0 0.1 9040 6176 pts/2 S+ Sep25 0:00 -bash
+valkey 791257 0.2 0.4 82992 17544 ? Ssl Sep25 114:21 /usr/bin/valkey-server 127.0.0.1:6379
+root 792532 0.0 0.2 26324 8596 ? Ss Sep25 0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
+searxng 1976899 0.0 0.3 33392 14516 ? S Oct27 0:07 /usr/bin/uwsgi --ini /usr/share/uwsgi/conf/default.ini --ini /etc/uwsgi/apps-enabled/searxng.ini --daemonize /var/log/uwsgi/app/searxng.log
+searxng 1976907 0.5 5.7 1120264 230328 ? Sl Oct27 12:41 /usr/bin/uwsgi --ini /usr/share/uwsgi/conf/default.ini --ini /etc/uwsgi/apps-enabled/searxng.ini --daemonize /var/log/uwsgi/app/searxng.log
+searxng 1976909 0.6 5.3 1177624 215760 ? Sl Oct27 14:07 /usr/bin/uwsgi --ini /usr/share/uwsgi/conf/default.ini --ini /etc/uwsgi/apps-enabled/searxng.ini --daemonize /var/log/uwsgi/app/searxng.log
+www-data 1976915 0.0 0.3 30464 15108 ? S Oct27 0:02 nginx: worker process
+www-data 1976916 0.0 0.3 30064 14700 ? S Oct27 0:02 nginx: worker process
+/etc/passwd:searxng:x:988:988:Privacy-respecting metasearch engine:/usr/local/searxng:/bin/bash
+/etc/group:sudo:x:27:searxng
+/etc/group:searxng:x:988:
+```
+
+---
+
+## 5. Firewall, Listening Ports, and TLS Certificates
+**Command:**
+```bash
+ssh root@miguelpedro.xyz.br '
+echo "=== UFW / IPTABLES / NFTABLES ==="
+which ufw iptables nft 2>&1
+ufw status verbose 2>/dev/null || true
+iptables -L -n -v 2>/dev/null || true
+nft list ruleset 2>/dev/null || true
+echo "=== PORTS LISTENING ==="
+ss -tulpn
+echo "=== CERTBOT / LETSENCRYPT ==="
+which certbot acme.sh 2>&1
+certbot certificates 2>/dev/null || true
+ls -la /etc/letsencrypt/live/ 2>/dev/null || true
+systemctl list-timers --no-pager
+'
+```
+**Output:**
+```text
+=== UFW / IPTABLES / NFTABLES ===
+/usr/sbin/nft
+=== PORTS LISTENING ===
+Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
+tcp LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("valkey-server",pid=791257,fd=8))
+tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=8328,fd=6))
+tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1976916,fd=6),("nginx",pid=1976915,fd=6),("nginx",pid=792532,fd=6))
+tcp LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1976916,fd=8),("nginx",pid=1976915,fd=8),("nginx",pid=792532,fd=8))
+tcp LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=8328,fd=7))
+tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=1976916,fd=7),("nginx",pid=1976915,fd=7),("nginx",pid=792532,fd=7))
+tcp LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=1976916,fd=9),("nginx",pid=1976915,fd=9),("nginx",pid=792532,fd=9))
+tcp LISTEN 0 511 [::1]:6379 [::]:* users:(("valkey-server",pid=791257,fd=9))
+=== CERTBOT / LETSENCRYPT ===
+/usr/bin/certbot
+
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Found the following certs:
+ Certificate Name: miguelpedro.xyz.br
+ Serial Number: 6a472cb5bc749aaaee4dfa3e4c3b0853061
+ Key Type: ECDSA
+ Domains: miguelpedro.xyz.br www.miguelpedro.xyz.br
+ Expiry Date: 2026-12-07 00:29:40+00:00 (VALID: 38 days)
+ Certificate Path: /etc/letsencrypt/live/miguelpedro.xyz.br/fullchain.pem
+ Private Key Path: /etc/letsencrypt/live/miguelpedro.xyz.br/privkey.pem
+ Certificate Name: searx.miguelpedro.xyz.br
+ Serial Number: 56f57df7717f294cc377199fce636ebca33
+ Key Type: ECDSA
+ Domains: searx.miguelpedro.xyz.br
+ Expiry Date: 2026-12-25 00:14:43+00:00 (VALID: 56 days)
+ Certificate Path: /etc/letsencrypt/live/searx.miguelpedro.xyz.br/fullchain.pem
+ Private Key Path: /etc/letsencrypt/live/searx.miguelpedro.xyz.br/privkey.pem
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+total 20
+drwx------ 4 root root 4096 Sep 25 22:13 .
+drwxr-xr-x 7 root root 4096 Oct 29 11:49 ..
+drwxr-xr-x 2 root root 4096 Sep 7 22:28 miguelpedro.xyz.br
+-rw-r--r-- 1 root root 740 Sep 7 22:28 README
+drwxr-xr-x 2 root root 4096 Sep 25 22:13 searx.miguelpedro.xyz.br
+NEXT LEFT LAST PASSED UNIT ACTIVATES
+Thu 2026-10-29 14:22:48 -03 2h 32min Thu 2026-10-29 05:12:54 -03 6h ago apt-daily.timer apt-daily.service
+Thu 2026-10-29 16:25:12 -03 4h 35min Thu 2026-10-29 01:34:23 -03 10h ago certbot.timer certbot.service
+Thu 2026-10-29 16:47:14 -03 4h 57min Wed 2026-10-28 16:47:14 -03 19h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
+Fri 2026-10-30 00:00:00 -03 12h Thu 2026-10-29 00:00:11 -03 11h ago dpkg-db-backup.timer dpkg-db-backup.service
+Fri 2026-10-30 00:25:43 -03 12h Thu 2026-10-29 00:13:23 -03 11h ago logrotate.timer logrotate.service
+Fri 2026-10-30 06:02:52 -03 18h Thu 2026-10-29 06:28:46 -03 5h 21min ago apt-daily-upgrade.timer apt-daily-upgrade.service
+Fri 2026-10-30 11:09:18 -03 23h Thu 2026-10-29 10:31:41 -03 1h 18min ago man-db.timer man-db.service
+Sun 2026-11-01 03:10:31 -03 2 days Sun 2026-10-25 03:10:14 -03 4 days ago e2scrub_all.timer e2scrub_all.service
+Mon 2026-11-02 00:12:12 -03 3 days Mon 2026-10-26 01:17:14 -03 3 days ago fstrim.timer fstrim.service
+```
+
+---
+
+## 6. SearXNG, uWSGI, and Git Installation Details
+**Command:**
+```bash
+ssh root@miguelpedro.xyz.br '
+echo "=== /etc/nginx/default.apps-available/searxng.conf ==="
+cat /etc/nginx/default.apps-available/searxng.conf
+echo "=== /etc/searxng ==="
+ls -la /etc/searxng 2>/dev/null || true
+cat /etc/searxng/* 2>/dev/null || true
+echo "=== /etc/uwsgi ==="
+find /etc/uwsgi -ls 2>/dev/null || true
+cat /etc/uwsgi/apps-available/* /etc/uwsgi/apps-enabled/* 2>/dev/null || true
+echo "=== /usr/local/searxng ==="
+ls -la /usr/local/searxng /usr/local/searxng/* 2>/dev/null || true
+echo "=== SEARXNG GIT INFO ==="
+git -C /usr/local/searxng/searxng-src status 2>/dev/null || true
+git -C /usr/local/searxng/searxng-src remote -v 2>/dev/null || true
+git -C /usr/local/searxng/searxng-src log -n 1 2>/dev/null || true
+'
+```
+**Output:**
+```text
+=== /etc/nginx/default.apps-available/searxng.conf ===
+location /searxng {
+
+ uwsgi_pass unix:///usr/local/searxng/run/socket;
+
+ include uwsgi_params;
+
+ uwsgi_param HTTP_HOST $host;
+ uwsgi_param HTTP_CONNECTION $http_connection;
+
+ # see flaskfix.py
+ uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
+ uwsgi_param HTTP_X_SCRIPT_NAME /searxng;
+
+ # see botdetection/trusted_proxies.py
+ uwsgi_param HTTP_X_REAL_IP $remote_addr;
+ uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
+}
+
+=== /etc/searxng ===
+total 16
+drwxr-xr-x 2 root root 4096 Oct 27 21:21 .
+drwxr-xr-x 78 root root 4096 Sep 25 22:00 ..
+-rw-r--r-- 1 searxng searxng 1458 Oct 27 21:21 limiter.toml
+-rw-r----- 1 searxng searxng 486 Oct 27 21:21 settings.yml
+
+# limiter.toml:
+[botdetection]
+ipv4_prefix = 32
+ipv6_prefix = 48
+trusted_proxies = [
+ '127.0.0.0/8',
+ '::1',
+]
+[botdetection.ip_limit]
+filter_link_local = false
+link_token = false
+[botdetection.ip_lists]
+block_ip = []
+pass_ip = []
+pass_searxng_org = true
+
+# settings.yml:
+use_default_settings: true
+
+general:
+ debug: false
+ instance_name: "SearXNG"
+
+search:
+ safe_search: 0
+ autocomplete: "duckduckgo"
+ formats:
+ - html
+ - json
+
+server:
+ # Is overwritten by ${SEARXNG_SECRET}
+ secret_key: "5e12306a0b0172e7b1c417f0b65320ac"
+ limiter: true
+ image_proxy: true
+ base_url: https://searx.miguelpedro.xyz.br/
+
+valkey:
+ # URL to connect valkey database. Is overwritten by ${SEARXNG_VALKEY_URL}.
+ url: valkey://localhost:6379/0
+
+=== /etc/uwsgi ===
+/etc/uwsgi/apps-enabled/searxng.ini -> /etc/uwsgi/apps-available/searxng.ini
+[uwsgi]
+uid = searxng
+gid = searxng
+env = LANG=C.UTF-8
+env = LANGUAGE=C.UTF-8
+env = LC_ALL=C.UTF-8
+chdir = /usr/local/searxng/searxng-src/searx
+env = SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
+disable-logging = true
+chmod-socket = 666
+single-interpreter = true
+master = true
+lazy-apps = true
+plugin = python3,http
+enable-threads = true
+workers = %k
+threads = 4
+module = searx.webapp
+virtualenv = /usr/local/searxng/searx-pyenv
+pythonpath = /usr/local/searxng/searxng-src
+socket = /usr/local/searxng/run/socket
+buffer-size = 8192
+offload-threads = %k
+
+=== /usr/local/searxng ===
+drwxr-xr-x 2 searxng searxng 4096 Oct 27 21:21 run (contains srw-rw-rw- 1 www-data root 0 socket)
+drwxrwxr-x 15 searxng searxng 4096 Oct 27 21:20 searxng-src
+drwxrwxr-x 5 searxng searxng 4096 Sep 25 21:58 searx-pyenv
+
+=== SEARXNG GIT INFO ===
+On branch master (origin: https://github.com/searxng/searxng)
+commit b5ed73fcb6c6bda5d4e8b13d1472aa3917f784dc (Tue Oct 27 21:33:35 2026 +0100)
+```
+
+---
+
+## 7. Valkey, Web Roots, Certbot Renewal, Systemd Units, and Packages
+**Command:**
+```bash
+ssh root@miguelpedro.xyz.br '
+echo "=== VALKEY CONFIG ==="
+grep -vE "^#|^$" /etc/valkey/valkey.conf 2>/dev/null || true
+echo "=== /var/www ==="
+find /var/www -maxdepth 3 -ls 2>/dev/null || true
+echo "=== /etc/letsencrypt/renewal ==="
+head -n 100 /etc/letsencrypt/renewal/* /etc/letsencrypt/options-ssl-nginx.conf 2>/dev/null || true
+echo "=== CRONTABS & SYSTEMD CUSTOM UNITS ==="
+crontab -l 2>/dev/null || true
+ls -la /etc/cron* /var/spool/cron/crontabs 2>/dev/null || true
+find /etc/systemd/system -maxdepth 2 -ls 2>/dev/null || true
+echo "=== APT MANUAL PACKAGES ==="
+apt-mark showmanual
+'
+```
+**Output:**
+```text
+=== VALKEY CONFIG ===
+bind 127.0.0.1 -::1
+protected-mode yes
+port 6379
+daemonize yes
+pidfile /run/valkey/valkey-server.pid
+logfile /var/log/valkey/valkey-server.log
+dir /var/lib/valkey
+(standard Debian Valkey configuration)
+
+=== /var/www ===
+ 1922 4 drwxr-xr-x 4 root root 4096 Sep 7 22:19 /var/www
+ 805 4 drwxr-xr-x 3 root root 4096 Sep 7 22:19 /var/www/miguelpedro.xyz.br
+ 888 4 drwxr-sr-x 2 root www-data 4096 Sep 7 22:19 /var/www/miguelpedro.xyz.br/html
+ 1925 4 drwxr-xr-x 2 root root 4096 Sep 7 17:30 /var/www/html
+ 1928 4 -rw-r--r-- 1 root root 615 Sep 7 17:30 /var/www/html/index.nginx-debian.html
+
+=== /etc/letsencrypt/renewal ===
+- miguelpedro.xyz.br.conf (authenticator = nginx, installer = nginx, key_type = ecdsa)
+- searx.miguelpedro.xyz.br.conf (authenticator = nginx, installer = nginx, key_type = ecdsa)
+
+=== APT MANUAL PACKAGES (Key Non-Base Packages) ===
+build-essential, certbot, git, libffi-dev, libssl-dev, libxslt1-dev, nftables, nginx,
+python-is-python3, python3, python3-babel, python3-certbot-nginx, python3-dev,
+python3-venv, screen, shellcheck, uwsgi, uwsgi-plugin-python3, valkey-server, zlib1g-dev
+```
+
+---
+
+## 8. Nftables, Web Root Files, uWSGI Defaults, and Sudoers
+**Command:**
+```bash
+ssh root@miguelpedro.xyz.br '
+echo "=== /etc/nftables.conf ==="
+cat /etc/nftables.conf 2>/dev/null || true
+systemctl status nftables 2>/dev/null || true
+echo "=== /var/www files ==="
+head -n 50 /var/www/html/* /var/www/miguelpedro.xyz.br/html/* 2>/dev/null || true
+echo "=== /etc/default/uwsgi ==="
+cat /etc/default/uwsgi 2>/dev/null || true
+echo "=== /usr/local/searxng/.profile ==="
+cat /usr/local/searxng/.profile 2>/dev/null || true
+echo "=== /etc/sudoers.d ==="
+ls -la /etc/sudoers.d/
+cat /etc/sudoers.d/* 2>/dev/null || true
+'
+```
+**Output:**
+```text
+=== /etc/nftables.conf ===
+Default empty filter chains (input, forward, output).
+nftables.service is loaded but inactive (dead).
+
+=== /var/www files ===
+/var/www/html/index.nginx-debian.html (standard Nginx welcome page)
+/var/www/miguelpedro.xyz.br/html (empty directory)
+
+=== /etc/default/uwsgi ===
+VERBOSE=yes
+PRINT_CONFNAMES_IN_INITD_SCRIPT_OUTPUT=no
+INHERITED_CONFIG=/usr/share/uwsgi/conf/default.ini
+
+=== /usr/local/searxng/.profile ===
+Standard ~/.profile with `source /usr/local/searxng/searx-pyenv/bin/activate` appended at the end.
+
+=== /etc/sudoers.d ===
+Only README present.
+```
+
+
+---
+
+## 9. Phase 3: Ansible IaC Verification Dry-Run (`--check --diff`)
+**Command:**
+```bash
+.venv/bin/ansible-playbook site.yml --check --diff
+```
+**Output Summary:**
+```text
+PLAY RECAP *********************************************************************
+vps : ok=28 changed=3 unreachable=0 failed=0 skipped=6 rescued=0 ignored=0
+```
+- **Verified 1:1 match (`ok`):** `common` packages, `certbot` timer & certs, `valkey` service, `searxng` user/group/dirs/git/settings.yml/limiter.toml/uwsgi.ini, `nginx.conf`, `default.apps-available/searxng.conf`, and `sites-available/searxng.conf`.
+- **Expected cleanups flagged (`changed`):** Removing unused `/etc/nginx/sites-enabled/miguelpedro.xyz.br` stub symlink (`example.com`) and trailing blank lines in `/etc/nginx/sites-available/default`.
diff --git a/DISCOVERY_REPORT.md b/DISCOVERY_REPORT.md
new file mode 100644
index 0000000..ab45750
--- /dev/null
+++ b/DISCOVERY_REPORT.md
@@ -0,0 +1,132 @@
+# VPS Discovery Report
+
+**Target Host:** `root@miguelpedro.xyz.br` (`dd.vps-kinghost.net`)
+**Discovery Date:** 2026-10-29
+**OS / Kernel:** Debian GNU/Linux 13 (trixie) v13.7 | Kernel `6.12.63+deb13-amd64` x86_64
+**Runtime Model:** Bare-metal / VM Systemd services (No Docker or Podman installed)
+
+---
+
+## 1. Architecture Overview
+
+```
+Internet (Ports 80/443)
+ │
+ ▼
+[ Nginx Reverse Proxy & Web Server ] (/etc/nginx)
+ ├── miguelpedro.xyz.br / www.miguelpedro.xyz.br ──► Static files (/var/www/html)
+ ├── _ (default_server :80) ───────────────────────► Static files + /searxng sub-path
+ └── searx.miguelpedro.xyz.br (HTTPS :443)
+ │
+ ▼ (Unix Socket: /usr/local/searxng/run/socket)
+ [ uWSGI Daemon ] (/etc/uwsgi/apps-enabled/searxng.ini)
+ │ Runs as user/group `searxng` (UID/GID 988)
+ │ Python venv: /usr/local/searxng/searx-pyenv
+ │ App source: /usr/local/searxng/searxng-src
+ │ Settings: /etc/searxng/settings.yml & limiter.toml
+ ▼ (TCP: 127.0.0.1:6379)
+ [ Valkey Server ] (/etc/valkey/valkey.conf)
+```
+
+---
+
+## 2. Web Server (Nginx)
+
+- **Service:** `nginx.service` (active, enabled)
+- **Main Config:** `/etc/nginx/nginx.conf` (standard Debian config with `server_tokens off;` enabled)
+- **Active Virtual Hosts (`/etc/nginx/sites-enabled/`):**
+ 1. **`default` (`-> /etc/nginx/sites-available/default`)**:
+ - Port 80 `default_server` (`server_name _;`): serves `/var/www/html` and includes `/etc/nginx/default.d/*.conf`.
+ - Port 443 SSL (`server_name www.miguelpedro.xyz.br miguelpedro.xyz.br;`): serves `/var/www/html` using Let's Encrypt certificate `miguelpedro.xyz.br`.
+ - Port 80 redirect block for `miguelpedro.xyz.br` and `www.miguelpedro.xyz.br` (301 to HTTPS).
+ - *Note:* `/etc/nginx/sites-available/default.bak` also exists as a backup prior to SearXNG setup.
+ 2. **`searxng.conf` (`-> /etc/nginx/sites-available/searxng.conf`)**:
+ - Port 443 SSL (`server_name searx.miguelpedro.xyz.br;`):
+ - Privacy logging: `access_log /dev/null;` and `error_log /dev/null;`
+ - Security headers: `X-Frame-Options "SAMEORIGIN"`, `X-Content-Type-Options "nosniff"`, `Referrer-Policy "no-referrer"`, `Strict-Transport-Security "max-age=31536000; includeSubDomains"`.
+ - Proxies `location /` via `uwsgi_pass unix:///usr/local/searxng/run/socket;` with `HTTP_HOST`, `HTTP_CONNECTION`, `HTTP_X_SCHEME`, `HTTP_X_REAL_IP`, and `HTTP_X_FORWARDED_FOR`.
+ - Port 80 redirect block for `searx.miguelpedro.xyz.br` (301 to HTTPS).
+ 3. **`miguelpedro.xyz.br` (`-> /etc/nginx/sites-available/miguelpedro.xyz.br`)**:
+ - Port 80 stub with `server_name example.com www.example.com;` pointing to `/var/www/miguelpedro.xyz.br/html` (currently an empty directory, while the active `miguelpedro.xyz.br` site is served from `sites-available/default`).
+- **Default App Includes (`/etc/nginx/default.d/`):**
+ - `/etc/nginx/default.d/searxng.conf -> /etc/nginx/default.apps-available/searxng.conf` (created by SearXNG's official installation script, exposing `location /searxng` on the default server block).
+
+---
+
+## 3. SearXNG, uWSGI & Valkey
+
+SearXNG was installed using the official SearXNG installation script (`utils/searxng.sh`), running natively via `uwsgi` and backed by `valkey-server`.
+
+### System User & Environment
+- **User/Group:** `searxng` (`UID=988`, `GID=988`, home `/usr/local/searxng`, shell `/bin/bash`, member of `sudo` group).
+- **Profile:** `/usr/local/searxng/.profile` automatically activates `/usr/local/searxng/searx-pyenv/bin/activate`.
+- **Source Repository:** `/usr/local/searxng/searxng-src`
+ - Git remote: `https://github.com/searxng/searxng` (`master` branch, commit `b5ed73fcb6c6bda5d4e8b13d1472aa3917f784dc`).
+- **Virtualenv:** `/usr/local/searxng/searx-pyenv`
+
+### SearXNG Configuration (`/etc/searxng/`)
+- **`/etc/searxng/settings.yml`** (`0640`, `searxng:searxng`):
+ - `use_default_settings: true`
+ - `general`: `debug: false`, `instance_name: "SearXNG"`
+ - `search`: `safe_search: 0`, `autocomplete: "duckduckgo"`, `formats: [html, json]`
+ - `server`: `secret_key` (32-char hex), `limiter: true`, `image_proxy: true`, `base_url: https://searx.miguelpedro.xyz.br/`
+ - `valkey`: `url: valkey://localhost:6379/0`
+- **`/etc/searxng/limiter.toml`** (`0644`, `searxng:searxng`):
+ - Configures `[botdetection]` with `ipv4_prefix = 32`, `ipv6_prefix = 48`, `trusted_proxies = ['127.0.0.0/8', '::1']`, and `pass_searxng_org = true`.
+
+### Application Server (uWSGI)
+- **Service:** `uwsgi.service` (SysV/LSB service via `/etc/init.d/uwsgi`, configured by `/etc/default/uwsgi`).
+- **App Config:** `/etc/uwsgi/apps-enabled/searxng.ini -> /etc/uwsgi/apps-available/searxng.ini`
+ - Runs as `uid = searxng`, `gid = searxng`
+ - `chdir = /usr/local/searxng/searxng-src/searx`
+ - `env = SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml`
+ - `disable-logging = true`, `chmod-socket = 666`
+ - `plugin = python3,http`, `workers = %k`, `threads = 4`, `offload-threads = %k`
+ - `module = searx.webapp`
+ - `virtualenv = /usr/local/searxng/searx-pyenv`
+ - `pythonpath = /usr/local/searxng/searxng-src`
+ - `socket = /usr/local/searxng/run/socket`
+
+### Key-Value Store (Valkey)
+- **Service:** `valkey-server.service` (active, enabled; aliased as `valkey.service`).
+- **Config:** `/etc/valkey/valkey.conf` (bound to `127.0.0.1` and `-::1` on port `6379`, data dir `/var/lib/valkey`).
+
+---
+
+## 4. TLS/SSL & Firewall State
+
+### TLS / Certbot
+- **Packages:** `certbot`, `python3-certbot-nginx`
+- **Timer:** `certbot.timer` (active, runs twice daily) and `/etc/cron.d/certbot`.
+- **Certificates (`/etc/letsencrypt/live/`):**
+ 1. `miguelpedro.xyz.br` (Domains: `miguelpedro.xyz.br`, `www.miguelpedro.xyz.br` | Key Type: ECDSA | Authenticator/Installer: `nginx`)
+ 2. `searx.miguelpedro.xyz.br` (Domains: `searx.miguelpedro.xyz.br` | Key Type: ECDSA | Authenticator/Installer: `nginx`)
+- **Shared Nginx TLS Configs:** `/etc/letsencrypt/options-ssl-nginx.conf` and `/etc/letsencrypt/ssl-dhparams.pem`.
+
+### Firewall & Network
+- **Listening Public Ports:**
+ - `22/tcp` (IPv4 & IPv6) — OpenSSH (`sshd`)
+ - `80/tcp` (IPv4 & IPv6) — Nginx HTTP
+ - `443/tcp` (IPv4 & IPv6) — Nginx HTTPS
+- **Listening Loopback Ports:**
+ - `6379/tcp` (`127.0.0.1` & `::1`) — Valkey Server
+- **Firewall State:**
+ - `ufw` and `iptables` are **not installed**.
+ - `nftables` (`/usr/sbin/nft`) is installed, with default empty tables in `/etc/nftables.conf`, and `nftables.service` is currently **disabled/inactive (dead)** (`nft list ruleset` is empty).
+
+---
+
+## 5. Complete Critical State Map
+
+| Component | Critical Path on VPS | Purpose |
+| :--- | :--- | :--- |
+| **Nginx Core** | `/etc/nginx/nginx.conf` | Global Nginx settings (`server_tokens off`, TLS 1.2/1.3) |
+| **Nginx VHosts** | `/etc/nginx/sites-available/default`<br>`/etc/nginx/sites-available/searxng.conf`<br>`/etc/nginx/sites-available/miguelpedro.xyz.br` | Main domain vhost, SearXNG reverse proxy vhost, and stub config |
+| **Nginx App Snippet** | `/etc/nginx/default.apps-available/searxng.conf` | `/searxng` sub-path snippet symlinked in `/etc/nginx/default.d/` |
+| **Web Root** | `/var/www/html/`<br>`/var/www/miguelpedro.xyz.br/html/` | Static web roots |
+| **SearXNG Config** | `/etc/searxng/settings.yml`<br>`/etc/searxng/limiter.toml` | SearXNG instance settings, secret key, Valkey URL, and bot limiter |
+| **SearXNG App & Venv** | `/usr/local/searxng/searxng-src/`<br>`/usr/local/searxng/searx-pyenv/`<br>`/usr/local/searxng/run/socket` | SearXNG git clone, Python virtualenv, and uWSGI Unix socket |
+| **uWSGI Config** | `/etc/uwsgi/apps-available/searxng.ini`<br>`/etc/default/uwsgi` | uWSGI worker configuration for SearXNG |
+| **Valkey Config** | `/etc/valkey/valkey.conf`<br>`/var/lib/valkey/` | Local Redis-compatible store used by SearXNG limiter |
+| **TLS Certificates** | `/etc/letsencrypt/live/*`<br>`/etc/letsencrypt/renewal/*.conf`<br>`/etc/letsencrypt/options-ssl-nginx.conf` | Certbot ECDSA certificates and renewal definitions |
+| **Firewall** | `/etc/nftables.conf` | Nftables ruleset (currently inactive) |
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..770013d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# `miguelpedro.xyz.br` Infrastructure-as-Code (IaC)
+
+This repository manages the configuration and state of the `miguelpedro.xyz.br` VPS (Debian 13 `trixie`) using **Ansible** and **Ansible Vault**.
+
+---
+
+## 1. Architecture & Managed Services
+
+| Service | Domain / Endpoint | Role | Description |
+| :--- | :--- | :--- | :--- |
+| **Nginx** | `miguelpedro.xyz.br`<br>`searx.miguelpedro.xyz.br` | [`roles/nginx`](file:///home/mpedro/Repos/my-infra/roles/nginx/tasks/main.yml) | Reverse proxy, TLS termination, and static web server |
+| **Certbot** | `/etc/letsencrypt/` | [`roles/certbot`](file:///home/mpedro/Repos/my-infra/roles/certbot/tasks/main.yml) | Automated Let's Encrypt ECDSA certificate management (`certbot.timer`) |
+| **SearXNG** | `https://searx.miguelpedro.xyz.br/` | [`roles/searxng`](file:///home/mpedro/Repos/my-infra/roles/searxng/tasks/main.yml) | Privacy-respecting metasearch engine served via `uwsgi` Unix socket |
+| **Valkey** | `127.0.0.1:6379` | [`roles/valkey`](file:///home/mpedro/Repos/my-infra/roles/valkey/tasks/main.yml) | Local key-value store backing SearXNG's rate limiter and bot detection |
+| **cgit** *(Prepared)* | `git.miguelpedro.xyz.br` | [`roles/cgit`](file:///home/mpedro/Repos/my-infra/roles/cgit/tasks/main.yml) | Ready-to-enable role (`cgit_enabled: false`) for self-hosted Git web UI via `fcgiwrap` |
+
+---
+
+## 2. Repository Structure
+
+```text
+my-infra/
+├── ansible.cfg # Ansible configuration (inventory, roles_path, vault_password_file)
+├── site.yml # Main playbook entrypoint
+├── inventory/
+│ └── hosts.yml # Target VPS inventory (root@miguelpedro.xyz.br)
+├── group_vars/
+│ └── all/
+│ ├── vars.yml # Global non-sensitive variables & feature flags
+│ └── vault.yml # AES-256 encrypted secrets (ansible-vault)
+├── roles/
+│ ├── common/ # System packages & build dependencies
+│ ├── certbot/ # Let's Encrypt packages, timer, and cert checks
+│ ├── valkey/ # Valkey server installation & systemd service
+│ ├── searxng/ # SearXNG user, git checkout, settings.yml, limiter.toml, uwsgi ini
+│ ├── nginx/ # nginx.conf, vhost templates, clean sites-enabled management
+│ └── cgit/ # Prepared role for upcoming cgit + fcgiwrap deployment
+├── AGENT_INSTRUCTIONS.md # Operational guide & Self-Updating Protocol for AI agents
+├── DISCOVERY_REPORT.md # Reverse-engineered state map of the VPS
+└── COMMAND_LOG.md # Running log of discovery and verification commands
+```
+
+---
+
+## 3. Usage & Operations
+
+### Prerequisites
+A local Python virtual environment (`.venv`) with `ansible-core` is pre-configured in this repository, along with a git-ignored `.vault_pass` file for automatic decryption of `group_vars/all/vault.yml`.
+
+If setting up on a new machine:
+```bash
+python3 -m venv .venv
+.venv/bin/pip install ansible-core
+# Place your Ansible Vault password in .vault_pass (chmod 600 .vault_pass)
+```
+
+### Dry-Run (Check for Configuration Drift)
+Always run in `--check --diff` mode first to preview changes without modifying the VPS:
+```bash
+.venv/bin/ansible-playbook site.yml --check --diff
+```
+
+### Apply Changes
+```bash
+.venv/bin/ansible-playbook site.yml --diff
+```
+
+### Run Specific Roles via Tags
+```bash
+.venv/bin/ansible-playbook site.yml --tags "nginx" --diff
+.venv/bin/ansible-playbook site.yml --tags "searxng" --diff
+```
+
+### Managing Secrets (`ansible-vault`)
+Sensitive values (such as `vault_searxng_secret_key`) are encrypted in [`group_vars/all/vault.yml`](file:///home/mpedro/Repos/my-infra/group_vars/all/vault.yml).
+- **View encrypted secrets:**
+ ```bash
+ .venv/bin/ansible-vault view group_vars/all/vault.yml
+ ```
+- **Edit encrypted secrets:**
+ ```bash
+ .venv/bin/ansible-vault edit group_vars/all/vault.yml
+ ```
+
+---
+
+## 4. Deploying `cgit` (Upcoming)
+
+The `cgit` role ([`roles/cgit`](file:///home/mpedro/Repos/my-infra/roles/cgit/tasks/main.yml)) is already scaffolded. When ready to deploy:
+1. Ensure DNS for `cgit_domain` (default `git.miguelpedro.xyz.br`) points to the VPS.
+2. In [`group_vars/all/vars.yml`](file:///home/mpedro/Repos/my-infra/group_vars/all/vars.yml), set:
+ ```yaml
+ cgit_enabled: true
+ ```
+3. Run `.venv/bin/ansible-playbook site.yml --tags "cgit" --diff`.
+4. Provision TLS via Certbot (`certbot --nginx -d git.miguelpedro.xyz.br`) and update [`roles/cgit/templates/site-cgit.conf.j2`](file:///home/mpedro/Repos/my-infra/roles/cgit/templates/site-cgit.conf.j2) to include the SSL directives.
diff --git a/ansible.cfg b/ansible.cfg
new file mode 100644
index 0000000..eb8d963
--- /dev/null
+++ b/ansible.cfg
@@ -0,0 +1,12 @@
+[defaults]
+inventory = inventory/hosts.yml
+roles_path = roles
+vault_password_file = .vault_pass
+host_key_checking = False
+retry_files_enabled = False
+interpreter_python = auto_silent
+stdout_callback = default
+result_format = yaml
+
+[ssh_connection]
+pipelining = True
diff --git a/group_vars/all/vars.yml b/group_vars/all/vars.yml
new file mode 100644
index 0000000..8ecb430
--- /dev/null
+++ b/group_vars/all/vars.yml
@@ -0,0 +1,44 @@
+---
+# Global Infrastructure Variables
+
+# Domains
+primary_domain: miguelpedro.xyz.br
+primary_domain_aliases:
+ - www.miguelpedro.xyz.br
+searxng_domain: searx.miguelpedro.xyz.br
+cgit_domain: git.miguelpedro.xyz.br
+
+# Web roots
+default_web_root: /var/www/html
+primary_web_root: /var/www/miguelpedro.xyz.br/html
+
+# Nginx configuration
+nginx_remove_unused_sites:
+ - miguelpedro.xyz.br
+
+# Valkey configuration
+valkey_bind_addresses: "127.0.0.1 -::1"
+valkey_port: 6379
+
+# SearXNG configuration
+searxng_user: searxng
+searxng_group: searxng
+searxng_uid: 988
+searxng_gid: 988
+searxng_home: /usr/local/searxng
+searxng_src_dir: /usr/local/searxng/searxng-src
+searxng_venv_dir: /usr/local/searxng/searx-pyenv
+searxng_run_dir: /usr/local/searxng/run
+searxng_socket_path: /usr/local/searxng/run/socket
+searxng_git_repo: https://github.com/searxng/searxng
+searxng_git_version: b5ed73fcb6c6bda5d4e8b13d1472aa3917f784dc
+searxng_instance_name: "SearXNG"
+searxng_base_url: "https://{{ searxng_domain }}/"
+searxng_valkey_url: "valkey://localhost:6379/0"
+searxng_secret_key: "{{ vault_searxng_secret_key }}"
+
+# Future cgit deployment toggle & defaults
+cgit_enabled: false
+cgit_repos_path: /srv/git
+cgit_title: "Miguel Pedro Git Repositories"
+cgit_description: "Self-hosted Git repositories powered by cgit"
diff --git a/group_vars/all/vault.yml b/group_vars/all/vault.yml
new file mode 100644
index 0000000..24d2418
--- /dev/null
+++ b/group_vars/all/vault.yml
@@ -0,0 +1,11 @@
+$ANSIBLE_VAULT;1.1;AES256
+36346435326632643666656638653461633663663564343262616135663231366637656338326431
+6538316131393133626634383632633838376535613733630a613733373063373930633163653330
+37323430646462313032373861613437653761623663663438313334383662363539393430643566
+3765613065353866620a616530386436623063346265376235363338333437653331343065626132
+38326565663137393061373566323435623065323631646337636437616539343239363162303538
+39366236643239616434633238323761666535636631643636373966653363306536353031646366
+31663431363233616239643165326161363163623761373734643438353930323233666263313334
+34373034396261346130366231353065666632343032323761653738636364623763353238643563
+32316233323436636265306366356639323865313062663561613039303238306639393933303634
+3965616565613436663032356437383862643034333231326165
diff --git a/inventory/hosts.yml b/inventory/hosts.yml
new file mode 100644
index 0000000..3789703
--- /dev/null
+++ b/inventory/hosts.yml
@@ -0,0 +1,7 @@
+---
+all:
+ hosts:
+ vps:
+ ansible_host: miguelpedro.xyz.br
+ ansible_user: root
+ ansible_port: 22
diff --git a/roles/certbot/tasks/main.yml b/roles/certbot/tasks/main.yml
new file mode 100644
index 0000000..02e8c0b
--- /dev/null
+++ b/roles/certbot/tasks/main.yml
@@ -0,0 +1,23 @@
+---
+- name: Ensure Certbot and Nginx plugin are installed
+ ansible.builtin.apt:
+ name:
+ - certbot
+ - python3-certbot-nginx
+ state: present
+
+- name: Ensure certbot renewal timer is enabled and active
+ ansible.builtin.systemd:
+ name: certbot.timer
+ enabled: true
+ state: started
+
+- name: Verify primary domain TLS certificate exists
+ ansible.builtin.stat:
+ path: "/etc/letsencrypt/live/{{ primary_domain }}/fullchain.pem"
+ register: primary_cert
+
+- name: Verify SearXNG domain TLS certificate exists
+ ansible.builtin.stat:
+ path: "/etc/letsencrypt/live/{{ searxng_domain }}/fullchain.pem"
+ register: searxng_cert
diff --git a/roles/cgit/tasks/main.yml b/roles/cgit/tasks/main.yml
new file mode 100644
index 0000000..44f9459
--- /dev/null
+++ b/roles/cgit/tasks/main.yml
@@ -0,0 +1,47 @@
+---
+- name: Install cgit and FastCGI dependencies
+ ansible.builtin.apt:
+ name:
+ - cgit
+ - fcgiwrap
+ - python3-pygments
+ - python3-markdown
+ state: present
+
+- name: Ensure Git repositories root directory exists
+ ansible.builtin.file:
+ path: "{{ cgit_repos_path }}"
+ state: directory
+ owner: www-data
+ group: www-data
+ mode: "0755"
+
+- name: Deploy /etc/cgitrc configuration
+ ansible.builtin.template:
+ src: cgitrc.j2
+ dest: /etc/cgitrc
+ owner: root
+ group: root
+ mode: "0644"
+
+- name: Ensure fcgiwrap socket/service is enabled and running
+ ansible.builtin.systemd:
+ name: fcgiwrap.socket
+ enabled: true
+ state: started
+
+- name: Deploy Nginx virtual host for cgit
+ ansible.builtin.template:
+ src: site-cgit.conf.j2
+ dest: /etc/nginx/sites-available/cgit.conf
+ owner: root
+ group: root
+ mode: "0644"
+ notify: Reload nginx
+
+- name: Enable Nginx virtual host for cgit
+ ansible.builtin.file:
+ src: /etc/nginx/sites-available/cgit.conf
+ dest: /etc/nginx/sites-enabled/cgit.conf
+ state: link
+ notify: Reload nginx
diff --git a/roles/cgit/templates/cgitrc.j2 b/roles/cgit/templates/cgitrc.j2
new file mode 100644
index 0000000..0cae2ab
--- /dev/null
+++ b/roles/cgit/templates/cgitrc.j2
@@ -0,0 +1,26 @@
+# /etc/cgitrc - Managed by Ansible
+css=/cgit-css/cgit.css
+logo=/cgit-css/cgit.png
+favicon=/cgit-css/favicon.ico
+
+root-title={{ cgit_title }}
+root-desc={{ cgit_description }}
+virtual-root=/
+
+enable-index-links=1
+enable-index-owner=0
+enable-commit-graph=1
+enable-log-filecount=1
+enable-log-linecount=1
+snapshots=tar.gz zip
+
+about-filter=/usr/lib/cgit/filters/about-formatting.sh
+source-filter=/usr/lib/cgit/filters/syntax-highlighting.py
+
+readme=:README.md
+readme=:readme.md
+readme=:README.rst
+readme=:README.txt
+readme=:README
+
+scan-path={{ cgit_repos_path }}
diff --git a/roles/cgit/templates/site-cgit.conf.j2 b/roles/cgit/templates/site-cgit.conf.j2
new file mode 100644
index 0000000..557c364
--- /dev/null
+++ b/roles/cgit/templates/site-cgit.conf.j2
@@ -0,0 +1,21 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name {{ cgit_domain }};
+
+ root /usr/share/cgit;
+ try_files $uri @cgit;
+
+ location ~* ^/cgit-css/(.*)$ {
+ alias /usr/share/cgit/$1;
+ }
+
+ location @cgit {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_param HTTP_HOST $server_name;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+}
diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml
new file mode 100644
index 0000000..413a3dd
--- /dev/null
+++ b/roles/common/tasks/main.yml
@@ -0,0 +1,26 @@
+---
+- name: Ensure base system and build dependencies are installed
+ ansible.builtin.apt:
+ name:
+ - build-essential
+ - ca-certificates
+ - curl
+ - git
+ - libffi-dev
+ - libssl-dev
+ - libxslt1-dev
+ - nftables
+ - python-is-python3
+ - python3
+ - python3-babel
+ - python3-dev
+ - python3-venv
+ - screen
+ - shellcheck
+ - uwsgi
+ - uwsgi-plugin-python3
+ - vim
+ - wget
+ - zlib1g-dev
+ state: present
+ update_cache: false
diff --git a/roles/nginx/handlers/main.yml b/roles/nginx/handlers/main.yml
new file mode 100644
index 0000000..3940edb
--- /dev/null
+++ b/roles/nginx/handlers/main.yml
@@ -0,0 +1,11 @@
+---
+- name: Validate and reload nginx
+ ansible.builtin.command: /usr/sbin/nginx -t
+ changed_when: false
+ notify: Reload nginx service
+
+- name: Reload nginx service
+ ansible.builtin.systemd:
+ name: nginx.service
+ state: reloaded
+ listen: Reload nginx
diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml
new file mode 100644
index 0000000..c4f55b6
--- /dev/null
+++ b/roles/nginx/tasks/main.yml
@@ -0,0 +1,87 @@
+---
+- name: Ensure nginx is installed
+ ansible.builtin.apt:
+ name:
+ - nginx
+ state: present
+
+- name: Ensure Nginx directories exist
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ owner: root
+ group: root
+ mode: "0755"
+ loop:
+ - /etc/nginx/sites-available
+ - /etc/nginx/sites-enabled
+ - /etc/nginx/default.apps-available
+ - /etc/nginx/default.d
+ - "{{ default_web_root }}"
+
+- name: Deploy /etc/nginx/nginx.conf
+ ansible.builtin.template:
+ src: nginx.conf.j2
+ dest: /etc/nginx/nginx.conf
+ owner: root
+ group: root
+ mode: "0644"
+ validate: "/usr/sbin/nginx -t -c %s"
+ notify: Reload nginx
+
+- name: Deploy /etc/nginx/default.apps-available/searxng.conf
+ ansible.builtin.template:
+ src: default-app-searxng.conf.j2
+ dest: /etc/nginx/default.apps-available/searxng.conf
+ owner: root
+ group: root
+ mode: "0644"
+ notify: Reload nginx
+
+- name: Enable /etc/nginx/default.d/searxng.conf symlink
+ ansible.builtin.file:
+ src: /etc/nginx/default.apps-available/searxng.conf
+ dest: /etc/nginx/default.d/searxng.conf
+ state: link
+ notify: Reload nginx
+
+- name: Deploy /etc/nginx/sites-available/default
+ ansible.builtin.template:
+ src: site-default.conf.j2
+ dest: /etc/nginx/sites-available/default
+ owner: root
+ group: root
+ mode: "0644"
+ notify: Reload nginx
+
+- name: Deploy /etc/nginx/sites-available/searxng.conf
+ ansible.builtin.template:
+ src: site-searxng.conf.j2
+ dest: /etc/nginx/sites-available/searxng.conf
+ owner: root
+ group: root
+ mode: "0644"
+ notify: Reload nginx
+
+- name: Enable active Nginx sites
+ ansible.builtin.file:
+ src: "/etc/nginx/sites-available/{{ item }}"
+ dest: "/etc/nginx/sites-enabled/{{ item }}"
+ state: link
+ loop:
+ - default
+ - searxng.conf
+ notify: Reload nginx
+
+- name: Remove unused/stub Nginx site symlinks (clean tracking)
+ ansible.builtin.file:
+ path: "/etc/nginx/sites-enabled/{{ item }}"
+ state: absent
+ loop: "{{ nginx_remove_unused_sites | default([]) }}"
+ notify: Reload nginx
+
+- name: Ensure nginx service is enabled and running
+ ansible.builtin.systemd:
+ name: nginx.service
+ enabled: true
+ state: started
diff --git a/roles/nginx/templates/default-app-searxng.conf.j2 b/roles/nginx/templates/default-app-searxng.conf.j2
new file mode 100644
index 0000000..66b1f86
--- /dev/null
+++ b/roles/nginx/templates/default-app-searxng.conf.j2
@@ -0,0 +1,23 @@
+location /searxng {
+
+ uwsgi_pass unix://{{ searxng_socket_path }};
+
+ include uwsgi_params;
+
+ uwsgi_param HTTP_HOST $host;
+ uwsgi_param HTTP_CONNECTION $http_connection;
+
+ # see flaskfix.py
+ uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
+ uwsgi_param HTTP_X_SCRIPT_NAME /searxng;
+
+ # see botdetection/trusted_proxies.py
+ uwsgi_param HTTP_X_REAL_IP $remote_addr;
+ uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
+}
+
+# To serve the static files via the HTTP server
+#
+# location /searxng/static/ {
+# alias /usr/local/searxng/searxng-src/searx/static/;
+# }
diff --git a/roles/nginx/templates/nginx.conf.j2 b/roles/nginx/templates/nginx.conf.j2
new file mode 100644
index 0000000..68a8fd4
--- /dev/null
+++ b/roles/nginx/templates/nginx.conf.j2
@@ -0,0 +1,84 @@
+user www-data;
+worker_processes auto;
+worker_cpu_affinity auto;
+pid /run/nginx.pid;
+error_log /var/log/nginx/error.log;
+include /etc/nginx/modules-enabled/*.conf;
+
+events {
+ worker_connections 768;
+ # multi_accept on;
+}
+
+http {
+
+ ##
+ # Basic Settings
+ ##
+
+ sendfile on;
+ tcp_nopush on;
+ types_hash_max_size 2048;
+ server_tokens off; # Recommended practice is to turn this off
+
+ # server_names_hash_bucket_size 64;
+ # server_name_in_redirect off;
+
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ ##
+ # SSL Settings
+ ##
+
+ ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1
+ ssl_prefer_server_ciphers off; # Don't force server cipher order.
+
+ ##
+ # Logging Settings
+ ##
+
+ access_log /var/log/nginx/access.log;
+
+ ##
+ # Gzip Settings
+ ##
+
+ gzip on;
+
+ # 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 application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+ ##
+ # Virtual Host Configs
+ ##
+
+ include /etc/nginx/conf.d/*.conf;
+ include /etc/nginx/sites-enabled/*;
+}
+
+
+#mail {
+# # See sample authentication script at:
+# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
+#
+# # auth_http localhost/auth.php;
+# # pop3_capabilities "TOP" "USER";
+# # imap_capabilities "IMAP4rev1" "UIDPLUS";
+#
+# server {
+# listen localhost:110;
+# protocol pop3;
+# proxy on;
+# }
+#
+# server {
+# listen localhost:143;
+# protocol imap;
+# proxy on;
+# }
+#}
diff --git a/roles/nginx/templates/site-default.conf.j2 b/roles/nginx/templates/site-default.conf.j2
new file mode 100644
index 0000000..2542a3a
--- /dev/null
+++ b/roles/nginx/templates/site-default.conf.j2
@@ -0,0 +1,173 @@
+##
+# You should look at the following URL's in order to grasp a solid understanding
+# of Nginx configuration files in order to fully unleash the power of Nginx.
+# https://www.nginx.com/resources/wiki/start/
+# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
+# https://wiki.debian.org/Nginx/DirectoryStructure
+#
+# In most cases, administrators will remove this file from sites-enabled/ and
+# leave it as reference inside of sites-available where it will continue to be
+# updated by the nginx packaging team.
+#
+# This file will automatically load configuration files provided by other
+# applications, such as Drupal or Wordpress. These applications will be made
+# available underneath a path with that package name, such as /drupal8.
+#
+# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
+##
+
+# Default server configuration
+#
+server {
+ # Load configuration files for the default server block.
+ include /etc/nginx/default.d/*.conf;
+
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ # SSL configuration
+ #
+ # listen 443 ssl default_server;
+ # listen [::]:443 ssl default_server;
+ #
+ # Note: You should disable gzip for SSL traffic.
+ # See: https://bugs.debian.org/773332
+ #
+ # Read up on ssl_ciphers to ensure a secure configuration.
+ # See: https://bugs.debian.org/765782
+ #
+ # Self signed certs generated by the ssl-cert package
+ # Don't use them in a production server!
+ #
+ # include snippets/snakeoil.conf;
+
+ root {{ default_web_root }};
+
+ # Add index.php to the list if you are using PHP
+ index index.html index.htm index.nginx-debian.html;
+
+ server_name _;
+
+ location / {
+ # First attempt to serve request as file, then
+ # as directory, then fall back to displaying a 404.
+ try_files $uri $uri/ =404;
+ }
+
+ # pass PHP scripts to FastCGI server
+ #
+ #location ~ \.php$ {
+ # include snippets/fastcgi-php.conf;
+ #
+ # # With php-fpm (or other unix sockets):
+ # fastcgi_pass unix:/run/php/php7.4-fpm.sock;
+ # # With php-cgi (or other tcp sockets):
+ # fastcgi_pass 127.0.0.1:9000;
+ #}
+
+ # deny access to .htaccess files, if Apache's document root
+ # concurs with nginx's one
+ #
+ #location ~ /\.ht {
+ # deny all;
+ #}
+}
+
+
+# Virtual Host configuration for example.com
+#
+# You can move that to a different file under sites-available/ and symlink that
+# to sites-enabled/ to enable it.
+#
+#server {
+# listen 80;
+# listen [::]:80;
+#
+# server_name example.com;
+#
+# root /var/www/example.com;
+# index index.html;
+#
+# location / {
+# try_files $uri $uri/ =404;
+# }
+#}
+
+server {
+
+ # SSL configuration
+ #
+ # listen 443 ssl default_server;
+ # listen [::]:443 ssl default_server;
+ #
+ # Note: You should disable gzip for SSL traffic.
+ # See: https://bugs.debian.org/773332
+ #
+ # Read up on ssl_ciphers to ensure a secure configuration.
+ # See: https://bugs.debian.org/765782
+ #
+ # Self signed certs generated by the ssl-cert package
+ # Don't use them in a production server!
+ #
+ # include snippets/snakeoil.conf;
+
+ root {{ default_web_root }};
+
+ # Add index.php to the list if you are using PHP
+ index index.html index.htm index.nginx-debian.html;
+ server_name {{ primary_domain_aliases | join(' ') }} {{ primary_domain }}; # managed by Certbot
+
+
+ location / {
+ # First attempt to serve request as file, then
+ # as directory, then fall back to displaying a 404.
+ try_files $uri $uri/ =404;
+ }
+
+ # pass PHP scripts to FastCGI server
+ #
+ #location ~ \.php$ {
+ # include snippets/fastcgi-php.conf;
+ #
+ # # With php-fpm (or other unix sockets):
+ # fastcgi_pass unix:/run/php/php7.4-fpm.sock;
+ # # With php-cgi (or other tcp sockets):
+ # fastcgi_pass 127.0.0.1:9000;
+ #}
+
+ # deny access to .htaccess files, if Apache's document root
+ # concurs with nginx's one
+ #
+ #location ~ /\.ht {
+ # deny all;
+ #}
+
+
+ listen 443 ssl; # managed by Certbot
+ listen [::]:443 ssl ipv6only=on; # managed by Certbot
+ ssl_certificate /etc/letsencrypt/live/{{ primary_domain }}/fullchain.pem; # managed by Certbot
+ ssl_certificate_key /etc/letsencrypt/live/{{ primary_domain }}/privkey.pem; # managed by Certbot
+ include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+
+
+}
+server {
+{% for alias in primary_domain_aliases %}
+ if ($host = {{ alias }}) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+
+{% endfor %}
+ if ($host = {{ primary_domain }}) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+
+ listen 80 ;
+ listen [::]:80 ;
+ server_name {{ primary_domain_aliases | join(' ') }} {{ primary_domain }};
+ return 404; # managed by Certbot
+}
diff --git a/roles/nginx/templates/site-searxng.conf.j2 b/roles/nginx/templates/site-searxng.conf.j2
new file mode 100644
index 0000000..67a5a2d
--- /dev/null
+++ b/roles/nginx/templates/site-searxng.conf.j2
@@ -0,0 +1,47 @@
+server {
+ server_name {{ searxng_domain }};
+
+ # Disable logging for user privacy
+ access_log /dev/null;
+ error_log /dev/null;
+
+ # Security headers
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Referrer-Policy "no-referrer" always;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+ location / {
+ uwsgi_pass unix://{{ searxng_socket_path }};
+
+ include uwsgi_params;
+
+ uwsgi_param HTTP_HOST $host;
+ uwsgi_param HTTP_CONNECTION $http_connection;
+
+ # see flaskfix.py
+ uwsgi_param HTTP_X_SCHEME $scheme;
+
+ # see limiter.py
+ uwsgi_param HTTP_X_REAL_IP $remote_addr;
+ uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
+ }
+
+ listen 443 ssl; # managed by Certbot
+ listen [::]:443 ssl; # managed by Certbot
+ ssl_certificate /etc/letsencrypt/live/{{ searxng_domain }}/fullchain.pem; # managed by Certbot
+ ssl_certificate_key /etc/letsencrypt/live/{{ searxng_domain }}/privkey.pem; # managed by Certbot
+ include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+}
+
+server {
+ if ($host = {{ searxng_domain }}) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+ listen 80;
+ listen [::]:80;
+ server_name {{ searxng_domain }};
+ return 404; # managed by Certbot
+}
diff --git a/roles/searxng/handlers/main.yml b/roles/searxng/handlers/main.yml
new file mode 100644
index 0000000..e049618
--- /dev/null
+++ b/roles/searxng/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Restart uwsgi
+ ansible.builtin.service:
+ name: uwsgi
+ state: restarted
diff --git a/roles/searxng/tasks/main.yml b/roles/searxng/tasks/main.yml
new file mode 100644
index 0000000..c7d45d4
--- /dev/null
+++ b/roles/searxng/tasks/main.yml
@@ -0,0 +1,84 @@
+---
+- name: Ensure searxng system group exists
+ ansible.builtin.group:
+ name: "{{ searxng_group }}"
+ gid: "{{ searxng_gid }}"
+ system: true
+ state: present
+
+- name: Ensure searxng system user exists
+ ansible.builtin.user:
+ name: "{{ searxng_user }}"
+ uid: "{{ searxng_uid }}"
+ group: "{{ searxng_group }}"
+ groups: sudo
+ append: true
+ comment: "Privacy-respecting metasearch engine"
+ home: "{{ searxng_home }}"
+ shell: /bin/bash
+ system: true
+ state: present
+
+- name: Ensure SearXNG directories exist
+ ansible.builtin.file:
+ path: "{{ item.path }}"
+ state: directory
+ owner: "{{ item.owner }}"
+ group: "{{ item.group }}"
+ mode: "{{ item.mode }}"
+ loop:
+ - { path: "{{ searxng_home }}", owner: "{{ searxng_user }}", group: "{{ searxng_group }}", mode: "0755" }
+ - { path: "{{ searxng_home }}/.ansible/tmp", owner: "{{ searxng_user }}", group: "{{ searxng_group }}", mode: "0700" }
+ - { path: "{{ searxng_run_dir }}", owner: "{{ searxng_user }}", group: "{{ searxng_group }}", mode: "0755" }
+ - { path: "/etc/searxng", owner: "root", group: "root", mode: "0755" }
+ - { path: "/etc/uwsgi/apps-available", owner: "root", group: "root", mode: "0755" }
+ - { path: "/etc/uwsgi/apps-enabled", owner: "root", group: "root", mode: "0755" }
+
+- name: Clone or update SearXNG source repository
+ ansible.builtin.git:
+ repo: "{{ searxng_git_repo }}"
+ dest: "{{ searxng_src_dir }}"
+ version: "{{ searxng_git_version }}"
+ update: false
+ become: true
+ become_user: "{{ searxng_user }}"
+
+- name: Deploy /etc/searxng/settings.yml
+ ansible.builtin.template:
+ src: settings.yml.j2
+ dest: /etc/searxng/settings.yml
+ owner: "{{ searxng_user }}"
+ group: "{{ searxng_group }}"
+ mode: "0640"
+ notify: Restart uwsgi
+
+- name: Deploy /etc/searxng/limiter.toml
+ ansible.builtin.template:
+ src: limiter.toml.j2
+ dest: /etc/searxng/limiter.toml
+ owner: "{{ searxng_user }}"
+ group: "{{ searxng_group }}"
+ mode: "0644"
+ notify: Restart uwsgi
+
+- name: Deploy uWSGI app configuration for SearXNG
+ ansible.builtin.template:
+ src: uwsgi-searxng.ini.j2
+ dest: /etc/uwsgi/apps-available/searxng.ini
+ owner: root
+ group: root
+ mode: "0644"
+ notify: Restart uwsgi
+
+- name: Enable uWSGI SearXNG app symlink
+ ansible.builtin.file:
+ src: /etc/uwsgi/apps-available/searxng.ini
+ dest: /etc/uwsgi/apps-enabled/searxng.ini
+ state: link
+ notify: Restart uwsgi
+
+- name: Ensure uwsgi service is enabled and running
+ ansible.builtin.service:
+ name: uwsgi
+ enabled: true
+ state: started
diff --git a/roles/searxng/templates/limiter.toml.j2 b/roles/searxng/templates/limiter.toml.j2
new file mode 100644
index 0000000..0b40bf8
--- /dev/null
+++ b/roles/searxng/templates/limiter.toml.j2
@@ -0,0 +1,49 @@
+[botdetection]
+
+# The prefix defines the number of leading bits in an address that are compared
+# to determine whether or not an address is part of a (client) network.
+
+ipv4_prefix = 32
+ipv6_prefix = 48
+
+# If the request IP is in trusted_proxies list, the client IP address is
+# extracted from the X-Forwarded-For and X-Real-IP headers. This should be
+# used if SearXNG is behind a reverse proxy or load balancer.
+
+trusted_proxies = [
+ '127.0.0.0/8',
+ '::1',
+ # '192.168.0.0/16',
+ # '172.16.0.0/12',
+ # '10.0.0.0/8',
+ # 'fd00::/8',
+]
+
+[botdetection.ip_limit]
+
+# To get unlimited access in a local network, by default link-local addresses
+# (networks) are not monitored by the ip_limit
+filter_link_local = false
+
+# activate link_token method in the ip_limit method
+link_token = false
+
+[botdetection.ip_lists]
+
+# In the limiter, the ip_lists method has priority over all other methods -> if
+# an IP is in the pass_ip list, it has unrestricted access and it is also not
+# checked if e.g. the "user agent" suggests a bot (e.g. curl).
+
+block_ip = [
+ # '93.184.216.34', # IPv4 of example.org
+ # '257.1.1.1', # invalid IP --> will be ignored, logged in ERROR class
+]
+
+pass_ip = [
+ # '192.168.0.0/16', # IPv4 private network
+ # 'fe80::/10' # IPv6 linklocal / wins over botdetection.ip_limit.filter_link_local
+]
+
+# Activate passlist of (hardcoded) IPs from the SearXNG organization,
+# e.g. `check.searx.space`.
+pass_searxng_org = true
diff --git a/roles/searxng/templates/settings.yml.j2 b/roles/searxng/templates/settings.yml.j2
new file mode 100644
index 0000000..ae23d6f
--- /dev/null
+++ b/roles/searxng/templates/settings.yml.j2
@@ -0,0 +1,25 @@
+# SearXNG settings
+
+use_default_settings: true
+
+general:
+ debug: false
+ instance_name: "{{ searxng_instance_name }}"
+
+search:
+ safe_search: 0
+ autocomplete: "duckduckgo"
+ formats:
+ - html
+ - json
+
+server:
+ # Is overwritten by ${SEARXNG_SECRET}
+ secret_key: "{{ searxng_secret_key }}"
+ limiter: true
+ image_proxy: true
+ base_url: {{ searxng_base_url }}
+
+valkey:
+ # URL to connect valkey database. Is overwritten by ${SEARXNG_VALKEY_URL}.
+ url: {{ searxng_valkey_url }}
diff --git a/roles/searxng/templates/uwsgi-searxng.ini.j2 b/roles/searxng/templates/uwsgi-searxng.ini.j2
new file mode 100644
index 0000000..87e333f
--- /dev/null
+++ b/roles/searxng/templates/uwsgi-searxng.ini.j2
@@ -0,0 +1,78 @@
+# -*- mode: conf; coding: utf-8 -*-
+[uwsgi]
+
+# uWSGI core
+# ----------
+#
+# https://uwsgi-docs.readthedocs.io/en/latest/Options.html#uwsgi-core
+
+# Who will run the code / Hint: in emperor-tyrant mode uid & gid setting will be
+# ignored [1]. Mode emperor-tyrant is the default on fedora (/etc/uwsgi.ini).
+#
+# [1] https://uwsgi-docs.readthedocs.io/en/latest/Emperor.html#tyrant-mode-secure-multi-user-hosting
+#
+uid = {{ searxng_user }}
+gid = {{ searxng_group }}
+
+# set (python) default encoding UTF-8
+env = LANG=C.UTF-8
+env = LANGUAGE=C.UTF-8
+env = LC_ALL=C.UTF-8
+
+# chdir to specified directory before apps loading
+chdir = {{ searxng_src_dir }}/searx
+
+# SearXNG configuration (settings.yml)
+env = SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
+
+# disable logging for privacy
+disable-logging = true
+
+# The right granted on the created socket
+chmod-socket = 666
+
+# Plugin to use and interpreter config
+single-interpreter = true
+
+# enable master process
+master = true
+
+# load apps in each worker instead of the master
+lazy-apps = true
+
+# load uWSGI plugins
+plugin = python3,http
+
+# By default the Python plugin does not initialize the GIL. This means your
+# app-generated threads will not run. If you need threads, remember to enable
+# them with enable-threads. Running uWSGI in multithreading mode (with the
+# threads options) will automatically enable threading support. This *strange*
+# default behaviour is for performance reasons.
+enable-threads = true
+
+# Number of workers (usually CPU count)
+workers = %k
+threads = 4
+
+# plugin: python
+# --------------
+#
+# https://uwsgi-docs.readthedocs.io/en/latest/Options.html#plugin-python
+
+# load a WSGI module
+module = searx.webapp
+
+# set PYTHONHOME/virtualenv
+virtualenv = {{ searxng_venv_dir }}
+
+# add directory (or glob) to pythonpath
+pythonpath = {{ searxng_src_dir }}
+
+
+# speak to upstream
+# -----------------
+
+socket = {{ searxng_socket_path }}
+buffer-size = 8192
+
+offload-threads = %k
diff --git a/roles/valkey/tasks/main.yml b/roles/valkey/tasks/main.yml
new file mode 100644
index 0000000..553869a
--- /dev/null
+++ b/roles/valkey/tasks/main.yml
@@ -0,0 +1,12 @@
+---
+- name: Ensure valkey-server is installed
+ ansible.builtin.apt:
+ name:
+ - valkey-server
+ state: present
+
+- name: Ensure valkey-server is enabled and running
+ ansible.builtin.systemd:
+ name: valkey-server.service
+ enabled: true
+ state: started
diff --git a/site.yml b/site.yml
new file mode 100644
index 0000000..97f42ed
--- /dev/null
+++ b/site.yml
@@ -0,0 +1,18 @@
+---
+- name: Configure and manage miguelpedro.xyz.br VPS
+ hosts: vps
+ become: true
+ roles:
+ - role: common
+ tags: [common]
+ - role: certbot
+ tags: [certbot, tls]
+ - role: valkey
+ tags: [valkey, searxng]
+ - role: searxng
+ tags: [searxng]
+ - role: nginx
+ tags: [nginx, web]
+ - role: cgit
+ tags: [cgit]
+ when: cgit_enabled | default(false) | bool