Initial commit - NUC server configuration and docs

- CLAUDE.md: Server instructions and service reference
- docs/: Persistent documentation (architecture, guides)
- .artifacts/: Session-generated notes
- playwriter-browser/: Remote browser container config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-01 20:49:20 +00:00
commit 390eda1595
25 changed files with 3664 additions and 0 deletions

279
docs/architecture.md Normal file
View File

@@ -0,0 +1,279 @@
# NUC Infrastructure Architecture
**Date:** 2026-02-01
**Context:** Secure self-hosted infrastructure with Tailscale Funnel for public access and Tailscale mesh for private admin access. Designed to bypass Spanish ISP blocks and handle dynamic IPs.
---
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ INTERNET │
└─────────────────────────────────────────────────────────────────────────────┘
│ │
│ Public Traffic │ Admin Traffic
▼ ▼
┌───────────────────────────────┐ ┌─────────────────────────────────────┐
│ whyrating.com │ │ TAILSCALE MESH │
│ │ │ │ (encrypted, private) │
│ ▼ │ │ │
│ ┌─────────────────────┐ │ │ ┌───────────┐ ┌───────────┐ │
│ │ Namecheap DNS │ │ │ │ Your Mac │◄──►│ NUC │ │
│ │ (301 redirect) │ │ │ │100.x.x.2 │ │100.x.x.1 │ │
│ └──────────┬──────────┘ │ │ └───────────┘ └───────────┘ │
│ │ │ │ ▲ ▲ │
│ ▼ │ │ │ Anywhere │ │
│ ┌─────────────────────┐ │ │ │ in world │ │
│ │ Tailscale Funnel │ │ └─────────┴────────────────┴─────────┘
│ │ nuc-tailscale.ts.net│◄─────┼────────────────────────┘
│ └──────────┬──────────┘ │
│ │ HTTPS/443 │
└─────────────┼─────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ NUC SERVER │
│ 192.168.1.3 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ SECURITY LAYER │ │
│ │ ┌─────────────┐ │ │
│ │ │ CrowdSec │ ← Blocks malicious IPs, DDoS protection │ │
│ │ │ :8083 │ │ │
│ │ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ COOLIFY (Docker Orchestrator) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ TRAEFIK (Reverse Proxy) │ │ │
│ │ │ Routes by domain │ │ │
│ │ └───────────┬─────────────────┬─────────────────┬─────────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ PUBLIC WEBSITES │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │Homepage │ │ App A │ │ App B │ │ App C │ │ │ │
│ │ │ │ :3000 │ │ :3001 │ │ :3002 │ │ :3003 │ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │
│ │ │ (internal ports only, not exposed) │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ PRIVATE SERVICES (Tailscale only) │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │Coolify │ │ Gitea │ │ MinIO │ │Postgres │ │ │ │
│ │ │ │ :8000 │ │ :3030 │ │ :9001 │ │ :5432 │ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │Authentik│ │ n8n │ │Vaultwrdn│ │ Outline │ │ │ │
│ │ │ │ :9090 │ │ :5678 │ │ :8222 │ │ :3080 │ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## Traffic Flows
### Public Website Access
```
User → whyrating.com → Namecheap 301 → nuc-tailscale.ts.net → Tailscale Funnel
→ CrowdSec → Traefik → Container (internal port)
```
### Admin Access (Remote)
```
Your Mac → Tailscale mesh (encrypted) → NUC Tailscale IP (100.x.x.x)
→ Direct access to any port (8000, 22, etc.)
```
### Admin Access (Home Network)
```
Your Mac → Local network → 192.168.1.3 → Any port
```
---
## Component Summary
| Component | Port | Access | Purpose |
|-----------|------|--------|---------|
| **Tailscale Funnel** | 443 | Public | Single internet entry point |
| **CrowdSec** | 8083 | Private | DDoS/attack protection |
| **Traefik** | 80/443 | Internal | Routes domains to containers |
| **Homepage** | 3000 | Via Funnel | Public dashboard |
| **Coolify** | 8000 | Tailscale only | Container management |
| **Databases** | various | Tailscale only | Data storage |
---
## Security Layers
```
┌─────────────────────────────────────────────────────────┐
│ Layer 1: TAILSCALE FUNNEL │
│ • Only entry point from internet │
│ • HTTPS termination │
│ • No open ports on router │
├─────────────────────────────────────────────────────────┤
│ Layer 2: CROWDSEC │
│ • Crowdsourced threat intelligence │
│ • Blocks known malicious IPs │
│ • DDoS mitigation │
├─────────────────────────────────────────────────────────┤
│ Layer 3: TRAEFIK │
│ • Domain-based routing │
│ • Only forwards to valid services │
│ • Rate limiting (configurable) │
├─────────────────────────────────────────────────────────┤
│ Layer 4: DOCKER NETWORK ISOLATION │
│ • Containers can't access each other unless configured │
│ • Databases on separate network from public apps │
├─────────────────────────────────────────────────────────┤
│ Layer 5: TAILSCALE MESH (Admin) │
│ • All admin traffic encrypted │
│ • No admin ports exposed to internet │
│ • Device authentication required │
└─────────────────────────────────────────────────────────┘
```
---
## Dynamic IP Handling
```
ISP Changes IP
┌─────────────┐ auto-update ┌──────────────────────┐
│ NUC detects │ ────────────────► │ Tailscale Coord │
│ new IP │ │ Server │
└─────────────┘ └──────────┬───────────┘
┌──────────────────────────────────────┘
│ broadcasts new location
┌─────────────────────────────────────────────────────────┐
│ UNCHANGED │
│ • Tailscale IP: 100.x.x.x (stable) │
│ • Funnel URL: nuc-tailscale.tail58f5ad.ts.net (stable)│
│ • whyrating.com redirect (stable) │
│ • All tunnels auto-reconnect (~10-30 sec) │
└─────────────────────────────────────────────────────────┘
```
**Key Point:** Your ISP can change your public IP anytime - Tailscale handles this automatically. No DDNS needed.
---
## Access Reference
| From | To | Method |
|------|----|--------|
| **Public users** | Websites | `whyrating.com` or `.ts.net` URL |
| **You (remote)** | NUC admin | `ssh nuc-tailscale` or `http://100.x.x.x:8000` |
| **You (home)** | NUC admin | `ssh nuc` or `http://192.168.1.3:8000` |
| **You (remote)** | Router | SSH jump: `ssh -J nuc-tailscale root@192.168.1.1` |
---
## URLs
| Service | Public URL | Private URL (Tailscale) |
|---------|------------|-------------------------|
| Main site | `whyrating.com` | - |
| Direct Funnel | `nuc-tailscale.tail58f5ad.ts.net` | - |
| Coolify | - | `http://nuc-tailscale:8000` |
| Homepage | Via Funnel :3000 | `http://nuc-tailscale:3000` |
| Gitea | - | `http://nuc-tailscale:3030` |
---
## What's NOT Exposed to Internet
- SSH (22)
- Coolify (8000)
- Databases (5432, 3306)
- MinIO (9000/9001)
- Authentik (9090)
- Router admin (192.168.1.1)
- Any direct ports on router
---
## Coolify Deployment Best Practices
### For Public Apps:
```yaml
services:
myapp:
image: myapp:latest
# NO ports: section - Traefik routes internally
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`myapp.whyrating.com`)"
networks:
- coolify
```
### For Private Apps:
```yaml
services:
mydb:
image: postgres:16
# No traefik labels
# No exposed ports
networks:
- internal # Separate from coolify network
```
---
## Quick Commands
```bash
# Check Tailscale status
tailscale status
# Check Funnel status
ssh nuc "tailscale funnel status"
# Access Coolify remotely
open http://nuc-tailscale:8000
# SSH to NUC from anywhere
ssh nuc-tailscale
# Check CrowdSec decisions
ssh nuc "docker exec crowdsec-* cscli decisions list"
```
---
## Related Documents
- `.artifacts/2026-02-01_19-11_domain-pre-purchase-check-guide.md` - Domain checking before purchase
- `.artifacts/domain-check.sh` - Script to check domains for blocks
- `CLAUDE.md` - Full NUC server documentation
---
## Why This Architecture?
1. **Spanish ISP Blocks** - Cloudflare shared IPs are blocked during LaLiga matches. Tailscale Funnel uses different infrastructure.
2. **Dynamic IP** - No need for DDNS or port forwarding. Tailscale handles IP changes automatically.
3. **Security** - Zero ports exposed on router. All admin via encrypted Tailscale mesh.
4. **Simplicity** - Single entry point (Funnel), single orchestrator (Coolify), single security layer (CrowdSec).

