diff --git a/.artifacts/migration-candidates.md b/.artifacts/migration-candidates.md new file mode 100644 index 0000000..a953df6 --- /dev/null +++ b/.artifacts/migration-candidates.md @@ -0,0 +1,64 @@ +# Migration Candidates + +**Date:** 2026-02-21 +**Context:** Docker images on Mac evaluated for NUC migration or cleanup + +## 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/CLAUDE.md b/CLAUDE.md index 1415ac5..885b603 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,39 +14,42 @@ ssh nuc ## DNS & Tailscale Setup -### DNS Strategy: Local IP + Tailscale Subnet Routing +### DNS Strategy: Dual Resolution (Router DNS + Mac /etc/hosts) -All `.nuc.lan` domains resolve to the **local LAN IP** (`192.168.1.3`). This allows any device on the LAN to access services without Tailscale. +**On the NUC's LAN (OpenWrt router):** All `.nuc.lan` domains resolve to `192.168.1.3` via dnsmasq. -**Remote access (Tailscale)** works via two mechanisms: -1. **Split DNS**: Tailscale forwards `*.nuc.lan` queries to the OpenWrt router (`192.168.1.1`) through the tunnel -2. **Subnet routing**: The NUC advertises `192.168.1.0/24` as a Tailscale subnet route, so `192.168.1.3` is reachable from any Tailscale device remotely +**On the Mac:** `/etc/hosts` maps `.nuc.lan` domains to Tailscale IP `100.113.153.45` (Mac and NUC are on different physical networks that both use `192.168.1.0/24`). -### Configured Domains (OpenWrt Router DNS) +**When adding a new `.nuc.lan` domain, update BOTH:** +1. OpenWrt router DNS (dnsmasq via `uci`) +2. Mac `/etc/hosts` → `100.113.153.45 newservice.nuc.lan` -| Domain | Resolves To | Service | -|--------|-------------|---------| -| `nuc.lan` | `192.168.1.3` | NUC Portal | -| `nuc.local` | `192.168.1.3` | NUC Portal | -| `coolify.nuc.lan` | `192.168.1.3` | Coolify | -| `gitea.nuc.lan` | `192.168.1.3` | Gitea | -| `outline.nuc.lan` | `192.168.1.3` | Outline Wiki | -| `files.nuc.lan` | `192.168.1.3` | FileBrowser | -| `mail.nuc.lan` | `192.168.1.3` | Snappymail | -| `vault.nuc.lan` | `192.168.1.3` | Vaultwarden | -| `homepage.nuc.lan` | `192.168.1.3` | NUC Portal | -| `brand.nuc.lan` | `192.168.1.3` | Whyrating Brand | -| `templates.nuc.lan` | `192.168.1.3` | Whyrating Templates | -| `whyrating.nuc.lan` | `192.168.1.3` | Whyrating Hub | -| `whyops.nuc.lan` | `192.168.1.3` | WhyOps (scraping, pipelines, testing) | -| `arrio.nuc.lan` | `192.168.1.3` | Arrio (hotel check-in) | +### Configured Domains -### Traefik Routing (Dynamic Config) +| Domain | Service | +|--------|---------| +| `nuc.lan` / `nuc.local` | NUC Portal | +| `coolify.nuc.lan` | Coolify | +| `gitea.nuc.lan` | Gitea | +| `outline.nuc.lan` | Outline Wiki | +| `files.nuc.lan` | FileBrowser | +| `mail.nuc.lan` | Snappymail | +| `vault.nuc.lan` | Vaultwarden | +| `homepage.nuc.lan` | NUC Portal | +| `brand.nuc.lan` | Whyrating Brand | +| `templates.nuc.lan` | Whyrating Templates | +| `whyrating.nuc.lan` | Whyrating Hub | +| `whyops.nuc.lan` | WhyOps | +| `arrio.nuc.lan` | Arrio | +| `whatsapp.nuc.lan` | WhatsApp MCP | -Traefik routes domain-based requests to the correct backend. Config location: `/data/coolify/proxy/dynamic/nuc-services.yaml` +All resolve to `192.168.1.3`. + +### Traefik Routing + +Config location: `/data/coolify/proxy/dynamic/nuc-services.yaml` ```yaml -# Routes for port-based services via domain names http: routers: coolify: @@ -62,7 +65,7 @@ http: ### Adding a New Domain ```bash -# 1. Add DNS entry on router (via NUC jump host) +# 1. Add DNS entry on router ssh nuc "ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 ' uci add dhcp domain uci set dhcp.@domain[-1].name=\"newservice.nuc.lan\" @@ -71,31 +74,31 @@ uci commit dhcp /etc/init.d/dnsmasq restart '" -# 2. Add Traefik route (if needed for port-based service) +# 2. Add to Mac /etc/hosts +echo "924718" | sudo -S sh -c 'echo "100.113.153.45 newservice.nuc.lan" >> /etc/hosts' +echo "924718" | sudo -S dscacheutil -flushcache && sudo killall -HUP mDNSResponder + +# 3. Add Traefik route (if needed for port-based service) # Edit /data/coolify/proxy/dynamic/nuc-services.yaml ``` ### Always-On Tailscale -**Keep Tailscale running** - it's designed to be always-on: -- When on home network: Uses direct connection (no relay, same performance as local) -- When remote: Routes through Tailscale mesh -- Minimal resource usage (~0% CPU when idle) +**Keep Tailscale running** - direct connection on home network, mesh routing when remote, ~0% CPU idle. ## Service Management ### Coolify (Primary Service Manager) -All services are managed through Coolify at `http://coolify.nuc.lan` (or `http://100.113.153.45:8000`) +All services managed through Coolify at `http://coolify.nuc.lan` (or `http://100.113.153.45:8000`). +**Prefer Coolify MCP** (`mcp__coolify__*`) over SSH commands. -**Prefer using Coolify MCP** (`mcp__coolify__*`) for service management - it's faster and more reliable than SSH commands. - -### ⚠️ STRICT RULE: Container Deployment Priority +### 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) +3. **Last resort:** Direct Docker commands via SSH ```python # Step 1: Try native Coolify service type @@ -121,282 +124,50 @@ volumes: """, 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 -### ⚠️ STRICT RULE: Browser MCP for Manual Configurations +**ALWAYS use Browser MCP instead of asking the user to do it manually.** -**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) +**Priority order:** +1. `mcp__playwriter-nuc-01__*` - Remote NUC browser (preferred) 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") -``` +3. `mcp__playwriter-local__*` - Local browser (fallback) **NEVER say "please go to X and do Y manually" - use browser MCP instead.** -### ⚠️ STRICT RULE: Parallel Subtasks for Multiple Operations +### 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.** +**ALWAYS use parallel Task agents for independent 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! +Task(subagent_type="general-purpose", prompt="Configure Tailscale...", description="Setup Tailscale") +Task(subagent_type="general-purpose", prompt="Install WireGuard...", description="Setup WireGuard") +# All 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 +### STRICT RULE: MCP Research Protocol -**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 +**When asked to find/recommend new MCPs**, read `docs/mcp-research-guide.md` first. +Quick workflow: Search `mcp.so`, `smithery.ai`, `mcpservers.org` → Verify on GitHub (50+ stars, <6 months active) → Evaluate. ### Available MCPs for NUC Management | MCP | Purpose | |-----|---------| | `mcp__coolify__*` | Service management, deployments, env vars | -| `mcp__stalwart-mail__*` | Email server management (users, domains, queue) | -| `mcp__email-client__*` | Read/send emails via IMAP/SMTP (see below) | +| `mcp__stalwart-mail__*` | Email server management | +| `mcp__email-client__*` | Read/send emails via IMAP/SMTP | | `mcp__nocodb__*` | Database operations, table management | | `mcp__ssh-manager__*` | Direct SSH commands, file transfers | -| `mcp__n8n__*` | Workflow automation (if configured) | -| `mcp__playwriter__*` | Browser automation fallback (see below) | -| `mcp__deepgram__*` | Audio transcription (STT) and text-to-speech (TTS) | +| `mcp__n8n__*` | Workflow automation | +| `mcp__playwriter__*` | Browser automation | +| `mcp__deepgram__*` | Audio transcription (STT) and TTS | -### Stalwart Mail MCP (Quick Guide) +> Full MCP config details (Stalwart, Email, Remote MCPs, Browser): `docs/mcp-configs.md` -**Location:** `~/mcp-servers/stalwart-mail/` +### Coolify CLI Commands -Manage the self-hosted Stalwart mail server via natural language. - -**Available Tools:** - -| Category | Tools | -|----------|-------| -| **Users** | `list_users`, `get_user`, `create_user`, `update_user_password`, `delete_user`, `add_email_alias` | -| **Domains** | `create_domain`, `generate_dkim` | -| **Queue** | `list_queue`, `get_queue_status`, `delete_queued_message`, `retry_queued_message` | -| **Monitoring** | `get_metrics`, `get_dmarc_reports`, `get_server_logs` | -| **DNS** | `check_dns_records`, `troubleshoot_delivery` | -| **Spam** | `train_spam`, `train_ham`, `update_spam_filter` | - -**Usage Examples:** -``` -"List all mail users" -"Create user sales with email sales@whyrating.com and password Secret123" -"Check the mail queue" -"Verify DNS records for whyrating.com" -"Show server metrics" -"Delete user john" -``` - -**Direct API Test (if MCP not responding):** -```bash -curl -s -u "admin:QfKYjCJdxu" "http://192.168.1.3:8081/api/principal" | jq . -``` - -**Reconfigure MCP:** -```bash -claude mcp remove stalwart-mail -claude mcp add stalwart-mail \ - -e STALWART_URL=http://192.168.1.3:8081 \ - -e STALWART_USER=admin \ - -e STALWART_PASS=QfKYjCJdxu \ - --scope user \ - -- ~/mcp-servers/stalwart-mail/.venv/bin/python ~/mcp-servers/stalwart-mail/server.py -``` - -**⚠️ SMTP Authentication Requirements:** -1. **Password format:** Must be SHA-512 hashed (not plaintext). When creating users via API: - ```python - import crypt - hashed = crypt.crypt('password', crypt.mksalt(crypt.METHOD_SHA512)) - # Use hashed value in 'secrets' field - ``` -2. **SMTP login:** Use username only (e.g., `info`), NOT full email (`info@whyrating.com`) -3. **Port 465 (SMTPS):** Supports PLAIN/LOGIN auth with implicit TLS -4. **Port 587 (Submission):** Requires STARTTLS, only OAuth supported without TLS - -**Send email via Python (from NUC):** -```python -import smtplib, ssl -from email.mime.text import MIMEText - -context = ssl.create_default_context() -context.check_hostname = False -context.verify_mode = ssl.CERT_NONE - -with smtplib.SMTP_SSL('localhost', 465, context=context) as server: - server.login('info', 'whyrating2026') # Username only! - server.sendmail('info@whyrating.com', 'recipient@example.com', msg.as_string()) -``` - -### Email Client MCP (Read/Send Emails) - -**Package:** [mcp-email-server](https://github.com/ai-zerolab/mcp-email-server) - -Read and send emails via IMAP/SMTP directly from Claude. - -**Configured for:** `info@whyrating.com` on Stalwart - -**Usage Examples:** -``` -"Check my inbox" -"Read the latest email" -"Send an email to john@example.com with subject Hello" -"Search emails from support@" -"List email folders" -``` - -**Reconfigure:** -```bash -claude mcp remove email-client -claude mcp add email-client \ - -e MCP_EMAIL_SERVER_EMAIL_ADDRESS=info@whyrating.com \ - -e MCP_EMAIL_SERVER_PASSWORD=whyrating2026 \ - -e MCP_EMAIL_SERVER_IMAP_HOST=192.168.1.3 \ - -e MCP_EMAIL_SERVER_IMAP_PORT=143 \ - -e MCP_EMAIL_SERVER_SMTP_HOST=192.168.1.3 \ - -e MCP_EMAIL_SERVER_SMTP_PORT=587 \ - -e MCP_EMAIL_SERVER_SMTP_VERIFY_SSL=false \ - -e MCP_EMAIL_SERVER_ENABLE_ATTACHMENT_DOWNLOAD=true \ - --scope user \ - -- uvx mcp-email-server@latest stdio -``` - -### Adding Remote MCP Servers (HTTP Transport) - -**Use `claude mcp add --transport http` for remote MCP endpoints** - this is the recommended method for services with native MCP support. - -```bash -# Basic syntax -claude mcp add --transport http --scope user --header "
" - -# Example: NocoDB MCP (globally available) -claude mcp add --transport http nocodb http://192.168.1.3:8084/mcp/ncnyir1cy6n9bf5p \ - --scope user \ - --header "xc-mcp-token: qjjAXRxuYzRtEn-cA4lbPFi5km_pojTX" -``` - -**Scope options:** -- `--scope user` - Available across all projects (stored in `~/.claude.json`) -- `--scope local` - Current project only (default) -- `--scope project` - Shared via `.mcp.json` (committed to repo) - -**Why CLI over JSON config:** -- JSON config with `mcp-remote` often fails to load tools -- CLI `--transport http` handles HTTP endpoints natively -- No need for `--allow-http` flag or other workarounds - -**Managing MCP servers:** -```bash -claude mcp list # List all configured servers -claude mcp get nocodb # Get details for specific server -claude mcp remove nocodb # Remove a server -/mcp # Check status in Claude Code -``` - -### ⚠️ 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=\"\"" @@ -418,25 +189,27 @@ use App\Models\EnvironmentVariable; \"" ``` +### Coolify MCP Quick Reference + +```python +mcp__coolify__get_infrastructure_overview() +mcp__coolify__control(resource="service", action="start|stop|restart", uuid="") +mcp__coolify__get_service(uuid="") +mcp__coolify__service(action="update", uuid="", docker_compose_raw="") +mcp__coolify__database(action="delete", uuid="", delete_volumes=True) +``` + ### 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 -**Preferred access via domain names** (works from anywhere via Tailscale): - | Service | Domain | Port-based URL | Container | |---------|--------|----------------|-----------| | NUC Portal | `http://nuc.lan` | - | nuc-portal-* | @@ -461,13 +234,13 @@ ssh nuc "docker exec " | WhyOps | `http://whyops.nuc.lan` | `http://100.113.153.45:3002` | whyrating-dashboard | | Palmr | `https://alezmad-nuc.tail58f5ad.ts.net:8443` | `http://100.113.153.45:3334` | palmr | | Arrio | `http://arrio.nuc.lan` | `http://100.113.153.45:3335` | web-tgksg0s8gocko4csggs0808c | +| WhatsApp MCP | `http://whatsapp.nuc.lan` | `http://100.113.153.45:3100` | whatsapp-mcp | | Deepgram MCP | - | `http://100.113.153.45:8009` | deepgram-mcp | **Note:** Use Tailscale IP (`100.113.153.45`) instead of `192.168.1.3` to avoid subnet conflicts when remote. ## 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::" @@ -475,41 +248,29 @@ ssh nuc "docker run -d --name port-fwd- --network -p ## Configuration Files -**Homepage Config:** -- Location: `/opt/homepage/config/` -- Services: `/opt/homepage/config/services.yaml` - -**Coolify Data:** -- Location: `/data/coolify/` +- **Homepage Config:** `/opt/homepage/config/` (services: `services.yaml`) +- **Coolify Data:** `/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}}'` +2. **Environment Variables in Coolify**: Encrypted with Laravel. Use `encrypt()` when updating. +3. **HSTS Issues**: Use nginx proxy with `proxy_hide_header Strict-Transport-Security;` to strip. +4. **Network Discovery**: `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: +**Always verify Coolify status with Docker** - Coolify's status can lag: ```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: +1. **Containers stuck in "Created"**: `ssh nuc "docker start "` +2. **Service shows "running:unknown"**: No healthcheck. Add via Coolify: ```yaml healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:"] @@ -518,94 +279,36 @@ ssh nuc "docker ps -a --format 'table {{.Names}}\t{{.Status}}' | grep " 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. **localhost resolves to IPv6 on NUC**: Docker containers bind to IPv4 only. Always use `127.0.0.1` instead of `localhost` in Tailscale Funnel/Serve targets, curl commands inside SSH, etc. - -8. **Tailscale Funnel port not working externally**: Funnel ONLY supports ports **443, 8443, and 10000**. Other ports will show as "Funnel on" in status but won't route internet traffic. - -9. **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) -``` +3. **Service dependencies not starting**: Check dependency containers are healthy first. +4. **Stale Coolify entries**: Verify container exists with `docker ps -a | grep `, then delete if missing. +5. **Embedded vs Standalone databases**: Services like Outline have their own PostgreSQL containers bundled in compose. +6. **Wrong healthcheck endpoint**: Some use `/healthz`. Verify: `docker exec wget -qO- http://127.0.0.1:/healthz` +7. **localhost resolves to IPv6 on NUC**: Always use `127.0.0.1` instead of `localhost` in Funnel/Serve targets. +8. **Funnel port not working externally**: Only ports **443, 8443, 10000** work for Funnel. +9. **API keys without UI**: Insert directly into SQLite: `docker run --rm -v :/data keinos/sqlite3 sqlite3 /data/database.sqlite ""` +10. **Can't reach NUC on LAN**: Mac and NUC on different networks both using `192.168.1.0/24`. Use Tailscale IP (`100.113.153.45`), never `192.168.1.3` from Mac. ## OpenWrt Router -The network is managed by an OpenWrt router at `192.168.1.1`. +Router at `192.168.1.1` — OpenWrt 23.05.0, ARM Cortex-A9. + +**Priority Order:** SSH > OpenWrt MCP > Chrome DevTools > Manual UI ### SSH Access ```bash -# Connect to router ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 - -# Or via NUC as jump host +# Or via NUC 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` +- HTTP API: `http://192.168.1.1:8090` | Token: `openwrt-mcp-secret-2026` +- Control: `ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "/etc/init.d/mcp-server start|stop|restart"` +### Port Forwarding ```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='' @@ -621,12 +324,8 @@ uci commit firewall " ``` -**Firewall Rules:** +### 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-' @@ -639,14 +338,8 @@ uci commit firewall " ``` -**DNS/DHCP (dnsmasq):** +### 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 @@ -667,133 +360,39 @@ uci commit dhcp " ``` -**Network Diagnostics:** +### 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" +ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "ifstatus wan" # WAN status +ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "cat /tmp/dhcp.leases" # Connected clients +ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "ip route" # Routing table +ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "logread | tail -50" # System logs +ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "opkg update && opkg install " # Packages ``` -**Package Management (opkg):** -```bash -# Update package lists -ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "opkg update" +## Credentials & Authentication -# Install package -ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 "opkg install " +### CloudBeaver +| **URL** | `http://192.168.1.3:8978` | **Admin** | `cbadmin` / `CloudBeaver2026!` | **UUID** | `joo4g4k0w08k8kcosgsgswc0` | -# 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` - -## ⚠️ Critical Credentials & Access - -### CloudBeaver (Database Manager) - -| Property | Value | -|----------|-------| -| **URL** | `http://192.168.1.3:8978` | -| **Admin User** | `cbadmin` | -| **Admin Password** | `CloudBeaver2026!` | -| **Service UUID** | `joo4g4k0w08k8kcosgsgswc0` | - -**Pre-configured connections:** 9 databases across 3 folders. Turbostarter DB is now in service `v4gogwwc8wkk4888ksscc4k4` (container: `db-v4gogwwc8wkk4888ksscc4k4`). -Connected to 7 Docker networks for direct container-to-container access. - -### Vaultwarden (Password Manager) - -**⚠️ CRITICAL: Vaultwarden REQUIRES HTTPS** - The Web Crypto API needs a secure context for client-side encryption. HTTP access will NOT work (blank page/loading forever). - -| Property | Value | -|----------|-------| -| **HTTPS URL** | `https://nuc-tailscale.tail58f5ad.ts.net:8443` | -| **HTTP URL** | `http://192.168.1.3:8222` (won't load - HTTPS required) | -| **Admin Email** | `admin@nuc.lan` | -| **Admin Password** | `NucVault2026!Secure` | - -**Access via Tailscale Funnel:** -```bash -# Vaultwarden is exposed on port 8443 via Tailscale Funnel -open "https://nuc-tailscale.tail58f5ad.ts.net:8443" -``` +### Vaultwarden (REQUIRES HTTPS) +| **HTTPS URL** | `https://nuc-tailscale.tail58f5ad.ts.net:8443` | **Admin** | `admin@nuc.lan` / `NucVault2026!Secure` | ### Stalwart Mail Server +| **Admin URL** | `http://192.168.1.3:8081` | **Creds** | `admin` / `QfKYjCJdxu` | **UUID** | `kw00kok0w0s8gcok008gk04k` | -| Property | Value | -|----------|-------| -| **Admin URL** | `http://192.168.1.3:8081` | -| **Username** | `admin` | -| **Password** | `QfKYjCJdxu` | -| **Webmail (Snappymail)** | `http://192.168.1.3:8082` | -| **Service UUID** | `kw00kok0w0s8gcok008gk04k` | -| **MCP Server** | `mcp__stalwart-mail__*` (see Quick Guide above) | +**Mail Users:** `info@whyrating.com` (username: `info`, password: `whyrating2026`) +**DNS:** SPF, DKIM (Ed25519 + RSA), DMARC, MX configured for `whyrating.com` -**Mail Users:** -| Email | Username | Password | -|-------|----------|----------| -| `info@whyrating.com` | `info` | `whyrating2026` | +### Outline OIDC (via Gitea) +- Client ID: `249a3a1d-92d4-47d8-b4a9-81c64e1da6ab` +- Auth/Token/Userinfo: `http://192.168.1.3:3030/login/oauth/authorize|access_token|userinfo` -**DNS Records Configured:** SPF, DKIM (Ed25519 + RSA), DMARC, MX for `whyrating.com` - -**Container Status:** Running - -### Gitea Users (for Outline OIDC) - -| Username | Password | Notes | -|----------|----------|-------| -| `nedas` | `NedasNUC2026!` | Regular user account | +### Gitea Users +| `nedas` | `NedasNUC2026!` | Regular user | ## Gitea-Coolify Integration (Git Auto-Deploy) -Deploy Next.js apps from self-hosted Gitea with auto-deploy on push. Full docs: `docs/gitea-coolify-auto-deploy.md` - -### ⚠️ CRITICAL: Gitea Webhook Allowed Hosts - -**Gitea blocks webhooks to internal hosts by default!** Before setting up webhooks, configure `ALLOWED_HOST_LIST` in Gitea's app.ini: - -```bash -# Edit Gitea config -ssh nuc "docker exec gitea-ho0cwgcwos88cwc48g84c0g8 vi /data/gitea/conf/app.ini" - -# Add/modify [webhook] section: -[webhook] -ALLOWED_HOST_LIST = coolify,10.0.1.5,192.168.1.3,localhost,host.docker.internal,external - -# Restart Gitea -ssh nuc "docker restart gitea-ho0cwgcwos88cwc48g84c0g8" -``` - -**Without this, webhooks fail with:** `webhook can only call allowed HTTP servers` +> Full docs: `docs/gitea-coolify-auto-deploy.md` ### Key References @@ -803,11 +402,17 @@ ssh nuc "docker restart gitea-ho0cwgcwos88cwc48g84c0g8" | **Gitea Container** | `gitea-ho0cwgcwos88cwc48g84c0g8` | | **Webhook Secret** | `9eb07a77964563378c5d66d99006e06ba3da39d232905d4b12554ff91ca39718` | -### ⚠️ CRITICAL: Webhook Secret Must Be Set in BOTH Places +### CRITICAL: Gitea Webhook Allowed Hosts -**Auto-deploy won't work unless the webhook secret is configured in BOTH Coolify AND Gitea!** +```bash +# Gitea's app.ini must have: +[webhook] +ALLOWED_HOST_LIST = coolify,10.0.1.5,192.168.1.3,localhost,host.docker.internal,external +``` -**Step 1: Set secret in Coolify** (via tinker or when creating app): +### CRITICAL: Webhook Secret Must Be Set in BOTH Places + +**Step 1 — Coolify:** ```bash ssh nuc "docker exec coolify php artisan tinker --execute=\" use App\\Models\\Application; @@ -817,158 +422,52 @@ use App\\Models\\Application; \"" ``` -**Step 2: Set secret in Gitea webhook**: -1. Go to repo → Settings → Webhooks → Add/Edit Webhook -2. Target URL: `http://coolify:8080/webhooks/source/gitea/events/manual?uuid=` -3. Secret: `9eb07a77964563378c5d66d99006e06ba3da39d232905d4b12554ff91ca39718` -4. Trigger: Push Events -5. Active: ✓ +**Step 2 — Gitea webhook:** +- URL: `http://coolify:8080/webhooks/source/gitea/events/manual?uuid=` (port **8080**, NOT 8000) +- Secret: `9eb07a77964563378c5d66d99006e06ba3da39d232905d4b12554ff91ca39718` +- Trigger: Push Events -**Common symptom when secret is missing:** Git pushes succeed but no deployment is triggered. Check: -```bash -# Verify Coolify has the secret -ssh nuc "docker exec coolify php artisan tinker --execute=\" -use App\\Models\\Application; -\\\$app = Application::where('uuid', '')->first(); -echo 'Secret: ' . (\\\$app->manual_webhook_secret_gitea ? 'SET' : 'MISSING'); -\"" -``` +### Webhook URLs -### Local Development (Clone & Push) - -SSH config is set up for direct git operations: - -```bash -# Clone a repo -git clone gitea:alezmad/nuc-portal.git - -# Push changes (triggers auto-deploy via webhook) -git push origin main -``` - -**SSH Host Config** (in `~/.ssh/config`): -``` -Host gitea - HostName 192.168.1.3 - Port 22222 - User git - IdentityFile ~/.ssh/id_ed25519_nuc -``` - -### Webhook URL Format (MUST include UUID and use port 8080!) - -``` -http://coolify:8080/webhooks/source/gitea/events/manual?uuid= -``` - -**⚠️ Port 8080 is required** - Coolify listens on 8080 internally (not 8000, which is the external mapping). - -| App | UUID | Webhook URL | -|-----|------|-------------| -| nuc-portal | `t80w0cw0oooc4g0soswos4so` | `...?uuid=t80w0cw0oooc4g0soswos4so` | -| whyrating-hub | `vw4ggc40socwkgwg4osc8wg8` | `...?uuid=vw4ggc40socwkgwg4osc8wg8` | -| whyrating-brand | `r80gk0ccgg0okos8cw848kkk` | `...?uuid=r80gk0ccgg0okos8cw848kkk` | -| whyrating-templates | `qw80g4sog0kk8cc4wkcs8sgc` | `...?uuid=qw80g4sog0kk8cc4wkcs8sgc` | +| App | UUID | +|-----|------| +| nuc-portal | `t80w0cw0oooc4g0soswos4so` | +| whyrating-hub | `vw4ggc40socwkgwg4osc8wg8` | +| whyrating-brand | `r80gk0ccgg0okos8cw848kkk` | +| whyrating-templates | `qw80g4sog0kk8cc4wkcs8sgc` | ### Quick Deploy (Next.js) ```python # 1. Create application with deploy key mcp__coolify__application( - action="create_key", - name="my-app", - project_uuid="a8484ggc88c40w4g4k004ow0", - environment_name="production", + action="create_key", name="my-app", + project_uuid="a8484ggc88c40w4g4k004ow0", environment_name="production", server_uuid="qk84w0goo4w48g4ggsoo0oss", git_repository="git@gitea-ho0cwgcwos88cwc48g84c0g8:alezmad/.git", - git_branch="main", - build_pack="nixpacks", - ports_exposes="3000", - private_key_uuid="akssgwowsccgwgoggs4ks8ck" -) + git_branch="main", build_pack="nixpacks", ports_exposes="3000", + private_key_uuid="akssgwowsccgwgoggs4ks8ck") -# 2. Set FQDN -ssh nuc "docker exec coolify php artisan tinker --execute=\" -use App\Models\Application; -\\\$app = Application::where('uuid', '')->first(); -\\\$app->fqdn = 'http://.nuc.lan'; -\\\$app->custom_labels = null; -\\\$app->save(); -\"" - -# 3. Set webhook secret -ssh nuc "docker exec coolify php artisan tinker --execute=\" -use App\Models\Application; -\\\$app = Application::where('uuid', '')->first(); -\\\$app->manual_webhook_secret_gitea = '9eb07a77964563378c5d66d99006e06ba3da39d232905d4b12554ff91ca39718'; -\\\$app->save(); -\"" - -# 4. Create webhook in Gitea (via browser or API): -# URL: http://coolify:8080/webhooks/source/gitea/events/manual?uuid= -# (Use port 8080, NOT 8000 - 8080 is the internal container port) -# Secret: 9eb07a77964563378c5d66d99006e06ba3da39d232905d4b12554ff91ca39718 -# Trigger: Push Events - -# 5. Deploy -mcp__coolify__deploy(tag_or_uuid="") +# 2. Set FQDN via tinker +# 3. Set webhook secret via tinker +# 4. Create Gitea webhook (URL with port 8080 + secret) +# 5. Deploy: mcp__coolify__deploy(tag_or_uuid="") ``` ### Deploy Key (add to each new repo) - ``` ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGHtsL3jicJTsBekYuwbKjO0EcRadYKhvLSUw/36XF7h coolify-gitea ``` +Add via Gitea: Repository → Settings → Deploy Keys → **Enable Write Access** -Add via Gitea: Repository → Settings → Deploy Keys → **Enable Write Access** ✓ - -### ✅ New Repo Auto-Deploy Checklist - -When creating a new repo that should auto-deploy: - -1. **[ ] Add deploy key to Gitea repo** - - Go to: `http://192.168.1.3:3030/alezmad//settings/keys` - - Add the deploy key above with **Write Access** enabled - -2. **[ ] Create Coolify application** (use `mcp__coolify__application` with `action="create_key"`) - -3. **[ ] Set FQDN** via tinker command - -4. **[ ] Set webhook secret** via tinker command (use shared secret above) - -5. **[ ] Create Gitea webhook** - - Go to: `http://192.168.1.3:3030/alezmad//settings/hooks` - - Add Webhook → Gitea - - **URL:** `http://coolify:8080/webhooks/source/gitea/events/manual?uuid=` - - **Secret:** `9eb07a77964563378c5d66d99006e06ba3da39d232905d4b12554ff91ca39718` - - **Trigger:** Push Events - - **Active:** ✓ - -6. **[ ] Test webhook** - Click "Test Delivery" and verify HTTP 200 response - -7. **[ ] Initial deploy** - `mcp__coolify__deploy(tag_or_uuid="")` - -**Common mistakes:** -- ❌ Using port 8000 instead of 8080 in webhook URL -- ❌ Forgetting `?uuid=` in webhook URL -- ❌ Not enabling Write Access on deploy key - -### Quick Verification Commands - -```bash -# Check Gitea has [webhook] section configured -ssh nuc "docker exec gitea-ho0cwgcwos88cwc48g84c0g8 cat /data/gitea/conf/app.ini | grep -A2 '\[webhook\]'" - -# Test Coolify is reachable from Gitea (should return HTML) -ssh nuc "docker exec gitea-ho0cwgcwos88cwc48g84c0g8 wget -qO- --timeout=5 http://coolify:8080/ | head -5" - -# Check Gitea is on coolify network -ssh nuc "docker inspect gitea-ho0cwgcwos88cwc48g84c0g8 --format '{{json .NetworkSettings.Networks}}' | jq -r 'keys[]'" -``` - -### Why NOT "Gitea Source" - -Coolify's "Gitea Source" uses GitHub App-style OAuth with JWT - **this doesn't work with Gitea**. Use deploy keys + manual webhooks instead. +### New Repo Auto-Deploy Checklist +1. [ ] Add deploy key to Gitea repo (Write Access enabled) +2. [ ] Create Coolify application (`action="create_key"`) +3. [ ] Set FQDN via tinker +4. [ ] Set webhook secret via tinker +5. [ ] Create Gitea webhook (port 8080 + UUID + secret) +6. [ ] Test webhook (HTTP 200) +7. [ ] Initial deploy ### Current Deployed Apps @@ -983,795 +482,98 @@ Coolify's "Gitea Source" uses GitHub App-style OAuth with JWT - **this doesn't w ### Turbostarter (Knosia) - Build & Deploy -Turbostarter is deployed as a **Coolify Service** (not a standalone app) with full docker-compose infrastructure: web + pgvector + minio. +Deployed as **Coolify Service** (web + pgvector + minio). Architecture: Tailscale Funnel (HTTPS) → Traefik (HTTP:80) → web container. -**Architecture:** Tailscale Funnel (HTTPS) → Traefik (HTTP:80) → web container - -**FQDN (Traefik):** `http://alezmad-nuc.tail58f5ad.ts.net` (HTTP internally — Tailscale handles TLS termination) - -**Build & Deploy workflow:** ```bash -# 1. Build image locally (ARM→AMD cross-compile) +# Build (ARM→AMD), push to Gitea registry, redeploy cd /Users/agutierrez/Desktop/turbostarter-export -docker build --platform linux/amd64 \ - --build-arg NEXT_PUBLIC_URL=https://alezmad-nuc.tail58f5ad.ts.net \ - -t 192.168.1.3:3030/alezmad/turbostarter:latest . - -# 2. Push to Gitea registry +docker build --platform linux/amd64 --build-arg NEXT_PUBLIC_URL=https://alezmad-nuc.tail58f5ad.ts.net -t 192.168.1.3:3030/alezmad/turbostarter:latest . docker push 192.168.1.3:3030/alezmad/turbostarter:latest - -# 3. Redeploy via Coolify (stop + start for full container recreation) mcp__coolify__control(resource="service", action="stop", uuid="v4gogwwc8wkk4888ksscc4k4") mcp__coolify__control(resource="service", action="start", uuid="v4gogwwc8wkk4888ksscc4k4") ``` -**Containers:** -| Container | Image | Purpose | -|-----------|-------|---------| -| `web-v4gogwwc8wkk4888ksscc4k4` | `localhost:3030/alezmad/turbostarter:latest` | Next.js app | -| `db-v4gogwwc8wkk4888ksscc4k4` | `pgvector/pgvector:pg17` | PostgreSQL + pgvector | -| `minio-v4gogwwc8wkk4888ksscc4k4` | `minio/minio:latest` | Object storage | -| `minio-init-v4gogwwc8wkk4888ksscc4k4` | `minio/mc:latest` | One-time bucket init | - -**Database access (via SSH tunnel):** -```bash -# Get DB container IP first -ssh nuc "docker inspect db-v4gogwwc8wkk4888ksscc4k4 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'" -# Then tunnel to IP (not container name) -ssh -L 5440::5432 nuc -# Connect: postgres://turbostarter:turbostarter@localhost:5440/core -``` - -**Seeded users:** `me+admin@turbostarter.dev` / `Pa$$w0rd` (admin), `me+user@turbostarter.dev` / `Pa$$w0rd` - -**Key env vars:** -- `BETTER_AUTH_TRUSTED_ORIGINS` — comma-separated list of allowed origins (CSRF protection) -- `NEXT_PUBLIC_URL` — build-time arg baked into Next.js static output (must rebuild to change) -- `DATABASE_URL` — internal docker network connection to pgvector +**DB access:** `ssh -L 5440::5432 nuc` → `postgres://turbostarter:turbostarter@localhost:5440/core` +**Seeded users:** `me+admin@turbostarter.dev` / `Pa$$w0rd`, `me+user@turbostarter.dev` / `Pa$$w0rd` ### New Site from nuc-portal Template -To create a new Next.js site using nuc-portal as a base: - ```bash -# 1. Copy and clean -cp -r /path/to/nuc-portal /path/to/new-site -cd /path/to/new-site +cp -r /path/to/nuc-portal /path/to/new-site && cd /path/to/new-site rm -rf .git .next node_modules - -# 2. Update package.json (name, description) -# 3. Customize src/app/page.tsx -# 4. Remove unused components if simplifying - -# 5. Initialize and push -npm install && npm run build # verify it builds +# Update package.json, customize src/app/page.tsx +npm install && npm run build git init && git add -A && git commit -m "Initial commit" -# Create repo in Gitea first, then: -git remote add origin gitea:alezmad/.git -git push -u origin main +git remote add origin gitea:alezmad/.git && git push -u origin main +``` +Then follow the Auto-Deploy Checklist above. + +### Local Git SSH Config +``` +Host gitea + HostName 192.168.1.3 + Port 22222 + User git + IdentityFile ~/.ssh/id_ed25519_nuc ``` -Then follow the "New Repo Auto-Deploy Checklist" above. +## Public Access & Security -## Public Access & Security Architecture +> Full architecture: `docs/architecture.md` -**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) +### Tailscale Funnel | 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"` | +| **Supported ports** | **443, 8443, 10000 ONLY** | -**⚠️ CRITICAL: Funnel only supports ports 443, 8443, and 10000.** Other ports will appear to work locally (`tailscale funnel status` shows them) but traffic will NOT route from the public internet. Always use one of these three ports. +**CRITICAL:** Always use `127.0.0.1`, NOT `localhost` in Funnel/Serve targets (IPv6 issue). -**⚠️ CRITICAL: Always use `127.0.0.1`, NOT `localhost`** in Funnel/Serve targets. On the NUC, `localhost` resolves to IPv6 (`::1`) but Docker containers bind to IPv4 only, causing connection resets. - -**Start Funnel for a service:** ```bash -# Expose port 3000 via Funnel (use 127.0.0.1, NOT localhost!) -ssh nuc "printf '\n' | sudo -S tailscale funnel --bg --https=8443 http://127.0.0.1:3000" +# Expose a service via Funnel +ssh nuc "tailscale funnel --bg --https=8443 http://127.0.0.1:3000" ``` **Current Funnel allocation:** - | Port | Service | Target | |------|---------|--------| | 443 | Traefik (main) | `http://192.168.1.3:80` | -| 8443 | Palmr (file sharing) | `http://127.0.0.1:3334` | -| 10000 | Palmr MinIO (uploads) | `http://127.0.0.1:9379` | - -### Current Domain Routes +| 8443 | Palmr | `http://127.0.0.1:3334` | +| 10000 | Palmr MinIO | `http://127.0.0.1:9379` | +### 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 +**NOT exposed to internet:** SSH, Coolify, databases, MinIO, Authentik, router admin. ## Artifacts Folder -**Location:** `.artifacts/` +**Location:** `.artifacts/` — stores credentials, configs, reports generated during sessions. -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:** `YYYY-MM-DD_HH-MM_.md` -### Naming Convention -Files must use datetime-prefixed names: -``` -YYYY-MM-DD_HH-MM_.md -``` +**Always save artifacts for:** API tokens, config changes, troubleshooting results, infrastructure changes, health reports. -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` +> Full conventions: see artifact template in this folder. -### When to Save Artifacts +## Detailed Documentation (split docs) -**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> -``` - -## Publishing JSX/React Artifacts Online - -Single-file React components (JSX) can be published as standalone web pages via the NUC's artifacts infrastructure. - -### How It Works - -``` -Public Internet → Tailscale Funnel (:443) → Traefik → artifacts-web (nginx) → /opt/artifacts/ -``` - -- **Funnel URL:** `https://alezmad-nuc.tail58f5ad.ts.net/artifacts/<name>/` -- **LAN URL:** `https://artifacts.nuc.lan/<name>/` -- **Nginx container:** `artifacts-web` (image: `nginx:alpine`, read-only mount of `/opt/artifacts`) -- **Traefik public route:** `Host(alezmad-nuc.tail58f5ad.ts.net) && PathPrefix(/artifacts)` → strips `/artifacts` → `artifacts-web:80` -- **Config file:** `/traefik/dynamic/nuc-services-public.yaml` (inside `coolify-proxy` container) - -### Quick Publish Steps - -```bash -# 1. Build self-contained HTML from JSX -# - Replace `import { useState, ... } from "react"` with `const { useState, ... } = React;` -# - Remove `export default ComponentName;` -# - Wrap in HTML with React 18 CDN + Babel standalone -# - Add `ReactDOM.createRoot(root).render(<Component />)` at the end - -# 2. Copy to NUC artifacts directory -ssh nuc "echo '7vXHpSTD.' | sudo -S mkdir -p /opt/artifacts/<name>" -scp /tmp/build/index.html nuc:~/tmp-artifact.html -ssh nuc "echo '7vXHpSTD.' | sudo -S mv ~/tmp-artifact.html /opt/artifacts/<name>/index.html" -ssh nuc "echo '7vXHpSTD.' | sudo -S chmod 644 /opt/artifacts/<name>/index.html" - -# 3. Done! No server restart needed — nginx serves it immediately. -``` - -### HTML Template for JSX Files - -```html -<!DOCTYPE html> -<html lang="es"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>TITLE - - - - - - - -
- - - -``` - -### Build Script (for large JSX files) - -```bash -# Automated: strips import/export, wraps in HTML -cat > /tmp/build.html << 'HEADER' - - - - - - -
- -FOOTER -``` - -### Currently Published - -| Path | Source | Public URL | -|------|--------|------------| -| `/opt/artifacts/checkin/` | `arrio/.scratch/checkin_demo_v1.jsx` | `https://alezmad-nuc.tail58f5ad.ts.net/artifacts/checkin/` | - -## 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 - -## Palmr (File Sharing - Dropbox Alternative) - -Self-hosted file sharing platform (like WeTransfer/Dropbox) for sending and receiving files. - -### Access - -| Property | Value | -|----------|-------| -| **Public URL** | `https://alezmad-nuc.tail58f5ad.ts.net:8443` | -| **Local URL** | `http://192.168.1.3:3334` | -| **Login page** | `/login` | -| **Container** | `palmr` | -| **Image** | `kyantech/palmr:latest` | -| **Network** | `coolify` (IP: `10.0.1.5`) | -| **Sudo pass** | `7vXHpSTD.` | - -### Users - -| Username | Email | Password | Admin | -|----------|-------|----------|-------| -| `alezmad` | `agutierrez@mineryreport.com` | *(set via UI)* | Yes | -| `michi` | `michi@nuc.lan` | `FlexiCar2025.` | No | - -### Architecture - -``` -External Browser → Funnel :8443 → 127.0.0.1:3334 → container:5487 (Next.js frontend) - Funnel :10000 → 127.0.0.1:9379 → container:9379 (MinIO uploads) - ↑ via port-fwd-palmr-minio -``` - -Palmr uses **presigned URLs** for file uploads — the browser uploads directly to MinIO, NOT through the backend. This means MinIO MUST be reachable from the client's browser. - -### Key Environment Variables - -| Variable | Value | Purpose | -|----------|-------|---------| -| `STORAGE_URL` | `https://alezmad-nuc.tail58f5ad.ts.net:10000` | Public MinIO URL for presigned upload URLs | -| `API_BASE_URL` | `http://127.0.0.1:3333` | Internal backend API (Next.js → Fastify) | - -### Volume Mounts - -| Volume | Mount Point | Contents | -|--------|-------------|----------| -| `5273abd0c...` (anonymous) | `/app/server` | SQLite DB, MinIO data, credentials | -| `palmr_uploads` | `/app/uploads` | Uploaded files | - -### Container Recreation - -```bash -ssh nuc "docker stop palmr && docker rm palmr && docker run -d \ - --name palmr \ - --network coolify \ - --restart unless-stopped \ - -p 3334:5487 \ - -v 5273abd0c536116056362397cdb568d2eab066b8289412dd91ecce58c174df68:/app/server \ - -v palmr_uploads:/app/uploads \ - -e STORAGE_URL=https://alezmad-nuc.tail58f5ad.ts.net:10000 \ - kyantech/palmr:latest" -``` - -### Port Forwarder (MinIO) - -MinIO runs inside the Palmr container on port 9379 (not exposed). A socat forwarder bridges it: - -```bash -ssh nuc "docker run -d --name port-fwd-palmr-minio --network coolify -p 9379:9379 \ - alpine/socat tcp-listen:9379,fork,reuseaddr tcp-connect:palmr:9379" -``` - -### Database Access - -Palmr uses SQLite at `/app/server/prisma/palmr.db`. Passwords are **bcrypt** hashed. - -```bash -# Copy DB locally for queries -ssh nuc "docker cp palmr:/app/server/prisma/palmr.db /tmp/palmr.db" -scp nuc:/tmp/palmr.db /tmp/palmr.db -sqlite3 /tmp/palmr.db "SELECT username, email, isAdmin FROM users;" - -# Generate bcrypt hash inside container -ssh nuc "docker exec palmr sh -c 'cd /app/palmr-app && node -e \"const bcrypt = require(\\\"bcryptjs\\\"); console.log(bcrypt.hashSync(\\\"PASSWORD\\\", 10));\"'" -``` - -### Troubleshooting - -1. **Upload stuck on loader**: Check `docker logs palmr` for `STORAGE_URL` errors. MinIO must be reachable from the client browser via the presigned URL. - -2. **Blank page externally**: Verify Funnel is on a supported port (443/8443/10000) and target uses `127.0.0.1` not `localhost`. - -3. **JWT token invalid after restart**: Expected — users must log in again after container recreation (JWT secret regenerated). - -4. **Config limits**: File size and storage limits are in `app_configs` table (values in bytes). Update via SQLite, copy DB back, restart. - -## MinIO Object Storage (Mac ↔ NUC File Transfer) - -### Architecture - -MinIO runs on the NUC as S3-compatible object storage. Used for Mac backups, file transfers, and app storage. - -``` -Mac (mc CLI) → Tailscale → 100.113.153.45:9000 → port-fwd (socat) → minio-xwowg8kswwsocssgocs8ss40:9000 -``` - -### Access - -| Property | Value | -|----------|-------| -| **API URL** | `http://100.113.153.45:9000` | -| **Console URL** | `http://100.113.153.45:9001` | -| **Access Key** | `minioadmin` | -| **Secret Key** | `minioadmin` | -| **Port Forwarder** | `minio-port-fwd` (socat, maps host :9000/:9001 → container) | -| **Main Container** | `minio-xwowg8kswwsocssgocs8ss40` | - -### Buckets - -| Bucket | Purpose | -|--------|---------| -| `mac-backups` | Database dumps, tar archives, important backups | -| `mac-downloads` | Files moved from ~/Downloads for archival | -| `mac-projects` | Archived project files | -| `nuc-portal-previews` | NUC Portal screenshot previews | -| `whyrating` | WhyRating app assets | - -### Mac CLI (mc) - -Installed at `/opt/homebrew/bin/mc`, alias `nuc` pre-configured. - -```bash -# List buckets -mc ls nuc/ - -# Upload file -mc cp file.tar nuc/mac-backups/ - -# Download file -mc cp nuc/mac-backups/file.tar ./ - -# Sync a directory -mc mirror ~/path/to/dir nuc/mac-projects/dirname/ - -# Check bucket size -mc du nuc/mac-backups -``` - -### nuc-sync CLI Tool - -Wrapper script at `~/.local/bin/nuc-sync` for daily operations: - -```bash -nuc-sync status # Check NUC + MinIO connectivity -nuc-sync upload file.tar # Upload to mac-backups bucket -nuc-sync upload file.sql mac-downloads # Upload to specific bucket -nuc-sync push ~/Desktop/project # rsync to NUC (excludes node_modules, .next, .git) -nuc-sync pull /opt/backups/mac/x . # rsync from NUC -nuc-sync list # List all buckets -nuc-sync list mac-backups # List bucket contents -``` - -### MinIO MCP Server - -MCP server for Claude Code integration: `minio-nuc` -- **Source:** `~/mcp-servers/minio-mcp-server/` -- **GitHub:** `https://github.com/Rafitis/minio-mcp-server` -- **Scope:** user (available in all projects) -- **Tools:** list buckets, upload/download objects, create/delete buckets - -### rsync Backup Directory - -```bash -# NUC backup path (owned by alezmad) -/opt/backups/mac/ -``` - -### Troubleshooting - -1. **MinIO unreachable (connection refused)**: The port forwarder may be down: - ```bash - ssh nuc "docker start minio-port-fwd" - ``` - -2. **mc alias not working**: Reconfigure: - ```bash - /opt/homebrew/bin/mc alias set nuc http://100.113.153.45:9000 minioadmin minioadmin - ``` - -3. **Console not loading**: Port 9001 needs the port-fwd container running. Check with: - ```bash - ssh nuc "docker ps | grep minio-port-fwd" - ``` - -## 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) - -## OpenClaw (AI Assistant Gateway) - -Self-hosted AI assistant gateway running on the NUC via Docker Compose. Connects to messaging platforms (WhatsApp, Telegram, Discord, etc.) and routes messages through Claude. - -### Access - -| Property | Value | -|----------|-------| -| **Control UI (HTTPS)** | `https://alezmad-nuc.tail58f5ad.ts.net:8443` | -| **Gateway WS** | `ws://192.168.1.3:18789` | -| **Gateway Token** | `3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee` | -| **Model** | `anthropic/claude-sonnet-4-5-20250929` | -| **Repo on NUC** | `~/openclaw/` | -| **Config** | `~/.openclaw/openclaw.json` | -| **Version** | `2026.2.10` | - -**⚠️ HTTPS Required:** The Control UI requires a secure context (HTTPS or localhost). Access via Tailscale Serve on port 8443. - -### Tailscale Serve (HTTPS access) - -The gateway is exposed via Tailscale Serve (not Funnel - tailnet only, not public): - -```bash -# Start HTTPS proxy (requires sudo, must run from NUC terminal) -ssh nuc -sudo tailscale serve --bg --https=8443 http://localhost:18789 -# Password: 7vXHpSTD -``` - -**⚠️ The `--bg` flag makes it persistent.** Without it, Ctrl+C stops the proxy. - -### Docker Compose Management - -```bash -# Start gateway -ssh nuc "cd ~/openclaw && docker compose up -d openclaw-gateway" - -# Restart gateway -ssh nuc "cd ~/openclaw && docker compose restart openclaw-gateway" - -# View logs -ssh nuc "docker logs openclaw-openclaw-gateway-1 2>&1 | tail -30" - -# Run CLI commands (use docker exec for commands that need gateway connection) -ssh nuc "docker exec openclaw-openclaw-gateway-1 node dist/index.js --token 3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee --url ws://127.0.0.1:18789" - -# Run CLI commands that don't need gateway (use docker compose run) -ssh nuc "cd ~/openclaw && script -qc 'docker compose run --rm openclaw-cli ' /dev/null" -``` - -### Device Pairing - -When the Control UI shows "pairing required", approve the pending device: - -```bash -# List pending devices -ssh nuc "docker exec openclaw-openclaw-gateway-1 node dist/index.js devices list --token 3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee --url ws://127.0.0.1:18789" - -# Approve a device (use requestId from the list) -ssh nuc "docker exec openclaw-openclaw-gateway-1 node dist/index.js devices approve --token 3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee --url ws://127.0.0.1:18789" -``` - -**Dashboard URL with embedded token (auto-authenticates):** -``` -https://alezmad-nuc.tail58f5ad.ts.net:8443/#token=3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee -``` - -### Channel Plugins - -Channels are plugins that must be enabled before use. 35 available, 4 loaded by default. - -```bash -# List all plugins -ssh nuc "script -qc 'cd ~/openclaw && docker compose run --rm openclaw-cli plugins list' /dev/null" - -# Enable a channel plugin -ssh nuc "script -qc 'cd ~/openclaw && docker compose run --rm openclaw-cli plugins enable ' /dev/null" - -# Then restart gateway -ssh nuc "cd ~/openclaw && docker compose restart openclaw-gateway" -``` - -**Currently enabled channels:** WhatsApp (linked and active) - -**Available channel plugins:** -| Plugin ID | Channel | -|-----------|---------| -| `whatsapp` | WhatsApp | -| `telegram` | Telegram | -| `discord` | Discord | -| `slack` | Slack | -| `signal` | Signal | -| `matrix` | Matrix | -| `msteams` | Microsoft Teams | -| `googlechat` | Google Chat | -| `imessage` | iMessage | -| `irc` | IRC | - -### Setting Up Channels That Require QR Codes (WhatsApp, etc.) - -**⚠️ The CLI needs a TTY for QR display.** Cannot run directly via `ssh nuc "command"`. Use `script` to fake a TTY and capture output: - -```bash -# Step 1: Enable the plugin -ssh nuc "script -qc 'cd ~/openclaw && docker compose run --rm openclaw-cli plugins enable whatsapp' /dev/null" - -# Step 2: Restart gateway -ssh nuc "cd ~/openclaw && docker compose restart openclaw-gateway" - -# Step 3: Run login with TTY capture (captures QR to file) -ssh nuc "script -q /tmp/openclaw-qr.txt -c 'cd ~/openclaw && docker compose run --rm openclaw-cli channels login'" - -# Step 4: If QR needs to be viewed remotely, copy and render as image -scp nuc:/tmp/openclaw-qr.txt /tmp/ -# Then use Python + Pillow to convert Unicode block chars to PNG -``` - -**QR to PNG conversion (run locally on Mac):** -```python -from PIL import Image -import re - -with open("/tmp/openclaw-qr.txt", "rb") as f: - content = f.read().decode("utf-8", errors="replace") - -lines = content.split("\n") -qr_lines = [] -for l in lines: - clean = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", l).replace(chr(0), "") - if any(c in clean for c in ["\u2584", "\u2588", "\u2580"]): - qr_lines.append(clean) - -scale = 10 -width = max(len(l) for l in qr_lines) -height = len(qr_lines) * 2 -img = Image.new("RGB", (width * scale, height * scale), "white") - -for row_idx, line in enumerate(qr_lines): - for col_idx, ch in enumerate(line): - top_black = ch in ["\u2588", "\u2580"] - bot_black = ch in ["\u2588", "\u2584"] - for dy in range(scale): - for dx in range(scale): - if top_black: - img.putpixel((col_idx*scale+dx, row_idx*2*scale+dy), (0,0,0)) - if bot_black: - img.putpixel((col_idx*scale+dx, (row_idx*2+1)*scale+dy), (0,0,0)) - -img.save("/tmp/qr.png") -``` - -**Alternative: User scans directly** — If the user has SSH terminal access, they can run `channels login` interactively and scan the QR from their terminal. - -### Anthropic Authentication - -Uses a Claude Code OAuth token (valid 1 year). Set in both config and docker-compose env: - -**Config (`~/.openclaw/openclaw.json`):** -```json -{ - "env": { - "ANTHROPIC_API_KEY": "sk-ant-oat01-..." - } -} -``` - -**Docker env (`~/openclaw/.env`):** -``` -ANTHROPIC_API_KEY=sk-ant-oat01-... -``` - -**To regenerate token (run on Mac where `claude` CLI is installed):** -```bash -claude setup-token -# Copy the output token and update both config + .env on NUC -``` - -### Config Schema (current) - -```json -{ - "agents": { - "defaults": { - "model": { - "primary": "anthropic/claude-sonnet-4-5-20250929" - } - } - }, - "gateway": { - "port": 18789, - "mode": "local", - "bind": "lan", - "auth": { - "mode": "token" - } - } -} -``` - -**⚠️ Config gotchas:** -- `agent.model` is **legacy** — use `agents.defaults.model.primary` instead -- Run `docker exec openclaw-openclaw-gateway-1 node dist/index.js doctor --fix` to migrate legacy keys -- The `gateway.pairing` key does NOT exist — device pairing is managed via the `devices` CLI, not config - -### Troubleshooting - -1. **"control ui requires HTTPS or localhost"**: Access via `https://alezmad-nuc.tail58f5ad.ts.net:8443` (Tailscale Serve), NOT `http://192.168.1.3:18789` - -2. **"pairing required"**: Approve the device via `devices approve` command (see Device Pairing section above) - -3. **"unauthorized: gateway token missing"**: Use the dashboard URL with `#token=...` hash to auto-authenticate - -4. **CLI commands fail with "gateway closed"**: Use `docker exec` into the running gateway container instead of `docker compose run` (the CLI container can't reach the gateway on its internal Docker IP) - -5. **Config "invalid" after edit**: Run `doctor --fix` inside the gateway container to clean up - -6. **Channel "unsupported"**: Enable the plugin first with `plugins enable `, then restart gateway - -7. **CLAUDE_AI_SESSION_KEY warnings**: Harmless — these are for Claude web session auth which isn't used when using API key +| Topic | File | +|-------|------| +| OpenClaw gateway | `docs/openclaw.md` | +| Palmr file sharing | `docs/palmr.md` | +| MinIO storage | `docs/minio.md` | +| Publishing JSX artifacts | `docs/publishing-artifacts.md` | +| MCP server configs | `docs/mcp-configs.md` | +| Gitea auto-deploy deep-dive | `docs/gitea-coolify-auto-deploy.md` | +| Security architecture | `docs/architecture.md` | +| MCP research guide | `docs/mcp-research-guide.md` | +| Turbostarter deployment | `docs/turbostarter-deployment.md` | diff --git a/docs/mcp-configs.md b/docs/mcp-configs.md new file mode 100644 index 0000000..a382fd5 --- /dev/null +++ b/docs/mcp-configs.md @@ -0,0 +1,180 @@ +# MCP Server Configurations + +Detailed setup and configuration guides for MCP servers used with the NUC. + +## Stalwart Mail MCP + +**Location:** `~/mcp-servers/stalwart-mail/` + +Manage the self-hosted Stalwart mail server via natural language. + +### Available Tools + +| Category | Tools | +|----------|-------| +| **Users** | `list_users`, `get_user`, `create_user`, `update_user_password`, `delete_user`, `add_email_alias` | +| **Domains** | `create_domain`, `generate_dkim` | +| **Queue** | `list_queue`, `get_queue_status`, `delete_queued_message`, `retry_queued_message` | +| **Monitoring** | `get_metrics`, `get_dmarc_reports`, `get_server_logs` | +| **DNS** | `check_dns_records`, `troubleshoot_delivery` | +| **Spam** | `train_spam`, `train_ham`, `update_spam_filter` | + +### Usage Examples +``` +"List all mail users" +"Create user sales with email sales@whyrating.com and password Secret123" +"Check the mail queue" +"Verify DNS records for whyrating.com" +"Show server metrics" +"Delete user john" +``` + +### Direct API Test (if MCP not responding) +```bash +curl -s -u "admin:QfKYjCJdxu" "http://192.168.1.3:8081/api/principal" | jq . +``` + +### Reconfigure MCP +```bash +claude mcp remove stalwart-mail +claude mcp add stalwart-mail \ + -e STALWART_URL=http://192.168.1.3:8081 \ + -e STALWART_USER=admin \ + -e STALWART_PASS=QfKYjCJdxu \ + --scope user \ + -- ~/mcp-servers/stalwart-mail/.venv/bin/python ~/mcp-servers/stalwart-mail/server.py +``` + +### SMTP Authentication Requirements + +1. **Password format:** Must be SHA-512 hashed (not plaintext). When creating users via API: + ```python + import crypt + hashed = crypt.crypt('password', crypt.mksalt(crypt.METHOD_SHA512)) + # Use hashed value in 'secrets' field + ``` +2. **SMTP login:** Use username only (e.g., `info`), NOT full email (`info@whyrating.com`) +3. **Port 465 (SMTPS):** Supports PLAIN/LOGIN auth with implicit TLS +4. **Port 587 (Submission):** Requires STARTTLS, only OAuth supported without TLS + +### Send email via Python (from NUC) +```python +import smtplib, ssl +from email.mime.text import MIMEText + +context = ssl.create_default_context() +context.check_hostname = False +context.verify_mode = ssl.CERT_NONE + +with smtplib.SMTP_SSL('localhost', 465, context=context) as server: + server.login('info', 'whyrating2026') # Username only! + server.sendmail('info@whyrating.com', 'recipient@example.com', msg.as_string()) +``` + +## Email Client MCP (Read/Send Emails) + +**Package:** [mcp-email-server](https://github.com/ai-zerolab/mcp-email-server) + +Read and send emails via IMAP/SMTP directly from Claude. + +**Configured for:** `info@whyrating.com` on Stalwart + +### Usage Examples +``` +"Check my inbox" +"Read the latest email" +"Send an email to john@example.com with subject Hello" +"Search emails from support@" +"List email folders" +``` + +### Reconfigure +```bash +claude mcp remove email-client +claude mcp add email-client \ + -e MCP_EMAIL_SERVER_EMAIL_ADDRESS=info@whyrating.com \ + -e MCP_EMAIL_SERVER_PASSWORD=whyrating2026 \ + -e MCP_EMAIL_SERVER_IMAP_HOST=192.168.1.3 \ + -e MCP_EMAIL_SERVER_IMAP_PORT=143 \ + -e MCP_EMAIL_SERVER_SMTP_HOST=192.168.1.3 \ + -e MCP_EMAIL_SERVER_SMTP_PORT=587 \ + -e MCP_EMAIL_SERVER_SMTP_VERIFY_SSL=false \ + -e MCP_EMAIL_SERVER_ENABLE_ATTACHMENT_DOWNLOAD=true \ + --scope user \ + -- uvx mcp-email-server@latest stdio +``` + +## Adding Remote MCP Servers (HTTP Transport) + +**Use `claude mcp add --transport http` for remote MCP endpoints** - this is the recommended method for services with native MCP support. + +```bash +# Basic syntax +claude mcp add --transport http --scope user --header "
" + +# Example: NocoDB MCP (globally available) +claude mcp add --transport http nocodb http://192.168.1.3:8084/mcp/ncnyir1cy6n9bf5p \ + --scope user \ + --header "xc-mcp-token: qjjAXRxuYzRtEn-cA4lbPFi5km_pojTX" +``` + +**Scope options:** +- `--scope user` - Available across all projects (stored in `~/.claude.json`) +- `--scope local` - Current project only (default) +- `--scope project` - Shared via `.mcp.json` (committed to repo) + +**Why CLI over JSON config:** +- JSON config with `mcp-remote` often fails to load tools +- CLI `--transport http` handles HTTP endpoints natively +- No need for `--allow-http` flag or other workarounds + +**Managing MCP servers:** +```bash +claude mcp list # List all configured servers +claude mcp get nocodb # Get details for specific server +claude mcp remove nocodb # Remove a server +/mcp # Check status in Claude Code +``` + +## 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) diff --git a/docs/minio.md b/docs/minio.md new file mode 100644 index 0000000..3164656 --- /dev/null +++ b/docs/minio.md @@ -0,0 +1,88 @@ +# MinIO Object Storage (Mac <-> NUC File Transfer) + +## Architecture + +MinIO runs on the NUC as S3-compatible object storage. Used for Mac backups, file transfers, and app storage. + +``` +Mac (mc CLI) → Tailscale → 100.113.153.45:9000 → port-fwd (socat) → minio-xwowg8kswwsocssgocs8ss40:9000 +``` + +## Access + +| Property | Value | +|----------|-------| +| **API URL** | `http://100.113.153.45:9000` | +| **Console URL** | `http://100.113.153.45:9001` | +| **Access Key** | `minioadmin` | +| **Secret Key** | `minioadmin` | +| **Port Forwarder** | `minio-port-fwd` (socat, maps host :9000/:9001 → container) | +| **Main Container** | `minio-xwowg8kswwsocssgocs8ss40` | + +## Buckets + +| Bucket | Purpose | +|--------|---------| +| `mac-backups` | Database dumps, tar archives, important backups | +| `mac-downloads` | Files moved from ~/Downloads for archival | +| `mac-projects` | Archived project files | +| `nuc-portal-previews` | NUC Portal screenshot previews | +| `whyrating` | WhyRating app assets | + +## Mac CLI (mc) + +Installed at `/opt/homebrew/bin/mc`, alias `nuc` pre-configured. + +```bash +mc ls nuc/ # List buckets +mc cp file.tar nuc/mac-backups/ # Upload file +mc cp nuc/mac-backups/file.tar ./ # Download file +mc mirror ~/path/to/dir nuc/mac-projects/dirname/ # Sync directory +mc du nuc/mac-backups # Check bucket size +``` + +## nuc-sync CLI Tool + +Wrapper script at `~/.local/bin/nuc-sync` for daily operations: + +```bash +nuc-sync status # Check NUC + MinIO connectivity +nuc-sync upload file.tar # Upload to mac-backups bucket +nuc-sync upload file.sql mac-downloads # Upload to specific bucket +nuc-sync push ~/Desktop/project # rsync to NUC (excludes node_modules, .next, .git) +nuc-sync pull /opt/backups/mac/x . # rsync from NUC +nuc-sync list # List all buckets +nuc-sync list mac-backups # List bucket contents +``` + +## MinIO MCP Server + +MCP server for Claude Code integration: `minio-nuc` +- **Source:** `~/mcp-servers/minio-mcp-server/` +- **GitHub:** `https://github.com/Rafitis/minio-mcp-server` +- **Scope:** user (available in all projects) +- **Tools:** list buckets, upload/download objects, create/delete buckets + +## rsync Backup Directory + +```bash +# NUC backup path (owned by alezmad) +/opt/backups/mac/ +``` + +## Troubleshooting + +1. **MinIO unreachable (connection refused)**: The port forwarder may be down: + ```bash + ssh nuc "docker start minio-port-fwd" + ``` + +2. **mc alias not working**: Reconfigure: + ```bash + /opt/homebrew/bin/mc alias set nuc http://100.113.153.45:9000 minioadmin minioadmin + ``` + +3. **Console not loading**: Port 9001 needs the port-fwd container running. Check with: + ```bash + ssh nuc "docker ps | grep minio-port-fwd" + ``` diff --git a/docs/openclaw.md b/docs/openclaw.md new file mode 100644 index 0000000..15115b1 --- /dev/null +++ b/docs/openclaw.md @@ -0,0 +1,217 @@ +# OpenClaw (AI Assistant Gateway) + +Self-hosted AI assistant gateway running on the NUC via Docker Compose. Connects to messaging platforms (WhatsApp, Telegram, Discord, etc.) and routes messages through Claude. + +## Access + +| Property | Value | +|----------|-------| +| **Control UI (HTTPS)** | `https://alezmad-nuc.tail58f5ad.ts.net:8443` | +| **Gateway WS** | `ws://192.168.1.3:18789` | +| **Gateway Token** | `3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee` | +| **Model** | `anthropic/claude-sonnet-4-5-20250929` | +| **Repo on NUC** | `~/openclaw/` | +| **Config** | `~/.openclaw/openclaw.json` | +| **Version** | `2026.2.10` | + +**HTTPS Required:** The Control UI requires a secure context (HTTPS or localhost). Access via Tailscale Serve on port 8443. + +## Tailscale Serve (HTTPS access) + +The gateway is exposed via Tailscale Serve (not Funnel - tailnet only, not public): + +```bash +# Start HTTPS proxy (requires sudo, must run from NUC terminal) +ssh nuc +sudo tailscale serve --bg --https=8443 http://localhost:18789 +# Password: 7vXHpSTD +``` + +**The `--bg` flag makes it persistent.** Without it, Ctrl+C stops the proxy. + +## Docker Compose Management + +```bash +# Start gateway +ssh nuc "cd ~/openclaw && docker compose up -d openclaw-gateway" + +# Restart gateway +ssh nuc "cd ~/openclaw && docker compose restart openclaw-gateway" + +# View logs +ssh nuc "docker logs openclaw-openclaw-gateway-1 2>&1 | tail -30" + +# Run CLI commands (use docker exec for commands that need gateway connection) +ssh nuc "docker exec openclaw-openclaw-gateway-1 node dist/index.js --token 3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee --url ws://127.0.0.1:18789" + +# Run CLI commands that don't need gateway (use docker compose run) +ssh nuc "cd ~/openclaw && script -qc 'docker compose run --rm openclaw-cli ' /dev/null" +``` + +## Device Pairing + +When the Control UI shows "pairing required", approve the pending device: + +```bash +# List pending devices +ssh nuc "docker exec openclaw-openclaw-gateway-1 node dist/index.js devices list --token 3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee --url ws://127.0.0.1:18789" + +# Approve a device (use requestId from the list) +ssh nuc "docker exec openclaw-openclaw-gateway-1 node dist/index.js devices approve --token 3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee --url ws://127.0.0.1:18789" +``` + +**Dashboard URL with embedded token (auto-authenticates):** +``` +https://alezmad-nuc.tail58f5ad.ts.net:8443/#token=3547c3f2b7b4a33eb077cf804bcca446057f81ba1578b2045dbb3aa4e04346ee +``` + +## Channel Plugins + +Channels are plugins that must be enabled before use. 35 available, 4 loaded by default. + +```bash +# List all plugins +ssh nuc "script -qc 'cd ~/openclaw && docker compose run --rm openclaw-cli plugins list' /dev/null" + +# Enable a channel plugin +ssh nuc "script -qc 'cd ~/openclaw && docker compose run --rm openclaw-cli plugins enable ' /dev/null" + +# Then restart gateway +ssh nuc "cd ~/openclaw && docker compose restart openclaw-gateway" +``` + +**Currently enabled channels:** WhatsApp (linked and active) + +**Available channel plugins:** +| Plugin ID | Channel | +|-----------|---------| +| `whatsapp` | WhatsApp | +| `telegram` | Telegram | +| `discord` | Discord | +| `slack` | Slack | +| `signal` | Signal | +| `matrix` | Matrix | +| `msteams` | Microsoft Teams | +| `googlechat` | Google Chat | +| `imessage` | iMessage | +| `irc` | IRC | + +## Setting Up Channels That Require QR Codes (WhatsApp, etc.) + +**The CLI needs a TTY for QR display.** Cannot run directly via `ssh nuc "command"`. Use `script` to fake a TTY and capture output: + +```bash +# Step 1: Enable the plugin +ssh nuc "script -qc 'cd ~/openclaw && docker compose run --rm openclaw-cli plugins enable whatsapp' /dev/null" + +# Step 2: Restart gateway +ssh nuc "cd ~/openclaw && docker compose restart openclaw-gateway" + +# Step 3: Run login with TTY capture (captures QR to file) +ssh nuc "script -q /tmp/openclaw-qr.txt -c 'cd ~/openclaw && docker compose run --rm openclaw-cli channels login'" + +# Step 4: If QR needs to be viewed remotely, copy and render as image +scp nuc:/tmp/openclaw-qr.txt /tmp/ +# Then use Python + Pillow to convert Unicode block chars to PNG +``` + +**QR to PNG conversion (run locally on Mac):** +```python +from PIL import Image +import re + +with open("/tmp/openclaw-qr.txt", "rb") as f: + content = f.read().decode("utf-8", errors="replace") + +lines = content.split("\n") +qr_lines = [] +for l in lines: + clean = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", l).replace(chr(0), "") + if any(c in clean for c in ["\u2584", "\u2588", "\u2580"]): + qr_lines.append(clean) + +scale = 10 +width = max(len(l) for l in qr_lines) +height = len(qr_lines) * 2 +img = Image.new("RGB", (width * scale, height * scale), "white") + +for row_idx, line in enumerate(qr_lines): + for col_idx, ch in enumerate(line): + top_black = ch in ["\u2588", "\u2580"] + bot_black = ch in ["\u2588", "\u2584"] + for dy in range(scale): + for dx in range(scale): + if top_black: + img.putpixel((col_idx*scale+dx, row_idx*2*scale+dy), (0,0,0)) + if bot_black: + img.putpixel((col_idx*scale+dx, (row_idx*2+1)*scale+dy), (0,0,0)) + +img.save("/tmp/qr.png") +``` + +## Anthropic Authentication + +Uses a Claude Code OAuth token (valid 1 year). Set in both config and docker-compose env: + +**Config (`~/.openclaw/openclaw.json`):** +```json +{ + "env": { + "ANTHROPIC_API_KEY": "sk-ant-oat01-..." + } +} +``` + +**Docker env (`~/openclaw/.env`):** +``` +ANTHROPIC_API_KEY=sk-ant-oat01-... +``` + +**To regenerate token (run on Mac where `claude` CLI is installed):** +```bash +claude setup-token +# Copy the output token and update both config + .env on NUC +``` + +## Config Schema (current) + +```json +{ + "agents": { + "defaults": { + "model": { + "primary": "anthropic/claude-sonnet-4-5-20250929" + } + } + }, + "gateway": { + "port": 18789, + "mode": "local", + "bind": "lan", + "auth": { + "mode": "token" + } + } +} +``` + +**Config gotchas:** +- `agent.model` is **legacy** — use `agents.defaults.model.primary` instead +- Run `docker exec openclaw-openclaw-gateway-1 node dist/index.js doctor --fix` to migrate legacy keys +- The `gateway.pairing` key does NOT exist — device pairing is managed via the `devices` CLI, not config + +## Troubleshooting + +1. **"control ui requires HTTPS or localhost"**: Access via `https://alezmad-nuc.tail58f5ad.ts.net:8443` (Tailscale Serve), NOT `http://192.168.1.3:18789` + +2. **"pairing required"**: Approve the device via `devices approve` command (see Device Pairing section above) + +3. **"unauthorized: gateway token missing"**: Use the dashboard URL with `#token=...` hash to auto-authenticate + +4. **CLI commands fail with "gateway closed"**: Use `docker exec` into the running gateway container instead of `docker compose run` (the CLI container can't reach the gateway on its internal Docker IP) + +5. **Config "invalid" after edit**: Run `doctor --fix` inside the gateway container to clean up + +6. **Channel "unsupported"**: Enable the plugin first with `plugins enable `, then restart gateway + +7. **CLAUDE_AI_SESSION_KEY warnings**: Harmless — these are for Claude web session auth which isn't used when using API key diff --git a/docs/palmr.md b/docs/palmr.md new file mode 100644 index 0000000..b4575d5 --- /dev/null +++ b/docs/palmr.md @@ -0,0 +1,92 @@ +# Palmr (File Sharing - Dropbox Alternative) + +Self-hosted file sharing platform (like WeTransfer/Dropbox) for sending and receiving files. + +## Access + +| Property | Value | +|----------|-------| +| **Public URL** | `https://alezmad-nuc.tail58f5ad.ts.net:8443` | +| **Local URL** | `http://192.168.1.3:3334` | +| **Login page** | `/login` | +| **Container** | `palmr` | +| **Image** | `kyantech/palmr:latest` | +| **Network** | `coolify` (IP: `10.0.1.5`) | + +## Users + +| Username | Email | Password | Admin | +|----------|-------|----------|-------| +| `alezmad` | `agutierrez@mineryreport.com` | *(set via UI)* | Yes | +| `michi` | `michi@nuc.lan` | `FlexiCar2025.` | No | + +## Architecture + +``` +External Browser → Funnel :8443 → 127.0.0.1:3334 → container:5487 (Next.js frontend) + Funnel :10000 → 127.0.0.1:9379 → container:9379 (MinIO uploads) + ↑ via port-fwd-palmr-minio +``` + +Palmr uses **presigned URLs** for file uploads — the browser uploads directly to MinIO, NOT through the backend. This means MinIO MUST be reachable from the client's browser. + +## Key Environment Variables + +| Variable | Value | Purpose | +|----------|-------|---------| +| `STORAGE_URL` | `https://alezmad-nuc.tail58f5ad.ts.net:10000` | Public MinIO URL for presigned upload URLs | +| `API_BASE_URL` | `http://127.0.0.1:3333` | Internal backend API (Next.js → Fastify) | + +## Volume Mounts + +| Volume | Mount Point | Contents | +|--------|-------------|----------| +| `5273abd0c...` (anonymous) | `/app/server` | SQLite DB, MinIO data, credentials | +| `palmr_uploads` | `/app/uploads` | Uploaded files | + +## Container Recreation + +```bash +ssh nuc "docker stop palmr && docker rm palmr && docker run -d \ + --name palmr \ + --network coolify \ + --restart unless-stopped \ + -p 3334:5487 \ + -v 5273abd0c536116056362397cdb568d2eab066b8289412dd91ecce58c174df68:/app/server \ + -v palmr_uploads:/app/uploads \ + -e STORAGE_URL=https://alezmad-nuc.tail58f5ad.ts.net:10000 \ + kyantech/palmr:latest" +``` + +## Port Forwarder (MinIO) + +MinIO runs inside the Palmr container on port 9379 (not exposed). A socat forwarder bridges it: + +```bash +ssh nuc "docker run -d --name port-fwd-palmr-minio --network coolify -p 9379:9379 \ + alpine/socat tcp-listen:9379,fork,reuseaddr tcp-connect:palmr:9379" +``` + +## Database Access + +Palmr uses SQLite at `/app/server/prisma/palmr.db`. Passwords are **bcrypt** hashed. + +```bash +# Copy DB locally for queries +ssh nuc "docker cp palmr:/app/server/prisma/palmr.db /tmp/palmr.db" +scp nuc:/tmp/palmr.db /tmp/palmr.db +sqlite3 /tmp/palmr.db "SELECT username, email, isAdmin FROM users;" + +# Generate bcrypt hash inside container +ssh nuc "docker exec palmr sh -c 'cd /app/palmr-app && node -e \"const bcrypt = require(\\\"bcryptjs\\\"); console.log(bcrypt.hashSync(\\\"PASSWORD\\\", 10));\"'" +``` + +## Troubleshooting + +1. **Upload stuck on loader**: Check `docker logs palmr` for `STORAGE_URL` errors. MinIO must be reachable from the client browser via the presigned URL. + +2. **Blank page externally**: Verify Funnel is on a supported port (443/8443/10000) and target uses `127.0.0.1` not `localhost`. + +3. **JWT token invalid after restart**: Expected — users must log in again after container recreation (JWT secret regenerated). + +4. **Config limits**: File size and storage limits are in `app_configs` table (values in bytes). Update via SQLite, copy DB back, restart. diff --git a/docs/publishing-artifacts.md b/docs/publishing-artifacts.md new file mode 100644 index 0000000..004612f --- /dev/null +++ b/docs/publishing-artifacts.md @@ -0,0 +1,99 @@ +# Publishing JSX/React Artifacts Online + +Single-file React components (JSX) can be published as standalone web pages via the NUC's artifacts infrastructure. + +## How It Works + +``` +Public Internet → Tailscale Funnel (:443) → Traefik → artifacts-web (nginx) → /opt/artifacts/ +``` + +- **Funnel URL:** `https://alezmad-nuc.tail58f5ad.ts.net/artifacts//` +- **LAN URL:** `https://artifacts.nuc.lan//` +- **Nginx container:** `artifacts-web` (image: `nginx:alpine`, read-only mount of `/opt/artifacts`) +- **Traefik public route:** `Host(alezmad-nuc.tail58f5ad.ts.net) && PathPrefix(/artifacts)` → strips `/artifacts` → `artifacts-web:80` +- **Config file:** `/traefik/dynamic/nuc-services-public.yaml` (inside `coolify-proxy` container) + +## Quick Publish Steps + +```bash +# 1. Build self-contained HTML from JSX +# - Replace `import { useState, ... } from "react"` with `const { useState, ... } = React;` +# - Remove `export default ComponentName;` +# - Wrap in HTML with React 18 CDN + Babel standalone +# - Add `ReactDOM.createRoot(root).render()` at the end + +# 2. Copy to NUC artifacts directory +ssh nuc "echo '7vXHpSTD.' | sudo -S mkdir -p /opt/artifacts/" +scp /tmp/build/index.html nuc:~/tmp-artifact.html +ssh nuc "echo '7vXHpSTD.' | sudo -S mv ~/tmp-artifact.html /opt/artifacts//index.html" +ssh nuc "echo '7vXHpSTD.' | sudo -S chmod 644 /opt/artifacts//index.html" + +# 3. Done! No server restart needed — nginx serves it immediately. +``` + +## HTML Template for JSX Files + +```html + + + + + + TITLE + + + + + + + +
+ + + +``` + +## Build Script (for large JSX files) + +```bash +# Automated: strips import/export, wraps in HTML +cat > /tmp/build.html << 'HEADER' + + + + + + +
+ +FOOTER +``` + +## Currently Published + +| Path | Source | Public URL | +|------|--------|------------| +| `/opt/artifacts/checkin/` | `arrio/.scratch/checkin_demo_v1.jsx` | `https://alezmad-nuc.tail58f5ad.ts.net/artifacts/checkin/` |