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:
279
docs/architecture.md
Normal file
279
docs/architecture.md
Normal 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
162
docs/backup-strategy.md
Normal 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
301
docs/domain-check-guide.md
Normal 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
259
docs/hard-disk-change.md
Normal 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
114
docs/lan-dns-setup.md
Normal 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
95
docs/mcp-browser-setup.md
Normal 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
135
docs/mcp-research-guide.md
Normal 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
50
docs/n8n-mcp-setup.md
Normal 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
117
docs/scripts/domain-check.sh
Executable 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 "╚════════════════════════════════════════════════════════════╝"
|
||||
Reference in New Issue
Block a user