162
docs/backup-strategy.md Normal file
View File

@@ -0,0 +1,162 @@
# NUC Backup Strategy
**Date:** 2026-02-01 17:30
**Context:** 3-layer backup strategy for NUC disaster recovery
---
## Layer 1: Daily Automated Backup (Kopia → MinIO)
**What:** All Docker volumes + Coolify configs
**Where:** NUC MinIO (local) + optionally offsite
**Recovery time:** ~15 min
### Configure Kopia
```bash
# Connect to Kopia container
ssh nuc "docker exec -it kopia /bin/sh"
# Add Docker volumes path
kopia policy set /var/lib/docker/volumes --add-include "*.sql" --add-include "*.db"
kopia snapshot create /var/lib/docker/volumes
# Add Coolify data
kopia snapshot create /data/coolify
# Set daily schedule
kopia policy set --global --snapshot-interval 24h
```
### Critical paths to backup
| Path | Contents |
|------|----------|
| `/data/coolify/` | Coolify configs, SSH keys, DB |
| `/var/lib/docker/volumes/` | All service data |
| `/opt/homepage/config/` | Homepage dashboard config |
### Manual backup command
```bash
ssh nuc "docker exec kopia kopia snapshot create /var/lib/docker/volumes /data/coolify"
```
### Verify backups
```bash
ssh nuc "docker exec kopia kopia snapshot list --all"
```
---
## Layer 2: Clonezilla Full Disk Image
**What:** Complete disk clone (OS + everything)
**Where:** External USB drive
**Recovery time:** ~30 min
**When:** Monthly or before hardware changes
### Create image
1. Boot NUC from Clonezilla USB
2. Select: `device-image``savedisk`
3. Choose external USB as destination
4. Select NUC internal disk as source
5. Use options: `-z1p` (parallel compression), `-fsck-src-part` (check filesystem)
### Recommended naming
```
nuc-full-YYYY-MM-DD.img
```
### Storage requirements
| NUC disk used | Compressed image |
|---------------|------------------|
| 50GB | ~20GB |
| 100GB | ~40GB |
| 250GB | ~80GB |
---
## Layer 3: Coolify Services Export
**What:** List of all services + configs for manual rebuild
**Where:** This repo + offsite
**Recovery time:** 1-2 hours (fresh install)
### Export current services
```bash
# Get all services with their configs
ssh nuc "docker exec coolify php artisan tinker --execute=\"
use App\Models\Service;
Service::all()->map(fn(\\\$s) => [
'name' => \\\$s->name,
'uuid' => \\\$s->uuid,
'type' => \\\$s->service_type,
'compose' => \\\$s->docker_compose_raw
])->toJson(JSON_PRETTY_PRINT);
\""
```
### Current NUC Services (as of 2026-02-01)
| Service | Port | Coolify UUID |
|---------|------|--------------|
| Homepage | 3000 | eo0g84scsss4osk0skk040ck |
| Coolify | 8000 | (system) |
| Gitea | 3030 | ho0cwgcwos88cwc48g84c0g8 |
| Outline | 3080 | pccg80wks4c084008owokkkg |
| n8n | 5678 | uk0o04o0g84s4sc80kkoooc0 |
| Vaultwarden | 8222 | h40w0ss4kgs0c8cgc0sc8k48 |
| Ntfy | 8333 | xgkkg8gkgg048g8gkc8ck4os |
| MinIO | 9000/9001 | dg4wkgg8skcssww0040sgk80 |
| Authentik | 9090 | e8owcw0s4wcswc4w4css0sws |
| FileBrowser | 8085 | o4swwwsowwg88coo0ws4cg48 |
| CloudBeaver | 8087 | joo4g4k0w08k8kcosgsgswc0 |
| Uptime Kuma | 3001 | s4ko04w88k048sw8o4swsoww |
| Dozzle | 9999 | vgko8w4kkkc8k0g4sggs4ks8 |
| Tailscale | - | posgwooww0s0c0okssooc4gw |
### Databases
| Database | Port | UUID |
|----------|------|------|
| LiquidGym MySQL | 3306 | hgwcgs4oswwc8scg080scoo4 |
| LiquidGym Postgres | 5433 | x4kk8g4k8w4g0cw480w84g4g |
| Knosia Postgres | 5442 | ik80skko0008w4000c4w40os |
---
## Recovery Priority
If total failure, restore in this order:
1. **Coolify** (manages everything else)
2. **Databases** (apps depend on them)
3. **Authentication** (Authentik, Vaultwarden)
4. **Core services** (Gitea, n8n, Outline)
5. **Monitoring** (Uptime Kuma, Dozzle)
---
## Offsite Backup (Optional)
For offsite, sync Kopia repository to cloud:
```bash
# Sync to Backblaze B2 (example)
kopia repository sync-to b2 --bucket=nuc-backup --key-id=XXX --key=YYY
```
Or rsync the Clonezilla image to another location.
---
## Related
- Hard Disk Change Guide: `2026-02-01_17-30_nuc-hard-disk-change.md`
- LiquidGym Migration: `2026-02-01_16-45_liquidgym-mysql-migration.md`

