Slim CLAUDE.md from 65K to 21K by splitting app-specific docs
Move OpenClaw, Palmr, MinIO, JSX publishing, MCP configs, and migration candidates into dedicated docs/ files. Keep only DevOps-essential content inline (deployment rules, DNS, router, credentials, troubleshooting). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
64
.artifacts/migration-candidates.md
Normal file
64
.artifacts/migration-candidates.md
Normal file
@@ -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)
|
||||
180
docs/mcp-configs.md
Normal file
180
docs/mcp-configs.md
Normal file
@@ -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 <name> <url> --scope user --header "<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)
|
||||
88
docs/minio.md
Normal file
88
docs/minio.md
Normal file
@@ -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"
|
||||
```
|
||||
217
docs/openclaw.md
Normal file
217
docs/openclaw.md
Normal file
@@ -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 <command> --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 <command>' /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 <requestId> --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 <plugin-id>' /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 <id>`, 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
|
||||
92
docs/palmr.md
Normal file
92
docs/palmr.md
Normal file
@@ -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.
|
||||
99
docs/publishing-artifacts.md
Normal file
99
docs/publishing-artifacts.md
Normal file
@@ -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/<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</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #08090d; overflow-x: hidden; }
|
||||
/* Add any @keyframes or global styles here */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="text/babel">
|
||||
const { useState, useEffect, useRef, useCallback } = React;
|
||||
|
||||
// ... paste JSX component code here (without import/export lines) ...
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(React.createElement(ComponentName));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Build Script (for large JSX files)
|
||||
|
||||
```bash
|
||||
# Automated: strips import/export, wraps in HTML
|
||||
cat > /tmp/build.html << 'HEADER'
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
</head><body><div id="root"></div>
|
||||
<script type="text/babel">
|
||||
const { useState, useEffect, useRef, useCallback, useMemo, useReducer } = React;
|
||||
HEADER
|
||||
|
||||
# Strip first line (import) and last line (export), append body
|
||||
sed -n '2,$p' source.jsx | sed '$d' >> /tmp/build.html
|
||||
|
||||
# Add render footer (replace COMPONENT_NAME)
|
||||
cat >> /tmp/build.html << 'FOOTER'
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(React.createElement(COMPONENT_NAME));
|
||||
</script></body></html>
|
||||
FOOTER
|
||||
```
|
||||
|
||||
## Currently Published
|
||||
|
||||
| Path | Source | Public URL |
|
||||
|------|--------|------------|
|
||||
| `/opt/artifacts/checkin/` | `arrio/.scratch/checkin_demo_v1.jsx` | `https://alezmad-nuc.tail58f5ad.ts.net/artifacts/checkin/` |
|
||||
Reference in New Issue
Block a user