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:
56
.artifacts/2026-02-01_13-20_task-runners-healthcheck-fix.md
Normal file
56
.artifacts/2026-02-01_13-20_task-runners-healthcheck-fix.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Task Runners Health Check Fix
|
||||
|
||||
**Date:** 2026-02-01 13:20
|
||||
**Context:** Fixed unhealthy status on n8n task-runners container
|
||||
|
||||
## Problem
|
||||
|
||||
The `task-runners-uk0o04o0g84s4sc80kkoooc0` container was showing as unhealthy with 60,395+ consecutive failures.
|
||||
|
||||
**Root Cause:** Health check was configured to hit `/` but the actual health endpoint is `/healthz`
|
||||
|
||||
| Configured | Actual Endpoint |
|
||||
|------------|-----------------|
|
||||
| `http://127.0.0.1:5680/` | Returns 404 |
|
||||
| `http://127.0.0.1:5680/healthz` | Returns `{"status":"ok"}` |
|
||||
|
||||
## Solution
|
||||
|
||||
Updated Coolify's docker-compose configuration for the n8n service:
|
||||
|
||||
```bash
|
||||
docker exec coolify php artisan tinker --execute="
|
||||
use App\Models\Service;
|
||||
\$service = Service::find(4);
|
||||
\$compose = \$service->docker_compose_raw;
|
||||
\$compose = str_replace(
|
||||
\"'wget -qO- http://127.0.0.1:5680/'\",
|
||||
\"'wget -qO- http://127.0.0.1:5680/healthz'\",
|
||||
\$compose
|
||||
);
|
||||
\$service->docker_compose_raw = \$compose;
|
||||
\$service->save();
|
||||
"
|
||||
```
|
||||
|
||||
Then restarted the service:
|
||||
```bash
|
||||
docker exec coolify php artisan tinker --execute="
|
||||
use App\Models\Service;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Actions\Service\StartService;
|
||||
\$service = Service::find(4);
|
||||
StopService::run(\$service);
|
||||
StartService::run(\$service);
|
||||
"
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
- Container now shows as **healthy**
|
||||
- Health check correctly hitting `/healthz` endpoint
|
||||
|
||||
## Related
|
||||
- n8n Service ID: 4
|
||||
- n8n Service UUID: uk0o04o0g84s4sc80kkoooc0
|
||||
- Coolify: http://192.168.1.3:8000
|
||||
132
.artifacts/2026-02-01_16-45_liquidgym-mysql-migration.md
Normal file
132
.artifacts/2026-02-01_16-45_liquidgym-mysql-migration.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# LiquidGym & Knosia Migration to NUC
|
||||
|
||||
**Date:** 2026-02-01 16:45
|
||||
**Context:** Migrated LiquidGym MySQL and Knosia PostgreSQL from local Docker to NUC Coolify
|
||||
|
||||
## NUC MySQL Connection
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Host | 192.168.1.3 |
|
||||
| Port | 3306 |
|
||||
| Database | liquidgym |
|
||||
| User | liquidgym |
|
||||
| Password | liquidgym_nuc_2026 |
|
||||
| Root Password | liquidgym_root_nuc_2026 |
|
||||
| Coolify UUID | hgwcgs4oswwc8scg080scoo4 |
|
||||
| External URL | mysql://liquidgym:liquidgym_nuc_2026@192.168.1.3:3306/liquidgym |
|
||||
|
||||
## Local Volumes (Safe to Delete)
|
||||
|
||||
These local volumes contained LiquidGym MySQL data and are now migrated:
|
||||
- `infra_mysql_data` - Active data (migrated)
|
||||
- `infra_mysql_logs` - Logs
|
||||
- `liquidgym_mysql_data` - Stale duplicate
|
||||
- `liquidgym_mysql_logs` - Stale duplicate
|
||||
|
||||
## Project Updates
|
||||
|
||||
The liquidgym project at `/Users/agutierrez/Desktop/liquidgym` has been updated:
|
||||
- Added `.env.nuc` with NUC MySQL configuration
|
||||
- Local MySQL in docker-compose.yml can still be used for isolated testing
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Use NUC MySQL (remote)
|
||||
cd ~/Desktop/liquidgym/infra
|
||||
cp .env.nuc .env
|
||||
docker compose up cloudbeaver -d
|
||||
|
||||
# Use local MySQL (isolated)
|
||||
docker compose --profile tier1 up mysql -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Knosia (LiquidRender) PostgreSQL
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Host | 192.168.1.3 |
|
||||
| Port | 5442 |
|
||||
| Database | knosia |
|
||||
| User | knosia |
|
||||
| Password | knosia_nuc_2026 |
|
||||
| Coolify UUID | ik80skko0008w4000c4w40os |
|
||||
| pgvector | 0.8.1 enabled |
|
||||
| External URL | postgresql://knosia:knosia_nuc_2026@192.168.1.3:5442/knosia |
|
||||
|
||||
## NUC MinIO for Knosia
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Endpoint | http://192.168.1.3:9000 |
|
||||
| Bucket | knosia |
|
||||
| Access Key | Dt6yvE0MTH7N4GBz |
|
||||
| Secret Key | iz6Fl5aAixTgyzRcRLZrjmCM02CBCdmZ |
|
||||
| Coolify Service | dg4wkgg8skcssww0040sgk80 |
|
||||
|
||||
## Project Updates
|
||||
|
||||
- LiquidGym: `/Desktop/liquidgym/infra/.env.nuc`
|
||||
- LiquidRender (Knosia): `/Desktop/liquidrender/.env.nuc`
|
||||
|
||||
---
|
||||
|
||||
## LiquidGym PostgreSQL (Test Datasets)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Host | 192.168.1.3 |
|
||||
| Port | 5433 |
|
||||
| User | postgres |
|
||||
| Password | postgres |
|
||||
| Coolify UUID | x4kk8g4k8w4g0cw480w84g4g |
|
||||
|
||||
**Databases migrated (~520MB total):**
|
||||
| Database | Size | Description |
|
||||
|----------|------|-------------|
|
||||
| employees | 334 MB | HR dataset |
|
||||
| adventureworks | 114 MB | Sales/HR sample |
|
||||
| lego | 43 MB | LEGO sets |
|
||||
| pagila | 17 MB | DVD rental |
|
||||
| netflix | 11 MB | Netflix titles |
|
||||
| chinook | 10 MB | Music store |
|
||||
| northwind | 8 MB | Classic sample |
|
||||
|
||||
---
|
||||
|
||||
## Google Reviews Scraper PostgreSQL
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Host | 192.168.1.3 |
|
||||
| Port | 5437 |
|
||||
| Database | scraper |
|
||||
| User | scraper |
|
||||
| Password | scraper_nuc_2026 |
|
||||
| Coolify UUID | g4s8w4csk8s8ocswg48kkogo |
|
||||
|
||||
**Data migrated:**
|
||||
| Table | Rows |
|
||||
|-------|------|
|
||||
| jobs | 21 |
|
||||
| gbp_categories | 4,141 |
|
||||
| canary_results | 25 |
|
||||
|
||||
**Project files:**
|
||||
- `.env.nuc` - NUC database config
|
||||
- `docker-compose.nuc.yml` - Override to use NUC DB
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
cd ~/Desktop/google-reviews-scraper-pro
|
||||
cp .env.nuc .env
|
||||
docker compose -f docker-compose.production.yml -f docker-compose.nuc.yml up -d
|
||||
```
|
||||
|
||||
## Related
|
||||
- CloudBeaver on NUC: http://192.168.1.3:8087
|
||||
- MinIO Console: http://192.168.1.3:9001
|
||||
- Coolify Dashboard: http://192.168.1.3:8000
|
||||
124
.artifacts/2026-02-01_17-15_liquidgym-database-engines.md
Normal file
124
.artifacts/2026-02-01_17-15_liquidgym-database-engines.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# LiquidGym Database Engines Reference
|
||||
|
||||
**Date:** 2026-02-01 17:15
|
||||
**Context:** Reference guide for LiquidGym's multi-engine SQL testing infrastructure
|
||||
|
||||
## Overview
|
||||
|
||||
LiquidGym is a multi-database testing environment designed to verify that analytical queries work identically across different database engines. This ensures engine-agnostic query generation.
|
||||
|
||||
## Engine Tiers
|
||||
|
||||
### Core (Always Started)
|
||||
| Engine | Image | Port | Purpose |
|
||||
|--------|-------|------|---------|
|
||||
| PostgreSQL 16 | `postgres:16` | 5433 | Primary test database with sample datasets |
|
||||
| CloudBeaver | `dbeaver/cloudbeaver` | 8978 | Web-based database UI |
|
||||
|
||||
### Tier 1: Essential Engines
|
||||
Different SQL dialects for cross-engine testing.
|
||||
|
||||
| Engine | Image | Port | Description |
|
||||
|--------|-------|------|-------------|
|
||||
| **ClickHouse** | `clickhouse/clickhouse-server` | 8123 (HTTP), 9000 (Native) | Column-oriented OLAP database. Extremely fast for analytics on billions of rows. Used by Cloudflare, Uber, eBay. Best for: logs, metrics, time-series analytics. |
|
||||
| **MySQL 8** | `mysql:8` | 3306 | World's most popular open-source RDBMS. Tests MySQL-specific SQL dialect. |
|
||||
|
||||
### Tier 2: Distributed & Specialized
|
||||
| Engine | Image | Port | Description |
|
||||
|--------|-------|------|-------------|
|
||||
| **Trino** | `trinodb/trino` | 8084 | Distributed SQL query engine. Queries data across multiple sources (Postgres, S3, Kafka) with single SQL. No storage - just a query layer. |
|
||||
| **StarRocks** | `starrocks/allin1-ubuntu` | 9030 (MySQL), 8030 (HTTP) | MPP analytics database. Sub-second queries on large datasets. Powers BI dashboards. Fork of Apache Doris with performance improvements. |
|
||||
| **TimescaleDB** | `timescale/timescaledb:latest-pg16` | 5434 | PostgreSQL extension for time-series data. Auto-partitions by time. Perfect for IoT, metrics, events. Familiar Postgres SQL. |
|
||||
|
||||
### Tier 3: Advanced/Specialized
|
||||
| Engine | Image | Port | Description |
|
||||
|--------|-------|------|-------------|
|
||||
| **Apache Doris** | `apache/doris:doris-all-in-one-2.1.0` | 9031 (MySQL), 8031 (HTTP) | Real-time analytical database. MySQL-compatible. Good for real-time dashboards and ad-hoc queries. |
|
||||
| **Apache Druid** | `apache/druid:26.0.0` | 8888 | Real-time OLAP for sub-second slice-and-dice analytics. Powers Airbnb, Netflix, Alibaba dashboards. Best for: high-concurrency, low-latency queries. |
|
||||
| **Apache Spark** | `apache/spark:3.5.0` | 7077 (Master), 8085 (UI) | Distributed compute engine for big data. ML pipelines, ETL, batch processing. Overkill for small datasets. |
|
||||
|
||||
## Observability Stack
|
||||
|
||||
| Tool | Image | Port | Description |
|
||||
|------|-------|------|-------------|
|
||||
| **Grafana** | `grafana/grafana` | 3005 | Visualization & dashboards. Query any data source, create alerts. Login: admin/liquidgym |
|
||||
| **Prometheus** | `prom/prometheus` | 9090 | Metrics collection & alerting. Scrapes metrics from all engines. |
|
||||
| **Redis** | `redis:7-alpine` | 6379 | In-memory cache. Used for session storage, caching query results. |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
cd ~/Desktop/liquidgym/infra
|
||||
|
||||
# Start core only (Postgres + CloudBeaver)
|
||||
docker compose up -d
|
||||
|
||||
# Start with Tier 1 engines (+ ClickHouse, MySQL)
|
||||
docker compose --profile tier1 up -d
|
||||
|
||||
# Start with Tier 2 engines (+ Trino, StarRocks, TimescaleDB)
|
||||
docker compose --profile tier2 up -d
|
||||
|
||||
# Start with Tier 3 engines (+ Doris, Spark)
|
||||
docker compose --profile tier3 up -d
|
||||
|
||||
# Start observability stack (+ Prometheus, Grafana, Redis)
|
||||
docker compose --profile observability up -d
|
||||
|
||||
# Start everything
|
||||
docker compose --profile all up -d
|
||||
|
||||
# Load sample datasets
|
||||
docker compose --profile loader up
|
||||
```
|
||||
|
||||
## Sample Datasets
|
||||
|
||||
| Dataset | Description | Tables |
|
||||
|---------|-------------|--------|
|
||||
| **Northwind** | Classic MS Access sample - orders, products, customers | 14 |
|
||||
| **Pagila** | DVD rental store (PostgreSQL port of Sakila) | 29 |
|
||||
| **Chinook** | Digital media store - artists, albums, tracks | 11 |
|
||||
| **AdventureWorks** | Microsoft sample - sales, HR, production | 68 |
|
||||
| **Employees** | Large HR dataset with 300K+ employee records | 6 |
|
||||
| **LEGO** | LEGO sets, parts, themes, colors | 8 |
|
||||
| **Netflix** | Netflix titles catalog | 1 |
|
||||
|
||||
## When to Use Each Engine
|
||||
|
||||
| Use Case | Recommended Engine |
|
||||
|----------|-------------------|
|
||||
| General OLTP | PostgreSQL, MySQL |
|
||||
| Analytics on large datasets | ClickHouse, StarRocks |
|
||||
| Time-series / IoT | TimescaleDB |
|
||||
| Real-time dashboards | Druid, Doris |
|
||||
| Query across multiple DBs | Trino |
|
||||
| Big data / ML pipelines | Spark |
|
||||
| Caching | Redis |
|
||||
|
||||
## Resource Requirements
|
||||
|
||||
| Profile | RAM | CPU | Disk |
|
||||
|---------|-----|-----|------|
|
||||
| Core | 1GB | 1 | 1GB |
|
||||
| + Tier 1 | 6GB | 2 | 3GB |
|
||||
| + Tier 2 | 10GB | 4 | 5GB |
|
||||
| + Tier 3 | 16GB+ | 6+ | 10GB+ |
|
||||
| + Observability | +2GB | +1 | +1GB |
|
||||
|
||||
## NUC Migration Status
|
||||
|
||||
The following have been migrated to NUC and no longer need local volumes:
|
||||
|
||||
| Service | NUC Location | Status |
|
||||
|---------|--------------|--------|
|
||||
| PostgreSQL (datasets) | 192.168.1.3:5433 | Migrated |
|
||||
| MySQL | 192.168.1.3:3306 | Migrated |
|
||||
|
||||
Tier 1-3 engines remain local-only for development testing.
|
||||
|
||||
## Related
|
||||
|
||||
- LiquidGym project: `~/Desktop/liquidgym/infra/`
|
||||
- Docker Compose: `~/Desktop/liquidgym/infra/docker-compose.yml`
|
||||
- Datasets: `~/Desktop/liquidgym/infra/datasets/`
|
||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Ignore sensitive files
|
||||
*.key
|
||||
*.pem
|
||||
secrets/
|
||||
.env
|
||||
|
||||
# Sensitive artifacts (tokens, credentials)
|
||||
.artifacts/*-token*.md
|
||||
.artifacts/*-api-token*.md
|
||||
.artifacts/wireguard*.conf
|
||||
|
||||
# Claude session files
|
||||
.claude/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor files
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
tmp/
|
||||
28
.mcp.json
Normal file
28
.mcp.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"coolify": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@masonator/coolify-mcp@latest"],
|
||||
"env": {
|
||||
"COOLIFY_BASE_URL": "http://192.168.1.3:8000",
|
||||
"COOLIFY_ACCESS_TOKEN": "8|UZt8zhHTMDJofF0OmONSegWoQTDkXpLv1TWDnM1X"
|
||||
}
|
||||
},
|
||||
"playwriter-local": {
|
||||
"command": "npx",
|
||||
"args": ["playwriter"]
|
||||
},
|
||||
"playwriter-nuc-01": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "playwriter", "--host", "192.168.1.3", "--token", "nuc-browser-token"]
|
||||
},
|
||||
"browsermcp": {
|
||||
"command": "npx",
|
||||
"args": ["@browsermcp/mcp@latest"]
|
||||
},
|
||||
"chrome-devtools-nuc-01": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "chrome-devtools-mcp@latest", "--wsEndpoint", "ws://192.168.1.3:19988/cdp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
696
CLAUDE.md
Normal file
696
CLAUDE.md
Normal file
@@ -0,0 +1,696 @@
|
||||
# NUC Server - Claude Code Instructions
|
||||
|
||||
## Server Access
|
||||
|
||||
The NUC server is accessible via SSH:
|
||||
```bash
|
||||
ssh nuc
|
||||
```
|
||||
|
||||
**Connection Details:**
|
||||
- Hostname: `192.168.1.3`
|
||||
- User: `alezmad`
|
||||
- SSH Key: `~/.ssh/id_ed25519_nuc`
|
||||
|
||||
## Service Management
|
||||
|
||||
### Coolify (Primary Service Manager)
|
||||
All services are managed through Coolify at `http://192.168.1.3:8000`
|
||||
|
||||
**Prefer using Coolify MCP** (`mcp__coolify__*`) for service management - it's faster and more reliable than SSH commands.
|
||||
|
||||
### ⚠️ STRICT RULE: Container Deployment Priority
|
||||
|
||||
**ALWAYS attempt Coolify first when adding any container/service:**
|
||||
|
||||
1. **First:** Try `mcp__coolify__service(action="create", type="<service-name>", ...)`
|
||||
2. **If type invalid:** Deploy via docker-compose in Coolify using `docker_compose_raw`
|
||||
3. **Last resort:** Direct Docker commands via SSH (only if Coolify can't handle it)
|
||||
|
||||
```python
|
||||
# Step 1: Try native Coolify service type
|
||||
mcp__coolify__service(action="create", type="servicename", name="ServiceName",
|
||||
server_uuid="qk84w0goo4w48g4ggsoo0oss", project_uuid="a8484ggc88c40w4g4k004ow0",
|
||||
environment_name="production", instant_deploy=True)
|
||||
|
||||
# Step 2: If "Invalid service type", use docker-compose
|
||||
mcp__coolify__service(action="create", name="ServiceName",
|
||||
server_uuid="qk84w0goo4w48g4ggsoo0oss", project_uuid="a8484ggc88c40w4g4k004ow0",
|
||||
environment_name="production",
|
||||
docker_compose_raw="""
|
||||
services:
|
||||
myservice:
|
||||
image: organization/image:tag
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- data:/app/data
|
||||
volumes:
|
||||
data:
|
||||
""", instant_deploy=True)
|
||||
```
|
||||
|
||||
**Why Coolify first:**
|
||||
- Centralized management in one UI
|
||||
- Automatic restarts, health checks
|
||||
- Easy updates and rollbacks
|
||||
- Visible in infrastructure overview
|
||||
- Consistent with existing services
|
||||
|
||||
### ⚠️ STRICT RULE: Browser MCP for Manual Configurations
|
||||
|
||||
**When a task requires manual web UI interaction (OAuth setup, API key generation, admin consoles), ALWAYS use Browser MCP instead of asking the user to do it manually.**
|
||||
|
||||
**Priority order for browser automation:**
|
||||
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)
|
||||
|
||||
**Browser MCP Naming Convention:**
|
||||
```
|
||||
playwriter-<location>-<id>
|
||||
chrome-devtools-<location>-<id>
|
||||
|
||||
Examples:
|
||||
- playwriter-local → Local machine browser
|
||||
- playwriter-nuc-01 → First NUC browser container
|
||||
- playwriter-nuc-02 → Second NUC browser (future)
|
||||
- playwriter-cloud-01 → Cloud browser instance (future)
|
||||
```
|
||||
|
||||
**Common use cases:**
|
||||
- Generating API keys/tokens (Tailscale, OAuth apps, etc.)
|
||||
- Configuring OAuth/OIDC providers
|
||||
- Admin console settings not available via API
|
||||
- Any "go to website and click" tasks
|
||||
|
||||
```python
|
||||
# Example: Navigate and interact
|
||||
mcp__chrome-devtools__navigate_page(type="url", url="https://admin.example.com")
|
||||
mcp__chrome-devtools__take_snapshot() # See current state
|
||||
mcp__chrome-devtools__click(uid="<element_uid>")
|
||||
mcp__chrome-devtools__fill(uid="<element_uid>", value="text")
|
||||
```
|
||||
|
||||
**NEVER say "please go to X and do Y manually" - use browser MCP instead.**
|
||||
|
||||
### ⚠️ STRICT RULE: Parallel Subtasks for Multiple Operations
|
||||
|
||||
**When multiple independent services, configurations, or entities need to be set up, ALWAYS use parallel Task agents instead of sequential operations.**
|
||||
|
||||
```python
|
||||
# WRONG - Sequential (slow)
|
||||
# Step 1: Configure Tailscale
|
||||
# Step 2: Configure WireGuard
|
||||
# Step 3: Add to Homepage
|
||||
|
||||
# CORRECT - Parallel subtasks (fast)
|
||||
Task(subagent_type="general-purpose", prompt="Configure Tailscale auth key...", description="Setup Tailscale")
|
||||
Task(subagent_type="general-purpose", prompt="Install WireGuard on router...", description="Setup WireGuard")
|
||||
Task(subagent_type="general-purpose", prompt="Add services to Homepage...", description="Update Homepage")
|
||||
# All three run simultaneously!
|
||||
```
|
||||
|
||||
**When to parallelize:**
|
||||
- Multiple service deployments
|
||||
- Multiple configuration changes across different systems
|
||||
- Independent API calls or browser automations
|
||||
- Any tasks that don't depend on each other's output
|
||||
|
||||
**How to parallelize:**
|
||||
- Use multiple `Task` tool calls in a single message
|
||||
- Each task gets its own agent with full context
|
||||
- Results are collected when all complete
|
||||
|
||||
### Available MCPs for NUC Management
|
||||
|
||||
| MCP | Purpose |
|
||||
|-----|---------|
|
||||
| `mcp__coolify__*` | Service management, deployments, env vars |
|
||||
| `mcp__ssh-manager__*` | Direct SSH commands, file transfers |
|
||||
| `mcp__n8n__*` | Workflow automation (if configured) |
|
||||
| `mcp__playwriter__*` | Browser automation fallback (see below) |
|
||||
|
||||
### ⚠️ STRICT RULE: MCP Research Protocol
|
||||
|
||||
**When asked to find/recommend new MCPs**, read the detailed guide at `docs/mcp-research-guide.md` which contains:
|
||||
- MCP directories (MCP.so, Smithery, MCPHub, etc.)
|
||||
- GitHub verification resources
|
||||
- Evaluation criteria checklist
|
||||
- Research workflow
|
||||
- Category quick reference
|
||||
|
||||
**Quick workflow:**
|
||||
1. Search directories: `mcp.so`, `smithery.ai`, `mcpservers.org`
|
||||
2. Verify on GitHub: stars, last commit, issues
|
||||
3. Evaluate: 50+ stars, <6 months active, 3+ tools, OSS license
|
||||
|
||||
### Playwriter as Fallback
|
||||
|
||||
When SSH, API endpoints, or other MCPs can't accomplish a task (e.g., no API available, UI-only settings), use **Playwriter MCP** to automate browser interactions:
|
||||
|
||||
```javascript
|
||||
// Navigate to service UI
|
||||
await page.goto('http://192.168.1.3:8000');
|
||||
// Get page state
|
||||
console.log(await accessibilitySnapshot({ page }));
|
||||
// Interact with elements
|
||||
await page.locator('aria-ref=e5').click();
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Configuring services that lack APIs (Coolify UI settings, etc.)
|
||||
- Creating OAuth apps, API keys through web interfaces
|
||||
- Debugging issues by inspecting service dashboards
|
||||
- Any task where clicking through a UI is the only option
|
||||
|
||||
### Remote Browser Container (NUC)
|
||||
|
||||
A dedicated browser container runs on the NUC for AI-controlled browsing without local resources:
|
||||
|
||||
**Access:**
|
||||
- noVNC Web: `http://192.168.1.3:6081/vnc.html`
|
||||
- Playwriter Relay: `ws://192.168.1.3:19988`
|
||||
- Chrome DevTools: `http://192.168.1.3:9222`
|
||||
|
||||
**MCP connects remotely via:**
|
||||
```json
|
||||
{
|
||||
"playwriter-nuc-01": {
|
||||
"_id": "nuc-01",
|
||||
"_host": "192.168.1.3",
|
||||
"args": ["playwriter", "--host", "ws://192.168.1.3:19988", "--token", "nuc-browser-token"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**First-time setup:** Access noVNC, install Playwriter extension, click to activate (turns green).
|
||||
|
||||
**Container location:** `~/playwriter-browser/` on NUC (deployed via docker compose)
|
||||
|
||||
**Coolify CLI Commands:**
|
||||
```bash
|
||||
# Access Coolify's Laravel tinker for direct database/service manipulation
|
||||
ssh nuc "docker exec coolify php artisan tinker --execute=\"<PHP_CODE>\""
|
||||
|
||||
# Restart a service (example for service ID 9 - Outline)
|
||||
ssh nuc "docker exec coolify php artisan tinker --execute=\"
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Models\Service;
|
||||
\\\$service = Service::find(9);
|
||||
StartService::run(\\\$service);
|
||||
\""
|
||||
|
||||
# Update environment variable (encrypted)
|
||||
ssh nuc "docker exec coolify php artisan tinker --execute=\"
|
||||
use App\Models\EnvironmentVariable;
|
||||
\\\$var = EnvironmentVariable::where('key', 'VAR_NAME')->where('resourceable_id', SERVICE_ID)->first();
|
||||
\\\$var->value = encrypt('new_value');
|
||||
\\\$var->save();
|
||||
\""
|
||||
```
|
||||
|
||||
### Docker Commands
|
||||
```bash
|
||||
# List all containers
|
||||
ssh nuc "docker ps -a --format '{{.Names}}\t{{.Status}}'"
|
||||
|
||||
# View container logs
|
||||
ssh nuc "docker logs <container_name> 2>&1 | tail -50"
|
||||
|
||||
# Restart a container
|
||||
ssh nuc "docker restart <container_name>"
|
||||
|
||||
# Execute command in container
|
||||
ssh nuc "docker exec <container_name> <command>"
|
||||
```
|
||||
|
||||
## Services & Ports
|
||||
|
||||
| Service | Port | URL | Container |
|
||||
|---------|------|-----|-----------|
|
||||
| Homepage | 3000 | http://192.168.1.3:3000 | homepage-* |
|
||||
| Coolify | 8000 | http://192.168.1.3:8000 | coolify |
|
||||
| Gitea | 3030 | http://192.168.1.3:3030 | gitea-* |
|
||||
| Outline | 3080 | http://192.168.1.3:3080 | outline-* |
|
||||
| n8n | 5678 | http://192.168.1.3:5678 | n8n-* |
|
||||
| Vaultwarden | 8222 | http://192.168.1.3:8222 | vaultwarden-* |
|
||||
| Ntfy | 8333 | http://192.168.1.3:8333 | ntfy-* |
|
||||
| MinIO Console | 9001 | http://192.168.1.3:9001 | minio-* |
|
||||
| MinIO API | 9000 | http://192.168.1.3:9000 | minio-* |
|
||||
| Authentik | 9090 | http://192.168.1.3:9090 | authentik-* |
|
||||
| FileBrowser | 8085 | http://192.168.1.3:8085 | filebrowser-* |
|
||||
| Adminer | 8088 | http://192.168.1.3:8088 | adminer |
|
||||
| Uptime Kuma | 3001 | http://192.168.1.3:3001 | uptime-kuma |
|
||||
| Kopia | 51515 | http://192.168.1.3:51515 | kopia |
|
||||
| Dozzle | 9999 | http://192.168.1.3:9999 | dozzle |
|
||||
|
||||
## Port Forwarding
|
||||
|
||||
Some services use port forwarding containers (alpine/socat or nginx) to expose internal Coolify services:
|
||||
```bash
|
||||
# Create a port forwarder for a Coolify service
|
||||
ssh nuc "docker run -d --name port-fwd-<service> --network <coolify_network> -p <external_port>:<internal_port> alpine/socat tcp-listen:<internal_port>,fork,reuseaddr tcp-connect:<container_name>:<container_port>"
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
**Homepage Config:**
|
||||
- Location: `/opt/homepage/config/`
|
||||
- Services: `/opt/homepage/config/services.yaml`
|
||||
|
||||
**Coolify Data:**
|
||||
- Location: `/data/coolify/`
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **After Coolify redeploy**: Containers may be in "Created" state - manually start with `docker start <container>`
|
||||
|
||||
2. **Environment Variables in Coolify**: Are encrypted with Laravel encryption. Use `encrypt()` when updating.
|
||||
|
||||
3. **HSTS Issues**: Some services send HSTS headers. Use nginx proxy with `proxy_hide_header Strict-Transport-Security;` to strip them.
|
||||
|
||||
4. **Network Discovery**: Find container's network with `docker inspect <container> --format '{{.NetworkSettings.Networks}}'`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Coolify MCP vs Direct Docker
|
||||
|
||||
**Always verify Coolify status with Docker** - Coolify's status can lag behind actual container state:
|
||||
```bash
|
||||
# Coolify may show "exited" but container is actually running
|
||||
ssh nuc "docker ps -a --format 'table {{.Names}}\t{{.Status}}' | grep <service>"
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Containers stuck in "Created" state**: After Coolify restart/redeploy, containers may not auto-start
|
||||
```bash
|
||||
ssh nuc "docker start <container_name>"
|
||||
```
|
||||
|
||||
2. **Service shows "running:unknown"**: No healthcheck configured. Add one via Coolify service update:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:<port>"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
3. **Service dependencies not starting**: Services with `depends_on: condition: service_healthy` won't start until dependencies are healthy. Check dependency containers first.
|
||||
|
||||
4. **Stale database entries in Coolify**: Coolify may have database/service entries with no corresponding container. Safe to delete if container doesn't exist:
|
||||
```bash
|
||||
# Verify container doesn't exist
|
||||
ssh nuc "docker ps -a | grep <container_name>"
|
||||
# Then delete via Coolify MCP or UI
|
||||
```
|
||||
|
||||
5. **Embedded vs Standalone databases**: Services like Outline and Authentik have their own PostgreSQL containers (e.g., `postgres-pccg80...`) bundled in the service compose. These are separate from standalone Coolify databases.
|
||||
|
||||
6. **Wrong healthcheck endpoint**: Some services use `/healthz` instead of `/`. Verify with:
|
||||
```bash
|
||||
ssh nuc "docker exec <container> wget -qO- http://127.0.0.1:<port>/healthz"
|
||||
```
|
||||
|
||||
7. **Creating API keys when no UI available** (e.g., n8n):
|
||||
```bash
|
||||
# Stop container, insert directly into SQLite, restart
|
||||
ssh nuc "docker run --rm -v <volume>:/data keinos/sqlite3 sqlite3 /data/database.sqlite \"<INSERT_QUERY>\""
|
||||
```
|
||||
|
||||
### Coolify MCP Quick Reference
|
||||
|
||||
```python
|
||||
# Check infrastructure overview
|
||||
mcp__coolify__get_infrastructure_overview()
|
||||
|
||||
# Start/stop/restart service
|
||||
mcp__coolify__control(resource="service", action="start|stop|restart", uuid="<uuid>")
|
||||
|
||||
# Get service details (including docker_compose)
|
||||
mcp__coolify__get_service(uuid="<uuid>")
|
||||
|
||||
# Update service config (e.g., add healthcheck)
|
||||
mcp__coolify__service(action="update", uuid="<uuid>", docker_compose_raw="<yaml>")
|
||||
|
||||
# Delete stale database
|
||||
mcp__coolify__database(action="delete", uuid="<uuid>", delete_volumes=True)
|
||||
```
|
||||
|
||||
## OpenWrt Router
|
||||
|
||||
The network is managed by an OpenWrt router at `192.168.1.1`.
|
||||
|
||||
### SSH Access
|
||||
```bash
|
||||
# Connect to router
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1
|
||||
|
||||
# Or via NUC as jump host
|
||||
ssh nuc "ssh root@192.168.1.1 '<command>'"
|
||||
```
|
||||
|
||||
**Router Details:**
|
||||
- IP: `192.168.1.1`
|
||||
- User: `root`
|
||||
- SSH Key: `~/.ssh/id_ed25519_nuc`
|
||||
- Firmware: OpenWrt 23.05.0
|
||||
- Architecture: ARM Cortex-A9 (mvebu/cortexa9)
|
||||
- LuCI Web UI: `http://192.168.1.1`
|
||||
|
||||
### OpenWrt MCP Server
|
||||
An MCP server runs on the router for AI integration:
|
||||
- **Location:** `/opt/mcp-server/openwrt-mcp-server`
|
||||
- **Config:** `/opt/mcp-server/config.toml`
|
||||
- **HTTP API:** `http://192.168.1.1:8090`
|
||||
- **API Token:** `openwrt-mcp-secret-2026`
|
||||
- **Init Script:** `/etc/init.d/mcp-server`
|
||||
|
||||
```bash
|
||||
# Control MCP server
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "/etc/init.d/mcp-server start|stop|restart"
|
||||
```
|
||||
|
||||
### Common Router Tasks
|
||||
|
||||
**Port Forwarding:**
|
||||
```bash
|
||||
# List current port forwards
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "uci show firewall | grep redirect"
|
||||
|
||||
# Add port forward
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "
|
||||
uci add firewall redirect
|
||||
uci set firewall.@redirect[-1].name='<name>'
|
||||
uci set firewall.@redirect[-1].src='wan'
|
||||
uci set firewall.@redirect[-1].src_dport='<external_port>'
|
||||
uci set firewall.@redirect[-1].dest='lan'
|
||||
uci set firewall.@redirect[-1].dest_ip='<internal_ip>'
|
||||
uci set firewall.@redirect[-1].dest_port='<internal_port>'
|
||||
uci set firewall.@redirect[-1].proto='tcp udp'
|
||||
uci set firewall.@redirect[-1].target='DNAT'
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall restart
|
||||
"
|
||||
```
|
||||
|
||||
**Firewall Rules:**
|
||||
```bash
|
||||
# Show firewall zones
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "uci show firewall | grep zone"
|
||||
|
||||
# Allow traffic from WAN to specific port
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "
|
||||
uci add firewall rule
|
||||
uci set firewall.@rule[-1].name='Allow-<service>'
|
||||
uci set firewall.@rule[-1].src='wan'
|
||||
uci set firewall.@rule[-1].dest_port='<port>'
|
||||
uci set firewall.@rule[-1].proto='tcp'
|
||||
uci set firewall.@rule[-1].target='ACCEPT'
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall restart
|
||||
"
|
||||
```
|
||||
|
||||
**DNS/DHCP (dnsmasq):**
|
||||
```bash
|
||||
# View DNS/DHCP config
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "uci show dhcp"
|
||||
|
||||
# Force DNS cache refresh
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "/etc/init.d/dnsmasq restart"
|
||||
|
||||
# Add static DHCP lease
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "
|
||||
uci add dhcp host
|
||||
uci set dhcp.@host[-1].name='<hostname>'
|
||||
uci set dhcp.@host[-1].mac='<mac_address>'
|
||||
uci set dhcp.@host[-1].ip='<ip_address>'
|
||||
uci commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
"
|
||||
|
||||
# Add custom DNS entry
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "
|
||||
uci add dhcp domain
|
||||
uci set dhcp.@domain[-1].name='<hostname>'
|
||||
uci set dhcp.@domain[-1].ip='<ip_address>'
|
||||
uci commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
"
|
||||
```
|
||||
|
||||
**Network Diagnostics:**
|
||||
```bash
|
||||
# Check WAN status
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "ifstatus wan"
|
||||
|
||||
# View connected clients
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "cat /tmp/dhcp.leases"
|
||||
|
||||
# Check routing table
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "ip route"
|
||||
|
||||
# View system logs
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "logread | tail -50"
|
||||
```
|
||||
|
||||
**Package Management (opkg):**
|
||||
```bash
|
||||
# Update package lists
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "opkg update"
|
||||
|
||||
# Install package
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "opkg install <package>"
|
||||
|
||||
# List installed packages
|
||||
ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "opkg list-installed"
|
||||
```
|
||||
|
||||
**Browser Automation (Chrome DevTools MCP):**
|
||||
When SSH commands aren't sufficient, use Chrome DevTools MCP to automate LuCI:
|
||||
```python
|
||||
# Navigate to router UI
|
||||
mcp__chrome-devtools__navigate_page(type="url", url="http://192.168.1.1/cgi-bin/luci/admin/...")
|
||||
|
||||
# Take snapshot of UI state
|
||||
mcp__chrome-devtools__take_snapshot()
|
||||
|
||||
# Interact with elements by uid
|
||||
mcp__chrome-devtools__click(uid="<uid>")
|
||||
mcp__chrome-devtools__fill(uid="<uid>", value="<text>")
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
**Outline OIDC (via Gitea):**
|
||||
- Client ID: `249a3a1d-92d4-47d8-b4a9-81c64e1da6ab`
|
||||
- Auth URL: `http://192.168.1.3:3030/login/oauth/authorize`
|
||||
- Token URL: `http://192.168.1.3:3030/login/oauth/access_token`
|
||||
- Userinfo URL: `http://192.168.1.3:3030/login/oauth/userinfo`
|
||||
|
||||
## Public Access & Security Architecture
|
||||
|
||||
**Full architecture details:** `docs/architecture.md`
|
||||
|
||||
### Why Tailscale Funnel (Not Cloudflare)
|
||||
|
||||
Cloudflare shared IPs get blocked by Spanish ISPs during LaLiga matches. Tailscale Funnel:
|
||||
- Uses different IP infrastructure (not blocked)
|
||||
- Handles dynamic ISP IP changes automatically
|
||||
- No ports exposed on router
|
||||
- HTTPS termination included
|
||||
|
||||
### Tailscale Funnel (Public Access)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Funnel URL** | `https://nuc-tailscale.tail58f5ad.ts.net` |
|
||||
| **Tailscale IP** | `100.x.x.x` (stable, never changes) |
|
||||
| **Status** | `ssh nuc "tailscale funnel status"` |
|
||||
|
||||
**Start Funnel for a service:**
|
||||
```bash
|
||||
# Expose port 3000 via Funnel
|
||||
ssh nuc "tailscale funnel 3000"
|
||||
|
||||
# Or with background (use screen/tmux)
|
||||
ssh nuc "screen -dmS funnel tailscale funnel 3000"
|
||||
```
|
||||
|
||||
### Current Domain Routes
|
||||
|
||||
| Domain | Destination | Method |
|
||||
|--------|-------------|--------|
|
||||
| whyrating.com | `nuc-tailscale.tail58f5ad.ts.net` | Namecheap 301 redirect |
|
||||
|
||||
### Adding a New Domain
|
||||
|
||||
1. **Check availability** (if needed):
|
||||
```python
|
||||
mcp__namecheap__namecheap_check_domain_availability(domains=["example.com"])
|
||||
```
|
||||
|
||||
2. **Configure redirect at registrar** (Namecheap):
|
||||
- Go to Domain List → Manage → Redirect Domain
|
||||
- Source: `example.com`
|
||||
- Destination: `https://nuc-tailscale.tail58f5ad.ts.net`
|
||||
- Type: Permanent (301)
|
||||
|
||||
3. **Start Funnel** on NUC for the target port
|
||||
|
||||
### Security Layers
|
||||
|
||||
```
|
||||
Internet → Tailscale Funnel (HTTPS) → CrowdSec → Traefik → Container
|
||||
↓
|
||||
Blocks malicious IPs
|
||||
```
|
||||
|
||||
| Layer | Purpose |
|
||||
|-------|---------|
|
||||
| **Tailscale Funnel** | Only entry point, HTTPS termination |
|
||||
| **CrowdSec** | DDoS protection, threat intelligence |
|
||||
| **Traefik** | Domain routing, rate limiting |
|
||||
| **Docker Networks** | Container isolation |
|
||||
|
||||
### Tailscale Mesh (Admin Access)
|
||||
|
||||
Private encrypted access from anywhere:
|
||||
|
||||
| From | Command |
|
||||
|------|---------|
|
||||
| **Remote SSH** | `ssh nuc-tailscale` |
|
||||
| **Remote Coolify** | `http://nuc-tailscale:8000` |
|
||||
| **Home network** | `ssh nuc` or `http://192.168.1.3:8000` |
|
||||
|
||||
**NOT exposed to internet:** SSH (22), Coolify (8000), databases, MinIO, Authentik, router admin.
|
||||
|
||||
### Dynamic IP Handling
|
||||
|
||||
ISP can change your public IP anytime. Tailscale handles this automatically:
|
||||
- Tailscale IP (100.x.x.x) stays stable
|
||||
- Funnel URL stays stable
|
||||
- Domain redirects stay stable
|
||||
- Tunnels auto-reconnect in ~10-30 seconds
|
||||
|
||||
## Artifacts Folder
|
||||
|
||||
**Location:** `.artifacts/`
|
||||
|
||||
This folder stores important information artifacts that should be preserved for future reference. Claude should proactively save artifacts here when relevant information is generated during sessions.
|
||||
|
||||
### Naming Convention
|
||||
Files must use datetime-prefixed names:
|
||||
```
|
||||
YYYY-MM-DD_HH-MM_<description>.md
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `2026-02-01_13-45_coolify-api-token.md`
|
||||
- `2026-02-01_14-30_n8n-mcp-setup.md`
|
||||
- `2026-02-01_15-00_service-health-report.md`
|
||||
|
||||
### When to Save Artifacts
|
||||
|
||||
**Always save artifacts for:**
|
||||
- API tokens, keys, or credentials generated during sessions
|
||||
- Configuration changes made to services
|
||||
- Troubleshooting steps that resolved issues
|
||||
- Infrastructure changes or deployments
|
||||
- MCP server configurations and setup details
|
||||
- Database schema changes or migrations
|
||||
- Important command outputs that may be needed later
|
||||
- Service health reports or diagnostics
|
||||
|
||||
### Artifact Format
|
||||
```markdown
|
||||
# <Title>
|
||||
|
||||
**Date:** YYYY-MM-DD HH:MM
|
||||
**Context:** <Brief description of what was being done>
|
||||
|
||||
## Details
|
||||
|
||||
<Relevant information, configs, tokens, commands, etc.>
|
||||
|
||||
## Related
|
||||
- <Links to services, docs, or other artifacts>
|
||||
```
|
||||
|
||||
## OpenWrt Interaction Methods (Quick Reference)
|
||||
|
||||
| Method | When to Use | Example |
|
||||
|--------|-------------|---------|
|
||||
| **SSH** | Direct commands, config changes, package management | `ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "uci show network"` |
|
||||
| **OpenWrt MCP** | AI-driven automation, status queries | `curl -H "x-api-token: openwrt-mcp-secret-2026" http://192.168.1.1:8090/status` |
|
||||
| **Chrome DevTools MCP** | LuCI UI automation when no CLI/API exists | `mcp__chrome-devtools__navigate_page(url="http://192.168.1.1/...")` |
|
||||
| **LuCI Web UI** | Manual configuration, visual inspection | `http://192.168.1.1` (user: root) |
|
||||
|
||||
**Priority Order:** SSH > OpenWrt MCP > Chrome DevTools > Manual UI
|
||||
|
||||
## Next Steps / Migration Candidates
|
||||
|
||||
### Priority 1: Safe to Delete (Duplicates/Old Versions)
|
||||
| Image | Size | Action | Reason |
|
||||
|-------|------|--------|--------|
|
||||
| `google-reviews-scraper-pro-api` (old) | 3.62GB | ❌ DELETE | Old version, newer exists |
|
||||
| `claudefarm-claudefarm` | 3.87GB | ❌ DELETE | Replaced by claudefarm-browser + claudefarm-api |
|
||||
| `postgres:16` | 657MB | ❌ DELETE | Using `16-alpine` (389MB) instead |
|
||||
| `prom/mysqld-exporter:v0.14.0` | 28MB | ❌ DELETE | 3 years old, likely unused |
|
||||
|
||||
**Savings: ~8.2GB**
|
||||
|
||||
### Priority 2: Migrate to NUC (High Value)
|
||||
| Image | Size | Priority | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `nocodb/nocodb` | 1.24GB | ⭐ HIGH | Airtable alternative - great for self-hosted data |
|
||||
| `grafana/grafana` | 932MB | ⭐ HIGH | Pairs with existing Uptime Kuma for monitoring |
|
||||
| `prom/prometheus` | 479MB | ⭐ HIGH | Metrics backend for Grafana |
|
||||
| `timescale/timescaledb` | 1.45GB | ⭐ HIGH | Time-series data, useful for IoT/metrics |
|
||||
|
||||
### Priority 3: Migrate to NUC (Medium Value)
|
||||
| Image | Size | Priority | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `mysql:8` | 1.07GB | 🔶 MEDIUM | Only if you have MySQL-specific apps |
|
||||
| `minio/minio + minio/mc` | 340MB | 🔶 SKIP | Already running on NUC via Coolify |
|
||||
|
||||
### Priority 4: MCP Tools - Evaluate Usage
|
||||
| Image | Size | Recommendation | Notes |
|
||||
|-------|------|----------------|-------|
|
||||
| `mcp/n8n` | 675MB | 🔶 SKIP | n8n already on NUC; this is just MCP wrapper |
|
||||
| `mcp/youtube-transcript` | 321MB | ✅ KEEP LOCAL | Useful for AI workflows |
|
||||
| `mcp/context7` | 423MB | ✅ KEEP LOCAL | Documentation lookup, AI essential |
|
||||
| `mcp/fetch` | ? | ✅ KEEP LOCAL | Web fetching for AI |
|
||||
|
||||
### Priority 5: Review Before Deleting
|
||||
| Image | Size | Action | Why Review |
|
||||
|-------|------|--------|------------|
|
||||
| `mysql:8` | 1.07GB | ⚠️ CHECK | May have local databases; verify before delete |
|
||||
| `timescale/timescaledb` | 1.45GB | ⚠️ CHECK | May have local time-series data |
|
||||
|
||||
### Recommended Coolify Deployments
|
||||
|
||||
```bash
|
||||
# 1. NocoDB (Airtable alternative)
|
||||
mcp__coolify__service(action="create", type="nocodb", name="NocoDB",
|
||||
server_uuid="qk84w0goo4w48g4ggsoo0oss", project_uuid="a8484ggc88c40w4g4k004ow0",
|
||||
environment_name="production", instant_deploy=True)
|
||||
|
||||
# 2. Prometheus + Grafana stack
|
||||
mcp__coolify__service(action="create", type="grafana", ...)
|
||||
mcp__coolify__service(action="create", type="prometheus", ...)
|
||||
```
|
||||
|
||||
### Migration Checklist
|
||||
- [ ] Delete old/duplicate images locally
|
||||
- [ ] Deploy NocoDB to NUC
|
||||
- [ ] Deploy Grafana + Prometheus monitoring stack
|
||||
- [ ] Consider TimescaleDB if IoT/metrics needed
|
||||
- [ ] Verify MySQL data before deleting
|
||||
- [ ] Add CloudBeaver to Uptime Kuma monitoring
|
||||
- [ ] Configure OpenWrt MCP MQTT broker (optional)
|
||||
72
NEXT_STEPS.md
Normal file
72
NEXT_STEPS.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Next Steps
|
||||
|
||||
## Immediate Tasks
|
||||
|
||||
### 1. Documentation Migration
|
||||
- [ ] Create documentation structure in Outline (http://192.168.1.3:3080)
|
||||
- [ ] Migrate guidelines from Homepage to Outline
|
||||
- [ ] Remove guidelines from Homepage after migration
|
||||
|
||||
### 2. Cleanup
|
||||
- [ ] Revoke duplicate "Claude MCP Server" API token in Coolify (Settings → Keys & Tokens → API Tokens)
|
||||
|
||||
## Configuration Tasks
|
||||
|
||||
### 3. Test Coolify MCP
|
||||
- [ ] Start a Claude Code session from `~/Desktop/nuc`
|
||||
- [ ] Verify Coolify MCP connects with the API token
|
||||
- [ ] Test listing services, deployments, and servers
|
||||
|
||||
### 4. SSH Setup (if not done)
|
||||
```bash
|
||||
cd ~/Desktop/nuc
|
||||
./setup-ssh.sh
|
||||
```
|
||||
|
||||
## Future Improvements
|
||||
|
||||
### Infrastructure
|
||||
- [ ] Configure Authentik as central identity provider
|
||||
- [ ] Set up automated backups verification in Kopia
|
||||
- [ ] Add more services to Uptime Kuma monitoring
|
||||
- [ ] Configure ntfy notifications for critical alerts
|
||||
|
||||
### Security
|
||||
- [ ] Enable HTTPS with Let's Encrypt certificates
|
||||
- [ ] Set up Vaultwarden backup strategy
|
||||
- [ ] Review and restrict Coolify API token IP allowlist
|
||||
- [ ] Audit exposed ports and services
|
||||
|
||||
### Automation
|
||||
- [ ] Create n8n workflows for common tasks
|
||||
- [ ] Set up automated health checks
|
||||
- [ ] Configure Coolify webhooks for deployment notifications
|
||||
|
||||
### Documentation
|
||||
- [ ] Document backup/restore procedures in Outline
|
||||
- [ ] Create runbooks for common incidents
|
||||
- [ ] Document service dependencies and startup order
|
||||
- [ ] Add network diagram to documentation
|
||||
|
||||
## Service-Specific Tasks
|
||||
|
||||
### Outline
|
||||
- [ ] Configure S3 storage with MinIO for attachments
|
||||
- [ ] Set up collections and permissions structure
|
||||
- [ ] Import existing documentation
|
||||
|
||||
### Gitea
|
||||
- [ ] Configure repository mirroring (if needed)
|
||||
- [ ] Set up CI/CD with Gitea Actions
|
||||
- [ ] Configure webhook integrations
|
||||
|
||||
### n8n
|
||||
- [ ] Create workflow templates
|
||||
- [ ] Set up credentials for external services
|
||||
- [ ] Configure error notifications
|
||||
|
||||
## Notes
|
||||
|
||||
- Coolify API Token: Configured in `.claude/settings.json`
|
||||
- All services accessible at `192.168.1.3` on their respective ports
|
||||
- SSH access: `ssh nuc` (after running setup-ssh.sh)
|
||||
151
README.md
Normal file
151
README.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# NUC Home Server
|
||||
|
||||
Personal home server running on Intel NUC at `192.168.1.3`
|
||||
|
||||
## Quick Access
|
||||
|
||||
| Service | URL | Description |
|
||||
|---------|-----|-------------|
|
||||
| **Homepage** | http://192.168.1.3:3000 | Dashboard with all services |
|
||||
| **Coolify** | http://192.168.1.3:8000 | Service deployment & management |
|
||||
| **Gitea** | http://192.168.1.3:3030 | Git repository hosting |
|
||||
| **Outline** | http://192.168.1.3:3080 | Documentation wiki (login via Gitea) |
|
||||
| **n8n** | http://192.168.1.3:5678 | Workflow automation |
|
||||
| **Vaultwarden** | http://192.168.1.3:8222 | Password manager (Bitwarden compatible) |
|
||||
| **Ntfy** | http://192.168.1.3:8333 | Push notifications |
|
||||
| **Uptime Kuma** | http://192.168.1.3:3001 | Service monitoring |
|
||||
|
||||
## SSH Access
|
||||
|
||||
```bash
|
||||
# Connect to the server
|
||||
ssh nuc
|
||||
|
||||
# Or explicitly
|
||||
ssh alezmad@192.168.1.3
|
||||
```
|
||||
|
||||
Make sure your SSH config includes:
|
||||
```
|
||||
Host nuc
|
||||
HostName 192.168.1.3
|
||||
User alezmad
|
||||
IdentityFile ~/.ssh/id_ed25519_nuc
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ NUC Server │
|
||||
│ 192.168.1.3 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Coolify (PaaS) │
|
||||
│ ├── Traefik (Reverse Proxy) :80/:443 │
|
||||
│ ├── Services: │
|
||||
│ │ ├── Homepage (Dashboard) │
|
||||
│ │ ├── Gitea (Git + OAuth2 Provider) │
|
||||
│ │ ├── Outline (Wiki/Docs) │
|
||||
│ │ ├── n8n (Automation) │
|
||||
│ │ ├── Vaultwarden (Passwords) │
|
||||
│ │ ├── Ntfy (Notifications) │
|
||||
│ │ ├── MinIO (S3 Storage) │
|
||||
│ │ ├── FileBrowser │
|
||||
│ │ └── Authentik (Identity - partially configured) │
|
||||
│ └── Databases (PostgreSQL, Redis per service) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Standalone Containers: │
|
||||
│ ├── Uptime Kuma (Monitoring) │
|
||||
│ ├── Dozzle (Log Viewer) │
|
||||
│ ├── Adminer (DB Admin) │
|
||||
│ └── Kopia (Backups) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Service Details
|
||||
|
||||
### Coolify
|
||||
- **URL:** http://192.168.1.3:8000
|
||||
- **Purpose:** Self-hosted PaaS for deploying and managing all services
|
||||
- **Login:** Admin account configured during setup
|
||||
|
||||
### Gitea
|
||||
- **URL:** http://192.168.1.3:3030
|
||||
- **SSH:** Port 22222
|
||||
- **Purpose:** Git repository hosting, also serves as OAuth2 provider for Outline
|
||||
- **Admin:** alezmad
|
||||
|
||||
### Outline
|
||||
- **URL:** http://192.168.1.3:3080
|
||||
- **Purpose:** Team documentation and wiki
|
||||
- **Auth:** Login via Gitea (OIDC)
|
||||
- **Note:** Uses nginx proxy to strip HSTS headers
|
||||
|
||||
### n8n
|
||||
- **URL:** http://192.168.1.3:5678
|
||||
- **Purpose:** Workflow automation (like Zapier)
|
||||
- **Runners:** Has dedicated task runners container
|
||||
|
||||
### Vaultwarden
|
||||
- **URL:** http://192.168.1.3:8222
|
||||
- **Purpose:** Self-hosted Bitwarden-compatible password manager
|
||||
- **Clients:** Use any Bitwarden client, point to this URL
|
||||
|
||||
### Ntfy
|
||||
- **URL:** http://192.168.1.3:8333
|
||||
- **Purpose:** Push notifications to mobile/desktop
|
||||
- **Mobile App:** Available on F-Droid and Play Store
|
||||
|
||||
### MinIO
|
||||
- **Console:** http://192.168.1.3:9001
|
||||
- **API:** http://192.168.1.3:9000
|
||||
- **Purpose:** S3-compatible object storage
|
||||
|
||||
### Monitoring & Tools
|
||||
- **Uptime Kuma:** http://192.168.1.3:3001 - Service health monitoring
|
||||
- **Dozzle:** http://192.168.1.3:9999 - Real-time Docker log viewer
|
||||
- **Adminer:** http://192.168.1.3:8088 - Database management UI
|
||||
- **Kopia:** http://192.168.1.3:51515 - Backup management
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### View Service Logs
|
||||
```bash
|
||||
ssh nuc "docker logs <container_name> -f --tail 100"
|
||||
```
|
||||
|
||||
### Restart a Service
|
||||
```bash
|
||||
ssh nuc "docker restart <container_name>"
|
||||
```
|
||||
|
||||
### Check All Services
|
||||
```bash
|
||||
ssh nuc "docker ps --format 'table {{.Names}}\t{{.Status}}'"
|
||||
```
|
||||
|
||||
### Backup Consideration
|
||||
- Kopia handles automated backups
|
||||
- Access at http://192.168.1.3:51515 to manage snapshots
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Not Accessible
|
||||
1. Check if container is running: `ssh nuc "docker ps | grep <service>"`
|
||||
2. Check logs: `ssh nuc "docker logs <container> --tail 50"`
|
||||
3. Check port forwarding container if applicable
|
||||
|
||||
### After Coolify Redeploy
|
||||
Containers may be in "Created" state. Start manually:
|
||||
```bash
|
||||
ssh nuc "docker start <container_name>"
|
||||
```
|
||||
|
||||
### HTTPS/SSL Issues
|
||||
Some browsers cache HSTS. Clear at `chrome://net-internals/#hsts`
|
||||
|
||||
## Files in This Directory
|
||||
|
||||
- `CLAUDE.md` - Instructions for Claude Code AI assistant
|
||||
- `README.md` - This file (human documentation)
|
||||
- `.claude/settings.json` - MCP server configuration for Claude Code
|
||||
171
commands.md
Normal file
171
commands.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Quick Reference Commands
|
||||
|
||||
## SSH Connection
|
||||
```bash
|
||||
ssh nuc
|
||||
```
|
||||
|
||||
## Docker Commands
|
||||
|
||||
### List Running Containers
|
||||
```bash
|
||||
ssh nuc "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# Last 100 lines
|
||||
ssh nuc "docker logs <container> --tail 100"
|
||||
|
||||
# Follow logs
|
||||
ssh nuc "docker logs <container> -f"
|
||||
|
||||
# With timestamps
|
||||
ssh nuc "docker logs <container> --tail 50 -t"
|
||||
```
|
||||
|
||||
### Container Management
|
||||
```bash
|
||||
# Restart
|
||||
ssh nuc "docker restart <container>"
|
||||
|
||||
# Stop
|
||||
ssh nuc "docker stop <container>"
|
||||
|
||||
# Start
|
||||
ssh nuc "docker start <container>"
|
||||
|
||||
# Remove
|
||||
ssh nuc "docker rm <container>"
|
||||
```
|
||||
|
||||
### Execute Commands in Container
|
||||
```bash
|
||||
ssh nuc "docker exec <container> <command>"
|
||||
|
||||
# Interactive shell
|
||||
ssh nuc -t "docker exec -it <container> /bin/sh"
|
||||
```
|
||||
|
||||
### Network Inspection
|
||||
```bash
|
||||
# Find container's network
|
||||
ssh nuc "docker inspect <container> --format '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}'"
|
||||
|
||||
# List networks
|
||||
ssh nuc "docker network ls"
|
||||
```
|
||||
|
||||
## Coolify Management
|
||||
|
||||
### Restart a Service
|
||||
```bash
|
||||
ssh nuc "docker exec coolify php artisan tinker --execute=\"
|
||||
use App\\Actions\\Service\\StartService;
|
||||
use App\\Models\\Service;
|
||||
\\\$service = Service::find(<SERVICE_ID>);
|
||||
StartService::run(\\\$service);
|
||||
\""
|
||||
```
|
||||
|
||||
### List Services
|
||||
```bash
|
||||
ssh nuc "docker exec coolify php artisan tinker --execute=\"
|
||||
use App\\Models\\Service;
|
||||
Service::all(['id','name','uuid'])->toJson();
|
||||
\""
|
||||
```
|
||||
|
||||
### Update Environment Variable
|
||||
```bash
|
||||
ssh nuc "docker exec coolify php artisan tinker --execute=\"
|
||||
use App\\Models\\EnvironmentVariable;
|
||||
\\\$var = EnvironmentVariable::where('key', 'VAR_NAME')
|
||||
->where('resourceable_id', <SERVICE_ID>)
|
||||
->where('resourceable_type', 'App\\\\\\\\Models\\\\\\\\Service')
|
||||
->first();
|
||||
\\\$var->value = encrypt('new_value');
|
||||
\\\$var->save();
|
||||
echo 'Updated';
|
||||
\""
|
||||
```
|
||||
|
||||
## Port Forwarding
|
||||
|
||||
### Create socat forwarder
|
||||
```bash
|
||||
ssh nuc "docker run -d \
|
||||
--name port-fwd-<name> \
|
||||
--network <network_id> \
|
||||
-p <external_port>:<internal_port> \
|
||||
alpine/socat \
|
||||
tcp-listen:<internal_port>,fork,reuseaddr \
|
||||
tcp-connect:<container>:<container_port>"
|
||||
```
|
||||
|
||||
### Create nginx forwarder (with HSTS stripping)
|
||||
```bash
|
||||
ssh nuc "docker run -d \
|
||||
--name port-fwd-<name> \
|
||||
--network <network_id> \
|
||||
-p <external_port>:<proxy_port> \
|
||||
nginx:alpine \
|
||||
sh -c 'echo \"server { listen <proxy_port>; location / { proxy_pass http://<container>:<port>; proxy_set_header Host \\\$host; proxy_hide_header Strict-Transport-Security; } }\" > /etc/nginx/conf.d/default.conf && nginx -g \"daemon off;\"'"
|
||||
```
|
||||
|
||||
## Service URLs
|
||||
|
||||
| Service | URL |
|
||||
|---------|-----|
|
||||
| Homepage | http://192.168.1.3:3000 |
|
||||
| Coolify | http://192.168.1.3:8000 |
|
||||
| Gitea | http://192.168.1.3:3030 |
|
||||
| Outline | http://192.168.1.3:3080 |
|
||||
| n8n | http://192.168.1.3:5678 |
|
||||
| Vaultwarden | http://192.168.1.3:8222 |
|
||||
| Ntfy | http://192.168.1.3:8333 |
|
||||
| MinIO | http://192.168.1.3:9001 |
|
||||
| Uptime Kuma | http://192.168.1.3:3001 |
|
||||
| Dozzle | http://192.168.1.3:9999 |
|
||||
| Adminer | http://192.168.1.3:8088 |
|
||||
| Kopia | http://192.168.1.3:51515 |
|
||||
| FileBrowser | http://192.168.1.3:8085 |
|
||||
|
||||
## Backup & Recovery
|
||||
|
||||
### Kopia Web UI
|
||||
http://192.168.1.3:51515
|
||||
|
||||
### Manual Backup Check
|
||||
```bash
|
||||
ssh nuc "docker exec kopia kopia snapshot list"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check disk space
|
||||
```bash
|
||||
ssh nuc "df -h"
|
||||
```
|
||||
|
||||
### Check memory
|
||||
```bash
|
||||
ssh nuc "free -h"
|
||||
```
|
||||
|
||||
### Check Docker disk usage
|
||||
```bash
|
||||
ssh nuc "docker system df"
|
||||
```
|
||||
|
||||
### Clean up Docker
|
||||
```bash
|
||||
# Remove unused images
|
||||
ssh nuc "docker image prune -a"
|
||||
|
||||
# Remove unused volumes
|
||||
ssh nuc "docker volume prune"
|
||||
|
||||
# Full cleanup
|
||||
ssh nuc "docker system prune -a"
|
||||
```
|
||||
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 "╚════════════════════════════════════════════════════════════╝"
|
||||
94
playwriter-browser/Dockerfile
Normal file
94
playwriter-browser/Dockerfile
Normal file
@@ -0,0 +1,94 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
# Basic utilities
|
||||
wget \
|
||||
gnupg \
|
||||
curl \
|
||||
ca-certificates \
|
||||
# Xvfb for virtual display
|
||||
xvfb \
|
||||
# VNC server and noVNC
|
||||
x11vnc \
|
||||
novnc \
|
||||
websockify \
|
||||
# Window manager (lightweight)
|
||||
openbox \
|
||||
# For clicking extension icon automatically
|
||||
xdotool \
|
||||
# Credential storage (gnome-keyring for password persistence)
|
||||
gnome-keyring \
|
||||
libsecret-1-0 \
|
||||
libsecret-1-dev \
|
||||
dbus-x11 \
|
||||
# Chrome dependencies
|
||||
fonts-liberation \
|
||||
libasound2 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libatspi2.0-0 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libdrm2 \
|
||||
libgbm1 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libwayland-client0 \
|
||||
libxcomposite1 \
|
||||
libxdamage1 \
|
||||
libxfixes3 \
|
||||
libxkbcommon0 \
|
||||
libxrandr2 \
|
||||
xdg-utils \
|
||||
libu2f-udev \
|
||||
libvulkan1 \
|
||||
# Node.js for Playwriter relay server
|
||||
nodejs \
|
||||
npm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Google Chrome (better extension support than Chromium)
|
||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Playwriter CLI globally
|
||||
RUN npm install -g playwriter
|
||||
|
||||
# Create directories for Chrome profile and keyrings
|
||||
RUN mkdir -p /app \
|
||||
/root/.config/google-chrome/Default/Extensions \
|
||||
/root/.local/share/keyrings \
|
||||
/run/dbus \
|
||||
&& chmod 700 /root/.local/share/keyrings
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy startup scripts
|
||||
COPY start.sh /app/start.sh
|
||||
COPY auto-activate.sh /app/auto-activate.sh
|
||||
RUN chmod +x /app/start.sh /app/auto-activate.sh
|
||||
|
||||
# Run as root (required for dbus/keyring; Chrome uses --no-sandbox in containers)
|
||||
USER root
|
||||
|
||||
# Environment variables
|
||||
ENV DISPLAY=:99
|
||||
ENV HOME=/root
|
||||
ENV GNOME_KEYRING_CONTROL=/tmp/keyring
|
||||
|
||||
# Expose ports
|
||||
# 5900 - VNC
|
||||
# 6080 - noVNC web interface
|
||||
# 19988 - Playwriter WebSocket relay
|
||||
EXPOSE 5900 6080 19988
|
||||
|
||||
# Health check - verify Chrome and relay are running
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD pgrep -x chrome > /dev/null && curl -s http://localhost:19988 || exit 1
|
||||
|
||||
CMD ["/app/start.sh"]
|
||||
219
playwriter-browser/README.md
Normal file
219
playwriter-browser/README.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Playwriter Browser Container
|
||||
|
||||
A persistent browser container running on the NUC for AI-driven browser automation via Playwriter MCP.
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides an always-available browser for Claude/AI agents to:
|
||||
- Navigate websites and perform web tasks
|
||||
- Fill forms, click buttons, extract data
|
||||
- Debug web applications
|
||||
- Access services that require browser interaction
|
||||
- Eliminate need for local browser resources
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ NUC Server │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ playwriter-browser container │ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ │
|
||||
│ │ │ Xvfb │ │ Chrome │ │ Playwriter │ │ │
|
||||
│ │ │ :99 │ │ +ext │ │ Relay │ │ │
|
||||
│ │ └──────────┘ └──────────┘ └───────────────┘ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ ┌──────────┐ │ ws://19988 │ │
|
||||
│ │ │ x11vnc │ │ │ │
|
||||
│ │ │ :5901 │ CDP://9222 │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌──────────┐ │ │
|
||||
│ │ │ noVNC │ │ │
|
||||
│ │ │ :6081 │ │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Web Browser DevTools MCP Playwriter MCP
|
||||
(manual) (AI agent) (AI agent)
|
||||
```
|
||||
|
||||
## Ports
|
||||
|
||||
| Port | Service | Purpose |
|
||||
|------|---------|---------|
|
||||
| 5901 | VNC | Direct VNC client access |
|
||||
| 6081 | noVNC | Web-based browser view |
|
||||
| 19988 | Playwriter | MCP WebSocket relay |
|
||||
| 9222 | CDP | Chrome DevTools Protocol |
|
||||
|
||||
## Deployment to NUC
|
||||
|
||||
### Option 1: Via Coolify
|
||||
|
||||
1. Create a new Docker Compose service in Coolify
|
||||
2. Paste the contents of `docker-compose.yml`
|
||||
3. Deploy
|
||||
|
||||
### Option 2: Direct Docker Compose
|
||||
|
||||
```bash
|
||||
# Copy files to NUC
|
||||
scp -r playwriter-browser nuc:/opt/
|
||||
|
||||
# SSH to NUC and deploy
|
||||
ssh nuc
|
||||
cd /opt/playwriter-browser
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Access noVNC at `http://192.168.1.3:6081/vnc.html`
|
||||
2. Install Playwriter extension from Chrome Web Store:
|
||||
https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe
|
||||
3. Navigate to any website (e.g., google.com)
|
||||
4. Click the Playwriter extension icon to activate (turns green)
|
||||
5. The extension stays active until container restart
|
||||
|
||||
## Auto-Activation Attempt
|
||||
|
||||
The container includes an auto-activation script that tries to click the extension icon using xdotool. However, due to security design, this may not always work. Manual activation via noVNC is the reliable method.
|
||||
|
||||
```bash
|
||||
# Run auto-activation manually
|
||||
docker exec playwriter-browser /app/auto-activate.sh
|
||||
```
|
||||
|
||||
## MCP Configuration
|
||||
|
||||
### Remote Connection (Recommended)
|
||||
|
||||
Configure Claude Code to use NUC's browser remotely:
|
||||
|
||||
```json
|
||||
{
|
||||
"playwriter": {
|
||||
"command": "npx",
|
||||
"args": ["playwriter", "--host", "ws://192.168.1.3:19988", "--token", "nuc-browser-token"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This connects to the NUC browser container, eliminating local resource usage.
|
||||
|
||||
### Keep Local Browser Option
|
||||
|
||||
If you also want local browser control:
|
||||
|
||||
```json
|
||||
{
|
||||
"playwriter-local": {
|
||||
"command": "npx",
|
||||
"args": ["playwriter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Chrome DevTools MCP (Alternative)
|
||||
|
||||
For performance analysis and debugging:
|
||||
|
||||
```json
|
||||
{
|
||||
"chrome-devtools": {
|
||||
"command": "npx",
|
||||
"args": ["chrome-devtools-mcp@latest"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PLAYWRITER_TOKEN` | `nuc-browser-token` | Auth token for remote connections |
|
||||
|
||||
## Session Persistence
|
||||
|
||||
The container persists browser state across restarts using Docker volumes:
|
||||
|
||||
| Volume | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `playwriter-chrome-profile` | `/root/.config/google-chrome` | Full Chrome profile (cookies, localStorage, passwords, bookmarks) |
|
||||
| `playwriter-browser-keyring` | `/root/.local/share/keyrings` | Encrypted credentials via gnome-keyring |
|
||||
| `playwriter-browser-sessions` | `/root/.config/google-chrome/Default/Sessions` | Browser session tabs |
|
||||
|
||||
### What's Persisted
|
||||
- **Login sessions**: Stay logged into websites (Google, GitHub, etc.)
|
||||
- **Cookies**: Session cookies, preferences
|
||||
- **Passwords**: Chrome password manager entries (encrypted via gnome-keyring)
|
||||
- **localStorage/IndexedDB**: Web app data
|
||||
- **Extensions**: Installed extensions including Playwriter
|
||||
- **Bookmarks**: Any saved bookmarks
|
||||
|
||||
### First Login via noVNC
|
||||
1. Access noVNC: `http://192.168.1.3:6081/vnc.html`
|
||||
2. Navigate to website and log in normally
|
||||
3. Allow Chrome to save password when prompted
|
||||
4. Sessions persist across container restarts
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Via Playwriter MCP
|
||||
|
||||
```javascript
|
||||
// Navigate to a page
|
||||
await page.goto('https://example.com');
|
||||
|
||||
// Get page state
|
||||
console.log(await accessibilitySnapshot({ page }));
|
||||
|
||||
// Interact with elements
|
||||
await page.locator('aria-ref=e5').click();
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/shot.png', scale: 'css' });
|
||||
```
|
||||
|
||||
### Via Chrome DevTools MCP
|
||||
|
||||
```
|
||||
"Check the LCP of https://example.com"
|
||||
"Analyze network requests on this page"
|
||||
"Find console errors"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension not activated
|
||||
Access noVNC and click the Playwriter extension icon manually.
|
||||
|
||||
### Container unhealthy
|
||||
```bash
|
||||
docker logs playwriter-browser
|
||||
docker exec playwriter-browser pgrep -la chrome
|
||||
```
|
||||
|
||||
### Reset Chrome profile (lose all logins)
|
||||
```bash
|
||||
docker compose down
|
||||
docker volume rm playwriter-chrome-profile playwriter-browser-keyring playwriter-browser-sessions
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Backup browser profile
|
||||
```bash
|
||||
# Create backup of logged-in sessions
|
||||
docker run --rm -v playwriter-chrome-profile:/data -v $(pwd):/backup alpine tar czf /backup/chrome-profile-backup.tar.gz -C /data .
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Playwriter requires explicit activation per tab (security by design)
|
||||
- Only tabs where extension is activated can be controlled
|
||||
- WebSocket relay only accepts localhost connections
|
||||
- VNC has no password (internal network only)
|
||||
64
playwriter-browser/auto-activate.sh
Normal file
64
playwriter-browser/auto-activate.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Auto-activate Playwriter extension by clicking its icon
|
||||
# This script attempts to find and click the Playwriter extension icon
|
||||
|
||||
export DISPLAY=:99
|
||||
|
||||
echo "Attempting to auto-activate Playwriter extension..."
|
||||
|
||||
# Wait for Chrome to fully load
|
||||
sleep 5
|
||||
|
||||
# Get Chrome window ID
|
||||
CHROME_WINDOW=$(xdotool search --name "Chrome" | head -1)
|
||||
|
||||
if [ -z "$CHROME_WINDOW" ]; then
|
||||
echo "Chrome window not found. Trying again..."
|
||||
sleep 5
|
||||
CHROME_WINDOW=$(xdotool search --name "Chrome" | head -1)
|
||||
fi
|
||||
|
||||
if [ -n "$CHROME_WINDOW" ]; then
|
||||
echo "Found Chrome window: $CHROME_WINDOW"
|
||||
|
||||
# Focus the Chrome window
|
||||
xdotool windowactivate --sync $CHROME_WINDOW
|
||||
sleep 1
|
||||
|
||||
# Navigate to a page first (extension won't activate on chrome:// pages)
|
||||
xdotool key ctrl+l
|
||||
sleep 0.5
|
||||
xdotool type "https://www.google.com"
|
||||
xdotool key Return
|
||||
sleep 3
|
||||
|
||||
# The extension icon is typically in the top-right corner
|
||||
# Get window geometry
|
||||
GEOMETRY=$(xdotool getwindowgeometry $CHROME_WINDOW)
|
||||
WIDTH=$(echo "$GEOMETRY" | grep "Geometry" | sed 's/.*Geometry: \([0-9]*\)x.*/\1/')
|
||||
|
||||
# Extension icons are usually ~100-200px from the right edge, ~50px from top
|
||||
# Try multiple positions since exact location varies
|
||||
echo "Attempting to click extension icon area..."
|
||||
|
||||
# Click positions to try (relative to window, near extension area)
|
||||
for OFFSET in 100 130 160 190 220; do
|
||||
X_POS=$((WIDTH - OFFSET))
|
||||
Y_POS=50
|
||||
|
||||
echo "Trying position: $X_POS, $Y_POS"
|
||||
xdotool mousemove --window $CHROME_WINDOW $X_POS $Y_POS
|
||||
sleep 0.3
|
||||
xdotool click 1
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Auto-activation attempts complete."
|
||||
echo "If extension didn't activate, please click it manually via noVNC at:"
|
||||
echo "http://localhost:6080/vnc.html"
|
||||
else
|
||||
echo "Could not find Chrome window. Please activate extension manually."
|
||||
fi
|
||||
|
||||
# Alternative: Use Chrome DevTools Protocol to check extension status
|
||||
# This would require more sophisticated scripting with CDP
|
||||
48
playwriter-browser/docker-compose.yml
Normal file
48
playwriter-browser/docker-compose.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
playwriter-browser:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: playwriter-browser
|
||||
hostname: playwriter-browser
|
||||
ports:
|
||||
- "5901:5900" # VNC (use 5901 to avoid conflicts)
|
||||
- "6081:6080" # noVNC web interface
|
||||
- "19988:19988" # Playwriter WebSocket relay
|
||||
- "9222:9222" # Chrome DevTools Protocol
|
||||
volumes:
|
||||
# Persist full Chrome profile (login sessions, cookies, localStorage, bookmarks, passwords)
|
||||
- chrome-profile:/root/.config/google-chrome
|
||||
# Persist keyrings for encrypted credentials (gnome-keyring)
|
||||
- browser-keyring:/root/.local/share/keyrings
|
||||
# Persist session data for quick restore
|
||||
- browser-sessions:/root/.config/google-chrome/Default/Sessions
|
||||
environment:
|
||||
- DISPLAY=:99
|
||||
- PLAYWRITER_TOKEN=${PLAYWRITER_TOKEN:-nuc-browser-token}
|
||||
# Enable password saving in Chrome
|
||||
- CHROME_ENABLE_PASSWORDS=true
|
||||
# Chrome requires shared memory for stability
|
||||
shm_size: 2gb
|
||||
# Chrome capabilities
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pgrep -x chrome > /dev/null || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
chrome-profile:
|
||||
name: playwriter-chrome-profile
|
||||
browser-keyring:
|
||||
name: playwriter-browser-keyring
|
||||
browser-sessions:
|
||||
name: playwriter-browser-sessions
|
||||
90
playwriter-browser/start.sh
Normal file
90
playwriter-browser/start.sh
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Starting Playwriter Browser Container ==="
|
||||
|
||||
# Start Xvfb (virtual display)
|
||||
echo "Starting Xvfb..."
|
||||
Xvfb :99 -screen 0 1920x1080x24 -ac +extension GLX +render -noreset &
|
||||
export DISPLAY=:99
|
||||
sleep 2
|
||||
|
||||
# Initialize D-Bus for gnome-keyring
|
||||
echo "Starting D-Bus session..."
|
||||
mkdir -p /run/dbus
|
||||
if [ ! -f /run/dbus/pid ]; then
|
||||
dbus-daemon --system --fork 2>/dev/null || true
|
||||
fi
|
||||
export $(dbus-launch)
|
||||
echo "D-Bus started: $DBUS_SESSION_BUS_ADDRESS"
|
||||
|
||||
# Initialize gnome-keyring (enables Chrome password saving)
|
||||
echo "Starting gnome-keyring daemon..."
|
||||
mkdir -p /root/.local/share/keyrings
|
||||
# Create default keyring with empty password for headless operation
|
||||
eval $(echo '' | gnome-keyring-daemon --unlock --start --components=secrets,pkcs11 2>/dev/null || true)
|
||||
export GNOME_KEYRING_CONTROL
|
||||
export SSH_AUTH_SOCK
|
||||
echo "Keyring initialized at: $GNOME_KEYRING_CONTROL"
|
||||
|
||||
# Start window manager (needed for proper window handling)
|
||||
echo "Starting Openbox window manager..."
|
||||
openbox &
|
||||
sleep 1
|
||||
|
||||
# Start VNC server
|
||||
echo "Starting VNC server on port 5900..."
|
||||
x11vnc -display :99 -forever -shared -rfbport 5900 -nopw -bg
|
||||
sleep 1
|
||||
|
||||
# Start noVNC web interface
|
||||
echo "Starting noVNC web interface on port 6080..."
|
||||
websockify --web=/usr/share/novnc/ 6080 localhost:5900 &
|
||||
sleep 1
|
||||
|
||||
# Start Playwriter relay server in background (binds to all interfaces for remote access)
|
||||
echo "Starting Playwriter relay server on port 19988..."
|
||||
playwriter serve --host 0.0.0.0 --token "${PLAYWRITER_TOKEN:-nuc-browser-token}" &
|
||||
RELAY_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Start Chrome with remote debugging enabled
|
||||
# The extension needs to be installed from Chrome Web Store on first run
|
||||
echo "Starting Google Chrome..."
|
||||
google-chrome-stable \
|
||||
--no-sandbox \
|
||||
--disable-gpu \
|
||||
--disable-dev-shm-usage \
|
||||
--remote-debugging-port=9222 \
|
||||
--remote-debugging-address=0.0.0.0 \
|
||||
--user-data-dir=/root/.config/google-chrome \
|
||||
--start-maximized \
|
||||
--no-first-run \
|
||||
--disable-default-apps \
|
||||
--disable-background-networking \
|
||||
--disable-sync \
|
||||
--disable-translate \
|
||||
--password-store=gnome \
|
||||
--enable-features=PasswordImport \
|
||||
"chrome://extensions" &
|
||||
CHROME_PID=$!
|
||||
|
||||
echo ""
|
||||
echo "=== Playwriter Browser Ready ==="
|
||||
echo "VNC: vnc://localhost:5900"
|
||||
echo "noVNC Web: http://localhost:6080/vnc.html"
|
||||
echo "Playwriter Relay: ws://localhost:19988"
|
||||
echo "Chrome DevTools: http://localhost:9222"
|
||||
echo ""
|
||||
echo "IMPORTANT: On first run, install Playwriter extension from Chrome Web Store:"
|
||||
echo "https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe"
|
||||
echo ""
|
||||
echo "Then run: /app/auto-activate.sh to try auto-activating the extension"
|
||||
echo ""
|
||||
|
||||
# Wait for auto-activation script if extension is installed
|
||||
sleep 10
|
||||
/app/auto-activate.sh &
|
||||
|
||||
# Keep container running
|
||||
wait $CHROME_PID
|
||||
157
services.yaml
Normal file
157
services.yaml
Normal file
@@ -0,0 +1,157 @@
|
||||
# NUC Server Services Inventory
|
||||
# Last updated: 2026-02-01
|
||||
|
||||
server:
|
||||
hostname: nuc
|
||||
ip: 192.168.1.3
|
||||
user: alezmad
|
||||
ssh_key: ~/.ssh/id_ed25519_nuc
|
||||
|
||||
services:
|
||||
# ============ Core Infrastructure ============
|
||||
coolify:
|
||||
port: 8000
|
||||
url: http://192.168.1.3:8000
|
||||
description: Self-hosted PaaS - manages all other services
|
||||
container: coolify
|
||||
type: standalone
|
||||
|
||||
traefik:
|
||||
ports: [80, 443, 8080]
|
||||
description: Reverse proxy managed by Coolify
|
||||
container: coolify-proxy
|
||||
|
||||
# ============ Development ============
|
||||
gitea:
|
||||
port: 3030
|
||||
ssh_port: 22222
|
||||
url: http://192.168.1.3:3030
|
||||
description: Git hosting + OAuth2 provider
|
||||
container: gitea-*
|
||||
coolify_service_id: unknown
|
||||
oauth2:
|
||||
outline_client_id: 249a3a1d-92d4-47d8-b4a9-81c64e1da6ab
|
||||
|
||||
adminer:
|
||||
port: 8088
|
||||
url: http://192.168.1.3:8088
|
||||
description: Database administration UI
|
||||
container: adminer
|
||||
type: standalone
|
||||
|
||||
# ============ Documentation ============
|
||||
outline:
|
||||
port: 3080
|
||||
url: http://192.168.1.3:3080
|
||||
description: Team wiki and documentation
|
||||
container: outline-pccg80wks4c084008owokkkg
|
||||
coolify_service_id: 9
|
||||
proxy: nginx (strips HSTS)
|
||||
auth: OIDC via Gitea
|
||||
database: postgres-pccg80wks4c084008owokkkg
|
||||
cache: redis-pccg80wks4c084008owokkkg
|
||||
|
||||
# ============ Dashboard ============
|
||||
homepage:
|
||||
port: 3000
|
||||
url: http://192.168.1.3:3000
|
||||
description: Service dashboard
|
||||
container: homepage-eo0g84scsss4osk0skk040ck
|
||||
config_path: /opt/homepage/config/
|
||||
|
||||
# ============ Automation ============
|
||||
n8n:
|
||||
port: 5678
|
||||
url: http://192.168.1.3:5678
|
||||
description: Workflow automation
|
||||
container: n8n-uk0o04o0g84s4sc80kkoooc0
|
||||
runners: task-runners-uk0o04o0g84s4sc80kkoooc0
|
||||
|
||||
# ============ Security ============
|
||||
vaultwarden:
|
||||
port: 8222
|
||||
url: http://192.168.1.3:8222
|
||||
description: Bitwarden-compatible password manager
|
||||
container: vaultwarden-h40w0ss4kgs0c8cgc0sc8k48
|
||||
|
||||
authentik:
|
||||
port: 9090
|
||||
url: http://192.168.1.3:9090
|
||||
description: Identity provider (partially configured)
|
||||
containers:
|
||||
- authentik-server-e8owcw0s4wcswc4w4css0sws
|
||||
- authentik-worker-e8owcw0s4wcswc4w4css0sws
|
||||
database: postgresql-e8owcw0s4wcswc4w4css0sws
|
||||
|
||||
# ============ Storage ============
|
||||
minio:
|
||||
console_port: 9001
|
||||
api_port: 9000
|
||||
console_url: http://192.168.1.3:9001
|
||||
api_url: http://192.168.1.3:9000
|
||||
description: S3-compatible object storage
|
||||
container: minio-dg4wkgg8skcssww0040sgk80
|
||||
|
||||
filebrowser:
|
||||
port: 8085
|
||||
url: http://192.168.1.3:8085
|
||||
description: Web-based file manager
|
||||
container: filebrowser-o4swwwsowwg88coo0ws4cg48
|
||||
|
||||
kopia:
|
||||
port: 51515
|
||||
url: http://192.168.1.3:51515
|
||||
description: Backup management
|
||||
container: kopia
|
||||
type: standalone
|
||||
|
||||
# ============ Notifications ============
|
||||
ntfy:
|
||||
port: 8333
|
||||
url: http://192.168.1.3:8333
|
||||
description: Push notification service
|
||||
container: ntfy-xgkkg8gkgg048g8gkc8ck4os
|
||||
|
||||
# ============ Monitoring ============
|
||||
uptime_kuma:
|
||||
port: 3001
|
||||
url: http://192.168.1.3:3001
|
||||
description: Service uptime monitoring
|
||||
container: uptime-kuma
|
||||
type: standalone
|
||||
|
||||
dozzle:
|
||||
port: 9999
|
||||
url: http://192.168.1.3:9999
|
||||
description: Docker log viewer
|
||||
container: dozzle
|
||||
type: standalone
|
||||
|
||||
# Port forwarding containers (for Coolify internal services)
|
||||
port_forwarders:
|
||||
- name: port-fwd-outline
|
||||
image: nginx:alpine
|
||||
external_port: 3080
|
||||
internal_target: outline-pccg80wks4c084008owokkkg:3000
|
||||
note: Strips HSTS headers
|
||||
|
||||
- name: port-fwd-homepage
|
||||
image: alpine/socat
|
||||
external_port: 3000
|
||||
|
||||
- name: port-fwd-gitea
|
||||
image: alpine/socat
|
||||
external_port: 3030
|
||||
|
||||
- name: port-fwd-minio-console
|
||||
image: alpine/socat
|
||||
external_port: 9001
|
||||
|
||||
- name: port-fwd-minio-api
|
||||
image: alpine/socat
|
||||
external_port: 9000
|
||||
|
||||
- name: port-fwd-authentik
|
||||
image: alpine/socat
|
||||
external_port: 9090
|
||||
internal_port: 9000
|
||||
23
setup-ssh.sh
Executable file
23
setup-ssh.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# Setup SSH config for NUC server
|
||||
|
||||
SSH_CONFIG="$HOME/.ssh/config"
|
||||
NUC_CONFIG="Host nuc
|
||||
HostName 192.168.1.3
|
||||
User alezmad
|
||||
IdentityFile ~/.ssh/id_ed25519_nuc"
|
||||
|
||||
# Check if nuc config already exists
|
||||
if grep -q "Host nuc" "$SSH_CONFIG" 2>/dev/null; then
|
||||
echo "NUC SSH config already exists in $SSH_CONFIG"
|
||||
else
|
||||
echo "Adding NUC config to $SSH_CONFIG"
|
||||
echo "" >> "$SSH_CONFIG"
|
||||
echo "$NUC_CONFIG" >> "$SSH_CONFIG"
|
||||
echo "Done! You can now connect with: ssh nuc"
|
||||
fi
|
||||
|
||||
# Test connection
|
||||
echo ""
|
||||
echo "Testing connection..."
|
||||
ssh -o ConnectTimeout=5 nuc "echo 'Successfully connected to NUC!'" 2>/dev/null || echo "Connection failed. Make sure the SSH key exists at ~/.ssh/id_ed25519_nuc"
|
||||
Reference in New Issue
Block a user