commit 390eda159532e30558888adb35674a189b6d80f8 Author: Alejandro Gutiérrez <35082514+alezmad@users.noreply.github.com> Date: Sun Feb 1 20:49:20 2026 +0000 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 diff --git a/.artifacts/2026-02-01_13-20_task-runners-healthcheck-fix.md b/.artifacts/2026-02-01_13-20_task-runners-healthcheck-fix.md new file mode 100644 index 0000000..b216742 --- /dev/null +++ b/.artifacts/2026-02-01_13-20_task-runners-healthcheck-fix.md @@ -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 diff --git a/.artifacts/2026-02-01_16-45_liquidgym-mysql-migration.md b/.artifacts/2026-02-01_16-45_liquidgym-mysql-migration.md new file mode 100644 index 0000000..ab8a406 --- /dev/null +++ b/.artifacts/2026-02-01_16-45_liquidgym-mysql-migration.md @@ -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 diff --git a/.artifacts/2026-02-01_17-15_liquidgym-database-engines.md b/.artifacts/2026-02-01_17-15_liquidgym-database-engines.md new file mode 100644 index 0000000..96b5833 --- /dev/null +++ b/.artifacts/2026-02-01_17-15_liquidgym-database-engines.md @@ -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/` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef3b589 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..93171bc --- /dev/null +++ b/.mcp.json @@ -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"] + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f99b32f --- /dev/null +++ b/CLAUDE.md @@ -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="", ...)` +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-- +chrome-devtools-- + +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="") +mcp__chrome-devtools__fill(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=\"\"" + +# 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 2>&1 | tail -50" + +# Restart a container +ssh nuc "docker restart " + +# Execute command in container +ssh nuc "docker exec " +``` + +## 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- --network -p : alpine/socat tcp-listen:,fork,reuseaddr tcp-connect::" +``` + +## 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 ` + +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 --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 " +``` + +### Common Issues + +1. **Containers stuck in "Created" state**: After Coolify restart/redeploy, containers may not auto-start + ```bash + ssh nuc "docker start " + ``` + +2. **Service shows "running:unknown"**: No healthcheck configured. Add one via Coolify service update: + ```yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:"] + 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 " + # 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 wget -qO- http://127.0.0.1:/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 :/data keinos/sqlite3 sqlite3 /data/database.sqlite \"\"" + ``` + +### 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="") + +# Get service details (including docker_compose) +mcp__coolify__get_service(uuid="") + +# Update service config (e.g., add healthcheck) +mcp__coolify__service(action="update", uuid="", docker_compose_raw="") + +# Delete stale database +mcp__coolify__database(action="delete", 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 ''" +``` + +**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='' +uci set firewall.@redirect[-1].src='wan' +uci set firewall.@redirect[-1].src_dport='' +uci set firewall.@redirect[-1].dest='lan' +uci set firewall.@redirect[-1].dest_ip='' +uci set firewall.@redirect[-1].dest_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-' +uci set firewall.@rule[-1].src='wan' +uci set firewall.@rule[-1].dest_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='' +uci set dhcp.@host[-1].mac='' +uci set dhcp.@host[-1].ip='' +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='' +uci set dhcp.@domain[-1].ip='' +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 " + +# 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="") +mcp__chrome-devtools__fill(uid="", value="") +``` + +## 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_.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 +# + +**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) diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md new file mode 100644 index 0000000..f4536a4 --- /dev/null +++ b/NEXT_STEPS.md @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..329a6e0 --- /dev/null +++ b/README.md @@ -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 diff --git a/commands.md b/commands.md new file mode 100644 index 0000000..70fb059 --- /dev/null +++ b/commands.md @@ -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" +``` diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..6645b97 --- /dev/null +++ b/docs/architecture.md @@ -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). diff --git a/docs/backup-strategy.md b/docs/backup-strategy.md new file mode 100644 index 0000000..a843792 --- /dev/null +++ b/docs/backup-strategy.md @@ -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` diff --git a/docs/domain-check-guide.md b/docs/domain-check-guide.md new file mode 100644 index 0000000..c109c13 --- /dev/null +++ b/docs/domain-check-guide.md @@ -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. diff --git a/docs/hard-disk-change.md b/docs/hard-disk-change.md new file mode 100644 index 0000000..f09dd6a --- /dev/null +++ b/docs/hard-disk-change.md @@ -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 diff --git a/docs/lan-dns-setup.md b/docs/lan-dns-setup.md new file mode 100644 index 0000000..1f5460a --- /dev/null +++ b/docs/lan-dns-setup.md @@ -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 diff --git a/docs/mcp-browser-setup.md b/docs/mcp-browser-setup.md new file mode 100644 index 0000000..3159881 --- /dev/null +++ b/docs/mcp-browser-setup.md @@ -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 diff --git a/docs/mcp-research-guide.md b/docs/mcp-research-guide.md new file mode 100644 index 0000000..6a7f661 --- /dev/null +++ b/docs/mcp-research-guide.md @@ -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 diff --git a/docs/n8n-mcp-setup.md b/docs/n8n-mcp-setup.md new file mode 100644 index 0000000..f30c831 --- /dev/null +++ b/docs/n8n-mcp-setup.md @@ -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) diff --git a/docs/scripts/domain-check.sh b/docs/scripts/domain-check.sh new file mode 100755 index 0000000..ee13e5f --- /dev/null +++ b/docs/scripts/domain-check.sh @@ -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 "╚════════════════════════════════════════════════════════════╝" diff --git a/playwriter-browser/Dockerfile b/playwriter-browser/Dockerfile new file mode 100644 index 0000000..c3ad219 --- /dev/null +++ b/playwriter-browser/Dockerfile @@ -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"] diff --git a/playwriter-browser/README.md b/playwriter-browser/README.md new file mode 100644 index 0000000..e2a9c33 --- /dev/null +++ b/playwriter-browser/README.md @@ -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) diff --git a/playwriter-browser/auto-activate.sh b/playwriter-browser/auto-activate.sh new file mode 100644 index 0000000..1f0a5f5 --- /dev/null +++ b/playwriter-browser/auto-activate.sh @@ -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 diff --git a/playwriter-browser/docker-compose.yml b/playwriter-browser/docker-compose.yml new file mode 100644 index 0000000..ba37ae1 --- /dev/null +++ b/playwriter-browser/docker-compose.yml @@ -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 diff --git a/playwriter-browser/start.sh b/playwriter-browser/start.sh new file mode 100644 index 0000000..01c68b4 --- /dev/null +++ b/playwriter-browser/start.sh @@ -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 diff --git a/services.yaml b/services.yaml new file mode 100644 index 0000000..5610071 --- /dev/null +++ b/services.yaml @@ -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 diff --git a/setup-ssh.sh b/setup-ssh.sh new file mode 100755 index 0000000..75e73d1 --- /dev/null +++ b/setup-ssh.sh @@ -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"