diff options
| author | Miguel Dalberto Pedro <miguel.pedro@precisioninno.com> | 2026-10-29 12:48:06 -0300 |
|---|---|---|
| committer | Miguel Dalberto Pedro <miguel.pedro@precisioninno.com> | 2026-10-29 12:48:06 -0300 |
| commit | 4b7379914a1865f07b2c54252234cc90158fc253 (patch) | |
| tree | a476c7e08bb994034d7d04b5b1cdfce216f49c43 | |
| download | my-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
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 |