301
docs/domain-check-guide.md Normal file
View File

@@ -0,0 +1,301 @@
# Domain Pre-Purchase Check Guide
**Date:** 2026-02-01
**Context:** After whymyrating.com was blocked by Spanish ISPs due to LaLiga's Cloudflare IP blocking, this guide documents how to check a domain before purchase to avoid similar issues.
---
## Quick Checklist
- [ ] Domain not previously used for spam/malware
- [ ] Not on any security blocklists
- [ ] Hosting provider IPs not blocked in target countries
- [ ] No trademark conflicts
- [ ] Clean WHOIS history
---
## Step 1: Check Domain Availability & History
### 1.1 Basic Availability
```bash
# WHOIS check
whois <domain> | grep -iE "registrar|creation|status"
# If "No match" = available and clean
# If registered = check who owns it
```
### 1.2 Historical Usage (Archive.org)
Check if domain was previously used (and for what):
- **URL:** `https://web.archive.org/web/*/https://<domain>`
- Look for: spam, adult content, piracy, gambling
### 1.3 Expired Domain History
- **ExpiredDomains.net:** `https://www.expireddomains.net/domain-name-search/?q=<domain>`
- Check: Previous backlinks, spam score, previous usage
---
## Step 2: Security & Reputation Checks
### 2.1 VirusTotal (Multi-vendor scan)
```
URL: https://www.virustotal.com/gui/domain/<domain>
```
- Check: Detection ratio (should be 0/90+)
- Look for: Any vendor flagging as malicious
### 2.2 Google Safe Browsing
```
URL: https://transparencyreport.google.com/safe-browsing/search?url=<domain>
```
- Status should be: "No unsafe content found"
### 2.3 Sucuri SiteCheck
```
URL: https://sitecheck.sucuri.net/?scan=<domain>
```
- Check: Blacklist status, malware, spam flags
- Should show: Clean across all vendors
### 2.4 Other Reputation Services
| Service | URL |
|---------|-----|
| MXToolbox | `https://mxtoolbox.com/blacklists.aspx` |
| Spamhaus | `https://check.spamhaus.org/` |
| SURBL | `http://www.surbl.org/surbl-analysis` |
| PhishTank | `https://phishtank.org/` |
| URLhaus | `https://urlhaus.abuse.ch/browse/` |
---
## Step 3: Regional Blocking Check (Critical for Spain)
### 3.1 Spanish ISP LaLiga Blocks
If targeting Spanish users, check if the hosting provider's IPs are blocked:
**Live Block Monitor:**
```
URL: https://hayahora.futbol
API: https://hayahora.futbol/estado/data.json
```
**Check specific IP:**
```bash
# Get your hosting provider's IPs
dig +short <domain> A
# Check if those IPs are in the block list
curl -s "https://hayahora.futbol/estado/data.json" | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
ip = '<YOUR_IP>' # Replace with actual IP
for entry in data.get('data', []):
if entry.get('ip') == ip:
print(f\"ISP: {entry['isp']} | Blocked: {entry['stateChanges'][-1]['state']}\")
"
```
### 3.2 Spanish ISPs That Block
| ISP | Brands | Block Type |
|-----|--------|------------|
| **MásOrange** | Orange, Yoigo, Jazztel, Masmovil, Simyo, Pepephone, Lebara, Lyca, Llamaya, Euskaltel | IP-based |
| **Movistar** | Movistar, O2 | IP-based |
| **Vodafone** | Vodafone, Lowi | IP-based |
| **DIGI** | DIGI | IP-based |
**Blocking occurs:** During LaLiga matches (weekends, some weekdays)
**Season:** August - May each year
### 3.3 Global Accessibility Test
```bash
# Multi-location HTTP check
curl -s "https://check-host.net/check-http?host=<domain>&max_nodes=10" \
-H "Accept: application/json"
# Wait 5 seconds, then get results
curl -s "https://check-host.net/check-result/<request_id>" \
-H "Accept: application/json"
```
### 3.4 Internet Censorship Check (China, Russia, Turkey)
```
URL: https://www.experte.com/internet-censorship
```
---
## Step 4: Hosting Provider Risk Assessment
### 4.1 High-Risk Providers for Spain
These providers use shared IPs that get blocked by LaLiga:
| Provider | Risk Level | Reason |
|----------|------------|--------|
| **Cloudflare (free)** | 🔴 HIGH | Shared IPs frequently blocked |
| **Vercel** | 🔴 HIGH | Affected by same blocks |
| **Netlify** | 🟡 MEDIUM | Some IP ranges blocked |
| **GitHub Pages** | 🟡 MEDIUM | Occasionally affected |
| **BunnyCDN** | 🟡 MEDIUM | Some blocks reported |
### 4.2 Lower-Risk Options for Spain
| Solution | Risk Level | Notes |
|----------|------------|-------|
| **Cloudflare Pro/Business** | 🟢 LOW | Dedicated IPs available |
| **Dedicated VPS** | 🟢 LOW | Own IP, not shared |
| **AWS CloudFront** | 🟢 LOW | Different IP ranges |
| **Non-CDN hosting** | 🟢 LOW | Direct IP, no sharing |
### 4.3 Check If Hosting IPs Are Clean
```bash
# Get hosting provider's IP range
dig +short <cdn-domain> A
# Check against Spanish block list
curl -s "https://hayahora.futbol/estado/data.json" | \
grep -c "<IP_PREFIX>"
# If count > 0, that IP range has been blocked before
```
---
## Step 5: DNS & Email Reputation
### 5.1 Check DNS Blacklists
```bash
# Using MXToolbox
curl -s "https://mxtoolbox.com/api/v1/lookup/blacklist/<domain>"
```
### 5.2 Email Deliverability
If you'll send emails from this domain:
- Check if IP range is on Spamhaus
- Verify no previous spam history
- Set up SPF, DKIM, DMARC from day 1
---
## Step 6: Trademark & Legal Check
### 6.1 Trademark Search
- **USPTO:** `https://tmsearch.uspto.gov`
- **EUIPO:** `https://euipo.europa.eu/eSearch/`
- **WIPO Global:** `https://branddb.wipo.int`
### 6.2 Domain Disputes History
- Check UDRP decisions: `https://www.wipo.int/amc/en/domains/search/`
---
## Complete Pre-Purchase Script
```bash
#!/bin/bash
DOMAIN="$1"
echo "=== Domain Pre-Purchase Check: $DOMAIN ==="
echo -e "\n[1/6] WHOIS Check"
whois "$DOMAIN" 2>/dev/null | grep -iE "registrar|creation|status|No match" | head -5
echo -e "\n[2/6] DNS Resolution"
dig +short "$DOMAIN" A
echo -e "\n[3/6] Security Check (Sucuri)"
echo "Visit: https://sitecheck.sucuri.net/?scan=$DOMAIN"
echo -e "\n[4/6] VirusTotal"
echo "Visit: https://www.virustotal.com/gui/domain/$DOMAIN"
echo -e "\n[5/6] Archive.org History"
echo "Visit: https://web.archive.org/web/*/$DOMAIN"
echo -e "\n[6/6] Spanish ISP Block Check"
IP=$(dig +short "$DOMAIN" A | head -1)
if [ -n "$IP" ]; then
echo "IP: $IP"
BLOCKED=$(curl -s "https://hayahora.futbol/estado/data.json" 2>/dev/null | grep -c "\"$IP\"")
if [ "$BLOCKED" -gt 0 ]; then
echo "⚠️ WARNING: This IP has been blocked by Spanish ISPs"
else
echo "✅ IP not in Spanish block list"
fi
else
echo "No IP yet (domain not configured)"
fi
echo -e "\n=== Check Complete ==="
```
---
## Red Flags to Avoid
| Red Flag | Why It's Bad |
|----------|--------------|
| Previously used for spam | Email deliverability issues |
| On any security blocklist | SEO penalties, browser warnings |
| Hosting on shared Cloudflare IPs | Spanish ISP blocks |
| Similar to trademarked name | Legal disputes |
| Expired domain with backlinks from spam sites | Google penalties |
| Previously used for piracy/gambling | Regulatory blocks |
---
## Recommended Workflow
```
1. Check availability (WHOIS)
2. Check history (Archive.org)
3. Security scan (VirusTotal, Sucuri)
4. Check hosting provider's IPs against block lists
5. If targeting Spain: Verify IPs not in LaLiga blocks
6. Trademark search
7. Purchase domain
8. Set up on dedicated IP or low-risk CDN
```
---
## Resources
| Resource | URL | Purpose |
|----------|-----|---------|
| hayahora.futbol | https://hayahora.futbol | Spanish ISP block monitor |
| VirusTotal | https://virustotal.com | Multi-vendor security scan |
| Sucuri SiteCheck | https://sitecheck.sucuri.net | Website security check |
| Archive.org | https://web.archive.org | Historical usage |
| Check-Host | https://check-host.net | Multi-location accessibility |
| EXPERTE | https://experte.com/internet-censorship | Censorship check |
| MXToolbox | https://mxtoolbox.com | Email/DNS blacklists |
---
## Lessons from whymyrating.com
**What happened:**
- Domain registered Jan 30, 2026
- Hosted on Cloudflare (free tier, shared IPs)
- IPs 188.114.97.5 and 188.114.96.5 are in LaLiga block rotation
- Blocked by Orange and Masmovil in Spain during matches
**How to avoid:**
1. Use dedicated IPs or non-Cloudflare CDN for Spanish audience
2. Check hayahora.futbol before choosing hosting
3. Consider Cloudflare Pro for dedicated IPs
4. Have VPN/alternative access ready for Spanish users
---
**Related:** See CLAUDE.md > Domain Configuration Workflow for setup after purchase.

259
docs/hard-disk-change.md Normal file
View File

@@ -0,0 +1,259 @@
# NUC Hard Disk Change Guide
**Date:** 2026-02-01 17:30
**Context:** Step-by-step guide to replace NUC hard disk with larger one
---
## Prerequisites
- [ ] Clonezilla USB boot drive ([download](https://clonezilla.org/downloads.php))
- [ ] External USB drive (larger than current disk usage)
- [ ] New internal disk (must be >= current used space)
- [ ] 1-2 hours of downtime
---
## Before You Start
### Check current disk usage
```bash
ssh nuc "df -h / && lsblk"
```
### Verify services are backed up
```bash
ssh nuc "docker exec kopia kopia snapshot list --all | tail -10"
```
### Export critical configs (safety net)
```bash
ssh nuc "tar czf /tmp/nuc-configs-backup.tar.gz /data/coolify /opt/homepage/config 2>/dev/null"
scp nuc:/tmp/nuc-configs-backup.tar.gz ~/Desktop/
```
---
## Phase 1: Create Clonezilla Image
### 1.1 Boot from Clonezilla USB
1. Insert Clonezilla USB into NUC
2. Power on, press **F10** for boot menu
3. Select USB drive
4. Choose: `Clonezilla live (Default)`
### 1.2 Create disk image
```
Select: device-image
Select: local_dev (save to external USB)
Choose: savedisk
Image name: nuc-full-2026-02-01
Source disk: /dev/sda (or /dev/nvme0n1)
Compression: -z1p (parallel gzip, fastest)
Options: -fsck-src-part -c (check & checksum)
```
### 1.3 Wait for completion
- 256GB disk → ~20-40 min
- 512GB disk → ~40-60 min
### 1.4 Verify image
Clonezilla shows checksums. Note them down:
```
Image checksum: ________________________________
```
---
## Phase 2: Swap Physical Disk
### 2.1 Power off NUC
```bash
ssh nuc "sudo shutdown now"
```
### 2.2 Open NUC and replace disk
1. Disconnect power
2. Remove bottom cover (4 screws)
3. Remove old disk (M.2 or 2.5" SATA)
4. Insert new disk
5. Replace cover
---
## Phase 3: Restore to New Disk
### 3.1 Boot Clonezilla again
1. Insert Clonezilla USB + external drive with image
2. Boot from USB
### 3.2 Restore image
```
Select: device-image
Select: local_dev
Choose: restoredisk
Image: nuc-full-2026-02-01
Target: /dev/sda (new disk)
```
### 3.3 Confirm and wait
Same time as backup (~20-60 min)
---
## Phase 4: Expand Partition
After restore, the new disk has the old partition sizes. Expand to use all space:
### 4.1 Boot into Ubuntu
Remove USB drives, boot normally.
### 4.2 Expand partition
```bash
# Check current layout
lsblk
# Expand partition (usually partition 2 or 3)
sudo growpart /dev/sda 2
# Expand filesystem
sudo resize2fs /dev/sda2
# Verify
df -h /
```
### For NVMe drives:
```bash
sudo growpart /dev/nvme0n1 2
sudo resize2fs /dev/nvme0n1p2
```
### For LVM:
```bash
sudo pvresize /dev/sda2
sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
```
---
## Phase 5: Verify Everything Works
### 5.1 Check services
```bash
# All containers running?
docker ps
# Coolify healthy?
curl -s http://localhost:8000/api/health
# Check each service
docker ps --format 'table {{.Names}}\t{{.Status}}' | head -20
```
### 5.2 Test critical services
| Service | Test |
|---------|------|
| Coolify | http://192.168.1.3:8000 |
| Homepage | http://192.168.1.3:3000 |
| Gitea | http://192.168.1.3:3030 |
| n8n | http://192.168.1.3:5678 |
### 5.3 Verify databases
```bash
# LiquidGym Postgres
docker exec postgres-x4kk8g4k8w4g0cw480w84g4g psql -U postgres -c "\\l"
# LiquidGym MySQL
docker exec hgwcgs4oswwc8scg080scoo4 mysql -u root -pliquidgym_root_nuc_2026 -e "SHOW DATABASES;"
```
---
## Troubleshooting
### GRUB bootloader not found
```bash
# Boot from Ubuntu Live USB
sudo mount /dev/sda2 /mnt
sudo mount /dev/sda1 /mnt/boot/efi
sudo chroot /mnt
grub-install /dev/sda
update-grub
exit
reboot
```
### NVMe device name changed
Edit `/etc/fstab` to use UUID instead of device names:
```bash
# Find UUIDs
blkid
# Edit fstab
sudo nano /etc/fstab
# Change /dev/sda2 to UUID=xxxxx
```
### Docker services not starting
```bash
sudo systemctl restart docker
docker ps -a # Check for exited containers
docker start $(docker ps -aq) # Start all
```
---
## Rollback Plan
If new disk fails, you still have:
1. **Original disk** (keep it safe for 1 week)
2. **Clonezilla image** on external drive
3. **Config backup** on your Mac (`~/Desktop/nuc-configs-backup.tar.gz`)
Worst case: put old disk back in, boot, everything works.
---
## Quick Reference
| Step | Time | Risk |
|------|------|------|
| Create image | 30-60 min | Low |
| Swap disk | 10 min | Low |
| Restore image | 30-60 min | Low |
| Expand partition | 5 min | Low |
| Verify services | 10 min | None |
| **Total** | ~2 hours | Low |
---
## Related
- Backup Strategy: `2026-02-01_17-30_nuc-backup-strategy.md`
- Coolify Dashboard: http://192.168.1.3:8000

114
docs/lan-dns-setup.md Normal file
View File

@@ -0,0 +1,114 @@
# NUC.lan DNS Configuration
**Date:** 2026-02-01 19:15
**Context:** Setting up friendly hostname for local NUC access via Tailscale
## Summary
Configured `nuc.lan` as a friendly hostname for accessing NUC services on the local network, working around macOS `.local` mDNS handling.
## Why .lan instead of .local?
macOS reserves the `.local` TLD for multicast DNS (Bonjour/mDNS). This means:
- `.local` domains bypass regular DNS and go to mDNS
- Tailscale split DNS cannot override this behavior
- `.lan` works correctly with standard DNS resolution
## Configuration
### 1. OpenWrt Router DNS Entry
```bash
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "
uci add dhcp domain
uci set dhcp.@domain[-1].name='nuc.lan'
uci set dhcp.@domain[-1].ip='192.168.1.3'
uci commit dhcp
/etc/init.d/dnsmasq restart
"
```
### 2. Tailscale Split DNS
| Setting | Value |
|---------|-------|
| **Nameserver** | 192.168.1.1 (router) |
| **Domain** | lan |
| **Type** | Split DNS |
**Dashboard:** https://login.tailscale.com/admin/dns
This tells Tailscale to forward all `.lan` domain queries to the router (192.168.1.1), which resolves `nuc.lan` to `192.168.1.3`.
## Verification
```bash
# DNS resolution
dig nuc.lan +short
# Returns: 192.168.1.3
# HTTP access
curl -s http://nuc.lan:8086
# Returns: NUC Portal (Homer dashboard)
```
## NUC Portal
| Property | Value |
|----------|-------|
| **URL** | **http://nuc.lan** (port 80) |
| **Alt URL** | http://nuc.lan:8086 (direct) |
| **Container** | portal-l44gcskok8c8wcocwswg08w8 |
| **Image** | b4bz/homer:latest |
| **Config** | /www/assets/config.yml |
The portal is routed through Traefik on port 80, making it accessible at the clean URL `http://nuc.lan`.
### Traefik Labels
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.nuc-portal.rule=Host(`nuc.lan`)"
- "traefik.http.routers.nuc-portal.entrypoints=http"
- "traefik.http.services.nuc-portal.loadbalancer.server.port=8080"
```
### Security: Local Only
This portal is **NOT accessible from the internet** because:
1. `nuc.lan` DNS only exists in local router + Tailscale split DNS
2. No Cloudflare Tunnel route exists for `nuc.lan`
3. Traefik only routes requests with `Host: nuc.lan` header
The portal provides links to all NUC services using `nuc.lan` URLs.
## Service URLs
| Service | URL |
|---------|-----|
| NUC Portal | http://nuc.lan:8086 |
| Coolify | http://nuc.lan:8000 |
| Homepage | http://nuc.lan:3000 |
| Snappymail | http://nuc.lan:8082 |
| Stalwart Admin | http://nuc.lan:8081 |
| Outline | http://nuc.lan:3080 |
| n8n | http://nuc.lan:5678 |
| NocoDB | http://nuc.lan:8084 |
| Gitea | http://nuc.lan:3030 |
| Uptime Kuma | http://nuc.lan:3001 |
| MinIO | http://nuc.lan:9001 |
| Vaultwarden | http://nuc.lan:8222 |
| Dozzle | http://nuc.lan:9999 |
## Requirements
- Must be connected to Tailscale network
- Works from any device on the Tailnet (Mac, iPhone, etc.)
- Router must be reachable from Tailscale devices
## Related
- NUC Portal artifact: Previous session
- Tailscale DNS: https://login.tailscale.com/admin/dns
- OpenWrt Router: 192.168.1.1

95
docs/mcp-browser-setup.md Normal file
View File

@@ -0,0 +1,95 @@
# MCP Browser Configuration
**Date:** 2026-02-01 15:45
**Context:** Configured Playwriter and Chrome DevTools MCP servers with naming convention for local vs remote browsers
## Naming Convention
```
playwriter-<location>-<id>
chrome-devtools-<location>-<id>
```
| Pattern | Example | Description |
|---------|---------|-------------|
| `<location>` | `local`, `nuc`, `cloud` | Where browser runs |
| `<id>` | `01`, `02`, etc. | Unique instance number |
## Current Configuration
### Local Browser
| Field | Value |
|-------|-------|
| **MCP Name** | `playwriter-local` |
| **Type** | Local |
| **Description** | Uses local machine resources |
| **Command** | `npx playwriter` |
### Remote NUC Browser
| Field | Value |
|-------|-------|
| **MCP Name** | `playwriter-nuc-01` |
| **Type** | Remote |
| **ID** | `nuc-01` |
| **Host** | `192.168.1.3` |
| **Port** | `19988` |
| **WebSocket** | `ws://192.168.1.3:19988` |
| **Token** | `nuc-browser-token` |
| **noVNC** | `http://192.168.1.3:6081/vnc.html` |
| **CDP** | `http://192.168.1.3:9222` |
| **Command** | `npx playwriter --host ws://192.168.1.3:19988 --token nuc-browser-token` |
### Chrome DevTools for NUC
| Field | Value |
|-------|-------|
| **MCP Name** | `chrome-devtools-nuc-01` |
| **Type** | Remote |
| **ID** | `nuc-01` |
| **Browser URL** | `http://192.168.1.3:19988` |
| **Command** | `npx chrome-devtools-mcp@latest --browserUrl http://192.168.1.3:19988` |
## Metadata Fields
Config files use underscore-prefixed fields for metadata (ignored by MCP):
```json
{
"playwriter-nuc-01": {
"_description": "Remote browser on NUC server - no local resources used",
"_id": "nuc-01",
"_host": "192.168.1.3",
"_port": 19988,
"command": "npx",
"args": ["playwriter", "--host", "ws://192.168.1.3:19988", "--token", "nuc-browser-token"]
}
}
```
## Priority Order
1. `mcp__playwriter-nuc-01__*` - Remote NUC browser (preferred, no local resources)
2. `mcp__chrome-devtools-nuc-01__*` - Chrome DevTools for NUC browser
3. `mcp__playwriter-local__*` - Local browser (fallback, uses local resources)
## Future Expansion Examples
| MCP Name | Location | Use Case |
|----------|----------|----------|
| `playwriter-nuc-02` | NUC | Second browser for parallel automation |
| `playwriter-cloud-01` | Cloud | Cloud-hosted browser instance |
| `playwriter-mac-mini-01` | Mac Mini | Dedicated Mac browser |
## NUC Browser Container
**Location:** `~/playwriter-browser/` on NUC (deployed via docker compose)
**First-time setup:**
1. Access noVNC at `http://192.168.1.3:6081/vnc.html`
2. Install Playwriter extension
3. Click extension icon to activate (turns green)
## Related
- Config file: `/Users/agutierrez/Desktop/nuc/.mcp.json`
- CLAUDE.md: Browser MCP documentation section
- NUC container: `playwriter-browser` service in Coolify

135
docs/mcp-research-guide.md Normal file
View File

@@ -0,0 +1,135 @@
# MCP Research Guide
**Date:** 2026-02-01
**Purpose:** Comprehensive guide for finding and evaluating MCP servers
---
## Primary MCP Directories (Check First)
| Resource | URL | MCPs | Best For |
|----------|-----|------|----------|
| **MCP Server Finder** | https://mcpserverfinder.com | 1,934+ | Largest directory, searchable |
| **MCPServers.org** | https://mcpservers.org | 100+ | Curated, categories, official/featured tabs |
| **MCP.so** | https://mcp.so | - | Official registry, curated list |
| **Smithery** | https://smithery.ai | - | Searchable MCP marketplace |
| **Glama MCP Directory** | https://glama.ai/mcp/servers | - | Curated collection with categories |
| **PulseMCP** | https://pulsemcp.com | - | Community-driven directory |
| **MCPHub** | https://mcphub.io | - | Hub for discovering MCPs |
| **Cursor MCP Directory** | https://cursor.directory/mcp | - | Cursor-focused MCP collection |
---
## GitHub Resources (For Verification)
| Resource | URL | Stars | Best For |
|----------|-----|-------|----------|
| **Awesome MCP Servers** | https://github.com/punkpeye/awesome-mcp-servers | 80k+ | Most popular curated list |
| **Awesome MCP** | https://github.com/wong2/awesome-mcp | - | Alternative curated list |
| **TensorBlock Awesome** | https://github.com/TensorBlock/awesome-mcp-servers | - | Covers 7,260+ MCPs |
| **MCP Servers Org** | https://github.com/modelcontextprotocol/servers | - | Official Anthropic examples |
| **GitHub Search** | `topic:mcp-server` or `"modelcontextprotocol"` | - | Find new/unlisted MCPs |
---
## MCP Discovery Tools (MCP-of-MCPs)
**Install these MCPs to discover/manage other MCPs from within Claude:**
| MCP | Repo/URL | Description |
|-----|----------|-------------|
| **Magg** | `sitbon/magg` | Meta-MCP hub - LLMs can autonomously discover, install, orchestrate MCPs |
| **MCPDiscovery** | `particlefuture/MCPDiscovery` | Auto-discovery and config of local MCP servers |
| **NCP** | `portel-dev/ncp` | Orchestrates MCP ecosystem, reduces token overhead |
| **AllInOneMCP** | https://mcp.pfvc.io/mcp/ | Remote MCP-of-MCPs, discover and learn MCPs |
| **1mcpserver** | https://mcp.1mcpserver.com/mcp/ | Remote auto-discovery and configuration |
**Official MCP Registry API:** https://registry.modelcontextprotocol.io (programmatic access)
---
## Vendor-Specific MCP Collections
| Vendor | URL | MCPs Included |
|--------|-----|---------------|
| **Google** | https://mcpservers.org/servers/google/mcp | Maps, BigQuery, GKE, Compute Engine, Workspace, Firebase, Cloud Run, Analytics, Cloud Storage, Security, gcloud CLI, Flutter/Dart |
---
## MCP Evaluation Criteria
**MUST evaluate every MCP candidate using this checklist:**
| Criteria | Minimum | Preferred | How to Check |
|----------|---------|-----------|--------------|
| **GitHub Stars** | 50+ | 500+ | Repo main page |
| **Last Commit** | < 6 months | < 1 month | Repo commits tab |
| **Open Issues** | < 50 unresolved | < 20 | Issues tab |
| **Documentation** | README exists | Full docs site | Repo README |
| **License** | Any OSS | MIT/Apache | LICENSE file |
| **TypeScript/Python** | Either | TypeScript | package.json or setup.py |
| **Tool Count** | 3+ tools | 10+ tools | README or source |
| **Active Maintainer** | 1+ | 2+ | Contributors tab |
**Red Flags (AVOID):**
- No commits in 12+ months
- Only 1-2 tools with narrow scope
- No error handling documented
- Requires API keys with no free tier
- Binary-only distribution (no source)
- Excessive permissions requested
---
## Research Workflow
```python
# 1. Search directories first
WebFetch(url="https://mcp.so/search?q=<category>", prompt="Find MCPs for <purpose>")
WebFetch(url="https://smithery.ai/search?q=<keyword>", prompt="List relevant MCPs")
# 2. Verify on GitHub
WebFetch(url="https://github.com/<org>/<repo>", prompt="Check stars, last commit, issues")
# 3. Check awesome lists for mentions
WebFetch(url="https://github.com/punkpeye/awesome-mcp-servers", prompt="Is <mcp> listed?")
# 4. Review actual tools provided
WebFetch(url="<repo>/blob/main/src/index.ts", prompt="List all available tools")
```
---
## Quick Search Commands
```bash
# Search GitHub for MCP servers by topic
# Use WebSearch with these queries:
"site:github.com mcp-server <category> stars:>100"
"site:smithery.ai <category> mcp"
"site:mcp.so <category>"
```
---
## MCP Categories Quick Reference
| Need | Search Terms | Top Options |
|------|--------------|-------------|
| **Mac/Desktop** | macos, desktop, system | `mcp-server-commands`, `desktop-mcp` |
| **File System** | filesystem, files, directory | `@anthropic/filesystem` |
| **Browser** | browser, playwright, puppeteer | `playwright-mcp`, `browsermcp` |
| **Database** | postgres, mysql, sqlite | `mcp-server-postgres`, `sqlite-mcp` |
| **Git/GitHub** | git, github, gitlab | `github-mcp`, `@anthropic/github` |
| **Docker** | docker, containers | `docker-mcp` |
| **Cloud** | aws, gcp, azure | `aws-mcp`, `cloudflare-mcp` |
| **API** | rest, graphql, http | `fetch-mcp`, `openapi-mcp` |
| **Search** | search, web, brave | `brave-search-mcp`, `exa-mcp` |
| **Notes/Docs** | notion, obsidian, markdown | `notion-mcp`, `obsidian-mcp` |
---
## Related
- CLAUDE.md - Main NUC server documentation
- See "Available MCPs for NUC Management" section in CLAUDE.md for currently installed MCPs

50
docs/n8n-mcp-setup.md Normal file
View File

@@ -0,0 +1,50 @@
# n8n MCP Setup
**Date:** 2026-02-01 13:50
**Context:** Set up n8n MCP server with API key for workflow automation
## API Key Details
- **API Key:** `n8n_api_d8bae3df1be532498e38a7bf3870a4e62713170f`
- **Label:** Claude-MCP
- **User ID:** ddac5ff1-e6fd-48bc-9a4a-970b6d73bafc (alezmad@gmail.com)
- **Scopes:** `["workflow:read","workflow:execute","user:read"]`
## MCP Configuration
Configured via MCP Docker gateway:
```bash
docker mcp secret set n8n.api_key=n8n_api_d8bae3df1be532498e38a7bf3870a4e62713170f
```
Config set:
```json
{
"api_url": "http://192.168.1.3:5678"
}
```
## Available Tools (42 total)
Key tools:
- `n8n_list_workflows` - List all workflows
- `n8n_create_workflow` - Create new workflow
- `n8n_get_workflow` - Get workflow by ID
- `search_nodes` - Search 543 n8n nodes
- `search_templates` - Browse workflow templates
- `n8n_trigger_webhook_workflow` - Execute via webhook
- `validate_workflow` - Validate workflow config
## Creation Method
API key created via direct SQLite insert (n8n was stopped briefly):
```bash
docker run --rm --user 1000:1000 -v uk0o04o0g84s4sc80kkoooc0_n8n-data:/data \
keinos/sqlite3 sqlite3 /data/database.sqlite \
"INSERT INTO user_api_keys (...) VALUES (...)"
```
## Related
- n8n UI: http://192.168.1.3:5678
- n8n container: n8n-uk0o04o0g84s4sc80kkoooc0
- MCP version: 2.22.17 (update available: 2.33.5)

117
docs/scripts/domain-check.sh Executable file
View File

@@ -0,0 +1,117 @@
#!/bin/bash
# Domain Pre-Purchase Check Script
# Usage: ./domain-check.sh example.com
DOMAIN="$1"
if [ -z "$DOMAIN" ]; then
echo "Usage: $0 <domain>"
echo "Example: $0 whyrating.com"
exit 1
fi
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Domain Pre-Purchase Check: $DOMAIN"
echo "╚════════════════════════════════════════════════════════════╝"
# 1. WHOIS Check
echo -e "\n━━━ [1/7] WHOIS Registration ━━━"
WHOIS_RESULT=$(whois "$DOMAIN" 2>/dev/null)
if echo "$WHOIS_RESULT" | grep -qi "No match"; then
echo "✅ Domain is AVAILABLE (not registered)"
else
echo "⚠️ Domain is REGISTERED"
echo "$WHOIS_RESULT" | grep -iE "registrar|creation|updated|status" | head -5
fi
# 2. DNS Resolution
echo -e "\n━━━ [2/7] DNS Resolution ━━━"
IPS=$(dig +short "$DOMAIN" A 2>/dev/null)
if [ -n "$IPS" ]; then
echo "IPs: $IPS"
else
echo "No A records (domain not configured or available)"
fi
# 3. HTTP Check
echo -e "\n━━━ [3/7] HTTP Accessibility ━━━"
HTTP_STATUS=$(curl -sI --max-time 5 "https://$DOMAIN" 2>/dev/null | head -1)
if [ -n "$HTTP_STATUS" ]; then
echo "$HTTP_STATUS"
else
echo "No HTTP response (site not live)"
fi
# 4. Spanish ISP Block Check
echo -e "\n━━━ [4/7] Spanish ISP Block Check (LaLiga) ━━━"
if [ -n "$IPS" ]; then
for IP in $IPS; do
echo "Checking IP: $IP"
BLOCK_DATA=$(curl -s "https://hayahora.futbol/estado/data.json" 2>/dev/null)
if [ -n "$BLOCK_DATA" ]; then
BLOCKED=$(echo "$BLOCK_DATA" | grep -c "\"$IP\"")
if [ "$BLOCKED" -gt 0 ]; then
echo " 🚫 WARNING: IP found in Spanish block list!"
echo "$BLOCK_DATA" | python3 -c "
import json, sys
data = json.load(sys.stdin)
ip = '$IP'
for entry in data.get('data', []):
if entry.get('ip') == ip:
last = entry['stateChanges'][-1]
status = '🚫 BLOCKED' if last['state'] else '✅ OK'
print(f\" {entry['isp']}: {status}\")
" 2>/dev/null
else
echo " ✅ IP not in Spanish block list"
fi
else
echo " ⚠️ Could not reach hayahora.futbol API"
fi
done
else
echo "No IPs to check (domain not configured)"
fi
# 5. Global Accessibility
echo -e "\n━━━ [5/7] Global Accessibility ━━━"
if [ -n "$IPS" ]; then
RESULT=$(curl -s "https://check-host.net/check-http?host=$DOMAIN&max_nodes=5" -H "Accept: application/json" 2>/dev/null)
REQUEST_ID=$(echo "$RESULT" | grep -o '"request_id":"[^"]*' | cut -d'"' -f4)
if [ -n "$REQUEST_ID" ]; then
echo "Check initiated: https://check-host.net/check-report/$REQUEST_ID"
sleep 3
LOCATIONS=$(curl -s "https://check-host.net/check-result/$REQUEST_ID" -H "Accept: application/json" 2>/dev/null | \
python3 -c "
import json, sys
try:
data = json.load(sys.stdin)
for loc, result in data.items():
if result and result[0]:
status = '✅' if result[0][0] == 1 else '❌'
code = result[0][3] if len(result[0]) > 3 else 'N/A'
print(f' {status} {loc}: HTTP {code}')
except: pass
" 2>/dev/null)
if [ -n "$LOCATIONS" ]; then
echo "$LOCATIONS"
fi
fi
else
echo "Skipped (no IPs configured)"
fi
# 6. Security Links
echo -e "\n━━━ [6/7] Security Check Links ━━━"
echo " VirusTotal: https://www.virustotal.com/gui/domain/$DOMAIN"
echo " Sucuri: https://sitecheck.sucuri.net/?scan=$DOMAIN"
echo " Safe Browsing: https://transparencyreport.google.com/safe-browsing/search?url=$DOMAIN"
# 7. History Links
echo -e "\n━━━ [7/7] Domain History ━━━"
echo " Archive.org: https://web.archive.org/web/*/$DOMAIN"
echo " ExpiredDomains: https://www.expireddomains.net/domain-name-search/?q=${DOMAIN%.*}"
echo -e "\n╔════════════════════════════════════════════════════════════╗"
echo "║ Check Complete ║"
echo "╚════════════════════════════════════════════════════════════╝"